@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 +121 -0
- package/dist/anchors.d.ts +7 -0
- package/dist/anchors.d.ts.map +1 -0
- package/dist/anchors.js +60 -0
- package/dist/anchors.js.map +1 -0
- package/dist/comment-popover.d.ts +36 -0
- package/dist/comment-popover.d.ts.map +1 -0
- package/dist/comment-popover.js +267 -0
- package/dist/comment-popover.js.map +1 -0
- package/dist/export.d.ts +5 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +30 -0
- package/dist/export.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +419 -0
- package/dist/index.js.map +1 -0
- package/dist/selection-comment-chip.d.ts +10 -0
- package/dist/selection-comment-chip.d.ts.map +1 -0
- package/dist/selection-comment-chip.js +118 -0
- package/dist/selection-comment-chip.js.map +1 -0
- package/dist/styles.d.ts +3 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +62 -0
- package/dist/styles.js.map +1 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +39 -0
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"}
|
package/dist/anchors.js
ADDED
|
@@ -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, "&")
|
|
4
|
+
.replace(/</g, "<")
|
|
5
|
+
.replace(/>/g, ">")
|
|
6
|
+
.replace(/"/g, """);
|
|
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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"}
|
package/dist/export.d.ts
ADDED
|
@@ -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, "&")
|
|
4
|
+
.replace(/</g, "<")
|
|
5
|
+
.replace(/>/g, ">")
|
|
6
|
+
.replace(/"/g, """);
|
|
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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|