Help with moving my last AppleScript to JavaScript (moving projects to another TaskPaper document)


#1

Hello all (or at least @jessegrosjean and @complexpoint), :slight_smile:

I’m finally down to my last script that needs to be updated from TP2 (AppleScript) to TP3 (JavaScript)!

The script takes the current selection from the active document and moves it to another TaskPaper document (in my case, a incubate/someday/maybe list).

The AppleScript:

property archiveFile : "Incubate.taskpaper"
property archiveFilePath : "/Users/jim/Dropbox/Text/-Tasks-"

tell application "Finder"
	if exists (archiveFilePath & "/" & archiveFile) as POSIX file then
		tell application "TaskPaper"
			set currentFile to name of front document
			open archiveFilePath & "/" & archiveFile
			move selected entries of document named currentFile to beginning of entries of document named archiveFile
			save document named archiveFile
			--			close document named archiveFile
		end tell
	end if
end tell

Thanks yet again for any help!

Jim


#2

Have you given this script a look? Maybe you can get it to work just like you want!

http://support.hogbaysoftware.com/t/move-selected-taskpaper-items-to-chosen-project-km-macro?source_topic_id=1887

#3

Thanks for the suggestion. I have seen that script, and I have no idea how to modify it to move the items to another document (as it is coded, it moves the items to the top of the chosen project).


#4

Well, three stages:

  1. Get the text of the selected items in the active document
  2. Put a copy at the top of another file
  3. Delete the copy in the active document

