Export perspective as Markdown

Hi,

Can anyone hep me find out if it’s possible using omni-automation to export a perspective content to an email?

And if yes, can I have projects title or groups formatted like Markdown and not plain text?

Thanks!

1 Like

Does it have to be Omni Automation? And which platform are you doing this on?

I ask because I know there is some reading the Omnifocus database you can do in iOS/iPadOS Shortcuts. (I also note you can’t do it via x-callback-url - unless I’m missing something (and I tried only yesterday).)

It can be with Shortcuts. But can you help me figure it out? Thanks!

https://omni-automation.com/omnifocus/perspective.html#OF0145

var customPerspective = Perspective.Custom.byName("Morning Meds")
var pName = customPerspective.name
var wrapper = customPerspective.fileWrapper()
var email = new Email()
email.subject = pName + " Perspective"
email.body = "Here is a copy of my OmniFocus perspective: “" + pName + "”\n\n"
email.fileWrappers = [wrapper]
email.generate()

The exported perspective will be a bundle containing an XMLfile, whose name can be derived like this:

var customPerspective = Perspective.Custom.byName("Morning Meds")
var pName = customPerspective.name
var wrapper = customPerspective.fileWrapper()
console.log(wrapper.children[0].preferredFilename)

//–> Info-v3.plist

I wonder if the OP was thinking of exporting data rather than settings ?

2 Likes

Thank you all!

Yes, I was talking about exporting data and for instance have the projects, tags or groups indented like markdown.

Thanks for trying help!

On the Shortcuts front, OmniFocus donates a “find items” action. In my case I have it set to remaining items that meet a criterion.

Then I iterate through the list of items it produces.

Thanks Martin!

Yes I know but if I have groups (and only not actions) I get a plain text list and have to manually separate the groups and the actions… Don’t know if I’m making myself clear!?

1 Like

I think what @Sal would need to show is:

  • The infoV3.plist written to file rather than email,
  • with the values of its
    • filterRules string,
    • viewState dictionary,
    • and perhaps useSaved :: {Columns, Expansion, Focus} booleans

all:

  1. parsed out, and
  2. mapped to a corresponding MD-formatted query of the OmniFocus database.

Wow…

Don’t know even were to start!?

Well, once @Sal has shown syntax for:

  • writing infoV3.plist to disk,
  • reading out its filterRules string,
  • and parsing that as JSON

you will have a list of key-value records with names and values like, for example, these:

[{
    "actionStatus": "due"
}, {
    "actionStatus": "flagged"
}, {
    "actionAvailability": "available"
}, {
    "actionAvailability": "firstAvailable"
}, {
    "actionAvailability": "remaining"
}, {
    "actionAvailability": "completed"
}, {
    "actionAvailability": "dropped"
}, {
    "actionHasDueDate": true
}, {
    "actionHasDeferDate": true
}, {
    "actionHasDuration": true
}]

and the next thing to show would be a corresponding query written in terms of the omniJS interface, and interpreting the list in terms either of the JS Array .some or JS Array .every methods (i.e. OR or AND sequencing of the query clauses)

1 Like

To get the items from a perspective, you can tell a window to display that perspective and then look through that window’s content. Here’s some example code:

// Generate TaskPaper for all items in the Review and Changed perspectives

// Putting everything in an `async` block isn't strictly necessary, but has two convenient outcomes for this example code:
// 1. Putting everything in a block means we don't pollute our global name space (making it easy to edit the constants declared in this code without having to close and reopen our main document).
// 2. Making that block asynchronous makes it easy to use `await` for promised results like the commented-out example code which creates a new window rather than reusing the current window.

(async () => {

    // This is a simple function which produces simplified TaskPaper containing the name of a TreeNode's object.
    const taskPaperForObject = (object) => {
        const objectName = object.name;
        if (!objectName) {
            return `- [Grouping Header: ${object}]`;
        }
        return `- ${objectName}`;
    }

    // This function takes a perspective and target window and produces TaskPaper content generated by calling `taskPaperForObject` for each visible object in that perspective.
    const taskPaperForPerspectiveUsingWindow = (perspective, perspectiveWindow) => {
        perspectiveWindow.perspective = perspective;
        const content = perspectiveWindow.content;
        var results = [];
        content.rootNode.apply((node) => {
            if (node.level >= 1) { // Don't include the empty root node
                const taskText = taskPaperForObject(node.object);
                if (taskText) {
                    const indentedText = '  '.repeat(node.level - 1) + taskText;
                    results.push(indentedText);
                }
            }
        });
        const result = results.join('\n');
        return result;
    }

    // This is an example of using the above function to produce TaskPaper output for all visible items in the built-in `Review` and custom `Changed` perspectives.
    try {
        // const perspectiveWindow = await document.newWindow(); // Create a new window
        const perspectiveWindow = document.windows[0]; // Reuse the first window
        const reviewTaskPaper = await taskPaperForPerspectiveUsingWindow(Perspective.BuiltIn.Review, perspectiveWindow);
        const changedTaskPaper = await taskPaperForPerspectiveUsingWindow(Perspective.Custom.byName("Changed"), perspectiveWindow);
        console.log(`Review:\n${reviewTaskPaper}`);
        console.log(`Changed:\n${changedTaskPaper}`);
    } catch (err) {
        // The above code shouldn't have any errors as currently written, but while I was building it I certainly made some and I wouldn't be surprised if you encounter some while making changes to it!
        console.log(`DEBUG: catch error ${err}`);
    }

})(); // This invokes our asynchronous block and returns a `Promise`---which in this case may have already produced a result.
1 Like

Hi Ken,

Thank you so much and really sorry about my question… but how do I run this example code you sended???

Thanks!

Everytime I try to use some part of the code I get an error:

SyntaxError: Unexpected identifier ‘taskPaperForPerspectiveUsingWindow’. Expected ‘;’ after variable declaration. undefined:4:0