Scrivener is a very good piece of work, but perhaps less scriptable (and more RTF focused) than is ideal for some workflows.
Has anyone experimented with:
- building document outlines from groups and (unsorted sequences of) DEVONthink text records,
- and then compiling a Markdown draft version of those outlines by traversing them with scripts ?
Roughly this kind of thing, I suppose, if we select a few groups to be treated as top level headers:
(() => {
'use strict';
// main :: IO ()
const main = () => {
const dt3 = Application('DEVONThink 3');
return mdHeadingsFromTextTreeForest(
dtTextForest(dt3.selection())
);
};
// ---------------- MARKDOWN HEADINGS ----------------
// mdHeadingsFromTextTreeForest ::
// [Tree {name::String, text::String}] -> String
const mdHeadingsFromTextTreeForest = forest =>
unlines(
forest.flatMap(
mdHeadingsFromNameTextTree(1)
)
);
// mdHeadingsFromNameTextTree :: Int ->
// Tree {name::String, text::String} -> String
const mdHeadingsFromNameTextTree = startLevel =>
tree => {
const go = level =>
node => {
const
dict = node.root,
text = dict.text;
return [
`\n${'#'.repeat(level)} ${dict.name}\n`
].concat(
Boolean(text) ? (
[text]
) : []
).concat(
node.nest.flatMap(
go(1 + level)
)
);
}
return go(startLevel)(tree);
};
// ------------------- DEVONTHINK --------------------
// dtTextTree :: [Item] ->
// [Tree {name::String, text::String}]
const dtTextForest = peers =>
peers.map(
fmapPureDT(
x => ({
name: x.name(),
text: 'text' === x.kind() ? (
x.plainText()
) : ''
})
)
);
// fmapPureDT :: (DTItem -> a) -> DTItem -> Tree a
const fmapPureDT = f => {
// f mapped over x and each of its descendants,
// with the resulting values held in a generic
// tree structure.
const go = x =>
Node(f(x))(
x.children()
.map(go)
);
return go;
};
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
type: 'Either',
Left: x
});
// Node :: a -> [Tree a] -> Tree a
const Node = v =>
// Constructor for a Tree node which connects a
// value of some kind to a list of zero or
// more child trees.
xs => ({
type: 'Node',
root: v,
nest: xs || []
});
// Right :: b -> Either a b
const Right = x => ({
type: 'Either',
Right: x
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = m =>
mf => undefined !== m.Left ? (
m
) : mf(m.Right);
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
// Application of the function fl to the
// contents of any Left value in e, or
// the application of fr to its Right value.
fr => e => 'Either' === e.type ? (
undefined !== e.Left ? (
fl(e.Left)
) : fr(e.Right)
) : undefined;
// sj :: a -> String
function sj() {
// Abbreviation of showJSON for quick testing.
// Default indent size is two, which can be
// overriden by any integer supplied as the
// first argument of more than one.
const args = Array.from(arguments);
return JSON.stringify.apply(
null,
1 < args.length && !isNaN(args[0]) ? [
args[1], null, args[0]
] : [args[0], null, 2]
);
}
// unlines :: [String] -> String
const unlines = xs =>
// A single string formed by the intercalation
// of a list of strings with the newline character.
xs.join('\n');
// MAIN ---
return main();
})();