@pixerate/editor 0.235.0 → 0.237.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.
@@ -17,21 +17,267 @@ import {
17
17
  plainTextToSlice
18
18
  } from "./chunk-2INPJDNY.js";
19
19
  import {
20
+ CommentStore,
20
21
  TRACK_CHANGES_SKIP_META,
22
+ createReviewId,
21
23
  isTrackChangesEnabled,
22
24
  safeMergeAttributes
23
25
  } from "./chunk-6IN474P4.js";
24
26
 
27
+ // src/extensions/comments.ts
28
+ import { Mark } from "@tiptap/core";
29
+ import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
30
+ import { Decoration, DecorationSet } from "@tiptap/pm/view";
31
+ var commentsPluginKey = new PluginKey("comments");
32
+ var COMMENT_MARK_NAME = "comment";
33
+ function findCommentRanges(doc) {
34
+ const result = /* @__PURE__ */ new Map();
35
+ doc.descendants((node, pos) => {
36
+ if (!node.isInline) return;
37
+ node.marks.forEach((mark) => {
38
+ if (mark.type.name !== COMMENT_MARK_NAME) return;
39
+ const id = mark.attrs.commentId;
40
+ if (!id) return;
41
+ const ranges = result.get(id) ?? [];
42
+ const last = ranges[ranges.length - 1];
43
+ const end = pos + node.nodeSize;
44
+ if (last && last.to === pos) {
45
+ last.to = end;
46
+ } else {
47
+ ranges.push({ from: pos, to: end });
48
+ }
49
+ result.set(id, ranges);
50
+ });
51
+ });
52
+ return result;
53
+ }
54
+ function getCommentAnchors(doc) {
55
+ const anchors = /* @__PURE__ */ new Map();
56
+ findCommentRanges(doc).forEach((ranges, id) => {
57
+ const from = ranges[0].from;
58
+ const to = ranges[ranges.length - 1].to;
59
+ anchors.set(id, {
60
+ from,
61
+ to,
62
+ quote: ranges.map((r) => doc.textBetween(r.from, r.to, " ", " ")).join(" ")
63
+ });
64
+ });
65
+ return anchors;
66
+ }
67
+ function getCommentIdsAt(state, pos = state.selection.from) {
68
+ const $pos = state.doc.resolve(pos);
69
+ const marks = [
70
+ ...$pos.nodeAfter?.marks ?? [],
71
+ ...$pos.nodeBefore?.marks ?? []
72
+ ];
73
+ const ids = [];
74
+ marks.forEach((m) => {
75
+ if (m.type.name === COMMENT_MARK_NAME && m.attrs.commentId && !ids.includes(m.attrs.commentId)) {
76
+ ids.push(m.attrs.commentId);
77
+ }
78
+ });
79
+ return ids;
80
+ }
81
+ function getCommentStore(editor) {
82
+ return editor?.storage?.[COMMENT_MARK_NAME]?.store ?? null;
83
+ }
84
+ function resolveAuthor(author) {
85
+ return typeof author === "function" ? author() : author;
86
+ }
87
+ var Comments = Mark.create({
88
+ name: COMMENT_MARK_NAME,
89
+ // Typing at the edge of a comment does not extend it.
90
+ inclusive: false,
91
+ // Allow several comment marks (different threads) on the same text.
92
+ excludes: "",
93
+ addOptions() {
94
+ return {
95
+ store: null,
96
+ author: { id: "anonymous", name: "Anonymous" },
97
+ HTMLAttributes: {},
98
+ commentClass: "pixerate-comment bg-amber-100 border-b-2 border-amber-400 dark:bg-amber-500/20 cursor-pointer",
99
+ activeClass: "pixerate-comment-active bg-amber-200 dark:bg-amber-500/40",
100
+ resolvedClass: "",
101
+ activateOnSelection: true
102
+ };
103
+ },
104
+ addStorage() {
105
+ return {
106
+ store: this.options.store ?? new CommentStore(),
107
+ // tiptap-markdown: comments are not part of the exported markdown.
108
+ markdown: { serialize: { open: "", close: "" }, parse: {} }
109
+ };
110
+ },
111
+ addAttributes() {
112
+ return {
113
+ commentId: {
114
+ default: null,
115
+ parseHTML: (el) => el.getAttribute("data-comment-id"),
116
+ renderHTML: (attrs) => attrs.commentId ? { "data-comment-id": attrs.commentId } : {}
117
+ }
118
+ };
119
+ },
120
+ parseHTML() {
121
+ return [{ tag: "span[data-comment-id]" }];
122
+ },
123
+ renderHTML({ HTMLAttributes }) {
124
+ return ["span", safeMergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
125
+ },
126
+ addCommands() {
127
+ const store = () => this.storage.store;
128
+ return {
129
+ addComment: (options = {}) => ({ state, tr, dispatch }) => {
130
+ const { from, to, empty } = state.selection;
131
+ if (empty) return false;
132
+ if (dispatch) {
133
+ const id = options.id ?? createReviewId("comment");
134
+ tr.addMark(from, to, this.type.create({ commentId: id }));
135
+ tr.setMeta(commentsPluginKey, "refresh");
136
+ store().createThread({
137
+ id,
138
+ body: options.body ?? "",
139
+ author: options.author ?? resolveAuthor(this.options.author),
140
+ anchor: { from, to, quote: state.doc.textBetween(from, to, " ", " ") }
141
+ });
142
+ store().setActiveThread(id);
143
+ }
144
+ return true;
145
+ },
146
+ resolveComment: (id, resolved = true) => ({ tr, dispatch }) => {
147
+ if (!store().getThread(id)) return false;
148
+ if (dispatch) {
149
+ store().resolveThread(id, resolved, resolveAuthor(this.options.author));
150
+ tr.setMeta(commentsPluginKey, "refresh");
151
+ }
152
+ return true;
153
+ },
154
+ deleteComment: (id) => ({ state, tr, dispatch }) => {
155
+ const ranges = findCommentRanges(state.doc).get(id) ?? [];
156
+ if (ranges.length === 0 && !store().getThread(id)) return false;
157
+ if (dispatch) {
158
+ ranges.forEach(({ from, to }) => {
159
+ state.doc.nodesBetween(from, to, (node, pos) => {
160
+ const mark = node.marks.find(
161
+ (m) => m.type === this.type && m.attrs.commentId === id
162
+ );
163
+ if (mark) {
164
+ tr.removeMark(Math.max(pos, from), Math.min(pos + node.nodeSize, to), mark);
165
+ }
166
+ });
167
+ });
168
+ tr.setMeta(commentsPluginKey, "refresh");
169
+ store().deleteThread(id);
170
+ }
171
+ return true;
172
+ },
173
+ setActiveComment: (id) => ({ tr, dispatch }) => {
174
+ if (id !== null && !store().getThread(id)) return false;
175
+ if (dispatch) {
176
+ store().setActiveThread(id);
177
+ tr.setMeta(commentsPluginKey, "refresh");
178
+ }
179
+ return true;
180
+ },
181
+ focusComment: (id) => ({ state, tr, dispatch }) => {
182
+ const ranges = findCommentRanges(state.doc).get(id);
183
+ if (!ranges || ranges.length === 0) return false;
184
+ if (dispatch) {
185
+ const from = ranges[0].from;
186
+ const to = ranges[ranges.length - 1].to;
187
+ tr.setSelection(TextSelection.create(tr.doc, from, to)).scrollIntoView();
188
+ store().setActiveThread(id);
189
+ tr.setMeta(commentsPluginKey, "refresh");
190
+ }
191
+ return true;
192
+ }
193
+ };
194
+ },
195
+ addProseMirrorPlugins() {
196
+ const store = this.storage.store;
197
+ const options = this.options;
198
+ let cache = null;
199
+ const buildDecorations2 = (doc) => {
200
+ const snapshot = store.getSnapshot();
201
+ if (cache && cache.doc === doc && cache.snapshot === snapshot) return cache.set;
202
+ const decorations = [];
203
+ findCommentRanges(doc).forEach((ranges, id) => {
204
+ const thread = store.getThread(id);
205
+ const resolved = thread?.resolved ?? false;
206
+ const active = snapshot.activeThreadId === id;
207
+ const cls = [
208
+ resolved ? options.resolvedClass : options.commentClass,
209
+ active ? options.activeClass : ""
210
+ ].filter(Boolean).join(" ");
211
+ if (!cls) return;
212
+ ranges.forEach(({ from, to }) => {
213
+ decorations.push(Decoration.inline(from, to, { class: cls }));
214
+ });
215
+ });
216
+ const set = DecorationSet.create(doc, decorations);
217
+ cache = { doc, snapshot, set };
218
+ return set;
219
+ };
220
+ return [
221
+ new Plugin({
222
+ key: commentsPluginKey,
223
+ props: {
224
+ decorations: (state) => buildDecorations2(state.doc)
225
+ },
226
+ view: (view) => {
227
+ let destroyed = false;
228
+ let scheduled = false;
229
+ const sync = () => store.syncAnchors(getCommentAnchors(view.state.doc));
230
+ sync();
231
+ const unsubscribe = store.subscribe(() => {
232
+ if (scheduled) return;
233
+ scheduled = true;
234
+ queueMicrotask(() => {
235
+ scheduled = false;
236
+ if (destroyed) return;
237
+ sync();
238
+ if (cache?.snapshot !== store.getSnapshot()) {
239
+ view.dispatch(
240
+ view.state.tr.setMeta(commentsPluginKey, "refresh").setMeta("addToHistory", false)
241
+ );
242
+ }
243
+ });
244
+ });
245
+ return {
246
+ update(view2, prevState) {
247
+ if (!prevState.doc.eq(view2.state.doc)) sync();
248
+ if (options.activateOnSelection && !prevState.selection.eq(view2.state.selection)) {
249
+ const ids = getCommentIdsAt(view2.state);
250
+ const active = store.getSnapshot().activeThreadId;
251
+ if (!active || !ids.includes(active)) {
252
+ store.setActiveThread(ids[0] ?? null);
253
+ }
254
+ }
255
+ },
256
+ destroy() {
257
+ destroyed = true;
258
+ unsubscribe();
259
+ }
260
+ };
261
+ }
262
+ })
263
+ ];
264
+ }
265
+ });
266
+ function getCommentAuthor(editor) {
267
+ const ext = editor?.extensionManager?.extensions.find((e) => e.name === COMMENT_MARK_NAME);
268
+ return ext ? resolveAuthor(ext.options.author) : null;
269
+ }
270
+
25
271
  // src/extensions/table/nesting.ts
26
272
  import { Fragment, Slice } from "@tiptap/pm/model";
27
- import { Plugin, PluginKey, Selection } from "@tiptap/pm/state";
273
+ import { Plugin as Plugin2, PluginKey as PluginKey2, Selection } from "@tiptap/pm/state";
28
274
  import {
29
275
  CellSelection,
30
276
  __pastedCells,
31
277
  handlePaste as handleTablePaste
32
278
  } from "@tiptap/pm/tables";
33
279
  var ALLOW_NESTED_TABLES_META = "allowNestedTables";
34
- var tableNestingPluginKey = new PluginKey("tableNesting");
280
+ var tableNestingPluginKey = new PluginKey2("tableNesting");
35
281
  var roleOf = (node) => node.type.spec.tableRole;
36
282
  var isTableNode = (node) => roleOf(node) === "table";
37
283
  function outermostTableDepth($pos) {
@@ -171,7 +417,7 @@ var skip = (tr) => !tr.docChanged || tr.getMeta(ALLOW_NESTED_TABLES_META) === tr
171
417
  // (nested) table back so the deletion can be reviewed.
172
418
  !!tr.getMeta("appendedTransaction") || replacesDocument(tr);
173
419
  function createTableNestingPlugin() {
174
- return new Plugin({
420
+ return new Plugin2({
175
421
  key: tableNestingPluginKey,
176
422
  props: {
177
423
  handlePaste: (view, event, slice) => handleTablePasteWithoutNesting(view, event, slice)
@@ -1334,7 +1580,7 @@ function createChartNodeView(mount, renderStatic) {
1334
1580
 
1335
1581
  // src/extensions/chart/chart-node.ts
1336
1582
  import { Node } from "@tiptap/core";
1337
- import { Plugin as Plugin2, PluginKey as PluginKey2 } from "@tiptap/pm/state";
1583
+ import { Plugin as Plugin3, PluginKey as PluginKey3 } from "@tiptap/pm/state";
1338
1584
 
1339
1585
  // src/extensions/document-structure/utils.ts
1340
1586
  import { createDocument } from "@tiptap/core";
@@ -1419,7 +1665,7 @@ function syncInitialContent(editor, sync) {
1419
1665
  var STRUCTURE_PRIORITY = 45;
1420
1666
 
1421
1667
  // src/extensions/chart/chart-node.ts
1422
- var chartPluginKey = new PluginKey2("chartNode");
1668
+ var chartPluginKey = new PluginKey3("chartNode");
1423
1669
  function getDocumentCharts(doc, typeName = "chart") {
1424
1670
  const charts = [];
1425
1671
  doc.descendants((node, pos) => {
@@ -1614,7 +1860,7 @@ var Chart = Node.create({
1614
1860
  const { name } = this;
1615
1861
  const { generateId } = this.options;
1616
1862
  return [
1617
- new Plugin2({
1863
+ new Plugin3({
1618
1864
  key: chartPluginKey,
1619
1865
  appendTransaction: (transactions, _oldState, newState) => {
1620
1866
  if (!transactions.some((tr) => tr.docChanged)) return null;
@@ -2007,11 +2253,11 @@ function createTableStore(editor) {
2007
2253
  // src/extensions/agent-caret.ts
2008
2254
  import { Extension } from "@tiptap/core";
2009
2255
  import { closeHistory } from "@tiptap/pm/history";
2010
- import { Plugin as Plugin3, PluginKey as PluginKey3 } from "@tiptap/pm/state";
2011
- import { Decoration, DecorationSet } from "@tiptap/pm/view";
2256
+ import { Plugin as Plugin4, PluginKey as PluginKey4 } from "@tiptap/pm/state";
2257
+ import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "@tiptap/pm/view";
2012
2258
  var DEFAULT_AGENT_CARET_COLOR = "#8b5cf6";
2013
2259
  var DEFAULT_AGENT_CARET_NAME = "Agent";
2014
- var agentCaretPluginKey = new PluginKey3("agentCaret");
2260
+ var agentCaretPluginKey = new PluginKey4("agentCaret");
2015
2261
  var clamp = (pos, size) => Math.max(0, Math.min(size, Math.round(pos)));
2016
2262
  function caretWidget(caret) {
2017
2263
  return (view) => {
@@ -2036,7 +2282,7 @@ function buildDecorations(state, carets) {
2036
2282
  for (const caret of carets) {
2037
2283
  for (const range of caret.changed) {
2038
2284
  decorations.push(
2039
- Decoration.inline(range.from, range.to, {
2285
+ Decoration2.inline(range.from, range.to, {
2040
2286
  class: "pe-agent-changed",
2041
2287
  "data-agent-id": caret.id,
2042
2288
  style: `background-color:${tint(caret.color, 18)};`
@@ -2047,7 +2293,7 @@ function buildDecorations(state, carets) {
2047
2293
  const to = Math.max(caret.anchor, caret.head);
2048
2294
  if (from < to) {
2049
2295
  decorations.push(
2050
- Decoration.inline(from, to, {
2296
+ Decoration2.inline(from, to, {
2051
2297
  class: "pe-agent-selection",
2052
2298
  "data-agent-id": caret.id,
2053
2299
  style: `background-color:${tint(caret.color, 28)};`
@@ -2055,20 +2301,20 @@ function buildDecorations(state, carets) {
2055
2301
  );
2056
2302
  }
2057
2303
  decorations.push(
2058
- Decoration.widget(caret.head, caretWidget(caret), {
2304
+ Decoration2.widget(caret.head, caretWidget(caret), {
2059
2305
  key: `agent-caret:${caret.id}:${caret.name}:${caret.color}`,
2060
2306
  side: 10,
2061
2307
  ignoreSelection: true
2062
2308
  })
2063
2309
  );
2064
2310
  }
2065
- return DecorationSet.create(state.doc, decorations);
2311
+ return DecorationSet2.create(state.doc, decorations);
2066
2312
  }
2067
2313
  function createAgentCaretPlugin() {
2068
- return new Plugin3({
2314
+ return new Plugin4({
2069
2315
  key: agentCaretPluginKey,
2070
2316
  state: {
2071
- init: (_, state) => ({ carets: [], spans: {}, decorations: DecorationSet.create(state.doc, []) }),
2317
+ init: (_, state) => ({ carets: [], spans: {}, decorations: DecorationSet2.create(state.doc, []) }),
2072
2318
  apply(tr, value, _old, state) {
2073
2319
  const meta = tr.getMeta(agentCaretPluginKey);
2074
2320
  if (!tr.docChanged && !meta) return value;
@@ -2217,6 +2463,14 @@ async function typeAgentText(editor, options) {
2217
2463
  }
2218
2464
 
2219
2465
  export {
2466
+ commentsPluginKey,
2467
+ COMMENT_MARK_NAME,
2468
+ findCommentRanges,
2469
+ getCommentAnchors,
2470
+ getCommentIdsAt,
2471
+ getCommentStore,
2472
+ Comments,
2473
+ getCommentAuthor,
2220
2474
  ALLOW_NESTED_TABLES_META,
2221
2475
  tableNestingPluginKey,
2222
2476
  outermostTableDepth,