Help with Markdown Javascript

Hi

I’ve tried looking for more info in the in-app help and I’m not turning up much info, so I thought I’d ask here.

I’d like to experiment with using the Javascript option in Markdown preferences.

I’ve seen the sample code and discussion here:

What I want to do, is to use javascript to do some really basic syntax highlighting. If the body of the document matches the string “ACTION” then I’ll surround this with a <mark> tag so that it is highlighted (and I may then use custom css to highlight this).

I presume this will take effect if the doc is converted to HTML, and I’m also hoping it’ll take effect in the side-by-side preview.

I’m not seeing much docs about how to reference the actual text of the markdown (html?) document.

Here’s what I have so far. Any kind of example of guidance would help; and I should then be able to take it from there!

const term = "ACTION";
var html = document.innerHTML;
html.replace(new RegExp(term, "gi"), (match) => `<mark>${match}</mark>`);

I’m curious why you don’t just manually use the built-in CriticMarkup option {==highighted text==}.

Are you just trying to learn something new?

@bluefrog was a lot faster than me, but I will still respond to your original questions and comment on your code.

You’re talking about a MD document here, right?

What is “this” here? The string “ACTION”? The document? Something else entirely?

It is, if you’re using the most recent version of DT.

You do not reference the MD, because JavaScript is not executed in the context of MD. There’s no DOM for MD, only for HTML. And this DOM only exists in a user agent, aka browser (or the Webkit preview available in DT). Long story short: check out Mozilla Developer Network for all the gory details.

Your code might in fact be ok (syntactically), but semantically it doesn’t make much sense. The replace method does not change the original string (fortunately), but returns a new one. So html.replace does nothing at all.

Secondly, why are you using a function as a second parameter to the replace call? That’s kind of over the top if you want to simply replace a string.

What you could try:

const term = "ACTION";
const RE = new RegExp("ACTION", "gi");
const html = document.innerHTML;
document.innerHTML = html.replaceAll(RE, `<mark>${term}</mark>`);

Also, why do you use the “i” modifier in the RE, if you only want to highlight “ACTION” (but possibly not “action” nor “re_action_” or “trans_action_”
Which leads me to: If you’re looking for the single word ACTION, you should say so. In which case using a RE makes sense again, as in
new RegExp(`\b${term}\b`, "g")

Also, using document.innerHTML seems way to generous. I suppose that your term occurs only in normal text, i.e. inside paragraphs. So you could do something like

const paras = document.getElementsByTagName('p');
paras.forEach( p => {
  ... work on the ACTION stuff here ...
})

That makes the whole enterprise a lot safer, since you do not risk to (e.g.) modify the word “ACTION” in the head element or somewhere else.

Finally, I’d never have a JS function like that sit around in the HTML. Rather, install it as a DOMContentLoaded handler. Then it will only be run when the DOM is already available. If you just put it into the HTML like here, the user agent will run it as soon as it sees the function. Which is probably before the DOM is even available, making it simply fail.

There’s one example for that here Code highlighting in Markdown documents - #4 by chrillek

and another, more geared towards what you’re doing, here CSS für Devonthink - #13 by chrillek
(the text is in German, the code is not)

Ah, great - Many thanks for the help!

Re: CriticMarkup — this will be useful for indicating amends to text, etc. - and it could be great in some circumstances. But in my case, I have a particular set of keywords I’m trying to match, and I won’t always have control over the markdown itself — other people may have written the document, or they may be consuming them in different programs and not be familiar with CriticMarkup.

The particular documents I have to work with, have the word %ACTION to indicate an action, and %NOTE to indicate a note, etc. The actual text to match isn’t important – it was just an example to be able to highlight lines in a document so I can see at a glance there’s something that needs to be done.

I’d also like to be able to do similar things with other markup, such as TaskPaper, which might appear in MarkDown documents but are not officially supported.

It’s great that DevonThink does now support Critic Markdown, though, and I can definitely see that as being a quick useful way of highlighting things.

Thank, chrillek - the code was really useful, and helped. There’s a lot of refinements I want to make, including just highlighting the one word (e.g “%ACTION”) rather than the whole line; so your examples will be really useful for that.

Here’s what I have got to. This highlights relevant lines, as long as they mention a specific word like “%NOTE” or “%ACTION”, etc. It loops through only the elements that contain text, so it won’t accidentally replace links or tags. Some code borrowed from this post:

Lots of possible improvements; and it’s probably a little in-efficient; but it still works fine for code preview, and I’ll improve it over time.

const highlightMd = (pattern, cssclass, {target = document.body} = {}) => {
	[
		target,
		...target.querySelectorAll("*:not(script):not(noscript):not(style)")
	].forEach(({childNodes: [...nodes]}) => nodes
		.filter(({nodeType}) => nodeType === document.TEXT_NODE)
		.forEach(
			(textNode) =>  {
				if (pattern.test(textNode.textContent)) { 
					var span = document.createElement("span");
					span.className = cssclass;
					var parent = textNode.parentElement;
					parent.insertBefore(span, textNode);
					span.appendChild(textNode);
				}
			})
		);
};

document.addEventListener("DOMContentLoaded", (event) =>  {
	const style = `
		mark { background-color: yellow;} 
		span.done { background-color: green;} 
		span.action { background-color: red;}
		span.note { background-color: yellow;}
		span.question { background-color: blue;}`;
	const styleEl=document.createElement('style');
	styleEl.innerHTML = style;
	document.body.appendChild(styleEl);
	highlightMd(/%ACTION/,'action');
	highlightMd(/%DONE/,'done');	
	highlightMd(/%QUESTION/,'question');	
	highlightMd(/%NOTE/,'question');				
})

Why do you add style verbatim instead of using external css?

Also, I have the impression that the code of overly complex. I actually gave up following what’s happening in this highlightMD function.

I’d use an object at the beginning that associates strings and classnames. Then I’d loop over the interesting DOM elements (querySelectorAll) and run all the replace actions. But all these chained array methods are making it difficult to understand what’s happening.

I suspect the code to do a lot more than is strictly necessary. But only you can know for sure.

1 Like