Action
Toggle @done
Toggle the @done tag (with date stamp) on all selected lines.
(All additional lines are toggled in sync with the first selected line)
Can also be run in macOS BBEdit as a JXA script.
To enable macOS use, first save the library code at:
https://gist.github.com/RobTrew/675b0f14f87b77ee025755e067022c62
as ~/Library/Script Libraries/BBDrafts.js
on the macOS machine.
Steps
-
script
(() => { 'use strict'; // A script for toggling date-stamped @done(2018-05-20 18) tags, // Either: // // 1. In a Drafts 5 script action for TaskPaper mode, or // 2. as a JXA script for macOS BBEdit. // BBEdit use requires the library at: // https://gist.github.com/RobTrew/675b0f14f87b77ee025755e067022c62 // saved on macOS as: ~/Library/Script Libraries/BBDrafts.js // Rob Trew (c) 2018 // Ver 0.2 // MAIN ----------------------------------------------- // toggleDone :: Drafts IO () -> String const toggleDone = () => { const e = editor, rngLines = e.getSelectedLineRange(), strLines = e.getTextInRange(...rngLines), rgxDone = tagRegex('done'), dcts = map(s => ({ line: s, mbRange: tagRangeMay(rgxDone, s) }), lines(strLines) ), lng = dcts.length, lrLines = bindLR( lng > 0 ? ( Right(dcts) ) : Left('No lines found'), xs => { const blnDone = !xs[0].mbRange.Nothing, strTag = blnDone ? ( '' ) : ` @done(${ take(10, (new Date()) .toISOString()) })`; return Right(map( x => { const mb = x.mbRange, range = mb.Just, strLine = x.line; return ( mb.Nothing ? ( strLine ) : ( // @done removed take(range[0] - 1, strLine) + drop( range[0] + range[1], strLine ) ) ) + ( (blnDone || ( strip(strLine).length < 1 )) ? '' : strTag ); }, xs )); } ); return lrLines.Left || (() => { const str = unlines(lrLines.Right); return ( e.setTextInRange( ...rngLines, str ), e.setSelectedRange( rngLines[0], str.length ), unlines(lrLines.Right) ); })(); }; // TAG TOGGLING FUNCTIONS ----------------------------- // tagRegex :: String -> Regex const tagRegex = strTag => new RegExp('(^|\\s*)(@' + strTag + ')(\\(.*\\)|)'); // A Range tuple: (startIndex :: Int, length :: Int) // Range :: (Int, Int) // tagRangeMay :: Regex -> String -> Maybe Range const tagRangeMay = (rgxTag, strLine) => { const m = strLine.match(rgxTag); return m ? ( Just([m.index + 1, m[0].length - 1]) ) : Nothing(); }; // GENERIC FUNCTIONS ---------------------------------- // Just :: a -> Just a const Just = x => ({ type: 'Maybe', Nothing: false, Just: x }); // Left :: a -> Either a b const Left = x => ({ type: 'Either', Left: x }); // Nothing :: () -> Nothing const Nothing = () => ({ type: 'Maybe', Nothing: true, }); // Right :: b -> Either a b const Right = x => ({ type: 'Either', Right: x }); // Determines whether all elements of the structure // satisfy the predicate. // all :: (a -> Bool) -> [a] -> Bool const all = (p, xs) => xs.every(p); // bindLR (>>=) :: Either a -> (a -> Either b) -> Either b const bindLR = (m, mf) => m.Right !== undefined ? ( mf(m.Right) ) : m; // concatMap :: (a -> [b]) -> [a] -> [b] const concatMap = (f, xs) => [].concat.apply([], xs.map(f)); // doesFileExist :: FilePath -> IO Bool const doesFileExist = strPath => { const ref = Ref(); return $.NSFileManager.defaultManager .fileExistsAtPathIsDirectory( $(strPath) .stringByStandardizingPath, ref ) && ref[0] !== 1; }; // drop :: Int -> [a] -> [a] // drop :: Int -> String -> String const drop = (n, xs) => xs.slice(n); // dropWhileEnd :: (Char -> Bool) -> String -> String // dropWhileEnd :: (a -> Bool) -> [a] -> [a] const dropWhileEnd = (p, s) => { let i = s.length; while (i-- && p(s[i])) {} return s.slice(0, i + 1); }; // isSpace :: Char -> Bool const isSpace = c => /\s/.test(c); // lines :: String -> [String] const lines = s => s.split(/[\r\n]/); // map :: (a -> b) -> [a] -> [b] const map = (f, xs) => xs.map(f); // readFile :: FilePath -> IO String const readFile = strPath => { let error = $(), str = ObjC.unwrap( $.NSString.stringWithContentsOfFileEncodingError( $(strPath) .stringByStandardizingPath, $.NSUTF8StringEncoding, error ) ); return Boolean(error.code) ? ( ObjC.unwrap(error.localizedDescription) ) : str; }; // strip :: String -> String const strip = s => s.trim(); // stripEnd :: String -> String const stripEnd = s => dropWhileEnd(isSpace, s); // take :: Int -> [a] -> [a] const take = (n, xs) => xs.slice(0, n); // unlines :: [String] -> String const unlines = xs => xs.join('\n'); // LIBRARY IMPORT -------------------------------------- // Evaluate a function f :: (() -> a) // in the context of the JS libraries whose source // filePaths are listed in fps :: [FilePath] // usingLibs :: [FilePath] -> (() -> a) -> a const usingLibs = (fps, f) => all(doesFileExist, fps) ? ( eval(`(() => { 'use strict'; ${fps.map(readFile).join('\n\n')} return (${f})(); })();`) ) : libraryRequest(fps); // libraryRequest :: [FilePath] -> IO [FilePath] const libraryRequest = fps => { const sa = standardSEAdditions(), gaps = concatMap( fp => doesFileExist(fp) ? ( [] ) : [fp], fps ); return ( sa.activate(), sa.displayDialog( `Library not found at: ${gaps.join('\n')}`, { withTitle: 'Library file needed', buttons: ['OK'] } ), gaps ); }; // standardSEAdditions :: () -> Application const standardSEAdditions = () => Object.assign(Application('System Events'), { includeStandardAdditions: true }); // iOS Drafts 5 ? return Boolean(this.editor) ? ( toggleDone() // macOS JXA, using the library at: // // https://gist.github.com/RobTrew/675b0f14f87b77ee025755e067022c62 // // Saved as ~/Library/Script Libraries/BBDrafts.js ) : usingLibs( [ '~/Library/Script Libraries/BBDrafts.js' ], toggleDone ); })();
Options
-
After Success Default Notification Error Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.