No changes desired on this. I am thrilled with this part as is.
Rob—this is great. It is helping me gain insight into how JavaScript parses code.
The (0) part points to the first tab, right? And what I want is the frontmost, or current, tab.
This made me think, then analyze, then research. “.” is sort of like “of” in AppleScript, right?
I took [tabs.at(0).name()] and adjusted it to:
[windows[0].currentTab.name()]
…and that seems to work well.
I am not sure why windows[0] needs to be there, but the code fails without it.
One oddity: If I run the script with Safari as the active app, the script works as expected. However, if Safari is hidden, and has multiple windows open, the tab that is grabbed is from a different window. Does the window index renumber if Safari is hidden?
Perfect! You rock!
So, do you have an Amazon wishlist? If you don’t want to post it publicly, please DM me.
The current iteration, which is now in active use in my workflow, is:
(() => {
"use strict";
const
fpMobile = "~/Library/Mobile Documents/",
fpWorkflows = "iCloud~is~workflow~my~workflows/",
fpLinks = "Documents/Links.txt",
fp = `${fpMobile}${fpWorkflows}${fpLinks}`,
strFullPath = ObjC.unwrap(
$(fp).stringByExpandingTildeInPath
);
const
tp = Application("TaskPaper"),
doc = tp.open(Path(strFullPath));
// main :: IO ()
const main = () => {
const windows = Application("Safari").windows;
return either(
// Left channel message in case of a problem.
msg => alert(
"Copy Safari tab list to TaskPaper"
)(msg)
)(
// Simple string value returned if all is well.
x => x
)(
bindLR(
0 < windows.length ? (
Right(windows.at(0).tabs)
) : Left("No windows open in Safari")
)(tabs => {
const
dateString = taskPaperDateString(
new Date()
).slice(0, 10),
linkStrings = zipWith(
name => link =>
`- ${name} → ${link} @link(${dateString})`
)(
[windows[0].currentTab.name()]
)(
[windows[0].currentTab.url()]
);
const result = doc.evaluate({
script: `${TaskPaperContext}`,
withOptions: {
projectName: "Links",
linkTexts: linkStrings
}
});
return Right(
(doc.save(), result)
);
})
);
};
// -------------------- TASKPAPER --------------------
// TaskPaperContext :: Editor -> Dict -> String
const TaskPaperContext = (editor, options) => {
const tp3Main = () => {
const
outline = editor.outline,
xs = options.linkTexts,
target = outline.root;
outline.groupUndoAndChanges(() => {
const children = xs.map(
txt => outline.createItem(txt)
);
target.insertChildrenBefore(
children, target.firstChild
);
});
return xs.join("\n");
};
// projectFoundOrCreated :: TP3Outline ->
// String -> Bool -> TP3Item
const projectFoundOrCreated = outline =>
// A reference to a TaskPaper Project item,
// either found in the outline, or created
// at the top of it.
projectName => {
const
k = projectName.trim(),
matches = outline.evaluateItemPath(
`//project "${k}"`
),
blnFound = 0 < matches.length,
project = blnFound ? (
matches[0]
) : outline.createItem(`${k}:`);
return (
blnFound || outline.insertItemsBefore(
project,
outline.root.firstChild
),
project
);
};
return tp3Main();
};
// ----------------------- JXA -----------------------
// alert :: String => String -> IO String
const alert = title =>
s => {
const sa = Object.assign(
Application("System Events"), {
includeStandardAdditions: true
});
return (
sa.activate(),
sa.displayDialog(s, {
withTitle: title,
buttons: ["OK"],
defaultButton: "OK"
}),
s
);
};
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
type: "Either",
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: "Either",
Right: x
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = m =>
mf => 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 => e.Left ? (
fl(e.Left)
) : fr(e.Right);
// iso8601Local :: Date -> String
const iso8601Local = dte =>
new Date(dte - (6E4 * dte.getTimezoneOffset()))
.toISOString();
// taskPaperDateString :: Date -> String
const taskPaperDateString = dte => {
const [d, t] = iso8601Local(dte).split("T");
return [d, t.slice(0, 5)].join(" ");
};
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f =>
// A list constructed by zipping with a
// custom function, rather than with the
// default tuple constructor.
xs => ys => xs.map(
(x, i) => f(x)(ys[i])
).slice(
0, Math.min(xs.length, ys.length)
);
return main();
})();