How can I insert an item at the cursor in a script?

I’m writing a script to insert some items into my TaskPaper document. Ideally, the items would be inserted at the cursor, but I haven’t been able to achieve this. I can only successfully add items to outline.root but that’s not perfect. I tried editor.selection, editor.selection.firstItem and editor.selection.parent, but appending items to any of these fails silently.

It would probably be helpful for us to to see the code in which you are framing that.

When you say "AppleScript" presumably you are submitting some JavaScript to the evaluate method of a document ?

That’s right, my mistake. Updated the title.

Since you asked, I’ve included the code here, but I’m not sure the details of the code will help much. Where I’m stuck is understanding how to append items at the cursor, instead of at the root or at an item selected using a search. The line let current = outline.root is what needs to be adjusted. The rest of the script works fine.

function extractSafariTabs() {
  const safari = Application("Safari");
  const window = safari.windows[0];

  return window.tabs().map(tab => 
  	Object({name: tab.name(), url: tab.url()})
  );
}

function saveTabs(editor, options) {
  'use strict';

  const safariTabs = options.tabList;
  const outline = editor.outline;

  outline.groupUndoAndChanges(function() {
  	let current = outline.root;
  
	let items = [];
   	for( let tabEntry of safariTabs ) {
		const entry = editor.outline.createItem(`- ${tabEntry.name}`);
		const urlNote = editor.outline.createItem(tabEntry.url);
		entry.appendChildren([urlNote]);
	
		items.push(entry);
	}

	current.appendChildren(items);
	editor.setCollapsed(items);
  })

  return true;
}

const docs = Application("TaskPaper").documents;
const todayDoc = docs[0];

todayDoc.evaluate({
  script: saveTabs.toString(),
  withOptions: { 
    tabList: extractSafariTabs(), 
  }
});

@batkins In case you haven’t found yet here’s scripting API:

https://www.taskpaper.com/guide/reference/scripting/

You were close with firstItem. I think you want to replace the problem line with:

let current = editor.selection.startItem

Perfect - that worked! Thanks, @jessegrosjean

Jesse beat me to it :slight_smile:

Here’s a draft which adds the urls as child nodes of the tab names.

(() => {
    "use strict";

    // Rough Draft – Rob Trew @2021

    // main :: IO ()
    const main = () => {
        const docs = Application("TaskPaper").documents;

        return 0 < docs.length ? (
            docs[0].evaluate({
                script: `${TaskPaperContext}`,
                withOptions: {
                    tabList: safariTabsNameAndURL()
                }
            })
        ) : "No documents open in TaskPaper";
    };

    // safariTabsNameAndURL :: IO () ->
    // [{name::String, url::String}]
    const safariTabsNameAndURL = () =>
        Application("Safari")
        .windows[0]
        .tabs().map(tab =>
            Object({
                name: tab.name(),
                url: tab.url()
            })
        );

    // TaskPaperContext :: Editor -> Dict -> IO String
    const TaskPaperContext = (editor, options) => {
        const
            outline = editor.outline,
            safariTabs = options.tabList;

        editor.selection.endItem.appendChildren(
            safariTabs.map(dict => {
                const
                    parent = outline.createItem(dict.name);

                return (
                    parent.appendChildren(
                        outline.createItem(dict.url)
                    ),
                    parent
                );
            })
        );

        return `${safariTabs.length} tabs added.`;
    };

    // MAIN ---
    return main();
})();

Phew, about time I answer one of these questions before you :slight_smile: Of course your answer is better, but sometimes you have to just celebrate being first!!!

Yours is much more informative. I was just being verbose : -)

@complexpoint Does TaskPaper make use of the strings returned from your JavaScript functions? Or is that just for debugging in Script Editor?

That’s right – I just find it speeds up writing and refactoring if every function returns an observable value.

When the top level function is embedded in a KM macro, I often return the text to a notification, or to an alert, if something unexpected seems to have happened – i.e. if the value has come back through a Left channel rather than a Right channel.

as in:

    // Left :: a -> Either a b
    const Left = x => ({
        type: "Either",
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: "Either",
        Right: x
    });

    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => m.Left ? (
            m
        ) : mf(m.Right);

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
    // Application of the function fl to the
    // contents of any Left value in e, or
    // the application of fr to its Right value.
        fr => e => e.Left ? (
            fl(e.Left)
        ) : fr(e.Right);
2 Likes