@mrsf/markdown-it-mrsf 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wictor Wilén
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @mrsf/markdown-it-mrsf
2
+
3
+ A [markdown-it](https://github.com/markdown-it/markdown-it) plugin for rendering [MRSF (Sidemark)](https://github.com/wictorwilen/MRSF) review comments directly into HTML output.
4
+
5
+ Renders badges, line highlights, inline text highlights, and tooltips — no client-side JavaScript required for static use. An optional interactive mode adds action buttons for host applications to hook into.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @mrsf/markdown-it-mrsf
11
+ ```
12
+
13
+ ## Browser & Bundler Support
14
+
15
+ The plugin works seamlessly in both Node.js and browser environments. When bundled with tools like **Vite**, **webpack**, or **esbuild**, the `"browser"` export condition in `package.json` automatically resolves to a browser-safe entry point — no special imports required.
16
+
17
+ ```ts
18
+ // Works everywhere — Node.js, Vite, webpack, etc.
19
+ import { mrsfPlugin } from "@mrsf/markdown-it-mrsf";
20
+ ```
21
+
22
+ In browser environments, only the `comments` and `loader` options are available (no file-system access). The `sidecarPath` and `documentPath` options require Node.js.
23
+
24
+ ## Usage
25
+
26
+ ### Inline data (recommended for testing and custom pipelines)
27
+
28
+ ```ts
29
+ import MarkdownIt from "markdown-it";
30
+ import { mrsfPlugin } from "@mrsf/markdown-it-mrsf";
31
+
32
+ const md = new MarkdownIt();
33
+ md.use(mrsfPlugin, {
34
+ comments: {
35
+ mrsf_version: "1.0",
36
+ document: "guide.md",
37
+ comments: [
38
+ {
39
+ id: "c1",
40
+ author: "Jane",
41
+ timestamp: "2026-01-01T00:00:00Z",
42
+ text: "Can you clarify this section?",
43
+ resolved: false,
44
+ line: 3,
45
+ },
46
+ ],
47
+ },
48
+ });
49
+
50
+ const html = md.render(markdownSource);
51
+ ```
52
+
53
+ ### Auto-discover from disk
54
+
55
+ ```ts
56
+ md.use(mrsfPlugin, {
57
+ documentPath: "docs/guide.md",
58
+ });
59
+ ```
60
+
61
+ The plugin looks for `docs/guide.md.review.yaml` (or `.review.json`) next to the document.
62
+
63
+ ### Explicit sidecar path
64
+
65
+ ```ts
66
+ md.use(mrsfPlugin, {
67
+ sidecarPath: ".reviews/docs/guide.md.review.yaml",
68
+ });
69
+ ```
70
+
71
+ ### Custom loader function
72
+
73
+ ```ts
74
+ md.use(mrsfPlugin, {
75
+ loader: () => {
76
+ // Load sidecar data however you like — from a database, API, in-memory store, etc.
77
+ return {
78
+ mrsf_version: "1.0",
79
+ document: "guide.md",
80
+ comments: [/* ... */],
81
+ };
82
+ },
83
+ });
84
+ ```
85
+
86
+ ## Stylesheet
87
+
88
+ Include the default CSS for badges, tooltips, and highlights:
89
+
90
+ ```ts
91
+ import "@mrsf/markdown-it-mrsf/style.css";
92
+ ```
93
+
94
+ Or link directly in HTML:
95
+
96
+ ```html
97
+ <link rel="stylesheet" href="node_modules/@mrsf/markdown-it-mrsf/dist/style.css">
98
+ ```
99
+
100
+ Customise with CSS custom properties:
101
+
102
+ ```css
103
+ :root {
104
+ --mrsf-accent: #3794ff;
105
+ --mrsf-badge-bg: #007acc;
106
+ --mrsf-badge-fg: #fff;
107
+ --mrsf-tooltip-bg: #252526;
108
+ --mrsf-tooltip-fg: #cccccc;
109
+ --mrsf-highlight-bg: rgba(255, 213, 79, 0.25);
110
+ }
111
+ ```
112
+
113
+ ## Interactive Mode
114
+
115
+ Enable `interactive: true` to add action buttons (Resolve, Reply, Edit) inside tooltips. These render as `<button data-mrsf-action="..." data-mrsf-comment-id="...">` elements — inert without JavaScript.
116
+
117
+ ```ts
118
+ md.use(mrsfPlugin, {
119
+ comments: sidecarData,
120
+ interactive: true,
121
+ });
122
+ ```
123
+
124
+ ### Controller
125
+
126
+ Include the optional controller to dispatch custom events when action buttons are clicked:
127
+
128
+ ```ts
129
+ import "@mrsf/markdown-it-mrsf/controller";
130
+
131
+ document.addEventListener("mrsf:resolve", (e) => {
132
+ console.log("Resolve comment:", e.detail.commentId);
133
+ });
134
+
135
+ document.addEventListener("mrsf:reply", (e) => {
136
+ console.log("Reply to:", e.detail.commentId);
137
+ });
138
+ ```
139
+
140
+ Events dispatched: `mrsf:resolve`, `mrsf:unresolve`, `mrsf:reply`, `mrsf:edit`, `mrsf:navigate`.
141
+
142
+ Each event's `detail` contains `{ commentId: string, line: number | null, action: string }`.
143
+
144
+ ## Options
145
+
146
+ | Option | Type | Default | Description |
147
+ |--------|------|---------|-------------|
148
+ | `comments` | `MrsfDocument` | — | Pre-loaded sidecar data (highest priority) |
149
+ | `loader` | `() => MrsfDocument \| null` | — | Custom loader function for sidecar data |
150
+ | `documentPath` | `string` | — | Path to Markdown file for auto-discovery |
151
+ | `sidecarPath` | `string` | — | Explicit path to sidecar file |
152
+ | `showResolved` | `boolean` | `true` | Whether to show resolved comments |
153
+ | `interactive` | `boolean` | `false` | Add action buttons for host JS integration |
154
+ | `gutterPosition` | `'left' \| 'tight' \| 'right'` | `'right'` | Badge position: left margin gutter, tight (before text), or floated right |
155
+ | `gutterForInline` | `boolean` | `true` | Show gutter badge for comments that also have inline highlights |
156
+ | `inlineHighlights` | `boolean` | `true` | Render inline text highlights for `selected_text` |
157
+ | `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Color scheme hint |
158
+ | `cwd` | `string` | `process.cwd()` | Working directory for file resolution |
159
+
160
+ ### Display Modes
161
+
162
+ The plugin supports several display modes that can be combined:
163
+
164
+ - **Gutter + inline** (default): Badges appear in the gutter, `selected_text` is highlighted inline with hover tooltips on both.
165
+ - **Gutter only** (`inlineHighlights: false`): Only gutter badges shown, no inline highlights.
166
+ - **Inline only** (`gutterForInline: false`): Lines with inline-highlighted comments show only the highlight (with tooltip) — no gutter badge. Lines without `selected_text` still show badges.
167
+
168
+ ## DOM Contract
169
+
170
+ All rendered elements follow a strict data-attribute contract:
171
+
172
+ - `data-mrsf-line` — source line number (1-based)
173
+ - `data-mrsf-comment-id` — comment identifier
174
+ - `data-mrsf-action` — action type (`resolve`, `unresolve`, `reply`, `edit`, `navigate`)
175
+
176
+ ## License
177
+
178
+ [MIT](../LICENSE)
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Browser-safe entry point for @mrsf/markdown-it-mrsf.
3
+ *
4
+ * This entry is automatically selected by bundlers (Vite, webpack, esbuild)
5
+ * via the `"browser"` export condition in package.json. It supports the
6
+ * `comments` and `loader` provisioning modes — no Node.js file-system APIs.
7
+ *
8
+ * You do NOT need to import this directly. Just use:
9
+ * import { mrsfPlugin } from "@mrsf/markdown-it-mrsf";
10
+ *
11
+ * Your bundler will resolve to this file automatically when targeting
12
+ * a browser environment.
13
+ */
14
+ import type { MrsfPluginOptions } from "./types.js";
15
+ export type { MrsfPluginOptions, SlimComment, CommentThread, LineMap } from "./types.js";
16
+ /**
17
+ * The markdown-it plugin function (browser-safe).
18
+ *
19
+ * Supports `comments` (inline data) and `loader` (custom function) options.
20
+ */
21
+ export declare const mrsfPlugin: (md: import("markdown-it").default, options?: MrsfPluginOptions) => void;
22
+ export default mrsfPlugin;
23
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Browser-safe entry point for @mrsf/markdown-it-mrsf.
3
+ *
4
+ * This entry is automatically selected by bundlers (Vite, webpack, esbuild)
5
+ * via the `"browser"` export condition in package.json. It supports the
6
+ * `comments` and `loader` provisioning modes — no Node.js file-system APIs.
7
+ *
8
+ * You do NOT need to import this directly. Just use:
9
+ * import { mrsfPlugin } from "@mrsf/markdown-it-mrsf";
10
+ *
11
+ * Your bundler will resolve to this file automatically when targeting
12
+ * a browser environment.
13
+ */
14
+ import { createMrsfPlugin } from "./shared.js";
15
+ /**
16
+ * The markdown-it plugin function (browser-safe).
17
+ *
18
+ * Supports `comments` (inline data) and `loader` (custom function) options.
19
+ */
20
+ export const mrsfPlugin = createMrsfPlugin((options) => {
21
+ if (options.comments) {
22
+ return options.comments;
23
+ }
24
+ if (options.loader) {
25
+ try {
26
+ return options.loader();
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ if (options.sidecarPath || options.documentPath) {
33
+ console.warn("[@mrsf/markdown-it-mrsf] sidecarPath and documentPath require Node.js. " +
34
+ "Use `comments` or `loader` options in browser environments.");
35
+ }
36
+ return null;
37
+ });
38
+ export default mrsfPlugin;
39
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Sidemark — Client-side event controller for interactive mode.
3
+ *
4
+ * Optional module that listens for clicks on `[data-mrsf-action]` elements
5
+ * and dispatches CustomEvents on the document for host applications.
6
+ *
7
+ * Usage (ESM):
8
+ * import "@mrsf/markdown-it-mrsf/controller";
9
+ *
10
+ * Usage (script tag):
11
+ * <script src=".../@mrsf/markdown-it-mrsf/dist/controller.js"></script>
12
+ *
13
+ * Events dispatched:
14
+ * - mrsf:resolve { commentId, line }
15
+ * - mrsf:unresolve { commentId, line }
16
+ * - mrsf:reply { commentId, line }
17
+ * - mrsf:edit { commentId, line }
18
+ * - mrsf:navigate { commentId, line }
19
+ */
20
+ export type MrsfAction = "resolve" | "unresolve" | "reply" | "edit" | "navigate";
21
+ export interface MrsfActionDetail {
22
+ commentId: string;
23
+ line: number | null;
24
+ action: MrsfAction;
25
+ }
26
+ //# sourceMappingURL=controller.d.ts.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Sidemark — Client-side event controller for interactive mode.
3
+ *
4
+ * Optional module that listens for clicks on `[data-mrsf-action]` elements
5
+ * and dispatches CustomEvents on the document for host applications.
6
+ *
7
+ * Usage (ESM):
8
+ * import "@mrsf/markdown-it-mrsf/controller";
9
+ *
10
+ * Usage (script tag):
11
+ * <script src=".../@mrsf/markdown-it-mrsf/dist/controller.js"></script>
12
+ *
13
+ * Events dispatched:
14
+ * - mrsf:resolve { commentId, line }
15
+ * - mrsf:unresolve { commentId, line }
16
+ * - mrsf:reply { commentId, line }
17
+ * - mrsf:edit { commentId, line }
18
+ * - mrsf:navigate { commentId, line }
19
+ */
20
+ function init() {
21
+ document.addEventListener("click", (e) => {
22
+ const target = e.target.closest("[data-mrsf-action]");
23
+ if (!target)
24
+ return;
25
+ const action = target.dataset.mrsfAction;
26
+ const commentId = target.dataset.mrsfCommentId;
27
+ if (!action || !commentId)
28
+ return;
29
+ const lineStr = target.dataset.mrsfLine;
30
+ const line = lineStr ? parseInt(lineStr, 10) : null;
31
+ e.preventDefault();
32
+ e.stopPropagation();
33
+ const detail = { commentId, line, action };
34
+ document.dispatchEvent(new CustomEvent(`mrsf:${action}`, { detail, bubbles: true }));
35
+ });
36
+ }
37
+ // Auto-initialize
38
+ init();
39
+ export {};
40
+ //# sourceMappingURL=controller.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @mrsf/markdown-it-mrsf — markdown-it plugin for MRSF review comments.
3
+ *
4
+ * This is the full-featured Node.js entry point. It supports all four
5
+ * sidecar provisioning modes: `comments`, `loader`, `sidecarPath`, and
6
+ * `documentPath`. The latter two require Node.js file-system APIs.
7
+ *
8
+ * In browser / bundler environments, the `"browser"` export condition in
9
+ * package.json automatically resolves to the browser-safe entry, so you
10
+ * never need to import a different path — just use:
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import MarkdownIt from "markdown-it";
15
+ * import { mrsfPlugin } from "@mrsf/markdown-it-mrsf";
16
+ *
17
+ * const md = new MarkdownIt();
18
+ * md.use(mrsfPlugin, { documentPath: "docs/guide.md" });
19
+ * const html = md.render(markdownSource);
20
+ * ```
21
+ */
22
+ import type { MrsfPluginOptions } from "./types.js";
23
+ export type { MrsfPluginOptions, SlimComment, CommentThread, LineMap } from "./types.js";
24
+ export type { MrsfAction, MrsfActionDetail } from "./controller.js";
25
+ /**
26
+ * The markdown-it plugin function (Node.js — full feature set).
27
+ */
28
+ export declare const mrsfPlugin: (md: import("markdown-it").default, options?: MrsfPluginOptions) => void;
29
+ export default mrsfPlugin;
30
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @mrsf/markdown-it-mrsf — markdown-it plugin for MRSF review comments.
3
+ *
4
+ * This is the full-featured Node.js entry point. It supports all four
5
+ * sidecar provisioning modes: `comments`, `loader`, `sidecarPath`, and
6
+ * `documentPath`. The latter two require Node.js file-system APIs.
7
+ *
8
+ * In browser / bundler environments, the `"browser"` export condition in
9
+ * package.json automatically resolves to the browser-safe entry, so you
10
+ * never need to import a different path — just use:
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import MarkdownIt from "markdown-it";
15
+ * import { mrsfPlugin } from "@mrsf/markdown-it-mrsf";
16
+ *
17
+ * const md = new MarkdownIt();
18
+ * md.use(mrsfPlugin, { documentPath: "docs/guide.md" });
19
+ * const html = md.render(markdownSource);
20
+ * ```
21
+ */
22
+ import { readFileSync } from "node:fs";
23
+ import path from "node:path";
24
+ import { parseSidecarContent } from "@mrsf/cli";
25
+ import { createMrsfPlugin } from "./shared.js";
26
+ /**
27
+ * The markdown-it plugin function (Node.js — full feature set).
28
+ */
29
+ export const mrsfPlugin = createMrsfPlugin((options) => {
30
+ // Priority 1: inline data
31
+ if (options.comments) {
32
+ return options.comments;
33
+ }
34
+ // Priority 2: custom loader function
35
+ if (options.loader) {
36
+ try {
37
+ return options.loader();
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ // Priority 3: explicit sidecar path
44
+ if (options.sidecarPath) {
45
+ try {
46
+ const abs = path.resolve(options.cwd || process.cwd(), options.sidecarPath);
47
+ const content = readFileSync(abs, "utf-8");
48
+ return parseSidecarContent(content, abs);
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ // Priority 4: auto-discover from documentPath
55
+ if (options.documentPath) {
56
+ try {
57
+ const cwd = options.cwd || process.cwd();
58
+ const abs = path.resolve(cwd, options.documentPath);
59
+ const yamlPath = abs + ".review.yaml";
60
+ const jsonPath = abs + ".review.json";
61
+ let raw = null;
62
+ let hint = null;
63
+ try {
64
+ raw = readFileSync(yamlPath, "utf-8");
65
+ hint = yamlPath;
66
+ }
67
+ catch {
68
+ try {
69
+ raw = readFileSync(jsonPath, "utf-8");
70
+ hint = jsonPath;
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ if (!raw)
77
+ return null;
78
+ return parseSidecarContent(raw, hint);
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ }
84
+ return null;
85
+ });
86
+ export default mrsfPlugin;
87
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * MRSF markdown-it plugin — core rule.
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).
7
+ */
8
+ import type StateCore from "markdown-it/lib/rules_core/state_core.mjs";
9
+ 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;
16
+ }
17
+ /**
18
+ * Install the MRSF core rule on a markdown-it instance.
19
+ */
20
+ export declare function installCoreRule(md: {
21
+ core: {
22
+ ruler: {
23
+ push: (name: string, fn: (state: StateCore) => void) => void;
24
+ };
25
+ };
26
+ }, lineMap: LineMap, options: CoreOptions): void;
27
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1,149 @@
1
+ /**
2
+ * MRSF markdown-it plugin — core rule.
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).
7
+ */
8
+ /**
9
+ * Find the token index for a given 1-based source line.
10
+ * markdown-it tokens use `token.map = [startLine, endLine]` (0-based).
11
+ */
12
+ function findTokenIndexForLine(tokens, line) {
13
+ const line0 = line - 1; // convert to 0-based
14
+ for (let i = 0; i < tokens.length; i++) {
15
+ const map = tokens[i].map;
16
+ if (map && map[0] <= line0 && line0 < map[1]) {
17
+ return i;
18
+ }
19
+ }
20
+ return -1;
21
+ }
22
+ /**
23
+ * Wrap occurrences of `text` inside inline token children with
24
+ * highlight open/close tokens. Stores thread data on the tokens
25
+ * so the renderer can produce an inline tooltip.
26
+ */
27
+ function highlightInlineText(token, thread, interactive, Token) {
28
+ if (!token.children)
29
+ return false;
30
+ const text = thread.comment.selected_text;
31
+ if (!text)
32
+ return false;
33
+ const trimmed = text.trim();
34
+ if (!trimmed)
35
+ return false;
36
+ for (let i = 0; i < token.children.length; i++) {
37
+ const child = token.children[i];
38
+ if (child.type !== "text")
39
+ continue;
40
+ const idx = child.content.indexOf(trimmed);
41
+ if (idx === -1)
42
+ continue;
43
+ // Split the text token into: before + highlight_open + matched + highlight_close + after
44
+ const newTokens = [];
45
+ // Text before match
46
+ if (idx > 0) {
47
+ const before = new Token("text", "", 0);
48
+ before.content = child.content.slice(0, idx);
49
+ newTokens.push(before);
50
+ }
51
+ // Highlight open — carries thread metadata for inline tooltip
52
+ const open = new Token("mrsf_highlight_open", "mark", 1);
53
+ open.attrSet("class", "mrsf-highlight");
54
+ open.attrSet("data-mrsf-comment-id", thread.comment.id);
55
+ open.meta = { thread };
56
+ newTokens.push(open);
57
+ // Matched text
58
+ const matched = new Token("text", "", 0);
59
+ matched.content = trimmed;
60
+ newTokens.push(matched);
61
+ // Highlight close — carries thread metadata for inline tooltip
62
+ const close = new Token("mrsf_highlight_close", "mark", -1);
63
+ close.meta = { thread, interactive };
64
+ newTokens.push(close);
65
+ // Text after match
66
+ if (idx + trimmed.length < child.content.length) {
67
+ const after = new Token("text", "", 0);
68
+ after.content = child.content.slice(idx + trimmed.length);
69
+ newTokens.push(after);
70
+ }
71
+ // Replace the original child with our new tokens
72
+ token.children.splice(i, 1, ...newTokens);
73
+ return true;
74
+ }
75
+ return false;
76
+ }
77
+ /**
78
+ * Install the MRSF core rule on a markdown-it instance.
79
+ */
80
+ export function installCoreRule(md, lineMap, options) {
81
+ const { interactive, gutterPosition, gutterForInline, inlineHighlights } = options;
82
+ md.core.ruler.push("mrsf_inject", (state) => {
83
+ const tokens = state.tokens;
84
+ const TokenCtor = state.Token;
85
+ // Track which lines we've already processed (to avoid double-injection
86
+ // when multiple tokens share the same source line).
87
+ const processed = new Set();
88
+ // We iterate backwards so that inserting tokens doesn't shift indices
89
+ // for tokens we haven't visited yet.
90
+ for (let i = tokens.length - 1; i >= 0; i--) {
91
+ const token = tokens[i];
92
+ const map = token.map;
93
+ if (!map)
94
+ continue;
95
+ // Skip inline tokens — they share the same map as their parent block.
96
+ // We must process the parent block (paragraph_open, heading_open, etc.)
97
+ // so the badge and class land on the correct rendered element.
98
+ if (token.type === "inline")
99
+ continue;
100
+ // Check each line in this token's range for comments
101
+ for (let line0 = map[0]; line0 < map[1]; line0++) {
102
+ const line = line0 + 1; // 1-based
103
+ if (processed.has(line))
104
+ continue;
105
+ const threads = lineMap.get(line);
106
+ if (!threads || threads.length === 0)
107
+ continue;
108
+ processed.add(line);
109
+ // Add line-highlight class to the block token
110
+ const existingClass = token.attrGet("class") || "";
111
+ token.attrSet("class", existingClass
112
+ ? `${existingClass} mrsf-line-highlight`
113
+ : "mrsf-line-highlight");
114
+ token.attrSet("data-mrsf-line", String(line));
115
+ // Determine whether all threads on this line have inline highlights
116
+ const allHaveInline = inlineHighlights &&
117
+ threads.every((t) => !!t.comment.selected_text);
118
+ // Inject badge unless gutterForInline is false AND all threads are inline-highlighted
119
+ const showBadge = gutterForInline || !allHaveInline || !inlineHighlights;
120
+ if (showBadge) {
121
+ const badgeToken = new TokenCtor("mrsf_badge", "", 0);
122
+ badgeToken.meta = { line, threads, interactive, gutterPosition };
123
+ tokens.splice(i + 1, 0, badgeToken);
124
+ }
125
+ // Highlight selected_text in the following inline token
126
+ if (inlineHighlights) {
127
+ const offset = showBadge ? 2 : 1;
128
+ const inlineAfter = tokens[i + offset];
129
+ if (inlineAfter && inlineAfter.type === "inline") {
130
+ for (const thread of threads) {
131
+ if (thread.comment.selected_text) {
132
+ highlightInlineText(inlineAfter, thread, interactive, TokenCtor);
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ // Wrap the entire output in a gutter container when using left gutter
140
+ if (gutterPosition === "left" && processed.size > 0) {
141
+ const openToken = new TokenCtor("mrsf_gutter_open", "div", 1);
142
+ openToken.attrSet("class", "mrsf-gutter-container");
143
+ tokens.unshift(openToken);
144
+ const closeToken = new TokenCtor("mrsf_gutter_close", "div", -1);
145
+ tokens.push(closeToken);
146
+ }
147
+ });
148
+ }
149
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * MRSF markdown-it plugin — renderer rules.
3
+ *
4
+ * Adds custom renderer rules for MRSF tokens:
5
+ * - mrsf_badge: renders badge + tooltip HTML (gutter)
6
+ * - mrsf_highlight_open/close: renders <mark> wrappers with inline tooltips
7
+ */
8
+ /**
9
+ * Install MRSF renderer rules on a markdown-it instance.
10
+ */
11
+ export declare function installRendererRules(md: {
12
+ renderer: {
13
+ rules: Record<string, ((...args: any[]) => string) | undefined>;
14
+ };
15
+ }): void;
16
+ //# sourceMappingURL=renderer.d.ts.map