Draft script :: Toggle task-completed status of selected row(s)

A draft script for toggling the task completed (data-done) status of one or more selected rows.

Only non-empty rows are affected. Any selected non-empty rows which are not yet of the task type become tasks and acquire task-completion checkboxes.

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

    // Toggle task-completed for one or more selected Bike rows.

    // Affects non-empty rows only.
    // Affected rows are set to type "task" where necessary.

    // All selected rows are taken to the same status.
    // Where their starting status is mixed, the new status
    // is the opposite of that of the first selected task.

    // Rob Trew @2023
    // Ver 2.3

    // ---------------------- MAIN -----------------------
    // main :: IO ()
    const main = () => {
        const
            bike = Application("Bike"),
            doc = bike.documents.at(0);

        return doc.exists()
            ? toggleCompleted(bike)(
                doc.rows.where({
                    selected: true,
                    _not: [{name: ""}]
                })()
            )
            : "No document open in Bike.";
    };

    // ---------------------- BIKE -----------------------

    // bikeAttribFoundOrCreated :: Bike Application ->
    // String -> Bike Row -> Bike Attribute
    const bikeAttribFoundOrCreated = bikeApp =>
        // A reference to an attribute (of the given name)
        // for a particular row.
        name => row => {
            const maybeAttrib = row.attributes.byName(name);

            return maybeAttrib.exists()
                ? maybeAttrib
                : (() => {
                    const
                        attrib = new bikeApp.Attribute({
                            name
                        });

                    return (
                        row.attributes.push(attrib),
                        attrib
                    );
                })();
        };


    // taskCleared :: Bike Application ->
    // Bike Row -> IO Bike Row
    const taskCleared = bike =>
        // A row with any `data-done` attribute removed.
        row => {
            const mb = row.attributes.byName("data-done");

            return (
                mb.exists() && bike.delete(mb),
                row
            );
        };


    // taskCompleted :: Bike Application ->
    // Bike Row -> IO Bike Row
    const taskCompleted = bike =>
        // A row with an ISO8601 time stamp value for
        // the `data-done` attribute.
        // If the attribute is newly created the
        // time-stamp is taken from current system time.
        row => {
            const
                attrib = bikeAttribFoundOrCreated(bike)(
                    "data-done"
                )(row);

            return (
                attrib.value = attrib.value() || (
                    new Date()
                )
                .toISOString(),
                row
            );
        };


    // taskTypeChecked :: Bike Row -> IO Bike Row
    const taskTypeChecked = row =>
        // A task with its type set to "task".
        (
            ("task" !== row.type()) && (row.type = "task"),
            row
        );


    // toggleCompleted :: Application ->
    // [Bike Row] -> IO String
    const toggleCompleted = bikeApp =>
        // A message reporting on the number and new
        // (toggled) task-completion status of the
        // given list of Bike rows.
        rows => {
            const n = rows.length;

            return 0 < n
                ? (() => {
                    const
                        iTask = rows.findIndex(
                            x => "task" === x.type()
                        ),
                        newStatus = !(
                            -1 !== iTask
                                ? rows[iTask].attributes
                                .byName("data-done")
                                .exists()
                                : false
                        ),
                        statusName = newStatus
                            ? "completed"
                            : "reset to incomplete";

                    const f = newStatus
                        ? x => taskCompleted(bikeApp)(
                            taskTypeChecked(x)
                        )
                        : x => taskCleared(bikeApp)(
                            taskTypeChecked(x)
                        );

                    return (
                        rows.forEach(f),
                        `${n} tasks ${statusName}.`
                    );
                })()
                : "No rows selected in Bike.";
        };


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



See: Using Scripts - Bike

To test in Script Editor.app set the language selector at top left to JavaScript rather than AppleScript.

You can assign a Bike script to a keyboard shortcut using utilities like Keyboard Maestro and FastScripts.


For a Keyboard Maestro version, see:

1 Like

A variant of the JavaScript source which gives the option of automatically including (in the toggle) all descendants of selected rows:

The value of includeDescendants at the top of the script can be edited to either true or false.

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

    // Toggle task-completed for one or more selected Bike rows.

    // Affects non-empty rows only.
    // Affected rows are set to type "task" where necessary.

    // All selected rows are taken to the same status.
    // Where their starting status is mixed, the new status
    // is the opposite of that of the first selected task.

    // Rob Trew @2023, 2024
    // Ver 2.4

    // ---------- OPTION (INCLUDE DESCENDANTS) -----------

    // Set `includeDescendants` :: false to toggle only the
    // done status of directly selected rows.
    // If `includeDescendants` :: true, then the done status
    // of descendants of selected rows will also be toggled.
    const includeDescendants = true;

    // ---------------------- MAIN -----------------------
    // main :: IO ()
    const main = () => {
        const
            bike = Application("Bike"),
            doc = bike.documents.at(0);

        return doc.exists()
            ? toggleCompleted(bike)(
                (() => {
                    const
                        // Selected non-empty rows.
                        selectedRows = doc.rows.where({
                            selected: true,
                            _not: [{name: ""}]
                        })();

                    return includeDescendants
                        ? selectedRows.flatMap(row => [
                            row,
                            ...row.entireContents()
                        ])
                        : selectedRows;
                })()
            )
            : "No document open in Bike.";
    };

    // ---------------------- BIKE -----------------------

    // bikeAttribFoundOrCreated :: Bike Application ->
    // String -> Bike Row -> Bike Attribute
    const bikeAttribFoundOrCreated = bikeApp =>
        // A reference to an attribute (of the given name)
        // for a particular row.
        name => row => {
            const maybeAttrib = row.attributes.byName(name);

            return maybeAttrib.exists()
                ? maybeAttrib
                : (() => {
                    const
                        attrib = new bikeApp.Attribute({
                            name
                        });

                    return (
                        row.attributes.push(attrib),
                        attrib
                    );
                })();
        };


    // taskCleared :: Bike Application ->
    // Bike Row -> IO Bike Row
    const taskCleared = bike =>
        // A row with any `data-done` attribute removed.
        row => {
            const mb = row.attributes.byName("data-done");

            return (
                mb.exists() && bike.delete(mb),
                row
            );
        };


    // taskCompleted :: Bike Application ->
    // Bike Row -> IO Bike Row
    const taskCompleted = bike =>
        // A row with an ISO8601 time stamp value for
        // the `data-done` attribute.
        // If the attribute is newly created the
        // time-stamp is taken from current system time.
        row => {
            const
                attrib = bikeAttribFoundOrCreated(bike)(
                    "data-done"
                )(row);

            return (
                attrib.value = attrib.value() || (
                    new Date()
                )
                .toISOString(),
                row
            );
        };


    // taskTypeChecked :: Bike Row -> IO Bike Row
    const taskTypeChecked = row =>
        // A task with its type set to "task".
        (
            ("task" !== row.type()) && (row.type = "task"),
            row
        );


    // toggleCompleted :: Application ->
    // [Bike Row] -> IO String
    const toggleCompleted = bikeApp =>
        // A message reporting on the number and new
        // (toggled) task-completion status of the
        // given list of Bike rows.
        rows => {
            const n = rows.length;

            return 0 < n
                ? (() => {
                    const
                        iTask = rows.findIndex(
                            x => "task" === x.type()
                        ),
                        newStatus = !(
                            -1 !== iTask
                                ? rows[iTask].attributes
                                .byName("data-done")
                                .exists()
                                : false
                        ),
                        statusName = newStatus
                            ? "completed"
                            : "reset to incomplete";

                    const f = newStatus
                        ? x => taskCompleted(bikeApp)(
                            taskTypeChecked(x)
                        )
                        : x => taskCleared(bikeApp)(
                            taskTypeChecked(x)
                        );

                    return (
                        rows.forEach(f),
                        `${n} tasks ${statusName}.`
                    );
                })()
                : "No rows selected in Bike.";
        };


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

To use in Keyboard Maestro, setting the option value through a Keyboard Maestro variable in KM11:

BIKE Outliner – Toggle Task Completion in selected row(s) - Macro Library - Keyboard Maestro Discourse

1 Like