Simpler is usually better (a short-cut for copy-paste to a second file is not a moon launch :slight_smile: but anything with a deletion stage probably needs a little bit of care and cautious testing, and perhaps it’s a way of looking at some TaskPaper scripting basics, so:

Getting the text of the selected items.

We could do this with the clipboard, but TaskPaper offers richer tools, so why not use them – the selection capture (gathering both the text and the unique ids of the selected lines) might look like this:

    // SELECTED  items
    function readSeln(editor) {
        var lstSeln = editor
            .selection
            .selectedItems;

        return {
            ids: lstSeln.map(function (x) {
                return x.id;
            }),
            
            text: ItemSerializer.serializeItems(
                lstSeln,
                editor.outline,
                ItemSerializer.TEXTMimeType
            )
        };
    }

Putting a copy at the top of another TaskPaper 3 document

Again, we could do this with paste, or simply using Bash to prepend text to the text file, but the TaskPaper API also lets us do something more interesting in the target document:

    // Make COPY of items in other doc
    function addCopyOfSeln(editor, options) {
        var outline = editor.outline,
            lstItems = ItemSerializer.deserializeItems(
                options.lines,
                outline,
                ItemSerializer.TEXTMimeType
            );

        outline.groupUndoAndChanges(function () {
            var root = outline.root;

            root.insertChildrenBefore(
                lstItems, root.firstChild
            );
        });

        return lstItems[0].parent !== undefined;
    }

Deleting the original lines from the active file

Assuming that the selection is still active, we could just use a delete keystroke now, but TaskPaper lets us use the unique ids of the copied lines, which gives a bit more confidence that we are likely to be deleting the right material :slight_smile:

(Using .groupUndoAndChanges() lets us wind the deletions back with a single Undo (⌘Z) if we have second thoughts after running the script)

    // CLEAR original items
    function clearSeln(editor, options) {
        var lstID = options.ids;

        if (lstID && lstID.length) {
            var outline = editor.outline;

            outline.groupUndoAndChanges(function () {
                outline.removeItems(
                    lstID.map(function (strID) {
                        return outline.getItemForID(strID);
                    })
                )
            });

            return true;
        }
    }

We’ll then need to pull out a few generic JavaScript for Automation functions for finding the specified target file, and perhaps creating it if it doesn’t yet exist:

    // expandTilde :: String -> FilePath
    function expandTilde(strPath) {
        return strPath.charCodeAt(0) === 126 ? ObjC.unwrap(
            $(strPath)
            .stringByExpandingTildeInPath
        ) : strPath;
    }

    // fileExists :: String -> Bool
    function fileExists(strPath) {
        var fm = $.NSFileManager.defaultManager,
            error = $();

        fm.attributesOfItemAtPathError(
            strPath,
            error
        );
        return error.code === undefined;
    }

    //writeFile :: FilePath -> String -> IO ()
    function writeFile(strPath, strText) {
        $.NSString.alloc.initWithUTF8String(strText)
            .writeToFileAtomicallyEncodingError(
                strPath, false,
                $.NSUTF8StringEncoding, null
            );
    }

And we might glue the pieces together into something like this, in which the path of the other file is specified in the targetDocPath option at the end of the script. (If no file exists at that path, the script will create one, so do check for typos in the path you give …)

Just a draft, so test it carefully before you entrust your work to it !

// Move selected TaskPaper 3 items to start of another TaskPaper 3 document

// 0.03

(function (dctOptions) {
    'use strict';

    // TASKPAPER CONTEXT


    // SELECTED  items
    function readSeln(editor) {
        var lstSeln = editor
            .selection
            .selectedItems;

        return {
            ids: lstSeln.map(function (x) {
                return x.id;
            }),

            text: ItemSerializer.serializeItems(
                lstSeln,
                editor.outline,
                ItemSerializer.TEXTMimeType
            )
        };
    }


    // Make COPY of items in other doc
    function addCopyOfSeln(editor, options) {
        var outline = editor.outline,
            lstItems = ItemSerializer.deserializeItems(
                options.lines,
                outline,
                ItemSerializer.TEXTMimeType
            );

        outline.groupUndoAndChanges(function () {
            var root = outline.root;

            root.insertChildrenBefore(
                lstItems, root.firstChild
            );
        });

        return lstItems[0].parent !== undefined;
    }


    // CLEARED original items
    function clearSeln(editor, options) {
        var lstID = options.ids;

        if (lstID && lstID.length) {
            var outline = editor.outline;

            outline.groupUndoAndChanges(function () {
                outline.removeItems(
                    lstID.map(function (strID) {
                        return outline.getItemForID(strID);
                    })
                )
            });

            return true;
        }
    }


    // JAVASCRIPT FOR AUTOMATION CONTEXT


    // Generic file system functions:

    // expandTilde :: String -> FilePath
    function expandTilde(strPath) {
        return strPath.charCodeAt(0) === 126 ? ObjC.unwrap(
            $(strPath)
            .stringByExpandingTildeInPath
        ) : strPath;
    }

    // fileExists :: String -> Bool
    function fileExists(strPath) {
        var fm = $.NSFileManager.defaultManager,
            error = $();

        fm.attributesOfItemAtPathError(
            strPath,
            error
        );
        return error.code === undefined;
    }

    //writeFile :: FilePath -> String -> IO ()
    function writeFile(strPath, strText) {
        $.NSString.alloc.initWithUTF8String(strText)
            .writeToFileAtomicallyEncodingError(
                strPath, true,
                $.NSUTF8StringEncoding, null
            );
    }

    // MAIN 

    var tp3 = Application("com.hogbaysoftware.TaskPaper3"),
        ds = tp3.documents,
        docSource = ds.length ? ds[0] : undefined;


    // If a document is open in TaskPaper 3
    if (docSource) {

        // 1. Anything selected ? (text and ids) ?
        var strSourceDocName = docSource.name(),
            dctItems = docSource.evaluate({
                script: readSeln.toString(),
                withOptions: dctOptions
            }),
            lstID = dctItems.ids;

        if (lstID && lstID.length) {

            // 2. Placed in target document ?
            var strFullPath = expandTilde(dctOptions.targetDocPath),
                dctTarget = (function () {
                    var lstPaths = ds.file()
                        .map(function (path) {
                            return path && path.toString();
                        }),
                        iIndex = lstPaths.indexOf(strFullPath),
                        blnOpen = iIndex !== -1;

                    return {
                        doc: blnOpen ? ds[iIndex] : (
                            fileExists(strFullPath) || writeFile(
                                strFullPath, '\n'
                            ),
                            tp3.open(strFullPath)
                        ),
                        wasOpen: blnOpen
                    }
                })(),
                docTarget = dctTarget.doc,
                blnWasOpen = dctTarget.wasOpen;


            // If we have found or created a target document,
            // Copy the text of the items to it
            if (docTarget) {
                var blnPlaced = docTarget.evaluate({
                    script: addCopyOfSeln.toString(),
                    withOptions: {
                        lines: dctItems.text
                    }
                });

                // 3. Removed from source document ?
                if (blnPlaced) {
                    var docMain = ds.byName(strSourceDocName);

                    docMain.evaluate({
                        script: clearSeln.toString(),
                        withOptions: {
                            ids: lstID
                        }
                    })

                    if (dctOptions.reClose && !dctTarget.wasOpen) {
                        dctTarget.doc.close({
                            saving: 'yes'
                        });
                    } else dctTarget.doc.save();

                    var a = Application.currentApplication(),
                        sa = (a.includeStandardAdditions = true, a),
                        intItems = lstID.length;

                    a.displayNotification(dctItems.text, {
                        withTitle: intItems + ' item' + (
                            intItems > 1 ? 's' : ''
                        ) + ' moved to',
                        subtitle: dctOptions.targetDocPath
                    });
                }
            }
        }
    }

})({
    targetDocPath: '~/Desktop/Incubate.taskpaper',
    reClose: false
});

#5

Here’s some API that might make this easier in the future:

  1. Outline.getOutlines() returns list of all open outlines.

  2. outline.getPath() return the file path associated with outline.

That way you can easily access multiple outlines (and identify them by path) within a single TaskPaper script context. I’m documenting these as public going forward.


Multi-outline scripting – a couple of questions
#6

Thanks @complexpoint! Initial tests are working great!

One question—I notice that if I have the destination document open, it gets closed. Is there a way to keep it open?


#7

Yes I wondered about that - should probably be an option, but it looked as if the original script was doing that.

For a quick fix, find these lines:

                    // Close the target document ?
                    docTarget.close({
                        saving: 'yes'
                    });

and comment them out with preceding // JavaScript comments

// Close the target document ?
// docTarget.close({
//     saving: 'yes'
//});

#8

Fantastic—thanks again!


#9

FWIW ver 0.3 above now makes ‘reClosing’ the target file (if the script found it initially closed), an option.

If .reClose=true at the bottom of the script, it saves and closes the target file after placing the selected lines.

If .reClose=false, it saves the updated target doc, but leaves it open


#10

Thanks for the update!