- Support for opening row links
This is very good – thank you !
What is the best approach to pasting row links in the middle of existing text, rather than as a new line ?
I can do this in two steps:
- Paste after ⇧⌘C, which defaults, I think, in both block and text-editing mode, to creating a new row from the
public.url
and public.url-name
in the clipboard,
- then copy again from selected blue link text, and paste the copied
OutlineFragment
to cursor point in an existing row.
but I may be missing something more direct.
( If not I could perhaps write a script for a direct Copy Row Link(s) to Outline Fragment )
Expand disclosure triangle to view rough sketch
(() => {
"use strict";
// Rough sketch of a JXA Copy As Row Link for Bike 2.0
// which allows for pasting a link inline (in text selection mode)
// as well as pasting as a new row (in block selection mode)
// Rob Trew @2025
// Ver 0.0
ObjC.import("AppKit");
const main = () => {
const doc = Application("Bike").documents.at(0);
return either(
alert("Copy Row Link")
)(
compose(
setClipOfTextType("com.hogbaysoftware.bike.xml"),
xmlFragment
)
)(
bindLR(
doc.exists()
? Right(doc.selectionRows() || [])
: Left("No document open in Bike")
)(
rows => bindLR(
0 < rows.length
? Right(
rows.flatMap(row => {
const name = row.name().trim();
return 0 < name.length
? [Tuple(name)(row.id())]
: [];
})
)
: Left(`Nothing selected in "${doc.name()}"`)
)(
bikeLiXMLFromNameIDPairsLR(doc)
)
)
);
}
// bikeXMLFromNameIDPairsLR :: Bike Document ->
// [(String, String)] -> Either String XML
const bikeLiXMLFromNameIDPairsLR = doc =>
nameIDPairs => {
const docID = doc.id();
return 0 < nameIDPairs.length
? Right(
nameIDPairs.map(
([rowName, rowId]) => (
`<li><p><a href="bike://${docID}/${rowId}">${rowName}</a></p></li>`
)
)
.join("\n\t\t")
)
: Left(`Only empty rows selected in "${doc.name()}"`);
};
const xmlFragment = liXML =>
`<?xml version="1.0" encoding="UTF-8"?>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<ul id="OutlineFragment">
${liXML}
</ul>
</body>
</html>`
// ------------------------- 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
);
};
// setClipOfTextType :: String -> String -> IO String
const setClipOfTextType = utiOrBundleID =>
txt => {
const pb = $.NSPasteboard.generalPasteboard;
return (
pb.clearContents,
pb.setStringForType(
$(txt),
utiOrBundleID
),
txt
);
};
// --------------------- 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
});
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a =>
// A pair of values, possibly of
// different types.
b => ({
type: "Tuple",
"0": a,
"1": b,
length: 2,
*[Symbol.iterator]() {
for (const k in this) {
if (!isNaN(k)) {
yield this[k];
}
}
}
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = lr =>
// Bind operator for the Either option type.
// If lr has a Left value then lr unchanged,
// otherwise the function mf applied to the
// Right value in lr.
mf => "Left" in lr
? lr
: mf(lr.Right);
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
const compose = (...fs) =>
// A function defined by the right-to-left
// composition of all the functions in fs.
fs.reduce(
(f, g) => x => f(g(x)),
x => x
);
// 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 => "Left" in e
? fl(e.Left)
: fr(e.Right);
// fmapLR (<$>) :: (b -> c) -> Either a b -> Either a c
const fmapLR = f =>
// Either f mapped into the contents of any Right
// value in e, or e unchanged if is a Left value.
e => "Left" in e
? e
: Right(f(e.Right));
return main()
// return sj(main());
})();
Good catch, will fix. Answer should be if copied text is not multiline then paste will work like any other pasted text. I just need to add logic for that. I also need to add special case logic to detect when link is pasted, and if you have a range of selected text I will apply the link to that text, as in Bike 1. Will get this fixed!
1 Like