Drafts Action Directory

Renumber lists

Last updated: 2018-05-27 15:47:40 UTC

(Re)number (selected, or all) Markdown and Taskpaper ordered lists.

HTML display automatically corrects numeric disorder in MD etc lists,
but this action aims to fix disordered numbering in the plain text document too.

Install

Steps

  • script

    // GENERIC FUNCTIONS ---------------------------------------
    
    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = (f, xs) => [].concat.apply([], xs.map(f));
    
    // Typical usage: groupBy(on(eq, f), xs)
    // groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
    const groupBy = (f, xs) => {
        const dct = xs.slice(1)
            .reduce((a, x) => {
                const h = a.active.length > 0 ? (
                    a.active[0]
                ) : undefined;
                return h !== undefined && f(h, x) ? {
                    active: a.active.concat([x]),
                    sofar: a.sofar
                } : {
                    active: [x],
                    sofar: a.sofar.concat([a.active])
                };
            }, {
                active: xs.length > 0 ? [xs[0]] : [],
                sofar: []
            });
        return dct.sofar.concat(dct.active.length > 0 ? (
            [dct.active]
        ) : []);
    };
    
    // justifyRight :: Int -> Char -> String -> String
    const justifyRight = (n, cFiller, strText) =>
        n > strText.length ? (
            (cFiller.repeat(n) + strText)
            .slice(-n)
        ) : strText;
    
    // length :: [a] -> Int
    const length = xs => xs.length;
    
    // lines :: String -> [String]
    const lines = s => s.split(/[\r\n]/);
    
    // map :: (a -> b) -> [a] -> [b]
    const map = (f, xs) => xs.map(f);
    
    //Ordering: (LT|EQ|GT):
    //  GT: 1 (or other positive n)
    //	EQ: 0
    //  LT: -1 (or other negative n)
    // minimumBy :: (a -> a -> Ordering) -> [a] -> a
    const minimumBy = (f, xs) =>
        xs.reduce((a, x) => a === undefined ? x : (
            f(x, a) < 0 ? x : a
        ), undefined);
    
    // showLog :: a -> Console String
    const showLog = (...args) =>
        alert(
            args.map(JSON.stringify)
            .join(' -> ')
        );
    
    // unlines :: [String] -> String
    const unlines = xs => xs.join('\n');
    
    // APPLYING ANY FUNCTION (of type [String] -> [String])
    // TO SELECTED (OR ALL) LINES IN DRAFT
    
    // draftOrLinesUpdated :: IO () ->
    //                    ([String] -> [String]) -> IO ()
    const draftOrLinesUpdated = f => {
        const
            functionName = f.name, // Used for logging.
            // Selected lines or whole of draft.
            [intStart, intLength] = editor.getSelectedLineRange(),
            [intFrom, intChars] = (
                editor.getSelectedText()
                .length > 0 ? (
                    [intStart, intLength]
                ) : [0, editor.getText().length]
            ),
            // Concatenated result of applying f to the list of lines
            strUpdated = unlines(f(lines(
                editor.getTextInRange(intFrom, intChars)
            )));
        return (
            // Various channels, for testing and use.
            editor.setTextInRange(intFrom, intChars, strUpdated),
            console.log(functionName, strUpdated),
            strUpdated
        );
    };
    
  • script

    (() => {
        'use strict';
    
        return draftOrLinesUpdated(
            draftLines => {
                const
                    rgxPrefix = /^\s*\d+\.\s*/,
                    rgxOLText = /^\s*\d+\.\s*(.*)$/,
                    rgxIndent = /^(\s*)\d+\./,
    
                    // renumbered :: [ {isOL :: Bool,
                    //                  txt :: String} ] -> [String]
                    renumbered = xs => {
                        const
                            // Longest sequence of digits,
                            intDigits = (xs.length - 1)
                            .toString()
                            .length,
    
                            // and shortest indent string.
                            indent = minimumBy(
                                length,
                                map(
                                    x => rgxIndent.exec(x.txt)[1],
                                    xs
                                )
                            );
                        return map(
                            (dict, i) =>
                            indent + (1 + i).toString() + '. ' +
                            rgxOLText.exec(dict.txt)[1],
                            xs
                        );
                    };
    
                // The list of line groups is flattened by concatMap
                // and returned as a list of strings.
                return concatMap(
                    xs => ((xs.length > 0) && xs[0].isOL) ? (
                        renumbered(xs)
                    ) : map(x => x.txt, xs),
                    groupBy(
                        // Adjacent lines grouped by whether
                        // they are numbered.
                        (a, b) => a.isOL === b.isOL,
                        map(line => ({
                                isOL: rgxPrefix.test(line),
                                txt: line
                            }),
                            draftLines
                        )
                    )
                );
            }
        );
    })();
    
Actions available in the Action Directory are uploaded by community members. Use appropriate caution reviewing downloaded actions before use.