@mrsf/markdown-it-mrsf 0.1.0 → 0.2.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/dist/browser.d.ts +1 -1
- package/dist/browser.js +320 -34
- package/dist/browser.js.map +7 -0
- package/dist/controller.d.ts +1 -4
- package/dist/controller.js +19 -37
- package/dist/controller.js.map +7 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +347 -75
- package/dist/index.js.map +7 -0
- package/dist/shared.d.ts +2 -16
- package/dist/types.d.ts +6 -2
- package/package.json +14 -7
- package/dist/rules/core.js +0 -149
- package/dist/rules/renderer.js +0 -159
- package/dist/shared.js +0 -86
- package/dist/types.js +0 -5
package/dist/index.js
CHANGED
|
@@ -1,87 +1,359 @@
|
|
|
1
|
-
|
|
2
|
-
* @mrsf/markdown-it-mrsf — markdown-it plugin for MRSF review comments.
|
|
3
|
-
*
|
|
4
|
-
* This is the full-featured Node.js entry point. It supports all four
|
|
5
|
-
* sidecar provisioning modes: `comments`, `loader`, `sidecarPath`, and
|
|
6
|
-
* `documentPath`. The latter two require Node.js file-system APIs.
|
|
7
|
-
*
|
|
8
|
-
* In browser / bundler environments, the `"browser"` export condition in
|
|
9
|
-
* package.json automatically resolves to the browser-safe entry, so you
|
|
10
|
-
* never need to import a different path — just use:
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```ts
|
|
14
|
-
* import MarkdownIt from "markdown-it";
|
|
15
|
-
* import { mrsfPlugin } from "@mrsf/markdown-it-mrsf";
|
|
16
|
-
*
|
|
17
|
-
* const md = new MarkdownIt();
|
|
18
|
-
* md.use(mrsfPlugin, { documentPath: "docs/guide.md" });
|
|
19
|
-
* const html = md.render(markdownSource);
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
1
|
+
// src/index.ts
|
|
22
2
|
import { readFileSync } from "node:fs";
|
|
23
3
|
import path from "node:path";
|
|
24
4
|
import { parseSidecarContent } from "@mrsf/cli";
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
5
|
+
|
|
6
|
+
// ../shared/dist/comments.js
|
|
7
|
+
function toSlimComments(doc) {
|
|
8
|
+
return doc.comments.map((c) => ({
|
|
9
|
+
id: c.id,
|
|
10
|
+
author: c.author || "Unknown",
|
|
11
|
+
text: c.text || "",
|
|
12
|
+
line: c.line ?? null,
|
|
13
|
+
end_line: c.end_line ?? null,
|
|
14
|
+
start_column: c.start_column ?? null,
|
|
15
|
+
end_column: c.end_column ?? null,
|
|
16
|
+
selected_text: c.selected_text || null,
|
|
17
|
+
resolved: !!c.resolved,
|
|
18
|
+
reply_to: c.reply_to || null,
|
|
19
|
+
severity: c.severity || null,
|
|
20
|
+
type: c.type || null,
|
|
21
|
+
timestamp: c.timestamp || null
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
function groupByLine(comments) {
|
|
25
|
+
const rootComments = comments.filter((c) => !c.reply_to && c.line != null);
|
|
26
|
+
const replies = comments.filter((c) => c.reply_to);
|
|
27
|
+
const replyMap = /* @__PURE__ */ new Map();
|
|
28
|
+
for (const r of replies) {
|
|
29
|
+
const list = replyMap.get(r.reply_to) || [];
|
|
30
|
+
list.push(r);
|
|
31
|
+
replyMap.set(r.reply_to, list);
|
|
32
|
+
}
|
|
33
|
+
const lineMap = /* @__PURE__ */ new Map();
|
|
34
|
+
for (const c of rootComments) {
|
|
35
|
+
const line = c.line;
|
|
36
|
+
const threads = lineMap.get(line) || [];
|
|
37
|
+
threads.push({
|
|
38
|
+
comment: c,
|
|
39
|
+
replies: replyMap.get(c.id) || []
|
|
40
|
+
});
|
|
41
|
+
lineMap.set(line, threads);
|
|
42
|
+
}
|
|
43
|
+
return lineMap;
|
|
44
|
+
}
|
|
45
|
+
function resolveComments(loader, options) {
|
|
46
|
+
const showResolved = options.showResolved ?? true;
|
|
47
|
+
const doc = loader(options);
|
|
48
|
+
if (!doc || !doc.comments || doc.comments.length === 0) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
let comments = toSlimComments(doc);
|
|
52
|
+
if (!showResolved) {
|
|
53
|
+
comments = comments.filter((c) => !c.resolved);
|
|
54
|
+
}
|
|
55
|
+
if (comments.length === 0)
|
|
56
|
+
return null;
|
|
57
|
+
const lineMap = groupByLine(comments);
|
|
58
|
+
return { lineMap, comments };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ../shared/dist/html.js
|
|
62
|
+
function escapeHtml(str) {
|
|
63
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
64
|
+
}
|
|
65
|
+
function formatTime(iso) {
|
|
66
|
+
if (!iso)
|
|
67
|
+
return "";
|
|
68
|
+
try {
|
|
69
|
+
const d = new Date(iso);
|
|
70
|
+
return d.toLocaleDateString(void 0, {
|
|
71
|
+
year: "numeric",
|
|
72
|
+
month: "short",
|
|
73
|
+
day: "numeric"
|
|
74
|
+
});
|
|
75
|
+
} catch {
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function renderCommentHtml(comment, isReply, interactive) {
|
|
80
|
+
const resolvedClass = comment.resolved ? " mrsf-resolved" : "";
|
|
81
|
+
const replyClass = isReply ? " mrsf-reply" : "";
|
|
82
|
+
let html = `<span class="mrsf-comment${resolvedClass}${replyClass}" data-mrsf-comment-id="${escapeHtml(comment.id)}">`;
|
|
83
|
+
html += `<span class="mrsf-comment-header">`;
|
|
84
|
+
html += `<span class="mrsf-author">${escapeHtml(comment.author)}</span>`;
|
|
85
|
+
if (comment.timestamp) {
|
|
86
|
+
html += `<span class="mrsf-date">${escapeHtml(formatTime(comment.timestamp))}</span>`;
|
|
87
|
+
}
|
|
88
|
+
if (comment.severity) {
|
|
89
|
+
html += `<span class="mrsf-severity mrsf-severity-${escapeHtml(comment.severity)}">${escapeHtml(comment.severity)}</span>`;
|
|
90
|
+
}
|
|
91
|
+
if (comment.type) {
|
|
92
|
+
html += `<span class="mrsf-type">${escapeHtml(comment.type)}</span>`;
|
|
93
|
+
}
|
|
94
|
+
if (comment.resolved) {
|
|
95
|
+
html += `<span class="mrsf-resolved-badge">\u2713 resolved</span>`;
|
|
96
|
+
}
|
|
97
|
+
html += `</span>`;
|
|
98
|
+
if (comment.selected_text) {
|
|
99
|
+
html += `<span class="mrsf-selected-text">${escapeHtml(comment.selected_text)}</span>`;
|
|
100
|
+
}
|
|
101
|
+
html += `<span class="mrsf-comment-body">${escapeHtml(comment.text)}</span>`;
|
|
102
|
+
if (interactive) {
|
|
103
|
+
const line = comment.line != null ? String(comment.line) : "";
|
|
104
|
+
html += `<span class="mrsf-actions">`;
|
|
105
|
+
if (comment.resolved) {
|
|
106
|
+
html += `<button class="mrsf-action-btn" data-mrsf-action="unresolve" data-mrsf-comment-id="${escapeHtml(comment.id)}" data-mrsf-line="${line}">Unresolve</button>`;
|
|
107
|
+
} else {
|
|
108
|
+
html += `<button class="mrsf-action-btn" data-mrsf-action="resolve" data-mrsf-comment-id="${escapeHtml(comment.id)}" data-mrsf-line="${line}">Resolve</button>`;
|
|
33
109
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
110
|
+
html += `<button class="mrsf-action-btn" data-mrsf-action="reply" data-mrsf-comment-id="${escapeHtml(comment.id)}" data-mrsf-line="${line}">Reply</button>`;
|
|
111
|
+
html += `<button class="mrsf-action-btn" data-mrsf-action="edit" data-mrsf-comment-id="${escapeHtml(comment.id)}" data-mrsf-line="${line}">Edit</button>`;
|
|
112
|
+
html += `</span>`;
|
|
113
|
+
}
|
|
114
|
+
html += `</span>`;
|
|
115
|
+
return html;
|
|
116
|
+
}
|
|
117
|
+
function renderThreadHtml(thread, interactive) {
|
|
118
|
+
let html = `<span class="mrsf-thread">`;
|
|
119
|
+
html += renderCommentHtml(thread.comment, false, interactive);
|
|
120
|
+
if (thread.replies.length > 0) {
|
|
121
|
+
html += `<span class="mrsf-replies">`;
|
|
122
|
+
for (const reply of thread.replies) {
|
|
123
|
+
html += renderCommentHtml(reply, true, interactive);
|
|
42
124
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
125
|
+
html += `</span>`;
|
|
126
|
+
}
|
|
127
|
+
html += `</span>`;
|
|
128
|
+
return html;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/rules/core.ts
|
|
132
|
+
function highlightInlineText(token, thread, interactive, Token) {
|
|
133
|
+
if (!token.children) return false;
|
|
134
|
+
const text = thread.comment.selected_text;
|
|
135
|
+
if (!text) return false;
|
|
136
|
+
const trimmed = text.trim();
|
|
137
|
+
if (!trimmed) return false;
|
|
138
|
+
for (let i = 0; i < token.children.length; i++) {
|
|
139
|
+
const child = token.children[i];
|
|
140
|
+
if (child.type !== "text") continue;
|
|
141
|
+
const idx = child.content.indexOf(trimmed);
|
|
142
|
+
if (idx === -1) continue;
|
|
143
|
+
const newTokens = [];
|
|
144
|
+
if (idx > 0) {
|
|
145
|
+
const before = new Token("text", "", 0);
|
|
146
|
+
before.content = child.content.slice(0, idx);
|
|
147
|
+
newTokens.push(before);
|
|
148
|
+
}
|
|
149
|
+
const open = new Token("mrsf_highlight_open", "mark", 1);
|
|
150
|
+
open.attrSet("class", "mrsf-highlight");
|
|
151
|
+
open.attrSet("data-mrsf-comment-id", thread.comment.id);
|
|
152
|
+
open.meta = { thread };
|
|
153
|
+
newTokens.push(open);
|
|
154
|
+
const matched = new Token("text", "", 0);
|
|
155
|
+
matched.content = trimmed;
|
|
156
|
+
newTokens.push(matched);
|
|
157
|
+
const close = new Token("mrsf_highlight_close", "mark", -1);
|
|
158
|
+
close.meta = { thread, interactive };
|
|
159
|
+
newTokens.push(close);
|
|
160
|
+
if (idx + trimmed.length < child.content.length) {
|
|
161
|
+
const after = new Token("text", "", 0);
|
|
162
|
+
after.content = child.content.slice(idx + trimmed.length);
|
|
163
|
+
newTokens.push(after);
|
|
164
|
+
}
|
|
165
|
+
token.children.splice(i, 1, ...newTokens);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
function installCoreRule(md, lineMap, options) {
|
|
171
|
+
const { interactive, gutterPosition, gutterForInline, inlineHighlights } = options;
|
|
172
|
+
md.core.ruler.push("mrsf_inject", (state) => {
|
|
173
|
+
const tokens = state.tokens;
|
|
174
|
+
const TokenCtor = state.Token;
|
|
175
|
+
const processed = /* @__PURE__ */ new Set();
|
|
176
|
+
for (let i = tokens.length - 1; i >= 0; i--) {
|
|
177
|
+
const token = tokens[i];
|
|
178
|
+
const map = token.map;
|
|
179
|
+
if (!map) continue;
|
|
180
|
+
if (token.type === "inline") continue;
|
|
181
|
+
for (let line0 = map[0]; line0 < map[1]; line0++) {
|
|
182
|
+
const line = line0 + 1;
|
|
183
|
+
if (processed.has(line)) continue;
|
|
184
|
+
const threads = lineMap.get(line);
|
|
185
|
+
if (!threads || threads.length === 0) continue;
|
|
186
|
+
processed.add(line);
|
|
187
|
+
const existingClass = token.attrGet("class") || "";
|
|
188
|
+
token.attrSet(
|
|
189
|
+
"class",
|
|
190
|
+
existingClass ? `${existingClass} mrsf-line-highlight` : "mrsf-line-highlight"
|
|
191
|
+
);
|
|
192
|
+
token.attrSet("data-mrsf-line", String(line));
|
|
193
|
+
const allHaveInline = inlineHighlights && threads.every((t) => !!t.comment.selected_text);
|
|
194
|
+
const showBadge = gutterForInline || !allHaveInline || !inlineHighlights;
|
|
195
|
+
if (showBadge) {
|
|
196
|
+
const badgeToken = new TokenCtor("mrsf_badge", "", 0);
|
|
197
|
+
badgeToken.meta = { line, threads, interactive, gutterPosition };
|
|
198
|
+
tokens.splice(i + 1, 0, badgeToken);
|
|
49
199
|
}
|
|
50
|
-
|
|
51
|
-
|
|
200
|
+
if (inlineHighlights) {
|
|
201
|
+
const offset = showBadge ? 2 : 1;
|
|
202
|
+
const inlineAfter = tokens[i + offset];
|
|
203
|
+
if (inlineAfter && inlineAfter.type === "inline") {
|
|
204
|
+
for (const thread of threads) {
|
|
205
|
+
if (thread.comment.selected_text) {
|
|
206
|
+
highlightInlineText(inlineAfter, thread, interactive, TokenCtor);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
52
210
|
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (gutterPosition === "left" && processed.size > 0) {
|
|
214
|
+
const openToken = new TokenCtor("mrsf_gutter_open", "div", 1);
|
|
215
|
+
openToken.attrSet("class", "mrsf-gutter-container");
|
|
216
|
+
tokens.unshift(openToken);
|
|
217
|
+
const closeToken = new TokenCtor("mrsf_gutter_close", "div", -1);
|
|
218
|
+
tokens.push(closeToken);
|
|
53
219
|
}
|
|
54
|
-
|
|
55
|
-
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/rules/renderer.ts
|
|
224
|
+
function installRendererRules(md) {
|
|
225
|
+
md.renderer.rules["mrsf_badge"] = (tokens, idx) => {
|
|
226
|
+
const { line, threads, interactive, gutterPosition } = tokens[idx].meta;
|
|
227
|
+
const total = threads.reduce(
|
|
228
|
+
(n, t) => n + 1 + t.replies.length,
|
|
229
|
+
0
|
|
230
|
+
);
|
|
231
|
+
const allResolved = threads.every((t) => t.comment.resolved);
|
|
232
|
+
const highestSeverity = threads.reduce((sev, t) => {
|
|
233
|
+
if (t.comment.severity === "high" || sev === "high") return "high";
|
|
234
|
+
if (t.comment.severity === "medium" || sev === "medium") return "medium";
|
|
235
|
+
if (t.comment.severity === "low" || sev === "low") return "low";
|
|
236
|
+
return sev;
|
|
237
|
+
}, null);
|
|
238
|
+
const resolvedClass = allResolved ? " mrsf-badge-resolved" : "";
|
|
239
|
+
const severityClass = highestSeverity === "high" || highestSeverity === "medium" ? ` mrsf-badge-severity-${highestSeverity}` : "";
|
|
240
|
+
const positionClass = ` mrsf-gutter-${gutterPosition}`;
|
|
241
|
+
const icon = allResolved ? "\u2713" : "\u{1F4AC}";
|
|
242
|
+
let html = `<span class="mrsf-tooltip-anchor${positionClass}">`;
|
|
243
|
+
html += `<span class="mrsf-badge${resolvedClass}${severityClass}" data-mrsf-line="${line}" data-mrsf-action="navigate" data-mrsf-comment-id="${escapeHtml(threads[0].comment.id)}" tabindex="0">${icon} ${total}</span>`;
|
|
244
|
+
html += `<span class="mrsf-tooltip" data-mrsf-line="${line}">`;
|
|
245
|
+
for (const thread of threads) {
|
|
246
|
+
html += renderThreadHtml(thread, interactive);
|
|
247
|
+
}
|
|
248
|
+
html += `</span>`;
|
|
249
|
+
html += `</span>`;
|
|
250
|
+
return html;
|
|
251
|
+
};
|
|
252
|
+
md.renderer.rules["mrsf_highlight_open"] = (tokens, idx) => {
|
|
253
|
+
const token = tokens[idx];
|
|
254
|
+
const cls = token.attrGet("class") || "mrsf-highlight";
|
|
255
|
+
const commentId = token.attrGet("data-mrsf-comment-id") || "";
|
|
256
|
+
const thread = token.meta?.thread;
|
|
257
|
+
let html = "";
|
|
258
|
+
if (thread) {
|
|
259
|
+
html += `<span class="mrsf-tooltip-anchor mrsf-inline-anchor">`;
|
|
260
|
+
}
|
|
261
|
+
html += `<mark class="${escapeHtml(cls)}" data-mrsf-comment-id="${escapeHtml(commentId)}" tabindex="0">`;
|
|
262
|
+
return html;
|
|
263
|
+
};
|
|
264
|
+
md.renderer.rules["mrsf_highlight_close"] = (tokens, idx) => {
|
|
265
|
+
const token = tokens[idx];
|
|
266
|
+
const meta = token.meta;
|
|
267
|
+
let html = "</mark>";
|
|
268
|
+
if (meta?.thread) {
|
|
269
|
+
const thread = meta.thread;
|
|
270
|
+
const interactive = meta.interactive;
|
|
271
|
+
html += `<span class="mrsf-tooltip mrsf-inline-tooltip">`;
|
|
272
|
+
html += renderThreadHtml(thread, interactive);
|
|
273
|
+
html += `</span>`;
|
|
274
|
+
html += `</span>`;
|
|
275
|
+
}
|
|
276
|
+
return html;
|
|
277
|
+
};
|
|
278
|
+
md.renderer.rules["mrsf_gutter_open"] = (tokens, idx) => {
|
|
279
|
+
const cls = tokens[idx].attrGet("class") || "mrsf-gutter-container";
|
|
280
|
+
return `<div class="${escapeHtml(cls)}">`;
|
|
281
|
+
};
|
|
282
|
+
md.renderer.rules["mrsf_gutter_close"] = () => {
|
|
283
|
+
return `</div>`;
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/shared.ts
|
|
288
|
+
function createMrsfPlugin(loader) {
|
|
289
|
+
return function mrsfPlugin2(md, options = {}) {
|
|
290
|
+
const interactive = options.interactive ?? false;
|
|
291
|
+
const gutterPosition = options.gutterPosition ?? "right";
|
|
292
|
+
const gutterForInline = options.gutterForInline ?? true;
|
|
293
|
+
const inlineHighlights = options.inlineHighlights ?? true;
|
|
294
|
+
const result = resolveComments(loader, options);
|
|
295
|
+
if (!result) return;
|
|
296
|
+
installCoreRule(md, result.lineMap, {
|
|
297
|
+
interactive,
|
|
298
|
+
gutterPosition,
|
|
299
|
+
gutterForInline,
|
|
300
|
+
inlineHighlights
|
|
301
|
+
});
|
|
302
|
+
installRendererRules(md);
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/index.ts
|
|
307
|
+
var mrsfPlugin = createMrsfPlugin((options) => {
|
|
308
|
+
if (options.comments) {
|
|
309
|
+
return options.comments;
|
|
310
|
+
}
|
|
311
|
+
if (options.loader) {
|
|
312
|
+
try {
|
|
313
|
+
return options.loader();
|
|
314
|
+
} catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (options.sidecarPath) {
|
|
319
|
+
try {
|
|
320
|
+
const abs = path.resolve(options.cwd || process.cwd(), options.sidecarPath);
|
|
321
|
+
const content = readFileSync(abs, "utf-8");
|
|
322
|
+
return parseSidecarContent(content, abs);
|
|
323
|
+
} catch {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (options.documentPath) {
|
|
328
|
+
try {
|
|
329
|
+
const cwd = options.cwd || process.cwd();
|
|
330
|
+
const abs = path.resolve(cwd, options.documentPath);
|
|
331
|
+
const yamlPath = abs + ".review.yaml";
|
|
332
|
+
const jsonPath = abs + ".review.json";
|
|
333
|
+
let raw = null;
|
|
334
|
+
let hint = null;
|
|
335
|
+
try {
|
|
336
|
+
raw = readFileSync(yamlPath, "utf-8");
|
|
337
|
+
hint = yamlPath;
|
|
338
|
+
} catch {
|
|
56
339
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
let raw = null;
|
|
62
|
-
let hint = null;
|
|
63
|
-
try {
|
|
64
|
-
raw = readFileSync(yamlPath, "utf-8");
|
|
65
|
-
hint = yamlPath;
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
try {
|
|
69
|
-
raw = readFileSync(jsonPath, "utf-8");
|
|
70
|
-
hint = jsonPath;
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (!raw)
|
|
77
|
-
return null;
|
|
78
|
-
return parseSidecarContent(raw, hint);
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
return null;
|
|
340
|
+
raw = readFileSync(jsonPath, "utf-8");
|
|
341
|
+
hint = jsonPath;
|
|
342
|
+
} catch {
|
|
343
|
+
return null;
|
|
82
344
|
}
|
|
345
|
+
}
|
|
346
|
+
if (!raw) return null;
|
|
347
|
+
return parseSidecarContent(raw, hint);
|
|
348
|
+
} catch {
|
|
349
|
+
return null;
|
|
83
350
|
}
|
|
84
|
-
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
85
353
|
});
|
|
86
|
-
|
|
87
|
-
|
|
354
|
+
var index_default = mrsfPlugin;
|
|
355
|
+
export {
|
|
356
|
+
index_default as default,
|
|
357
|
+
mrsfPlugin
|
|
358
|
+
};
|
|
359
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../../shared/src/comments.ts", "../../shared/src/html.ts", "../src/rules/core.ts", "../src/rules/renderer.ts", "../src/shared.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * @mrsf/markdown-it-mrsf \u2014 markdown-it plugin for MRSF review comments.\n *\n * This is the full-featured Node.js entry point. It supports all four\n * sidecar provisioning modes: `comments`, `loader`, `sidecarPath`, and\n * `documentPath`. The latter two require Node.js file-system APIs.\n *\n * In browser / bundler environments, the `\"browser\"` export condition in\n * package.json automatically resolves to the browser-safe entry, so you\n * never need to import a different path \u2014 just use:\n *\n * @example\n * ```ts\n * import MarkdownIt from \"markdown-it\";\n * import { mrsfPlugin } from \"@mrsf/markdown-it-mrsf\";\n *\n * const md = new MarkdownIt();\n * md.use(mrsfPlugin, { documentPath: \"docs/guide.md\" });\n * const html = md.render(markdownSource);\n * ```\n */\n\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { parseSidecarContent } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions } from \"./types.js\";\nimport { createMrsfPlugin } from \"./shared.js\";\n\n// Re-export types for consumers\nexport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\n/**\n * The markdown-it plugin function (Node.js \u2014 full feature set).\n */\nexport const mrsfPlugin = createMrsfPlugin((options: MrsfPluginOptions) => {\n // Priority 1: inline data\n if (options.comments) {\n return options.comments;\n }\n\n // Priority 2: custom loader function\n if (options.loader) {\n try {\n return options.loader();\n } catch {\n return null;\n }\n }\n\n // Priority 3: explicit sidecar path\n if (options.sidecarPath) {\n try {\n const abs = path.resolve(options.cwd || process.cwd(), options.sidecarPath);\n const content = readFileSync(abs, \"utf-8\");\n return parseSidecarContent(content, abs);\n } catch {\n return null;\n }\n }\n\n // Priority 4: auto-discover from documentPath\n if (options.documentPath) {\n try {\n const cwd = options.cwd || process.cwd();\n const abs = path.resolve(cwd, options.documentPath);\n const yamlPath = abs + \".review.yaml\";\n const jsonPath = abs + \".review.json\";\n\n let raw: string | null = null;\n let hint: string | null = null;\n try {\n raw = readFileSync(yamlPath, \"utf-8\");\n hint = yamlPath;\n } catch {\n try {\n raw = readFileSync(jsonPath, \"utf-8\");\n hint = jsonPath;\n } catch {\n return null;\n }\n }\n\n if (!raw) return null;\n return parseSidecarContent(raw, hint!);\n } catch {\n return null;\n }\n }\n\n return null;\n});\n\nexport default mrsfPlugin;\n", "/**\n * Shared comment loading and grouping logic for MRSF rendering plugins.\n */\n\nimport type { MrsfDocument } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\nexport type { CommentLoader };\n\n/**\n * Convert an MrsfDocument into a slim comment array.\n */\nexport function toSlimComments(doc: MrsfDocument): SlimComment[] {\n return doc.comments.map((c) => ({\n id: c.id,\n author: c.author || \"Unknown\",\n text: c.text || \"\",\n line: c.line ?? null,\n end_line: c.end_line ?? null,\n start_column: c.start_column ?? null,\n end_column: c.end_column ?? null,\n selected_text: c.selected_text || null,\n resolved: !!c.resolved,\n reply_to: c.reply_to || null,\n severity: c.severity || null,\n type: c.type || null,\n timestamp: c.timestamp || null,\n }));\n}\n\n/**\n * Group comments by line number, threading replies under parents.\n */\nexport function groupByLine(comments: SlimComment[]): LineMap {\n const rootComments = comments.filter((c) => !c.reply_to && c.line != null);\n const replies = comments.filter((c) => c.reply_to);\n\n const replyMap = new Map<string, SlimComment[]>();\n for (const r of replies) {\n const list = replyMap.get(r.reply_to!) || [];\n list.push(r);\n replyMap.set(r.reply_to!, list);\n }\n\n const lineMap: LineMap = new Map();\n for (const c of rootComments) {\n const line = c.line!;\n const threads = lineMap.get(line) || [];\n threads.push({\n comment: c,\n replies: replyMap.get(c.id) || [],\n });\n lineMap.set(line, threads);\n }\n\n return lineMap;\n}\n\n/**\n * Resolve options into a filtered LineMap ready for rendering.\n * Returns null if there are no comments to render.\n */\nexport function resolveComments(\n loader: CommentLoader,\n options: MrsfPluginOptions,\n): { lineMap: LineMap; comments: SlimComment[] } | null {\n const showResolved = options.showResolved ?? true;\n\n const doc = loader(options);\n if (!doc || !doc.comments || doc.comments.length === 0) {\n return null;\n }\n\n let comments = toSlimComments(doc);\n if (!showResolved) {\n comments = comments.filter((c) => !c.resolved);\n }\n\n if (comments.length === 0) return null;\n\n const lineMap = groupByLine(comments);\n return { lineMap, comments };\n}\n", "/**\n * Shared HTML rendering helpers for MRSF rendering plugins.\n *\n * These produce the HTML strings used in tooltips, badges, and comment\n * rendering. Both the markdown-it and rehype plugins use these functions\n * to ensure identical visual output.\n */\n\nimport type { CommentThread, SlimComment } from \"./types.js\";\n\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\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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",
|
|
6
|
+
"names": ["mrsfPlugin"]
|
|
7
|
+
}
|
package/dist/shared.d.ts
CHANGED
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared logic for @mrsf/markdown-it-mrsf.
|
|
3
3
|
*
|
|
4
|
-
* Contains the plugin factory
|
|
5
|
-
* used by both the Node.js (index.ts) and browser (browser.ts) entry points.
|
|
4
|
+
* Contains the plugin factory using shared comment loading/grouping logic.
|
|
6
5
|
*/
|
|
7
6
|
import type MarkdownIt from "markdown-it";
|
|
8
|
-
import type {
|
|
9
|
-
import type { MrsfPluginOptions, SlimComment, LineMap } from "./types.js";
|
|
10
|
-
/**
|
|
11
|
-
* A function that loads sidecar data from plugin options.
|
|
12
|
-
*/
|
|
13
|
-
export type CommentLoader = (options: MrsfPluginOptions) => MrsfDocument | null;
|
|
14
|
-
/**
|
|
15
|
-
* Convert an MrsfDocument into a slim comment array.
|
|
16
|
-
*/
|
|
17
|
-
export declare function toSlimComments(doc: MrsfDocument): SlimComment[];
|
|
18
|
-
/**
|
|
19
|
-
* Group comments by line number, threading replies under parents.
|
|
20
|
-
*/
|
|
21
|
-
export declare function groupByLine(comments: SlimComment[]): LineMap;
|
|
7
|
+
import type { MrsfPluginOptions, CommentLoader } from "./types.js";
|
|
22
8
|
/**
|
|
23
9
|
* Create a markdown-it plugin function with the given comment loader.
|
|
24
10
|
*
|
package/dist/types.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Shared type definitions for MRSF rendering plugins.
|
|
3
|
+
*
|
|
4
|
+
* These types are used by both @mrsf/markdown-it-mrsf and @mrsf/rehype-mrsf.
|
|
3
5
|
*/
|
|
4
6
|
import type { MrsfDocument } from "@mrsf/cli";
|
|
5
|
-
/**
|
|
7
|
+
/** Base options shared by all MRSF rendering plugins. */
|
|
6
8
|
export interface MrsfPluginOptions {
|
|
7
9
|
/**
|
|
8
10
|
* Path to the Markdown document on disk.
|
|
@@ -81,4 +83,6 @@ export interface CommentThread {
|
|
|
81
83
|
}
|
|
82
84
|
/** Comments grouped by their anchored source line. */
|
|
83
85
|
export type LineMap = Map<number, CommentThread[]>;
|
|
86
|
+
/** A function that loads sidecar data from plugin options. */
|
|
87
|
+
export type CommentLoader = (options: MrsfPluginOptions) => MrsfDocument | null;
|
|
84
88
|
//# sourceMappingURL=types.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrsf/markdown-it-mrsf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "markdown-it plugin for rendering Sidemark/MRSF (Markdown Review Sidecar Format) review comments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"node": ">=18"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
|
-
"build": "
|
|
31
|
+
"build": "node ../sync-types.mjs && node esbuild.config.mjs",
|
|
32
32
|
"dev": "tsc --watch",
|
|
33
33
|
"test": "vitest run",
|
|
34
34
|
"test:watch": "vitest",
|
|
35
35
|
"lint": "tsc --noEmit",
|
|
36
|
-
"prepublishOnly": "cp
|
|
36
|
+
"prepublishOnly": "cp ../../LICENSE . && npm run build && npm test",
|
|
37
37
|
"postpublish": "rm -f LICENSE"
|
|
38
38
|
},
|
|
39
39
|
"keywords": [
|
|
@@ -44,32 +44,39 @@
|
|
|
44
44
|
"sidecar",
|
|
45
45
|
"mrsf",
|
|
46
46
|
"sidemark",
|
|
47
|
-
"comments"
|
|
47
|
+
"comments",
|
|
48
|
+
"annotation",
|
|
49
|
+
"highlight",
|
|
50
|
+
"tooltip",
|
|
51
|
+
"rendering"
|
|
48
52
|
],
|
|
49
53
|
"license": "MIT",
|
|
50
54
|
"author": "Wictor Wilén (https://github.com/wictorwilen)",
|
|
51
|
-
"homepage": "https://github.com/wictorwilen/MRSF/tree/main/markdown-it#readme",
|
|
55
|
+
"homepage": "https://github.com/wictorwilen/MRSF/tree/main/plugins/markdown-it#readme",
|
|
52
56
|
"bugs": {
|
|
53
57
|
"url": "https://github.com/wictorwilen/MRSF/issues"
|
|
54
58
|
},
|
|
55
59
|
"repository": {
|
|
56
60
|
"type": "git",
|
|
57
61
|
"url": "https://github.com/wictorwilen/MRSF.git",
|
|
58
|
-
"directory": "markdown-it"
|
|
62
|
+
"directory": "plugins/markdown-it"
|
|
59
63
|
},
|
|
60
64
|
"files": [
|
|
61
65
|
"dist/**/*.js",
|
|
66
|
+
"dist/**/*.js.map",
|
|
62
67
|
"dist/**/*.d.ts",
|
|
63
68
|
"dist/style.css",
|
|
64
69
|
"README.md",
|
|
65
70
|
"LICENSE"
|
|
66
71
|
],
|
|
67
72
|
"dependencies": {
|
|
68
|
-
"@mrsf/cli": "
|
|
73
|
+
"@mrsf/cli": "^0.3.0"
|
|
69
74
|
},
|
|
70
75
|
"devDependencies": {
|
|
76
|
+
"@mrsf/plugin-shared": "file:../shared",
|
|
71
77
|
"@types/markdown-it": "^14.1.2",
|
|
72
78
|
"@types/node": "^22.13.10",
|
|
79
|
+
"esbuild": "^0.27.3",
|
|
73
80
|
"markdown-it": "^14.1.0",
|
|
74
81
|
"typescript": "^5.8.2",
|
|
75
82
|
"vitest": "^3.0.9"
|