/**
* highlight-markdown — read-only `==text==` → `text` transform.
*
* The editor (Tiptap) parses `==text==` natively via the HighlightExtension's
* markdownTokenizer. The read-only surface uses react-markdown, which has no
* notion of `==` highlight syntax, so we lower it to a raw `` element here.
* readonly-content.tsx already runs `rehype-raw` (so the raw `` becomes a
* real element) and whitelists `mark` in its sanitize schema. Because the inner
* text is left untouched, nested Markdown inside a highlight (e.g. `==**bold**==`
* or an inline `` `code` ``) is still parsed by remark — matching the editor.
*
* The matching rules live in highlight-match.ts and are shared with the editor
* tokenizer, so the storage ↔ editor ↔ read-only boundary can never drift:
* fences may not sit inside code/math, and a highlight may not cross a blank
* line / block boundary.
*/
import { findLiteralRanges, matchHighlightAt } from "./highlight-match";
/**
* Lower `==text==` highlight syntax to raw `` for the react-markdown
* read-only pipeline. No-op when the text contains no `==`.
*/
export function highlightToHtml(markdown: string): string {
if (!markdown.includes("==")) return markdown;
const ranges = findLiteralRanges(markdown);
let result = "";
let cursor = 0;
let i = markdown.indexOf("==");
while (i !== -1) {
const match = matchHighlightAt(markdown, i, ranges);
if (match) {
result += markdown.slice(cursor, i);
result += `${match.inner}`;
cursor = match.end;
i = markdown.indexOf("==", match.end);
} else {
// Not a highlight here — advance past this `==` and keep looking.
i = markdown.indexOf("==", i + 2);
}
}
result += markdown.slice(cursor);
return result;
}