From 6567a871b8e09ff6287652b47584058c85fb0bf6 Mon Sep 17 00:00:00 2001 From: John McLear Date: Fri, 17 Apr 2026 15:44:31 +0100 Subject: [PATCH] fix: export links cleanly inside formatted lines + don't escape _ in code blocks (#156, part) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two export-to-Markdown bugs from #156: 1. A link on a line that is also bold/italic came out as \`**bold prefix**[link text](**https://linkurl**)\`. The URL handler was stepping past the \`[\` without closing the open format tags, so every URL character produced another \`**\` inside the link token. Close any currently-open inline tags before emitting the URL, write the URL text verbatim, and reopen the tags after the closing \`)\`. 2. Underscores inside a \`heading: 'code'\` line were globally escaped to \`\\_\` by the final \`assem.replace(/_/g, '\\\\_')\`. Identifiers in code blocks should stay literal, so the renderer doesn't turn \`snake_case\` into \`snake\\\\_case\`. Skip the underscore escape on code-styled lines. Doesn't address the other two items in #156 (stray indentation turning into a code block, and bold-list markers landing outside the \`-\`) — those need follow-ups. Co-Authored-By: Claude Opus 4.7 (1M context) --- exportMarkdown.ts | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/exportMarkdown.ts b/exportMarkdown.ts index 7fe829d..e38c6b7 100644 --- a/exportMarkdown.ts +++ b/exportMarkdown.ts @@ -218,9 +218,27 @@ const getMarkdownFromAtext = (pad, atext) => { const url = urlData[1]; const urlLength = url.length; processNextChars(startIndex - idx); + // Close any currently-open inline format tags (bold, italic, etc.) + // before writing the URL. If we don't, processing the URL's chars + // re-emits `**` / `*` markers *inside* the Markdown link token, + // producing broken output like `[url](**https://example.com**)` + // (regression for #156). + const reopen = [...openTags]; + const tags2close = [...openTags]; + orderdCloseTags(tags2close); + for (let i = 0; i < propVals.length; i++) { propVals[i] = false; } assem.append(`[${url}](`); - processNextChars(urlLength); + // Emit the URL's chars as raw text — links in Markdown never + // contain inline formatting markers. + assem.append(taker.take(urlLength)); + idx += urlLength; assem.append(')'); + // Restore the formatting tags so any trailing same-line text picks + // them back up. + for (const i of reopen.slice().reverse()) { + emitOpenTag(i); + propVals[i] = true; + } }); } @@ -229,8 +247,15 @@ const getMarkdownFromAtext = (pad, atext) => { // replace &, _ assem = assem.toString(); assem = assem.replace(/&/g, '\\&'); - // this breaks Markdown math mode: $\sum_i^j$ becomes $\sum\_i^j$ - assem = assem.replace(/_/g, '\\_'); + // Only escape underscores OUTSIDE code spans / code blocks. On a line + // with the `heading: 'code'` attribute (rendered with the 4-space + // block-code prefix) underscores should be preserved verbatim, + // otherwise `myVar_name` comes out as `myVar\_name` in the exported + // Markdown rendering (regression for #156). Math-mode ($...$) still + // has no special handling here — that is a separate concern. + if (heading !== headingtags[6]) { + assem = assem.replace(/_/g, '\\_'); + } return assem; };