Useful JXA snippets

Just in time for Christmas… Some JXA snippets that might be useful when writing JavaScript code for DT.

All snippets assume that app is defined as
app = Application('DEVONthink 3'); and r is a DT record.

Access database by name

const db = app.databases["MyDatabase"];

How it works: databases is a top-level collection of Elements of the DT app. Every database has a name and can be accessed as app.databases.byName("MyDatabase"). The array notation ["MyDatabase"] is a shorthand for byName("MyDatabase").

Consider only a certain type from the selected records

const MDRecords = app.selectedRecords().filter(r => 
  r.type() === 'markdown');

How it works: selectedRecords() is an array containing the currently selected records. Since it provides all array methods, you can use filter with a condition. Only those records meeting the condition are returned by filter.

Consider several types from the selected records

const HTMLRecords = app.selectedRecords().filter(r => 
  ["HMTL", "formatted note"].includes(r.type());

How it works: See above for filter. The condition here uses a literal array ["HTML", "formatted note"] and its includes method. includes returns true if its parameter (r.type()) is contained in the array. Therefore, filter will return only records of type “HTML” and “formatted note”.

Get the names of all selected records as an array

const names = app.selectedRecords.name();

How it works: selectedRecords is an Object Specifier, not and array, since it is used without appended parenthesis. Therefore, you can dereference its properties, in this case name. The parenthesis appended to this property convert it to an array. This method works with all record properties.

Find all headings in a Markdown record

const headings = MDRecord.plainText().split("\n").filter(para =>
 para.startsWith('#'));

How it works: The plainText property of a markdown record contains the record’s text, which is converted to a string by using the parenthesis. It is then split at every newline, which gives us an array of the text’s paragraphs. The filter condition is true only for paragraphs beginning with a pound sign, thus a markdown headline. Finally, headings is an array containing all the headlines.

Add tag to record

record.tags = record.tags().concat("newTag");

How it works: tags() returns an array of the record’s tags, and concat appends “newTag” to it. Assigning this array to record.tags updates the records tags.

How not to add tag to record

This code does not do what you might think it should do

record.tags = record.tags().push("newTag");

Why it does not work: The push() method appends its parameter to the array. But it does not return the array, only its length. Therefore, the code would try to set the records.tags property to an integer, which is not possible, since tags expects an array.

Delete tag from record

record.tags = record.tags().filter(t => 
  t !== 'removeTag');

How it works: As before, filter returns an array whose elements meet a certain condition. Here, the condition is that a tag is not equal to “removeTag”. Consequently, the array returned by filter contains all tags but “removeTag”.

Get keywords from record as comma-separated string

const metadata = r.metaData();
const keywords = ("kMDItemKeywords" in metadata) ? 
   metadata.kMDItemKeywords : "";

How it works: The metaData() property is an object possibly containing values from the Spotlight collection of metadata. kMDItemKeywords is an array in Spotlight, but DT stores it as a list of comma separated strings. The code returns this string if keywords are set for the record r, otherwise an empty string.

Get keywords from record as an array

const metadata = r.metaData();
const keywords = ("kMDItemKeywords" in metadata) ? 
   metadata.kMDItemKeywords.split(",") : [];

How it works: The code works mostly as described above. The only difference is that it returns an array with the keywords (or an empty one). This array is created by applying split(",") to the comma-separated string of keywords ;

Set custom metadata

app.addCustomMetaData("value", {for: "key", to: record});

How it works: That’s DT’s utility method to set custom metadata. It has the advantage to create the metadata “key” if it doesn’t exist already. The alternative approach

const md = r.customMetaData();
md.key = "value";
r.customMetaData = md;

doesn’t work at all if there are no custom metadata values defined: md will be undefined in that case.

Call shell commands

const curApp = application.currentApplication();
curApp.includeStandardAdditions = true;
const file = "/Users/me/My Folder";
const result = curApp.doShellScript(`command '${file}'`)

How it works: doShellScript can only be called at the object returned by application.currentApplication(), and this object must have its property includeStandardAdditions to true. The simplest way to build the command for doShellScript is to use a template string included in `. If you want to pass one or more files to the shell command, include them in single quotes like above. That escapes special characters in filenames, like spaces. Unescaped, they would break the shell command.

7 Likes

What a gift! Thank you @chrillek :raised_hands:

1 Like

Thanks for sharing some of these tidbits for the JXA-inclined.