Many scripts act on selected rows.
A difficulty can arise, in the context of an outline, if the selection includes rows that are descendants of other selected rows – a script may need to derive a subset of the selected rows, acting only on the top level selections, and ignoring any selected descendants of them.
Sketches here of one approach – using an intersect(xs, ys) function to skip any selected rows which have selected ancestors – in AppleScript and then in JavaScript for Automation.
(We can take advantage of the fact that a selected a row is always listed before any selected descendants that it may have – the selected rows
property returns rows in document order rather than selection order. )
AppleScript
-- SELECTED ROWS (Excluding descendents of other selected rows) ----------
-- topLevelSelections :: OO Document -> [OO Row]
on topLevelSelections(oDoc)
using terms from application "OmniOutliner"
tell oDoc
script noAncestorSelected
on |λ|(acc, x)
if intersect(id of ancestors of x, acc) ≠ {} then
acc -- unchanged accumulator (skips this x)
else
acc & id of x -- expanded accumulator
end if
end |λ|
end script
script rowByID
on |λ|(x)
row id x of oDoc
end |λ|
end script
my map(rowByID, my foldl(noAncestorSelected, {}, selected rows))
end tell
end using terms from
end topLevelSelections
-- TEST ----------------------------------------------------------------------
-- List only topics of: selected rows which have no selected ancestors
on run
tell application "OmniOutliner"
script rowTopic
on |λ|(x)
topic of x
end |λ|
end script
my map(rowTopic, my topLevelSelections(front document))
end tell
end run
-- GENERIC FUNCTIONS ---------------------------------------------------------
-- filter :: (a -> Bool) -> [a] -> [a]
on filter(f, xs)
tell mReturn(f)
set lst to {}
set lng to length of xs
repeat with i from 1 to lng
set v to item i of xs
if |λ|(v, i, xs) then set end of lst to v
end repeat
return lst
end tell
end filter
-- foldl :: (a -> b -> a) -> a -> [b] -> a
on foldl(f, startValue, xs)
tell mReturn(f)
set v to startValue
set lng to length of xs
repeat with i from 1 to lng
set v to |λ|(v, item i of xs, i, xs)
end repeat
return v
end tell
end foldl
-- intersect :: (Eq a) => [a] -> [a] -> [a]
on intersect(xs, ys)
script found
on |λ|(x)
ys contains x
end |λ|
end script
filter(found, xs)
end intersect
-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
tell mReturn(f)
set lng to length of xs
set lst to {}
repeat with i from 1 to lng
set end of lst to |λ|(item i of xs, i, xs)
end repeat
return lst
end tell
end map
-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: Handler -> Script
on mReturn(f)
if class of f is script then
f
else
script
property |λ| : f
end script
end if
end mReturn
JavaScript for Automation (JXA)
(() => {
'use strict';
// IGNORING SELECTED DESCENDANTS OF OTHER SELECTED ROWS --------------
// topLevelSelections :: OO Document -> [OO Row]
const topLevelSelections = ooDoc => map(
strID => ooDoc.rows.byId(strID),
foldl(
(acc, row) => isNull(intersect(row.ancestors.id(), acc)) ? (
cons(row.id(), acc)
) : acc, [],
ooDoc.selectedRows()
)
);
// GENERIC FUNCTIONS -------------------------------------------------
// cons :: a -> [a] -> [a]
const cons = (x, xs) => [x].concat(xs);
// intersect :: (Eq a) => [a] -> [a] -> [a]
const intersect = (xs, ys) =>
xs.length && ys.length ? (
xs.filter(x => ys.indexOf(x) !== -1)
) : [];
// isNull :: [a] | String -> Bool
const isNull = xs =>
Array.isArray(xs) || typeof xs === 'string' ? (
xs.length < 1
) : undefined;
// map :: (a -> b) -> [a] -> [b]
const map = (f, xs) => xs.map(f);
// foldl :: (b -> a -> b) -> b -> [a] -> b
const foldl = (f, a, xs) => xs.reduce(f, a);
// unlines :: [String] -> String
const unlines = xs => xs.join('\n');
// TEST --------------------------------------------------------------
// List only topics of: selected rows which have no selected ancestors
const
docs = Application('OmniOutliner')
.documents,
maybeDoc = docs.length > 0 ? {
just: docs.at(0)
} : {
nothing: true
};
return maybeDoc.nothing ? '' : unlines(
map(x => x.topic(), topLevelSelections(maybeDoc.just))
);
})();