@mrsf/markdown-it-mrsf 0.5.1 → 0.5.2
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 -21
- package/README.md +215 -215
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js.map +1 -1
- package/dist/controller.cjs.map +1 -1
- package/dist/controller.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/style.css +522 -522
- package/package.json +96 -96
package/LICENSE
CHANGED
|
@@ -1,21 +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.
|
|
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
CHANGED
|
@@ -1,215 +1,215 @@
|
|
|
1
|
-
# @mrsf/markdown-it-mrsf
|
|
2
|
-
|
|
3
|
-
A [markdown-it](https://github.com/markdown-it/markdown-it) plugin for rendering [MRSF (Sidemark)](https://github.com/wictorwilen/MRSF) review comments directly into HTML output.
|
|
4
|
-
|
|
5
|
-
Renders badges, line highlights, inline text highlights, and tooltips — no client-side JavaScript required for static use. An optional interactive mode adds action buttons for host applications to hook into.
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install @mrsf/markdown-it-mrsf
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Browser & Bundler Support
|
|
14
|
-
|
|
15
|
-
The plugin works seamlessly in both Node.js and browser environments. When bundled with tools like **Vite**, **webpack**, or **esbuild**, the `"browser"` export condition in `package.json` automatically resolves to a browser-safe entry point — no special imports required.
|
|
16
|
-
|
|
17
|
-
```ts
|
|
18
|
-
// Works everywhere — Node.js, Vite, webpack, etc.
|
|
19
|
-
import { mrsfPlugin } from "@mrsf/markdown-it-mrsf";
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
In browser environments, only the `comments` and `loader` options are available (no file-system access). The `sidecarPath` and `documentPath` options require Node.js.
|
|
23
|
-
|
|
24
|
-
## Usage
|
|
25
|
-
|
|
26
|
-
### Inline data (recommended for testing and custom pipelines)
|
|
27
|
-
|
|
28
|
-
```ts
|
|
29
|
-
import MarkdownIt from "markdown-it";
|
|
30
|
-
import { mrsfPlugin } from "@mrsf/markdown-it-mrsf";
|
|
31
|
-
|
|
32
|
-
const md = new MarkdownIt();
|
|
33
|
-
md.use(mrsfPlugin, {
|
|
34
|
-
comments: {
|
|
35
|
-
mrsf_version: "1.0",
|
|
36
|
-
document: "guide.md",
|
|
37
|
-
comments: [
|
|
38
|
-
{
|
|
39
|
-
id: "c1",
|
|
40
|
-
author: "Jane",
|
|
41
|
-
timestamp: "2026-01-01T00:00:00Z",
|
|
42
|
-
text: "Can you clarify this section?",
|
|
43
|
-
resolved: false,
|
|
44
|
-
line: 3,
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const html = md.render(markdownSource);
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Auto-discover from disk
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
md.use(mrsfPlugin, {
|
|
57
|
-
documentPath: "docs/guide.md",
|
|
58
|
-
});
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
The plugin looks for `docs/guide.md.review.yaml` (or `.review.json`) next to the document.
|
|
62
|
-
|
|
63
|
-
### Explicit sidecar path
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
md.use(mrsfPlugin, {
|
|
67
|
-
sidecarPath: ".reviews/docs/guide.md.review.yaml",
|
|
68
|
-
});
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Custom loader function
|
|
72
|
-
|
|
73
|
-
```ts
|
|
74
|
-
md.use(mrsfPlugin, {
|
|
75
|
-
loader: () => {
|
|
76
|
-
// Load sidecar data however you like — from a database, API, in-memory store, etc.
|
|
77
|
-
return {
|
|
78
|
-
mrsf_version: "1.0",
|
|
79
|
-
document: "guide.md",
|
|
80
|
-
comments: [/* ... */],
|
|
81
|
-
};
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## Stylesheet
|
|
87
|
-
|
|
88
|
-
Include the default CSS for badges, tooltips, and highlights:
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
import "@mrsf/markdown-it-mrsf/style.css";
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Or link directly in HTML:
|
|
95
|
-
|
|
96
|
-
```html
|
|
97
|
-
<link rel="stylesheet" href="node_modules/@mrsf/markdown-it-mrsf/dist/style.css">
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Customise with CSS custom properties:
|
|
101
|
-
|
|
102
|
-
```css
|
|
103
|
-
:root {
|
|
104
|
-
--mrsf-accent: #3794ff;
|
|
105
|
-
--mrsf-badge-bg: #007acc;
|
|
106
|
-
--mrsf-badge-fg: #fff;
|
|
107
|
-
--mrsf-tooltip-bg: #252526;
|
|
108
|
-
--mrsf-tooltip-fg: #cccccc;
|
|
109
|
-
--mrsf-highlight-bg: rgba(255, 213, 79, 0.25);
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## Interactive Mode
|
|
114
|
-
|
|
115
|
-
Enable `interactive: true` to add action buttons 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.
|
|
121
|
-
|
|
122
|
-
```ts
|
|
123
|
-
md.use(mrsfPlugin, {
|
|
124
|
-
comments: sidecarData,
|
|
125
|
-
interactive: true,
|
|
126
|
-
});
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Controller
|
|
130
|
-
|
|
131
|
-
Include the optional controller to wire up the inline and gutter actions. By default it shows built-in dialogs for add, reply, edit, resolve, unresolve, and delete, then dispatches events you can persist through your own API:
|
|
132
|
-
|
|
133
|
-
```ts
|
|
134
|
-
import { refreshAll } from "@mrsf/markdown-it-mrsf/controller";
|
|
135
|
-
|
|
136
|
-
document.addEventListener("mrsf:submit", async (e) => {
|
|
137
|
-
// { action, commentId, text?, line?, end_line?, start_column?, end_column?, selection_text? }
|
|
138
|
-
await saveComment(e.detail);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
await mermaid.run();
|
|
142
|
-
refreshAll();
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
Events fired after user confirmation: `mrsf:add`, `mrsf:reply`, `mrsf:edit`, `mrsf:resolve`, `mrsf:unresolve`, `mrsf:delete`, `mrsf:navigate`, plus `mrsf:submit` with the full payload.
|
|
146
|
-
|
|
147
|
-
Set `window.mrsfDisableBuiltinUi = true` before loading the controller if you want to render your own UI and only consume the events.
|
|
148
|
-
|
|
149
|
-
The controller also auto-refreshes gutter positions on DOM mutations inside the rendered container, which covers common async renderers such as Mermaid. Call `refreshAll()` after third-party rendering when you need a deterministic reposition step.
|
|
150
|
-
|
|
151
|
-
### Shared Gutter Render Hooks
|
|
152
|
-
|
|
153
|
-
The controller accepts `gutterRenderers` so hosts can override the shared badge and add-button presentation without forking plugin-specific UX:
|
|
154
|
-
|
|
155
|
-
```ts
|
|
156
|
-
import { MrsfController } from "@mrsf/markdown-it-mrsf/controller";
|
|
157
|
-
|
|
158
|
-
new MrsfController(document.querySelector(".markdown-body")!, {
|
|
159
|
-
interactive: true,
|
|
160
|
-
gutterRenderers: {
|
|
161
|
-
badge: ({ defaultPresentation }) => ({
|
|
162
|
-
label: `🗨 ${defaultPresentation.countText}`,
|
|
163
|
-
icon: "🗨",
|
|
164
|
-
countText: defaultPresentation.countText,
|
|
165
|
-
}),
|
|
166
|
-
addButton: () => ({
|
|
167
|
-
label: "New",
|
|
168
|
-
}),
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
The default renderer contract is shared with the Monaco integration, including count capping at `9+` and the standard tooltip/add-button labels.
|
|
174
|
-
|
|
175
|
-
## Options
|
|
176
|
-
|
|
177
|
-
| Option | Type | Default | Description |
|
|
178
|
-
|--------|------|---------|-------------|
|
|
179
|
-
| `comments` | `MrsfDocument` | — | Pre-loaded sidecar data (highest priority) |
|
|
180
|
-
| `loader` | `() => MrsfDocument \| null` | — | Custom loader function for sidecar data |
|
|
181
|
-
| `documentPath` | `string` | — | Path to Markdown file for auto-discovery |
|
|
182
|
-
| `sidecarPath` | `string` | — | Explicit path to sidecar file |
|
|
183
|
-
| `showResolved` | `boolean` | `true` | Whether to show resolved comments |
|
|
184
|
-
| `interactive` | `boolean` | `false` | Add action buttons for host JS integration |
|
|
185
|
-
| `gutterPosition` | `'left' \| 'right'` | `'right'` | Badge position: left margin gutter or floated right |
|
|
186
|
-
| `gutterForInline` | `boolean` | `true` | Show gutter badge for comments that also have inline highlights |
|
|
187
|
-
| `inlineHighlights` | `boolean` | `true` | Render inline text highlights for `selected_text` |
|
|
188
|
-
| `lineHighlight` | `boolean` | `false` | Add background tint on commented lines |
|
|
189
|
-
| `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Color scheme hint |
|
|
190
|
-
| `cwd` | `string` | `process.cwd()` | Working directory for file resolution |
|
|
191
|
-
|
|
192
|
-
### Display Modes
|
|
193
|
-
|
|
194
|
-
The plugin supports several display modes that can be combined:
|
|
195
|
-
|
|
196
|
-
- **Gutter + inline** (default): Badges appear in the gutter, `selected_text` is highlighted inline with hover tooltips on both.
|
|
197
|
-
- **Gutter only** (`inlineHighlights: false`): Only gutter badges shown, no inline highlights.
|
|
198
|
-
- **Inline only** (`gutterForInline: false`): Lines with inline-highlighted comments show only the highlight (with tooltip) — no gutter badge. Lines without `selected_text` still show badges.
|
|
199
|
-
|
|
200
|
-
## DOM Contract
|
|
201
|
-
|
|
202
|
-
All rendered elements follow a strict data-attribute contract:
|
|
203
|
-
|
|
204
|
-
- `data-mrsf-line` — source line number (1-based)
|
|
205
|
-
- `data-mrsf-comment-id` — comment identifier
|
|
206
|
-
- `data-mrsf-action` — action type (`add`, `resolve`, `unresolve`, `reply`, `edit`, `delete`, `navigate`)
|
|
207
|
-
- `data-mrsf-start-line`, `data-mrsf-end-line`, `data-mrsf-start-column`, `data-mrsf-end-column` — optional selection anchors when present
|
|
208
|
-
|
|
209
|
-
## Security / XSS
|
|
210
|
-
|
|
211
|
-
Per the MRSF specification, comment `text` is plain text, not HTML or Markdown. The renderer HTML-escapes comment `text`, `author`, `selected_text`, and `id` before emitting HTML, so untrusted comment content cannot inject markup through MRSF. Hosts should keep their Markdown pipeline's sanitization enabled for non-MRSF content.
|
|
212
|
-
|
|
213
|
-
## License
|
|
214
|
-
|
|
215
|
-
[MIT](../LICENSE)
|
|
1
|
+
# @mrsf/markdown-it-mrsf
|
|
2
|
+
|
|
3
|
+
A [markdown-it](https://github.com/markdown-it/markdown-it) plugin for rendering [MRSF (Sidemark)](https://github.com/wictorwilen/MRSF) review comments directly into HTML output.
|
|
4
|
+
|
|
5
|
+
Renders badges, line highlights, inline text highlights, and tooltips — no client-side JavaScript required for static use. An optional interactive mode adds action buttons for host applications to hook into.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @mrsf/markdown-it-mrsf
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Browser & Bundler Support
|
|
14
|
+
|
|
15
|
+
The plugin works seamlessly in both Node.js and browser environments. When bundled with tools like **Vite**, **webpack**, or **esbuild**, the `"browser"` export condition in `package.json` automatically resolves to a browser-safe entry point — no special imports required.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// Works everywhere — Node.js, Vite, webpack, etc.
|
|
19
|
+
import { mrsfPlugin } from "@mrsf/markdown-it-mrsf";
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
In browser environments, only the `comments` and `loader` options are available (no file-system access). The `sidecarPath` and `documentPath` options require Node.js.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### Inline data (recommended for testing and custom pipelines)
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import MarkdownIt from "markdown-it";
|
|
30
|
+
import { mrsfPlugin } from "@mrsf/markdown-it-mrsf";
|
|
31
|
+
|
|
32
|
+
const md = new MarkdownIt();
|
|
33
|
+
md.use(mrsfPlugin, {
|
|
34
|
+
comments: {
|
|
35
|
+
mrsf_version: "1.0",
|
|
36
|
+
document: "guide.md",
|
|
37
|
+
comments: [
|
|
38
|
+
{
|
|
39
|
+
id: "c1",
|
|
40
|
+
author: "Jane",
|
|
41
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
42
|
+
text: "Can you clarify this section?",
|
|
43
|
+
resolved: false,
|
|
44
|
+
line: 3,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const html = md.render(markdownSource);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Auto-discover from disk
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
md.use(mrsfPlugin, {
|
|
57
|
+
documentPath: "docs/guide.md",
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The plugin looks for `docs/guide.md.review.yaml` (or `.review.json`) next to the document.
|
|
62
|
+
|
|
63
|
+
### Explicit sidecar path
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
md.use(mrsfPlugin, {
|
|
67
|
+
sidecarPath: ".reviews/docs/guide.md.review.yaml",
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Custom loader function
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
md.use(mrsfPlugin, {
|
|
75
|
+
loader: () => {
|
|
76
|
+
// Load sidecar data however you like — from a database, API, in-memory store, etc.
|
|
77
|
+
return {
|
|
78
|
+
mrsf_version: "1.0",
|
|
79
|
+
document: "guide.md",
|
|
80
|
+
comments: [/* ... */],
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Stylesheet
|
|
87
|
+
|
|
88
|
+
Include the default CSS for badges, tooltips, and highlights:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import "@mrsf/markdown-it-mrsf/style.css";
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Or link directly in HTML:
|
|
95
|
+
|
|
96
|
+
```html
|
|
97
|
+
<link rel="stylesheet" href="node_modules/@mrsf/markdown-it-mrsf/dist/style.css">
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Customise with CSS custom properties:
|
|
101
|
+
|
|
102
|
+
```css
|
|
103
|
+
:root {
|
|
104
|
+
--mrsf-accent: #3794ff;
|
|
105
|
+
--mrsf-badge-bg: #007acc;
|
|
106
|
+
--mrsf-badge-fg: #fff;
|
|
107
|
+
--mrsf-tooltip-bg: #252526;
|
|
108
|
+
--mrsf-tooltip-fg: #cccccc;
|
|
109
|
+
--mrsf-highlight-bg: rgba(255, 213, 79, 0.25);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Interactive Mode
|
|
114
|
+
|
|
115
|
+
Enable `interactive: true` to add action buttons 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.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
md.use(mrsfPlugin, {
|
|
124
|
+
comments: sidecarData,
|
|
125
|
+
interactive: true,
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Controller
|
|
130
|
+
|
|
131
|
+
Include the optional controller to wire up the inline and gutter actions. By default it shows built-in dialogs for add, reply, edit, resolve, unresolve, and delete, then dispatches events you can persist through your own API:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { refreshAll } from "@mrsf/markdown-it-mrsf/controller";
|
|
135
|
+
|
|
136
|
+
document.addEventListener("mrsf:submit", async (e) => {
|
|
137
|
+
// { action, commentId, text?, line?, end_line?, start_column?, end_column?, selection_text? }
|
|
138
|
+
await saveComment(e.detail);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await mermaid.run();
|
|
142
|
+
refreshAll();
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Events fired after user confirmation: `mrsf:add`, `mrsf:reply`, `mrsf:edit`, `mrsf:resolve`, `mrsf:unresolve`, `mrsf:delete`, `mrsf:navigate`, plus `mrsf:submit` with the full payload.
|
|
146
|
+
|
|
147
|
+
Set `window.mrsfDisableBuiltinUi = true` before loading the controller if you want to render your own UI and only consume the events.
|
|
148
|
+
|
|
149
|
+
The controller also auto-refreshes gutter positions on DOM mutations inside the rendered container, which covers common async renderers such as Mermaid. Call `refreshAll()` after third-party rendering when you need a deterministic reposition step.
|
|
150
|
+
|
|
151
|
+
### Shared Gutter Render Hooks
|
|
152
|
+
|
|
153
|
+
The controller accepts `gutterRenderers` so hosts can override the shared badge and add-button presentation without forking plugin-specific UX:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { MrsfController } from "@mrsf/markdown-it-mrsf/controller";
|
|
157
|
+
|
|
158
|
+
new MrsfController(document.querySelector(".markdown-body")!, {
|
|
159
|
+
interactive: true,
|
|
160
|
+
gutterRenderers: {
|
|
161
|
+
badge: ({ defaultPresentation }) => ({
|
|
162
|
+
label: `🗨 ${defaultPresentation.countText}`,
|
|
163
|
+
icon: "🗨",
|
|
164
|
+
countText: defaultPresentation.countText,
|
|
165
|
+
}),
|
|
166
|
+
addButton: () => ({
|
|
167
|
+
label: "New",
|
|
168
|
+
}),
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The default renderer contract is shared with the Monaco integration, including count capping at `9+` and the standard tooltip/add-button labels.
|
|
174
|
+
|
|
175
|
+
## Options
|
|
176
|
+
|
|
177
|
+
| Option | Type | Default | Description |
|
|
178
|
+
|--------|------|---------|-------------|
|
|
179
|
+
| `comments` | `MrsfDocument` | — | Pre-loaded sidecar data (highest priority) |
|
|
180
|
+
| `loader` | `() => MrsfDocument \| null` | — | Custom loader function for sidecar data |
|
|
181
|
+
| `documentPath` | `string` | — | Path to Markdown file for auto-discovery |
|
|
182
|
+
| `sidecarPath` | `string` | — | Explicit path to sidecar file |
|
|
183
|
+
| `showResolved` | `boolean` | `true` | Whether to show resolved comments |
|
|
184
|
+
| `interactive` | `boolean` | `false` | Add action buttons for host JS integration |
|
|
185
|
+
| `gutterPosition` | `'left' \| 'right'` | `'right'` | Badge position: left margin gutter or floated right |
|
|
186
|
+
| `gutterForInline` | `boolean` | `true` | Show gutter badge for comments that also have inline highlights |
|
|
187
|
+
| `inlineHighlights` | `boolean` | `true` | Render inline text highlights for `selected_text` |
|
|
188
|
+
| `lineHighlight` | `boolean` | `false` | Add background tint on commented lines |
|
|
189
|
+
| `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Color scheme hint |
|
|
190
|
+
| `cwd` | `string` | `process.cwd()` | Working directory for file resolution |
|
|
191
|
+
|
|
192
|
+
### Display Modes
|
|
193
|
+
|
|
194
|
+
The plugin supports several display modes that can be combined:
|
|
195
|
+
|
|
196
|
+
- **Gutter + inline** (default): Badges appear in the gutter, `selected_text` is highlighted inline with hover tooltips on both.
|
|
197
|
+
- **Gutter only** (`inlineHighlights: false`): Only gutter badges shown, no inline highlights.
|
|
198
|
+
- **Inline only** (`gutterForInline: false`): Lines with inline-highlighted comments show only the highlight (with tooltip) — no gutter badge. Lines without `selected_text` still show badges.
|
|
199
|
+
|
|
200
|
+
## DOM Contract
|
|
201
|
+
|
|
202
|
+
All rendered elements follow a strict data-attribute contract:
|
|
203
|
+
|
|
204
|
+
- `data-mrsf-line` — source line number (1-based)
|
|
205
|
+
- `data-mrsf-comment-id` — comment identifier
|
|
206
|
+
- `data-mrsf-action` — action type (`add`, `resolve`, `unresolve`, `reply`, `edit`, `delete`, `navigate`)
|
|
207
|
+
- `data-mrsf-start-line`, `data-mrsf-end-line`, `data-mrsf-start-column`, `data-mrsf-end-column` — optional selection anchors when present
|
|
208
|
+
|
|
209
|
+
## Security / XSS
|
|
210
|
+
|
|
211
|
+
Per the MRSF specification, comment `text` is plain text, not HTML or Markdown. The renderer HTML-escapes comment `text`, `author`, `selected_text`, and `id` before emitting HTML, so untrusted comment content cannot inject markup through MRSF. Hosts should keep their Markdown pipeline's sanitization enabled for non-MRSF content.
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
[MIT](../LICENSE)
|
package/dist/browser.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/browser.ts", "../src/comments.ts", "../src/rules/core.ts", "../src/rules/renderer.ts", "../src/shared.ts"],
|
|
4
|
-
"sourcesContent": ["/**\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, 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/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", "/* ---------------------------------------------------------------\n * AUTO-GENERATED \u2014 DO NOT EDIT\n *\n * Source: plugins/shared/src/comments.ts\n * Run `node plugins/sync-types.mjs` to regenerate.\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\nfunction getExtensionFields(comment: Record<string, unknown>): Record<`x_${string}`, unknown> {\n const extensions: Record<`x_${string}`, unknown> = {};\n\n for (const [key, value] of Object.entries(comment)) {\n if (key.startsWith(\"x_\")) {\n extensions[key as `x_${string}`] = value;\n }\n }\n\n return extensions;\n}\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 ...getExtensionFields(c as Record<string, unknown>),\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 * MRSF markdown-it plugin \u2014 core rule (overlay gutter architecture).\n *\n * Walks the parsed token stream and annotates block tokens with\n * `data-mrsf-line` attributes. Adds `mrsf-line-highlight` class on\n * commented lines. Appends an `mrsf_data_script` token with serialized\n * comment data for the client-side MrsfController.\n *\n * NO visual tokens are injected \u2014 all badges, tooltips, and gutter\n * elements are created at runtime by the controller.\n */\n\nimport type Token from \"markdown-it/lib/token.mjs\";\nimport type { Nesting } from \"markdown-it/lib/token.mjs\";\nimport type StateCore from \"markdown-it/lib/rules_core/state_core.mjs\";\nimport type { LineMap, CommentThread, SlimComment } from \"../types.js\";\n\nexport interface CoreRuleOptions {\n /** Whether to add mrsf-line-highlight class on commented tokens. Default: false. */\n lineHighlight?: boolean;\n}\n\nexport interface ResolvedCommentData {\n lineMap: LineMap;\n comments: SlimComment[];\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 resolveComments: (state: StateCore) => ResolvedCommentData | null,\n options: CoreRuleOptions = {},\n): void {\n md.core.ruler.push(\"mrsf_inject\", (state: StateCore) => {\n const result = resolveComments(state);\n if (!result) return;\n\n const { lineMap } = result;\n const tokens = state.tokens;\n const TokenCtor = state.Token;\n const processed = new Set<number>();\n\n for (let i = tokens.length - 1; i >= 0; i--) {\n const token = tokens[i];\n const map = token.map;\n if (!map) continue;\n\n // Skip inline tokens \u2014 process their parent block tokens only.\n if (token.type === \"inline\") continue;\n\n // Annotate the element with line range\n const startLine1 = map[0] + 1;\n const endLine1 = map[1];\n token.attrSet(\"data-mrsf-line\", String(startLine1));\n token.attrSet(\"data-mrsf-start-line\", String(startLine1));\n token.attrSet(\"data-mrsf-end-line\", String(endLine1));\n\n // Check each line in this token's range for comments\n for (let line0 = map[0]; line0 < map[1]; line0++) {\n const line = line0 + 1; // 1-based\n if (processed.has(line)) continue;\n processed.add(line);\n\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n if (options.lineHighlight) {\n const existingClass = token.attrGet(\"class\") || \"\";\n if (!existingClass.includes(\"mrsf-line-highlight\")) {\n token.attrSet(\n \"class\",\n existingClass\n ? `${existingClass} mrsf-line-highlight`\n : \"mrsf-line-highlight\",\n );\n }\n }\n token.attrSet(\"data-mrsf-line\", String(line));\n }\n }\n }\n\n // Append a data script token with all comment data\n const allThreads: CommentThread[] = [];\n for (const threads of lineMap.values()) {\n allThreads.push(...threads);\n }\n if (allThreads.length > 0) {\n const scriptToken = new TokenCtor(\"mrsf_data_script\", \"\", 0);\n scriptToken.meta = { threads: allThreads };\n tokens.push(scriptToken);\n }\n });\n}\n", "/**\n * MRSF markdown-it plugin \u2014 renderer rules (overlay gutter architecture).\n *\n * Adds a single custom renderer rule:\n * - mrsf_data_script: renders a <script type=\"application/mrsf+json\">\n * containing serialized comment data for the MrsfController.\n */\n\nimport type { CommentThread } from \"../types.js\";\n\nexport interface RendererRuleOptions {\n dataContainer?: \"script\" | \"element\";\n dataElementId?: string;\n}\n\nfunction escapeAttribute(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n}\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 options: RendererRuleOptions = {},\n): void {\n md.renderer.rules[\"mrsf_data_script\"] = (\n tokens: { meta: { threads: CommentThread[] } }[],\n idx: number,\n ): string => {\n const { threads } = tokens[idx].meta;\n 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 // Escape < to prevent script tag injection (XSS safety)\n const data = payload.replace(/</g, \"\\\\u003c\");\n return `<script type=\"application/mrsf+json\">${data}</script>`;\n };\n}\n", "/**\n * Shared logic for @mrsf/markdown-it-mrsf.\n *\n * Contains the plugin factory using shared comment loading/grouping logic.\n */\n\nimport type MarkdownIt from \"markdown-it\";\nimport type { MrsfPluginOptions, CommentLoader } from \"./types.js\";\nimport { resolveComments } from \"./comments.js\";\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 installCoreRule(\n md,\n (state) => resolveComments(loader, options, state.env),\n { lineHighlight: options.lineHighlight ?? false },\n );\n installRendererRules(md, {\n dataContainer: options.dataContainer,\n dataElementId: options.dataElementId,\n });\n };\n}\n"],
|
|
4
|
+
"sourcesContent": ["/**\r\n * Browser-safe entry point for @mrsf/markdown-it-mrsf.\r\n *\r\n * This entry is automatically selected by bundlers (Vite, webpack, esbuild)\r\n * via the `\"browser\"` export condition in package.json. It supports the\r\n * `comments` and `loader` provisioning modes \u2014 no Node.js file-system APIs.\r\n *\r\n * You do NOT need to import this directly. Just use:\r\n * import { mrsfPlugin } from \"@mrsf/markdown-it-mrsf\";\r\n *\r\n * Your bundler will resolve to this file automatically when targeting\r\n * a browser environment.\r\n */\r\n\r\nimport type { MrsfPluginOptions } from \"./types.js\";\r\nimport { createMrsfPlugin } from \"./shared.js\";\r\n\r\nexport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\r\n\r\n/**\r\n * The markdown-it plugin function (browser-safe).\r\n *\r\n * Supports `comments` (inline data) and `loader` (custom function) options.\r\n */\r\nexport const mrsfPlugin = createMrsfPlugin((options: MrsfPluginOptions, env?: unknown) => {\r\n if (options.comments) {\r\n return options.comments;\r\n }\r\n\r\n if (options.loader) {\r\n try {\r\n return options.loader(options, env);\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n if (options.sidecarPath || options.documentPath) {\r\n console.warn(\r\n \"[@mrsf/markdown-it-mrsf] sidecarPath and documentPath require Node.js. \" +\r\n \"Use `comments` or `loader` options in browser environments.\"\r\n );\r\n }\r\n\r\n return null;\r\n});\r\n\r\nexport default mrsfPlugin;\r\n", "/* ---------------------------------------------------------------\n * AUTO-GENERATED \u2014 DO NOT EDIT\n *\n * Source: plugins/shared/src/comments.ts\n * Run `node plugins/sync-types.mjs` to regenerate.\n * --------------------------------------------------------------- */\n\n/**\r\n * Shared comment loading and grouping logic for MRSF rendering plugins.\r\n */\r\n\r\nimport type { MrsfDocument } from \"@mrsf/cli\";\r\nimport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\r\n\r\nexport type { CommentLoader };\r\n\r\nfunction getExtensionFields(comment: Record<string, unknown>): Record<`x_${string}`, unknown> {\r\n const extensions: Record<`x_${string}`, unknown> = {};\r\n\r\n for (const [key, value] of Object.entries(comment)) {\r\n if (key.startsWith(\"x_\")) {\r\n extensions[key as `x_${string}`] = value;\r\n }\r\n }\r\n\r\n return extensions;\r\n}\r\n\r\n/**\r\n * Convert an MrsfDocument into a slim comment array.\r\n */\r\nexport function toSlimComments(doc: MrsfDocument): SlimComment[] {\r\n return doc.comments.map((c) => ({\r\n id: c.id,\r\n author: c.author || \"Unknown\",\r\n text: c.text || \"\",\r\n line: c.line ?? null,\r\n end_line: c.end_line ?? null,\r\n start_column: c.start_column ?? null,\r\n end_column: c.end_column ?? null,\r\n selected_text: c.selected_text || null,\r\n resolved: !!c.resolved,\r\n reply_to: c.reply_to || null,\r\n severity: c.severity || null,\r\n type: c.type || null,\r\n timestamp: c.timestamp || null,\r\n ...getExtensionFields(c as Record<string, unknown>),\r\n }));\r\n}\r\n\r\n/**\r\n * Group comments by line number, threading replies under parents.\r\n */\r\nexport function groupByLine(comments: SlimComment[]): LineMap {\r\n const rootComments = comments.filter((c) => !c.reply_to && c.line != null);\r\n const replies = comments.filter((c) => c.reply_to);\r\n\r\n const replyMap = new Map<string, SlimComment[]>();\r\n for (const r of replies) {\r\n const list = replyMap.get(r.reply_to!) || [];\r\n list.push(r);\r\n replyMap.set(r.reply_to!, list);\r\n }\r\n\r\n const lineMap: LineMap = new Map();\r\n for (const c of rootComments) {\r\n const line = c.line!;\r\n const threads = lineMap.get(line) || [];\r\n threads.push({\r\n comment: c,\r\n replies: replyMap.get(c.id) || [],\r\n });\r\n lineMap.set(line, threads);\r\n }\r\n\r\n return lineMap;\r\n}\r\n\r\n/**\r\n * Resolve options into a filtered LineMap ready for rendering.\r\n * Returns null if there are no comments to render.\r\n */\r\nexport function resolveComments(\r\n loader: CommentLoader,\r\n options: MrsfPluginOptions,\r\n env?: unknown,\r\n): { lineMap: LineMap; comments: SlimComment[] } | null {\r\n const showResolved = options.showResolved ?? true;\r\n\r\n const doc = loader(options, env);\r\n if (!doc || !doc.comments || doc.comments.length === 0) {\r\n return null;\r\n }\r\n\r\n let comments = toSlimComments(doc);\r\n if (!showResolved) {\r\n comments = comments.filter((c) => !c.resolved);\r\n }\r\n\r\n if (comments.length === 0) return null;\r\n\r\n const lineMap = groupByLine(comments);\r\n return { lineMap, comments };\r\n}\r\n", "/**\r\n * MRSF markdown-it plugin \u2014 core rule (overlay gutter architecture).\r\n *\r\n * Walks the parsed token stream and annotates block tokens with\r\n * `data-mrsf-line` attributes. Adds `mrsf-line-highlight` class on\r\n * commented lines. Appends an `mrsf_data_script` token with serialized\r\n * comment data for the client-side MrsfController.\r\n *\r\n * NO visual tokens are injected \u2014 all badges, tooltips, and gutter\r\n * elements are created at runtime by the controller.\r\n */\r\n\r\nimport type Token from \"markdown-it/lib/token.mjs\";\r\nimport type { Nesting } from \"markdown-it/lib/token.mjs\";\r\nimport type StateCore from \"markdown-it/lib/rules_core/state_core.mjs\";\r\nimport type { LineMap, CommentThread, SlimComment } from \"../types.js\";\r\n\r\nexport interface CoreRuleOptions {\r\n /** Whether to add mrsf-line-highlight class on commented tokens. Default: false. */\r\n lineHighlight?: boolean;\r\n}\r\n\r\nexport interface ResolvedCommentData {\r\n lineMap: LineMap;\r\n comments: SlimComment[];\r\n}\r\n\r\n/**\r\n * Install the MRSF core rule on a markdown-it instance.\r\n */\r\nexport function installCoreRule(\r\n md: { core: { ruler: { push: (name: string, fn: (state: StateCore) => void) => void } } },\r\n resolveComments: (state: StateCore) => ResolvedCommentData | null,\r\n options: CoreRuleOptions = {},\r\n): void {\r\n md.core.ruler.push(\"mrsf_inject\", (state: StateCore) => {\r\n const result = resolveComments(state);\r\n if (!result) return;\r\n\r\n const { lineMap } = result;\r\n const tokens = state.tokens;\r\n const TokenCtor = state.Token;\r\n const processed = new Set<number>();\r\n\r\n for (let i = tokens.length - 1; i >= 0; i--) {\r\n const token = tokens[i];\r\n const map = token.map;\r\n if (!map) continue;\r\n\r\n // Skip inline tokens \u2014 process their parent block tokens only.\r\n if (token.type === \"inline\") continue;\r\n\r\n // Annotate the element with line range\r\n const startLine1 = map[0] + 1;\r\n const endLine1 = map[1];\r\n token.attrSet(\"data-mrsf-line\", String(startLine1));\r\n token.attrSet(\"data-mrsf-start-line\", String(startLine1));\r\n token.attrSet(\"data-mrsf-end-line\", String(endLine1));\r\n\r\n // Check each line in this token's range for comments\r\n for (let line0 = map[0]; line0 < map[1]; line0++) {\r\n const line = line0 + 1; // 1-based\r\n if (processed.has(line)) continue;\r\n processed.add(line);\r\n\r\n const threads = lineMap.get(line);\r\n if (threads && threads.length > 0) {\r\n if (options.lineHighlight) {\r\n const existingClass = token.attrGet(\"class\") || \"\";\r\n if (!existingClass.includes(\"mrsf-line-highlight\")) {\r\n token.attrSet(\r\n \"class\",\r\n existingClass\r\n ? `${existingClass} mrsf-line-highlight`\r\n : \"mrsf-line-highlight\",\r\n );\r\n }\r\n }\r\n token.attrSet(\"data-mrsf-line\", String(line));\r\n }\r\n }\r\n }\r\n\r\n // Append a data script token with all comment data\r\n const allThreads: CommentThread[] = [];\r\n for (const threads of lineMap.values()) {\r\n allThreads.push(...threads);\r\n }\r\n if (allThreads.length > 0) {\r\n const scriptToken = new TokenCtor(\"mrsf_data_script\", \"\", 0);\r\n scriptToken.meta = { threads: allThreads };\r\n tokens.push(scriptToken);\r\n }\r\n });\r\n}\r\n", "/**\r\n * MRSF markdown-it plugin \u2014 renderer rules (overlay gutter architecture).\r\n *\r\n * Adds a single custom renderer rule:\r\n * - mrsf_data_script: renders a <script type=\"application/mrsf+json\">\r\n * containing serialized comment data for the MrsfController.\r\n */\r\n\r\nimport type { CommentThread } from \"../types.js\";\r\n\r\nexport interface RendererRuleOptions {\r\n dataContainer?: \"script\" | \"element\";\r\n dataElementId?: string;\r\n}\r\n\r\nfunction escapeAttribute(value: string): string {\r\n return value\r\n .replace(/&/g, \"&\")\r\n .replace(/</g, \"<\")\r\n .replace(/>/g, \">\")\r\n .replace(/\"/g, \""\");\r\n}\r\n\r\n/**\r\n * Install MRSF renderer rules on a markdown-it instance.\r\n */\r\nexport function installRendererRules(\r\n md: { renderer: { rules: Record<string, ((...args: any[]) => string) | undefined> } },\r\n options: RendererRuleOptions = {},\r\n): void {\r\n md.renderer.rules[\"mrsf_data_script\"] = (\r\n tokens: { meta: { threads: CommentThread[] } }[],\r\n idx: number,\r\n ): string => {\r\n const { threads } = tokens[idx].meta;\r\n const payload = JSON.stringify({ threads });\r\n if (options.dataContainer === \"element\") {\r\n const elementId = options.dataElementId || \"mrsf-comment-data\";\r\n return `<div id=\"${escapeAttribute(elementId)}\" data-mrsf-json=\"${escapeAttribute(payload)}\" aria-hidden=\"true\"></div>`;\r\n }\r\n\r\n // Escape < to prevent script tag injection (XSS safety)\r\n const data = payload.replace(/</g, \"\\\\u003c\");\r\n return `<script type=\"application/mrsf+json\">${data}</script>`;\r\n };\r\n}\r\n", "/**\r\n * Shared logic for @mrsf/markdown-it-mrsf.\r\n *\r\n * Contains the plugin factory using shared comment loading/grouping logic.\r\n */\r\n\r\nimport type MarkdownIt from \"markdown-it\";\r\nimport type { MrsfPluginOptions, CommentLoader } from \"./types.js\";\r\nimport { resolveComments } from \"./comments.js\";\r\nimport { installCoreRule } from \"./rules/core.js\";\r\nimport { installRendererRules } from \"./rules/renderer.js\";\r\n\r\n/**\r\n * Create a markdown-it plugin function with the given comment loader.\r\n *\r\n * @param loader - Function that resolves sidecar data from plugin options\r\n * @returns A markdown-it plugin function\r\n */\r\nexport function createMrsfPlugin(loader: CommentLoader) {\r\n return function mrsfPlugin(md: MarkdownIt, options: MrsfPluginOptions = {}): void {\r\n installCoreRule(\r\n md,\r\n (state) => resolveComments(loader, options, state.env),\r\n { lineHighlight: options.lineHighlight ?? false },\r\n );\r\n installRendererRules(md, {\r\n dataContainer: options.dataContainer,\r\n dataElementId: options.dataElementId,\r\n });\r\n };\r\n}\r\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["resolveComments", "mrsfPlugin"]
|
|
7
7
|
}
|
package/dist/browser.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/comments.ts", "../src/rules/core.ts", "../src/rules/renderer.ts", "../src/shared.ts", "../src/browser.ts"],
|
|
4
|
-
"sourcesContent": ["/* ---------------------------------------------------------------\n * AUTO-GENERATED \u2014 DO NOT EDIT\n *\n * Source: plugins/shared/src/comments.ts\n * Run `node plugins/sync-types.mjs` to regenerate.\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\nfunction getExtensionFields(comment: Record<string, unknown>): Record<`x_${string}`, unknown> {\n const extensions: Record<`x_${string}`, unknown> = {};\n\n for (const [key, value] of Object.entries(comment)) {\n if (key.startsWith(\"x_\")) {\n extensions[key as `x_${string}`] = value;\n }\n }\n\n return extensions;\n}\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 ...getExtensionFields(c as Record<string, unknown>),\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 * MRSF markdown-it plugin \u2014 core rule (overlay gutter architecture).\n *\n * Walks the parsed token stream and annotates block tokens with\n * `data-mrsf-line` attributes. Adds `mrsf-line-highlight` class on\n * commented lines. Appends an `mrsf_data_script` token with serialized\n * comment data for the client-side MrsfController.\n *\n * NO visual tokens are injected \u2014 all badges, tooltips, and gutter\n * elements are created at runtime by the controller.\n */\n\nimport type Token from \"markdown-it/lib/token.mjs\";\nimport type { Nesting } from \"markdown-it/lib/token.mjs\";\nimport type StateCore from \"markdown-it/lib/rules_core/state_core.mjs\";\nimport type { LineMap, CommentThread, SlimComment } from \"../types.js\";\n\nexport interface CoreRuleOptions {\n /** Whether to add mrsf-line-highlight class on commented tokens. Default: false. */\n lineHighlight?: boolean;\n}\n\nexport interface ResolvedCommentData {\n lineMap: LineMap;\n comments: SlimComment[];\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 resolveComments: (state: StateCore) => ResolvedCommentData | null,\n options: CoreRuleOptions = {},\n): void {\n md.core.ruler.push(\"mrsf_inject\", (state: StateCore) => {\n const result = resolveComments(state);\n if (!result) return;\n\n const { lineMap } = result;\n const tokens = state.tokens;\n const TokenCtor = state.Token;\n const processed = new Set<number>();\n\n for (let i = tokens.length - 1; i >= 0; i--) {\n const token = tokens[i];\n const map = token.map;\n if (!map) continue;\n\n // Skip inline tokens \u2014 process their parent block tokens only.\n if (token.type === \"inline\") continue;\n\n // Annotate the element with line range\n const startLine1 = map[0] + 1;\n const endLine1 = map[1];\n token.attrSet(\"data-mrsf-line\", String(startLine1));\n token.attrSet(\"data-mrsf-start-line\", String(startLine1));\n token.attrSet(\"data-mrsf-end-line\", String(endLine1));\n\n // Check each line in this token's range for comments\n for (let line0 = map[0]; line0 < map[1]; line0++) {\n const line = line0 + 1; // 1-based\n if (processed.has(line)) continue;\n processed.add(line);\n\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n if (options.lineHighlight) {\n const existingClass = token.attrGet(\"class\") || \"\";\n if (!existingClass.includes(\"mrsf-line-highlight\")) {\n token.attrSet(\n \"class\",\n existingClass\n ? `${existingClass} mrsf-line-highlight`\n : \"mrsf-line-highlight\",\n );\n }\n }\n token.attrSet(\"data-mrsf-line\", String(line));\n }\n }\n }\n\n // Append a data script token with all comment data\n const allThreads: CommentThread[] = [];\n for (const threads of lineMap.values()) {\n allThreads.push(...threads);\n }\n if (allThreads.length > 0) {\n const scriptToken = new TokenCtor(\"mrsf_data_script\", \"\", 0);\n scriptToken.meta = { threads: allThreads };\n tokens.push(scriptToken);\n }\n });\n}\n", "/**\n * MRSF markdown-it plugin \u2014 renderer rules (overlay gutter architecture).\n *\n * Adds a single custom renderer rule:\n * - mrsf_data_script: renders a <script type=\"application/mrsf+json\">\n * containing serialized comment data for the MrsfController.\n */\n\nimport type { CommentThread } from \"../types.js\";\n\nexport interface RendererRuleOptions {\n dataContainer?: \"script\" | \"element\";\n dataElementId?: string;\n}\n\nfunction escapeAttribute(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n}\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 options: RendererRuleOptions = {},\n): void {\n md.renderer.rules[\"mrsf_data_script\"] = (\n tokens: { meta: { threads: CommentThread[] } }[],\n idx: number,\n ): string => {\n const { threads } = tokens[idx].meta;\n 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 // Escape < to prevent script tag injection (XSS safety)\n const data = payload.replace(/</g, \"\\\\u003c\");\n return `<script type=\"application/mrsf+json\">${data}</script>`;\n };\n}\n", "/**\n * Shared logic for @mrsf/markdown-it-mrsf.\n *\n * Contains the plugin factory using shared comment loading/grouping logic.\n */\n\nimport type MarkdownIt from \"markdown-it\";\nimport type { MrsfPluginOptions, CommentLoader } from \"./types.js\";\nimport { resolveComments } from \"./comments.js\";\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 installCoreRule(\n md,\n (state) => resolveComments(loader, options, state.env),\n { lineHighlight: options.lineHighlight ?? false },\n );\n installRendererRules(md, {\n dataContainer: options.dataContainer,\n dataElementId: options.dataElementId,\n });\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, 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/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"],
|
|
4
|
+
"sourcesContent": ["/* ---------------------------------------------------------------\n * AUTO-GENERATED \u2014 DO NOT EDIT\n *\n * Source: plugins/shared/src/comments.ts\n * Run `node plugins/sync-types.mjs` to regenerate.\n * --------------------------------------------------------------- */\n\n/**\r\n * Shared comment loading and grouping logic for MRSF rendering plugins.\r\n */\r\n\r\nimport type { MrsfDocument } from \"@mrsf/cli\";\r\nimport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\r\n\r\nexport type { CommentLoader };\r\n\r\nfunction getExtensionFields(comment: Record<string, unknown>): Record<`x_${string}`, unknown> {\r\n const extensions: Record<`x_${string}`, unknown> = {};\r\n\r\n for (const [key, value] of Object.entries(comment)) {\r\n if (key.startsWith(\"x_\")) {\r\n extensions[key as `x_${string}`] = value;\r\n }\r\n }\r\n\r\n return extensions;\r\n}\r\n\r\n/**\r\n * Convert an MrsfDocument into a slim comment array.\r\n */\r\nexport function toSlimComments(doc: MrsfDocument): SlimComment[] {\r\n return doc.comments.map((c) => ({\r\n id: c.id,\r\n author: c.author || \"Unknown\",\r\n text: c.text || \"\",\r\n line: c.line ?? null,\r\n end_line: c.end_line ?? null,\r\n start_column: c.start_column ?? null,\r\n end_column: c.end_column ?? null,\r\n selected_text: c.selected_text || null,\r\n resolved: !!c.resolved,\r\n reply_to: c.reply_to || null,\r\n severity: c.severity || null,\r\n type: c.type || null,\r\n timestamp: c.timestamp || null,\r\n ...getExtensionFields(c as Record<string, unknown>),\r\n }));\r\n}\r\n\r\n/**\r\n * Group comments by line number, threading replies under parents.\r\n */\r\nexport function groupByLine(comments: SlimComment[]): LineMap {\r\n const rootComments = comments.filter((c) => !c.reply_to && c.line != null);\r\n const replies = comments.filter((c) => c.reply_to);\r\n\r\n const replyMap = new Map<string, SlimComment[]>();\r\n for (const r of replies) {\r\n const list = replyMap.get(r.reply_to!) || [];\r\n list.push(r);\r\n replyMap.set(r.reply_to!, list);\r\n }\r\n\r\n const lineMap: LineMap = new Map();\r\n for (const c of rootComments) {\r\n const line = c.line!;\r\n const threads = lineMap.get(line) || [];\r\n threads.push({\r\n comment: c,\r\n replies: replyMap.get(c.id) || [],\r\n });\r\n lineMap.set(line, threads);\r\n }\r\n\r\n return lineMap;\r\n}\r\n\r\n/**\r\n * Resolve options into a filtered LineMap ready for rendering.\r\n * Returns null if there are no comments to render.\r\n */\r\nexport function resolveComments(\r\n loader: CommentLoader,\r\n options: MrsfPluginOptions,\r\n env?: unknown,\r\n): { lineMap: LineMap; comments: SlimComment[] } | null {\r\n const showResolved = options.showResolved ?? true;\r\n\r\n const doc = loader(options, env);\r\n if (!doc || !doc.comments || doc.comments.length === 0) {\r\n return null;\r\n }\r\n\r\n let comments = toSlimComments(doc);\r\n if (!showResolved) {\r\n comments = comments.filter((c) => !c.resolved);\r\n }\r\n\r\n if (comments.length === 0) return null;\r\n\r\n const lineMap = groupByLine(comments);\r\n return { lineMap, comments };\r\n}\r\n", "/**\r\n * MRSF markdown-it plugin \u2014 core rule (overlay gutter architecture).\r\n *\r\n * Walks the parsed token stream and annotates block tokens with\r\n * `data-mrsf-line` attributes. Adds `mrsf-line-highlight` class on\r\n * commented lines. Appends an `mrsf_data_script` token with serialized\r\n * comment data for the client-side MrsfController.\r\n *\r\n * NO visual tokens are injected \u2014 all badges, tooltips, and gutter\r\n * elements are created at runtime by the controller.\r\n */\r\n\r\nimport type Token from \"markdown-it/lib/token.mjs\";\r\nimport type { Nesting } from \"markdown-it/lib/token.mjs\";\r\nimport type StateCore from \"markdown-it/lib/rules_core/state_core.mjs\";\r\nimport type { LineMap, CommentThread, SlimComment } from \"../types.js\";\r\n\r\nexport interface CoreRuleOptions {\r\n /** Whether to add mrsf-line-highlight class on commented tokens. Default: false. */\r\n lineHighlight?: boolean;\r\n}\r\n\r\nexport interface ResolvedCommentData {\r\n lineMap: LineMap;\r\n comments: SlimComment[];\r\n}\r\n\r\n/**\r\n * Install the MRSF core rule on a markdown-it instance.\r\n */\r\nexport function installCoreRule(\r\n md: { core: { ruler: { push: (name: string, fn: (state: StateCore) => void) => void } } },\r\n resolveComments: (state: StateCore) => ResolvedCommentData | null,\r\n options: CoreRuleOptions = {},\r\n): void {\r\n md.core.ruler.push(\"mrsf_inject\", (state: StateCore) => {\r\n const result = resolveComments(state);\r\n if (!result) return;\r\n\r\n const { lineMap } = result;\r\n const tokens = state.tokens;\r\n const TokenCtor = state.Token;\r\n const processed = new Set<number>();\r\n\r\n for (let i = tokens.length - 1; i >= 0; i--) {\r\n const token = tokens[i];\r\n const map = token.map;\r\n if (!map) continue;\r\n\r\n // Skip inline tokens \u2014 process their parent block tokens only.\r\n if (token.type === \"inline\") continue;\r\n\r\n // Annotate the element with line range\r\n const startLine1 = map[0] + 1;\r\n const endLine1 = map[1];\r\n token.attrSet(\"data-mrsf-line\", String(startLine1));\r\n token.attrSet(\"data-mrsf-start-line\", String(startLine1));\r\n token.attrSet(\"data-mrsf-end-line\", String(endLine1));\r\n\r\n // Check each line in this token's range for comments\r\n for (let line0 = map[0]; line0 < map[1]; line0++) {\r\n const line = line0 + 1; // 1-based\r\n if (processed.has(line)) continue;\r\n processed.add(line);\r\n\r\n const threads = lineMap.get(line);\r\n if (threads && threads.length > 0) {\r\n if (options.lineHighlight) {\r\n const existingClass = token.attrGet(\"class\") || \"\";\r\n if (!existingClass.includes(\"mrsf-line-highlight\")) {\r\n token.attrSet(\r\n \"class\",\r\n existingClass\r\n ? `${existingClass} mrsf-line-highlight`\r\n : \"mrsf-line-highlight\",\r\n );\r\n }\r\n }\r\n token.attrSet(\"data-mrsf-line\", String(line));\r\n }\r\n }\r\n }\r\n\r\n // Append a data script token with all comment data\r\n const allThreads: CommentThread[] = [];\r\n for (const threads of lineMap.values()) {\r\n allThreads.push(...threads);\r\n }\r\n if (allThreads.length > 0) {\r\n const scriptToken = new TokenCtor(\"mrsf_data_script\", \"\", 0);\r\n scriptToken.meta = { threads: allThreads };\r\n tokens.push(scriptToken);\r\n }\r\n });\r\n}\r\n", "/**\r\n * MRSF markdown-it plugin \u2014 renderer rules (overlay gutter architecture).\r\n *\r\n * Adds a single custom renderer rule:\r\n * - mrsf_data_script: renders a <script type=\"application/mrsf+json\">\r\n * containing serialized comment data for the MrsfController.\r\n */\r\n\r\nimport type { CommentThread } from \"../types.js\";\r\n\r\nexport interface RendererRuleOptions {\r\n dataContainer?: \"script\" | \"element\";\r\n dataElementId?: string;\r\n}\r\n\r\nfunction escapeAttribute(value: string): string {\r\n return value\r\n .replace(/&/g, \"&\")\r\n .replace(/</g, \"<\")\r\n .replace(/>/g, \">\")\r\n .replace(/\"/g, \""\");\r\n}\r\n\r\n/**\r\n * Install MRSF renderer rules on a markdown-it instance.\r\n */\r\nexport function installRendererRules(\r\n md: { renderer: { rules: Record<string, ((...args: any[]) => string) | undefined> } },\r\n options: RendererRuleOptions = {},\r\n): void {\r\n md.renderer.rules[\"mrsf_data_script\"] = (\r\n tokens: { meta: { threads: CommentThread[] } }[],\r\n idx: number,\r\n ): string => {\r\n const { threads } = tokens[idx].meta;\r\n const payload = JSON.stringify({ threads });\r\n if (options.dataContainer === \"element\") {\r\n const elementId = options.dataElementId || \"mrsf-comment-data\";\r\n return `<div id=\"${escapeAttribute(elementId)}\" data-mrsf-json=\"${escapeAttribute(payload)}\" aria-hidden=\"true\"></div>`;\r\n }\r\n\r\n // Escape < to prevent script tag injection (XSS safety)\r\n const data = payload.replace(/</g, \"\\\\u003c\");\r\n return `<script type=\"application/mrsf+json\">${data}</script>`;\r\n };\r\n}\r\n", "/**\r\n * Shared logic for @mrsf/markdown-it-mrsf.\r\n *\r\n * Contains the plugin factory using shared comment loading/grouping logic.\r\n */\r\n\r\nimport type MarkdownIt from \"markdown-it\";\r\nimport type { MrsfPluginOptions, CommentLoader } from \"./types.js\";\r\nimport { resolveComments } from \"./comments.js\";\r\nimport { installCoreRule } from \"./rules/core.js\";\r\nimport { installRendererRules } from \"./rules/renderer.js\";\r\n\r\n/**\r\n * Create a markdown-it plugin function with the given comment loader.\r\n *\r\n * @param loader - Function that resolves sidecar data from plugin options\r\n * @returns A markdown-it plugin function\r\n */\r\nexport function createMrsfPlugin(loader: CommentLoader) {\r\n return function mrsfPlugin(md: MarkdownIt, options: MrsfPluginOptions = {}): void {\r\n installCoreRule(\r\n md,\r\n (state) => resolveComments(loader, options, state.env),\r\n { lineHighlight: options.lineHighlight ?? false },\r\n );\r\n installRendererRules(md, {\r\n dataContainer: options.dataContainer,\r\n dataElementId: options.dataElementId,\r\n });\r\n };\r\n}\r\n", "/**\r\n * Browser-safe entry point for @mrsf/markdown-it-mrsf.\r\n *\r\n * This entry is automatically selected by bundlers (Vite, webpack, esbuild)\r\n * via the `\"browser\"` export condition in package.json. It supports the\r\n * `comments` and `loader` provisioning modes \u2014 no Node.js file-system APIs.\r\n *\r\n * You do NOT need to import this directly. Just use:\r\n * import { mrsfPlugin } from \"@mrsf/markdown-it-mrsf\";\r\n *\r\n * Your bundler will resolve to this file automatically when targeting\r\n * a browser environment.\r\n */\r\n\r\nimport type { MrsfPluginOptions } from \"./types.js\";\r\nimport { createMrsfPlugin } from \"./shared.js\";\r\n\r\nexport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\r\n\r\n/**\r\n * The markdown-it plugin function (browser-safe).\r\n *\r\n * Supports `comments` (inline data) and `loader` (custom function) options.\r\n */\r\nexport const mrsfPlugin = createMrsfPlugin((options: MrsfPluginOptions, env?: unknown) => {\r\n if (options.comments) {\r\n return options.comments;\r\n }\r\n\r\n if (options.loader) {\r\n try {\r\n return options.loader(options, env);\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n if (options.sidecarPath || options.documentPath) {\r\n console.warn(\r\n \"[@mrsf/markdown-it-mrsf] sidecarPath and documentPath require Node.js. \" +\r\n \"Use `comments` or `loader` options in browser environments.\"\r\n );\r\n }\r\n\r\n return null;\r\n});\r\n\r\nexport default mrsfPlugin;\r\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["resolveComments", "mrsfPlugin"]
|
|
7
7
|
}
|