Printing TaskPaper 3 documents with Marked 2 CSS templates

A Keyboard Maestro version (⌘⌥ P for TaskPaper 3)

Print TaskPaper 3 document in Marked 2.kmmacros.zip (12.6 KB)

JavaScript source:

// COPY THE ACTIVE TASKPAPER DOCUMENT INTO THE CLIPBOARD IN A FORM THAT CAN BE 
// PREVIEWED AND PRINTED (WITH CSS STYLESHEETS) USING MARKED 2

Ver 0.2

var kmVars = Application("com.stairways.keyboardmaestro.engine")
    .variables;

// Marked 2 > Preview > Clipboard Preview
// http://marked2app.com/

// If Marked 2 is installed, the script opens it and displays a CSS-formatted clipboard preview

// Draft 0.09  Rob Trew 2016-04-13

// 0.09 Allows for hiding of @saved searches
// 0.08 Allows for printing only outline-visible + filter-visible lines

// 0.06 Allows for an API change in 3.2 Preview (197) 
//    (item.bodyContentString || item.bodyDisplayString)

// 0.05 adds further options (at bottom of script):
// 	- baseHeaderLevel
//  - hideProjectColon
//  - markedStyle
//  - tagEmphasis
//  - tagsToHide

// 0.02 adds option for blank line between a task and its note(s)

