Recipes :: Working with a subset of lines in a draft

1 of 3 – extended editor selection

The simplest route to a subset of lines is through an extended selection in the editor.

// selectedOrAllLines :: Editor () -> [String]
const selectedOrAllLines = () => {
    const e = editor;
    return (
        e.getSelectedRange()[1] > 0 ? (
            e.getTextInRange(
                ...e.getSelectedLineRange()
            )
        ) : draft.content
    ).split('\n');
};

In the function above, which returns a list of line strings:

  • If the length of the selection is non-zero (e.getSelectedRange()[1] > 0 ?) then
  • the text which we split into lines is that in editor.getSelectedLineRange(), otherwise
  • it is the whole draft.content.

2 of 3 – filtering

The second route is to get all the lines, and then filter them.

// lines :: String -> [String]
const lines = s => s.split(/[\r\n]/);

const doneLines = lines(draft.content).filter(x => x.includes('@done'))

See filter and includes

3 of 3 – combining mapping and filtering with concatMap

The built-in map lets us define a new list as a transformation of an existing one. (See this post for an example)

We can also write ourselves a concatMap function which lets us combine transformation with some filtering:

// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = (f, xs) => [].concat(...xs.map(f));

(where xs is a list (for example of lines, or words), and f is a function on one of these items, returning some value wrapped in [list] brackets)

concatMap maps an existing list to a transformed version (in which each new item is [wrapped] in list brackets), and then applies concat – a function which joins all the items in a list together.

Given a list of lists like:

[["alpha"], [], ["beta"], ["gamma"], [], ["delta"]]

concat would concatenate everything and return:

["alpha", "beta", "gamma", "delta"]

from which any empty [] lists have vanished.

It’s these vanishing empty lists which allow us to do some filtering with concatMap.

The map part of

concatMap(x => isEven(x) ? [] : [x], [1, 2, 3, 4, 5, 6, 7, 8, 9])

produces:

[[1],[],[3],[],[5],[],[7],[],[9]]

and the subsequent concat reduces this to:

[1,3,5,7,9]

In a Drafts action which generates a new draft for each non-blank line in the extended selection ( or for every non-blank line if the selection is not extended ), we can use concatMap to ignore the blank lines:

http://actions.getdrafts.com/a/1I3

If the length of a line is zero, then the concatMap f function returns [], (which vanishes under the following concatenation) but,
if the length of a line is non-zero, then the concatMap f function returns a new Draft, wrapped in [brackets], which the concatenation simply removes:

(() => {
    'use strict';

    // main :: Drafts ()
    const main = () => draftsFromLines(
        selectedOrAllLines()
    );

    // selectedOrAllLines :: Editor () -> [String]
    const selectedOrAllLines = () => {
        const e = editor;
        return (
            e.getSelectedRange()[1] > 0 ? (
                e.getTextInRange(
                    ...e.getSelectedLineRange()
                )
            ) : draft.content
        ).split('\n');
    };

    // draftsFromLines :: [Strings] -> [UUID]
    const draftsFromLines = xs =>

        // Concatenation (of map result) purges empty lists
        // ( no new draft created from any blank line )

        concatMap(
            // Function applied ...
            s => s.length > 0 ? [
                Object.assign(
                    Draft.create(), {
                        content: s
                    }
                )
            ] : [],

            // ... to each item of this list.
            xs
        )
        .reverse() // Inbox order matching line order
        .map(x => (x.update(), x.uuid));

    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = (f, xs) => [].concat(...xs.map(f));

    // MAIN ---
    const result = main();
    return (
        console.log(result),
        result
    );
})();
3 Likes