Auto completion for tag values

To generalize tag-value cycling a bit, we could specify:

  • one or more tag names, and
  • a list of values for each tag name

in the withOptions key-value dictionary passed to document.evaluate

in this kind of pattern:

e.g. for @place or @grade or @sprint

docs.at(0).evaluate({
    script: `${TaskPaperContext}`,
    withOptions: {
        "place": [
            "Berlin", "Dublin",
            "London", "Paris"
        ],
        "grade": "ABCD",
        "sprint": [10, 25, 55]
    }
})

Generalized tag-value cycling:

Expand disclosure triangle to view JS Source
/* eslint-disable max-lines-per-function */
(() => {
    "use strict";

    // Cycle tag value of:
    // - First tag in selection(s) which matches a key in
    //   options,
    // - to the next value in the list given in options
    //   for that key.

    // Rob Trew @2021

    // Ver 0.05
    // (Where a line has more than one of the tags with
    //  cycle lists given in options, the *leftmost* of
    //  these tags in the selected line will be cycled)

    // ------------------- JXA CONTEXT -------------------

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

        return 0 < docs.length ? (
            docs.at(0).evaluate({
                script: `${TaskPaperContext}`,
                withOptions: {
                    "place": [
                        "Berlin", "Dublin",
                        "London", "Paris"
                    ],
                    "grade": "ABCD",
                    "sprint": [10, 25, 55]
                }
            })
        ) : "No documents open in TaskPaper.";
    };

    // ---------------- TASKPAPER CONTEXT ----------------

    const TaskPaperContext = (editor, options) => {
        const tp3Main = () => {
            const
                selection = editor.selection,
                items = selection.selectedItems,
                startLocn = selection.location,
                endItem = selection.endItem,
                endOffset = selection.endOffset,
                cyclableNames = Object.keys(options || {})
                .map(k => `data-${k}`);

            let report = "";

            return 0 < items.length ? (
                0 < cyclableNames.length ? (
                    editor.outline.groupUndoAndChanges(() => {
                        report = items.filter(
                                x => Boolean(
                                    x.bodyString.trim()
                                )
                            )
                            .map(valueCycled(cyclableNames))
                            .join("\n");
                    }),
                    editor.moveSelectionToRange(
                        startLocn,
                        editor.getLocationForItemOffset(
                            endItem, endOffset
                        )
                    ),
                    report
                ) : "No name list pairs supplied in options."
            ) : "No TaskPaper items selected";
        };

        // ---------------- VALUE CYCLING ----------------

        // valueCycled :: [String] -> Item -> IO String
        const valueCycled = cyclableNames =>
            item => {
                const
                    overlap = intersect(
                        item.attributeNames
                    )(
                        cyclableNames
                    ),
                    tagName = 0 < overlap.length ? (
                        overlap[0]
                    ) : cyclableNames[0],
                    shortName = tagName.slice(5),
                    newValue = nextValue(
                        // Values as [String] array.
                        Array.from(options[shortName])
                        .map(x => `${x}`)
                    )(
                        item.getAttribute(tagName)
                    );

                return (
                    item.setAttribute(tagName, newValue),
                    `@${shortName} -> ${newValue}`
                );
            };


        // nextValue :: [String] -> String -> String
        const nextValue = ks =>
            k => {
                const iPosn = ks.findIndex(x => k === x);

                return -1 !== iPosn ? (
                    ks[(1 + iPosn) % ks.length]
                ) : ks[0];
            };

        // ------------------- GENERIC -------------------

        // intersect :: (Eq a) => [a] -> [a] -> [a]
        const intersect = xs =>
            // The intersection of lists xs and ys.
            ys => xs.filter(x => ys.includes(x));

        return tp3Main();
    };

    // JXA main
    return main();
})();
2 Likes