@mrsf/markdown-it-mrsf 0.2.0 → 0.3.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/README.md +14 -4
- package/dist/browser.js +70 -25
- package/dist/browser.js.map +2 -2
- package/dist/controller.d.ts +31 -8
- package/dist/controller.js +303 -3
- package/dist/controller.js.map +2 -2
- package/dist/index.js +70 -25
- package/dist/index.js.map +2 -2
- package/dist/style.css +85 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,7 +112,12 @@ Customise with CSS custom properties:
|
|
|
112
112
|
|
|
113
113
|
## Interactive Mode
|
|
114
114
|
|
|
115
|
-
Enable `interactive: true` to add action buttons (Resolve, Reply, Edit) inside tooltips
|
|
115
|
+
Enable `interactive: true` to add action buttons (Resolve, Reply, Edit) inside tooltips, plus:
|
|
116
|
+
|
|
117
|
+
- Gutter “Add comment” buttons for each line badge
|
|
118
|
+
- An inline “Add comment” floater that appears when users select text
|
|
119
|
+
|
|
120
|
+
Buttons render as `<button data-mrsf-action="..." data-mrsf-comment-id="...">` elements — inert without JavaScript.
|
|
116
121
|
|
|
117
122
|
```ts
|
|
118
123
|
md.use(mrsfPlugin, {
|
|
@@ -135,11 +140,15 @@ document.addEventListener("mrsf:resolve", (e) => {
|
|
|
135
140
|
document.addEventListener("mrsf:reply", (e) => {
|
|
136
141
|
console.log("Reply to:", e.detail.commentId);
|
|
137
142
|
});
|
|
143
|
+
|
|
144
|
+
document.addEventListener("mrsf:add", (e) => {
|
|
145
|
+
console.log("Add on line", e.detail.line, "selection", e.detail.selectionText);
|
|
146
|
+
});
|
|
138
147
|
```
|
|
139
148
|
|
|
140
|
-
Events dispatched: `mrsf:resolve`, `mrsf:unresolve`, `mrsf:reply`, `mrsf:edit`, `mrsf:navigate`.
|
|
149
|
+
Events dispatched: `mrsf:add`, `mrsf:resolve`, `mrsf:unresolve`, `mrsf:reply`, `mrsf:edit`, `mrsf:navigate`.
|
|
141
150
|
|
|
142
|
-
Each event's `detail` contains `{ commentId: string, line: number | null, action: string }
|
|
151
|
+
Each event's `detail` contains `{ commentId: string | null, line: number | null, action: string, selectionText?: string | null, start_line?: number | null, end_line?: number | null, start_column?: number | null, end_column?: number | null }` (snake_case to align with the CLI SDK parameters).
|
|
143
152
|
|
|
144
153
|
## Options
|
|
145
154
|
|
|
@@ -171,7 +180,8 @@ All rendered elements follow a strict data-attribute contract:
|
|
|
171
180
|
|
|
172
181
|
- `data-mrsf-line` — source line number (1-based)
|
|
173
182
|
- `data-mrsf-comment-id` — comment identifier
|
|
174
|
-
- `data-mrsf-action` — action type (`resolve`, `unresolve`, `reply`, `edit`, `navigate`)
|
|
183
|
+
- `data-mrsf-action` — action type (`add`, `resolve`, `unresolve`, `reply`, `edit`, `navigate`)
|
|
184
|
+
- `data-mrsf-start-line`, `data-mrsf-end-line`, `data-mrsf-start-column`, `data-mrsf-end-column` — optional selection anchors when present
|
|
175
185
|
|
|
176
186
|
## License
|
|
177
187
|
|
package/dist/browser.js
CHANGED
|
@@ -173,35 +173,53 @@ function installCoreRule(md, lineMap, options) {
|
|
|
173
173
|
const map = token.map;
|
|
174
174
|
if (!map) continue;
|
|
175
175
|
if (token.type === "inline") continue;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
176
|
+
const startLine1 = map[0] + 1;
|
|
177
|
+
const endLine1 = map[1];
|
|
178
|
+
token.attrSet("data-mrsf-line", String(startLine1));
|
|
179
|
+
token.attrSet("data-mrsf-start-line", String(startLine1));
|
|
180
|
+
token.attrSet("data-mrsf-end-line", String(endLine1));
|
|
181
|
+
if (interactive) {
|
|
182
182
|
const existingClass = token.attrGet("class") || "";
|
|
183
183
|
token.attrSet(
|
|
184
184
|
"class",
|
|
185
|
-
existingClass ? `${existingClass} mrsf-line-
|
|
185
|
+
existingClass ? `${existingClass} mrsf-line-hover-target` : "mrsf-line-hover-target"
|
|
186
186
|
);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
187
|
+
}
|
|
188
|
+
for (let line0 = map[0]; line0 < map[1]; line0++) {
|
|
189
|
+
const line = line0 + 1;
|
|
190
|
+
if (processed.has(line)) continue;
|
|
191
|
+
const threads = lineMap.get(line) || [];
|
|
192
|
+
const hasThreads = threads.length > 0;
|
|
193
|
+
processed.add(line);
|
|
194
|
+
if (hasThreads) {
|
|
195
|
+
const existingClass = token.attrGet("class") || "";
|
|
196
|
+
token.attrSet(
|
|
197
|
+
"class",
|
|
198
|
+
existingClass ? `${existingClass} mrsf-line-highlight` : "mrsf-line-highlight"
|
|
199
|
+
);
|
|
200
|
+
token.attrSet("data-mrsf-line", String(line));
|
|
201
|
+
const allHaveInline = inlineHighlights && threads.every((t) => !!t.comment.selected_text);
|
|
202
|
+
const showBadge = gutterForInline || !allHaveInline || !inlineHighlights;
|
|
203
|
+
if (showBadge) {
|
|
204
|
+
const badgeToken = new TokenCtor("mrsf_badge", "", 0);
|
|
205
|
+
badgeToken.meta = { line, threads, interactive, gutterPosition };
|
|
206
|
+
tokens.splice(i + 1, 0, badgeToken);
|
|
207
|
+
}
|
|
208
|
+
if (inlineHighlights) {
|
|
209
|
+
const offset = showBadge ? 2 : 1;
|
|
210
|
+
const inlineAfter = tokens[i + offset];
|
|
211
|
+
if (inlineAfter && inlineAfter.type === "inline") {
|
|
212
|
+
for (const thread of threads) {
|
|
213
|
+
if (thread.comment.selected_text) {
|
|
214
|
+
highlightInlineText(inlineAfter, thread, interactive, TokenCtor);
|
|
215
|
+
}
|
|
202
216
|
}
|
|
203
217
|
}
|
|
204
218
|
}
|
|
219
|
+
} else if (interactive) {
|
|
220
|
+
const addToken = new TokenCtor("mrsf_gutter_add", "", 0);
|
|
221
|
+
addToken.meta = { line, gutterPosition };
|
|
222
|
+
tokens.splice(i + 1, 0, addToken);
|
|
205
223
|
}
|
|
206
224
|
}
|
|
207
225
|
}
|
|
@@ -236,22 +254,48 @@ function installRendererRules(md) {
|
|
|
236
254
|
const icon = allResolved ? "\u2713" : "\u{1F4AC}";
|
|
237
255
|
let html = `<span class="mrsf-tooltip-anchor${positionClass}">`;
|
|
238
256
|
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>`;
|
|
239
|
-
|
|
257
|
+
if (interactive) {
|
|
258
|
+
html += `<button class="mrsf-gutter-add" data-mrsf-action="add" data-mrsf-line="${line}" data-mrsf-start-line="${line}" data-mrsf-end-line="${line}" aria-label="Add comment">+</button>`;
|
|
259
|
+
}
|
|
260
|
+
const tooltipClass = interactive ? "mrsf-tooltip mrsf-interactive" : "mrsf-tooltip";
|
|
261
|
+
html += `<span class="${tooltipClass}" data-mrsf-line="${line}">`;
|
|
240
262
|
for (const thread of threads) {
|
|
241
263
|
html += renderThreadHtml(thread, interactive);
|
|
242
264
|
}
|
|
265
|
+
if (interactive) {
|
|
266
|
+
html += `<span class="mrsf-tooltip-actions"><button class="mrsf-action-btn" data-mrsf-action="add" data-mrsf-line="${line}">Add comment</button></span>`;
|
|
267
|
+
}
|
|
243
268
|
html += `</span>`;
|
|
244
269
|
html += `</span>`;
|
|
245
270
|
return html;
|
|
246
271
|
};
|
|
272
|
+
md.renderer.rules["mrsf_gutter_add"] = (tokens, idx) => {
|
|
273
|
+
const { line, gutterPosition } = tokens[idx].meta;
|
|
274
|
+
const positionClass = ` mrsf-gutter-${gutterPosition}`;
|
|
275
|
+
let html = `<span class="mrsf-tooltip-anchor mrsf-gutter-add-only${positionClass}">`;
|
|
276
|
+
html += `<button class="mrsf-gutter-add" data-mrsf-action="add" data-mrsf-line="${line}" data-mrsf-start-line="${line}" data-mrsf-end-line="${line}" aria-label="Add comment">+</button>`;
|
|
277
|
+
html += `</span>`;
|
|
278
|
+
return html;
|
|
279
|
+
};
|
|
247
280
|
md.renderer.rules["mrsf_highlight_open"] = (tokens, idx) => {
|
|
248
281
|
const token = tokens[idx];
|
|
249
282
|
const cls = token.attrGet("class") || "mrsf-highlight";
|
|
250
283
|
const commentId = token.attrGet("data-mrsf-comment-id") || "";
|
|
251
284
|
const thread = token.meta?.thread;
|
|
285
|
+
const line = thread?.comment.line ?? null;
|
|
286
|
+
const endLine = thread?.comment.end_line ?? line ?? null;
|
|
287
|
+
const startCol = thread?.comment.start_column ?? null;
|
|
288
|
+
const endCol = thread?.comment.end_column ?? null;
|
|
252
289
|
let html = "";
|
|
253
290
|
if (thread) {
|
|
254
|
-
|
|
291
|
+
const dataAttrs = [
|
|
292
|
+
line != null ? `data-mrsf-line="${line}"` : "",
|
|
293
|
+
line != null ? `data-mrsf-start-line="${line}"` : "",
|
|
294
|
+
endLine != null ? `data-mrsf-end-line="${endLine}"` : "",
|
|
295
|
+
startCol != null ? `data-mrsf-start-column="${startCol}"` : "",
|
|
296
|
+
endCol != null ? `data-mrsf-end-column="${endCol}"` : ""
|
|
297
|
+
].filter(Boolean).join(" ");
|
|
298
|
+
html += `<span class="mrsf-tooltip-anchor mrsf-inline-anchor" ${dataAttrs}>`;
|
|
255
299
|
}
|
|
256
300
|
html += `<mark class="${escapeHtml(cls)}" data-mrsf-comment-id="${escapeHtml(commentId)}" tabindex="0">`;
|
|
257
301
|
return html;
|
|
@@ -263,7 +307,8 @@ function installRendererRules(md) {
|
|
|
263
307
|
if (meta?.thread) {
|
|
264
308
|
const thread = meta.thread;
|
|
265
309
|
const interactive = meta.interactive;
|
|
266
|
-
|
|
310
|
+
const tooltipClass = interactive ? "mrsf-tooltip mrsf-inline-tooltip mrsf-interactive" : "mrsf-tooltip mrsf-inline-tooltip";
|
|
311
|
+
html += `<span class="${tooltipClass}">`;
|
|
267
312
|
html += renderThreadHtml(thread, interactive);
|
|
268
313
|
html += `</span>`;
|
|
269
314
|
html += `</span>`;
|
package/dist/browser.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../shared/src/comments.ts", "../../shared/src/html.ts", "../src/rules/core.ts", "../src/rules/renderer.ts", "../src/shared.ts", "../src/browser.ts"],
|
|
4
|
-
"sourcesContent": ["/**\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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\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", "/**\n * Browser-safe entry point for @mrsf/markdown-it-mrsf.\n *\n * This entry is automatically selected by bundlers (Vite, webpack, esbuild)\n * via the `\"browser\"` export condition in package.json. It supports the\n * `comments` and `loader` provisioning modes \u2014 no Node.js file-system APIs.\n *\n * You do NOT need to import this directly. Just use:\n * import { mrsfPlugin } from \"@mrsf/markdown-it-mrsf\";\n *\n * Your bundler will resolve to this file automatically when targeting\n * a browser environment.\n */\n\nimport type { MrsfPluginOptions } from \"./types.js\";\nimport { createMrsfPlugin } from \"./shared.js\";\n\nexport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\n/**\n * The markdown-it plugin function (browser-safe).\n *\n * Supports `comments` (inline data) and `loader` (custom function) options.\n */\nexport const mrsfPlugin = createMrsfPlugin((options: MrsfPluginOptions) => {\n if (options.comments) {\n return options.comments;\n }\n\n if (options.loader) {\n try {\n return options.loader();\n } catch {\n return null;\n }\n }\n\n if (options.sidecarPath || options.documentPath) {\n console.warn(\n \"[@mrsf/markdown-it-mrsf] sidecarPath and documentPath require Node.js. \" +\n \"Use `comments` or `loader` options in browser environments.\"\n );\n }\n\n return null;\n});\n\nexport default mrsfPlugin;\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["/**\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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\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 += `<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 * 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 // Always expose source line metadata for selection-based adds\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 // Mark line elements as hover targets (for gutter add visibility)\n if (interactive) {\n const existingClass = token.attrGet(\"class\") || \"\";\n token.attrSet(\n \"class\",\n existingClass\n ? `${existingClass} mrsf-line-hover-target`\n : \"mrsf-line-hover-target\",\n );\n }\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 const hasThreads = threads.length > 0;\n processed.add(line);\n\n // Add line-highlight class to the block token if we have threads on this line\n if (hasThreads) {\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 } else if (interactive) {\n // No existing threads: inject a gutter add button for this line\n const addToken = new TokenCtor(\"mrsf_gutter_add\", \"\", 0);\n addToken.meta = { line, gutterPosition };\n tokens.splice(i + 1, 0, addToken);\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 if (interactive) {\n html += `<button class=\"mrsf-gutter-add\" data-mrsf-action=\"add\" data-mrsf-line=\"${line}\" data-mrsf-start-line=\"${line}\" data-mrsf-end-line=\"${line}\" aria-label=\"Add comment\">+</button>`;\n }\n\n // Tooltip\n const tooltipClass = interactive ? \"mrsf-tooltip mrsf-interactive\" : \"mrsf-tooltip\";\n html += `<span class=\"${tooltipClass}\" data-mrsf-line=\"${line}\">`;\n for (const thread of threads) {\n html += renderThreadHtml(thread, interactive);\n }\n if (interactive) {\n html += `<span class=\"mrsf-tooltip-actions\"><button class=\"mrsf-action-btn\" data-mrsf-action=\"add\" data-mrsf-line=\"${line}\">Add comment</button></span>`;\n }\n html += `</span>`;\n\n html += `</span>`;\n return html;\n };\n\n // Gutter add button for lines without existing threads\n md.renderer.rules[\"mrsf_gutter_add\"] = (\n tokens: { meta: { line: number; gutterPosition: \"left\" | \"tight\" | \"right\" } }[],\n idx: number,\n ): string => {\n const { line, gutterPosition } = tokens[idx].meta;\n const positionClass = ` mrsf-gutter-${gutterPosition}`;\n let html = `<span class=\"mrsf-tooltip-anchor mrsf-gutter-add-only${positionClass}\">`;\n html += `<button class=\"mrsf-gutter-add\" data-mrsf-action=\"add\" data-mrsf-line=\"${line}\" data-mrsf-start-line=\"${line}\" data-mrsf-end-line=\"${line}\" aria-label=\"Add comment\">+</button>`;\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 const line = thread?.comment.line ?? null;\n const endLine = thread?.comment.end_line ?? line ?? null;\n const startCol = thread?.comment.start_column ?? null;\n const endCol = thread?.comment.end_column ?? null;\n\n let html = \"\";\n if (thread) {\n const dataAttrs = [\n line != null ? `data-mrsf-line=\"${line}\"` : \"\",\n line != null ? `data-mrsf-start-line=\"${line}\"` : \"\",\n endLine != null ? `data-mrsf-end-line=\"${endLine}\"` : \"\",\n startCol != null ? `data-mrsf-start-column=\"${startCol}\"` : \"\",\n endCol != null ? `data-mrsf-end-column=\"${endCol}\"` : \"\",\n ].filter(Boolean).join(\" \");\n html += `<span class=\"mrsf-tooltip-anchor mrsf-inline-anchor\" ${dataAttrs}>`;\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 const tooltipClass = interactive\n ? \"mrsf-tooltip mrsf-inline-tooltip mrsf-interactive\"\n : \"mrsf-tooltip mrsf-inline-tooltip\";\n html += `<span class=\"${tooltipClass}\">`;\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", "/**\n * Browser-safe entry point for @mrsf/markdown-it-mrsf.\n *\n * This entry is automatically selected by bundlers (Vite, webpack, esbuild)\n * via the `\"browser\"` export condition in package.json. It supports the\n * `comments` and `loader` provisioning modes \u2014 no Node.js file-system APIs.\n *\n * You do NOT need to import this directly. Just use:\n * import { mrsfPlugin } from \"@mrsf/markdown-it-mrsf\";\n *\n * Your bundler will resolve to this file automatically when targeting\n * a browser environment.\n */\n\nimport type { MrsfPluginOptions } from \"./types.js\";\nimport { createMrsfPlugin } from \"./shared.js\";\n\nexport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\n/**\n * The markdown-it plugin function (browser-safe).\n *\n * Supports `comments` (inline data) and `loader` (custom function) options.\n */\nexport const mrsfPlugin = createMrsfPlugin((options: MrsfPluginOptions) => {\n if (options.comments) {\n return options.comments;\n }\n\n if (options.loader) {\n try {\n return options.loader();\n } catch {\n return null;\n }\n }\n\n if (options.sidecarPath || options.documentPath) {\n console.warn(\n \"[@mrsf/markdown-it-mrsf] sidecarPath and documentPath require Node.js. \" +\n \"Use `comments` or `loader` options in browser environments.\"\n );\n }\n\n return null;\n});\n\nexport default mrsfPlugin;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["mrsfPlugin"]
|
|
7
7
|
}
|
package/dist/controller.d.ts
CHANGED
|
@@ -7,17 +7,40 @@
|
|
|
7
7
|
* Usage (ESM):
|
|
8
8
|
* import "@mrsf/plugin-shared/controller";
|
|
9
9
|
*
|
|
10
|
-
* Events dispatched:
|
|
11
|
-
* - mrsf:resolve { commentId, line }
|
|
12
|
-
* - mrsf:unresolve { commentId, line }
|
|
13
|
-
* - mrsf:reply { commentId, line }
|
|
14
|
-
* - mrsf:edit { commentId, line }
|
|
15
|
-
* - mrsf:
|
|
10
|
+
* Events dispatched (host listens on document):
|
|
11
|
+
* - mrsf:resolve { commentId, line, ... }
|
|
12
|
+
* - mrsf:unresolve { commentId, line, ... }
|
|
13
|
+
* - mrsf:reply { commentId, line, ... }
|
|
14
|
+
* - mrsf:edit { commentId, line, ... }
|
|
15
|
+
* - mrsf:delete { commentId, line, ... }
|
|
16
|
+
* - mrsf:navigate { commentId, line, ... }
|
|
17
|
+
* - mrsf:add { commentId: null, line, selectionText?, ... }
|
|
18
|
+
* - mrsf:submit { action, commentId, text?, line?, end_line?, start_column?, end_column?, selection_text? }
|
|
19
|
+
*
|
|
20
|
+
* For actions that open built-in UI (add/edit/reply/resolve/unresolve/delete), the
|
|
21
|
+
* mrsf:<action> event is fired with the same payload as mrsf:submit after the user confirms.
|
|
16
22
|
*/
|
|
17
|
-
export type MrsfAction = "resolve" | "unresolve" | "reply" | "edit" | "navigate";
|
|
23
|
+
export type MrsfAction = "resolve" | "unresolve" | "reply" | "edit" | "delete" | "navigate" | "add";
|
|
18
24
|
export interface MrsfActionDetail {
|
|
19
|
-
commentId: string;
|
|
25
|
+
commentId: string | null;
|
|
20
26
|
line: number | null;
|
|
21
27
|
action: MrsfAction;
|
|
28
|
+
selectionText?: string | null;
|
|
29
|
+
start_line?: number | null;
|
|
30
|
+
end_line?: number | null;
|
|
31
|
+
start_column?: number | null;
|
|
32
|
+
end_column?: number | null;
|
|
33
|
+
}
|
|
34
|
+
export interface MrsfSubmitDetail {
|
|
35
|
+
action: "add" | "edit" | "reply" | "resolve" | "unresolve" | "delete";
|
|
36
|
+
commentId: string | null;
|
|
37
|
+
text: string;
|
|
38
|
+
type?: string | null;
|
|
39
|
+
severity?: "low" | "medium" | "high" | null;
|
|
40
|
+
line?: number | null;
|
|
41
|
+
end_line?: number | null;
|
|
42
|
+
start_column?: number | null;
|
|
43
|
+
end_column?: number | null;
|
|
44
|
+
selection_text?: string | null;
|
|
22
45
|
}
|
|
23
46
|
//# sourceMappingURL=controller.d.ts.map
|
package/dist/controller.js
CHANGED
|
@@ -1,18 +1,318 @@
|
|
|
1
1
|
// src/controller.ts
|
|
2
|
+
var floatingAddButton = null;
|
|
3
|
+
var lastSelectionText = null;
|
|
4
|
+
var overlayEl = null;
|
|
5
|
+
var styleInjected = false;
|
|
6
|
+
function injectStyles() {
|
|
7
|
+
if (styleInjected) return;
|
|
8
|
+
const css = `
|
|
9
|
+
.mrsf-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 2000; display: flex; align-items: center; justify-content: center; }
|
|
10
|
+
.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; }
|
|
11
|
+
.mrsf-dialog header { padding: 12px 16px; font-weight: 600; border-bottom: 1px solid var(--mrsf-dialog-border, var(--vp-c-divider, #333)); }
|
|
12
|
+
.mrsf-dialog form { padding: 16px; display: flex; flex-direction: column; gap: 12px; }
|
|
13
|
+
.mrsf-field { display: flex; flex-direction: column; gap: 6px; }
|
|
14
|
+
.mrsf-field label { font-size: 13px; color: var(--mrsf-dialog-muted, var(--vp-c-text-2, #ccc)); }
|
|
15
|
+
.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; }
|
|
16
|
+
.mrsf-field textarea { min-height: 100px; resize: vertical; }
|
|
17
|
+
.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)); }
|
|
18
|
+
.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; }
|
|
19
|
+
.mrsf-btn-primary { background: var(--mrsf-button-primary-bg, #0e639c); border-color: var(--mrsf-button-primary-bg, #0e639c); color: #fff; }
|
|
20
|
+
.mrsf-helper { font-size: 12px; color: var(--mrsf-dialog-muted, var(--vp-c-text-2, #aaa)); }
|
|
21
|
+
`;
|
|
22
|
+
const style = document.createElement("style");
|
|
23
|
+
style.textContent = css;
|
|
24
|
+
document.head.appendChild(style);
|
|
25
|
+
styleInjected = true;
|
|
26
|
+
}
|
|
27
|
+
function ensureFloatingAddButton() {
|
|
28
|
+
if (floatingAddButton) return floatingAddButton;
|
|
29
|
+
const btn = document.createElement("button");
|
|
30
|
+
btn.textContent = "Add comment";
|
|
31
|
+
btn.className = "mrsf-add-inline-button";
|
|
32
|
+
btn.dataset.mrsfAction = "add";
|
|
33
|
+
btn.style.display = "none";
|
|
34
|
+
btn.style.position = "absolute";
|
|
35
|
+
btn.style.zIndex = "1200";
|
|
36
|
+
document.body.appendChild(btn);
|
|
37
|
+
floatingAddButton = btn;
|
|
38
|
+
return btn;
|
|
39
|
+
}
|
|
40
|
+
function hideFloatingAddButton() {
|
|
41
|
+
if (!floatingAddButton) return;
|
|
42
|
+
floatingAddButton.style.display = "none";
|
|
43
|
+
floatingAddButton.dataset.mrsfLine = "";
|
|
44
|
+
floatingAddButton.dataset.mrsfStartLine = "";
|
|
45
|
+
floatingAddButton.dataset.mrsfEndLine = "";
|
|
46
|
+
lastSelectionText = null;
|
|
47
|
+
}
|
|
48
|
+
function showFloatingAddButton(startLine, endLine, startColumn, endColumn, rect, selectionText) {
|
|
49
|
+
const btn = ensureFloatingAddButton();
|
|
50
|
+
if (startLine != null) {
|
|
51
|
+
btn.dataset.mrsfLine = String(startLine);
|
|
52
|
+
btn.dataset.mrsfStartLine = String(startLine);
|
|
53
|
+
btn.dataset.mrsfEndLine = String(endLine ?? startLine);
|
|
54
|
+
} else {
|
|
55
|
+
delete btn.dataset.mrsfLine;
|
|
56
|
+
delete btn.dataset.mrsfStartLine;
|
|
57
|
+
delete btn.dataset.mrsfEndLine;
|
|
58
|
+
}
|
|
59
|
+
if (startColumn != null) {
|
|
60
|
+
btn.dataset.mrsfStartColumn = String(startColumn);
|
|
61
|
+
} else {
|
|
62
|
+
delete btn.dataset.mrsfStartColumn;
|
|
63
|
+
}
|
|
64
|
+
if (endColumn != null) {
|
|
65
|
+
btn.dataset.mrsfEndColumn = String(endColumn);
|
|
66
|
+
} else {
|
|
67
|
+
delete btn.dataset.mrsfEndColumn;
|
|
68
|
+
}
|
|
69
|
+
lastSelectionText = selectionText;
|
|
70
|
+
const margin = 6;
|
|
71
|
+
btn.style.visibility = "hidden";
|
|
72
|
+
btn.style.display = "block";
|
|
73
|
+
const height = btn.offsetHeight || 0;
|
|
74
|
+
const top = window.scrollY + rect.top - height - margin;
|
|
75
|
+
const left = window.scrollX + rect.left;
|
|
76
|
+
btn.style.top = `${Math.max(0, top)}px`;
|
|
77
|
+
btn.style.left = `${Math.max(0, left)}px`;
|
|
78
|
+
btn.style.visibility = "visible";
|
|
79
|
+
}
|
|
80
|
+
function handleSelectionChange() {
|
|
81
|
+
const sel = document.getSelection();
|
|
82
|
+
if (!sel || sel.isCollapsed || sel.rangeCount === 0) {
|
|
83
|
+
hideFloatingAddButton();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const text = sel.toString().trim();
|
|
87
|
+
if (!text) {
|
|
88
|
+
hideFloatingAddButton();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const range = sel.getRangeAt(0);
|
|
92
|
+
const rect = range.getBoundingClientRect();
|
|
93
|
+
if (!rect || rect.width === 0 && rect.height === 0) {
|
|
94
|
+
hideFloatingAddButton();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const startAnchor = range.startContainer.parentElement?.closest(
|
|
98
|
+
"[data-mrsf-line]"
|
|
99
|
+
);
|
|
100
|
+
const endAnchor = range.endContainer.parentElement?.closest(
|
|
101
|
+
"[data-mrsf-line]"
|
|
102
|
+
);
|
|
103
|
+
const startLineStr = startAnchor?.dataset.mrsfLine;
|
|
104
|
+
const endLineStr = endAnchor?.dataset.mrsfLine ?? startLineStr;
|
|
105
|
+
const startLine = startLineStr ? parseInt(startLineStr, 10) : null;
|
|
106
|
+
const endLine = endLineStr ? parseInt(endLineStr, 10) : startLine;
|
|
107
|
+
const startColumn = startAnchor?.dataset.mrsfStartColumn ? parseInt(startAnchor.dataset.mrsfStartColumn, 10) : range.startContainer.nodeType === Node.TEXT_NODE ? range.startOffset : null;
|
|
108
|
+
const endColumn = endAnchor?.dataset.mrsfEndColumn ? parseInt(endAnchor.dataset.mrsfEndColumn, 10) : range.endContainer.nodeType === Node.TEXT_NODE ? range.endOffset : null;
|
|
109
|
+
showFloatingAddButton(startLine, endLine, startColumn, endColumn, rect, text);
|
|
110
|
+
}
|
|
111
|
+
function closeOverlay() {
|
|
112
|
+
if (overlayEl && overlayEl.parentElement) {
|
|
113
|
+
overlayEl.parentElement.removeChild(overlayEl);
|
|
114
|
+
}
|
|
115
|
+
overlayEl = null;
|
|
116
|
+
}
|
|
117
|
+
function openForm(action, detail) {
|
|
118
|
+
if (window.mrsfDisableBuiltinUi) return;
|
|
119
|
+
injectStyles();
|
|
120
|
+
closeOverlay();
|
|
121
|
+
const selText = detail.selectionText ?? "";
|
|
122
|
+
const line = detail.line ?? detail.start_line ?? null;
|
|
123
|
+
const endLine = detail.end_line ?? detail.line ?? null;
|
|
124
|
+
const startCol = detail.start_column ?? null;
|
|
125
|
+
const endCol = detail.end_column ?? null;
|
|
126
|
+
const overlay = document.createElement("div");
|
|
127
|
+
overlay.className = "mrsf-overlay";
|
|
128
|
+
const dialog = document.createElement("div");
|
|
129
|
+
dialog.className = "mrsf-dialog";
|
|
130
|
+
const header = document.createElement("header");
|
|
131
|
+
header.textContent = action === "add" ? "Add comment" : action === "edit" ? "Edit comment" : "Reply";
|
|
132
|
+
dialog.appendChild(header);
|
|
133
|
+
const form = document.createElement("form");
|
|
134
|
+
const field = (labelText, inputEl, helper) => {
|
|
135
|
+
const wrap = document.createElement("div");
|
|
136
|
+
wrap.className = "mrsf-field";
|
|
137
|
+
const label = document.createElement("label");
|
|
138
|
+
label.textContent = labelText;
|
|
139
|
+
wrap.appendChild(label);
|
|
140
|
+
wrap.appendChild(inputEl);
|
|
141
|
+
if (helper) {
|
|
142
|
+
const h = document.createElement("div");
|
|
143
|
+
h.className = "mrsf-helper";
|
|
144
|
+
h.textContent = helper;
|
|
145
|
+
wrap.appendChild(h);
|
|
146
|
+
}
|
|
147
|
+
form.appendChild(wrap);
|
|
148
|
+
};
|
|
149
|
+
const textArea = document.createElement("textarea");
|
|
150
|
+
textArea.name = "text";
|
|
151
|
+
textArea.required = true;
|
|
152
|
+
field("Comment text", textArea);
|
|
153
|
+
const typeSelect = document.createElement("select");
|
|
154
|
+
typeSelect.name = "type";
|
|
155
|
+
["", "suggestion", "issue", "question", "accuracy", "style", "clarity"].forEach((t) => {
|
|
156
|
+
const opt = document.createElement("option");
|
|
157
|
+
opt.value = t;
|
|
158
|
+
opt.textContent = t || "(none)";
|
|
159
|
+
typeSelect.appendChild(opt);
|
|
160
|
+
});
|
|
161
|
+
field("Type", typeSelect, "Optional");
|
|
162
|
+
const severitySelect = document.createElement("select");
|
|
163
|
+
severitySelect.name = "severity";
|
|
164
|
+
["", "low", "medium", "high"].forEach((s) => {
|
|
165
|
+
const opt = document.createElement("option");
|
|
166
|
+
opt.value = s;
|
|
167
|
+
opt.textContent = s || "(none)";
|
|
168
|
+
severitySelect.appendChild(opt);
|
|
169
|
+
});
|
|
170
|
+
field("Severity", severitySelect, "Optional");
|
|
171
|
+
if (selText) {
|
|
172
|
+
const pre = document.createElement("pre");
|
|
173
|
+
pre.textContent = selText;
|
|
174
|
+
pre.style.margin = "0";
|
|
175
|
+
pre.style.whiteSpace = "pre-wrap";
|
|
176
|
+
field("Selected text", pre, "Captured automatically");
|
|
177
|
+
}
|
|
178
|
+
const actions = document.createElement("div");
|
|
179
|
+
actions.className = "mrsf-actions-row";
|
|
180
|
+
const cancelBtn = document.createElement("button");
|
|
181
|
+
cancelBtn.type = "button";
|
|
182
|
+
cancelBtn.className = "mrsf-btn";
|
|
183
|
+
cancelBtn.textContent = "Cancel";
|
|
184
|
+
cancelBtn.addEventListener("click", () => closeOverlay());
|
|
185
|
+
actions.appendChild(cancelBtn);
|
|
186
|
+
const submitBtn = document.createElement("button");
|
|
187
|
+
submitBtn.type = "submit";
|
|
188
|
+
submitBtn.className = "mrsf-btn mrsf-btn-primary";
|
|
189
|
+
submitBtn.textContent = action === "add" ? "Add" : action === "reply" ? "Reply" : "Save";
|
|
190
|
+
actions.appendChild(submitBtn);
|
|
191
|
+
form.appendChild(actions);
|
|
192
|
+
form.addEventListener("submit", (ev) => {
|
|
193
|
+
ev.preventDefault();
|
|
194
|
+
const detailOut = {
|
|
195
|
+
action,
|
|
196
|
+
commentId: detail.commentId,
|
|
197
|
+
text: textArea.value.trim(),
|
|
198
|
+
type: typeSelect.value || null,
|
|
199
|
+
severity: severitySelect.value || null,
|
|
200
|
+
line,
|
|
201
|
+
end_line: endLine,
|
|
202
|
+
start_column: startCol,
|
|
203
|
+
end_column: endCol,
|
|
204
|
+
selection_text: selText || null
|
|
205
|
+
};
|
|
206
|
+
document.dispatchEvent(new CustomEvent("mrsf:submit", { detail: detailOut, bubbles: true }));
|
|
207
|
+
closeOverlay();
|
|
208
|
+
});
|
|
209
|
+
dialog.appendChild(form);
|
|
210
|
+
overlay.appendChild(dialog);
|
|
211
|
+
overlay.addEventListener("click", (e) => {
|
|
212
|
+
if (e.target === overlay) closeOverlay();
|
|
213
|
+
});
|
|
214
|
+
document.body.appendChild(overlay);
|
|
215
|
+
overlayEl = overlay;
|
|
216
|
+
}
|
|
217
|
+
function openConfirm(action, detail) {
|
|
218
|
+
if (window.mrsfDisableBuiltinUi) return;
|
|
219
|
+
injectStyles();
|
|
220
|
+
closeOverlay();
|
|
221
|
+
const overlay = document.createElement("div");
|
|
222
|
+
overlay.className = "mrsf-overlay";
|
|
223
|
+
const dialog = document.createElement("div");
|
|
224
|
+
dialog.className = "mrsf-dialog";
|
|
225
|
+
const header = document.createElement("header");
|
|
226
|
+
header.textContent = action === "delete" ? "Delete comment" : "Change status";
|
|
227
|
+
dialog.appendChild(header);
|
|
228
|
+
const body = document.createElement("div");
|
|
229
|
+
body.style.padding = "16px";
|
|
230
|
+
body.textContent = action === "delete" ? "Delete this comment?" : action === "resolve" ? "Mark this comment as resolved?" : "Mark this comment as unresolved?";
|
|
231
|
+
dialog.appendChild(body);
|
|
232
|
+
const actions = document.createElement("div");
|
|
233
|
+
actions.className = "mrsf-actions-row";
|
|
234
|
+
const cancelBtn = document.createElement("button");
|
|
235
|
+
cancelBtn.type = "button";
|
|
236
|
+
cancelBtn.className = "mrsf-btn";
|
|
237
|
+
cancelBtn.textContent = "Cancel";
|
|
238
|
+
cancelBtn.addEventListener("click", () => closeOverlay());
|
|
239
|
+
actions.appendChild(cancelBtn);
|
|
240
|
+
const confirmBtn = document.createElement("button");
|
|
241
|
+
confirmBtn.type = "button";
|
|
242
|
+
confirmBtn.className = "mrsf-btn mrsf-btn-primary";
|
|
243
|
+
confirmBtn.textContent = action === "delete" ? "Delete" : "Confirm";
|
|
244
|
+
confirmBtn.addEventListener("click", () => {
|
|
245
|
+
const detailOut = {
|
|
246
|
+
action,
|
|
247
|
+
commentId: detail.commentId,
|
|
248
|
+
text: "",
|
|
249
|
+
type: null,
|
|
250
|
+
severity: null,
|
|
251
|
+
line: detail.line,
|
|
252
|
+
end_line: detail.end_line ?? detail.line ?? null,
|
|
253
|
+
start_column: detail.start_column ?? null,
|
|
254
|
+
end_column: detail.end_column ?? null,
|
|
255
|
+
selection_text: detail.selectionText ?? null
|
|
256
|
+
};
|
|
257
|
+
document.dispatchEvent(new CustomEvent("mrsf:submit", { detail: detailOut, bubbles: true }));
|
|
258
|
+
closeOverlay();
|
|
259
|
+
});
|
|
260
|
+
actions.appendChild(confirmBtn);
|
|
261
|
+
dialog.appendChild(actions);
|
|
262
|
+
overlay.appendChild(dialog);
|
|
263
|
+
overlay.addEventListener("click", (e) => {
|
|
264
|
+
if (e.target === overlay) closeOverlay();
|
|
265
|
+
});
|
|
266
|
+
document.body.appendChild(overlay);
|
|
267
|
+
overlayEl = overlay;
|
|
268
|
+
}
|
|
2
269
|
function init() {
|
|
270
|
+
const globalAny = window;
|
|
271
|
+
if (globalAny.__mrsfControllerReady) return;
|
|
272
|
+
globalAny.__mrsfControllerReady = true;
|
|
273
|
+
document.addEventListener("selectionchange", handleSelectionChange);
|
|
3
274
|
document.addEventListener("click", (e) => {
|
|
4
275
|
const target = e.target.closest(
|
|
5
276
|
"[data-mrsf-action]"
|
|
6
277
|
);
|
|
7
278
|
if (!target) return;
|
|
8
279
|
const action = target.dataset.mrsfAction;
|
|
9
|
-
const commentId = target.dataset.mrsfCommentId;
|
|
10
|
-
if (!action
|
|
280
|
+
const commentId = target.dataset.mrsfCommentId ?? null;
|
|
281
|
+
if (!action) return;
|
|
11
282
|
const lineStr = target.dataset.mrsfLine;
|
|
12
283
|
const line = lineStr ? parseInt(lineStr, 10) : null;
|
|
284
|
+
const selectionText = target.dataset.mrsfSelection ?? lastSelectionText ?? null;
|
|
285
|
+
const startLineStr = target.dataset.mrsfStartLine;
|
|
286
|
+
const endLineStr = target.dataset.mrsfEndLine;
|
|
287
|
+
const startColStr = target.dataset.mrsfStartColumn;
|
|
288
|
+
const endColStr = target.dataset.mrsfEndColumn;
|
|
289
|
+
const startLine = startLineStr ? parseInt(startLineStr, 10) : line ?? null;
|
|
290
|
+
const endLine = endLineStr ? parseInt(endLineStr, 10) : line ?? null;
|
|
291
|
+
const startColumn = startColStr ? parseInt(startColStr, 10) : null;
|
|
292
|
+
const endColumn = endColStr ? parseInt(endColStr, 10) : null;
|
|
13
293
|
e.preventDefault();
|
|
14
294
|
e.stopPropagation();
|
|
15
|
-
const detail = {
|
|
295
|
+
const detail = {
|
|
296
|
+
commentId,
|
|
297
|
+
line,
|
|
298
|
+
action,
|
|
299
|
+
selectionText,
|
|
300
|
+
start_line: startLine,
|
|
301
|
+
end_line: endLine,
|
|
302
|
+
start_column: startColumn,
|
|
303
|
+
end_column: endColumn
|
|
304
|
+
};
|
|
305
|
+
if (action === "add" || action === "edit" || action === "reply") {
|
|
306
|
+
if (action === "add") {
|
|
307
|
+
hideFloatingAddButton();
|
|
308
|
+
}
|
|
309
|
+
openForm(action, detail);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (action === "resolve" || action === "unresolve" || action === "delete") {
|
|
313
|
+
openConfirm(action, detail);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
16
316
|
document.dispatchEvent(
|
|
17
317
|
new CustomEvent(`mrsf:${action}`, { detail, bubbles: true })
|
|
18
318
|
);
|
package/dist/controller.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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": ";
|
|
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 (host listens 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?, end_line?, start_column?, end_column?, selection_text? }\n *\n * For actions that open built-in UI (add/edit/reply/resolve/unresolve/delete), the\n * mrsf:<action> event is fired with the same payload as mrsf:submit after the user confirms.\n */\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\nlet floatingAddButton: HTMLButtonElement | null = null;\nlet lastSelectionText: string | null = null;\nlet overlayEl: HTMLDivElement | null = null;\nlet styleInjected = false;\n\nfunction injectStyles(): void {\n if (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 styleInjected = true;\n}\n\nfunction ensureFloatingAddButton(): HTMLButtonElement {\n if (floatingAddButton) return 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 floatingAddButton = btn;\n return btn;\n}\n\nfunction hideFloatingAddButton(): void {\n if (!floatingAddButton) return;\n floatingAddButton.style.display = \"none\";\n floatingAddButton.dataset.mrsfLine = \"\";\n floatingAddButton.dataset.mrsfStartLine = \"\";\n floatingAddButton.dataset.mrsfEndLine = \"\";\n lastSelectionText = null;\n}\n\nfunction 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 = 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 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\nfunction handleSelectionChange(): void {\n const sel = document.getSelection();\n if (!sel || sel.isCollapsed || sel.rangeCount === 0) {\n hideFloatingAddButton();\n return;\n }\n\n const text = sel.toString().trim();\n if (!text) {\n 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 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.mrsfLine;\n const endLineStr = endAnchor?.dataset.mrsfLine ?? startLineStr;\n const startLine = startLineStr ? parseInt(startLineStr, 10) : null;\n const endLine = endLineStr ? parseInt(endLineStr, 10) : startLine;\n\n const startColumn = startAnchor?.dataset.mrsfStartColumn\n ? parseInt(startAnchor.dataset.mrsfStartColumn, 10)\n : range.startContainer.nodeType === Node.TEXT_NODE\n ? range.startOffset\n : null;\n const endColumn = endAnchor?.dataset.mrsfEndColumn\n ? parseInt(endAnchor.dataset.mrsfEndColumn, 10)\n : range.endContainer.nodeType === Node.TEXT_NODE\n ? range.endOffset\n : null;\n\n showFloatingAddButton(startLine, endLine, startColumn, endColumn, rect, text);\n}\n\nfunction closeOverlay(): void {\n if (overlayEl && overlayEl.parentElement) {\n overlayEl.parentElement.removeChild(overlayEl);\n }\n overlayEl = null;\n}\n\nfunction openForm(action: \"add\" | \"edit\" | \"reply\", detail: MrsfActionDetail): void {\n if ((window as any).mrsfDisableBuiltinUi) return;\n injectStyles();\n 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\", () => 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 closeOverlay();\n });\n\n dialog.appendChild(form);\n overlay.appendChild(dialog);\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) closeOverlay();\n });\n\n document.body.appendChild(overlay);\n overlayEl = overlay;\n}\n\nfunction openConfirm(action: \"resolve\" | \"unresolve\" | \"delete\", detail: MrsfActionDetail): void {\n if ((window as any).mrsfDisableBuiltinUi) return;\n injectStyles();\n 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\", () => 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 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) closeOverlay();\n });\n\n document.body.appendChild(overlay);\n overlayEl = overlay;\n}\n\nfunction init(): void {\n const globalAny = window as any;\n if (globalAny.__mrsfControllerReady) return;\n globalAny.__mrsfControllerReady = true;\n\n document.addEventListener(\"selectionchange\", handleSelectionChange);\n\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 ?? null;\n if (!action) return;\n\n const lineStr = target.dataset.mrsfLine;\n const line = lineStr ? parseInt(lineStr, 10) : null;\n\n const selectionText = target.dataset.mrsfSelection ?? 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 if (action === \"add\" || action === \"edit\" || action === \"reply\") {\n if (action === \"add\") {\n hideFloatingAddButton();\n }\n openForm(action, detail);\n return;\n }\n\n if (action === \"resolve\" || action === \"unresolve\" || action === \"delete\") {\n openConfirm(action, detail);\n return;\n }\n\n document.dispatchEvent(\n new CustomEvent(`mrsf:${action}`, { detail, bubbles: true }),\n );\n });\n}\n\n// Auto-initialize\ninit();\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/index.js
CHANGED
|
@@ -178,35 +178,53 @@ function installCoreRule(md, lineMap, options) {
|
|
|
178
178
|
const map = token.map;
|
|
179
179
|
if (!map) continue;
|
|
180
180
|
if (token.type === "inline") continue;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
const startLine1 = map[0] + 1;
|
|
182
|
+
const endLine1 = map[1];
|
|
183
|
+
token.attrSet("data-mrsf-line", String(startLine1));
|
|
184
|
+
token.attrSet("data-mrsf-start-line", String(startLine1));
|
|
185
|
+
token.attrSet("data-mrsf-end-line", String(endLine1));
|
|
186
|
+
if (interactive) {
|
|
187
187
|
const existingClass = token.attrGet("class") || "";
|
|
188
188
|
token.attrSet(
|
|
189
189
|
"class",
|
|
190
|
-
existingClass ? `${existingClass} mrsf-line-
|
|
190
|
+
existingClass ? `${existingClass} mrsf-line-hover-target` : "mrsf-line-hover-target"
|
|
191
191
|
);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
192
|
+
}
|
|
193
|
+
for (let line0 = map[0]; line0 < map[1]; line0++) {
|
|
194
|
+
const line = line0 + 1;
|
|
195
|
+
if (processed.has(line)) continue;
|
|
196
|
+
const threads = lineMap.get(line) || [];
|
|
197
|
+
const hasThreads = threads.length > 0;
|
|
198
|
+
processed.add(line);
|
|
199
|
+
if (hasThreads) {
|
|
200
|
+
const existingClass = token.attrGet("class") || "";
|
|
201
|
+
token.attrSet(
|
|
202
|
+
"class",
|
|
203
|
+
existingClass ? `${existingClass} mrsf-line-highlight` : "mrsf-line-highlight"
|
|
204
|
+
);
|
|
205
|
+
token.attrSet("data-mrsf-line", String(line));
|
|
206
|
+
const allHaveInline = inlineHighlights && threads.every((t) => !!t.comment.selected_text);
|
|
207
|
+
const showBadge = gutterForInline || !allHaveInline || !inlineHighlights;
|
|
208
|
+
if (showBadge) {
|
|
209
|
+
const badgeToken = new TokenCtor("mrsf_badge", "", 0);
|
|
210
|
+
badgeToken.meta = { line, threads, interactive, gutterPosition };
|
|
211
|
+
tokens.splice(i + 1, 0, badgeToken);
|
|
212
|
+
}
|
|
213
|
+
if (inlineHighlights) {
|
|
214
|
+
const offset = showBadge ? 2 : 1;
|
|
215
|
+
const inlineAfter = tokens[i + offset];
|
|
216
|
+
if (inlineAfter && inlineAfter.type === "inline") {
|
|
217
|
+
for (const thread of threads) {
|
|
218
|
+
if (thread.comment.selected_text) {
|
|
219
|
+
highlightInlineText(inlineAfter, thread, interactive, TokenCtor);
|
|
220
|
+
}
|
|
207
221
|
}
|
|
208
222
|
}
|
|
209
223
|
}
|
|
224
|
+
} else if (interactive) {
|
|
225
|
+
const addToken = new TokenCtor("mrsf_gutter_add", "", 0);
|
|
226
|
+
addToken.meta = { line, gutterPosition };
|
|
227
|
+
tokens.splice(i + 1, 0, addToken);
|
|
210
228
|
}
|
|
211
229
|
}
|
|
212
230
|
}
|
|
@@ -241,22 +259,48 @@ function installRendererRules(md) {
|
|
|
241
259
|
const icon = allResolved ? "\u2713" : "\u{1F4AC}";
|
|
242
260
|
let html = `<span class="mrsf-tooltip-anchor${positionClass}">`;
|
|
243
261
|
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
|
-
|
|
262
|
+
if (interactive) {
|
|
263
|
+
html += `<button class="mrsf-gutter-add" data-mrsf-action="add" data-mrsf-line="${line}" data-mrsf-start-line="${line}" data-mrsf-end-line="${line}" aria-label="Add comment">+</button>`;
|
|
264
|
+
}
|
|
265
|
+
const tooltipClass = interactive ? "mrsf-tooltip mrsf-interactive" : "mrsf-tooltip";
|
|
266
|
+
html += `<span class="${tooltipClass}" data-mrsf-line="${line}">`;
|
|
245
267
|
for (const thread of threads) {
|
|
246
268
|
html += renderThreadHtml(thread, interactive);
|
|
247
269
|
}
|
|
270
|
+
if (interactive) {
|
|
271
|
+
html += `<span class="mrsf-tooltip-actions"><button class="mrsf-action-btn" data-mrsf-action="add" data-mrsf-line="${line}">Add comment</button></span>`;
|
|
272
|
+
}
|
|
248
273
|
html += `</span>`;
|
|
249
274
|
html += `</span>`;
|
|
250
275
|
return html;
|
|
251
276
|
};
|
|
277
|
+
md.renderer.rules["mrsf_gutter_add"] = (tokens, idx) => {
|
|
278
|
+
const { line, gutterPosition } = tokens[idx].meta;
|
|
279
|
+
const positionClass = ` mrsf-gutter-${gutterPosition}`;
|
|
280
|
+
let html = `<span class="mrsf-tooltip-anchor mrsf-gutter-add-only${positionClass}">`;
|
|
281
|
+
html += `<button class="mrsf-gutter-add" data-mrsf-action="add" data-mrsf-line="${line}" data-mrsf-start-line="${line}" data-mrsf-end-line="${line}" aria-label="Add comment">+</button>`;
|
|
282
|
+
html += `</span>`;
|
|
283
|
+
return html;
|
|
284
|
+
};
|
|
252
285
|
md.renderer.rules["mrsf_highlight_open"] = (tokens, idx) => {
|
|
253
286
|
const token = tokens[idx];
|
|
254
287
|
const cls = token.attrGet("class") || "mrsf-highlight";
|
|
255
288
|
const commentId = token.attrGet("data-mrsf-comment-id") || "";
|
|
256
289
|
const thread = token.meta?.thread;
|
|
290
|
+
const line = thread?.comment.line ?? null;
|
|
291
|
+
const endLine = thread?.comment.end_line ?? line ?? null;
|
|
292
|
+
const startCol = thread?.comment.start_column ?? null;
|
|
293
|
+
const endCol = thread?.comment.end_column ?? null;
|
|
257
294
|
let html = "";
|
|
258
295
|
if (thread) {
|
|
259
|
-
|
|
296
|
+
const dataAttrs = [
|
|
297
|
+
line != null ? `data-mrsf-line="${line}"` : "",
|
|
298
|
+
line != null ? `data-mrsf-start-line="${line}"` : "",
|
|
299
|
+
endLine != null ? `data-mrsf-end-line="${endLine}"` : "",
|
|
300
|
+
startCol != null ? `data-mrsf-start-column="${startCol}"` : "",
|
|
301
|
+
endCol != null ? `data-mrsf-end-column="${endCol}"` : ""
|
|
302
|
+
].filter(Boolean).join(" ");
|
|
303
|
+
html += `<span class="mrsf-tooltip-anchor mrsf-inline-anchor" ${dataAttrs}>`;
|
|
260
304
|
}
|
|
261
305
|
html += `<mark class="${escapeHtml(cls)}" data-mrsf-comment-id="${escapeHtml(commentId)}" tabindex="0">`;
|
|
262
306
|
return html;
|
|
@@ -268,7 +312,8 @@ function installRendererRules(md) {
|
|
|
268
312
|
if (meta?.thread) {
|
|
269
313
|
const thread = meta.thread;
|
|
270
314
|
const interactive = meta.interactive;
|
|
271
|
-
|
|
315
|
+
const tooltipClass = interactive ? "mrsf-tooltip mrsf-inline-tooltip mrsf-interactive" : "mrsf-tooltip mrsf-inline-tooltip";
|
|
316
|
+
html += `<span class="${tooltipClass}">`;
|
|
272
317
|
html += renderThreadHtml(thread, interactive);
|
|
273
318
|
html += `</span>`;
|
|
274
319
|
html += `</span>`;
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\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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
|
|
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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\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 += `<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 * 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 // Always expose source line metadata for selection-based adds\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 // Mark line elements as hover targets (for gutter add visibility)\n if (interactive) {\n const existingClass = token.attrGet(\"class\") || \"\";\n token.attrSet(\n \"class\",\n existingClass\n ? `${existingClass} mrsf-line-hover-target`\n : \"mrsf-line-hover-target\",\n );\n }\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 const hasThreads = threads.length > 0;\n processed.add(line);\n\n // Add line-highlight class to the block token if we have threads on this line\n if (hasThreads) {\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 } else if (interactive) {\n // No existing threads: inject a gutter add button for this line\n const addToken = new TokenCtor(\"mrsf_gutter_add\", \"\", 0);\n addToken.meta = { line, gutterPosition };\n tokens.splice(i + 1, 0, addToken);\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 if (interactive) {\n html += `<button class=\"mrsf-gutter-add\" data-mrsf-action=\"add\" data-mrsf-line=\"${line}\" data-mrsf-start-line=\"${line}\" data-mrsf-end-line=\"${line}\" aria-label=\"Add comment\">+</button>`;\n }\n\n // Tooltip\n const tooltipClass = interactive ? \"mrsf-tooltip mrsf-interactive\" : \"mrsf-tooltip\";\n html += `<span class=\"${tooltipClass}\" data-mrsf-line=\"${line}\">`;\n for (const thread of threads) {\n html += renderThreadHtml(thread, interactive);\n }\n if (interactive) {\n html += `<span class=\"mrsf-tooltip-actions\"><button class=\"mrsf-action-btn\" data-mrsf-action=\"add\" data-mrsf-line=\"${line}\">Add comment</button></span>`;\n }\n html += `</span>`;\n\n html += `</span>`;\n return html;\n };\n\n // Gutter add button for lines without existing threads\n md.renderer.rules[\"mrsf_gutter_add\"] = (\n tokens: { meta: { line: number; gutterPosition: \"left\" | \"tight\" | \"right\" } }[],\n idx: number,\n ): string => {\n const { line, gutterPosition } = tokens[idx].meta;\n const positionClass = ` mrsf-gutter-${gutterPosition}`;\n let html = `<span class=\"mrsf-tooltip-anchor mrsf-gutter-add-only${positionClass}\">`;\n html += `<button class=\"mrsf-gutter-add\" data-mrsf-action=\"add\" data-mrsf-line=\"${line}\" data-mrsf-start-line=\"${line}\" data-mrsf-end-line=\"${line}\" aria-label=\"Add comment\">+</button>`;\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 const line = thread?.comment.line ?? null;\n const endLine = thread?.comment.end_line ?? line ?? null;\n const startCol = thread?.comment.start_column ?? null;\n const endCol = thread?.comment.end_column ?? null;\n\n let html = \"\";\n if (thread) {\n const dataAttrs = [\n line != null ? `data-mrsf-line=\"${line}\"` : \"\",\n line != null ? `data-mrsf-start-line=\"${line}\"` : \"\",\n endLine != null ? `data-mrsf-end-line=\"${endLine}\"` : \"\",\n startCol != null ? `data-mrsf-start-column=\"${startCol}\"` : \"\",\n endCol != null ? `data-mrsf-end-column=\"${endCol}\"` : \"\",\n ].filter(Boolean).join(\" \");\n html += `<span class=\"mrsf-tooltip-anchor mrsf-inline-anchor\" ${dataAttrs}>`;\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 const tooltipClass = interactive\n ? \"mrsf-tooltip mrsf-inline-tooltip mrsf-interactive\"\n : \"mrsf-tooltip mrsf-inline-tooltip\";\n html += `<span class=\"${tooltipClass}\">`;\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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",
|
|
6
6
|
"names": ["mrsfPlugin"]
|
|
7
7
|
}
|
package/dist/style.css
CHANGED
|
@@ -108,6 +108,48 @@
|
|
|
108
108
|
vertical-align: baseline;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
.mrsf-gutter-add {
|
|
112
|
+
position: absolute;
|
|
113
|
+
right: -18px;
|
|
114
|
+
top: 50%;
|
|
115
|
+
transform: translateY(-50%);
|
|
116
|
+
opacity: 0;
|
|
117
|
+
pointer-events: none;
|
|
118
|
+
border: 1px solid var(--mrsf-tooltip-border);
|
|
119
|
+
background: var(--mrsf-tooltip-bg);
|
|
120
|
+
color: var(--mrsf-tooltip-fg);
|
|
121
|
+
border-radius: 50%;
|
|
122
|
+
width: 20px;
|
|
123
|
+
height: 20px;
|
|
124
|
+
font-size: 12px;
|
|
125
|
+
line-height: 18px;
|
|
126
|
+
text-align: center;
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
transition: opacity 0.12s ease, transform 0.12s ease;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.mrsf-tooltip-anchor:hover > .mrsf-gutter-add,
|
|
132
|
+
.mrsf-tooltip-anchor:focus-within > .mrsf-gutter-add {
|
|
133
|
+
opacity: 1;
|
|
134
|
+
pointer-events: auto;
|
|
135
|
+
transform: translateY(-50%) scale(1.02);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Add-only anchors need hoverable area even without a badge */
|
|
139
|
+
.mrsf-tooltip-anchor.mrsf-gutter-add-only {
|
|
140
|
+
display: inline-block;
|
|
141
|
+
width: 24px;
|
|
142
|
+
height: 24px;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Reveal gutter add when hovering the whole line */
|
|
146
|
+
.mrsf-line-hover-target:hover .mrsf-gutter-add,
|
|
147
|
+
.mrsf-line-hover-target:focus-within .mrsf-gutter-add {
|
|
148
|
+
opacity: 1;
|
|
149
|
+
pointer-events: auto;
|
|
150
|
+
transform: translateY(-50%) scale(1.02);
|
|
151
|
+
}
|
|
152
|
+
|
|
111
153
|
/* Gutter position: right (default) — float the badge to the right */
|
|
112
154
|
.mrsf-tooltip-anchor.mrsf-gutter-right {
|
|
113
155
|
float: right;
|
|
@@ -131,6 +173,16 @@
|
|
|
131
173
|
left: 4px;
|
|
132
174
|
}
|
|
133
175
|
|
|
176
|
+
.mrsf-tooltip-anchor.mrsf-gutter-left > .mrsf-gutter-add {
|
|
177
|
+
left: -18px;
|
|
178
|
+
right: auto;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.mrsf-tooltip-anchor.mrsf-gutter-right > .mrsf-gutter-add {
|
|
182
|
+
right: -18px;
|
|
183
|
+
left: auto;
|
|
184
|
+
}
|
|
185
|
+
|
|
134
186
|
.mrsf-tooltip-anchor.mrsf-gutter-left .mrsf-badge {
|
|
135
187
|
margin-left: 0;
|
|
136
188
|
}
|
|
@@ -339,6 +391,15 @@
|
|
|
339
391
|
display: flex;
|
|
340
392
|
}
|
|
341
393
|
|
|
394
|
+
.mrsf-tooltip-actions {
|
|
395
|
+
display: flex;
|
|
396
|
+
justify-content: flex-end;
|
|
397
|
+
gap: 6px;
|
|
398
|
+
margin-top: 8px;
|
|
399
|
+
padding-top: 6px;
|
|
400
|
+
border-top: 1px solid var(--mrsf-tooltip-border);
|
|
401
|
+
}
|
|
402
|
+
|
|
342
403
|
.mrsf-action-btn {
|
|
343
404
|
font-size: 11px;
|
|
344
405
|
padding: 2px 8px;
|
|
@@ -351,6 +412,30 @@
|
|
|
351
412
|
transition: background 0.15s ease;
|
|
352
413
|
}
|
|
353
414
|
|
|
415
|
+
.mrsf-action-btn.mrsf-action-danger {
|
|
416
|
+
color: #d13438;
|
|
417
|
+
border-color: rgba(209, 52, 56, 0.7);
|
|
418
|
+
}
|
|
419
|
+
|
|
354
420
|
.mrsf-action-btn:hover {
|
|
355
421
|
background: rgba(127, 127, 127, 0.2);
|
|
356
422
|
}
|
|
423
|
+
|
|
424
|
+
.mrsf-add-inline-button {
|
|
425
|
+
position: absolute;
|
|
426
|
+
z-index: 1200;
|
|
427
|
+
display: none;
|
|
428
|
+
font-size: 12px;
|
|
429
|
+
padding: 6px 10px;
|
|
430
|
+
border-radius: 6px;
|
|
431
|
+
border: 1px solid var(--mrsf-tooltip-border);
|
|
432
|
+
background: var(--mrsf-tooltip-bg);
|
|
433
|
+
color: var(--mrsf-tooltip-fg);
|
|
434
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
|
435
|
+
cursor: pointer;
|
|
436
|
+
font-family: var(--mrsf-font-family);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.mrsf-add-inline-button:hover {
|
|
440
|
+
background: rgba(127, 127, 127, 0.2);
|
|
441
|
+
}
|
package/package.json
CHANGED