@richhtmleditor/comments 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @richhtmleditor/comments
2
+
3
+ Review and comments workflow plugin for Rich HTML Editor (enterprise). Anchored highlights, Add Comment modal, hover tooltips, comments panel, @mentions, and export HTML — requires `features.comments` from [`@richhtmleditor/enterprise`](https://www.npmjs.com/package/@richhtmleditor/enterprise), built on [`@richhtmleditor/core`](https://www.npmjs.com/package/@richhtmleditor/core).
4
+
5
+ **Current release: 1.0.0** — Depends on `@richhtmleditor/core` **^1.0.0**.
6
+
7
+ **Repository:** [github.com/rajkishorsahu89/richhtmleditor](https://github.com/rajkishorsahu89/richhtmleditor)
8
+
9
+ **Demo:** [richhtmleditor.vercel.app](https://richhtmleditor.vercel.app/) — [demo](https://richhtmleditor.vercel.app/demo) · [guide](https://richhtmleditor.vercel.app/guide) · [API](https://richhtmleditor.vercel.app/api). Doc Preview joint demo: [doc-preview-app.vercel.app/demo/enterprise](https://doc-preview-app.vercel.app/demo/enterprise)
10
+
11
+ ### What's in 1.0.0
12
+
13
+ - **`createCommentsPlugin`** — toolbar Comment and Comments panel buttons
14
+ - **Add Comment modal** — compose popover anchored to the current text selection
15
+ - **Anchored highlights** — `<span data-de-comment-id>` markers in document HTML
16
+ - **Hover tooltips** — preview thread excerpt and messages on anchor hover
17
+ - **Comments panel** — thread list with reply, resolve, and reopen
18
+ - **@mentions** — autocomplete in compose box (`mentionUsers` option)
19
+ - **Export HTML** — `buildCommentsExportHtml` and `editor.getCommentsExportHtml()` for publish bundles
20
+
21
+ > Requires `features.comments: true`. Demo keys: `RHE-ENT-DEMO-2026-FULL` or `RHE-ENT-DEMO-2026-COMMENTS`.
22
+
23
+ **Keywords:** `richhtmleditor` `comments` `review` `enterprise` `annotations` `wysiwyg`
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install @richhtmleditor/comments @richhtmleditor/enterprise
29
+ # Requires @richhtmleditor/core (via framework wrapper or direct install).
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ```ts
35
+ import { createEditor } from "@richhtmleditor/core";
36
+ import { createCommentsPlugin } from "@richhtmleditor/comments";
37
+ import { resolveEnterpriseFeatures } from "@richhtmleditor/enterprise/browser";
38
+
39
+ const gate = resolveEnterpriseFeatures({ token: "RHE-ENT-DEMO-2026-FULL" });
40
+
41
+ const editor = createEditor({
42
+ element: host,
43
+ toolbar: { preset: "full" },
44
+ features: gate.features,
45
+ plugins: [
46
+ createCommentsPlugin({
47
+ author: "Reviewer",
48
+ mentionUsers: ["reviewer", "approver", "legal", "publisher"],
49
+ onChange: (threads) => saveThreads(threads)
50
+ })
51
+ ]
52
+ });
53
+ ```
54
+
55
+ Select text in the document, click **Comment** (toolbar or selection chip), and add a comment in the compose popover. Anchors persist in exported HTML as `data-de-comment-id` spans.
56
+
57
+ ## Usage — export with comments
58
+
59
+ ```ts
60
+ import { buildCommentsExportHtml } from "@richhtmleditor/comments";
61
+
62
+ const commentsHtml = editor.getCommentsExportHtml?.()
63
+ ?? buildCommentsExportHtml(threads);
64
+
65
+ const preview = editor.exportForPreview({
66
+ workflowState: "in_review",
67
+ commentsHtml
68
+ });
69
+ ```
70
+
71
+ ## API
72
+
73
+ ### `createCommentsPlugin(options?)`
74
+
75
+ | Option | Type | Description |
76
+ | --- | --- | --- |
77
+ | `author` | `string` | Default author name for new messages (default `"Reviewer"`). |
78
+ | `initialThreads` | `CommentThread[]` | Pre-load comment threads. |
79
+ | `onChange` | `(threads: CommentThread[]) => void` | Fired when threads are added, replied, or resolved. |
80
+ | `mentionUsers` | `string[]` | Users for `@mention` autocomplete. |
81
+
82
+ ### `CommentThread`
83
+
84
+ | Field | Type | Description |
85
+ | --- | --- | --- |
86
+ | `id` | `string` | Thread identifier. |
87
+ | `excerpt` | `string` | Quoted selection text. |
88
+ | `status` | `"open" \| "resolved"` | Thread state. |
89
+ | `anchorId` | `string?` | Links to `data-de-comment-id` in document HTML. |
90
+ | `messages` | `Array<{ id, author, body, createdAt }>` | Thread messages. |
91
+
92
+ ### Toolbar tools registered
93
+
94
+ | Tool ID | Description |
95
+ | --- | --- |
96
+ | `commentAdd` | Comment on selected text (opens compose popover). |
97
+ | `commentPanel` | Toggle comments side panel. |
98
+
99
+ ### Editor extensions (when plugin attached)
100
+
101
+ | Method | Description |
102
+ | --- | --- |
103
+ | `getCommentThreads()` | Current thread list. |
104
+ | `getCommentsExportHtml()` | HTML aside for `exportForPreview({ commentsHtml })`. |
105
+
106
+ ### `buildCommentsExportHtml(threads)`
107
+
108
+ Standalone export helper — returns an HTML fragment for comment threads.
109
+
110
+ ## Related packages
111
+
112
+ - [`@richhtmleditor/core`](https://www.npmjs.com/package/@richhtmleditor/core) — editor engine and `exportForPreview`.
113
+ - [`@richhtmleditor/enterprise`](https://www.npmjs.com/package/@richhtmleditor/enterprise) — licence gating for `features.comments`.
114
+ - [`@richhtmleditor/workflows`](https://www.npmjs.com/package/@richhtmleditor/workflows) — draft/review/approved states.
115
+ - [`@richhtmleditor/ai`](https://www.npmjs.com/package/@richhtmleditor/ai) — AI authoring plugin.
116
+ - [`@richhtmleditor/angular`](https://www.npmjs.com/package/@richhtmleditor/angular) — Angular wrapper.
117
+ - [`@richhtmleditor/react`](https://www.npmjs.com/package/@richhtmleditor/react) — React wrapper.
118
+
119
+ ## License
120
+
121
+ [MIT](./LICENSE)
@@ -0,0 +1,7 @@
1
+ export declare function wrapRangeAnchor(contentEl: HTMLElement, range: Range, threadId: string): boolean;
2
+ export declare function wrapSelectionAnchor(contentEl: HTMLElement, threadId: string): boolean;
3
+ export declare function setAnchorResolved(contentEl: HTMLElement, threadId: string, resolved: boolean): void;
4
+ export declare function scrollToAnchor(contentEl: HTMLElement, threadId: string): void;
5
+ export declare function readAnchorExcerpt(contentEl: HTMLElement, threadId: string): string;
6
+ export declare function scanAnchorsFromHtml(contentEl: HTMLElement): string[];
7
+ //# sourceMappingURL=anchors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchors.d.ts","sourceRoot":"","sources":["../src/anchors.ts"],"names":[],"mappings":"AAGA,wBAAgB,eAAe,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAwB/F;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAMrF;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAWnG;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAG7E;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGlF;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,WAAW,GAAG,MAAM,EAAE,CAIpE"}
@@ -0,0 +1,60 @@
1
+ const ANCHOR_CLASS = "de-comment-anchor";
2
+ const ANCHOR_ATTR = "data-de-comment-id";
3
+ export function wrapRangeAnchor(contentEl, range, threadId) {
4
+ if (!contentEl.contains(range.commonAncestorContainer)) {
5
+ return false;
6
+ }
7
+ const span = document.createElement("span");
8
+ span.className = ANCHOR_CLASS;
9
+ span.setAttribute(ANCHOR_ATTR, threadId);
10
+ span.style.background = "rgba(251,191,36,.38)";
11
+ span.style.borderBottom = "2px solid #f59e0b";
12
+ span.style.borderRadius = "2px";
13
+ const working = range.cloneRange();
14
+ try {
15
+ working.surroundContents(span);
16
+ }
17
+ catch {
18
+ try {
19
+ const fragment = working.extractContents();
20
+ span.appendChild(fragment);
21
+ working.insertNode(span);
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ contentEl.dispatchEvent(new Event("input", { bubbles: true }));
28
+ return true;
29
+ }
30
+ export function wrapSelectionAnchor(contentEl, threadId) {
31
+ const sel = window.getSelection();
32
+ if (!sel?.rangeCount || sel.isCollapsed) {
33
+ return false;
34
+ }
35
+ return wrapRangeAnchor(contentEl, sel.getRangeAt(0), threadId);
36
+ }
37
+ export function setAnchorResolved(contentEl, threadId, resolved) {
38
+ const anchor = contentEl.querySelector(`[${ANCHOR_ATTR}="${threadId}"]`);
39
+ if (!anchor) {
40
+ return;
41
+ }
42
+ const wasResolved = anchor.classList.contains("de-comment-anchor--resolved");
43
+ if (wasResolved === resolved) {
44
+ return;
45
+ }
46
+ anchor.classList.toggle("de-comment-anchor--resolved", resolved);
47
+ contentEl.dispatchEvent(new Event("input", { bubbles: true }));
48
+ }
49
+ export function scrollToAnchor(contentEl, threadId) {
50
+ const anchor = contentEl.querySelector(`[${ANCHOR_ATTR}="${threadId}"]`);
51
+ anchor?.scrollIntoView({ block: "center", behavior: "smooth" });
52
+ }
53
+ export function readAnchorExcerpt(contentEl, threadId) {
54
+ const anchor = contentEl.querySelector(`[${ANCHOR_ATTR}="${threadId}"]`);
55
+ return anchor?.textContent?.trim() ?? "";
56
+ }
57
+ export function scanAnchorsFromHtml(contentEl) {
58
+ return [...contentEl.querySelectorAll(`[${ANCHOR_ATTR}]`)].map((node) => node.getAttribute(ANCHOR_ATTR) ?? "").filter(Boolean);
59
+ }
60
+ //# sourceMappingURL=anchors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchors.js","sourceRoot":"","sources":["../src/anchors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { EditorInstance } from "@richhtmleditor/core";
2
+ import type { CommentThread, CommentsPluginOptions } from "./types.js";
3
+ type SavedSelection = {
4
+ anchor: {
5
+ block: number;
6
+ offset: number;
7
+ listItem?: number;
8
+ tableRow?: number;
9
+ tableCol?: number;
10
+ sectionPath?: number[];
11
+ sectionTitle?: boolean;
12
+ collapsiblePath?: Array<"summary" | number>;
13
+ };
14
+ head: SavedSelection["anchor"];
15
+ };
16
+ export type CommentComposePopoverMount = {
17
+ openAtSelection: () => void;
18
+ close: () => void;
19
+ isOpen: () => boolean;
20
+ destroy: () => void;
21
+ };
22
+ type ComposeStore = {
23
+ threads: CommentThread[];
24
+ };
25
+ export declare function mountCommentComposePopover(_editor: EditorInstance, host: HTMLElement, contentEl: HTMLElement, store: ComposeStore, pluginOptions: CommentsPluginOptions, helpers: {
26
+ newId: () => string;
27
+ notify: () => void;
28
+ refreshPanel: () => void;
29
+ openPanel: () => void;
30
+ captureSelection: () => SavedSelection | null;
31
+ applyAnchor: (threadId: string, selection: SavedSelection) => boolean;
32
+ readAnchorExcerpt: (threadId: string) => string;
33
+ }): CommentComposePopoverMount;
34
+ export declare function mountCommentHoverPreview(host: HTMLElement, contentEl: HTMLElement, getThread: (anchorId: string) => CommentThread | undefined): () => void;
35
+ export {};
36
+ //# sourceMappingURL=comment-popover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-popover.d.ts","sourceRoot":"","sources":["../src/comment-popover.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEvE,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC,CAAC;KAC7C,CAAC;IACF,IAAI,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;CAChC,CAAC;AAUF,MAAM,MAAM,0BAA0B,GAAG;IACvC,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,OAAO,CAAC;IACtB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,KAAK,YAAY,GAAG;IAClB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B,CAAC;AAEF,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,WAAW,EACjB,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,YAAY,EACnB,aAAa,EAAE,qBAAqB,EACpC,OAAO,EAAE;IACP,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,gBAAgB,EAAE,MAAM,cAAc,GAAG,IAAI,CAAC;IAC9C,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,KAAK,OAAO,CAAC;IACtE,iBAAiB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;CACjD,GACA,0BAA0B,CAsJ5B;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,WAAW,EACjB,SAAS,EAAE,WAAW,EACtB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,GACzD,MAAM,IAAI,CAoIZ"}
@@ -0,0 +1,267 @@
1
+ function escapeHtml(text) {
2
+ return text
3
+ .replace(/&/g, "&amp;")
4
+ .replace(/</g, "&lt;")
5
+ .replace(/>/g, "&gt;")
6
+ .replace(/"/g, "&quot;");
7
+ }
8
+ export function mountCommentComposePopover(_editor, host, contentEl, store, pluginOptions, helpers) {
9
+ let backdrop = null;
10
+ let savedSelection = null;
11
+ let savedExcerpt = "";
12
+ const removeModal = () => {
13
+ backdrop?.remove();
14
+ backdrop = null;
15
+ savedSelection = null;
16
+ savedExcerpt = "";
17
+ };
18
+ const submitComment = (body) => {
19
+ if (!savedSelection) {
20
+ return;
21
+ }
22
+ const threadId = helpers.newId();
23
+ const anchored = helpers.applyAnchor(threadId, savedSelection);
24
+ const excerpt = anchored ? helpers.readAnchorExcerpt(threadId) || savedExcerpt : savedExcerpt;
25
+ store.threads.unshift({
26
+ id: threadId,
27
+ excerpt,
28
+ anchorId: anchored ? threadId : undefined,
29
+ status: "open",
30
+ messages: [
31
+ {
32
+ id: helpers.newId(),
33
+ author: pluginOptions.author ?? "Reviewer",
34
+ body,
35
+ createdAt: new Date().toISOString()
36
+ }
37
+ ]
38
+ });
39
+ helpers.refreshPanel();
40
+ helpers.notify();
41
+ helpers.openPanel();
42
+ removeModal();
43
+ };
44
+ const bindModalEvents = (root) => {
45
+ root.addEventListener("keydown", (event) => event.stopPropagation());
46
+ root.addEventListener("beforeinput", (event) => event.stopPropagation());
47
+ root.addEventListener("input", (event) => event.stopPropagation());
48
+ };
49
+ const openAtSelection = () => {
50
+ const sel = window.getSelection();
51
+ if (!sel?.rangeCount || sel.isCollapsed || !contentEl.contains(sel.anchorNode)) {
52
+ return;
53
+ }
54
+ const excerpt = sel.toString().trim();
55
+ const selection = helpers.captureSelection();
56
+ if (!excerpt || !selection) {
57
+ return;
58
+ }
59
+ removeModal();
60
+ savedExcerpt = excerpt;
61
+ savedSelection = selection;
62
+ backdrop = document.createElement("div");
63
+ backdrop.className = "de-comment-modal-backdrop";
64
+ backdrop.setAttribute("role", "presentation");
65
+ backdrop.innerHTML = `
66
+ <div class="de-comment-modal" role="dialog" aria-label="Add comment" contenteditable="false">
67
+ <div class="de-comment-modal__head">
68
+ <h4 class="de-comment-modal__title">Add Comment</h4>
69
+ <button type="button" class="de-comment-modal__close" aria-label="Close">×</button>
70
+ </div>
71
+ <div class="de-comment-modal__quote">
72
+ <span class="de-comment-modal__quote-bar" aria-hidden="true"></span>
73
+ <span class="de-comment-modal__quote-text">${escapeHtml(excerpt)}</span>
74
+ </div>
75
+ <label class="de-comment-modal__label">Comment:</label>
76
+ <textarea class="de-comment-modal__input" placeholder="Type your comment…" rows="4"></textarea>
77
+ <div class="de-comment-modal__actions">
78
+ <button type="button" class="de-comment-modal__btn de-comment-modal__btn--submit">Add Comment</button>
79
+ </div>
80
+ </div>
81
+ `;
82
+ bindModalEvents(backdrop);
83
+ host.appendChild(backdrop);
84
+ const modal = backdrop.querySelector(".de-comment-modal");
85
+ const textarea = backdrop.querySelector(".de-comment-modal__input");
86
+ const closeBtn = backdrop.querySelector(".de-comment-modal__close");
87
+ const submitBtn = backdrop.querySelector(".de-comment-modal__btn--submit");
88
+ modal.addEventListener("mousedown", (event) => {
89
+ if (!event.target.closest("textarea, button")) {
90
+ event.preventDefault();
91
+ }
92
+ });
93
+ const close = () => removeModal();
94
+ closeBtn.addEventListener("click", (event) => {
95
+ event.stopPropagation();
96
+ close();
97
+ });
98
+ backdrop.addEventListener("click", (event) => {
99
+ if (event.target === backdrop) {
100
+ close();
101
+ }
102
+ });
103
+ submitBtn.addEventListener("click", (event) => {
104
+ event.stopPropagation();
105
+ const body = textarea.value.trim();
106
+ if (!body) {
107
+ textarea.focus();
108
+ return;
109
+ }
110
+ submitComment(body);
111
+ });
112
+ textarea.addEventListener("keydown", (event) => {
113
+ event.stopPropagation();
114
+ if (event.key === "Escape") {
115
+ event.preventDefault();
116
+ close();
117
+ }
118
+ if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
119
+ event.preventDefault();
120
+ const body = textarea.value.trim();
121
+ if (body) {
122
+ submitComment(body);
123
+ }
124
+ }
125
+ });
126
+ requestAnimationFrame(() => textarea.focus());
127
+ };
128
+ const onDocKeyDown = (event) => {
129
+ if (event.key === "Escape" && backdrop) {
130
+ removeModal();
131
+ }
132
+ };
133
+ document.addEventListener("keydown", onDocKeyDown);
134
+ return {
135
+ openAtSelection,
136
+ close: removeModal,
137
+ isOpen: () => Boolean(backdrop),
138
+ destroy: () => {
139
+ document.removeEventListener("keydown", onDocKeyDown);
140
+ removeModal();
141
+ }
142
+ };
143
+ }
144
+ export function mountCommentHoverPreview(host, contentEl, getThread) {
145
+ let tip = null;
146
+ let showTimer = 0;
147
+ let hideTimer = 0;
148
+ let activeAnchorId = null;
149
+ const removeTip = () => {
150
+ tip?.remove();
151
+ tip = null;
152
+ activeAnchorId = null;
153
+ };
154
+ const positionTip = (anchor) => {
155
+ if (!tip) {
156
+ return;
157
+ }
158
+ const rect = anchor.getBoundingClientRect();
159
+ const hostRect = host.getBoundingClientRect();
160
+ const width = tip.offsetWidth || 280;
161
+ let left = rect.left - hostRect.left + host.scrollLeft;
162
+ let top = rect.bottom - hostRect.top + host.scrollTop + 6;
163
+ const maxLeft = Math.max(4, host.clientWidth - width - 4);
164
+ left = Math.max(4, Math.min(left, maxLeft));
165
+ tip.style.left = `${left}px`;
166
+ tip.style.top = `${top}px`;
167
+ };
168
+ const showTip = (anchor, anchorId) => {
169
+ const thread = getThread(anchorId);
170
+ const highlighted = anchor.textContent?.trim() || thread?.excerpt || "";
171
+ if (!highlighted && !thread?.messages.length) {
172
+ return;
173
+ }
174
+ removeTip();
175
+ activeAnchorId = anchorId;
176
+ tip = document.createElement("div");
177
+ tip.className = `de-comment-hover-tip${thread?.status === "resolved" ? " de-comment-hover-tip--resolved" : ""}`;
178
+ tip.setAttribute("role", "tooltip");
179
+ const title = document.createElement("p");
180
+ title.className = "de-comment-hover-tip__title";
181
+ title.textContent = thread?.status === "resolved" ? "Resolved" : "Open";
182
+ tip.appendChild(title);
183
+ if (highlighted) {
184
+ const excerpt = document.createElement("p");
185
+ excerpt.className = "de-comment-hover-tip__highlight";
186
+ excerpt.textContent = highlighted;
187
+ tip.appendChild(excerpt);
188
+ }
189
+ const messages = thread?.messages ?? [];
190
+ for (const msg of messages.slice(-3)) {
191
+ const line = document.createElement("p");
192
+ line.className = "de-comment-hover-tip__msg";
193
+ line.innerHTML = `<strong>${escapeHtml(msg.author)}:</strong> ${escapeHtml(msg.body)}`;
194
+ tip.appendChild(line);
195
+ }
196
+ if (getComputedStyle(host).position === "static") {
197
+ host.style.position = "relative";
198
+ }
199
+ tip.addEventListener("mouseenter", () => window.clearTimeout(hideTimer));
200
+ tip.addEventListener("mouseleave", () => {
201
+ hideTimer = window.setTimeout(removeTip, 120);
202
+ });
203
+ host.appendChild(tip);
204
+ positionTip(anchor);
205
+ };
206
+ const onPointerOver = (event) => {
207
+ const anchor = event.target?.closest("[data-de-comment-id]");
208
+ if (!anchor || !contentEl.contains(anchor)) {
209
+ return;
210
+ }
211
+ const anchorId = anchor.getAttribute("data-de-comment-id");
212
+ if (!anchorId) {
213
+ return;
214
+ }
215
+ window.clearTimeout(hideTimer);
216
+ window.clearTimeout(showTimer);
217
+ showTimer = window.setTimeout(() => {
218
+ if (activeAnchorId !== anchorId) {
219
+ showTip(anchor, anchorId);
220
+ }
221
+ else {
222
+ positionTip(anchor);
223
+ }
224
+ }, 100);
225
+ };
226
+ const onPointerOut = (event) => {
227
+ const related = event.relatedTarget;
228
+ if (tip?.contains(related)) {
229
+ return;
230
+ }
231
+ const anchor = event.target?.closest("[data-de-comment-id]");
232
+ if (!anchor) {
233
+ return;
234
+ }
235
+ if (related && anchor.contains(related)) {
236
+ return;
237
+ }
238
+ window.clearTimeout(showTimer);
239
+ hideTimer = window.setTimeout(removeTip, 120);
240
+ };
241
+ const onScroll = () => {
242
+ if (!tip || !activeAnchorId) {
243
+ return;
244
+ }
245
+ const anchor = contentEl.querySelector(`[data-de-comment-id="${activeAnchorId}"]`);
246
+ if (anchor) {
247
+ positionTip(anchor);
248
+ }
249
+ else {
250
+ removeTip();
251
+ }
252
+ };
253
+ contentEl.addEventListener("pointerover", onPointerOver);
254
+ contentEl.addEventListener("pointerout", onPointerOut);
255
+ host.addEventListener("scroll", onScroll, { passive: true });
256
+ contentEl.addEventListener("scroll", onScroll, { passive: true });
257
+ return () => {
258
+ window.clearTimeout(showTimer);
259
+ window.clearTimeout(hideTimer);
260
+ contentEl.removeEventListener("pointerover", onPointerOver);
261
+ contentEl.removeEventListener("pointerout", onPointerOut);
262
+ host.removeEventListener("scroll", onScroll);
263
+ contentEl.removeEventListener("scroll", onScroll);
264
+ removeTip();
265
+ };
266
+ }
267
+ //# sourceMappingURL=comment-popover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-popover.js","sourceRoot":"","sources":["../src/comment-popover.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { CommentThread } from "./types.js";
2
+ export type CommentExportThread = Pick<CommentThread, "id" | "excerpt" | "status" | "anchorId" | "messages">;
3
+ /** Build a comments aside for preview / print / DOCX export HTML. */
4
+ export declare function buildCommentsExportHtml(threads: CommentExportThread[]): string;
5
+ //# sourceMappingURL=export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC,CAAC;AAU7G,qEAAqE;AACrE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,MAAM,CAyB9E"}
package/dist/export.js ADDED
@@ -0,0 +1,30 @@
1
+ function escapeHtml(text) {
2
+ return text
3
+ .replace(/&/g, "&amp;")
4
+ .replace(/</g, "&lt;")
5
+ .replace(/>/g, "&gt;")
6
+ .replace(/"/g, "&quot;");
7
+ }
8
+ /** Build a comments aside for preview / print / DOCX export HTML. */
9
+ export function buildCommentsExportHtml(threads) {
10
+ if (!threads.length) {
11
+ return "";
12
+ }
13
+ const items = threads
14
+ .map((thread) => {
15
+ const resolved = thread.status === "resolved";
16
+ const excerpt = thread.excerpt
17
+ ? `<p class="de-comments-export__excerpt">"${escapeHtml(thread.excerpt)}"</p>`
18
+ : thread.anchorId
19
+ ? `<p class="de-comments-export__excerpt">(anchored selection)</p>`
20
+ : "";
21
+ const messages = thread.messages
22
+ .map((msg) => `<p class="de-comments-export__msg"><strong>${escapeHtml(msg.author)}:</strong> ${escapeHtml(msg.body)}</p>`)
23
+ .join("");
24
+ const status = `<p class="de-comments-export__status">${resolved ? "Resolved" : "Open"}</p>`;
25
+ return `<article class="de-comments-export__thread${resolved ? " de-comments-export__thread--resolved" : ""}"${thread.anchorId ? ` data-comment-id="${escapeHtml(thread.anchorId)}"` : ""}>${excerpt}${messages}${status}</article>`;
26
+ })
27
+ .join("");
28
+ return `<aside class="de-comments-export" aria-label="Document comments"><h2 class="de-comments-export__title">Comments</h2>${items}</aside>`;
29
+ }
30
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"AAIA,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI;SACR,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,uBAAuB,CAAC,OAA8B;IACpE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,KAAK,GAAG,OAAO;SAClB,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QACd,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC;QAC9C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO;YAC5B,CAAC,CAAC,2CAA2C,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO;YAC9E,CAAC,CAAC,MAAM,CAAC,QAAQ;gBACf,CAAC,CAAC,iEAAiE;gBACnE,CAAC,CAAC,EAAE,CAAC;QACT,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ;aAC7B,GAAG,CACF,CAAC,GAAG,EAAE,EAAE,CACN,8CAA8C,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAC/G;aACA,IAAI,CAAC,EAAE,CAAC,CAAC;QACZ,MAAM,MAAM,GAAG,yCAAyC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC;QAC7F,OAAO,6CAA6C,QAAQ,CAAC,CAAC,CAAC,uCAAuC,CAAC,CAAC,CAAC,EAAE,IACzG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,qBAAqB,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAC1E,IAAI,OAAO,GAAG,QAAQ,GAAG,MAAM,YAAY,CAAC;IAC9C,CAAC,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;IACZ,OAAO,uHAAuH,KAAK,UAAU,CAAC;AAChJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { EditorPlugin } from "@richhtmleditor/core";
2
+ import type { CommentsPluginOptions } from "./types.js";
3
+ export type { CommentStatus, CommentThread, CommentsPluginOptions } from "./types.js";
4
+ /**
5
+ * Enterprise review/comments workflow — requires `features.comments`.
6
+ * Wraps the current selection in a document anchor and links threads to
7
+ * <span data-de-comment-id> markers in exported HTML.
8
+ */
9
+ export declare function createCommentsPlugin(options?: CommentsPluginOptions): EditorPlugin;
10
+ export { buildCommentsExportHtml } from "./export.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,YAAY,EAAyB,MAAM,sBAAsB,CAAC;AAWhG,OAAO,KAAK,EAAiB,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEvE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAkWtF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,YAAY,CAwItF;AAED,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}