How to run Javascript on Markdown "summarize highlights" output?

Coming a bit late to the party, but here’s a version in JavaScript:

(() => {
	const app = Application("DEVONthink 3");
	/* get all MD records from current selection */
	const records = app.selectedRecords.whose({ _match: [ObjectSpecifier().type, "markdown"] })();
	/* Loop over records */
	records.forEach(r => {
	    /* Split plaintext in paragraphs */
		const txtArray = r.plainText().split("\n");
		const newText = [];
		let heading = "";
		/* Loop over paragraphs */
		txtArray.forEach(txt => {
		    /* Check for level 2 heading */
			let isHeading = txt.match(/^##\s+(.*)/);
			/* Check for bullet list block */
			let isList = txt.match(/^\*\s+(.*)/);
			if (isHeading) {
			    /* Save heading text in temp variable */
				heading = isHeading[1];
			} else if (isList) {
			    /* save new paragraph: 
				  Bullet list block without the bullet and
				  corresponding heading appended in parenthesis 
				 */
				newText.push(`${isList[1]} (${heading})`)
			} else {
			    /* If neither bullet nor heading, save the paragraph as is */
				newText.push(txt);
			}
		})
		r.plainText = newText.join('\n');
	})
})()

If someone needs not the heading but only a part of it, this should be done in the if (isHeading) part. Just set heading to whatever you want to appear in the parenthesis after the (ex-)bullet blocks.

2 Likes

Thanks for the reply @chrillek.

How do we use JavaScript? Not the way Bluefrog explains above for the Applescript i guess. Tried that. Didn’t work.

Copy/paste the code into script editor and save it from there in DT‘s script folder. Then you can run it from the script menu.

1 Like

Oh it did the trick! Thanks a lot!

I managed to solve this both in Apple Script and Javascript.
Now I get the Heading 1 in the parentheses.

This is the Applescript version:

on performSmartRule(theRecords)
	tell application id "DNtp"
		repeat with theRecord in theRecords
			if (type of theRecord as string) = "markdown" then
				-- Only process Markdown docs
				
				set newText to {}
				-- Set up an empty list to push the paragraphs into)
				
				set currentText to (plain text of theRecord)
				-- Get the current text of the document
				
				set titleText to (characters 3 thru -1 of paragraph 1 of currentText as string) -- Cache all but the control characters in a variable     
				
				repeat with thisLine in (paragraphs of currentText)
					-- Loop through the document, paragraph by paragraph
					
					if thisLine begins with "##" then
						-- If this is an H2 page link
						set cachedText to (characters 4 thru -1 of thisLine as string) -- Cache all but the control characters in a variable
						
					else if thisLine begins with "* " then
						-- If it's just a bulleted paragraph
						
						copy ((characters 3 thru -1 of thisLine as string) & " (" & (titleText) & ": " & (cachedText) & ") " & return) to end of newText
						-- Get all but the control characters and concatenate the cachedText.
						-- Then push it to the newText list
					else
						copy thisLine & return to end of newText
						-- Anything else, including the H1 line at the top, just push into the newText list
					end if
				end repeat
				
				set plain text of theRecord to (newText as string)
				-- Set the text of the document to the new text
				
			end if
		end repeat
	end tell
end performSmartRule

And this is the Javascript version:

(() => {
	const app = Application("DEVONthink 3");
	/* get all MD records from current selection */
	const records = app.selectedRecords.whose({ _match: [ObjectSpecifier().type, "markdown"] })();
	/* Loop over records */
	records.forEach(r => {
	    /* Split plaintext in paragraphs */
		const txtArray = r.plainText().split("\n");
		const newText = [];
		let heading = "";
		let heading1 = "";

		txtArray.every(txt => {
			/* Loop over paragraphs */
			let isHeading1 = txt.match(/^#\s+(.*)/);
			if (isHeading1) {
				heading1 = isHeading1[1];
				return false;
			}
			return true;
		})
		
		txtArray.forEach(txt => {
			/* Check for level 2 heading */
			let isHeading = txt.match(/^##\s+(.*)/);
			/* Check fior bullet list block */
			let isList = txt.match(/^\*\s+(.*)/);
			if (isHeading) {
			    /* Save heading text in temp variable */
				heading = isHeading[1];
			} else if (isList) {
			    /* save new paragraph: 
				  Bullet list block without the bullet and
				  corresponding heading appended in parenthesis 
				 */
				newText.push(`${isList[1]} (${heading1}: ${heading})`)
			} else {
			    /* If neither bullet nor heading, save the paragraph as is */
				newText.push(txt);
			}
		})
		r.plainText = newText.join('\n');
	})
})()

The AppleScript is for use in a smart rule, the JavaScript is not (yet). It’s not too difficult to make it work as script in a smart rule, too.

In the JavaScript, there’s no need for let. unless you modify the variable later on. Otherwise, use const. Also, you seem to have two loops over the same array – why?

If anyone does want to tidy the JS up a little and adapt it for a smart rule, then a comment I might add to @chrillek’s good points is that the idiom:

  • declare a []
  • .forEach.push

may be making life a bit complicated, especially for new users, when you can just define the new array that you want directly, in terms of .map or, even more flexibly: .flatMap, or .reduce

1 Like