(function (dctOptions) {
    'use strict';


    // TASKPAPER CONTEXT

    function TaskPaperContext(editor, options) {

        // tagString :: item -> String
        function tagString(item, lstHidden, strMDChar) {
            strMDChar = strMDChar || '';

            var dctAttribs = item.attributes,
                lstTags = Object.keys(dctAttribs)
                .filter(function (k) {
                    return lstHidden.indexOf(k) === -1 &&
                        k !== 'data-type' && k !== 'indent';
                })
                .map(function (k) {
                    var v = dctAttribs[k];

                    return strMDChar + '@' + k.slice(5) +
                        (v ? '(' + v + ')' : '') + strMDChar;
                });

            return ' ' + lstTags.join(' ');
        }

        var blnNoteGap = options.noteGap,
            blnShowAll = !(parseInt(options.hideFoldedAndFiltered, 10)),
            blnHideSearches = parseInt(options.hideSavedSearches, 10),
            intBaseLevel = options.baseHeaderLevel,
            strStyle = options.markedStyle,
            blnColon = options.hideProjectColon === "0",
            maybeHidden = options.tagsToHide,
            lstHidden = maybeHidden instanceof Array ? ['search'].concat(
                maybeHidden)
            .map(
                function (tag) {
                    return tag !== '*' ? (
                        'data-' + (tag.charAt(0) === '@' ? tag.slice(1) :
                            tag)
                    ) : '*';
                }) : ['search'],
            blnAllTagsHidden = lstHidden.indexOf('*') !== -1,
            strEmphasis = options.tagEmphasis;

        // Lines to print
        var lstVisible = editor.outline.items
            .filter(function (x) {
                return (blnShowAll || editor.isDisplayed(x));
            }),
            lstItems = blnHideSearches ? lstVisible.filter(function (x) {
                return x.bodyString.indexOf('@search') !== 0;
            }) : lstVisible;
        lngLast = lstItems.length - 1;

        return (
            intBaseLevel ? 'Base Header Level: ' +
            intBaseLevel.toString() + '\n' : ''
        ) + (
            strStyle ? 'Marked Style: ' + strStyle + '\n\n' : ''
        ) + lstItems

        // Indent levels required by Markdown format
            .reduce(function (a, item) {
                var strType = item.getAttribute('data-type'),
                    blnProj = strType === 'project';

                if (blnProj) a.depth = item.depth;
                a.lines.push(
                    blnProj ? {
                        isProject: true,
                        item: item,
                        mdIndent: 0
                    } : {
                        isNote: strType !== 'task',
                        item: item,
                        mdIndent: item.depth - a.depth
                    }
                );

                return a;
            }, {
                lines: [],
                depth: 0
            })
            .lines


        // Hash prefixes, zero indents and extra line breaks for projects,
        // adjusted indents for tasks and notes.
            .map(function (mItem, i, xs) {

                var item = mItem.item,
                    blnProject = mItem.isProject,
                    blnNote = mItem.isNote || false,
                    blnTask = !(blnProject || blnNote),
                    blnGap = blnNote && blnNoteGap,
                    blnLastGap = (blnGap && (i < lngLast)) ? (!xs[i + 1]
                        .isNote
                    ) : false
                strText = (item.bodyContentString || item.bodyDisplayString ||
                    '');

                return (
                        blnProject ? (
                            '\n' + Array(item.depth + 1)
                            .join('#') + ' '
                        ) : (mItem.mdIndent > 1 ? Array(mItem.mdIndent) : [])
                        .join('\t')
                    ) +
                    (blnTask ? '- ' : '') +
                    (blnGap ? '<p>' : '') +
                    strText +
                    (blnProject && blnColon ? ':' : '') +
                    (blnAllTagsHidden ? '' : tagString(item, lstHidden,
                        strEmphasis)) +
                    (blnLastGap ? '<p>' : '');
            })
            .join('\n');
    }


    // JAVASCRIPT FOR AUTOMATION CONTEXT

    // appIsInstalled :: String -> Bool
    function appIsInstalled(strBundleId) {
        ObjC.import('AppKit');

        return ObjC.unwrap(
            $.NSWorkspace.sharedWorkspace
            .URLForApplicationWithBundleIdentifier(
                strBundleId
            )
            .fileSystemRepresentation
        ) !== undefined;
    }

    // menuItemClick :: String -> [String] -> IO ()
    function menuItemClick(strAppID, lstMenuPath) {
        var oApp = Application(strAppID),
            strAppName = oApp.name(),
            lngChain = lstMenuPath.length,
            blnResult = false;

        if (lngChain > 1) {

            var appSE = Application("System Events"),
                lstApps = appSE.processes.where({
                    name: strAppName
                }),
                procApp = lstApps.length ? lstApps[0] : null;

            if (procApp) {
                oApp.activate();
                var strMenu = lstMenuPath[0],
                    fnMenu = procApp.menuBars[0].menus.byName(strMenu),
                    lngLast = lngChain - 1;

                for (var i = 1; i < lngLast; i++) {
                    strMenu = lstMenuPath[i];
                    fnMenu = fnMenu.menuItems[strMenu].menus[strMenu];
                }

                fnMenu.menuItems[
                    lstMenuPath[lngLast]
                    ].click();
                blnResult = true;
            }
        }
        return blnResult;
    }

    var strMarked2 = "com.brettterpstra.marked2";

    var ds = Application("TaskPaper")
        .documents,
        d = ds.length ? ds[0] : undefined;

    var strClip = d ? d.evaluate({
        script: TaskPaperContext.toString(),
        withOptions: dctOptions
    }) : '';


    // Place Markdown copy in clipboard,
    // displaying in Marked 2 if it is installed
    if (strClip.length > 0) {
        var a = Application.currentApplication(),
            sa = (a.includeStandardAdditions = true, a);

        sa.setTheClipboardTo(strClip);

        if (appIsInstalled(strMarked2)) {
            Application(strMarked2)
                .activate();

            menuItemClick(strMarked2, ['Preview', 'Clipboard Preview']);
        }
    }

    return strClip;

})({
    // Integer 1-6  Render unindented projects as H1-H6
    baseHeaderLevel: kmVars['Base header level'].value(),

    // Bool (Print outline-visible and filter-visible lines only ?)
    hideFoldedAndFiltered: kmVars['Hide folded and filtered'].value(),

    // Bool 
    hideProjectColon: kmVars['Hide project colon'].value(),

    // Bool 
    hideSavedSearches: kmVars['Hide saved searches'].value(),

    // StyleName or empty string or undefined to use Marked default
    markedStyle: kmVars['Marked 2 style'].value(),

    // blank line between a task and its note text ?
    noteGap: kmVars['Gap between notes'].value(),

    // asterisk for italic, two for bold, backtick for code
    tagEmphasis: kmVars['Tag emphasis'].value(),

    // ['*'] hides all tags in printed version
    // ['done', 'due'] hides @done and @due tags
    // [] or false hides no tags
    tagsToHide: kmVars['Tags to hide'].value()
        .split(/[\s\,\;]+/)
});