@mrsf/marked-mrsf 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/dist/browser.d.ts +8 -0
- package/dist/browser.js +272 -0
- package/dist/browser.js.map +7 -0
- package/dist/controller.d.ts +169 -0
- package/dist/controller.js +1059 -0
- package/dist/controller.js.map +7 -0
- package/dist/html.d.ts +13 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +306 -0
- package/dist/index.js.map +7 -0
- package/dist/shared.d.ts +7 -0
- package/dist/style.css +496 -0
- package/dist/types.d.ts +104 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wictor Wilén
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @mrsf/marked-mrsf
|
|
2
|
+
|
|
3
|
+
A [Marked](https://github.com/markedjs/marked) plugin for rendering [MRSF (Sidemark)](https://github.com/wictorwilen/MRSF) review comments into HTML output.
|
|
4
|
+
|
|
5
|
+
Like the other MRSF rendering plugins, it annotates rendered block elements with `data-mrsf-*` line metadata and appends a serialized comment payload for the shared client-side controller.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install marked @mrsf/marked-mrsf
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Marked } from "marked";
|
|
17
|
+
import { markedMrsf } from "@mrsf/marked-mrsf";
|
|
18
|
+
|
|
19
|
+
const parser = new Marked();
|
|
20
|
+
parser.use(markedMrsf({
|
|
21
|
+
documentPath: "docs/guide.md",
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const html = parser.parse(markdownSource);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For browser usage, pass `comments` or `loader` instead of `documentPath` / `sidecarPath`.
|
|
28
|
+
|
|
29
|
+
## Stylesheet and Controller
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import "@mrsf/marked-mrsf/style.css";
|
|
33
|
+
import { refreshAll } from "@mrsf/marked-mrsf/controller";
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The shared controller wires up gutter buttons, selection actions, and built-in dialogs, then dispatches `mrsf:*` events for your host app to persist.
|
|
37
|
+
|
|
38
|
+
## Options
|
|
39
|
+
|
|
40
|
+
The plugin accepts the same `MrsfPluginOptions` surface as the markdown-it and rehype packages: `comments`, `loader`, `documentPath`, `sidecarPath`, `showResolved`, `dataContainer`, `dataElementId`, `interactive`, `gutterPosition`, `gutterForInline`, `inlineHighlights`, `lineHighlight`, `theme`, and `cwd`.
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
|
|
44
|
+
[MIT](../LICENSE)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe entry point for @mrsf/marked-mrsf.
|
|
3
|
+
*/
|
|
4
|
+
import type { MrsfPluginOptions } from "./types.js";
|
|
5
|
+
export type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from "./types.js";
|
|
6
|
+
export declare const markedMrsf: (options?: MrsfPluginOptions) => import("marked").MarkedExtension;
|
|
7
|
+
export default markedMrsf;
|
|
8
|
+
//# sourceMappingURL=browser.d.ts.map
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// src/shared.ts
|
|
2
|
+
import { Marked } from "marked";
|
|
3
|
+
|
|
4
|
+
// ../shared/dist/comments.js
|
|
5
|
+
function toSlimComments(doc) {
|
|
6
|
+
return doc.comments.map((c) => ({
|
|
7
|
+
id: c.id,
|
|
8
|
+
author: c.author || "Unknown",
|
|
9
|
+
text: c.text || "",
|
|
10
|
+
line: c.line ?? null,
|
|
11
|
+
end_line: c.end_line ?? null,
|
|
12
|
+
start_column: c.start_column ?? null,
|
|
13
|
+
end_column: c.end_column ?? null,
|
|
14
|
+
selected_text: c.selected_text || null,
|
|
15
|
+
resolved: !!c.resolved,
|
|
16
|
+
reply_to: c.reply_to || null,
|
|
17
|
+
severity: c.severity || null,
|
|
18
|
+
type: c.type || null,
|
|
19
|
+
timestamp: c.timestamp || null
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
function groupByLine(comments) {
|
|
23
|
+
const rootComments = comments.filter((c) => !c.reply_to && c.line != null);
|
|
24
|
+
const replies = comments.filter((c) => c.reply_to);
|
|
25
|
+
const replyMap = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const r of replies) {
|
|
27
|
+
const list = replyMap.get(r.reply_to) || [];
|
|
28
|
+
list.push(r);
|
|
29
|
+
replyMap.set(r.reply_to, list);
|
|
30
|
+
}
|
|
31
|
+
const lineMap = /* @__PURE__ */ new Map();
|
|
32
|
+
for (const c of rootComments) {
|
|
33
|
+
const line = c.line;
|
|
34
|
+
const threads = lineMap.get(line) || [];
|
|
35
|
+
threads.push({
|
|
36
|
+
comment: c,
|
|
37
|
+
replies: replyMap.get(c.id) || []
|
|
38
|
+
});
|
|
39
|
+
lineMap.set(line, threads);
|
|
40
|
+
}
|
|
41
|
+
return lineMap;
|
|
42
|
+
}
|
|
43
|
+
function resolveComments(loader, options, env) {
|
|
44
|
+
const showResolved = options.showResolved ?? true;
|
|
45
|
+
const doc = loader(options, env);
|
|
46
|
+
if (!doc || !doc.comments || doc.comments.length === 0) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
let comments = toSlimComments(doc);
|
|
50
|
+
if (!showResolved) {
|
|
51
|
+
comments = comments.filter((c) => !c.resolved);
|
|
52
|
+
}
|
|
53
|
+
if (comments.length === 0)
|
|
54
|
+
return null;
|
|
55
|
+
const lineMap = groupByLine(comments);
|
|
56
|
+
return { lineMap, comments };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/shared.ts
|
|
60
|
+
var markedRuntime = new Marked();
|
|
61
|
+
var BaseRenderer = markedRuntime.Renderer;
|
|
62
|
+
var blockquoteRenderer = BaseRenderer.prototype.blockquote;
|
|
63
|
+
var codeRenderer = BaseRenderer.prototype.code;
|
|
64
|
+
var headingRenderer = BaseRenderer.prototype.heading;
|
|
65
|
+
var hrRenderer = BaseRenderer.prototype.hr;
|
|
66
|
+
var listItemRenderer = BaseRenderer.prototype.listitem;
|
|
67
|
+
var paragraphRenderer = BaseRenderer.prototype.paragraph;
|
|
68
|
+
var tableRenderer = BaseRenderer.prototype.table;
|
|
69
|
+
function countLines(value) {
|
|
70
|
+
if (!value) return 1;
|
|
71
|
+
return value.split("\n").length;
|
|
72
|
+
}
|
|
73
|
+
function trimTrailingBlankLines(value) {
|
|
74
|
+
return value.replace(/\n+$/g, "");
|
|
75
|
+
}
|
|
76
|
+
function firstCommentLine(lineMap, startLine, endLine) {
|
|
77
|
+
for (let line = startLine; line <= endLine; line++) {
|
|
78
|
+
const threads = lineMap.get(line);
|
|
79
|
+
if (threads && threads.length > 0) {
|
|
80
|
+
return line;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return startLine;
|
|
84
|
+
}
|
|
85
|
+
function hasCommentInRange(lineMap, startLine, endLine) {
|
|
86
|
+
for (let line = startLine; line <= endLine; line++) {
|
|
87
|
+
const threads = lineMap.get(line);
|
|
88
|
+
if (threads && threads.length > 0) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
function stampBlock(token, startLine, lineMap) {
|
|
95
|
+
const raw = typeof token.raw === "string" ? token.raw : "";
|
|
96
|
+
const semantic = trimTrailingBlankLines(raw);
|
|
97
|
+
const endLine = startLine + countLines(semantic) - 1;
|
|
98
|
+
token.mrsfStartLine = startLine;
|
|
99
|
+
token.mrsfEndLine = endLine;
|
|
100
|
+
token.mrsfLine = firstCommentLine(lineMap, startLine, endLine);
|
|
101
|
+
token.mrsfLineHighlight = hasCommentInRange(lineMap, startLine, endLine);
|
|
102
|
+
if (token.type === "table") {
|
|
103
|
+
const tableToken = token;
|
|
104
|
+
tableToken.mrsfHeaderLine = startLine;
|
|
105
|
+
tableToken.mrsfRowLines = tableToken.rows.map((_, index) => startLine + 2 + index);
|
|
106
|
+
}
|
|
107
|
+
return startLine + countLines(raw) - 1;
|
|
108
|
+
}
|
|
109
|
+
function stampListItems(token, lineMap) {
|
|
110
|
+
let currentLine = token.mrsfStartLine ?? 1;
|
|
111
|
+
for (const item of token.items) {
|
|
112
|
+
const raw = typeof item.raw === "string" ? item.raw : "";
|
|
113
|
+
const semantic = trimTrailingBlankLines(raw);
|
|
114
|
+
const endLine = currentLine + countLines(semantic) - 1;
|
|
115
|
+
item.mrsfStartLine = currentLine;
|
|
116
|
+
item.mrsfEndLine = endLine;
|
|
117
|
+
item.mrsfLine = firstCommentLine(lineMap, currentLine, endLine);
|
|
118
|
+
item.mrsfLineHighlight = hasCommentInRange(lineMap, currentLine, endLine);
|
|
119
|
+
stampTokens(item.tokens, currentLine, lineMap);
|
|
120
|
+
currentLine += countLines(raw) - 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function stampTokens(tokens, startLine, lineMap) {
|
|
124
|
+
let currentLine = startLine;
|
|
125
|
+
for (const token of tokens) {
|
|
126
|
+
currentLine = stampBlock(token, currentLine, lineMap);
|
|
127
|
+
if (token.type === "blockquote") {
|
|
128
|
+
stampTokens(token.tokens, token.mrsfStartLine ?? currentLine, lineMap);
|
|
129
|
+
} else if (token.type === "list") {
|
|
130
|
+
stampListItems(token, lineMap);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return currentLine;
|
|
134
|
+
}
|
|
135
|
+
function escapeAttribute(value) {
|
|
136
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
137
|
+
}
|
|
138
|
+
function buildAttrs(token, lineHighlight) {
|
|
139
|
+
if (token.mrsfStartLine == null || token.mrsfEndLine == null || token.mrsfLine == null) {
|
|
140
|
+
return "";
|
|
141
|
+
}
|
|
142
|
+
const attrs = [
|
|
143
|
+
`data-mrsf-line="${token.mrsfLine}"`,
|
|
144
|
+
`data-mrsf-start-line="${token.mrsfStartLine}"`,
|
|
145
|
+
`data-mrsf-end-line="${token.mrsfEndLine}"`
|
|
146
|
+
];
|
|
147
|
+
if (lineHighlight && token.mrsfLineHighlight) {
|
|
148
|
+
attrs.push('class="mrsf-line-highlight"');
|
|
149
|
+
}
|
|
150
|
+
return attrs.length > 0 ? " " + attrs.join(" ") : "";
|
|
151
|
+
}
|
|
152
|
+
function addAttrs(html, attrs, tagName) {
|
|
153
|
+
if (!attrs) return html;
|
|
154
|
+
const pattern = tagName ? new RegExp(`^<${tagName}(?=[\\s>])`, "i") : /^<([a-z0-9-]+)(?=[\s>])/i;
|
|
155
|
+
return html.replace(pattern, (match) => match + attrs);
|
|
156
|
+
}
|
|
157
|
+
function flattenThreads(lineMap) {
|
|
158
|
+
const threads = [];
|
|
159
|
+
for (const lineThreads of lineMap.values()) {
|
|
160
|
+
threads.push(...lineThreads);
|
|
161
|
+
}
|
|
162
|
+
return threads;
|
|
163
|
+
}
|
|
164
|
+
function createDataContainer(options, threads) {
|
|
165
|
+
const payload = JSON.stringify({ threads });
|
|
166
|
+
if (options.dataContainer === "element") {
|
|
167
|
+
const elementId = options.dataElementId || "mrsf-comment-data";
|
|
168
|
+
return `<div id="${escapeAttribute(elementId)}" data-mrsf-json="${escapeAttribute(payload)}" aria-hidden="true"></div>`;
|
|
169
|
+
}
|
|
170
|
+
return `<script type="application/mrsf+json">${payload.replace(/</g, "\\u003c")}</script>`;
|
|
171
|
+
}
|
|
172
|
+
function renderAnnotatedTable(token, lineMap, lineHighlight) {
|
|
173
|
+
const baseHtml = tableRenderer.call(this, token);
|
|
174
|
+
const rowLines = [token.mrsfHeaderLine, ...token.mrsfRowLines ?? []];
|
|
175
|
+
let rowIndex = 0;
|
|
176
|
+
return baseHtml.replace(/<tr>/g, () => {
|
|
177
|
+
const line = rowLines[rowIndex++];
|
|
178
|
+
if (line == null) return "<tr>";
|
|
179
|
+
const hasComment = hasCommentInRange(lineMap, line, line);
|
|
180
|
+
const attrs = [
|
|
181
|
+
`data-mrsf-line="${line}"`,
|
|
182
|
+
`data-mrsf-start-line="${line}"`,
|
|
183
|
+
`data-mrsf-end-line="${line}"`
|
|
184
|
+
];
|
|
185
|
+
if (lineHighlight && hasComment) {
|
|
186
|
+
attrs.push('class="mrsf-line-highlight"');
|
|
187
|
+
}
|
|
188
|
+
return `<tr ${attrs.join(" ")}>`;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function createMarkedMrsf(loader) {
|
|
192
|
+
return function markedMrsf2(options = {}) {
|
|
193
|
+
let currentLineMap = null;
|
|
194
|
+
let currentThreads = [];
|
|
195
|
+
return {
|
|
196
|
+
hooks: {
|
|
197
|
+
processAllTokens(tokens) {
|
|
198
|
+
const result = resolveComments(loader, options);
|
|
199
|
+
if (!result) {
|
|
200
|
+
currentLineMap = null;
|
|
201
|
+
currentThreads = [];
|
|
202
|
+
return tokens;
|
|
203
|
+
}
|
|
204
|
+
currentLineMap = result.lineMap;
|
|
205
|
+
currentThreads = flattenThreads(result.lineMap);
|
|
206
|
+
stampTokens(tokens, 1, result.lineMap);
|
|
207
|
+
return tokens;
|
|
208
|
+
},
|
|
209
|
+
postprocess(html) {
|
|
210
|
+
if (!currentLineMap || currentThreads.length === 0) {
|
|
211
|
+
return html;
|
|
212
|
+
}
|
|
213
|
+
return html + createDataContainer(options, currentThreads);
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
renderer: {
|
|
217
|
+
heading(token) {
|
|
218
|
+
return addAttrs(headingRenderer.call(this, token), buildAttrs(token, options.lineHighlight ?? false), `h${token.depth}`);
|
|
219
|
+
},
|
|
220
|
+
paragraph(token) {
|
|
221
|
+
return addAttrs(paragraphRenderer.call(this, token), buildAttrs(token, options.lineHighlight ?? false), "p");
|
|
222
|
+
},
|
|
223
|
+
code(token) {
|
|
224
|
+
return addAttrs(codeRenderer.call(this, token), buildAttrs(token, options.lineHighlight ?? false), "pre");
|
|
225
|
+
},
|
|
226
|
+
blockquote(token) {
|
|
227
|
+
return addAttrs(blockquoteRenderer.call(this, token), buildAttrs(token, options.lineHighlight ?? false), "blockquote");
|
|
228
|
+
},
|
|
229
|
+
listitem(token) {
|
|
230
|
+
return addAttrs(listItemRenderer.call(this, token), buildAttrs(token, options.lineHighlight ?? false), "li");
|
|
231
|
+
},
|
|
232
|
+
hr(token) {
|
|
233
|
+
return addAttrs(hrRenderer.call(this, token), buildAttrs(token, options.lineHighlight ?? false), "hr");
|
|
234
|
+
},
|
|
235
|
+
table(token) {
|
|
236
|
+
return renderAnnotatedTable.call(
|
|
237
|
+
this,
|
|
238
|
+
token,
|
|
239
|
+
currentLineMap ?? /* @__PURE__ */ new Map(),
|
|
240
|
+
options.lineHighlight ?? false
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/browser.ts
|
|
249
|
+
var markedMrsf = createMarkedMrsf((options, env) => {
|
|
250
|
+
if (options.comments) {
|
|
251
|
+
return options.comments;
|
|
252
|
+
}
|
|
253
|
+
if (options.loader) {
|
|
254
|
+
try {
|
|
255
|
+
return options.loader(options, env);
|
|
256
|
+
} catch {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (options.sidecarPath || options.documentPath) {
|
|
261
|
+
console.warn(
|
|
262
|
+
"[@mrsf/marked-mrsf] sidecarPath and documentPath require Node.js. Use `comments` or `loader` options in browser environments."
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
266
|
+
});
|
|
267
|
+
var browser_default = markedMrsf;
|
|
268
|
+
export {
|
|
269
|
+
browser_default as default,
|
|
270
|
+
markedMrsf
|
|
271
|
+
};
|
|
272
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/shared.ts", "../../shared/src/comments.ts", "../src/browser.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Shared logic for @mrsf/marked-mrsf.\n */\n\nimport { Marked, type MarkedExtension, type Token, type Tokens } from \"marked\";\nimport { resolveComments } from \"@mrsf/plugin-shared\";\nimport type { CommentLoader, CommentThread, LineMap, MrsfPluginOptions } from \"./types.js\";\n\ntype TokenWithMrsf = Token & {\n mrsfStartLine?: number;\n mrsfEndLine?: number;\n mrsfLine?: number;\n mrsfLineHighlight?: boolean;\n mrsfHeaderLine?: number;\n mrsfRowLines?: number[];\n};\n\ntype ListItemWithMrsf = Tokens.ListItem & {\n mrsfStartLine?: number;\n mrsfEndLine?: number;\n mrsfLine?: number;\n mrsfLineHighlight?: boolean;\n};\n\nconst markedRuntime = new Marked();\nconst BaseRenderer = markedRuntime.Renderer;\n\nconst blockquoteRenderer = BaseRenderer.prototype.blockquote;\nconst codeRenderer = BaseRenderer.prototype.code;\nconst headingRenderer = BaseRenderer.prototype.heading;\nconst hrRenderer = BaseRenderer.prototype.hr;\nconst listItemRenderer = BaseRenderer.prototype.listitem;\nconst paragraphRenderer = BaseRenderer.prototype.paragraph;\nconst tableRenderer = BaseRenderer.prototype.table;\n\nfunction countLines(value: string): number {\n if (!value) return 1;\n return value.split(\"\\n\").length;\n}\n\nfunction trimTrailingBlankLines(value: string): string {\n return value.replace(/\\n+$/g, \"\");\n}\n\nfunction firstCommentLine(lineMap: LineMap, startLine: number, endLine: number): number {\n for (let line = startLine; line <= endLine; line++) {\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n return line;\n }\n }\n return startLine;\n}\n\nfunction hasCommentInRange(lineMap: LineMap, startLine: number, endLine: number): boolean {\n for (let line = startLine; line <= endLine; line++) {\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n return true;\n }\n }\n return false;\n}\n\nfunction stampBlock(token: TokenWithMrsf, startLine: number, lineMap: LineMap): number {\n const raw = typeof token.raw === \"string\" ? token.raw : \"\";\n const semantic = trimTrailingBlankLines(raw);\n const endLine = startLine + countLines(semantic) - 1;\n\n token.mrsfStartLine = startLine;\n token.mrsfEndLine = endLine;\n token.mrsfLine = firstCommentLine(lineMap, startLine, endLine);\n token.mrsfLineHighlight = hasCommentInRange(lineMap, startLine, endLine);\n\n if (token.type === \"table\") {\n const tableToken = token as Tokens.Table & TokenWithMrsf;\n tableToken.mrsfHeaderLine = startLine;\n tableToken.mrsfRowLines = tableToken.rows.map((_, index) => startLine + 2 + index);\n }\n\n return startLine + countLines(raw) - 1;\n}\n\nfunction stampListItems(token: Tokens.List & TokenWithMrsf, lineMap: LineMap): void {\n let currentLine = token.mrsfStartLine ?? 1;\n\n for (const item of token.items as ListItemWithMrsf[]) {\n const raw = typeof item.raw === \"string\" ? item.raw : \"\";\n const semantic = trimTrailingBlankLines(raw);\n const endLine = currentLine + countLines(semantic) - 1;\n item.mrsfStartLine = currentLine;\n item.mrsfEndLine = endLine;\n item.mrsfLine = firstCommentLine(lineMap, currentLine, endLine);\n item.mrsfLineHighlight = hasCommentInRange(lineMap, currentLine, endLine);\n\n stampTokens(item.tokens as TokenWithMrsf[], currentLine, lineMap);\n currentLine += countLines(raw) - 1;\n }\n}\n\nfunction stampTokens(tokens: TokenWithMrsf[], startLine: number, lineMap: LineMap): number {\n let currentLine = startLine;\n\n for (const token of tokens) {\n currentLine = stampBlock(token, currentLine, lineMap);\n\n if (token.type === \"blockquote\") {\n stampTokens((token as Tokens.Blockquote).tokens as TokenWithMrsf[], token.mrsfStartLine ?? currentLine, lineMap);\n } else if (token.type === \"list\") {\n stampListItems(token as Tokens.List & TokenWithMrsf, lineMap);\n }\n }\n\n return currentLine;\n}\n\nfunction escapeAttribute(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n}\n\nfunction buildAttrs(token: { mrsfStartLine?: number; mrsfEndLine?: number; mrsfLine?: number; mrsfLineHighlight?: boolean }, lineHighlight: boolean): string {\n if (token.mrsfStartLine == null || token.mrsfEndLine == null || token.mrsfLine == null) {\n return \"\";\n }\n\n const attrs = [\n `data-mrsf-line=\"${token.mrsfLine}\"`,\n `data-mrsf-start-line=\"${token.mrsfStartLine}\"`,\n `data-mrsf-end-line=\"${token.mrsfEndLine}\"`,\n ];\n\n if (lineHighlight && token.mrsfLineHighlight) {\n attrs.push('class=\"mrsf-line-highlight\"');\n }\n\n return attrs.length > 0 ? \" \" + attrs.join(\" \") : \"\";\n}\n\nfunction addAttrs(html: string, attrs: string, tagName?: string): string {\n if (!attrs) return html;\n const pattern = tagName\n ? new RegExp(`^<${tagName}(?=[\\\\s>])`, \"i\")\n : /^<([a-z0-9-]+)(?=[\\s>])/i;\n return html.replace(pattern, (match) => match + attrs);\n}\n\nfunction flattenThreads(lineMap: LineMap): CommentThread[] {\n const threads: CommentThread[] = [];\n for (const lineThreads of lineMap.values()) {\n threads.push(...lineThreads);\n }\n return threads;\n}\n\nfunction createDataContainer(options: MrsfPluginOptions, threads: CommentThread[]): string {\n const payload = JSON.stringify({ threads });\n if (options.dataContainer === \"element\") {\n const elementId = options.dataElementId || \"mrsf-comment-data\";\n return `<div id=\"${escapeAttribute(elementId)}\" data-mrsf-json=\"${escapeAttribute(payload)}\" aria-hidden=\"true\"></div>`;\n }\n\n return `<script type=\"application/mrsf+json\">${payload.replace(/</g, \"\\\\u003c\")}</script>`;\n}\n\nfunction renderAnnotatedTable(\n this: { parser: { parseInline: (tokens: Token[]) => string } },\n token: Tokens.Table & TokenWithMrsf,\n lineMap: LineMap,\n lineHighlight: boolean,\n): string {\n const baseHtml = tableRenderer.call(this, token);\n const rowLines = [token.mrsfHeaderLine, ...(token.mrsfRowLines ?? [])];\n let rowIndex = 0;\n\n return baseHtml.replace(/<tr>/g, () => {\n const line = rowLines[rowIndex++];\n if (line == null) return \"<tr>\";\n\n const hasComment = hasCommentInRange(lineMap, line, line);\n\n const attrs = [\n `data-mrsf-line=\"${line}\"`,\n `data-mrsf-start-line=\"${line}\"`,\n `data-mrsf-end-line=\"${line}\"`,\n ];\n if (lineHighlight && hasComment) {\n attrs.push('class=\"mrsf-line-highlight\"');\n }\n return `<tr ${attrs.join(\" \")}>`;\n });\n}\n\nexport function createMarkedMrsf(loader: CommentLoader) {\n return function markedMrsf(options: MrsfPluginOptions = {}): MarkedExtension {\n let currentLineMap: LineMap | null = null;\n let currentThreads: CommentThread[] = [];\n\n return {\n hooks: {\n processAllTokens(tokens) {\n const result = resolveComments(loader, options);\n if (!result) {\n currentLineMap = null;\n currentThreads = [];\n return tokens;\n }\n\n currentLineMap = result.lineMap;\n currentThreads = flattenThreads(result.lineMap);\n stampTokens(tokens as TokenWithMrsf[], 1, result.lineMap);\n return tokens;\n },\n postprocess(html) {\n if (!currentLineMap || currentThreads.length === 0) {\n return html;\n }\n return html + createDataContainer(options, currentThreads);\n },\n },\n renderer: {\n heading(token) {\n return addAttrs(headingRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), `h${token.depth}`);\n },\n paragraph(token) {\n return addAttrs(paragraphRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"p\");\n },\n code(token) {\n return addAttrs(codeRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"pre\");\n },\n blockquote(token) {\n return addAttrs(blockquoteRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"blockquote\");\n },\n listitem(token) {\n return addAttrs(listItemRenderer.call(this, token), buildAttrs(token as ListItemWithMrsf, options.lineHighlight ?? false), \"li\");\n },\n hr(token) {\n return addAttrs(hrRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"hr\");\n },\n table(token) {\n return renderAnnotatedTable.call(\n this,\n token as Tokens.Table & TokenWithMrsf,\n currentLineMap ?? new Map(),\n options.lineHighlight ?? false,\n );\n },\n },\n };\n };\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 env?: unknown,\n): { lineMap: LineMap; comments: SlimComment[] } | null {\n const showResolved = options.showResolved ?? true;\n\n const doc = loader(options, env);\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 * Browser-safe entry point for @mrsf/marked-mrsf.\n */\n\nimport type { MrsfPluginOptions } from \"./types.js\";\nimport { createMarkedMrsf } from \"./shared.js\";\n\nexport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\nexport const markedMrsf = createMarkedMrsf((options: MrsfPluginOptions, env?: unknown) => {\n if (options.comments) {\n return options.comments;\n }\n\n if (options.loader) {\n try {\n return options.loader(options, env);\n } catch {\n return null;\n }\n }\n\n if (options.sidecarPath || options.documentPath) {\n console.warn(\n \"[@mrsf/marked-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 markedMrsf;"],
|
|
5
|
+
"mappings": ";AAIA,SAAS,cAA6D;;;ACQhE,SAAU,eAAe,KAAiB;AAC9C,SAAO,IAAI,SAAS,IAAI,CAAC,OAAO;IAC9B,IAAI,EAAE;IACN,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE,YAAY;IACxB,cAAc,EAAE,gBAAgB;IAChC,YAAY,EAAE,cAAc;IAC5B,eAAe,EAAE,iBAAiB;IAClC,UAAU,CAAC,CAAC,EAAE;IACd,UAAU,EAAE,YAAY;IACxB,UAAU,EAAE,YAAY;IACxB,MAAM,EAAE,QAAQ;IAChB,WAAW,EAAE,aAAa;IAC1B;AACJ;AAKM,SAAU,YAAY,UAAuB;AACjD,QAAM,eAAe,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,QAAQ,IAAI;AACzE,QAAM,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,QAAQ;AAEjD,QAAM,WAAW,oBAAI,IAAG;AACxB,aAAW,KAAK,SAAS;AACvB,UAAM,OAAO,SAAS,IAAI,EAAE,QAAS,KAAK,CAAA;AAC1C,SAAK,KAAK,CAAC;AACX,aAAS,IAAI,EAAE,UAAW,IAAI;EAChC;AAEA,QAAM,UAAmB,oBAAI,IAAG;AAChC,aAAW,KAAK,cAAc;AAC5B,UAAM,OAAO,EAAE;AACf,UAAM,UAAU,QAAQ,IAAI,IAAI,KAAK,CAAA;AACrC,YAAQ,KAAK;MACX,SAAS;MACT,SAAS,SAAS,IAAI,EAAE,EAAE,KAAK,CAAA;KAChC;AACD,YAAQ,IAAI,MAAM,OAAO;EAC3B;AAEA,SAAO;AACT;AAMM,SAAU,gBACd,QACA,SACA,KAAa;AAEb,QAAM,eAAe,QAAQ,gBAAgB;AAE7C,QAAM,MAAM,OAAO,SAAS,GAAG;AAC/B,MAAI,CAAC,OAAO,CAAC,IAAI,YAAY,IAAI,SAAS,WAAW,GAAG;AACtD,WAAO;EACT;AAEA,MAAI,WAAW,eAAe,GAAG;AACjC,MAAI,CAAC,cAAc;AACjB,eAAW,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ;EAC/C;AAEA,MAAI,SAAS,WAAW;AAAG,WAAO;AAElC,QAAM,UAAU,YAAY,QAAQ;AACpC,SAAO,EAAE,SAAS,SAAQ;AAC5B;;;AD3DA,IAAM,gBAAgB,IAAI,OAAO;AACjC,IAAM,eAAe,cAAc;AAEnC,IAAM,qBAAqB,aAAa,UAAU;AAClD,IAAM,eAAe,aAAa,UAAU;AAC5C,IAAM,kBAAkB,aAAa,UAAU;AAC/C,IAAM,aAAa,aAAa,UAAU;AAC1C,IAAM,mBAAmB,aAAa,UAAU;AAChD,IAAM,oBAAoB,aAAa,UAAU;AACjD,IAAM,gBAAgB,aAAa,UAAU;AAE7C,SAAS,WAAW,OAAuB;AACzC,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,MAAM,MAAM,IAAI,EAAE;AAC3B;AAEA,SAAS,uBAAuB,OAAuB;AACrD,SAAO,MAAM,QAAQ,SAAS,EAAE;AAClC;AAEA,SAAS,iBAAiB,SAAkB,WAAmB,SAAyB;AACtF,WAAS,OAAO,WAAW,QAAQ,SAAS,QAAQ;AAClD,UAAM,UAAU,QAAQ,IAAI,IAAI;AAChC,QAAI,WAAW,QAAQ,SAAS,GAAG;AACjC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,SAAkB,WAAmB,SAA0B;AACxF,WAAS,OAAO,WAAW,QAAQ,SAAS,QAAQ;AAClD,UAAM,UAAU,QAAQ,IAAI,IAAI;AAChC,QAAI,WAAW,QAAQ,SAAS,GAAG;AACjC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,WAAW,OAAsB,WAAmB,SAA0B;AACrF,QAAM,MAAM,OAAO,MAAM,QAAQ,WAAW,MAAM,MAAM;AACxD,QAAM,WAAW,uBAAuB,GAAG;AAC3C,QAAM,UAAU,YAAY,WAAW,QAAQ,IAAI;AAEnD,QAAM,gBAAgB;AACtB,QAAM,cAAc;AACpB,QAAM,WAAW,iBAAiB,SAAS,WAAW,OAAO;AAC7D,QAAM,oBAAoB,kBAAkB,SAAS,WAAW,OAAO;AAEvE,MAAI,MAAM,SAAS,SAAS;AAC1B,UAAM,aAAa;AACnB,eAAW,iBAAiB;AAC5B,eAAW,eAAe,WAAW,KAAK,IAAI,CAAC,GAAG,UAAU,YAAY,IAAI,KAAK;AAAA,EACnF;AAEA,SAAO,YAAY,WAAW,GAAG,IAAI;AACvC;AAEA,SAAS,eAAe,OAAoC,SAAwB;AAClF,MAAI,cAAc,MAAM,iBAAiB;AAEzC,aAAW,QAAQ,MAAM,OAA6B;AACpD,UAAM,MAAM,OAAO,KAAK,QAAQ,WAAW,KAAK,MAAM;AACtD,UAAM,WAAW,uBAAuB,GAAG;AAC3C,UAAM,UAAU,cAAc,WAAW,QAAQ,IAAI;AACrD,SAAK,gBAAgB;AACrB,SAAK,cAAc;AACnB,SAAK,WAAW,iBAAiB,SAAS,aAAa,OAAO;AAC9D,SAAK,oBAAoB,kBAAkB,SAAS,aAAa,OAAO;AAExE,gBAAY,KAAK,QAA2B,aAAa,OAAO;AAChE,mBAAe,WAAW,GAAG,IAAI;AAAA,EACnC;AACF;AAEA,SAAS,YAAY,QAAyB,WAAmB,SAA0B;AACzF,MAAI,cAAc;AAElB,aAAW,SAAS,QAAQ;AAC1B,kBAAc,WAAW,OAAO,aAAa,OAAO;AAEpD,QAAI,MAAM,SAAS,cAAc;AAC/B,kBAAa,MAA4B,QAA2B,MAAM,iBAAiB,aAAa,OAAO;AAAA,IACjH,WAAW,MAAM,SAAS,QAAQ;AAChC,qBAAe,OAAsC,OAAO;AAAA,IAC9D;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,gBAAgB,OAAuB;AAC9C,SAAO,MACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ;AAC3B;AAEA,SAAS,WAAW,OAAyG,eAAgC;AAC3J,MAAI,MAAM,iBAAiB,QAAQ,MAAM,eAAe,QAAQ,MAAM,YAAY,MAAM;AACtF,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ;AAAA,IACZ,mBAAmB,MAAM,QAAQ;AAAA,IACjC,yBAAyB,MAAM,aAAa;AAAA,IAC5C,uBAAuB,MAAM,WAAW;AAAA,EAC1C;AAEA,MAAI,iBAAiB,MAAM,mBAAmB;AAC5C,UAAM,KAAK,6BAA6B;AAAA,EAC1C;AAEA,SAAO,MAAM,SAAS,IAAI,MAAM,MAAM,KAAK,GAAG,IAAI;AACpD;AAEA,SAAS,SAAS,MAAc,OAAe,SAA0B;AACvE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,UAAU,UACZ,IAAI,OAAO,KAAK,OAAO,cAAc,GAAG,IACxC;AACJ,SAAO,KAAK,QAAQ,SAAS,CAAC,UAAU,QAAQ,KAAK;AACvD;AAEA,SAAS,eAAe,SAAmC;AACzD,QAAM,UAA2B,CAAC;AAClC,aAAW,eAAe,QAAQ,OAAO,GAAG;AAC1C,YAAQ,KAAK,GAAG,WAAW;AAAA,EAC7B;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,SAA4B,SAAkC;AACzF,QAAM,UAAU,KAAK,UAAU,EAAE,QAAQ,CAAC;AAC1C,MAAI,QAAQ,kBAAkB,WAAW;AACvC,UAAM,YAAY,QAAQ,iBAAiB;AAC3C,WAAO,YAAY,gBAAgB,SAAS,CAAC,qBAAqB,gBAAgB,OAAO,CAAC;AAAA,EAC5F;AAEA,SAAO,wCAAwC,QAAQ,QAAQ,MAAM,SAAS,CAAC;AACjF;AAEA,SAAS,qBAEP,OACA,SACA,eACQ;AACR,QAAM,WAAW,cAAc,KAAK,MAAM,KAAK;AAC/C,QAAM,WAAW,CAAC,MAAM,gBAAgB,GAAI,MAAM,gBAAgB,CAAC,CAAE;AACrE,MAAI,WAAW;AAEf,SAAO,SAAS,QAAQ,SAAS,MAAM;AACrC,UAAM,OAAO,SAAS,UAAU;AAChC,QAAI,QAAQ,KAAM,QAAO;AAEzB,UAAM,aAAa,kBAAkB,SAAS,MAAM,IAAI;AAExD,UAAM,QAAQ;AAAA,MACZ,mBAAmB,IAAI;AAAA,MACvB,yBAAyB,IAAI;AAAA,MAC7B,uBAAuB,IAAI;AAAA,IAC7B;AACA,QAAI,iBAAiB,YAAY;AAC/B,YAAM,KAAK,6BAA6B;AAAA,IAC1C;AACA,WAAO,OAAO,MAAM,KAAK,GAAG,CAAC;AAAA,EAC/B,CAAC;AACH;AAEO,SAAS,iBAAiB,QAAuB;AACtD,SAAO,SAASA,YAAW,UAA6B,CAAC,GAAoB;AAC3E,QAAI,iBAAiC;AACrC,QAAI,iBAAkC,CAAC;AAEvC,WAAO;AAAA,MACL,OAAO;AAAA,QACL,iBAAiB,QAAQ;AACvB,gBAAM,SAAS,gBAAgB,QAAQ,OAAO;AAC9C,cAAI,CAAC,QAAQ;AACX,6BAAiB;AACjB,6BAAiB,CAAC;AAClB,mBAAO;AAAA,UACT;AAEA,2BAAiB,OAAO;AACxB,2BAAiB,eAAe,OAAO,OAAO;AAC9C,sBAAY,QAA2B,GAAG,OAAO,OAAO;AACxD,iBAAO;AAAA,QACT;AAAA,QACA,YAAY,MAAM;AAChB,cAAI,CAAC,kBAAkB,eAAe,WAAW,GAAG;AAClD,mBAAO;AAAA,UACT;AACA,iBAAO,OAAO,oBAAoB,SAAS,cAAc;AAAA,QAC3D;AAAA,MACF;AAAA,MACA,UAAU;AAAA,QACR,QAAQ,OAAO;AACb,iBAAO,SAAS,gBAAgB,KAAK,MAAM,KAAK,GAAG,WAAW,OAAwB,QAAQ,iBAAiB,KAAK,GAAG,IAAI,MAAM,KAAK,EAAE;AAAA,QAC1I;AAAA,QACA,UAAU,OAAO;AACf,iBAAO,SAAS,kBAAkB,KAAK,MAAM,KAAK,GAAG,WAAW,OAAwB,QAAQ,iBAAiB,KAAK,GAAG,GAAG;AAAA,QAC9H;AAAA,QACA,KAAK,OAAO;AACV,iBAAO,SAAS,aAAa,KAAK,MAAM,KAAK,GAAG,WAAW,OAAwB,QAAQ,iBAAiB,KAAK,GAAG,KAAK;AAAA,QAC3H;AAAA,QACA,WAAW,OAAO;AAChB,iBAAO,SAAS,mBAAmB,KAAK,MAAM,KAAK,GAAG,WAAW,OAAwB,QAAQ,iBAAiB,KAAK,GAAG,YAAY;AAAA,QACxI;AAAA,QACA,SAAS,OAAO;AACd,iBAAO,SAAS,iBAAiB,KAAK,MAAM,KAAK,GAAG,WAAW,OAA2B,QAAQ,iBAAiB,KAAK,GAAG,IAAI;AAAA,QACjI;AAAA,QACA,GAAG,OAAO;AACR,iBAAO,SAAS,WAAW,KAAK,MAAM,KAAK,GAAG,WAAW,OAAwB,QAAQ,iBAAiB,KAAK,GAAG,IAAI;AAAA,QACxH;AAAA,QACA,MAAM,OAAO;AACX,iBAAO,qBAAqB;AAAA,YAC1B;AAAA,YACA;AAAA,YACA,kBAAkB,oBAAI,IAAI;AAAA,YAC1B,QAAQ,iBAAiB;AAAA,UAC3B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AEpPO,IAAM,aAAa,iBAAiB,CAAC,SAA4B,QAAkB;AACxF,MAAI,QAAQ,UAAU;AACpB,WAAO,QAAQ;AAAA,EACjB;AAEA,MAAI,QAAQ,QAAQ;AAClB,QAAI;AACF,aAAO,QAAQ,OAAO,SAAS,GAAG;AAAA,IACpC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI,QAAQ,eAAe,QAAQ,cAAc;AAC/C,YAAQ;AAAA,MACN;AAAA,IAEF;AAAA,EACF;AAEA,SAAO;AACT,CAAC;AAED,IAAO,kBAAQ;",
|
|
6
|
+
"names": ["markedMrsf"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidemark — MrsfController: overlay gutter architecture.
|
|
3
|
+
*
|
|
4
|
+
* The controller is the runtime engine for interactive MRSF rendering.
|
|
5
|
+
* It reads comment data from the DOM (embedded `<script type="application/mrsf+json">`)
|
|
6
|
+
* or constructor options, creates gutter overlay columns, positions
|
|
7
|
+
* badges/buttons at the correct vertical offsets, and handles all
|
|
8
|
+
* user interactions (tooltips, selection, action buttons).
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { MrsfController } from "@mrsf/plugin-shared/controller";
|
|
12
|
+
*
|
|
13
|
+
* const ctrl = new MrsfController(document.querySelector(".my-content")!, {
|
|
14
|
+
* interactive: true,
|
|
15
|
+
* gutterPosition: "left",
|
|
16
|
+
* });
|
|
17
|
+
* // ctrl.destroy() to clean up
|
|
18
|
+
*
|
|
19
|
+
* Events dispatched on document:
|
|
20
|
+
* - mrsf:resolve { commentId, line, ... }
|
|
21
|
+
* - mrsf:unresolve { commentId, line, ... }
|
|
22
|
+
* - mrsf:reply { commentId, line, ... }
|
|
23
|
+
* - mrsf:edit { commentId, line, ... }
|
|
24
|
+
* - mrsf:delete { commentId, line, ... }
|
|
25
|
+
* - mrsf:navigate { commentId, line, ... }
|
|
26
|
+
* - mrsf:add { commentId: null, line, selectionText?, ... }
|
|
27
|
+
* - mrsf:submit { action, commentId, text?, line?, ... }
|
|
28
|
+
*/
|
|
29
|
+
import type { CommentThread } from "./types.js";
|
|
30
|
+
export type MrsfAction = "resolve" | "unresolve" | "reply" | "edit" | "delete" | "navigate" | "add";
|
|
31
|
+
export interface MrsfActionDetail {
|
|
32
|
+
commentId: string | null;
|
|
33
|
+
line: number | null;
|
|
34
|
+
action: MrsfAction;
|
|
35
|
+
selectionText?: string | null;
|
|
36
|
+
start_line?: number | null;
|
|
37
|
+
end_line?: number | null;
|
|
38
|
+
start_column?: number | null;
|
|
39
|
+
end_column?: number | null;
|
|
40
|
+
}
|
|
41
|
+
export interface MrsfSubmitDetail {
|
|
42
|
+
action: "add" | "edit" | "reply" | "resolve" | "unresolve" | "delete";
|
|
43
|
+
commentId: string | null;
|
|
44
|
+
text: string;
|
|
45
|
+
type?: string | null;
|
|
46
|
+
severity?: "low" | "medium" | "high" | null;
|
|
47
|
+
line?: number | null;
|
|
48
|
+
end_line?: number | null;
|
|
49
|
+
start_column?: number | null;
|
|
50
|
+
end_column?: number | null;
|
|
51
|
+
selection_text?: string | null;
|
|
52
|
+
}
|
|
53
|
+
export interface MrsfControllerOptions {
|
|
54
|
+
/** Show gutter on left or right side. Default: "right". */
|
|
55
|
+
gutterPosition?: "left" | "right";
|
|
56
|
+
/** Enable interactive actions (add, resolve, reply, etc.). Default: false. */
|
|
57
|
+
interactive?: boolean;
|
|
58
|
+
/** Comment data passed directly (overrides embedded script). */
|
|
59
|
+
comments?: CommentThread[];
|
|
60
|
+
/**
|
|
61
|
+
* Render inline text highlights for comments that have `selected_text`.
|
|
62
|
+
* Wraps matching text in `<mark>` elements with hover tooltips.
|
|
63
|
+
* Default: true.
|
|
64
|
+
*/
|
|
65
|
+
inlineHighlights?: boolean;
|
|
66
|
+
}
|
|
67
|
+
export declare class MrsfController {
|
|
68
|
+
private container;
|
|
69
|
+
private opts;
|
|
70
|
+
private threads;
|
|
71
|
+
private gutterLeft;
|
|
72
|
+
private gutterRight;
|
|
73
|
+
private activeTooltip;
|
|
74
|
+
private floatingAddButton;
|
|
75
|
+
private overlayEl;
|
|
76
|
+
private lastSelectionText;
|
|
77
|
+
private resizeObserver;
|
|
78
|
+
private mutationObserver;
|
|
79
|
+
private styleInjected;
|
|
80
|
+
private inlineMarks;
|
|
81
|
+
private inlineTooltipEl;
|
|
82
|
+
private orphanedSection;
|
|
83
|
+
private refreshQueued;
|
|
84
|
+
private handleResizeBound;
|
|
85
|
+
private handleMutationBound;
|
|
86
|
+
private handleClickBound;
|
|
87
|
+
private handleSelectionBound;
|
|
88
|
+
constructor(container: HTMLElement, options?: MrsfControllerOptions);
|
|
89
|
+
/** Remove all controller DOM and listeners. */
|
|
90
|
+
destroy(): void;
|
|
91
|
+
/** Recalculate gutter positions after async layout changes such as Mermaid renders. */
|
|
92
|
+
refresh(): void;
|
|
93
|
+
private loadCommentData;
|
|
94
|
+
private buildThreadMap;
|
|
95
|
+
private findCommentById;
|
|
96
|
+
private orderThreadsForDisplay;
|
|
97
|
+
private setupOverlayStructure;
|
|
98
|
+
private createGutter;
|
|
99
|
+
/** Build badge/add-button elements for each line in the gutter(s). */
|
|
100
|
+
private renderGutterItems;
|
|
101
|
+
private primaryGutter;
|
|
102
|
+
private shouldExpandRange;
|
|
103
|
+
private addVisibleLinesForElement;
|
|
104
|
+
/** Collect all unique line numbers from data-mrsf-line elements, expanding multi-line ranges. */
|
|
105
|
+
private collectLines;
|
|
106
|
+
private createBadgeItem;
|
|
107
|
+
private createAddItem;
|
|
108
|
+
private createAddButton;
|
|
109
|
+
/** Measure [data-mrsf-line] elements and set gutter item Y offsets. */
|
|
110
|
+
positionGutterItems(): void;
|
|
111
|
+
private suppressAddItemsThatShareBadgeTargets;
|
|
112
|
+
private handleMutations;
|
|
113
|
+
private isExternalContentMutation;
|
|
114
|
+
private isControllerOwnedNode;
|
|
115
|
+
private queueRefresh;
|
|
116
|
+
private findDirectElementForLine;
|
|
117
|
+
private calculateItemTop;
|
|
118
|
+
/**
|
|
119
|
+
* Find the element whose start-line/end-line range contains the given line.
|
|
120
|
+
* Used for positioning gutter items on expanded multi-line elements.
|
|
121
|
+
*/
|
|
122
|
+
private findElementForLine;
|
|
123
|
+
/**
|
|
124
|
+
* Render orphaned comments (whose line doesn't match any DOM element)
|
|
125
|
+
* in a dedicated section at the bottom of the container.
|
|
126
|
+
*/
|
|
127
|
+
private renderOrphanedSection;
|
|
128
|
+
/**
|
|
129
|
+
* For comments with `selected_text`, find the matching text in the DOM
|
|
130
|
+
* and wrap it in a `<mark class="mrsf-inline-highlight">` element with
|
|
131
|
+
* hover/click behaviour to show the comment tooltip.
|
|
132
|
+
*/
|
|
133
|
+
private renderInlineHighlights;
|
|
134
|
+
/**
|
|
135
|
+
* Strip common inline markdown syntax so `selected_text` from source
|
|
136
|
+
* can be matched against rendered text content.
|
|
137
|
+
*/
|
|
138
|
+
private static stripInlineMarkdown;
|
|
139
|
+
/**
|
|
140
|
+
* Walk text nodes inside `root` to find `text`, then wrap the matching
|
|
141
|
+
* range in a `<mark>` element. Falls back to markdown-stripped matching.
|
|
142
|
+
*/
|
|
143
|
+
private wrapSelectedText;
|
|
144
|
+
private showInlineTooltip;
|
|
145
|
+
private applyThemeVariables;
|
|
146
|
+
private hideInlineTimeout;
|
|
147
|
+
private scheduleHideInlineTooltip;
|
|
148
|
+
private cancelHideInlineTooltip;
|
|
149
|
+
private hideInlineTooltip;
|
|
150
|
+
/** Remove all inline marks, unwrapping their contents back to text. */
|
|
151
|
+
private removeInlineHighlights;
|
|
152
|
+
private toggleTooltip;
|
|
153
|
+
private showTooltip;
|
|
154
|
+
private hideTooltip;
|
|
155
|
+
private handleClick;
|
|
156
|
+
private handleSelectionChange;
|
|
157
|
+
private selectionBelongsToContainer;
|
|
158
|
+
private findSelectionAnchor;
|
|
159
|
+
private ensureFloatingAddButton;
|
|
160
|
+
private hideFloatingAddButton;
|
|
161
|
+
private showFloatingAddButton;
|
|
162
|
+
private injectStyles;
|
|
163
|
+
private closeOverlay;
|
|
164
|
+
private openForm;
|
|
165
|
+
private openConfirm;
|
|
166
|
+
}
|
|
167
|
+
export declare function refreshAll(): void;
|
|
168
|
+
export declare function autoInit(): void;
|
|
169
|
+
//# sourceMappingURL=controller.d.ts.map
|