@mrsf/markdown-it-mrsf 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/controller.ts"],
4
- "sourcesContent": ["/* ---------------------------------------------------------------\n * AUTO-GENERATED \u2014 DO NOT EDIT\n *\n * Source: plugins/shared/src/controller.ts\n * Run `node plugins/sync-types.mjs` to regenerate.\n * --------------------------------------------------------------- */\n\n/**\n * Sidemark \u2014 Client-side event controller for interactive mode.\n *\n * Optional module that listens for clicks on `[data-mrsf-action]` elements\n * and dispatches CustomEvents on the document for host applications.\n *\n * Usage (ESM):\n * import \"@mrsf/plugin-shared/controller\";\n *\n * Events dispatched:\n * - mrsf:resolve { commentId, line }\n * - mrsf:unresolve { commentId, line }\n * - mrsf:reply { commentId, line }\n * - mrsf:edit { commentId, line }\n * - mrsf:navigate { commentId, line }\n */\n\nexport type MrsfAction = \"resolve\" | \"unresolve\" | \"reply\" | \"edit\" | \"navigate\";\n\nexport interface MrsfActionDetail {\n commentId: string;\n line: number | null;\n action: MrsfAction;\n}\n\nfunction init(): void {\n document.addEventListener(\"click\", (e) => {\n const target = (e.target as HTMLElement).closest<HTMLElement>(\n \"[data-mrsf-action]\",\n );\n if (!target) return;\n\n const action = target.dataset.mrsfAction as MrsfAction | undefined;\n const commentId = target.dataset.mrsfCommentId;\n if (!action || !commentId) return;\n\n const lineStr = target.dataset.mrsfLine;\n const line = lineStr ? parseInt(lineStr, 10) : null;\n\n e.preventDefault();\n e.stopPropagation();\n\n const detail: MrsfActionDetail = { commentId, line, action };\n document.dispatchEvent(\n new CustomEvent(`mrsf:${action}`, { detail, bubbles: true }),\n );\n });\n}\n\n// Auto-initialize\ninit();\n"],
5
- "mappings": ";AAgCA,SAAS,OAAa;AACpB,WAAS,iBAAiB,SAAS,CAAC,MAAM;AACxC,UAAM,SAAU,EAAE,OAAuB;AAAA,MACvC;AAAA,IACF;AACA,QAAI,CAAC,OAAQ;AAEb,UAAM,SAAS,OAAO,QAAQ;AAC9B,UAAM,YAAY,OAAO,QAAQ;AACjC,QAAI,CAAC,UAAU,CAAC,UAAW;AAE3B,UAAM,UAAU,OAAO,QAAQ;AAC/B,UAAM,OAAO,UAAU,SAAS,SAAS,EAAE,IAAI;AAE/C,MAAE,eAAe;AACjB,MAAE,gBAAgB;AAElB,UAAM,SAA2B,EAAE,WAAW,MAAM,OAAO;AAC3D,aAAS;AAAA,MACP,IAAI,YAAY,QAAQ,MAAM,IAAI,EAAE,QAAQ,SAAS,KAAK,CAAC;AAAA,IAC7D;AAAA,EACF,CAAC;AACH;AAGA,KAAK;",
3
+ "sources": ["../src/html.ts", "../src/controller.ts"],
4
+ "sourcesContent": ["/* ---------------------------------------------------------------\n * AUTO-GENERATED \u2014 DO NOT EDIT\n *\n * Source: plugins/shared/src/html.ts\n * Run `node plugins/sync-types.mjs` to regenerate.\n * --------------------------------------------------------------- */\n\n/**\n * Shared HTML rendering helpers for MRSF rendering plugins.\n *\n * These produce the HTML strings used in tooltips, badges, and comment\n * rendering. Both the markdown-it and rehype plugins use these functions\n * to ensure identical visual output.\n */\n\nimport type { CommentThread, SlimComment } from \"./types.js\";\n\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nexport function formatTime(iso: string | null): string {\n if (!iso) return \"\";\n try {\n const d = new Date(iso);\n return d.toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n } catch {\n return \"\";\n }\n}\n\nexport function renderCommentHtml(\n comment: SlimComment,\n isReply: boolean,\n interactive: boolean,\n): string {\n const resolvedClass = comment.resolved ? \" mrsf-resolved\" : \"\";\n const replyClass = isReply ? \" mrsf-reply\" : \"\";\n let html = `<span class=\"mrsf-comment${resolvedClass}${replyClass}\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\">`;\n\n // Header\n html += `<span class=\"mrsf-comment-header\">`;\n html += `<span class=\"mrsf-author\">${escapeHtml(comment.author)}</span>`;\n if (comment.timestamp) {\n html += `<span class=\"mrsf-date\">${escapeHtml(formatTime(comment.timestamp))}</span>`;\n }\n if (comment.severity) {\n html += `<span class=\"mrsf-severity mrsf-severity-${escapeHtml(comment.severity)}\">${escapeHtml(comment.severity)}</span>`;\n }\n if (comment.type) {\n html += `<span class=\"mrsf-type\">${escapeHtml(comment.type)}</span>`;\n }\n if (comment.resolved) {\n html += `<span class=\"mrsf-resolved-badge\">\u2713 resolved</span>`;\n }\n html += `</span>`;\n\n // Selected text quote (collapsible)\n if (comment.selected_text) {\n html += `<details class=\"mrsf-selected-text\"><summary class=\"mrsf-selected-text-summary\">${escapeHtml(comment.selected_text)}</summary><span class=\"mrsf-selected-text-full\">${escapeHtml(comment.selected_text)}</span></details>`;\n }\n\n // Body\n html += `<span class=\"mrsf-comment-body\">${escapeHtml(comment.text)}</span>`;\n\n // Action buttons (interactive mode)\n if (interactive) {\n const line = comment.line != null ? String(comment.line) : \"\";\n html += `<span class=\"mrsf-actions\">`;\n if (comment.resolved) {\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"unresolve\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Unresolve</button>`;\n } else {\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"resolve\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Resolve</button>`;\n }\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"reply\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Reply</button>`;\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"edit\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Edit</button>`;\n html += `<button class=\"mrsf-action-btn mrsf-action-danger\" data-mrsf-action=\"delete\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Delete</button>`;\n html += `</span>`;\n }\n\n html += `</span>`;\n return html;\n}\n\nexport function renderThreadHtml(thread: CommentThread, interactive: boolean): string {\n let html = `<span class=\"mrsf-thread\">`;\n html += renderCommentHtml(thread.comment, false, interactive);\n if (thread.replies.length > 0) {\n html += `<span class=\"mrsf-replies\">`;\n for (const reply of thread.replies) {\n html += renderCommentHtml(reply, true, interactive);\n }\n html += `</span>`;\n }\n html += `</span>`;\n return html;\n}\n", "/* ---------------------------------------------------------------\n * AUTO-GENERATED \u2014 DO NOT EDIT\n *\n * Source: plugins/shared/src/controller.ts\n * Run `node plugins/sync-types.mjs` to regenerate.\n * --------------------------------------------------------------- */\n\n/**\n * Sidemark \u2014 MrsfController: overlay gutter architecture.\n *\n * The controller is the runtime engine for interactive MRSF rendering.\n * It reads comment data from the DOM (embedded `<script type=\"application/mrsf+json\">`)\n * or constructor options, creates gutter overlay columns, positions\n * badges/buttons at the correct vertical offsets, and handles all\n * user interactions (tooltips, selection, action buttons).\n *\n * Usage:\n * import { MrsfController } from \"@mrsf/plugin-shared/controller\";\n *\n * const ctrl = new MrsfController(document.querySelector(\".my-content\")!, {\n * interactive: true,\n * gutterPosition: \"left\",\n * });\n * // ctrl.destroy() to clean up\n *\n * Events dispatched on document:\n * - mrsf:resolve { commentId, line, ... }\n * - mrsf:unresolve { commentId, line, ... }\n * - mrsf:reply { commentId, line, ... }\n * - mrsf:edit { commentId, line, ... }\n * - mrsf:delete { commentId, line, ... }\n * - mrsf:navigate { commentId, line, ... }\n * - mrsf:add { commentId: null, line, selectionText?, ... }\n * - mrsf:submit { action, commentId, text?, line?, ... }\n */\n\nimport type { CommentThread, SlimComment } from \"./types.js\";\nimport { renderThreadHtml, escapeHtml } from \"./html.js\";\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type MrsfAction = \"resolve\" | \"unresolve\" | \"reply\" | \"edit\" | \"delete\" | \"navigate\" | \"add\";\n\nexport interface MrsfActionDetail {\n commentId: string | null;\n line: number | null;\n action: MrsfAction;\n selectionText?: string | null;\n start_line?: number | null;\n end_line?: number | null;\n start_column?: number | null;\n end_column?: number | null;\n}\n\nexport interface MrsfSubmitDetail {\n action: \"add\" | \"edit\" | \"reply\" | \"resolve\" | \"unresolve\" | \"delete\";\n commentId: string | null;\n text: string;\n type?: string | null;\n severity?: \"low\" | \"medium\" | \"high\" | null;\n line?: number | null;\n end_line?: number | null;\n start_column?: number | null;\n end_column?: number | null;\n selection_text?: string | null;\n}\n\nexport interface MrsfControllerOptions {\n /** Show gutter on left or right side. Default: \"right\". */\n gutterPosition?: \"left\" | \"right\";\n /** Enable interactive actions (add, resolve, reply, etc.). Default: false. */\n interactive?: boolean;\n /** Comment data passed directly (overrides embedded script). */\n comments?: CommentThread[];\n /**\n * Render inline text highlights for comments that have `selected_text`.\n * Wraps matching text in `<mark>` elements with hover tooltips.\n * Default: true.\n */\n inlineHighlights?: boolean;\n}\n\n// \u2500\u2500 MrsfController \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class MrsfController {\n private container: HTMLElement;\n private opts: Required<MrsfControllerOptions>;\n private threads: Map<number, CommentThread[]> = new Map();\n private gutterLeft: HTMLDivElement | null = null;\n private gutterRight: HTMLDivElement | null = null;\n private activeTooltip: HTMLElement | null = null;\n private floatingAddButton: HTMLButtonElement | null = null;\n private overlayEl: HTMLDivElement | null = null;\n private lastSelectionText: string | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private styleInjected = false;\n private inlineMarks: HTMLElement[] = [];\n private inlineTooltipEl: HTMLElement | null = null;\n private orphanedSection: HTMLDivElement | null = null;\n\n private handleResizeBound = this.positionGutterItems.bind(this);\n private handleClickBound = this.handleClick.bind(this);\n private handleSelectionBound = this.handleSelectionChange.bind(this);\n\n constructor(container: HTMLElement, options: MrsfControllerOptions = {}) {\n this.container = container;\n this.opts = {\n gutterPosition: options.gutterPosition ?? \"right\",\n interactive: options.interactive ?? false,\n comments: options.comments ?? [],\n inlineHighlights: options.inlineHighlights ?? true,\n };\n\n this.loadCommentData();\n this.setupOverlayStructure();\n this.renderGutterItems();\n this.positionGutterItems();\n this.renderInlineHighlights();\n this.renderOrphanedSection();\n\n // Listeners\n this.resizeObserver = new ResizeObserver(this.handleResizeBound);\n this.resizeObserver.observe(this.container);\n document.addEventListener(\"click\", this.handleClickBound);\n if (this.opts.interactive) {\n document.addEventListener(\"selectionchange\", this.handleSelectionBound);\n }\n }\n\n /** Remove all controller DOM and listeners. */\n destroy(): void {\n this.resizeObserver?.disconnect();\n document.removeEventListener(\"click\", this.handleClickBound);\n document.removeEventListener(\"selectionchange\", this.handleSelectionBound);\n this.removeInlineHighlights();\n this.orphanedSection?.remove();\n this.gutterLeft?.remove();\n this.gutterRight?.remove();\n this.floatingAddButton?.remove();\n this.closeOverlay();\n this.container.classList.remove(\"mrsf-overlay-root\");\n }\n\n // \u2500\u2500 Data loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private loadCommentData(): void {\n // Priority: constructor options > embedded script\n if (this.opts.comments.length > 0) {\n this.buildThreadMap(this.opts.comments);\n return;\n }\n\n const script = this.container.querySelector('script[type=\"application/mrsf+json\"]');\n if (!script?.textContent) return;\n\n try {\n const data = JSON.parse(script.textContent) as { threads?: CommentThread[] };\n if (data.threads) {\n this.buildThreadMap(data.threads);\n }\n } catch {\n // silently ignore malformed data\n }\n }\n\n private buildThreadMap(threads: CommentThread[]): void {\n this.threads.clear();\n for (const t of threads) {\n const line = t.comment.line;\n if (line == null) continue;\n const arr = this.threads.get(line) ?? [];\n arr.push(t);\n this.threads.set(line, arr);\n }\n }\n\n // \u2500\u2500 Overlay structure \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private setupOverlayStructure(): void {\n this.container.classList.add(\"mrsf-overlay-root\");\n\n const pos = this.opts.gutterPosition;\n if (pos === \"left\") {\n this.gutterLeft = this.createGutter(\"mrsf-gutter-left\");\n } else {\n this.gutterRight = this.createGutter(\"mrsf-gutter-right\");\n }\n }\n\n private createGutter(cls: string): HTMLDivElement {\n const gutter = document.createElement(\"div\");\n gutter.className = `mrsf-gutter ${cls}`;\n this.container.appendChild(gutter);\n return gutter;\n }\n\n // \u2500\u2500 Gutter rendering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Build badge/add-button elements for each line in the gutter(s). */\n private renderGutterItems(): void {\n const lines = this.collectLines();\n const gutter = this.primaryGutter();\n if (!gutter) return;\n\n for (const line of lines) {\n const threads = this.threads.get(line);\n if (threads && threads.length > 0) {\n const item = this.createBadgeItem(line, threads);\n gutter.appendChild(item);\n } else if (this.opts.interactive) {\n const item = this.createAddItem(line);\n gutter.appendChild(item);\n }\n }\n }\n\n private primaryGutter(): HTMLDivElement | null {\n return this.gutterLeft ?? this.gutterRight;\n }\n\n /** Collect all unique line numbers from data-mrsf-line elements, expanding multi-line ranges. */\n private collectLines(): number[] {\n const els = this.container.querySelectorAll<HTMLElement>(\"[data-mrsf-line]\");\n const seen = new Set<number>();\n for (const el of els) {\n if (el.tagName === \"SCRIPT\") continue;\n const line = parseInt(el.dataset.mrsfLine!, 10);\n if (isNaN(line)) continue;\n const startLine = parseInt(el.dataset.mrsfStartLine ?? \"\", 10);\n const endLine = parseInt(el.dataset.mrsfEndLine ?? \"\", 10);\n if (!isNaN(startLine) && !isNaN(endLine) && endLine > startLine) {\n for (let l = startLine; l <= endLine; l++) seen.add(l);\n } else {\n seen.add(line);\n }\n }\n return [...seen].sort((a, b) => a - b);\n }\n\n private createBadgeItem(line: number, threads: CommentThread[]): HTMLDivElement {\n const item = document.createElement(\"div\");\n item.className = \"mrsf-gutter-item\";\n item.dataset.mrsfGutterLine = String(line);\n\n const total = threads.reduce((n, t) => n + 1 + t.replies.length, 0);\n const allResolved = threads.every((t) => t.comment.resolved);\n const highestSeverity = threads.reduce<string | null>((sev, t) => {\n if (t.comment.severity === \"high\" || sev === \"high\") return \"high\";\n if (t.comment.severity === \"medium\" || sev === \"medium\") return \"medium\";\n if (t.comment.severity === \"low\" || sev === \"low\") return \"low\";\n return sev;\n }, null);\n\n const classes = [\"mrsf-badge\"];\n if (allResolved) classes.push(\"mrsf-badge-resolved\");\n if (highestSeverity === \"high\" || highestSeverity === \"medium\") {\n classes.push(`mrsf-badge-severity-${highestSeverity}`);\n }\n\n const icon = allResolved ? \"\u2713\" : \"\uD83D\uDCAC\";\n const badge = document.createElement(\"span\");\n badge.className = classes.join(\" \");\n badge.dataset.mrsfLine = String(line);\n badge.dataset.mrsfAction = \"navigate\";\n badge.dataset.mrsfCommentId = threads[0].comment.id;\n badge.tabIndex = 0;\n badge.textContent = `${icon} ${total}`;\n badge.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.toggleTooltip(item, line, threads);\n });\n\n item.appendChild(badge);\n\n if (this.opts.interactive) {\n const addBtn = this.createAddButton(line);\n item.appendChild(addBtn);\n }\n\n return item;\n }\n\n private createAddItem(line: number): HTMLDivElement {\n const item = document.createElement(\"div\");\n item.className = \"mrsf-gutter-item\";\n item.dataset.mrsfGutterLine = String(line);\n\n const addBtn = this.createAddButton(line);\n item.appendChild(addBtn);\n return item;\n }\n\n private createAddButton(line: number): HTMLButtonElement {\n const btn = document.createElement(\"button\");\n btn.className = \"mrsf-gutter-add\";\n btn.dataset.mrsfAction = \"add\";\n btn.dataset.mrsfLine = String(line);\n btn.dataset.mrsfStartLine = String(line);\n btn.dataset.mrsfEndLine = String(line);\n btn.setAttribute(\"aria-label\", \"Add comment\");\n btn.textContent = \"+\";\n return btn;\n }\n\n // \u2500\u2500 Positioning \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Measure [data-mrsf-line] elements and set gutter item Y offsets. */\n positionGutterItems(): void {\n const containerRect = this.container.getBoundingClientRect();\n\n const gutters = [this.gutterLeft, this.gutterRight].filter(Boolean) as HTMLDivElement[];\n for (const gutter of gutters) {\n const items = gutter.querySelectorAll<HTMLDivElement>(\".mrsf-gutter-item\");\n for (const item of items) {\n const line = parseInt(item.dataset.mrsfGutterLine!, 10);\n // For expanded multi-line elements, find the element whose range contains this line\n let target = this.container.querySelector<HTMLElement>(\n `[data-mrsf-line=\"${line}\"]:not(script):not(.mrsf-gutter-item)`,\n );\n if (!target) {\n target = this.findElementForLine(line);\n }\n if (!target) {\n item.style.display = \"none\";\n continue;\n }\n const targetRect = target.getBoundingClientRect();\n const top = targetRect.top - containerRect.top;\n item.style.top = `${top}px`;\n item.style.display = \"\";\n }\n }\n }\n\n /**\n * Find the element whose start-line/end-line range contains the given line.\n * Used for positioning gutter items on expanded multi-line elements.\n */\n private findElementForLine(line: number): HTMLElement | null {\n const els = this.container.querySelectorAll<HTMLElement>(\"[data-mrsf-start-line][data-mrsf-end-line]\");\n for (const el of els) {\n if (el.tagName === \"SCRIPT\") continue;\n const start = parseInt(el.dataset.mrsfStartLine!, 10);\n const end = parseInt(el.dataset.mrsfEndLine!, 10);\n if (!isNaN(start) && !isNaN(end) && line >= start && line <= end) {\n return el;\n }\n }\n return null;\n }\n\n // \u2500\u2500 Orphaned comments section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Render orphaned comments (whose line doesn't match any DOM element)\n * in a dedicated section at the bottom of the container.\n */\n private renderOrphanedSection(): void {\n const lines = this.collectLines();\n const lineSet = new Set(lines);\n const orphanedThreads: CommentThread[] = [];\n\n for (const [line, threads] of this.threads) {\n if (!lineSet.has(line)) {\n orphanedThreads.push(...threads);\n }\n }\n\n if (orphanedThreads.length === 0) return;\n\n const section = document.createElement(\"div\");\n section.className = \"mrsf-orphaned-section\";\n\n const heading = document.createElement(\"div\");\n heading.className = \"mrsf-orphaned-heading\";\n heading.textContent = `Orphaned Comments (${orphanedThreads.length})`;\n section.appendChild(heading);\n\n const interactive = this.opts.interactive;\n for (const thread of orphanedThreads) {\n const wrapper = document.createElement(\"div\");\n wrapper.className = interactive\n ? \"mrsf-orphaned-thread mrsf-interactive\"\n : \"mrsf-orphaned-thread\";\n wrapper.innerHTML = renderThreadHtml(thread, interactive);\n section.appendChild(wrapper);\n }\n\n this.container.appendChild(section);\n this.orphanedSection = section;\n }\n\n // \u2500\u2500 Inline text highlights \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * For comments with `selected_text`, find the matching text in the DOM\n * and wrap it in a `<mark class=\"mrsf-inline-highlight\">` element with\n * hover/click behaviour to show the comment tooltip.\n */\n private renderInlineHighlights(): void {\n if (!this.opts.inlineHighlights) return;\n\n for (const [line, threads] of this.threads) {\n for (const thread of threads) {\n const comment = thread.comment;\n if (!comment.selected_text) continue;\n\n const el = this.container.querySelector<HTMLElement>(\n `[data-mrsf-line=\"${line}\"]:not(script):not(.mrsf-gutter):not(.mrsf-gutter-item)`,\n );\n if (!el) continue;\n\n this.wrapSelectedText(el, comment.selected_text, thread);\n }\n }\n }\n\n /**\n * Strip common inline markdown syntax so `selected_text` from source\n * can be matched against rendered text content.\n */\n private static stripInlineMarkdown(text: string): string {\n let s = text;\n // Backtick code spans: `code` \u2192 code\n s = s.replace(/`([^`]+)`/g, \"$1\");\n // Bold: **text** or __text__\n s = s.replace(/\\*\\*(.+?)\\*\\*/g, \"$1\");\n s = s.replace(/__(.+?)__/g, \"$1\");\n // Italic: *text* or _text_\n s = s.replace(/\\*(.+?)\\*/g, \"$1\");\n s = s.replace(/_(.+?)_/g, \"$1\");\n // Strikethrough: ~~text~~\n s = s.replace(/~~(.+?)~~/g, \"$1\");\n return s;\n }\n\n /**\n * Walk text nodes inside `root` to find `text`, then wrap the matching\n * range in a `<mark>` element. Falls back to markdown-stripped matching.\n */\n private wrapSelectedText(\n root: HTMLElement,\n selectedText: string,\n thread: CommentThread,\n ): void {\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n let accumulated = \"\";\n const textNodes: { node: Text; start: number; end: number }[] = [];\n\n let node: Text | null;\n while ((node = walker.nextNode() as Text | null)) {\n const start = accumulated.length;\n accumulated += node.textContent || \"\";\n textNodes.push({ node, start, end: accumulated.length });\n }\n\n // Try exact match first, then stripped markdown\n let matchStart = accumulated.indexOf(selectedText);\n let matchLen = selectedText.length;\n if (matchStart === -1) {\n const stripped = MrsfController.stripInlineMarkdown(selectedText);\n if (stripped !== selectedText) {\n matchStart = accumulated.indexOf(stripped);\n matchLen = stripped.length;\n }\n }\n if (matchStart === -1) return;\n\n const matchEnd = matchStart + matchLen;\n\n // Build a Range spanning the matched text nodes\n const range = document.createRange();\n let startSet = false;\n\n for (const tn of textNodes) {\n if (!startSet && tn.end > matchStart) {\n range.setStart(tn.node, matchStart - tn.start);\n startSet = true;\n }\n if (startSet && tn.end >= matchEnd) {\n range.setEnd(tn.node, matchEnd - tn.start);\n break;\n }\n }\n\n if (!startSet) return;\n\n const mark = document.createElement(\"mark\");\n mark.className = \"mrsf-inline-highlight\";\n mark.dataset.mrsfCommentId = thread.comment.id;\n mark.dataset.mrsfLine = String(thread.comment.line);\n\n try {\n range.surroundContents(mark);\n } catch {\n // Range crosses element boundaries \u2014 extract and re-insert\n const fragment = range.extractContents();\n mark.appendChild(fragment);\n range.insertNode(mark);\n }\n\n this.inlineMarks.push(mark);\n\n // Hover shows tooltip inline\n mark.addEventListener(\"mouseenter\", () => {\n this.showInlineTooltip(mark, thread);\n });\n mark.addEventListener(\"mouseleave\", (e) => {\n // Don't hide if moving into the tooltip itself\n const related = (e as MouseEvent).relatedTarget as HTMLElement | null;\n if (related && this.inlineTooltipEl?.contains(related)) return;\n this.scheduleHideInlineTooltip();\n });\n\n // Click toggles tooltip (for touch / accessibility)\n mark.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (this.inlineTooltipEl && this.inlineTooltipEl.dataset.mrsfForMark === thread.comment.id) {\n this.hideInlineTooltip();\n } else {\n this.showInlineTooltip(mark, thread);\n }\n });\n }\n\n private showInlineTooltip(mark: HTMLElement, thread: CommentThread): void {\n this.hideInlineTooltip();\n\n const tooltip = document.createElement(\"div\");\n tooltip.className = this.opts.interactive\n ? \"mrsf-inline-tooltip mrsf-interactive mrsf-tooltip-visible\"\n : \"mrsf-inline-tooltip mrsf-tooltip-visible\";\n tooltip.dataset.mrsfForMark = thread.comment.id;\n tooltip.innerHTML = renderThreadHtml(thread, this.opts.interactive);\n\n // Let user mouse into tooltip without it disappearing\n tooltip.addEventListener(\"mouseenter\", () => {\n this.cancelHideInlineTooltip();\n });\n tooltip.addEventListener(\"mouseleave\", () => {\n this.hideInlineTooltip();\n });\n\n // Append to body with fixed positioning to avoid clipping\n document.body.appendChild(tooltip);\n this.inlineTooltipEl = tooltip;\n\n // Position relative to the mark element\n const rect = mark.getBoundingClientRect();\n const margin = 4;\n const tooltipH = tooltip.offsetHeight;\n\n // Prefer below; flip above if not enough space at bottom\n if (rect.bottom + margin + tooltipH > window.innerHeight) {\n tooltip.style.top = `${rect.top - tooltipH - margin}px`;\n } else {\n tooltip.style.top = `${rect.bottom + margin}px`;\n }\n tooltip.style.left = `${Math.max(0, rect.left)}px`;\n }\n\n private hideInlineTimeout: ReturnType<typeof setTimeout> | null = null;\n\n private scheduleHideInlineTooltip(): void {\n this.hideInlineTimeout = setTimeout(() => this.hideInlineTooltip(), 120);\n }\n\n private cancelHideInlineTooltip(): void {\n if (this.hideInlineTimeout) {\n clearTimeout(this.hideInlineTimeout);\n this.hideInlineTimeout = null;\n }\n }\n\n private hideInlineTooltip(): void {\n this.cancelHideInlineTooltip();\n if (this.inlineTooltipEl) {\n this.inlineTooltipEl.remove();\n this.inlineTooltipEl = null;\n }\n }\n\n /** Remove all inline marks, unwrapping their contents back to text. */\n private removeInlineHighlights(): void {\n this.hideInlineTooltip();\n for (const mark of this.inlineMarks) {\n const parent = mark.parentNode;\n if (!parent) continue;\n while (mark.firstChild) {\n parent.insertBefore(mark.firstChild, mark);\n }\n parent.removeChild(mark);\n }\n this.inlineMarks = [];\n }\n\n // \u2500\u2500 Tooltip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private toggleTooltip(anchor: HTMLElement, line: number, threads: CommentThread[]): void {\n // If already visible on this anchor, hide it\n if (this.activeTooltip && this.activeTooltip.parentElement === anchor) {\n this.hideTooltip();\n return;\n }\n this.hideTooltip();\n this.showTooltip(anchor, line, threads);\n }\n\n private showTooltip(anchor: HTMLElement, line: number, threads: CommentThread[]): void {\n const tooltip = document.createElement(\"div\");\n const interactive = this.opts.interactive;\n tooltip.className = interactive\n ? \"mrsf-tooltip mrsf-interactive mrsf-tooltip-visible\"\n : \"mrsf-tooltip mrsf-tooltip-visible\";\n tooltip.dataset.mrsfLine = String(line);\n\n let html = \"\";\n for (const thread of threads) {\n html += renderThreadHtml(thread, interactive);\n }\n if (interactive) {\n html += `<div class=\"mrsf-tooltip-actions\"><button class=\"mrsf-action-btn\" data-mrsf-action=\"add\" data-mrsf-line=\"${line}\" data-mrsf-start-line=\"${line}\" data-mrsf-end-line=\"${line}\">Add comment</button></div>`;\n }\n tooltip.innerHTML = html;\n\n anchor.appendChild(tooltip);\n this.activeTooltip = tooltip;\n }\n\n private hideTooltip(): void {\n if (this.activeTooltip) {\n this.activeTooltip.remove();\n this.activeTooltip = null;\n }\n }\n\n // \u2500\u2500 Click handling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private handleClick(e: Event): void {\n const target = (e.target as HTMLElement).closest<HTMLElement>(\"[data-mrsf-action]\");\n\n // Close tooltip when clicking outside\n if (!target && this.activeTooltip) {\n const tooltipClick = (e.target as HTMLElement).closest(\".mrsf-tooltip\");\n if (!tooltipClick) {\n this.hideTooltip();\n }\n return;\n }\n if (!target) return;\n\n const action = target.dataset.mrsfAction as MrsfAction | undefined;\n if (!action) return;\n\n const commentId = target.dataset.mrsfCommentId ?? null;\n const lineStr = target.dataset.mrsfLine;\n const line = lineStr ? parseInt(lineStr, 10) : null;\n const selectionText = target.dataset.mrsfSelection ?? this.lastSelectionText ?? null;\n\n const startLineStr = target.dataset.mrsfStartLine;\n const endLineStr = target.dataset.mrsfEndLine;\n const startColStr = target.dataset.mrsfStartColumn;\n const endColStr = target.dataset.mrsfEndColumn;\n const startLine = startLineStr ? parseInt(startLineStr, 10) : (line ?? null);\n const endLine = endLineStr ? parseInt(endLineStr, 10) : (line ?? null);\n const startColumn = startColStr ? parseInt(startColStr, 10) : null;\n const endColumn = endColStr ? parseInt(endColStr, 10) : null;\n\n e.preventDefault();\n e.stopPropagation();\n\n const detail: MrsfActionDetail = {\n commentId,\n line,\n action,\n selectionText,\n start_line: startLine,\n end_line: endLine,\n start_column: startColumn,\n end_column: endColumn,\n };\n\n if (action === \"add\" || action === \"edit\" || action === \"reply\") {\n if (action === \"add\") {\n this.hideFloatingAddButton();\n }\n this.openForm(action, detail);\n return;\n }\n\n if (action === \"resolve\" || action === \"unresolve\" || action === \"delete\") {\n this.openConfirm(action, detail);\n return;\n }\n\n document.dispatchEvent(\n new CustomEvent(`mrsf:${action}`, { detail, bubbles: true }),\n );\n }\n\n // \u2500\u2500 Selection handling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private handleSelectionChange(): void {\n const sel = document.getSelection();\n if (!sel || sel.isCollapsed || sel.rangeCount === 0) {\n this.hideFloatingAddButton();\n return;\n }\n\n const text = sel.toString().trim();\n if (!text) {\n this.hideFloatingAddButton();\n return;\n }\n\n const range = sel.getRangeAt(0);\n const rect = range.getBoundingClientRect();\n if (!rect || (rect.width === 0 && rect.height === 0)) {\n this.hideFloatingAddButton();\n return;\n }\n\n const startAnchor = (range.startContainer as Node).parentElement?.closest<HTMLElement>(\n \"[data-mrsf-line]\",\n );\n const endAnchor = (range.endContainer as Node).parentElement?.closest<HTMLElement>(\n \"[data-mrsf-line]\",\n );\n const startLineStr = startAnchor?.dataset.mrsfStartLine ?? startAnchor?.dataset.mrsfLine;\n const endLineStr = endAnchor?.dataset.mrsfEndLine ?? endAnchor?.dataset.mrsfLine ?? startLineStr;\n const startLine = startLineStr ? parseInt(startLineStr, 10) : null;\n const endLine = endLineStr ? parseInt(endLineStr, 10) : startLine;\n\n const startColumn = range.startContainer.nodeType === Node.TEXT_NODE\n ? range.startOffset : null;\n const endColumn = range.endContainer.nodeType === Node.TEXT_NODE\n ? range.endOffset : null;\n\n this.showFloatingAddButton(startLine, endLine, startColumn, endColumn, rect, text);\n }\n\n private ensureFloatingAddButton(): HTMLButtonElement {\n if (this.floatingAddButton) return this.floatingAddButton;\n const btn = document.createElement(\"button\");\n btn.textContent = \"Add comment\";\n btn.className = \"mrsf-add-inline-button\";\n btn.dataset.mrsfAction = \"add\";\n btn.style.display = \"none\";\n btn.style.position = \"absolute\";\n btn.style.zIndex = \"1200\";\n document.body.appendChild(btn);\n this.floatingAddButton = btn;\n return btn;\n }\n\n private hideFloatingAddButton(): void {\n if (!this.floatingAddButton) return;\n this.floatingAddButton.style.display = \"none\";\n this.floatingAddButton.dataset.mrsfLine = \"\";\n this.floatingAddButton.dataset.mrsfStartLine = \"\";\n this.floatingAddButton.dataset.mrsfEndLine = \"\";\n this.lastSelectionText = null;\n }\n\n private showFloatingAddButton(\n startLine: number | null,\n endLine: number | null,\n startColumn: number | null,\n endColumn: number | null,\n rect: DOMRect,\n selectionText: string,\n ): void {\n const btn = this.ensureFloatingAddButton();\n if (startLine != null) {\n btn.dataset.mrsfLine = String(startLine);\n btn.dataset.mrsfStartLine = String(startLine);\n btn.dataset.mrsfEndLine = String(endLine ?? startLine);\n } else {\n delete btn.dataset.mrsfLine;\n delete btn.dataset.mrsfStartLine;\n delete btn.dataset.mrsfEndLine;\n }\n if (startColumn != null) {\n btn.dataset.mrsfStartColumn = String(startColumn);\n } else {\n delete btn.dataset.mrsfStartColumn;\n }\n if (endColumn != null) {\n btn.dataset.mrsfEndColumn = String(endColumn);\n } else {\n delete btn.dataset.mrsfEndColumn;\n }\n this.lastSelectionText = selectionText;\n\n const margin = 6;\n btn.style.visibility = \"hidden\";\n btn.style.display = \"block\";\n const height = btn.offsetHeight || 0;\n const top = window.scrollY + rect.top - height - margin;\n const left = window.scrollX + rect.left;\n btn.style.top = `${Math.max(0, top)}px`;\n btn.style.left = `${Math.max(0, left)}px`;\n btn.style.visibility = \"visible\";\n }\n\n // \u2500\u2500 Dialog: form (add/edit/reply) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private injectStyles(): void {\n if (this.styleInjected) return;\n const css = `\n.mrsf-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 2000; display: flex; align-items: center; justify-content: center; }\n.mrsf-dialog { background: var(--mrsf-dialog-bg, var(--vp-c-bg, #1e1e1e)); color: var(--mrsf-dialog-fg, var(--vp-c-text-1, #f3f3f3)); border: 1px solid var(--mrsf-dialog-border, var(--vp-c-divider, #444)); border-radius: 8px; width: min(520px, 90vw); box-shadow: 0 10px 40px rgba(0,0,0,0.35); font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif; }\n.mrsf-dialog header { padding: 12px 16px; font-weight: 600; border-bottom: 1px solid var(--mrsf-dialog-border, var(--vp-c-divider, #333)); }\n.mrsf-dialog form { padding: 16px; display: flex; flex-direction: column; gap: 12px; }\n.mrsf-field { display: flex; flex-direction: column; gap: 6px; }\n.mrsf-field label { font-size: 13px; color: var(--mrsf-dialog-muted, var(--vp-c-text-2, #ccc)); }\n.mrsf-field input, .mrsf-field select, .mrsf-field textarea { background: var(--mrsf-field-bg, var(--vp-c-bg-soft, #252526)); color: var(--mrsf-dialog-fg, var(--vp-c-text-1, #f3f3f3)); border: 1px solid var(--mrsf-dialog-border, var(--vp-c-divider, #3c3c3c)); border-radius: 4px; padding: 6px 8px; font-size: 13px; }\n.mrsf-field textarea { min-height: 100px; resize: vertical; }\n.mrsf-actions-row { display: flex; justify-content: flex-end; gap: 8px; margin-top: 0; padding: 12px 16px 16px; border-top: 1px solid var(--mrsf-dialog-border, var(--vp-c-divider, #333)); }\n.mrsf-btn { padding: 6px 12px; border-radius: 4px; border: 1px solid var(--mrsf-dialog-border, var(--vp-c-divider, #3c3c3c)); background: var(--mrsf-button-bg, var(--vp-c-bg-soft, #2d2d30)); color: var(--mrsf-dialog-fg, var(--vp-c-text-1, #f3f3f3)); cursor: pointer; }\n.mrsf-btn-primary { background: var(--mrsf-button-primary-bg, #0e639c); border-color: var(--mrsf-button-primary-bg, #0e639c); color: #fff; }\n.mrsf-helper { font-size: 12px; color: var(--mrsf-dialog-muted, var(--vp-c-text-2, #aaa)); }\n`;\n const style = document.createElement(\"style\");\n style.textContent = css;\n document.head.appendChild(style);\n this.styleInjected = true;\n }\n\n private closeOverlay(): void {\n if (this.overlayEl?.parentElement) {\n this.overlayEl.parentElement.removeChild(this.overlayEl);\n }\n this.overlayEl = null;\n }\n\n private openForm(action: \"add\" | \"edit\" | \"reply\", detail: MrsfActionDetail): void {\n if ((window as any).mrsfDisableBuiltinUi) return;\n this.injectStyles();\n this.closeOverlay();\n\n const selText = detail.selectionText ?? \"\";\n const line = detail.line ?? detail.start_line ?? null;\n const endLine = detail.end_line ?? detail.line ?? null;\n const startCol = detail.start_column ?? null;\n const endCol = detail.end_column ?? null;\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"mrsf-overlay\";\n\n const dialog = document.createElement(\"div\");\n dialog.className = \"mrsf-dialog\";\n\n const header = document.createElement(\"header\");\n header.textContent =\n action === \"add\" ? \"Add comment\" : action === \"edit\" ? \"Edit comment\" : \"Reply\";\n dialog.appendChild(header);\n\n const form = document.createElement(\"form\");\n\n const field = (labelText: string, inputEl: HTMLElement, helper?: string) => {\n const wrap = document.createElement(\"div\");\n wrap.className = \"mrsf-field\";\n const label = document.createElement(\"label\");\n label.textContent = labelText;\n wrap.appendChild(label);\n wrap.appendChild(inputEl);\n if (helper) {\n const h = document.createElement(\"div\");\n h.className = \"mrsf-helper\";\n h.textContent = helper;\n wrap.appendChild(h);\n }\n form.appendChild(wrap);\n };\n\n const textArea = document.createElement(\"textarea\");\n textArea.name = \"text\";\n textArea.required = true;\n field(\"Comment text\", textArea);\n\n const typeSelect = document.createElement(\"select\");\n typeSelect.name = \"type\";\n [\"\", \"suggestion\", \"issue\", \"question\", \"accuracy\", \"style\", \"clarity\"].forEach((t) => {\n const opt = document.createElement(\"option\");\n opt.value = t;\n opt.textContent = t || \"(none)\";\n typeSelect.appendChild(opt);\n });\n field(\"Type\", typeSelect, \"Optional\");\n\n const severitySelect = document.createElement(\"select\");\n severitySelect.name = \"severity\";\n [\"\", \"low\", \"medium\", \"high\"].forEach((s) => {\n const opt = document.createElement(\"option\");\n opt.value = s;\n opt.textContent = s || \"(none)\";\n severitySelect.appendChild(opt);\n });\n field(\"Severity\", severitySelect, \"Optional\");\n\n if (selText) {\n const pre = document.createElement(\"pre\");\n pre.textContent = selText;\n pre.style.margin = \"0\";\n pre.style.whiteSpace = \"pre-wrap\";\n field(\"Selected text\", pre as unknown as HTMLElement, \"Captured automatically\");\n }\n\n const actions = document.createElement(\"div\");\n actions.className = \"mrsf-actions-row\";\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.type = \"button\";\n cancelBtn.className = \"mrsf-btn\";\n cancelBtn.textContent = \"Cancel\";\n cancelBtn.addEventListener(\"click\", () => this.closeOverlay());\n actions.appendChild(cancelBtn);\n\n const submitBtn = document.createElement(\"button\");\n submitBtn.type = \"submit\";\n submitBtn.className = \"mrsf-btn mrsf-btn-primary\";\n submitBtn.textContent = action === \"add\" ? \"Add\" : action === \"reply\" ? \"Reply\" : \"Save\";\n actions.appendChild(submitBtn);\n\n form.appendChild(actions);\n\n form.addEventListener(\"submit\", (ev) => {\n ev.preventDefault();\n const detailOut: MrsfSubmitDetail = {\n action,\n commentId: detail.commentId,\n text: textArea.value.trim(),\n type: typeSelect.value || null,\n severity: (severitySelect.value as MrsfSubmitDetail[\"severity\"]) || null,\n line,\n end_line: endLine,\n start_column: startCol,\n end_column: endCol,\n selection_text: selText || null,\n };\n document.dispatchEvent(new CustomEvent(\"mrsf:submit\", { detail: detailOut, bubbles: true }));\n this.closeOverlay();\n });\n\n dialog.appendChild(form);\n overlay.appendChild(dialog);\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) this.closeOverlay();\n });\n\n document.body.appendChild(overlay);\n this.overlayEl = overlay;\n }\n\n // \u2500\u2500 Dialog: confirm (resolve / unresolve / delete) \u2500\u2500\u2500\u2500\u2500\u2500\n\n private openConfirm(action: \"resolve\" | \"unresolve\" | \"delete\", detail: MrsfActionDetail): void {\n if ((window as any).mrsfDisableBuiltinUi) return;\n this.injectStyles();\n this.closeOverlay();\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"mrsf-overlay\";\n\n const dialog = document.createElement(\"div\");\n dialog.className = \"mrsf-dialog\";\n\n const header = document.createElement(\"header\");\n header.textContent = action === \"delete\" ? \"Delete comment\" : \"Change status\";\n dialog.appendChild(header);\n\n const body = document.createElement(\"div\");\n body.style.padding = \"16px\";\n body.textContent =\n action === \"delete\"\n ? \"Delete this comment?\"\n : action === \"resolve\"\n ? \"Mark this comment as resolved?\"\n : \"Mark this comment as unresolved?\";\n dialog.appendChild(body);\n\n const actions = document.createElement(\"div\");\n actions.className = \"mrsf-actions-row\";\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.type = \"button\";\n cancelBtn.className = \"mrsf-btn\";\n cancelBtn.textContent = \"Cancel\";\n cancelBtn.addEventListener(\"click\", () => this.closeOverlay());\n actions.appendChild(cancelBtn);\n\n const confirmBtn = document.createElement(\"button\");\n confirmBtn.type = \"button\";\n confirmBtn.className = \"mrsf-btn mrsf-btn-primary\";\n confirmBtn.textContent = action === \"delete\" ? \"Delete\" : \"Confirm\";\n confirmBtn.addEventListener(\"click\", () => {\n const detailOut: MrsfSubmitDetail = {\n action,\n commentId: detail.commentId,\n text: \"\",\n type: null,\n severity: null,\n line: detail.line,\n end_line: detail.end_line ?? detail.line ?? null,\n start_column: detail.start_column ?? null,\n end_column: detail.end_column ?? null,\n selection_text: detail.selectionText ?? null,\n };\n document.dispatchEvent(new CustomEvent(\"mrsf:submit\", { detail: detailOut, bubbles: true }));\n this.closeOverlay();\n });\n actions.appendChild(confirmBtn);\n\n dialog.appendChild(actions);\n\n overlay.appendChild(dialog);\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) this.closeOverlay();\n });\n\n document.body.appendChild(overlay);\n this.overlayEl = overlay;\n }\n}\n\n// \u2500\u2500 Auto-init convenience \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Scan for containers with [data-mrsf-controller] and auto-init.\n\nlet autoInitDone = false;\n\nexport function autoInit(): void {\n if (autoInitDone) return;\n autoInitDone = true;\n\n const containers = document.querySelectorAll<HTMLElement>(\"[data-mrsf-controller]\");\n for (const container of containers) {\n const pos = container.dataset.mrsfGutterPosition as MrsfControllerOptions[\"gutterPosition\"] | undefined;\n const interactive = container.dataset.mrsfInteractive === \"true\";\n new MrsfController(container, { gutterPosition: pos ?? \"right\", interactive });\n }\n}\n\n// Auto-init on DOMContentLoaded if in browser context\nif (typeof document !== \"undefined\") {\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", autoInit);\n } else {\n autoInit();\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/html.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shared HTML rendering helpers for MRSF rendering plugins.
3
+ *
4
+ * These produce the HTML strings used in tooltips, badges, and comment
5
+ * rendering. Both the markdown-it and rehype plugins use these functions
6
+ * to ensure identical visual output.
7
+ */
8
+ import type { CommentThread, SlimComment } from "./types.js";
9
+ export declare function escapeHtml(str: string): string;
10
+ export declare function formatTime(iso: string | null): string;
11
+ export declare function renderCommentHtml(comment: SlimComment, isReply: boolean, interactive: boolean): string;
12
+ export declare function renderThreadHtml(thread: CommentThread, interactive: boolean): string;
13
+ //# sourceMappingURL=html.d.ts.map
package/dist/index.js CHANGED
@@ -58,117 +58,8 @@ function resolveComments(loader, options) {
58
58
  return { lineMap, comments };
59
59
  }
60
60
 
61
- // ../shared/dist/html.js
62
- function escapeHtml(str) {
63
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
64
- }
65
- function formatTime(iso) {
66
- if (!iso)
67
- return "";
68
- try {
69
- const d = new Date(iso);
70
- return d.toLocaleDateString(void 0, {
71
- year: "numeric",
72
- month: "short",
73
- day: "numeric"
74
- });
75
- } catch {
76
- return "";
77
- }
78
- }
79
- function renderCommentHtml(comment, isReply, interactive) {
80
- const resolvedClass = comment.resolved ? " mrsf-resolved" : "";
81
- const replyClass = isReply ? " mrsf-reply" : "";
82
- let html = `<span class="mrsf-comment${resolvedClass}${replyClass}" data-mrsf-comment-id="${escapeHtml(comment.id)}">`;
83
- html += `<span class="mrsf-comment-header">`;
84
- html += `<span class="mrsf-author">${escapeHtml(comment.author)}</span>`;
85
- if (comment.timestamp) {
86
- html += `<span class="mrsf-date">${escapeHtml(formatTime(comment.timestamp))}</span>`;
87
- }
88
- if (comment.severity) {
89
- html += `<span class="mrsf-severity mrsf-severity-${escapeHtml(comment.severity)}">${escapeHtml(comment.severity)}</span>`;
90
- }
91
- if (comment.type) {
92
- html += `<span class="mrsf-type">${escapeHtml(comment.type)}</span>`;
93
- }
94
- if (comment.resolved) {
95
- html += `<span class="mrsf-resolved-badge">\u2713 resolved</span>`;
96
- }
97
- html += `</span>`;
98
- if (comment.selected_text) {
99
- html += `<span class="mrsf-selected-text">${escapeHtml(comment.selected_text)}</span>`;
100
- }
101
- html += `<span class="mrsf-comment-body">${escapeHtml(comment.text)}</span>`;
102
- if (interactive) {
103
- const line = comment.line != null ? String(comment.line) : "";
104
- html += `<span class="mrsf-actions">`;
105
- if (comment.resolved) {
106
- html += `<button class="mrsf-action-btn" data-mrsf-action="unresolve" data-mrsf-comment-id="${escapeHtml(comment.id)}" data-mrsf-line="${line}">Unresolve</button>`;
107
- } else {
108
- html += `<button class="mrsf-action-btn" data-mrsf-action="resolve" data-mrsf-comment-id="${escapeHtml(comment.id)}" data-mrsf-line="${line}">Resolve</button>`;
109
- }
110
- html += `<button class="mrsf-action-btn" data-mrsf-action="reply" data-mrsf-comment-id="${escapeHtml(comment.id)}" data-mrsf-line="${line}">Reply</button>`;
111
- html += `<button class="mrsf-action-btn" data-mrsf-action="edit" data-mrsf-comment-id="${escapeHtml(comment.id)}" data-mrsf-line="${line}">Edit</button>`;
112
- html += `</span>`;
113
- }
114
- html += `</span>`;
115
- return html;
116
- }
117
- function renderThreadHtml(thread, interactive) {
118
- let html = `<span class="mrsf-thread">`;
119
- html += renderCommentHtml(thread.comment, false, interactive);
120
- if (thread.replies.length > 0) {
121
- html += `<span class="mrsf-replies">`;
122
- for (const reply of thread.replies) {
123
- html += renderCommentHtml(reply, true, interactive);
124
- }
125
- html += `</span>`;
126
- }
127
- html += `</span>`;
128
- return html;
129
- }
130
-
131
61
  // src/rules/core.ts
132
- function highlightInlineText(token, thread, interactive, Token) {
133
- if (!token.children) return false;
134
- const text = thread.comment.selected_text;
135
- if (!text) return false;
136
- const trimmed = text.trim();
137
- if (!trimmed) return false;
138
- for (let i = 0; i < token.children.length; i++) {
139
- const child = token.children[i];
140
- if (child.type !== "text") continue;
141
- const idx = child.content.indexOf(trimmed);
142
- if (idx === -1) continue;
143
- const newTokens = [];
144
- if (idx > 0) {
145
- const before = new Token("text", "", 0);
146
- before.content = child.content.slice(0, idx);
147
- newTokens.push(before);
148
- }
149
- const open = new Token("mrsf_highlight_open", "mark", 1);
150
- open.attrSet("class", "mrsf-highlight");
151
- open.attrSet("data-mrsf-comment-id", thread.comment.id);
152
- open.meta = { thread };
153
- newTokens.push(open);
154
- const matched = new Token("text", "", 0);
155
- matched.content = trimmed;
156
- newTokens.push(matched);
157
- const close = new Token("mrsf_highlight_close", "mark", -1);
158
- close.meta = { thread, interactive };
159
- newTokens.push(close);
160
- if (idx + trimmed.length < child.content.length) {
161
- const after = new Token("text", "", 0);
162
- after.content = child.content.slice(idx + trimmed.length);
163
- newTokens.push(after);
164
- }
165
- token.children.splice(i, 1, ...newTokens);
166
- return true;
167
- }
168
- return false;
169
- }
170
- function installCoreRule(md, lineMap, options) {
171
- const { interactive, gutterPosition, gutterForInline, inlineHighlights } = options;
62
+ function installCoreRule(md, lineMap, options = {}) {
172
63
  md.core.ruler.push("mrsf_inject", (state) => {
173
64
  const tokens = state.tokens;
174
65
  const TokenCtor = state.Token;
@@ -178,127 +69,57 @@ function installCoreRule(md, lineMap, options) {
178
69
  const map = token.map;
179
70
  if (!map) continue;
180
71
  if (token.type === "inline") continue;
72
+ const startLine1 = map[0] + 1;
73
+ const endLine1 = map[1];
74
+ token.attrSet("data-mrsf-line", String(startLine1));
75
+ token.attrSet("data-mrsf-start-line", String(startLine1));
76
+ token.attrSet("data-mrsf-end-line", String(endLine1));
181
77
  for (let line0 = map[0]; line0 < map[1]; line0++) {
182
78
  const line = line0 + 1;
183
79
  if (processed.has(line)) continue;
184
- const threads = lineMap.get(line);
185
- if (!threads || threads.length === 0) continue;
186
80
  processed.add(line);
187
- const existingClass = token.attrGet("class") || "";
188
- token.attrSet(
189
- "class",
190
- existingClass ? `${existingClass} mrsf-line-highlight` : "mrsf-line-highlight"
191
- );
192
- token.attrSet("data-mrsf-line", String(line));
193
- const allHaveInline = inlineHighlights && threads.every((t) => !!t.comment.selected_text);
194
- const showBadge = gutterForInline || !allHaveInline || !inlineHighlights;
195
- if (showBadge) {
196
- const badgeToken = new TokenCtor("mrsf_badge", "", 0);
197
- badgeToken.meta = { line, threads, interactive, gutterPosition };
198
- tokens.splice(i + 1, 0, badgeToken);
199
- }
200
- if (inlineHighlights) {
201
- const offset = showBadge ? 2 : 1;
202
- const inlineAfter = tokens[i + offset];
203
- if (inlineAfter && inlineAfter.type === "inline") {
204
- for (const thread of threads) {
205
- if (thread.comment.selected_text) {
206
- highlightInlineText(inlineAfter, thread, interactive, TokenCtor);
207
- }
81
+ const threads = lineMap.get(line);
82
+ if (threads && threads.length > 0) {
83
+ if (options.lineHighlight) {
84
+ const existingClass = token.attrGet("class") || "";
85
+ if (!existingClass.includes("mrsf-line-highlight")) {
86
+ token.attrSet(
87
+ "class",
88
+ existingClass ? `${existingClass} mrsf-line-highlight` : "mrsf-line-highlight"
89
+ );
208
90
  }
209
91
  }
92
+ token.attrSet("data-mrsf-line", String(line));
210
93
  }
211
94
  }
212
95
  }
213
- if (gutterPosition === "left" && processed.size > 0) {
214
- const openToken = new TokenCtor("mrsf_gutter_open", "div", 1);
215
- openToken.attrSet("class", "mrsf-gutter-container");
216
- tokens.unshift(openToken);
217
- const closeToken = new TokenCtor("mrsf_gutter_close", "div", -1);
218
- tokens.push(closeToken);
96
+ const allThreads = [];
97
+ for (const threads of lineMap.values()) {
98
+ allThreads.push(...threads);
99
+ }
100
+ if (allThreads.length > 0) {
101
+ const scriptToken = new TokenCtor("mrsf_data_script", "", 0);
102
+ scriptToken.meta = { threads: allThreads };
103
+ tokens.push(scriptToken);
219
104
  }
220
105
  });
221
106
  }
222
107
 
223
108
  // src/rules/renderer.ts
224
109
  function installRendererRules(md) {
225
- md.renderer.rules["mrsf_badge"] = (tokens, idx) => {
226
- const { line, threads, interactive, gutterPosition } = tokens[idx].meta;
227
- const total = threads.reduce(
228
- (n, t) => n + 1 + t.replies.length,
229
- 0
230
- );
231
- const allResolved = threads.every((t) => t.comment.resolved);
232
- const highestSeverity = threads.reduce((sev, t) => {
233
- if (t.comment.severity === "high" || sev === "high") return "high";
234
- if (t.comment.severity === "medium" || sev === "medium") return "medium";
235
- if (t.comment.severity === "low" || sev === "low") return "low";
236
- return sev;
237
- }, null);
238
- const resolvedClass = allResolved ? " mrsf-badge-resolved" : "";
239
- const severityClass = highestSeverity === "high" || highestSeverity === "medium" ? ` mrsf-badge-severity-${highestSeverity}` : "";
240
- const positionClass = ` mrsf-gutter-${gutterPosition}`;
241
- const icon = allResolved ? "\u2713" : "\u{1F4AC}";
242
- let html = `<span class="mrsf-tooltip-anchor${positionClass}">`;
243
- html += `<span class="mrsf-badge${resolvedClass}${severityClass}" data-mrsf-line="${line}" data-mrsf-action="navigate" data-mrsf-comment-id="${escapeHtml(threads[0].comment.id)}" tabindex="0">${icon} ${total}</span>`;
244
- html += `<span class="mrsf-tooltip" data-mrsf-line="${line}">`;
245
- for (const thread of threads) {
246
- html += renderThreadHtml(thread, interactive);
247
- }
248
- html += `</span>`;
249
- html += `</span>`;
250
- return html;
251
- };
252
- md.renderer.rules["mrsf_highlight_open"] = (tokens, idx) => {
253
- const token = tokens[idx];
254
- const cls = token.attrGet("class") || "mrsf-highlight";
255
- const commentId = token.attrGet("data-mrsf-comment-id") || "";
256
- const thread = token.meta?.thread;
257
- let html = "";
258
- if (thread) {
259
- html += `<span class="mrsf-tooltip-anchor mrsf-inline-anchor">`;
260
- }
261
- html += `<mark class="${escapeHtml(cls)}" data-mrsf-comment-id="${escapeHtml(commentId)}" tabindex="0">`;
262
- return html;
263
- };
264
- md.renderer.rules["mrsf_highlight_close"] = (tokens, idx) => {
265
- const token = tokens[idx];
266
- const meta = token.meta;
267
- let html = "</mark>";
268
- if (meta?.thread) {
269
- const thread = meta.thread;
270
- const interactive = meta.interactive;
271
- html += `<span class="mrsf-tooltip mrsf-inline-tooltip">`;
272
- html += renderThreadHtml(thread, interactive);
273
- html += `</span>`;
274
- html += `</span>`;
275
- }
276
- return html;
277
- };
278
- md.renderer.rules["mrsf_gutter_open"] = (tokens, idx) => {
279
- const cls = tokens[idx].attrGet("class") || "mrsf-gutter-container";
280
- return `<div class="${escapeHtml(cls)}">`;
281
- };
282
- md.renderer.rules["mrsf_gutter_close"] = () => {
283
- return `</div>`;
110
+ md.renderer.rules["mrsf_data_script"] = (tokens, idx) => {
111
+ const { threads } = tokens[idx].meta;
112
+ const data = JSON.stringify({ threads }).replace(/</g, "\\u003c");
113
+ return `<script type="application/mrsf+json">${data}</script>`;
284
114
  };
285
115
  }
286
116
 
287
117
  // src/shared.ts
288
118
  function createMrsfPlugin(loader) {
289
119
  return function mrsfPlugin2(md, options = {}) {
290
- const interactive = options.interactive ?? false;
291
- const gutterPosition = options.gutterPosition ?? "right";
292
- const gutterForInline = options.gutterForInline ?? true;
293
- const inlineHighlights = options.inlineHighlights ?? true;
294
120
  const result = resolveComments(loader, options);
295
121
  if (!result) return;
296
- installCoreRule(md, result.lineMap, {
297
- interactive,
298
- gutterPosition,
299
- gutterForInline,
300
- inlineHighlights
301
- });
122
+ installCoreRule(md, result.lineMap, { lineHighlight: options.lineHighlight ?? false });
302
123
  installRendererRules(md);
303
124
  };
304
125
  }
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/index.ts", "../../shared/src/comments.ts", "../../shared/src/html.ts", "../src/rules/core.ts", "../src/rules/renderer.ts", "../src/shared.ts"],
4
- "sourcesContent": ["/**\n * @mrsf/markdown-it-mrsf \u2014 markdown-it plugin for MRSF review comments.\n *\n * This is the full-featured Node.js entry point. It supports all four\n * sidecar provisioning modes: `comments`, `loader`, `sidecarPath`, and\n * `documentPath`. The latter two require Node.js file-system APIs.\n *\n * In browser / bundler environments, the `\"browser\"` export condition in\n * package.json automatically resolves to the browser-safe entry, so you\n * never need to import a different path \u2014 just use:\n *\n * @example\n * ```ts\n * import MarkdownIt from \"markdown-it\";\n * import { mrsfPlugin } from \"@mrsf/markdown-it-mrsf\";\n *\n * const md = new MarkdownIt();\n * md.use(mrsfPlugin, { documentPath: \"docs/guide.md\" });\n * const html = md.render(markdownSource);\n * ```\n */\n\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { parseSidecarContent } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions } from \"./types.js\";\nimport { createMrsfPlugin } from \"./shared.js\";\n\n// Re-export types for consumers\nexport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\n/**\n * The markdown-it plugin function (Node.js \u2014 full feature set).\n */\nexport const mrsfPlugin = createMrsfPlugin((options: MrsfPluginOptions) => {\n // Priority 1: inline data\n if (options.comments) {\n return options.comments;\n }\n\n // Priority 2: custom loader function\n if (options.loader) {\n try {\n return options.loader();\n } catch {\n return null;\n }\n }\n\n // Priority 3: explicit sidecar path\n if (options.sidecarPath) {\n try {\n const abs = path.resolve(options.cwd || process.cwd(), options.sidecarPath);\n const content = readFileSync(abs, \"utf-8\");\n return parseSidecarContent(content, abs);\n } catch {\n return null;\n }\n }\n\n // Priority 4: auto-discover from documentPath\n if (options.documentPath) {\n try {\n const cwd = options.cwd || process.cwd();\n const abs = path.resolve(cwd, options.documentPath);\n const yamlPath = abs + \".review.yaml\";\n const jsonPath = abs + \".review.json\";\n\n let raw: string | null = null;\n let hint: string | null = null;\n try {\n raw = readFileSync(yamlPath, \"utf-8\");\n hint = yamlPath;\n } catch {\n try {\n raw = readFileSync(jsonPath, \"utf-8\");\n hint = jsonPath;\n } catch {\n return null;\n }\n }\n\n if (!raw) return null;\n return parseSidecarContent(raw, hint!);\n } catch {\n return null;\n }\n }\n\n return null;\n});\n\nexport default mrsfPlugin;\n", "/**\n * Shared comment loading and grouping logic for MRSF rendering plugins.\n */\n\nimport type { MrsfDocument } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\nexport type { CommentLoader };\n\n/**\n * Convert an MrsfDocument into a slim comment array.\n */\nexport function toSlimComments(doc: MrsfDocument): SlimComment[] {\n return doc.comments.map((c) => ({\n id: c.id,\n author: c.author || \"Unknown\",\n text: c.text || \"\",\n line: c.line ?? null,\n end_line: c.end_line ?? null,\n start_column: c.start_column ?? null,\n end_column: c.end_column ?? null,\n selected_text: c.selected_text || null,\n resolved: !!c.resolved,\n reply_to: c.reply_to || null,\n severity: c.severity || null,\n type: c.type || null,\n timestamp: c.timestamp || null,\n }));\n}\n\n/**\n * Group comments by line number, threading replies under parents.\n */\nexport function groupByLine(comments: SlimComment[]): LineMap {\n const rootComments = comments.filter((c) => !c.reply_to && c.line != null);\n const replies = comments.filter((c) => c.reply_to);\n\n const replyMap = new Map<string, SlimComment[]>();\n for (const r of replies) {\n const list = replyMap.get(r.reply_to!) || [];\n list.push(r);\n replyMap.set(r.reply_to!, list);\n }\n\n const lineMap: LineMap = new Map();\n for (const c of rootComments) {\n const line = c.line!;\n const threads = lineMap.get(line) || [];\n threads.push({\n comment: c,\n replies: replyMap.get(c.id) || [],\n });\n lineMap.set(line, threads);\n }\n\n return lineMap;\n}\n\n/**\n * Resolve options into a filtered LineMap ready for rendering.\n * Returns null if there are no comments to render.\n */\nexport function resolveComments(\n loader: CommentLoader,\n options: MrsfPluginOptions,\n): { lineMap: LineMap; comments: SlimComment[] } | null {\n const showResolved = options.showResolved ?? true;\n\n const doc = loader(options);\n if (!doc || !doc.comments || doc.comments.length === 0) {\n return null;\n }\n\n let comments = toSlimComments(doc);\n if (!showResolved) {\n comments = comments.filter((c) => !c.resolved);\n }\n\n if (comments.length === 0) return null;\n\n const lineMap = groupByLine(comments);\n return { lineMap, comments };\n}\n", "/**\n * Shared HTML rendering helpers for MRSF rendering plugins.\n *\n * These produce the HTML strings used in tooltips, badges, and comment\n * rendering. Both the markdown-it and rehype plugins use these functions\n * to ensure identical visual output.\n */\n\nimport type { CommentThread, SlimComment } from \"./types.js\";\n\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nexport function formatTime(iso: string | null): string {\n if (!iso) return \"\";\n try {\n const d = new Date(iso);\n return d.toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n } catch {\n return \"\";\n }\n}\n\nexport function renderCommentHtml(\n comment: SlimComment,\n isReply: boolean,\n interactive: boolean,\n): string {\n const resolvedClass = comment.resolved ? \" mrsf-resolved\" : \"\";\n const replyClass = isReply ? \" mrsf-reply\" : \"\";\n let html = `<span class=\"mrsf-comment${resolvedClass}${replyClass}\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\">`;\n\n // Header\n html += `<span class=\"mrsf-comment-header\">`;\n html += `<span class=\"mrsf-author\">${escapeHtml(comment.author)}</span>`;\n if (comment.timestamp) {\n html += `<span class=\"mrsf-date\">${escapeHtml(formatTime(comment.timestamp))}</span>`;\n }\n if (comment.severity) {\n html += `<span class=\"mrsf-severity mrsf-severity-${escapeHtml(comment.severity)}\">${escapeHtml(comment.severity)}</span>`;\n }\n if (comment.type) {\n html += `<span class=\"mrsf-type\">${escapeHtml(comment.type)}</span>`;\n }\n if (comment.resolved) {\n html += `<span class=\"mrsf-resolved-badge\">\u2713 resolved</span>`;\n }\n html += `</span>`;\n\n // Selected text quote\n if (comment.selected_text) {\n html += `<span class=\"mrsf-selected-text\">${escapeHtml(comment.selected_text)}</span>`;\n }\n\n // Body\n html += `<span class=\"mrsf-comment-body\">${escapeHtml(comment.text)}</span>`;\n\n // Action buttons (interactive mode)\n if (interactive) {\n const line = comment.line != null ? String(comment.line) : \"\";\n html += `<span class=\"mrsf-actions\">`;\n if (comment.resolved) {\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"unresolve\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Unresolve</button>`;\n } else {\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"resolve\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Resolve</button>`;\n }\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"reply\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Reply</button>`;\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"edit\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Edit</button>`;\n html += `</span>`;\n }\n\n html += `</span>`;\n return html;\n}\n\nexport function renderThreadHtml(thread: CommentThread, interactive: boolean): string {\n let html = `<span class=\"mrsf-thread\">`;\n html += renderCommentHtml(thread.comment, false, interactive);\n if (thread.replies.length > 0) {\n html += `<span class=\"mrsf-replies\">`;\n for (const reply of thread.replies) {\n html += renderCommentHtml(reply, true, interactive);\n }\n html += `</span>`;\n }\n html += `</span>`;\n return html;\n}\n", "/**\n * MRSF markdown-it plugin \u2014 core rule.\n *\n * Walks the parsed token stream and injects `mrsf_badge` tokens\n * at lines that have comments, and wraps `selected_text` matches\n * with `mrsf_highlight_open`/`close` tokens (with tooltip metadata).\n */\n\nimport type Token from \"markdown-it/lib/token.mjs\";\nimport type { Nesting } from \"markdown-it/lib/token.mjs\";\nimport type StateCore from \"markdown-it/lib/rules_core/state_core.mjs\";\nimport type { LineMap, CommentThread } from \"../types.js\";\n\n/** Options forwarded from the plugin to the core rule. */\nexport interface CoreOptions {\n interactive: boolean;\n gutterPosition: \"left\" | \"tight\" | \"right\";\n gutterForInline: boolean;\n inlineHighlights: boolean;\n}\n\n/**\n * Find the token index for a given 1-based source line.\n * markdown-it tokens use `token.map = [startLine, endLine]` (0-based).\n */\nfunction findTokenIndexForLine(\n tokens: Token[],\n line: number,\n): number {\n const line0 = line - 1; // convert to 0-based\n for (let i = 0; i < tokens.length; i++) {\n const map = tokens[i].map;\n if (map && map[0] <= line0 && line0 < map[1]) {\n return i;\n }\n }\n return -1;\n}\n\n/**\n * Wrap occurrences of `text` inside inline token children with\n * highlight open/close tokens. Stores thread data on the tokens\n * so the renderer can produce an inline tooltip.\n */\nfunction highlightInlineText(\n token: Token,\n thread: CommentThread,\n interactive: boolean,\n Token: new (type: string, tag: string, nesting: Nesting) => Token,\n): boolean {\n if (!token.children) return false;\n\n const text = thread.comment.selected_text;\n if (!text) return false;\n const trimmed = text.trim();\n if (!trimmed) return false;\n\n for (let i = 0; i < token.children.length; i++) {\n const child = token.children[i];\n if (child.type !== \"text\") continue;\n\n const idx = child.content.indexOf(trimmed);\n if (idx === -1) continue;\n\n // Split the text token into: before + highlight_open + matched + highlight_close + after\n const newTokens: Token[] = [];\n\n // Text before match\n if (idx > 0) {\n const before = new Token(\"text\", \"\", 0);\n before.content = child.content.slice(0, idx);\n newTokens.push(before);\n }\n\n // Highlight open \u2014 carries thread metadata for inline tooltip\n const open = new Token(\"mrsf_highlight_open\", \"mark\", 1);\n open.attrSet(\"class\", \"mrsf-highlight\");\n open.attrSet(\"data-mrsf-comment-id\", thread.comment.id);\n open.meta = { thread };\n newTokens.push(open);\n\n // Matched text\n const matched = new Token(\"text\", \"\", 0);\n matched.content = trimmed;\n newTokens.push(matched);\n\n // Highlight close \u2014 carries thread metadata for inline tooltip\n const close = new Token(\"mrsf_highlight_close\", \"mark\", -1);\n close.meta = { thread, interactive };\n newTokens.push(close);\n\n // Text after match\n if (idx + trimmed.length < child.content.length) {\n const after = new Token(\"text\", \"\", 0);\n after.content = child.content.slice(idx + trimmed.length);\n newTokens.push(after);\n }\n\n // Replace the original child with our new tokens\n token.children.splice(i, 1, ...newTokens);\n return true;\n }\n\n return false;\n}\n\n/**\n * Install the MRSF core rule on a markdown-it instance.\n */\nexport function installCoreRule(\n md: { core: { ruler: { push: (name: string, fn: (state: StateCore) => void) => void } } },\n lineMap: LineMap,\n options: CoreOptions,\n): void {\n const { interactive, gutterPosition, gutterForInline, inlineHighlights } = options;\n\n md.core.ruler.push(\"mrsf_inject\", (state: StateCore) => {\n const tokens = state.tokens;\n const TokenCtor = state.Token;\n\n // Track which lines we've already processed (to avoid double-injection\n // when multiple tokens share the same source line).\n const processed = new Set<number>();\n\n // We iterate backwards so that inserting tokens doesn't shift indices\n // for tokens we haven't visited yet.\n for (let i = tokens.length - 1; i >= 0; i--) {\n const token = tokens[i];\n const map = token.map;\n if (!map) continue;\n\n // Skip inline tokens \u2014 they share the same map as their parent block.\n // We must process the parent block (paragraph_open, heading_open, etc.)\n // so the badge and class land on the correct rendered element.\n if (token.type === \"inline\") continue;\n\n // Check each line in this token's range for comments\n for (let line0 = map[0]; line0 < map[1]; line0++) {\n const line = line0 + 1; // 1-based\n if (processed.has(line)) continue;\n\n const threads = lineMap.get(line);\n if (!threads || threads.length === 0) continue;\n processed.add(line);\n\n // Add line-highlight class to the block token\n const existingClass = token.attrGet(\"class\") || \"\";\n token.attrSet(\n \"class\",\n existingClass\n ? `${existingClass} mrsf-line-highlight`\n : \"mrsf-line-highlight\",\n );\n token.attrSet(\"data-mrsf-line\", String(line));\n\n // Determine whether all threads on this line have inline highlights\n const allHaveInline = inlineHighlights &&\n threads.every((t) => !!t.comment.selected_text);\n\n // Inject badge unless gutterForInline is false AND all threads are inline-highlighted\n const showBadge = gutterForInline || !allHaveInline || !inlineHighlights;\n if (showBadge) {\n const badgeToken = new TokenCtor(\"mrsf_badge\", \"\", 0);\n badgeToken.meta = { line, threads, interactive, gutterPosition };\n tokens.splice(i + 1, 0, badgeToken);\n }\n\n // Highlight selected_text in the following inline token\n if (inlineHighlights) {\n const offset = showBadge ? 2 : 1;\n const inlineAfter = tokens[i + offset];\n if (inlineAfter && inlineAfter.type === \"inline\") {\n for (const thread of threads) {\n if (thread.comment.selected_text) {\n highlightInlineText(inlineAfter, thread, interactive, TokenCtor);\n }\n }\n }\n }\n }\n }\n\n // Wrap the entire output in a gutter container when using left gutter\n if (gutterPosition === \"left\" && processed.size > 0) {\n const openToken = new TokenCtor(\"mrsf_gutter_open\", \"div\", 1);\n openToken.attrSet(\"class\", \"mrsf-gutter-container\");\n tokens.unshift(openToken);\n const closeToken = new TokenCtor(\"mrsf_gutter_close\", \"div\", -1 as Nesting);\n tokens.push(closeToken);\n }\n });\n}\n", "/**\n * MRSF markdown-it plugin \u2014 renderer rules.\n *\n * Adds custom renderer rules for MRSF tokens:\n * - mrsf_badge: renders badge + tooltip HTML (gutter)\n * - mrsf_highlight_open/close: renders <mark> wrappers with inline tooltips\n */\n\nimport type { CommentThread } from \"../types.js\";\nimport { escapeHtml, renderThreadHtml } from \"@mrsf/plugin-shared\";\n\n/**\n * Install MRSF renderer rules on a markdown-it instance.\n */\nexport function installRendererRules(\n md: { renderer: { rules: Record<string, ((...args: any[]) => string) | undefined> } },\n): void {\n // Badge + tooltip (gutter)\n md.renderer.rules[\"mrsf_badge\"] = (\n tokens: { meta: { line: number; threads: CommentThread[]; interactive: boolean; gutterPosition: \"left\" | \"tight\" | \"right\" } }[],\n idx: number,\n ): string => {\n const { line, threads, interactive, gutterPosition } = tokens[idx].meta;\n\n const total = threads.reduce(\n (n, t) => n + 1 + t.replies.length,\n 0,\n );\n const allResolved = threads.every((t) => t.comment.resolved);\n const highestSeverity = threads.reduce<string | null>((sev, t) => {\n if (t.comment.severity === \"high\" || sev === \"high\") return \"high\";\n if (t.comment.severity === \"medium\" || sev === \"medium\") return \"medium\";\n if (t.comment.severity === \"low\" || sev === \"low\") return \"low\";\n return sev;\n }, null);\n\n const resolvedClass = allResolved ? \" mrsf-badge-resolved\" : \"\";\n const severityClass = highestSeverity === \"high\" || highestSeverity === \"medium\"\n ? ` mrsf-badge-severity-${highestSeverity}`\n : \"\";\n const positionClass = ` mrsf-gutter-${gutterPosition}`;\n\n const icon = allResolved ? \"\u2713\" : \"\uD83D\uDCAC\";\n\n let html = `<span class=\"mrsf-tooltip-anchor${positionClass}\">`;\n\n // Badge\n html += `<span class=\"mrsf-badge${resolvedClass}${severityClass}\" data-mrsf-line=\"${line}\" data-mrsf-action=\"navigate\" data-mrsf-comment-id=\"${escapeHtml(threads[0].comment.id)}\" tabindex=\"0\">${icon} ${total}</span>`;\n\n // Tooltip\n html += `<span class=\"mrsf-tooltip\" data-mrsf-line=\"${line}\">`;\n for (const thread of threads) {\n html += renderThreadHtml(thread, interactive);\n }\n html += `</span>`;\n\n html += `</span>`;\n return html;\n };\n\n // Highlight open \u2014 wraps in tooltip-anchor for inline hover (feature D)\n md.renderer.rules[\"mrsf_highlight_open\"] = (\n tokens: any[],\n idx: number,\n ): string => {\n const token = tokens[idx];\n const cls = token.attrGet(\"class\") || \"mrsf-highlight\";\n const commentId = token.attrGet(\"data-mrsf-comment-id\") || \"\";\n const thread = token.meta?.thread as CommentThread | undefined;\n\n let html = \"\";\n if (thread) {\n html += `<span class=\"mrsf-tooltip-anchor mrsf-inline-anchor\">`;\n }\n html += `<mark class=\"${escapeHtml(cls)}\" data-mrsf-comment-id=\"${escapeHtml(commentId)}\" tabindex=\"0\">`;\n return html;\n };\n\n // Highlight close \u2014 adds inline tooltip and closes tooltip-anchor\n md.renderer.rules[\"mrsf_highlight_close\"] = (\n tokens: any[],\n idx: number,\n ): string => {\n const token = tokens[idx];\n const meta = token.meta;\n let html = \"</mark>\";\n\n if (meta?.thread) {\n const thread = meta.thread as CommentThread;\n const interactive = meta.interactive as boolean;\n html += `<span class=\"mrsf-tooltip mrsf-inline-tooltip\">`;\n html += renderThreadHtml(thread, interactive);\n html += `</span>`;\n html += `</span>`;\n }\n\n return html;\n };\n\n // Gutter container wrapper (left gutter mode)\n md.renderer.rules[\"mrsf_gutter_open\"] = (\n tokens: any[],\n idx: number,\n ): string => {\n const cls = tokens[idx].attrGet(\"class\") || \"mrsf-gutter-container\";\n return `<div class=\"${escapeHtml(cls)}\">`;\n };\n\n md.renderer.rules[\"mrsf_gutter_close\"] = (): string => {\n return `</div>`;\n };\n}\n", "/**\n * Shared logic for @mrsf/markdown-it-mrsf.\n *\n * Contains the plugin factory using shared comment loading/grouping logic.\n */\n\nimport type MarkdownIt from \"markdown-it\";\nimport type { MrsfPluginOptions, CommentLoader } from \"./types.js\";\nimport { resolveComments } from \"@mrsf/plugin-shared\";\nimport { installCoreRule } from \"./rules/core.js\";\nimport { installRendererRules } from \"./rules/renderer.js\";\n\n/**\n * Create a markdown-it plugin function with the given comment loader.\n *\n * @param loader - Function that resolves sidecar data from plugin options\n * @returns A markdown-it plugin function\n */\nexport function createMrsfPlugin(loader: CommentLoader) {\n return function mrsfPlugin(md: MarkdownIt, options: MrsfPluginOptions = {}): void {\n const interactive = options.interactive ?? false;\n const gutterPosition = options.gutterPosition ?? \"right\";\n const gutterForInline = options.gutterForInline ?? true;\n const inlineHighlights = options.inlineHighlights ?? true;\n\n const result = resolveComments(loader, options);\n if (!result) return;\n\n installCoreRule(md, result.lineMap, {\n interactive,\n gutterPosition,\n gutterForInline,\n inlineHighlights,\n });\n installRendererRules(md);\n };\n}\n"],
5
- "mappings": "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",
3
+ "sources": ["../src/index.ts", "../../shared/src/comments.ts", "../src/rules/core.ts", "../src/rules/renderer.ts", "../src/shared.ts"],
4
+ "sourcesContent": ["/**\n * @mrsf/markdown-it-mrsf \u2014 markdown-it plugin for MRSF review comments.\n *\n * This is the full-featured Node.js entry point. It supports all four\n * sidecar provisioning modes: `comments`, `loader`, `sidecarPath`, and\n * `documentPath`. The latter two require Node.js file-system APIs.\n *\n * In browser / bundler environments, the `\"browser\"` export condition in\n * package.json automatically resolves to the browser-safe entry, so you\n * never need to import a different path \u2014 just use:\n *\n * @example\n * ```ts\n * import MarkdownIt from \"markdown-it\";\n * import { mrsfPlugin } from \"@mrsf/markdown-it-mrsf\";\n *\n * const md = new MarkdownIt();\n * md.use(mrsfPlugin, { documentPath: \"docs/guide.md\" });\n * const html = md.render(markdownSource);\n * ```\n */\n\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { parseSidecarContent } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions } from \"./types.js\";\nimport { createMrsfPlugin } from \"./shared.js\";\n\n// Re-export types for consumers\nexport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\n/**\n * The markdown-it plugin function (Node.js \u2014 full feature set).\n */\nexport const mrsfPlugin = createMrsfPlugin((options: MrsfPluginOptions) => {\n // Priority 1: inline data\n if (options.comments) {\n return options.comments;\n }\n\n // Priority 2: custom loader function\n if (options.loader) {\n try {\n return options.loader();\n } catch {\n return null;\n }\n }\n\n // Priority 3: explicit sidecar path\n if (options.sidecarPath) {\n try {\n const abs = path.resolve(options.cwd || process.cwd(), options.sidecarPath);\n const content = readFileSync(abs, \"utf-8\");\n return parseSidecarContent(content, abs);\n } catch {\n return null;\n }\n }\n\n // Priority 4: auto-discover from documentPath\n if (options.documentPath) {\n try {\n const cwd = options.cwd || process.cwd();\n const abs = path.resolve(cwd, options.documentPath);\n const yamlPath = abs + \".review.yaml\";\n const jsonPath = abs + \".review.json\";\n\n let raw: string | null = null;\n let hint: string | null = null;\n try {\n raw = readFileSync(yamlPath, \"utf-8\");\n hint = yamlPath;\n } catch {\n try {\n raw = readFileSync(jsonPath, \"utf-8\");\n hint = jsonPath;\n } catch {\n return null;\n }\n }\n\n if (!raw) return null;\n return parseSidecarContent(raw, hint!);\n } catch {\n return null;\n }\n }\n\n return null;\n});\n\nexport default mrsfPlugin;\n", "/**\n * Shared comment loading and grouping logic for MRSF rendering plugins.\n */\n\nimport type { MrsfDocument } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\nexport type { CommentLoader };\n\n/**\n * Convert an MrsfDocument into a slim comment array.\n */\nexport function toSlimComments(doc: MrsfDocument): SlimComment[] {\n return doc.comments.map((c) => ({\n id: c.id,\n author: c.author || \"Unknown\",\n text: c.text || \"\",\n line: c.line ?? null,\n end_line: c.end_line ?? null,\n start_column: c.start_column ?? null,\n end_column: c.end_column ?? null,\n selected_text: c.selected_text || null,\n resolved: !!c.resolved,\n reply_to: c.reply_to || null,\n severity: c.severity || null,\n type: c.type || null,\n timestamp: c.timestamp || null,\n }));\n}\n\n/**\n * Group comments by line number, threading replies under parents.\n */\nexport function groupByLine(comments: SlimComment[]): LineMap {\n const rootComments = comments.filter((c) => !c.reply_to && c.line != null);\n const replies = comments.filter((c) => c.reply_to);\n\n const replyMap = new Map<string, SlimComment[]>();\n for (const r of replies) {\n const list = replyMap.get(r.reply_to!) || [];\n list.push(r);\n replyMap.set(r.reply_to!, list);\n }\n\n const lineMap: LineMap = new Map();\n for (const c of rootComments) {\n const line = c.line!;\n const threads = lineMap.get(line) || [];\n threads.push({\n comment: c,\n replies: replyMap.get(c.id) || [],\n });\n lineMap.set(line, threads);\n }\n\n return lineMap;\n}\n\n/**\n * Resolve options into a filtered LineMap ready for rendering.\n * Returns null if there are no comments to render.\n */\nexport function resolveComments(\n loader: CommentLoader,\n options: MrsfPluginOptions,\n): { lineMap: LineMap; comments: SlimComment[] } | null {\n const showResolved = options.showResolved ?? true;\n\n const doc = loader(options);\n if (!doc || !doc.comments || doc.comments.length === 0) {\n return null;\n }\n\n let comments = toSlimComments(doc);\n if (!showResolved) {\n comments = comments.filter((c) => !c.resolved);\n }\n\n if (comments.length === 0) return null;\n\n const lineMap = groupByLine(comments);\n return { lineMap, comments };\n}\n", "/**\n * MRSF markdown-it plugin \u2014 core rule (overlay gutter architecture).\n *\n * Walks the parsed token stream and annotates block tokens with\n * `data-mrsf-line` attributes. Adds `mrsf-line-highlight` class on\n * commented lines. Appends an `mrsf_data_script` token with serialized\n * comment data for the client-side MrsfController.\n *\n * NO visual tokens are injected \u2014 all badges, tooltips, and gutter\n * elements are created at runtime by the controller.\n */\n\nimport type Token from \"markdown-it/lib/token.mjs\";\nimport type { Nesting } from \"markdown-it/lib/token.mjs\";\nimport type StateCore from \"markdown-it/lib/rules_core/state_core.mjs\";\nimport type { LineMap, CommentThread } from \"../types.js\";\n\nexport interface CoreRuleOptions {\n /** Whether to add mrsf-line-highlight class on commented tokens. Default: false. */\n lineHighlight?: boolean;\n}\n\n/**\n * Install the MRSF core rule on a markdown-it instance.\n */\nexport function installCoreRule(\n md: { core: { ruler: { push: (name: string, fn: (state: StateCore) => void) => void } } },\n lineMap: LineMap,\n options: CoreRuleOptions = {},\n): void {\n md.core.ruler.push(\"mrsf_inject\", (state: StateCore) => {\n const tokens = state.tokens;\n const TokenCtor = state.Token;\n const processed = new Set<number>();\n\n for (let i = tokens.length - 1; i >= 0; i--) {\n const token = tokens[i];\n const map = token.map;\n if (!map) continue;\n\n // Skip inline tokens \u2014 process their parent block tokens only.\n if (token.type === \"inline\") continue;\n\n // Annotate the element with line range\n const startLine1 = map[0] + 1;\n const endLine1 = map[1];\n token.attrSet(\"data-mrsf-line\", String(startLine1));\n token.attrSet(\"data-mrsf-start-line\", String(startLine1));\n token.attrSet(\"data-mrsf-end-line\", String(endLine1));\n\n // Check each line in this token's range for comments\n for (let line0 = map[0]; line0 < map[1]; line0++) {\n const line = line0 + 1; // 1-based\n if (processed.has(line)) continue;\n processed.add(line);\n\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n if (options.lineHighlight) {\n const existingClass = token.attrGet(\"class\") || \"\";\n if (!existingClass.includes(\"mrsf-line-highlight\")) {\n token.attrSet(\n \"class\",\n existingClass\n ? `${existingClass} mrsf-line-highlight`\n : \"mrsf-line-highlight\",\n );\n }\n }\n token.attrSet(\"data-mrsf-line\", String(line));\n }\n }\n }\n\n // Append a data script token with all comment data\n const allThreads: CommentThread[] = [];\n for (const threads of lineMap.values()) {\n allThreads.push(...threads);\n }\n if (allThreads.length > 0) {\n const scriptToken = new TokenCtor(\"mrsf_data_script\", \"\", 0);\n scriptToken.meta = { threads: allThreads };\n tokens.push(scriptToken);\n }\n });\n}\n", "/**\n * MRSF markdown-it plugin \u2014 renderer rules (overlay gutter architecture).\n *\n * Adds a single custom renderer rule:\n * - mrsf_data_script: renders a <script type=\"application/mrsf+json\">\n * containing serialized comment data for the MrsfController.\n */\n\nimport type { CommentThread } from \"../types.js\";\n\n/**\n * Install MRSF renderer rules on a markdown-it instance.\n */\nexport function installRendererRules(\n md: { renderer: { rules: Record<string, ((...args: any[]) => string) | undefined> } },\n): void {\n md.renderer.rules[\"mrsf_data_script\"] = (\n tokens: { meta: { threads: CommentThread[] } }[],\n idx: number,\n ): string => {\n const { threads } = tokens[idx].meta;\n // Escape < to prevent script tag injection (XSS safety)\n const data = JSON.stringify({ threads }).replace(/</g, \"\\\\u003c\");\n return `<script type=\"application/mrsf+json\">${data}</script>`;\n };\n}\n", "/**\n * Shared logic for @mrsf/markdown-it-mrsf.\n *\n * Contains the plugin factory using shared comment loading/grouping logic.\n */\n\nimport type MarkdownIt from \"markdown-it\";\nimport type { MrsfPluginOptions, CommentLoader } from \"./types.js\";\nimport { resolveComments } from \"@mrsf/plugin-shared\";\nimport { installCoreRule } from \"./rules/core.js\";\nimport { installRendererRules } from \"./rules/renderer.js\";\n\n/**\n * Create a markdown-it plugin function with the given comment loader.\n *\n * @param loader - Function that resolves sidecar data from plugin options\n * @returns A markdown-it plugin function\n */\nexport function createMrsfPlugin(loader: CommentLoader) {\n return function mrsfPlugin(md: MarkdownIt, options: MrsfPluginOptions = {}): void {\n const result = resolveComments(loader, options);\n if (!result) return;\n\n installCoreRule(md, result.lineMap, { lineHighlight: options.lineHighlight ?? false });\n installRendererRules(md);\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["mrsfPlugin"]
7
7
  }
@@ -1,18 +1,19 @@
1
1
  /**
2
- * MRSF markdown-it plugin — core rule.
2
+ * MRSF markdown-it plugin — core rule (overlay gutter architecture).
3
3
  *
4
- * Walks the parsed token stream and injects `mrsf_badge` tokens
5
- * at lines that have comments, and wraps `selected_text` matches
6
- * with `mrsf_highlight_open`/`close` tokens (with tooltip metadata).
4
+ * Walks the parsed token stream and annotates block tokens with
5
+ * `data-mrsf-line` attributes. Adds `mrsf-line-highlight` class on
6
+ * commented lines. Appends an `mrsf_data_script` token with serialized
7
+ * comment data for the client-side MrsfController.
8
+ *
9
+ * NO visual tokens are injected — all badges, tooltips, and gutter
10
+ * elements are created at runtime by the controller.
7
11
  */
8
12
  import type StateCore from "markdown-it/lib/rules_core/state_core.mjs";
9
13
  import type { LineMap } from "../types.js";
10
- /** Options forwarded from the plugin to the core rule. */
11
- export interface CoreOptions {
12
- interactive: boolean;
13
- gutterPosition: "left" | "tight" | "right";
14
- gutterForInline: boolean;
15
- inlineHighlights: boolean;
14
+ export interface CoreRuleOptions {
15
+ /** Whether to add mrsf-line-highlight class on commented tokens. Default: false. */
16
+ lineHighlight?: boolean;
16
17
  }
17
18
  /**
18
19
  * Install the MRSF core rule on a markdown-it instance.
@@ -23,5 +24,5 @@ export declare function installCoreRule(md: {
23
24
  push: (name: string, fn: (state: StateCore) => void) => void;
24
25
  };
25
26
  };
26
- }, lineMap: LineMap, options: CoreOptions): void;
27
+ }, lineMap: LineMap, options?: CoreRuleOptions): void;
27
28
  //# sourceMappingURL=core.d.ts.map