Last active
October 3, 2022 18:43
-
-
Save RobTrew/3df0110db67282f200a3908cb0a4695e to your computer and use it in GitHub Desktop.
Selected DEVONthink Sheet -> DEVONthink markdown record (MMD table)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
((options) => { | |
'use strict'; | |
// (NB JavaScript for Automation – JXA: Save as .scpt ) | |
// Selected DEVONthink Sheet -> DEVONthink markdown record (MMD table) | |
// (New record containing MMD table created is same group as selection) | |
// Uses original fileName stem, with '.md' appended. | |
// Default alignment string is centered ':--:' | |
// (adjustable in options dictionary at foot of script) | |
// This is the tab formatted version | |
// (as in MMD Composer 'Clean up selected Table') | |
// For the whitespace pretty-printed version, see: | |
// https://gist.github.com/RobTrew/a6ef7c551f0874f6f3374e3bfbeb0b42 | |
// Rob Trew 2017 | |
// Ver 0.03 | |
// Note that this copy is in ES6 JS and thus for Sierra onwards only. | |
// For a (Yosemite-onward) ES5 copy, paste into the Babel JS repl at | |
// https://babeljs.io/repl/ | |
// GENERIC FUNCTIONS ----------------------------------------------------- | |
// (++) :: [a] -> [a] -> [a] | |
const append = (xs, ys) => xs.concat(ys); | |
// comparing :: (a -> b) -> (a -> a -> Ordering) | |
const comparing = f => | |
(x, y) => { | |
const | |
a = f(x), | |
b = f(y); | |
return a < b ? -1 : (a > b ? 1 : 0); | |
}; | |
// concat :: [[a]] -> [a] | [String] -> String | |
const concat = xs => | |
xs.length > 0 ? (() => { | |
const unit = typeof xs[0] === 'string' ? '' : []; | |
return unit.concat.apply(unit, xs); | |
})() : []; | |
// curry :: Function -> Function | |
const curry = (f, ...args) => { | |
const go = xs => xs.length >= f.length ? (f.apply(null, xs)) : | |
function () { | |
return go(xs.concat(Array.from(arguments))); | |
}; | |
return go([].slice.call(args, 1)); | |
}; | |
// drop :: Int -> [a] -> [a] | |
// drop :: Int -> String -> String | |
const drop = (n, xs) => xs.slice(n); | |
// filter :: (a -> Bool) -> [a] -> [a] | |
const filter = (f, xs) => xs.filter(f); | |
// findIndex :: (a -> Bool) -> [a] -> Maybe Int | |
const findIndex = (p, xs) => | |
xs.reduce((a, x, i) => | |
a.nothing ? ( | |
p(x) ? { | |
just: i, | |
nothing: false | |
} : a | |
) : a, { | |
nothing: true | |
}); | |
// intercalate :: String -> [a] -> String | |
const intercalate = curry((s, xs) => xs.join(s)); | |
// 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); | |
// maximumBy :: (a -> a -> Ordering) -> [a] -> a | |
const maximumBy = (f, xs) => | |
xs.reduce((a, x) => a === undefined ? x : ( | |
f(x, a) > 0 ? x : a | |
), undefined); | |
// replicate :: Int -> a -> [a] | |
const replicate = (n, x) => | |
Array.from({ | |
length: n | |
}, () => x); | |
// show :: Int -> a -> Indented String | |
// show :: a -> String | |
const show = (...x) => | |
JSON.stringify.apply( | |
null, x.length > 1 ? [x[1], null, x[0]] : x | |
); | |
// splitOn :: a -> [a] -> [[a]] | |
// splitOn :: String -> String -> [String] | |
const splitOn = curry((needle, haystack) => | |
typeof haystack === 'string' ? ( | |
haystack.split(needle) | |
) : (function sp_(ndl, hay) { | |
const mbi = findIndex(x => ndl === x, hay); | |
return mbi.nothing ? ( | |
[hay] | |
) : append( | |
[take(mbi.just, hay)], | |
sp_(ndl, drop(mbi.just + 1, hay)) | |
); | |
})(needle, haystack)); | |
// unconsMay :: [a] -> Maybe (a, [a]) | |
const unconsMay = xs => xs.length > 0 ? { | |
just: [xs[0], xs.slice(1)], | |
nothing: false | |
} : { | |
nothing: true | |
}; | |
// unlines :: [String] -> String | |
const unlines = xs => xs.join('\n'); | |
// MMD TABLE ------------------------------------------------------------- | |
// mmdTableFromTabbed :: String -> String | |
const mmdTableFromTabbed = curry((alignmentString, s) => { | |
const mbHeadTail = unconsMay( | |
map(splitOn('\t'), filter(x => length(x) > 0, lines(s))) | |
); | |
return mbHeadTail.nothing ? s : (() => { | |
const | |
ht = mbHeadTail.just, | |
t = ht[1]; | |
return unlines(map(x => '|' + intercalate('\t|', x) + '\t|', | |
append( | |
[ | |
ht[0], | |
replicate( | |
length(maximumBy(comparing(length), t)), | |
alignmentString | |
) | |
], | |
t | |
) | |
)); | |
})(); | |
}); | |
// SELECTED DT SHEET -> NEW MD RECORD WITH SAME FILESTEM + '.md' | |
const | |
dt = Application('DEVONthink Pro'), | |
seln = dt.selection(), | |
mbRec = seln.length > 0 ? { | |
just: seln[0] | |
} : { | |
nothing: true | |
}, | |
mbMD = mbRec.nothing ? ( | |
mbRec | |
) : mbRec.just.type() === 'sheet' ? { | |
just: mmdTableFromTabbed( | |
options.alignmentString || ':--:', | |
mbRec.just.richText.text() | |
) | |
} : { | |
nothing: true, | |
msg: 'No DEVONthink Sheet selected' | |
}; | |
return mbMD.nothing ? mbMD.msg : ( | |
dt.createRecordWith({ | |
name: mbRec.just.name() + '.md', | |
type: 'markdown', | |
content: mbMD.just | |
}, { in: dt.currentGroup() | |
}), | |
mbMD.just | |
); | |
})({ | |
alignmentString: ':--:' // Adjust to ':---' or '---:' etc | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment