Skip to content

fix: stop html-escaping template output - #174

Open
alondmnt wants to merge 1 commit into
joplin:masterfrom
alondmnt:fix/handlebars-escaping
Open

alondmnt wants to merge 1 commit into
joplin:masterfrom
alondmnt:fix/handlebars-escaping

Conversation

@alondmnt

@alondmnt alondmnt commented Sep 16, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #173

What

Pass { noEscape: true } to both Handlebars.compile() calls in src/parser.ts, so template output is no longer HTML-escaped.

Why

Characters like ', ", & and < typed into a template variable came out as HTML entities in the generated note. it's a "test" became it&#x27;s a &quot;test&quot;.

Both compile calls used the default options, so every {{ variable }} ran through escapeExpression. That is right for an HTML target, but templates render into markdown notes. The note looks fine in the viewer because Joplin's renderer passes entities through, so the damage only surfaces when you edit, search, export or sync the note.

The only workaround was the triple stache, {{{ variable }}}, which is not discoverable and should not be needed for ordinary text.

How

noEscape on both sites:

  • the note body in parseTemplate
  • parseSpecialVariables, so the problem stops reaching the note title as well as its body

This covers all four special variables, not just the two in the test. template_notebook is a folder ID and template_todo_alarm a date string, and getNoteMetadata throws on an unparseable value for either, so the existing tests for those still hold.

The variable-name validity probe is left alone, since its output is discarded.

Back-compat. {{{ variable }}} behaves identically under noEscape. The test renders the double and triple stache side by side and expects the same output from both, so that is pinned rather than assumed. No existing test covered the old escaping behaviour, so nothing was relying on it.

Testing. The new test goes red against master with the exact reported symptom and green with the change. Full suite 99/99, lint clean. Also verified against a dev build in the app, including dropdown variables (see the comment below).

One thing a reviewer may ask. wrapInQuotes still quotes template_title in the frontmatter while its value is no longer escaped. Those are different stages and do not interact. That function has two separate defects of its own, now filed as #176 and #177 rather than widening this PR.

Templates render into markdown notes, not html, but both Handlebars
compile calls used the default options, so every {{variable}} was run
through escapeExpression. A value containing an apostrophe came out as
&#x27; in the generated note, and the only workaround was the triple
stache {{{variable}}}, which most users never find.

Pass noEscape to both compile sites:

- parseSpecialVariables, which covers all four special variables, so
  template_title and template_tags stop mangling values too
- the note body itself

The variable-name validity probe is left alone, since its output is
discarded.

The test covers the double and triple stache side by side, so the
existing {{{variable}}} behaviour is pinned as unchanged rather than
assumed.

Fixes joplin#173
@alondmnt

Copy link
Copy Markdown
Collaborator Author

Verified against a dev build in the app, not just in jest. A template variable containing it's a "test" & <b> now comes through verbatim in both the note body and the title, and the double and triple stache produce identical output.

Dropdown (enum) variables were checked at the same time and are unaffected. That is worth recording, because it answers a question this fix raised: enum.ts writes its options into the HTML as <option value="${encode(o)}"> and nothing decodes them on return, so if the dialog handed back raw encoded text, dropdowns would still be mangled after this change. They aren't, which confirms values arrive already decoded and that noEscape is the complete fix rather than half of one.

@alondmnt

Copy link
Copy Markdown
Collaborator Author

@nishantwrp mind taking a look at this one? Small diff, but it changes rendering for every existing template, so I'd rather not merge on my own verification alone.

@nishantwrp nishantwrp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only concern is if people use html tags for format their notes (tables, etc.). Maybe make it a template level setting?

@alondmnt

Copy link
Copy Markdown
Collaborator Author

Thanks @nishantwrp. Literal markup isn't affected, Handlebars only escapes the output of {{ }}. I ran a template with a hand-written table and one built with {{#repeat}}, and both come out byte for byte identical before and after. The only lines that change are values, and there it's a fix: a variable holding <b>bold</b> currently arrives as &lt;b&gt;bold&lt;/b&gt; and never renders.

The real change is that angle brackets in a value now reach the renderer raw, so a typed <3 behaves like a <3 typed straight into a note. I think that's right, and the suite passes on master with only noEscape applied, so nothing was relying on the escaping. That's also the only case a setting would cover, and I don't think it earns a frontmatter key and two rendering modes to keep tested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Apostrophes and quotes in variables are converted to HTML entities in the generated note

2 participants