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