BIKE Outliner – Toggle @done tag in selected rows

A macro for Jesse Grosjean's Bike Outliner.

This is something for which, in due course, Bike.app may no longer need tags (stylesheets revealing toggled attributes might intrude less on the line, and reward the eye less noisily), but for the moment, I find myself marking things something off, as I think them through, with either:

  • plain @done, or a timestamped:
  • @done(2022-07-08 08:14) etc.

Here is an interim macro for toggling a @done tag (with or without timestamp) in one or more selected (and non-empty) Bike rows.

BIKE – Toggle @done tag in selected rows.kmmacros (27 KB)


Expand disclosure triangle to view JS source
(() => {
    "use strict";

    // `@done` tag toggled in selected and non-empty
    // lines of a Bike.app (1.3.1 Preview) outline.
    // Optionally with date-time stamp
    // (for simple `@done`, edit tagValue to "" below)

    // Rob Trew @2022
    // Ver 0.03

    // main :: IO ()
    // eslint-disable-next-line max-lines-per-function
    const main = () => {
        // ------------------- OPTIONS -------------------
        const tagName = "done";

        const tagValue = taskPaperDateString(new Date());
        // OR tagValue = "";

        // ------------------- TOGGLE --------------------
        const
            bike = Application("Bike"),
            doc = bike.documents.at(0);

        return doc.exists() ? (() => {
            const
                selectedRows = doc.rows.where({
                    selected: true,
                    _not: [{
                        name: ""
                    }]
                });

            return Boolean(selectedRows.length) ? (() => {
                const
                    tagRegex = new RegExp(
                        Boolean(tagValue.length) ? (
                            `@${tagName}\\(.*\\)`
                        ) : `@${tagName}`, "u"
                    ),
                    isTagged = Boolean(
                        tagRegex.exec(
                            selectedRows.at(0).name()
                        )
                    ),
                    updated = isTagged ? (
                        clearTag(tagRegex)
                    ) : addTag(tagRegex)(tagName)(tagValue);

                return (
                    selectedRows().forEach(row => (
                        row.name = updated(row.name()),
                        row.textContent.strikethrough = !(
                            isTagged
                        )
                    )),
                    isTagged ? (
                        `@${tagName} cleared`
                    ) : `Tagged @${tagName}`
                );
            })() : "Nothing selected in Bike";
        })() : "No documents open in Bike";
    };

    // ---------------------- TAGS -----------------------

    // clearTag :: Regex -> String -> String
    const clearTag = tagRegex =>
        rowText => {
            const match = tagRegex.exec(rowText);

            return Boolean(match) ? (() => {
                const
                    matchStart = match.index,
                    preTag = rowText.slice(
                        0, matchStart
                    ).trim(),
                    postTag = rowText.slice(
                        matchStart + match[0].length
                    ).trim();

                return `${preTag} ${postTag}`.trim();
            })() : rowText.trim();
        };


    // addTag :: String -> String -> String -> String
    const addTag = tagRegex =>
        tagName => tagValue => txt => {
            const
                affix = Boolean(tagValue.length) ? (
                    `@${tagName}(${tagValue})`
                ) : `@${tagName}`;

            return `${clearTag(tagRegex)(txt)} ${affix}`;
        };


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

    // iso8601Local :: Date -> String
    const iso8601Local = dte =>
        new Date(dte - (6E4 * dte.getTimezoneOffset()))
        .toISOString();


    // taskPaperDateString :: Date -> String
    const taskPaperDateString = dte => {
        const [d, t] = iso8601Local(dte).split("T");

        return [d, t.slice(0, 5)].join(" ");
    };

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



Other Keyboard Maestro macros for BIKE Outliner

2 Likes

Updated above for Bike Preview 76, which introduces a .textContent interface for format scripting, and drops the earlier .htmlContent property.

2 Likes