Let’s say documents A and B point to C and D. Now you create E to override C and D and want to automagically have A and B point to E. This question originated in another thread. In the simplest case, the group would contain only two documents: the old and the new one. All links from other docs to the old one would be changed to point to the new one – classic MD links and Wikilinks.
That’s what the following script does. For it to work, the new document (E) and the old ones (C and D) must be contained in the same group. The new document (E) must be selected. Then you can run the script.
And this works only for MD documents. I might be possible to make it work with PDFs, too, but that’s a lot more complicated.
(() => {
const DTapp = Application("DEVONthink 3");
const currentApp = Application.currentApplication();
currentApp.includeStandardAdditions = true;
const targetRec = DTapp.selectedRecords[0];
if (targetRec.type() !== 'markdown') {
currentApp.displayAlert("Please select exactly one Markdown record",
{message: "Select the record you want to consolidate the others in this group to and start again.", as: "critical"});
return;
}
const targetUUID = targetRec.uuid();
const targetName = targetRec.name();
const targetGroup = targetRec.locationGroup;
const targetGroupUUID = targetGroup.uuid();
/* Get the other records in the group of the selected record */
const oldRecords = targetGroup.children().filter(c => c.uuid() !== targetUUID);
/* Build the regular expressions for Wiki and item links.
Note: The REs must match the UUIDs and names of _all_ old records.
Therefore, we build them here once.
itemLinkRE: \[.*?\]\(?:x-devonthink-item:\/\/(uuid1|uuid2|uuid3…)\)
wikiLinkRE: \[\[(?:name1|name2|name3…)\]\]
*/
const wikiLinkRE = new RegExp(`\\[\\[(?:${oldRecords.map(r => `${r.name()}`).join('|')})\\]\\]`, 'g');
const itemLinkRE = new RegExp(`\\[.*?\\]\\(x-devonthink-item:\/\/(?:${oldRecords.map(r => `${r.uuid()}`).join('|')})\\)`, 'g');
/* Loop over the other records, finding all incoming links.
For each incominig link, open the corresponding file and
replace all item and Wiki links to the current record with links to the target record */
oldRecords.forEach(r => {
/* Build a set of the referencing UUIDs. Using a set to get _unique_ UUIDs */
const unifiedRefUUIDs = Array.from(new Set([...r.incomingReferences.uuid(),...r.incomingWikiReferences.uuid()]));
/* Filter out non-MD records and those in the current group, build an array of the _records_ from the UUIDs */
const referringMDs = unifiedRefUUIDs
.filter(uuid => {
const rec = DTapp.getRecordWithUuid(uuid);
return rec.type() === 'markdown'
&& rec.locationGroup.uuid() !== targetGroupUUID})
.map(uuid => DTapp.getRecordWithUuid(uuid));
/* For each record referring to the current one, replace all references to _all_
of the old records with references the the new one, changing the name for item links, too */
referringMDs.forEach(rec => {
const txt = rec.plainText().replaceAll(itemLinkRE, `[${targetName}](x-devonthink-item://${targetUUID}`).replaceAll(wikiLinkRE, `[[${targetName}]]`);
console.log(`Target "${targetName}" Record "${rec.name()}", ${rec.uuid()} **\n${txt}`);
/* The next line updates the referring record!
REMOVE COMMENT (//) only if you're sure the script works */
// rec.plainText = txt;
})
})
})()
As it stands, the script will not change anything. If run in Script Editor or with osascript
– provided the new document is selected – it will simply output what it would do to the referring MD docs. Remove the comment before the line
rec.plainText = txt
to make it change the references – if you are sure that it does what you want it to do.
It will not only replace the links, but also the names. So
[[OldDoc]]
will become [[NewDoc]]
and
[Link to old document](x-devonthink-item://xxxx)
will become
[NewDoc](x-devonthink-item://xxxx)
with NewDoc
being the name of the new document.
The script has been tested by @Kourosh (thanks for that!), who started the original thread.