quoin-editor 0.4.2 → 1.0.1
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/CHANGELOG.md +251 -0
- package/MIGRATION.md +233 -0
- package/README.md +78 -716
- package/dist/ai.cjs +1148 -0
- package/dist/ai.cjs.map +1 -0
- package/dist/ai.d.cts +68 -0
- package/dist/ai.d.ts +68 -0
- package/dist/ai.js +1148 -0
- package/dist/ai.js.map +1 -0
- package/dist/catalogue.cjs +59 -0
- package/dist/catalogue.cjs.map +1 -0
- package/dist/catalogue.d.cts +39 -0
- package/dist/catalogue.d.ts +39 -0
- package/dist/catalogue.js +59 -0
- package/dist/catalogue.js.map +1 -0
- package/dist/chunk-26Q4LSIU.js +100 -0
- package/dist/chunk-26Q4LSIU.js.map +1 -0
- package/dist/chunk-56C7GCKO.cjs +1385 -0
- package/dist/chunk-56C7GCKO.cjs.map +1 -0
- package/dist/chunk-5M4QG3ZC.cjs +3303 -0
- package/dist/chunk-5M4QG3ZC.cjs.map +1 -0
- package/dist/chunk-5R3ISX4A.cjs +6643 -0
- package/dist/chunk-5R3ISX4A.cjs.map +1 -0
- package/dist/chunk-62WKUAQY.js +3335 -0
- package/dist/chunk-62WKUAQY.js.map +1 -0
- package/dist/chunk-6LMS5J3Y.js +320 -0
- package/dist/chunk-6LMS5J3Y.js.map +1 -0
- package/dist/chunk-6PKVJZQV.cjs +100 -0
- package/dist/chunk-6PKVJZQV.cjs.map +1 -0
- package/dist/chunk-6YSGUAVW.js +376 -0
- package/dist/chunk-6YSGUAVW.js.map +1 -0
- package/dist/chunk-75C6PX2L.cjs +239 -0
- package/dist/chunk-75C6PX2L.cjs.map +1 -0
- package/dist/chunk-A4IJSJKL.js +598 -0
- package/dist/chunk-A4IJSJKL.js.map +1 -0
- package/dist/chunk-BTBHHBVR.cjs +3335 -0
- package/dist/chunk-BTBHHBVR.cjs.map +1 -0
- package/dist/chunk-CLTHD5GO.cjs +34 -0
- package/dist/chunk-CLTHD5GO.cjs.map +1 -0
- package/dist/chunk-EH5KXQXY.cjs +62 -0
- package/dist/chunk-EH5KXQXY.cjs.map +1 -0
- package/dist/chunk-ESMAW4NK.cjs +184 -0
- package/dist/chunk-ESMAW4NK.cjs.map +1 -0
- package/dist/chunk-EYFCOV3T.cjs +30 -0
- package/dist/chunk-EYFCOV3T.cjs.map +1 -0
- package/dist/chunk-F5V3IEEE.cjs +326 -0
- package/dist/chunk-F5V3IEEE.cjs.map +1 -0
- package/dist/chunk-GPIDU3GN.js +239 -0
- package/dist/chunk-GPIDU3GN.js.map +1 -0
- package/dist/chunk-HOIDJEVI.cjs +4063 -0
- package/dist/chunk-HOIDJEVI.cjs.map +1 -0
- package/dist/chunk-JJ3RASQS.cjs +706 -0
- package/dist/chunk-JJ3RASQS.cjs.map +1 -0
- package/dist/chunk-LE3F7WIP.js +6643 -0
- package/dist/chunk-LE3F7WIP.js.map +1 -0
- package/dist/chunk-LE5XMIFJ.cjs +14 -0
- package/dist/chunk-LE5XMIFJ.cjs.map +1 -0
- package/dist/chunk-LMAEPFTX.js +30 -0
- package/dist/chunk-LMAEPFTX.js.map +1 -0
- package/dist/chunk-LV6EYPJO.js +34 -0
- package/dist/chunk-LV6EYPJO.js.map +1 -0
- package/dist/chunk-MLRAVUFG.cjs +376 -0
- package/dist/chunk-MLRAVUFG.cjs.map +1 -0
- package/dist/chunk-MQUCCSWG.js +14 -0
- package/dist/chunk-MQUCCSWG.js.map +1 -0
- package/dist/chunk-O4Z2WMFF.js +62 -0
- package/dist/chunk-O4Z2WMFF.js.map +1 -0
- package/dist/chunk-OCCKQHCY.js +25 -0
- package/dist/chunk-OCCKQHCY.js.map +1 -0
- package/dist/chunk-PGSS4IOS.js +706 -0
- package/dist/chunk-PGSS4IOS.js.map +1 -0
- package/dist/chunk-PIWRSX7U.js +562 -0
- package/dist/chunk-PIWRSX7U.js.map +1 -0
- package/dist/chunk-PX6LZMH4.cjs +598 -0
- package/dist/chunk-PX6LZMH4.cjs.map +1 -0
- package/dist/chunk-QXG3HWDS.js +184 -0
- package/dist/chunk-QXG3HWDS.js.map +1 -0
- package/dist/chunk-RQW3P3GH.js +504 -0
- package/dist/chunk-RQW3P3GH.js.map +1 -0
- package/dist/chunk-SSIRYUCK.js +3303 -0
- package/dist/chunk-SSIRYUCK.js.map +1 -0
- package/dist/chunk-UBNG5YCG.js +326 -0
- package/dist/chunk-UBNG5YCG.js.map +1 -0
- package/dist/chunk-UCXLRRWY.js +1385 -0
- package/dist/chunk-UCXLRRWY.js.map +1 -0
- package/dist/chunk-UDAVVEFC.cjs +562 -0
- package/dist/chunk-UDAVVEFC.cjs.map +1 -0
- package/dist/chunk-UPSLDTLT.js +4063 -0
- package/dist/chunk-UPSLDTLT.js.map +1 -0
- package/dist/chunk-UWNZG2SD.cjs +25 -0
- package/dist/chunk-UWNZG2SD.cjs.map +1 -0
- package/dist/chunk-V3KLMSRO.cjs +504 -0
- package/dist/chunk-V3KLMSRO.cjs.map +1 -0
- package/dist/chunk-VACTAZWS.cjs +249 -0
- package/dist/chunk-VACTAZWS.cjs.map +1 -0
- package/dist/chunk-WMN6566P.js +249 -0
- package/dist/chunk-WMN6566P.js.map +1 -0
- package/dist/chunk-XX5FTPJR.cjs +320 -0
- package/dist/chunk-XX5FTPJR.cjs.map +1 -0
- package/dist/comments.cjs +1008 -0
- package/dist/comments.cjs.map +1 -0
- package/dist/comments.d.cts +93 -0
- package/dist/comments.d.ts +93 -0
- package/dist/comments.js +1008 -0
- package/dist/comments.js.map +1 -0
- package/dist/diff.cjs +346 -0
- package/dist/diff.cjs.map +1 -0
- package/dist/diff.d.cts +67 -0
- package/dist/diff.d.ts +67 -0
- package/dist/diff.js +346 -0
- package/dist/diff.js.map +1 -0
- package/dist/docx.cjs +874 -0
- package/dist/docx.cjs.map +1 -0
- package/dist/docx.d.cts +33 -0
- package/dist/docx.d.ts +33 -0
- package/dist/docx.js +874 -0
- package/dist/docx.js.map +1 -0
- package/dist/embed-provider-jXwlRmnp.d.cts +19 -0
- package/dist/embed-provider-jXwlRmnp.d.ts +19 -0
- package/dist/embed.cjs +17 -0
- package/dist/embed.cjs.map +1 -0
- package/dist/embed.d.cts +56 -0
- package/dist/embed.d.ts +56 -0
- package/dist/embed.js +17 -0
- package/dist/embed.js.map +1 -0
- package/dist/emoji-data.cjs +1968 -0
- package/dist/emoji-data.cjs.map +1 -0
- package/dist/emoji-data.js +1968 -0
- package/dist/emoji-data.js.map +1 -0
- package/dist/emoji-extension-CmPlRssm.d.ts +46 -0
- package/dist/emoji-extension-zJ6F7hW8.d.cts +46 -0
- package/dist/emoji.cjs +18 -0
- package/dist/emoji.cjs.map +1 -0
- package/dist/emoji.d.cts +31 -0
- package/dist/emoji.d.ts +31 -0
- package/dist/emoji.js +18 -0
- package/dist/emoji.js.map +1 -0
- package/dist/engine.cjs +119 -0
- package/dist/engine.cjs.map +1 -0
- package/dist/engine.d.cts +144 -0
- package/dist/engine.d.ts +144 -0
- package/dist/engine.js +119 -0
- package/dist/engine.js.map +1 -0
- package/dist/find-extension-BcRyE9K4.d.cts +41 -0
- package/dist/find-extension-CmFehyul.d.ts +41 -0
- package/dist/find.cjs +20 -0
- package/dist/find.cjs.map +1 -0
- package/dist/find.d.cts +44 -0
- package/dist/find.d.ts +44 -0
- package/dist/find.js +20 -0
- package/dist/find.js.map +1 -0
- package/dist/headless.cjs +151 -518
- package/dist/headless.cjs.map +1 -1
- package/dist/headless.d.cts +102 -69
- package/dist/headless.d.ts +102 -69
- package/dist/headless.js +149 -467
- package/dist/headless.js.map +1 -1
- package/dist/index-CFPmCee4.d.cts +68 -0
- package/dist/index-DiCGV60d.d.ts +68 -0
- package/dist/index.cjs +183 -2753
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +345 -253
- package/dist/index.d.ts +345 -253
- package/dist/index.js +168 -2696
- package/dist/index.js.map +1 -1
- package/dist/math.cjs +18 -0
- package/dist/math.cjs.map +1 -0
- package/dist/math.d.cts +49 -0
- package/dist/math.d.ts +49 -0
- package/dist/math.js +18 -0
- package/dist/math.js.map +1 -0
- package/dist/mention.cjs +18 -0
- package/dist/mention.cjs.map +1 -0
- package/dist/mention.d.cts +50 -0
- package/dist/mention.d.ts +50 -0
- package/dist/mention.js +18 -0
- package/dist/mention.js.map +1 -0
- package/dist/presence.cjs +562 -0
- package/dist/presence.cjs.map +1 -0
- package/dist/presence.d.cts +30 -0
- package/dist/presence.d.ts +30 -0
- package/dist/presence.js +562 -0
- package/dist/presence.js.map +1 -0
- package/dist/registry-CWBOSAHm.d.cts +121 -0
- package/dist/registry-CWBOSAHm.d.ts +121 -0
- package/dist/stats-extension-DisOEbbI.d.ts +40 -0
- package/dist/stats-extension-Dt1lgP3m.d.cts +40 -0
- package/dist/stats.cjs +19 -0
- package/dist/stats.cjs.map +1 -0
- package/dist/stats.d.cts +19 -0
- package/dist/stats.d.ts +19 -0
- package/dist/stats.js +19 -0
- package/dist/stats.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/table-extension-BTMJnT7J.d.ts +57 -0
- package/dist/table-extension-DxZz3jqp.d.cts +57 -0
- package/dist/table.cjs +50 -0
- package/dist/table.cjs.map +1 -0
- package/dist/table.d.cts +57 -0
- package/dist/table.d.ts +57 -0
- package/dist/table.js +50 -0
- package/dist/table.js.map +1 -0
- package/dist/to-html-C5t7xyaM.d.ts +21 -0
- package/dist/to-html-CNCITCln.d.cts +21 -0
- package/dist/toc-extension-CR9U-l6j.d.ts +25 -0
- package/dist/toc-extension-DGHywOBv.d.cts +25 -0
- package/dist/toc.cjs +18 -0
- package/dist/toc.cjs.map +1 -0
- package/dist/toc.d.cts +24 -0
- package/dist/toc.d.ts +24 -0
- package/dist/toc.js +18 -0
- package/dist/toc.js.map +1 -0
- package/dist/types-C6_QkXzy.d.ts +358 -0
- package/dist/types-CJ7Gq3GX.d.cts +358 -0
- package/dist/types-DULc8wwg.d.cts +120 -0
- package/dist/types-DULc8wwg.d.ts +120 -0
- package/dist/view-controller-BD1KwYfE.d.cts +105 -0
- package/dist/view-controller-y4cb60ue.d.ts +105 -0
- package/dist/view.cjs +23 -0
- package/dist/view.cjs.map +1 -0
- package/dist/view.d.cts +40 -0
- package/dist/view.d.ts +40 -0
- package/dist/view.js +23 -0
- package/dist/view.js.map +1 -0
- package/dist/yjs.cjs +864 -0
- package/dist/yjs.cjs.map +1 -0
- package/dist/yjs.d.cts +22 -0
- package/dist/yjs.d.ts +22 -0
- package/dist/yjs.js +864 -0
- package/dist/yjs.js.map +1 -0
- package/package.json +217 -18
package/dist/comments.js
ADDED
|
@@ -0,0 +1,1008 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
webAddress
|
|
4
|
+
} from "./chunk-OCCKQHCY.js";
|
|
5
|
+
import {
|
|
6
|
+
ownsNode
|
|
7
|
+
} from "./chunk-LV6EYPJO.js";
|
|
8
|
+
import {
|
|
9
|
+
Button,
|
|
10
|
+
FOCUS_RING,
|
|
11
|
+
Popover,
|
|
12
|
+
PopoverAnchor,
|
|
13
|
+
PopoverContent,
|
|
14
|
+
cn,
|
|
15
|
+
rangeRect,
|
|
16
|
+
toError,
|
|
17
|
+
useQuoin,
|
|
18
|
+
virtualAnchor
|
|
19
|
+
} from "./chunk-LE3F7WIP.js";
|
|
20
|
+
import {
|
|
21
|
+
useEditorState,
|
|
22
|
+
useIsomorphicLayoutEffect,
|
|
23
|
+
useReadOnly
|
|
24
|
+
} from "./chunk-SSIRYUCK.js";
|
|
25
|
+
import "./chunk-O4Z2WMFF.js";
|
|
26
|
+
import {
|
|
27
|
+
Transaction,
|
|
28
|
+
addMarkStep,
|
|
29
|
+
comparePos,
|
|
30
|
+
createPluginKey,
|
|
31
|
+
defineExtension,
|
|
32
|
+
getPluginState,
|
|
33
|
+
isCollapsed,
|
|
34
|
+
markRanges,
|
|
35
|
+
removeMarkStep,
|
|
36
|
+
sameSelection,
|
|
37
|
+
textSelection
|
|
38
|
+
} from "./chunk-UPSLDTLT.js";
|
|
39
|
+
import "./chunk-6LMS5J3Y.js";
|
|
40
|
+
import {
|
|
41
|
+
blockById,
|
|
42
|
+
indexOfBlock,
|
|
43
|
+
inlineText,
|
|
44
|
+
nodeLength,
|
|
45
|
+
sliceInline
|
|
46
|
+
} from "./chunk-62WKUAQY.js";
|
|
47
|
+
|
|
48
|
+
// src/extensions/comments/comments-extension.tsx
|
|
49
|
+
import { MessageSquarePlus } from "lucide-react";
|
|
50
|
+
|
|
51
|
+
// src/extensions/comments/comment-anchors.ts
|
|
52
|
+
var idsOf = (node) => node?.marks.flatMap((mark) => mark.type === "comment" && mark.attrs?.id ? [mark.attrs.id] : []) ?? [];
|
|
53
|
+
function addRegion(anchors, block, region, inline2) {
|
|
54
|
+
let position = 0;
|
|
55
|
+
for (const node of inline2) {
|
|
56
|
+
const from = position;
|
|
57
|
+
position += nodeLength(node);
|
|
58
|
+
for (const id of idsOf(node)) {
|
|
59
|
+
const ranges = anchors.get(id) ?? [];
|
|
60
|
+
const last = ranges[ranges.length - 1];
|
|
61
|
+
const continues = last?.block === block && last.region === region && last.to === from;
|
|
62
|
+
if (continues) ranges[ranges.length - 1] = { ...last, to: position };
|
|
63
|
+
else ranges.push({ block, region, from, to: position });
|
|
64
|
+
anchors.set(id, ranges);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
var read = /* @__PURE__ */ new WeakMap();
|
|
69
|
+
function commentAnchors(doc) {
|
|
70
|
+
let anchors = read.get(doc);
|
|
71
|
+
if (anchors === void 0) {
|
|
72
|
+
const found = /* @__PURE__ */ new Map();
|
|
73
|
+
for (const block of doc.blocks) {
|
|
74
|
+
for (const [region, inline2] of Object.entries(block.regions)) addRegion(found, block.id, region, inline2);
|
|
75
|
+
}
|
|
76
|
+
anchors = found;
|
|
77
|
+
read.set(doc, anchors);
|
|
78
|
+
}
|
|
79
|
+
return anchors;
|
|
80
|
+
}
|
|
81
|
+
var atomLabel = (node) => node.kind === "atom" ? node.attrs.label ?? "" : "";
|
|
82
|
+
function anchorText(doc, ranges) {
|
|
83
|
+
return ranges.map(({ block, region, from, to }) => {
|
|
84
|
+
const inline2 = blockById(doc, block)?.regions[region] ?? [];
|
|
85
|
+
return inlineText(sliceInline(inline2, from, to), atomLabel);
|
|
86
|
+
}).join(" ");
|
|
87
|
+
}
|
|
88
|
+
function commentIdsAt(doc, { block, region, offset }) {
|
|
89
|
+
const inline2 = blockById(doc, block)?.regions[region] ?? [];
|
|
90
|
+
const ids = /* @__PURE__ */ new Set();
|
|
91
|
+
let position = 0;
|
|
92
|
+
for (const node of inline2) {
|
|
93
|
+
const end = position + nodeLength(node);
|
|
94
|
+
if (position <= offset && offset <= end && end > position) idsOf(node).forEach((id) => ids.add(id));
|
|
95
|
+
position = end;
|
|
96
|
+
}
|
|
97
|
+
return [...ids].sort();
|
|
98
|
+
}
|
|
99
|
+
function threadRemoval(state, id) {
|
|
100
|
+
const tr = new Transaction(state).setOrigin("command");
|
|
101
|
+
for (const { block, region, from, to } of commentAnchors(state.doc).get(id) ?? []) {
|
|
102
|
+
tr.step(removeMarkStep(block, region, from, to, { type: "comment", attrs: { id } }));
|
|
103
|
+
}
|
|
104
|
+
return tr;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/extensions/comments/pending-ranges.ts
|
|
108
|
+
function mapPendingRanges(tr, ranges, state) {
|
|
109
|
+
return ranges.flatMap((range) => {
|
|
110
|
+
const from = tr.mapPos({ block: range.block, region: range.region, offset: range.from }, 1);
|
|
111
|
+
const to = tr.mapPos({ block: range.block, region: range.region, offset: range.to }, -1);
|
|
112
|
+
const { index } = state.doc;
|
|
113
|
+
if (!index.has(from.block) || !index.has(to.block) || comparePos(state.doc, from, to) >= 0) return [];
|
|
114
|
+
return markRanges({ ...state, selection: textSelection(from, to) });
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/extensions/comments/comments-plugin.ts
|
|
119
|
+
var isEnded = (ended, id, current) => ended.some((thread) => thread.id === id && thread.seen?.resolved === current?.resolved);
|
|
120
|
+
var commentsKey = createPluginKey("comments");
|
|
121
|
+
var COMMENTS = "comments";
|
|
122
|
+
var IDLE = { pending: [], active: null, open: null, ended: [] };
|
|
123
|
+
function act(value, action) {
|
|
124
|
+
switch (action.type) {
|
|
125
|
+
case "pending":
|
|
126
|
+
return { ...value, pending: [...value.pending, action.pending] };
|
|
127
|
+
case "settled": {
|
|
128
|
+
const pending = value.pending.filter(({ key }) => key !== action.key);
|
|
129
|
+
if (action.open === void 0) return { ...value, pending };
|
|
130
|
+
return { ...value, pending, active: action.open.ids[0] ?? null, open: action.open };
|
|
131
|
+
}
|
|
132
|
+
case "activate":
|
|
133
|
+
return value.active === action.id ? value : { ...value, active: action.id };
|
|
134
|
+
case "open":
|
|
135
|
+
return { ...value, active: action.open.ids.length === 1 ? action.open.ids[0] : value.active, open: action.open };
|
|
136
|
+
case "close":
|
|
137
|
+
return value.open === null ? value : { ...value, active: null, open: null };
|
|
138
|
+
case "forget": {
|
|
139
|
+
const active = value.active === action.id ? null : value.active;
|
|
140
|
+
const open = value.open?.ids.includes(action.id) ? null : value.open;
|
|
141
|
+
const ended = [...value.ended.filter(({ id }) => id !== action.id), { id: action.id, seen: action.seen }];
|
|
142
|
+
return { ...value, active, open, ended };
|
|
143
|
+
}
|
|
144
|
+
case "caught":
|
|
145
|
+
return { ...value, ended: action.kept };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function afterEdit(tr, value, state) {
|
|
149
|
+
if (!tr.docChanged || value.pending.length === 0) return value;
|
|
150
|
+
const pending = value.pending.map((anchor) => ({ ...anchor, ranges: mapPendingRanges(tr, anchor.ranges, state) }));
|
|
151
|
+
return { ...value, pending };
|
|
152
|
+
}
|
|
153
|
+
var WRITER_MOVES = ["input", "history", "drop"];
|
|
154
|
+
function afterMove(tr, value, state) {
|
|
155
|
+
const { open } = value;
|
|
156
|
+
if (open === null || open.focus || !tr.selectionSet || !WRITER_MOVES.includes(tr.origin)) return value;
|
|
157
|
+
const { selection } = state;
|
|
158
|
+
const here = selection?.kind === "text" ? commentIdsAt(state.doc, selection.head) : [];
|
|
159
|
+
return open.ids.some((id) => here.includes(id)) ? value : { ...value, active: null, open: null };
|
|
160
|
+
}
|
|
161
|
+
var commentsPlugin = {
|
|
162
|
+
key: commentsKey,
|
|
163
|
+
init: () => IDLE,
|
|
164
|
+
apply(tr, value, _oldState, newState) {
|
|
165
|
+
const edited = afterMove(tr, afterEdit(tr, value, newState), newState);
|
|
166
|
+
const action = tr.getMeta(COMMENTS);
|
|
167
|
+
return action === void 0 ? edited : act(edited, action);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// src/extensions/comments/comment-create.ts
|
|
172
|
+
var nextKey = 0;
|
|
173
|
+
var hasCommentableText = (state) => state.selection?.kind === "text" && markRanges(state).length > 0;
|
|
174
|
+
var requestComment = (state, dispatch) => {
|
|
175
|
+
const { selection } = state;
|
|
176
|
+
if (selection?.kind !== "text" || getPluginState(state, commentsKey).pending.length > 0) return false;
|
|
177
|
+
const ranges = markRanges(state);
|
|
178
|
+
if (ranges.length === 0) return false;
|
|
179
|
+
if (dispatch === void 0) return true;
|
|
180
|
+
const pending = { key: ++nextKey, ranges, selection };
|
|
181
|
+
const action = { type: "pending", pending };
|
|
182
|
+
dispatch(new Transaction(state).setMeta(COMMENTS, action).setMeta("addToHistory", false));
|
|
183
|
+
return true;
|
|
184
|
+
};
|
|
185
|
+
function settledTransaction(editor, key, id) {
|
|
186
|
+
const pending = getPluginState(editor.state, commentsKey).pending.find((anchor) => anchor.key === key);
|
|
187
|
+
const tr = editor.createTransaction().setOrigin("command");
|
|
188
|
+
const ranges = id === null ? [] : pending?.ranges ?? [];
|
|
189
|
+
for (const { block, region, from, to } of ranges) {
|
|
190
|
+
tr.step(addMarkStep(block, region, from, to, { type: "comment", attrs: { id } }));
|
|
191
|
+
}
|
|
192
|
+
const untouched = pending !== void 0 && sameSelection(editor.state.selection, pending.selection);
|
|
193
|
+
const open = id !== null && ranges.length > 0 && untouched ? { ids: [id], focus: true } : void 0;
|
|
194
|
+
const action = { type: "settled", key, open };
|
|
195
|
+
return tr.setMeta(COMMENTS, action);
|
|
196
|
+
}
|
|
197
|
+
function create(editor, pending, onCreate, link) {
|
|
198
|
+
const input = { anchorText: anchorText(editor.state.doc, pending.ranges), blockId: pending.ranges[0].block };
|
|
199
|
+
const settle = (id, error) => {
|
|
200
|
+
if (link.mounts === 0) return;
|
|
201
|
+
const tr = settledTransaction(editor, pending.key, id);
|
|
202
|
+
editor.dispatch(tr);
|
|
203
|
+
if (id === null) link.report(toError(error));
|
|
204
|
+
else if (!tr.docChanged) link.report(new Error(`Comment thread ${id} has no text: it was deleted meanwhile`));
|
|
205
|
+
};
|
|
206
|
+
new Promise((resolve) => resolve(onCreate(input))).then(
|
|
207
|
+
(id) => typeof id === "string" && id !== "" ? settle(id) : settle(null, new Error("onCreate gave no thread id")),
|
|
208
|
+
(error) => settle(null, error)
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
function watchCreates(editor, onCreate, link) {
|
|
212
|
+
return editor.subscribe((_, tr) => {
|
|
213
|
+
const action = tr?.getMeta(COMMENTS);
|
|
214
|
+
if (action?.type === "pending") create(editor, action.pending, onCreate, link);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/extensions/comments/comment-open.ts
|
|
219
|
+
function openIds(state, threads, ids) {
|
|
220
|
+
const { ended } = getPluginState(state, commentsKey);
|
|
221
|
+
return ids.filter((id) => {
|
|
222
|
+
const thread = threads.find(id);
|
|
223
|
+
return thread?.resolved === false && !isEnded(ended, id, thread);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function commentsTransaction(state, action) {
|
|
227
|
+
return new Transaction(state).setMeta(COMMENTS, action).setMeta("addToHistory", false);
|
|
228
|
+
}
|
|
229
|
+
function commentCommand(threads) {
|
|
230
|
+
return (state, dispatch) => {
|
|
231
|
+
if (requestComment(state, dispatch)) return true;
|
|
232
|
+
const { selection } = state;
|
|
233
|
+
if (selection?.kind !== "text" || !isCollapsed(selection)) return false;
|
|
234
|
+
const ids = openIds(state, threads, commentIdsAt(state.doc, selection.head));
|
|
235
|
+
if (ids.length === 0) return false;
|
|
236
|
+
dispatch?.(commentsTransaction(state, { type: "open", open: { ids, focus: true } }));
|
|
237
|
+
return true;
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/extensions/comments/comments-labels.ts
|
|
242
|
+
var DATE = new Intl.DateTimeFormat("en", { dateStyle: "medium", timeStyle: "short" });
|
|
243
|
+
function englishDate(createdAt) {
|
|
244
|
+
const date = new Date(createdAt);
|
|
245
|
+
return Number.isNaN(date.getTime()) ? createdAt : DATE.format(date);
|
|
246
|
+
}
|
|
247
|
+
var englishCommentsLabels = {
|
|
248
|
+
comment: "Comment",
|
|
249
|
+
comments: "Comments",
|
|
250
|
+
showComments: (count) => `Comments (${count})`,
|
|
251
|
+
thread: (anchorText2) => `Comment on "${anchorText2}"`,
|
|
252
|
+
threadsHere: "Comments on this text",
|
|
253
|
+
textRemoved: "Text removed",
|
|
254
|
+
addComment: "Add a comment",
|
|
255
|
+
reply: "Reply",
|
|
256
|
+
send: "Send",
|
|
257
|
+
resolve: "Resolve",
|
|
258
|
+
delete: "Delete",
|
|
259
|
+
close: "Close",
|
|
260
|
+
commentCount: (count) => `${count} ${count === 1 ? "comment" : "comments"}`,
|
|
261
|
+
postedAt: englishDate
|
|
262
|
+
};
|
|
263
|
+
function resolveCommentsLabels(overrides) {
|
|
264
|
+
const given = Object.entries(overrides ?? {}).filter(([, value]) => value !== void 0);
|
|
265
|
+
return given.length === 0 ? englishCommentsLabels : { ...englishCommentsLabels, ...Object.fromEntries(given) };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/extensions/comments/comments-link.ts
|
|
269
|
+
var links = /* @__PURE__ */ new WeakMap();
|
|
270
|
+
function linkOf(editor) {
|
|
271
|
+
let link = links.get(editor);
|
|
272
|
+
if (link === void 0) {
|
|
273
|
+
link = { mounts: 0, report: (error) => console.error(error), stopWatching: null };
|
|
274
|
+
links.set(editor, link);
|
|
275
|
+
}
|
|
276
|
+
return link;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/extensions/comments/CommentsOverlay.tsx
|
|
280
|
+
import { useCallback, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef6, useState as useState5 } from "react";
|
|
281
|
+
|
|
282
|
+
// src/extensions/comments/CommentMarkers.tsx
|
|
283
|
+
import { MessageSquare } from "lucide-react";
|
|
284
|
+
|
|
285
|
+
// src/extensions/comments/rail-layout.ts
|
|
286
|
+
function stackTops(items, gap) {
|
|
287
|
+
let next = 0;
|
|
288
|
+
return items.map(({ top, height }) => {
|
|
289
|
+
const placed = Math.max(top ?? next, next);
|
|
290
|
+
next = placed + height + gap;
|
|
291
|
+
return placed;
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/extensions/comments/use-block-aligned.ts
|
|
296
|
+
function topOf(controller, root, block) {
|
|
297
|
+
const shell = block === "" ? void 0 : controller.blockElement(block);
|
|
298
|
+
if (shell === void 0) return null;
|
|
299
|
+
return shell.getBoundingClientRect().top - root.getBoundingClientRect().top - root.clientTop + root.scrollTop;
|
|
300
|
+
}
|
|
301
|
+
var HEIGHT = "--quoin-comments-height";
|
|
302
|
+
function place(controller, box, gap) {
|
|
303
|
+
const root = box.parentElement;
|
|
304
|
+
if (root === null) return;
|
|
305
|
+
const items = Array.from(box.querySelectorAll("[data-rail-item]"));
|
|
306
|
+
const tops = stackTops(
|
|
307
|
+
items.map((item) => ({ top: topOf(controller, root, item.dataset.block ?? ""), height: item.offsetHeight })),
|
|
308
|
+
gap
|
|
309
|
+
);
|
|
310
|
+
items.forEach((item, i) => item.style.transform = `translateY(${tops[i]}px)`);
|
|
311
|
+
const bottom = Math.max(0, ...items.map((item, i) => tops[i] + item.offsetHeight));
|
|
312
|
+
root.style.setProperty(HEIGHT, `${bottom}px`);
|
|
313
|
+
}
|
|
314
|
+
function useBlockAligned(controller, box, gap, key) {
|
|
315
|
+
useIsomorphicLayoutEffect(() => {
|
|
316
|
+
const element = box.current;
|
|
317
|
+
const view = element?.ownerDocument.defaultView;
|
|
318
|
+
if (!element || !view) return void 0;
|
|
319
|
+
const update = () => place(controller, element, gap);
|
|
320
|
+
update();
|
|
321
|
+
let frame = 0;
|
|
322
|
+
const later = () => {
|
|
323
|
+
view.cancelAnimationFrame(frame);
|
|
324
|
+
frame = view.requestAnimationFrame(update);
|
|
325
|
+
};
|
|
326
|
+
const unsubscribe = controller.editor.subscribe(later);
|
|
327
|
+
const observer = new ResizeObserver(later);
|
|
328
|
+
if (element.parentElement) observer.observe(element.parentElement);
|
|
329
|
+
element.querySelectorAll("[data-rail-item]").forEach((item) => observer.observe(item));
|
|
330
|
+
return () => {
|
|
331
|
+
view.cancelAnimationFrame(frame);
|
|
332
|
+
unsubscribe();
|
|
333
|
+
observer.disconnect();
|
|
334
|
+
element.parentElement?.style.removeProperty(HEIGHT);
|
|
335
|
+
};
|
|
336
|
+
}, [controller, box, gap, key]);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/extensions/comments/CommentMarkers.tsx
|
|
340
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
341
|
+
var GAP = 4;
|
|
342
|
+
function groupsOf(entries) {
|
|
343
|
+
const groups = /* @__PURE__ */ new Map();
|
|
344
|
+
for (const { thread, ranges } of entries) {
|
|
345
|
+
const block = ranges[0]?.block ?? "";
|
|
346
|
+
groups.set(block, [...groups.get(block) ?? [], thread.id]);
|
|
347
|
+
}
|
|
348
|
+
const removed = groups.get("");
|
|
349
|
+
groups.delete("");
|
|
350
|
+
return [...groups, ...removed ? [["", removed]] : []].map(([block, ids]) => ({ block, ids }));
|
|
351
|
+
}
|
|
352
|
+
function CommentMarkers({ env, box, entries, onOpen }) {
|
|
353
|
+
const groups = groupsOf(entries);
|
|
354
|
+
useBlockAligned(env.controller, box, GAP, groups.map(({ block, ids }) => `${block}:${ids}`).join(" "));
|
|
355
|
+
return /* @__PURE__ */ jsx("div", { ref: box, className: "quoin-comments-markers", children: groups.map(({ block, ids }) => /* @__PURE__ */ jsxs(
|
|
356
|
+
"button",
|
|
357
|
+
{
|
|
358
|
+
type: "button",
|
|
359
|
+
"data-rail-item": "",
|
|
360
|
+
"data-block": block,
|
|
361
|
+
"aria-label": env.labels.showComments(ids.length),
|
|
362
|
+
className: cn("quoin-comments-marker", FOCUS_RING),
|
|
363
|
+
onClick: (event) => onOpen(ids[0], event.currentTarget),
|
|
364
|
+
children: [
|
|
365
|
+
/* @__PURE__ */ jsx(MessageSquare, { "aria-hidden": "true" }),
|
|
366
|
+
ids.length
|
|
367
|
+
]
|
|
368
|
+
},
|
|
369
|
+
block
|
|
370
|
+
)) });
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/extensions/comments/ThreadCard.tsx
|
|
374
|
+
import { useRef as useRef3 } from "react";
|
|
375
|
+
|
|
376
|
+
// src/extensions/comments/ThreadView.tsx
|
|
377
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
|
|
378
|
+
|
|
379
|
+
// src/extensions/comments/comment-actions.ts
|
|
380
|
+
async function called(link, call) {
|
|
381
|
+
try {
|
|
382
|
+
await call();
|
|
383
|
+
return true;
|
|
384
|
+
} catch (error) {
|
|
385
|
+
link.report(toError(error));
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async function endThread(editor, link, call, id, threads) {
|
|
390
|
+
if (!await called(link, () => call(id)) || link.mounts === 0) return false;
|
|
391
|
+
const action = { type: "forget", id, seen: threads?.find(id) };
|
|
392
|
+
editor.dispatch(threadRemoval(editor.state, id).setMeta(COMMENTS, action));
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
function sendReply(link, call, id, body) {
|
|
396
|
+
return called(link, () => call(id, body));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/extensions/comments/CommentAvatar.tsx
|
|
400
|
+
import { useState } from "react";
|
|
401
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
402
|
+
var initials = (name) => name.trim().split(/\s+/).slice(0, 2).map((word) => Array.from(word)[0]?.toUpperCase() ?? "").join("");
|
|
403
|
+
function CommentAvatar({ author }) {
|
|
404
|
+
const picture = webAddress(author.avatarUrl);
|
|
405
|
+
const [failed, setFailed] = useState(null);
|
|
406
|
+
if (picture !== null && picture !== failed) {
|
|
407
|
+
return /* @__PURE__ */ jsx2(
|
|
408
|
+
"img",
|
|
409
|
+
{
|
|
410
|
+
src: picture,
|
|
411
|
+
alt: "",
|
|
412
|
+
loading: "lazy",
|
|
413
|
+
referrerPolicy: "no-referrer",
|
|
414
|
+
draggable: false,
|
|
415
|
+
className: "size-6 shrink-0 rounded-full object-cover",
|
|
416
|
+
onError: () => setFailed(picture)
|
|
417
|
+
}
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
return /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", className: "quoin-comments-initials", children: initials(author.name) });
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// src/extensions/comments/CommentItem.tsx
|
|
424
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
425
|
+
function CommentItem({ comment, labels }) {
|
|
426
|
+
return /* @__PURE__ */ jsxs2("li", { className: "flex gap-2", children: [
|
|
427
|
+
/* @__PURE__ */ jsx3(CommentAvatar, { author: comment.author }),
|
|
428
|
+
/* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
|
|
429
|
+
/* @__PURE__ */ jsxs2("p", { className: "flex flex-wrap items-baseline gap-x-2 text-sm", children: [
|
|
430
|
+
/* @__PURE__ */ jsx3("span", { className: "font-medium", children: comment.author.name }),
|
|
431
|
+
/* @__PURE__ */ jsx3("time", { dateTime: comment.createdAt, className: "text-xs text-muted-foreground", children: labels.postedAt(comment.createdAt) })
|
|
432
|
+
] }),
|
|
433
|
+
/* @__PURE__ */ jsx3("p", { className: "quoin-comments-body text-sm", children: comment.body })
|
|
434
|
+
] })
|
|
435
|
+
] });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/extensions/comments/ReplyBox.tsx
|
|
439
|
+
import { SendHorizontal } from "lucide-react";
|
|
440
|
+
import { useEffect, useRef, useState as useState2 } from "react";
|
|
441
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
442
|
+
var REPLY_FIELD = "data-comments-field";
|
|
443
|
+
function ReplyBox({ env, threadId, first }) {
|
|
444
|
+
const { controller, host, labels, link } = env;
|
|
445
|
+
const [body, setBody] = useState2("");
|
|
446
|
+
const [sending, setSending] = useState2(false);
|
|
447
|
+
const alive = useRef(true);
|
|
448
|
+
useEffect(() => {
|
|
449
|
+
alive.current = true;
|
|
450
|
+
return () => {
|
|
451
|
+
alive.current = false;
|
|
452
|
+
};
|
|
453
|
+
}, []);
|
|
454
|
+
const label = first ? labels.addComment : labels.reply;
|
|
455
|
+
const text = body.trim();
|
|
456
|
+
const send = async () => {
|
|
457
|
+
if (text === "" || sending) return;
|
|
458
|
+
setSending(true);
|
|
459
|
+
const sent = await sendReply(link, host.onReply, threadId, text);
|
|
460
|
+
if (!alive.current) return;
|
|
461
|
+
setSending(false);
|
|
462
|
+
if (sent) setBody("");
|
|
463
|
+
};
|
|
464
|
+
const onSubmit = (event) => {
|
|
465
|
+
event.preventDefault();
|
|
466
|
+
void send();
|
|
467
|
+
};
|
|
468
|
+
const onKeyDown = (event) => {
|
|
469
|
+
if (event.nativeEvent.isComposing || event.key !== "Enter") return;
|
|
470
|
+
if (controller.isMac ? !event.metaKey : !event.ctrlKey) return;
|
|
471
|
+
event.preventDefault();
|
|
472
|
+
void send();
|
|
473
|
+
};
|
|
474
|
+
return /* @__PURE__ */ jsxs3("form", { className: "flex items-start gap-2", onSubmit, children: [
|
|
475
|
+
/* @__PURE__ */ jsx4(CommentAvatar, { author: host.currentUser }),
|
|
476
|
+
/* @__PURE__ */ jsx4(
|
|
477
|
+
"textarea",
|
|
478
|
+
{
|
|
479
|
+
...{ [REPLY_FIELD]: "" },
|
|
480
|
+
"aria-label": label,
|
|
481
|
+
placeholder: label,
|
|
482
|
+
rows: 2,
|
|
483
|
+
value: body,
|
|
484
|
+
onChange: (event) => setBody(event.target.value),
|
|
485
|
+
onKeyDown,
|
|
486
|
+
className: "quoin-comments-field"
|
|
487
|
+
}
|
|
488
|
+
),
|
|
489
|
+
/* @__PURE__ */ jsx4(
|
|
490
|
+
Button,
|
|
491
|
+
{
|
|
492
|
+
type: "submit",
|
|
493
|
+
variant: "ghost",
|
|
494
|
+
size: "icon-sm",
|
|
495
|
+
"aria-label": labels.send,
|
|
496
|
+
"aria-disabled": text === "" || sending || void 0,
|
|
497
|
+
children: /* @__PURE__ */ jsx4(SendHorizontal, {})
|
|
498
|
+
}
|
|
499
|
+
)
|
|
500
|
+
] });
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/extensions/comments/ThreadView.tsx
|
|
504
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
505
|
+
function ThreadView({ env, id, thread, onEnded }) {
|
|
506
|
+
const { controller, host, labels, link } = env;
|
|
507
|
+
const readOnly = useReadOnly(controller);
|
|
508
|
+
const [busy, setBusy] = useState3(false);
|
|
509
|
+
const alive = useRef2(true);
|
|
510
|
+
const root = useRef2(null);
|
|
511
|
+
useEffect2(() => {
|
|
512
|
+
alive.current = true;
|
|
513
|
+
return () => {
|
|
514
|
+
alive.current = false;
|
|
515
|
+
};
|
|
516
|
+
}, []);
|
|
517
|
+
const comments = thread?.comments ?? [];
|
|
518
|
+
const end = async (call) => {
|
|
519
|
+
if (busy) return;
|
|
520
|
+
setBusy(true);
|
|
521
|
+
const ended = await endThread(controller.editor, link, call, id, host.threads);
|
|
522
|
+
if (alive.current) setBusy(false);
|
|
523
|
+
const active = document.activeElement;
|
|
524
|
+
const stayed = active === null || active === document.body || root.current?.contains(active) === true;
|
|
525
|
+
if (ended && stayed) onEnded();
|
|
526
|
+
};
|
|
527
|
+
return /* @__PURE__ */ jsxs4("div", { ref: root, className: "flex flex-col gap-3", children: [
|
|
528
|
+
comments.length > 0 && /* @__PURE__ */ jsx5("ol", { className: "flex flex-col gap-3", children: comments.map((comment) => /* @__PURE__ */ jsx5(CommentItem, { comment, labels }, comment.id)) }),
|
|
529
|
+
/* @__PURE__ */ jsx5(ReplyBox, { env, threadId: id, first: comments.length === 0 }),
|
|
530
|
+
!readOnly && /* @__PURE__ */ jsxs4("div", { className: "flex justify-end gap-1", children: [
|
|
531
|
+
/* @__PURE__ */ jsx5(
|
|
532
|
+
Button,
|
|
533
|
+
{
|
|
534
|
+
type: "button",
|
|
535
|
+
variant: "ghost",
|
|
536
|
+
size: "xs",
|
|
537
|
+
"aria-disabled": busy || void 0,
|
|
538
|
+
onClick: () => void end(host.onResolve),
|
|
539
|
+
children: labels.resolve
|
|
540
|
+
}
|
|
541
|
+
),
|
|
542
|
+
/* @__PURE__ */ jsx5(
|
|
543
|
+
Button,
|
|
544
|
+
{
|
|
545
|
+
type: "button",
|
|
546
|
+
variant: "ghost",
|
|
547
|
+
size: "xs",
|
|
548
|
+
"aria-disabled": busy || void 0,
|
|
549
|
+
onClick: () => void end(host.onDelete),
|
|
550
|
+
children: labels.delete
|
|
551
|
+
}
|
|
552
|
+
)
|
|
553
|
+
] })
|
|
554
|
+
] });
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/extensions/comments/ThreadCard.tsx
|
|
558
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
559
|
+
function ThreadCard({ env, entry, active, expanded, onToggle }) {
|
|
560
|
+
const { labels, controller } = env;
|
|
561
|
+
const { thread, anchorText: anchorText2, ranges } = entry;
|
|
562
|
+
const header = useRef3(null);
|
|
563
|
+
const [first] = thread.comments;
|
|
564
|
+
const removed = ranges.length === 0;
|
|
565
|
+
const backToHeader = () => {
|
|
566
|
+
if (header.current?.isConnected) header.current.focus();
|
|
567
|
+
else controller.focus();
|
|
568
|
+
};
|
|
569
|
+
const onKeyDown = (event) => {
|
|
570
|
+
if (event.key !== "Escape" || !expanded) return;
|
|
571
|
+
event.preventDefault();
|
|
572
|
+
event.stopPropagation();
|
|
573
|
+
onToggle(null);
|
|
574
|
+
header.current?.focus();
|
|
575
|
+
};
|
|
576
|
+
return /* @__PURE__ */ jsxs5("div", { className: "quoin-comments-card", "data-active": active || void 0, onKeyDown, children: [
|
|
577
|
+
/* @__PURE__ */ jsxs5(
|
|
578
|
+
"button",
|
|
579
|
+
{
|
|
580
|
+
ref: header,
|
|
581
|
+
type: "button",
|
|
582
|
+
"aria-expanded": expanded,
|
|
583
|
+
className: cn("flex w-full flex-col items-start gap-1 rounded-sm text-start", FOCUS_RING),
|
|
584
|
+
onClick: () => onToggle(expanded ? null : thread.id),
|
|
585
|
+
children: [
|
|
586
|
+
/* @__PURE__ */ jsx6("span", { className: removed ? "text-xs text-muted-foreground italic" : "quoin-comments-quote", children: removed ? labels.textRemoved : anchorText2 }),
|
|
587
|
+
first !== void 0 && /* @__PURE__ */ jsxs5("span", { className: "line-clamp-2 text-sm", children: [
|
|
588
|
+
/* @__PURE__ */ jsx6("span", { className: "font-medium", children: first.author.name }),
|
|
589
|
+
" ",
|
|
590
|
+
first.body
|
|
591
|
+
] }),
|
|
592
|
+
/* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: labels.commentCount(thread.comments.length) })
|
|
593
|
+
]
|
|
594
|
+
}
|
|
595
|
+
),
|
|
596
|
+
expanded && /* @__PURE__ */ jsx6("div", { className: "mt-3", role: "group", "aria-label": removed ? labels.textRemoved : labels.thread(anchorText2), children: /* @__PURE__ */ jsx6(ThreadView, { env, id: thread.id, thread, onEnded: backToHeader }) })
|
|
597
|
+
] });
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/extensions/comments/CommentsRail.tsx
|
|
601
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
602
|
+
var GAP2 = 8;
|
|
603
|
+
var layoutKey = (entries, value) => `${value.active} ${value.open !== null} ${entries.map(({ thread, ranges }) => `${thread.id}:${ranges[0]?.block}`)}`;
|
|
604
|
+
function CommentsRail({ env, box, entries, value, onToggle }) {
|
|
605
|
+
useBlockAligned(env.controller, box, GAP2, layoutKey(entries, value));
|
|
606
|
+
return /* @__PURE__ */ jsx7("div", { ref: box, className: "quoin-comments-rail", children: /* @__PURE__ */ jsx7("ul", { "aria-label": env.labels.comments, children: entries.map((entry) => /* @__PURE__ */ jsx7("li", { "data-rail-item": "", "data-block": entry.ranges[0]?.block ?? "", children: /* @__PURE__ */ jsx7(
|
|
607
|
+
ThreadCard,
|
|
608
|
+
{
|
|
609
|
+
env,
|
|
610
|
+
entry,
|
|
611
|
+
active: value.active === entry.thread.id,
|
|
612
|
+
expanded: value.active === entry.thread.id && value.open === null,
|
|
613
|
+
onToggle
|
|
614
|
+
}
|
|
615
|
+
) }, entry.thread.id)) }) });
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// src/extensions/comments/CommentsSheet.tsx
|
|
619
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
620
|
+
import { XIcon } from "lucide-react";
|
|
621
|
+
import { useRef as useRef4 } from "react";
|
|
622
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
623
|
+
function CommentsSheet({ env, entries, value, onToggle, onClose, returnFocus }) {
|
|
624
|
+
const { labels } = env;
|
|
625
|
+
const content = useRef4(null);
|
|
626
|
+
const outside = useRef4(false);
|
|
627
|
+
const focusOpened = (event) => {
|
|
628
|
+
const opened = content.current?.querySelector('[aria-expanded="true"]');
|
|
629
|
+
if (!opened) return;
|
|
630
|
+
event.preventDefault();
|
|
631
|
+
opened.focus();
|
|
632
|
+
};
|
|
633
|
+
return /* @__PURE__ */ jsx8(
|
|
634
|
+
DialogPrimitive.Root,
|
|
635
|
+
{
|
|
636
|
+
open: true,
|
|
637
|
+
modal: false,
|
|
638
|
+
onOpenChange: (open) => {
|
|
639
|
+
if (!open) onClose();
|
|
640
|
+
},
|
|
641
|
+
children: /* @__PURE__ */ jsx8(DialogPrimitive.Portal, { children: /* @__PURE__ */ jsxs6(
|
|
642
|
+
DialogPrimitive.Content,
|
|
643
|
+
{
|
|
644
|
+
ref: content,
|
|
645
|
+
"aria-describedby": void 0,
|
|
646
|
+
className: "quoin quoin-comments-sheet",
|
|
647
|
+
onOpenAutoFocus: focusOpened,
|
|
648
|
+
onInteractOutside: () => {
|
|
649
|
+
outside.current = true;
|
|
650
|
+
},
|
|
651
|
+
onCloseAutoFocus: (event) => {
|
|
652
|
+
event.preventDefault();
|
|
653
|
+
if (!outside.current) returnFocus();
|
|
654
|
+
},
|
|
655
|
+
children: [
|
|
656
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between gap-2", children: [
|
|
657
|
+
/* @__PURE__ */ jsx8(DialogPrimitive.Title, { className: "text-sm font-semibold", children: labels.comments }),
|
|
658
|
+
/* @__PURE__ */ jsx8(DialogPrimitive.Close, { asChild: true, children: /* @__PURE__ */ jsx8(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": labels.close, children: /* @__PURE__ */ jsx8(XIcon, {}) }) })
|
|
659
|
+
] }),
|
|
660
|
+
/* @__PURE__ */ jsx8("ul", { "aria-label": labels.comments, className: "flex flex-col gap-2", children: entries.map((entry) => /* @__PURE__ */ jsx8("li", { children: /* @__PURE__ */ jsx8(
|
|
661
|
+
ThreadCard,
|
|
662
|
+
{
|
|
663
|
+
env,
|
|
664
|
+
entry,
|
|
665
|
+
active: value.active === entry.thread.id,
|
|
666
|
+
expanded: value.active === entry.thread.id,
|
|
667
|
+
onToggle
|
|
668
|
+
}
|
|
669
|
+
) }, entry.thread.id)) })
|
|
670
|
+
]
|
|
671
|
+
}
|
|
672
|
+
) })
|
|
673
|
+
}
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/extensions/comments/rail-entries.ts
|
|
678
|
+
var NONE = [];
|
|
679
|
+
function railEntries(doc, threads, ended = []) {
|
|
680
|
+
const anchors = commentAnchors(doc);
|
|
681
|
+
const entries = threads.filter((thread) => !thread.resolved && !isEnded(ended, thread.id, thread)).map((thread) => {
|
|
682
|
+
const ranges = anchors.get(thread.id) ?? NONE;
|
|
683
|
+
return { thread, ranges, anchorText: anchorText(doc, ranges) };
|
|
684
|
+
});
|
|
685
|
+
const place2 = ({ ranges: [first] }) => first === void 0 ? [Infinity, 0] : [indexOfBlock(doc, first.block), first.from];
|
|
686
|
+
return entries.map((entry, order) => ({ entry, order, at: place2(entry) })).sort((a, b) => a.at[0] - b.at[0] || a.at[1] - b.at[1] || a.order - b.order).map(({ entry }) => entry);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// src/extensions/comments/ThreadPopover.tsx
|
|
690
|
+
import { useMemo, useRef as useRef5 } from "react";
|
|
691
|
+
|
|
692
|
+
// src/extensions/comments/ThreadChooser.tsx
|
|
693
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
694
|
+
function ThreadChooser({ env, choices, onChoose }) {
|
|
695
|
+
return /* @__PURE__ */ jsx9("ul", { "aria-label": env.labels.threadsHere, className: "flex flex-col gap-1", children: choices.map(({ id, anchorText: anchorText2, preview }) => /* @__PURE__ */ jsx9("li", { children: /* @__PURE__ */ jsxs7(
|
|
696
|
+
"button",
|
|
697
|
+
{
|
|
698
|
+
type: "button",
|
|
699
|
+
className: "quoin-comments-choice",
|
|
700
|
+
onClick: () => onChoose(id),
|
|
701
|
+
children: [
|
|
702
|
+
/* @__PURE__ */ jsx9("span", { className: "quoin-comments-quote", children: anchorText2 }),
|
|
703
|
+
preview && /* @__PURE__ */ jsx9("span", { className: "line-clamp-1 text-sm", children: preview })
|
|
704
|
+
]
|
|
705
|
+
}
|
|
706
|
+
) }, id)) });
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// src/extensions/comments/ThreadPopover.tsx
|
|
710
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
711
|
+
var docOf = (state) => state.doc;
|
|
712
|
+
function focusIsOurs(controller, content) {
|
|
713
|
+
const active = content?.ownerDocument.activeElement ?? null;
|
|
714
|
+
return active === null || active === active.ownerDocument.body || ownsNode(controller, active);
|
|
715
|
+
}
|
|
716
|
+
function ThreadPopover({ env, open, threads, onChoose, onClose }) {
|
|
717
|
+
const { controller, labels } = env;
|
|
718
|
+
const doc = useEditorState(controller, docOf);
|
|
719
|
+
const anchors = commentAnchors(doc);
|
|
720
|
+
const { ids } = open;
|
|
721
|
+
const first = ids.map((id) => anchors.get(id)?.[0]).find(Boolean);
|
|
722
|
+
const latest = useRef5(first);
|
|
723
|
+
useIsomorphicLayoutEffect(() => {
|
|
724
|
+
if (first) latest.current = first;
|
|
725
|
+
});
|
|
726
|
+
const anchor = useMemo(
|
|
727
|
+
() => ({
|
|
728
|
+
current: virtualAnchor(
|
|
729
|
+
() => latest.current ? rangeRect(controller, latest.current) : null,
|
|
730
|
+
() => latest.current && controller.registry.get(latest.current.block, latest.current.region)
|
|
731
|
+
)
|
|
732
|
+
}),
|
|
733
|
+
[controller]
|
|
734
|
+
);
|
|
735
|
+
const content = useRef5(null);
|
|
736
|
+
const focusField = () => {
|
|
737
|
+
const target = content.current?.querySelector(`[${REPLY_FIELD}], button`);
|
|
738
|
+
if (focusIsOurs(controller, content.current) && !content.current?.contains(content.current.ownerDocument.activeElement)) {
|
|
739
|
+
target?.focus();
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
useIsomorphicLayoutEffect(() => {
|
|
743
|
+
if (open.focus) focusField();
|
|
744
|
+
}, [open]);
|
|
745
|
+
if (first === void 0 && latest.current === void 0) return null;
|
|
746
|
+
const textOf = (id) => anchorText(doc, anchors.get(id) ?? []) || labels.textRemoved;
|
|
747
|
+
const single = ids.length === 1;
|
|
748
|
+
const gone = single && !anchors.has(ids[0]);
|
|
749
|
+
const backToText = () => controller.focus();
|
|
750
|
+
const focusInside = (event) => {
|
|
751
|
+
event.preventDefault();
|
|
752
|
+
if (open.focus) focusField();
|
|
753
|
+
};
|
|
754
|
+
const onEscape = (event) => {
|
|
755
|
+
event.preventDefault();
|
|
756
|
+
event.stopPropagation();
|
|
757
|
+
const inside = content.current?.contains(content.current.ownerDocument.activeElement) ?? false;
|
|
758
|
+
onClose();
|
|
759
|
+
if (inside) backToText();
|
|
760
|
+
};
|
|
761
|
+
const choices = ids.map((id) => ({ id, anchorText: textOf(id), preview: threads.find(id)?.comments[0]?.body ?? "" }));
|
|
762
|
+
return /* @__PURE__ */ jsxs8(
|
|
763
|
+
Popover,
|
|
764
|
+
{
|
|
765
|
+
open: true,
|
|
766
|
+
modal: false,
|
|
767
|
+
onOpenChange: (next) => {
|
|
768
|
+
if (!next) onClose();
|
|
769
|
+
},
|
|
770
|
+
children: [
|
|
771
|
+
/* @__PURE__ */ jsx10(PopoverAnchor, { virtualRef: anchor }),
|
|
772
|
+
/* @__PURE__ */ jsxs8(
|
|
773
|
+
PopoverContent,
|
|
774
|
+
{
|
|
775
|
+
ref: content,
|
|
776
|
+
"aria-label": gone ? labels.textRemoved : single ? labels.thread(textOf(ids[0])) : labels.threadsHere,
|
|
777
|
+
side: "bottom",
|
|
778
|
+
align: "start",
|
|
779
|
+
className: "w-80 p-3",
|
|
780
|
+
onOpenAutoFocus: focusInside,
|
|
781
|
+
onCloseAutoFocus: (event) => event.preventDefault(),
|
|
782
|
+
onEscapeKeyDown: onEscape,
|
|
783
|
+
children: [
|
|
784
|
+
gone && /* @__PURE__ */ jsx10("p", { className: "mb-2 text-xs text-muted-foreground italic", children: labels.textRemoved }),
|
|
785
|
+
single ? /* @__PURE__ */ jsx10(ThreadView, { env, id: ids[0], thread: threads.find(ids[0]), onEnded: backToText }) : /* @__PURE__ */ jsx10(ThreadChooser, { env, choices, onChoose })
|
|
786
|
+
]
|
|
787
|
+
},
|
|
788
|
+
ids.join(" ")
|
|
789
|
+
)
|
|
790
|
+
]
|
|
791
|
+
}
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/extensions/comments/use-anchor-clicks.ts
|
|
796
|
+
import { useEffect as useEffect3 } from "react";
|
|
797
|
+
function idsUnder(root, target) {
|
|
798
|
+
const ids = [];
|
|
799
|
+
let element = target instanceof Element ? target.closest("[data-comment]") : null;
|
|
800
|
+
while (element !== null && root.contains(element)) {
|
|
801
|
+
const id = element.getAttribute("data-comment");
|
|
802
|
+
if (id) ids.push(id);
|
|
803
|
+
element = element.parentElement?.closest("[data-comment]") ?? null;
|
|
804
|
+
}
|
|
805
|
+
return ids;
|
|
806
|
+
}
|
|
807
|
+
function useAnchorClicks(controller, threads) {
|
|
808
|
+
useEffect3(() => {
|
|
809
|
+
const onClick = (event) => {
|
|
810
|
+
const root = controller.root;
|
|
811
|
+
if (root === null || event.defaultPrevented || !root.ownerDocument.getSelection()?.isCollapsed) return;
|
|
812
|
+
const { editor } = controller;
|
|
813
|
+
const ids = openIds(editor.state, threads, idsUnder(root, event.target));
|
|
814
|
+
if (ids.length === 0) return;
|
|
815
|
+
editor.dispatch(commentsTransaction(editor.state, { type: "open", open: { ids, focus: false } }));
|
|
816
|
+
};
|
|
817
|
+
document.addEventListener("click", onClick);
|
|
818
|
+
return () => document.removeEventListener("click", onClick);
|
|
819
|
+
}, [controller, threads]);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// src/extensions/comments/use-canvas-wide.ts
|
|
823
|
+
import { useState as useState4 } from "react";
|
|
824
|
+
var WIDE_CANVAS = 800;
|
|
825
|
+
function useCanvasWide(box, initial) {
|
|
826
|
+
const [wide, setWide] = useState4(initial);
|
|
827
|
+
useIsomorphicLayoutEffect(() => {
|
|
828
|
+
const canvas = box.current?.parentElement;
|
|
829
|
+
if (!canvas) return void 0;
|
|
830
|
+
const measure = () => setWide(canvas.offsetWidth >= WIDE_CANVAS);
|
|
831
|
+
measure();
|
|
832
|
+
const observer = new ResizeObserver(measure);
|
|
833
|
+
observer.observe(canvas);
|
|
834
|
+
return () => observer.disconnect();
|
|
835
|
+
}, [box]);
|
|
836
|
+
return wide;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/extensions/comments/use-threads.ts
|
|
840
|
+
import { useSyncExternalStore } from "react";
|
|
841
|
+
function useThreads(threads) {
|
|
842
|
+
return useSyncExternalStore(threads.subscribe, threads.get, threads.get);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/extensions/comments/CommentsOverlay.tsx
|
|
846
|
+
import { Fragment, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
847
|
+
var commentsOf = (state) => getPluginState(state, commentsKey);
|
|
848
|
+
var docOf2 = (state) => state.doc;
|
|
849
|
+
function CommentsOverlay({ controller, host, labels }) {
|
|
850
|
+
const { onError } = useQuoin();
|
|
851
|
+
const link = linkOf(controller.editor);
|
|
852
|
+
useIsomorphicLayoutEffect(() => {
|
|
853
|
+
link.report = onError;
|
|
854
|
+
}, [link, onError]);
|
|
855
|
+
useAnchorClicks(controller, host.threads);
|
|
856
|
+
const threads = useThreads(host.threads);
|
|
857
|
+
const value = useEditorState(controller, commentsOf);
|
|
858
|
+
const doc = useEditorState(controller, docOf2);
|
|
859
|
+
const entries = useMemo2(() => railEntries(doc, threads, value.ended), [doc, threads, value.ended]);
|
|
860
|
+
const box = useRef6(null);
|
|
861
|
+
const wide = useCanvasWide(box, host.initialLayout === "wide");
|
|
862
|
+
const [sheet, setSheet] = useState5(false);
|
|
863
|
+
const opener = useRef6(null);
|
|
864
|
+
useEffect4(() => {
|
|
865
|
+
if (wide) setSheet(false);
|
|
866
|
+
}, [wide]);
|
|
867
|
+
const env = useMemo2(() => ({ controller, host, labels, link }), [controller, host, labels, link]);
|
|
868
|
+
const act2 = useCallback(
|
|
869
|
+
(action) => controller.editor.dispatch(commentsTransaction(controller.editor.state, action)),
|
|
870
|
+
[controller]
|
|
871
|
+
);
|
|
872
|
+
const toggle = useCallback((id) => act2({ type: "activate", id }), [act2]);
|
|
873
|
+
const openSheet = (id, marker) => {
|
|
874
|
+
opener.current = marker;
|
|
875
|
+
toggle(id);
|
|
876
|
+
setSheet(true);
|
|
877
|
+
};
|
|
878
|
+
const returnFocus = () => {
|
|
879
|
+
if (opener.current?.isConnected) opener.current.focus();
|
|
880
|
+
else controller.focus();
|
|
881
|
+
};
|
|
882
|
+
const closeSheet = () => {
|
|
883
|
+
setSheet(false);
|
|
884
|
+
toggle(null);
|
|
885
|
+
};
|
|
886
|
+
return /* @__PURE__ */ jsxs9(Fragment, { children: [
|
|
887
|
+
wide ? /* @__PURE__ */ jsx11(CommentsRail, { env, box, entries, value, onToggle: toggle }) : /* @__PURE__ */ jsx11(CommentMarkers, { env, box, entries, onOpen: openSheet }),
|
|
888
|
+
!wide && sheet && /* @__PURE__ */ jsx11(
|
|
889
|
+
CommentsSheet,
|
|
890
|
+
{
|
|
891
|
+
env,
|
|
892
|
+
entries,
|
|
893
|
+
value,
|
|
894
|
+
onToggle: toggle,
|
|
895
|
+
onClose: closeSheet,
|
|
896
|
+
returnFocus
|
|
897
|
+
}
|
|
898
|
+
),
|
|
899
|
+
value.open && /* @__PURE__ */ jsx11(
|
|
900
|
+
ThreadPopover,
|
|
901
|
+
{
|
|
902
|
+
env,
|
|
903
|
+
open: value.open,
|
|
904
|
+
threads: host.threads,
|
|
905
|
+
onChoose: (id) => act2({ type: "open", open: { ids: [id], focus: true } }),
|
|
906
|
+
onClose: () => act2({ type: "close" })
|
|
907
|
+
}
|
|
908
|
+
)
|
|
909
|
+
] });
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// src/extensions/comments/comments-decorations.ts
|
|
913
|
+
var PENDING = { class: "quoin-comment-pending" };
|
|
914
|
+
var ACTIVE = { class: "quoin-comment-active" };
|
|
915
|
+
var inline = (ranges, attrs) => ranges.map((range) => ({ kind: "inline", ...range, attrs }));
|
|
916
|
+
var drawn = /* @__PURE__ */ new WeakMap();
|
|
917
|
+
function commentsDecorations(state) {
|
|
918
|
+
const value = getPluginState(state, commentsKey);
|
|
919
|
+
const last = drawn.get(value);
|
|
920
|
+
if (last?.doc === state.doc) return last.decorations;
|
|
921
|
+
const active = value.active === null ? [] : commentAnchors(state.doc).get(value.active) ?? [];
|
|
922
|
+
const decorations = [...value.pending.flatMap(({ ranges }) => inline(ranges, PENDING)), ...inline(active, ACTIVE)];
|
|
923
|
+
drawn.set(value, { doc: state.doc, decorations });
|
|
924
|
+
return decorations;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// src/extensions/comments/watch-ended.ts
|
|
928
|
+
function watchEnded(editor, threads) {
|
|
929
|
+
return threads.subscribe(() => {
|
|
930
|
+
const { ended } = getPluginState(editor.state, commentsKey);
|
|
931
|
+
const kept = ended.filter(({ id }) => isEnded(ended, id, threads.find(id)));
|
|
932
|
+
if (kept.length < ended.length) editor.dispatch(commentsTransaction(editor.state, { type: "caught", kept }));
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// src/extensions/comments/comments-extension.tsx
|
|
937
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
938
|
+
var commentItem = (labels) => ({
|
|
939
|
+
id: "comment",
|
|
940
|
+
title: labels.comment,
|
|
941
|
+
icon: MessageSquarePlus,
|
|
942
|
+
shortcut: "Mod-Alt-m",
|
|
943
|
+
available: (state) => hasCommentableText(state) && getPluginState(state, commentsKey).pending.length === 0,
|
|
944
|
+
// Focus goes back to the text first: the button leaves the toolbar while the thread is being asked for.
|
|
945
|
+
run: (controller) => {
|
|
946
|
+
controller.focus();
|
|
947
|
+
controller.run(requestComment);
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
function commentsExtension(options) {
|
|
951
|
+
const { labels: overrides, ...host } = options;
|
|
952
|
+
const labels = resolveCommentsLabels(overrides);
|
|
953
|
+
function Overlay({ controller }) {
|
|
954
|
+
return /* @__PURE__ */ jsx12(CommentsOverlay, { controller, host, labels });
|
|
955
|
+
}
|
|
956
|
+
const ui = { bubbleItems: [commentItem(labels)], overlays: [Overlay] };
|
|
957
|
+
return defineExtension({
|
|
958
|
+
name: "comments",
|
|
959
|
+
plugins: [commentsPlugin],
|
|
960
|
+
decorations: commentsDecorations,
|
|
961
|
+
keymap: { "Mod-Alt-m": commentCommand(host.threads) },
|
|
962
|
+
// One watcher per editor however many views mount it at once, so the host is asked once per thread.
|
|
963
|
+
onMount(editor) {
|
|
964
|
+
const link = linkOf(editor);
|
|
965
|
+
if (link.mounts++ === 0) {
|
|
966
|
+
const stops = [watchCreates(editor, host.onCreate, link), watchEnded(editor, host.threads)];
|
|
967
|
+
link.stopWatching = () => stops.forEach((stop) => stop());
|
|
968
|
+
}
|
|
969
|
+
return () => {
|
|
970
|
+
if (--link.mounts > 0) return;
|
|
971
|
+
link.stopWatching?.();
|
|
972
|
+
link.stopWatching = null;
|
|
973
|
+
};
|
|
974
|
+
},
|
|
975
|
+
ui
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// src/extensions/comments/comment-threads.ts
|
|
980
|
+
var NO_THREADS = [];
|
|
981
|
+
function createCommentThreads(initial = NO_THREADS) {
|
|
982
|
+
let threads = initial;
|
|
983
|
+
let byId = null;
|
|
984
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
985
|
+
return {
|
|
986
|
+
get: () => threads,
|
|
987
|
+
set(next) {
|
|
988
|
+
if (next === threads) return;
|
|
989
|
+
threads = next;
|
|
990
|
+
byId = null;
|
|
991
|
+
for (const listener of [...listeners]) listener();
|
|
992
|
+
},
|
|
993
|
+
find(id) {
|
|
994
|
+
byId ??= new Map(threads.map((thread) => [thread.id, thread]));
|
|
995
|
+
return byId.get(id);
|
|
996
|
+
},
|
|
997
|
+
subscribe(listener) {
|
|
998
|
+
listeners.add(listener);
|
|
999
|
+
return () => listeners.delete(listener);
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
export {
|
|
1004
|
+
commentsExtension,
|
|
1005
|
+
createCommentThreads,
|
|
1006
|
+
englishCommentsLabels
|
|
1007
|
+
};
|
|
1008
|
+
//# sourceMappingURL=comments.js.map
|