@richscripts/richtexteditor 2.6.2 → 2.6.4

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.
@@ -230,7 +230,17 @@ function RTE_Plugin_Comments() {
230
230
 
231
231
  var id = "cmt-" + Date.now() + "-" + Math.floor(Math.random() * 10000);
232
232
  var user = getCurrentUser();
233
- var anchor = options.anchor;
233
+ var anchor = options.anchor || options.range || null;
234
+ // Accept a plain DOM Range (or Range-like) as the anchor: it has the
235
+ // container/offset fields but no `.text`, so until 2026-09-02 the common
236
+ // call add({ anchor: range }) silently recorded the comment WITHOUT a
237
+ // highlight. Derive the text from the range instead.
238
+ if (anchor && anchor.startContainer && anchor.endContainer && !anchor.text) {
239
+ var derived = "";
240
+ try { derived = typeof anchor.toString === "function" ? String(anchor.toString()) : ""; } catch (e) { derived = ""; }
241
+ if (!derived) { try { derived = anchor.cloneContents ? (anchor.cloneContents().textContent || "") : ""; } catch (e2) { derived = ""; } }
242
+ anchor = { startContainer: anchor.startContainer, startOffset: anchor.startOffset, endContainer: anchor.endContainer, endOffset: anchor.endOffset, text: derived };
243
+ }
234
244
  var wrappedText = "";
235
245
 
236
246
  if (anchor && anchor.startContainer && anchor.endContainer && anchor.text) {
@@ -29,9 +29,6 @@
29
29
  restoreSelection: () => restoreSelection
30
30
  });
31
31
 
32
- // src/types.ts
33
- var LOCAL_ORIGIN = /* @__PURE__ */ Symbol.for("rte.crdt.local");
34
-
35
32
  // node_modules/yjs/dist/yjs.mjs
36
33
  var yjs_exports = {};
37
34
  __export(yjs_exports, {
@@ -9716,6 +9713,9 @@ ${err.toString()}`);
9716
9713
  }
9717
9714
  glo[importIdentifier] = true;
9718
9715
 
9716
+ // src/types.ts
9717
+ var LOCAL_ORIGIN = /* @__PURE__ */ Symbol.for("rte.crdt.local");
9718
+
9719
9719
  // src/schema.ts
9720
9720
  var BLOCK_TAGS = /* @__PURE__ */ new Set([
9721
9721
  "p",
@@ -9803,7 +9803,13 @@ ${err.toString()}`);
9803
9803
  "fontSize",
9804
9804
  "link",
9805
9805
  // value: href
9806
- "code"
9806
+ "code",
9807
+ // Passthrough for inline elements that are not a formatting mark: a JSON
9808
+ // string {tag, attrs} (span.rte-comment[data-comment-id], span.rte-mention,
9809
+ // span.rte-tc-insert, sup.rte-footnote-ref, ...). Without it these folded
9810
+ // into the text run with NO mark, so peers never received comment anchors,
9811
+ // mention chips or tracked-change spans (2026-09-02).
9812
+ "inline"
9807
9813
  ];
9808
9814
 
9809
9815
  // src/render.ts
@@ -9871,31 +9877,72 @@ ${err.toString()}`);
9871
9877
  var _a;
9872
9878
  const frag = doc2.createDocumentFragment();
9873
9879
  const delta = ytext.toDelta();
9880
+ let openInline = null;
9874
9881
  for (const op of delta) {
9875
9882
  if (typeof op.insert !== "string") continue;
9876
9883
  if (op.insert.length === 0) continue;
9877
- const marks = sortedMarks(op.attributes);
9884
+ const attrs = (_a = op.attributes) != null ? _a : {};
9885
+ const marks = sortedMarks(attrs);
9886
+ const inlineKey = typeof attrs["inline"] === "string" ? attrs["inline"] : "";
9878
9887
  let target = doc2.createTextNode(op.insert);
9879
9888
  for (let i = marks.length - 1; i >= 0; i--) {
9880
- const wrapper = wrapForMark(marks[i], (_a = op.attributes) != null ? _a : {}, doc2);
9889
+ if (marks[i] === "inline") continue;
9890
+ const wrapper = wrapForMark(marks[i], attrs, doc2);
9881
9891
  wrapper.appendChild(target);
9882
9892
  target = wrapper;
9883
9893
  }
9884
- frag.appendChild(target);
9894
+ if (inlineKey) {
9895
+ if (!openInline || openInline.key !== inlineKey) {
9896
+ openInline = { key: inlineKey, el: wrapForMark("inline", attrs, doc2) };
9897
+ frag.appendChild(openInline.el);
9898
+ }
9899
+ openInline.el.appendChild(target);
9900
+ } else {
9901
+ openInline = null;
9902
+ frag.appendChild(target);
9903
+ }
9885
9904
  }
9886
9905
  return frag;
9887
9906
  }
9888
- var MARK_PRECEDENCE = ["link", "color", "backcolor", "fontFamily", "fontSize", "code", "underline", "strikethrough", "subscript", "superscript", "italic", "bold"];
9907
+ var MARK_PRECEDENCE = ["inline", "link", "color", "backcolor", "fontFamily", "fontSize", "code", "underline", "strikethrough", "subscript", "superscript", "italic", "bold"];
9889
9908
  function sortedMarks(attrs) {
9909
+ var _a;
9890
9910
  if (!attrs) return [];
9891
9911
  const present = [];
9912
+ let inlineTag = "";
9913
+ if (typeof attrs["inline"] === "string") {
9914
+ try {
9915
+ inlineTag = String((_a = JSON.parse(attrs["inline"]).tag) != null ? _a : "").toLowerCase();
9916
+ } catch (e) {
9917
+ inlineTag = "";
9918
+ }
9919
+ }
9892
9920
  for (const m of MARK_PRECEDENCE) {
9893
- if (m in attrs && attrs[m] != null && attrs[m] !== false) present.push(m);
9921
+ if (!(m in attrs) || attrs[m] == null || attrs[m] === false) continue;
9922
+ if (m === "superscript" && inlineTag === "sup" || m === "subscript" && inlineTag === "sub") continue;
9923
+ present.push(m);
9894
9924
  }
9895
9925
  return present;
9896
9926
  }
9897
9927
  function wrapForMark(mark, attrs, doc2) {
9898
9928
  switch (mark) {
9929
+ case "inline": {
9930
+ let tag = "span";
9931
+ let attrsMap = {};
9932
+ try {
9933
+ const parsed = JSON.parse(String(attrs["inline"]));
9934
+ if (parsed.tag) tag = parsed.tag;
9935
+ if (parsed.attrs) attrsMap = parsed.attrs;
9936
+ } catch (e) {
9937
+ }
9938
+ if (!/^[a-z][a-z0-9-]*$/.test(tag)) tag = "span";
9939
+ const el = doc2.createElementNS(HTML_NS, tag);
9940
+ for (const k of Object.keys(attrsMap)) {
9941
+ if (/^on/i.test(k) || k === "style") continue;
9942
+ el.setAttribute(k, attrsMap[k]);
9943
+ }
9944
+ return el;
9945
+ }
9899
9946
  case "bold":
9900
9947
  return doc2.createElementNS(HTML_NS, "strong");
9901
9948
  case "italic":
@@ -10029,11 +10076,13 @@ ${err.toString()}`);
10029
10076
  if (!win) {
10030
10077
  return { disconnect: () => {
10031
10078
  }, drainPending: () => {
10079
+ }, flushPending: () => {
10032
10080
  } };
10033
10081
  }
10034
10082
  const MO = (_b = win.MutationObserver) != null ? _b : globalThis.MutationObserver;
10035
10083
  if (!MO) return { disconnect: () => {
10036
10084
  }, drainPending: () => {
10085
+ }, flushPending: () => {
10037
10086
  } };
10038
10087
  const observer = new MO((mutations) => {
10039
10088
  var _a2;
@@ -10052,10 +10101,22 @@ ${err.toString()}`);
10052
10101
  attributes: true,
10053
10102
  attributeOldValue: true
10054
10103
  });
10104
+ const process2 = (mutations) => {
10105
+ var _a2;
10106
+ if (!mutations.length || opts.isApplyingRemote()) return;
10107
+ (_a2 = opts.fragment.doc) == null ? void 0 : _a2.transact(() => {
10108
+ for (const m of mutations) {
10109
+ handleMutation(m, opts);
10110
+ }
10111
+ }, LOCAL_ORIGIN);
10112
+ };
10055
10113
  return {
10056
10114
  disconnect: () => observer.disconnect(),
10057
10115
  drainPending: () => {
10058
10116
  observer.takeRecords();
10117
+ },
10118
+ flushPending: () => {
10119
+ process2(observer.takeRecords());
10059
10120
  }
10060
10121
  };
10061
10122
  }
@@ -10078,9 +10139,38 @@ ${err.toString()}`);
10078
10139
  const yNode = locateYNodeForText(target, opts);
10079
10140
  if (!yNode) return;
10080
10141
  const newText = target.data;
10081
- if (yNode.toString() === newText) return;
10082
- yNode.delete(0, yNode.length);
10083
- yNode.insert(0, newText);
10142
+ applyTextDiff(yNode, newText);
10143
+ }
10144
+ function applyTextDiff(yNode, newText) {
10145
+ const oldText = yNode.toString();
10146
+ if (oldText === newText) return;
10147
+ let prefix = 0;
10148
+ const maxPrefix = Math.min(oldText.length, newText.length);
10149
+ while (prefix < maxPrefix && oldText.charCodeAt(prefix) === newText.charCodeAt(prefix)) prefix++;
10150
+ let suffix = 0;
10151
+ const maxSuffix = Math.min(oldText.length, newText.length) - prefix;
10152
+ while (suffix < maxSuffix && oldText.charCodeAt(oldText.length - 1 - suffix) === newText.charCodeAt(newText.length - 1 - suffix)) suffix++;
10153
+ const removeLen = oldText.length - prefix - suffix;
10154
+ const insertStr = newText.slice(prefix, newText.length - suffix);
10155
+ if (removeLen > 0) yNode.delete(prefix, removeLen);
10156
+ if (insertStr.length > 0) {
10157
+ let attrs;
10158
+ try {
10159
+ const delta = yNode.toDelta();
10160
+ let pos = 0;
10161
+ for (const op of delta) {
10162
+ const len = typeof op.insert === "string" ? op.insert.length : 1;
10163
+ if (prefix > pos && prefix <= pos + len) {
10164
+ attrs = op.attributes;
10165
+ break;
10166
+ }
10167
+ pos += len;
10168
+ }
10169
+ } catch (e) {
10170
+ attrs = void 0;
10171
+ }
10172
+ yNode.insert(prefix, insertStr, attrs);
10173
+ }
10084
10174
  }
10085
10175
  function handleChildList(m, opts) {
10086
10176
  if (m.target === opts.editable) {
@@ -10091,13 +10181,51 @@ ${err.toString()}`);
10091
10181
  rebuildSubtreeAtElement(m.target, opts);
10092
10182
  }
10093
10183
  function rebuildSubtreeAtElement(domEl, opts) {
10184
+ var _a;
10094
10185
  const yEl = locateYElement(domEl, opts);
10095
10186
  if (!yEl) {
10096
10187
  scheduleCoarseResync(opts);
10097
10188
  return;
10098
10189
  }
10099
- yEl.delete(0, yEl.length);
10100
10190
  const folded = foldInlineRunsToYChildren(Array.from(domEl.childNodes));
10191
+ if (yEl.length === 1 && folded.children.length === 1) {
10192
+ const existing = yEl.get(0);
10193
+ const fresh = folded.children[0];
10194
+ if (existing instanceof YXmlText && fresh instanceof YXmlText) {
10195
+ const plan = folded.marked.find((m) => m.text === fresh);
10196
+ const newText = (_a = domEl.textContent) != null ? _a : "";
10197
+ applyTextDiff(existing, newText);
10198
+ if (plan) {
10199
+ const len = existing.length;
10200
+ if (len > 0) {
10201
+ const reset = {};
10202
+ for (const m of SUPPORTED_MARKS) reset[m] = null;
10203
+ const wanted = plan.ranges.map((r) => JSON.stringify([r.start, r.end, r.marks])).join("|");
10204
+ const current = existing.toDelta();
10205
+ let pos = 0;
10206
+ const currentRanges = [];
10207
+ for (const op of current) {
10208
+ const l = typeof op.insert === "string" ? op.insert.length : 1;
10209
+ if (op.attributes && Object.keys(op.attributes).length) currentRanges.push(JSON.stringify([pos, pos + l, op.attributes]));
10210
+ pos += l;
10211
+ }
10212
+ if (currentRanges.join("|") !== wanted) {
10213
+ existing.format(0, len, reset);
10214
+ for (const r of plan.ranges) existing.format(r.start, r.end - r.start, r.marks);
10215
+ }
10216
+ }
10217
+ } else if (existing.length > 0) {
10218
+ const current = existing.toDelta();
10219
+ if (current.some((op) => op.attributes && Object.keys(op.attributes).length)) {
10220
+ const reset = {};
10221
+ for (const m of SUPPORTED_MARKS) reset[m] = null;
10222
+ existing.format(0, existing.length, reset);
10223
+ }
10224
+ }
10225
+ return;
10226
+ }
10227
+ }
10228
+ yEl.delete(0, yEl.length);
10101
10229
  if (folded.children.length > 0) {
10102
10230
  yEl.insert(0, folded.children);
10103
10231
  applyDeferredMarks(folded.marked);
@@ -10300,6 +10428,20 @@ ${err.toString()}`);
10300
10428
  applyDeferredMarks
10301
10429
  };
10302
10430
  function marksFromMarkElement(el) {
10431
+ const base = baseMarksFromMarkElement(el);
10432
+ const tag = el.tagName.toLowerCase();
10433
+ if (tag === "a") return base;
10434
+ const attrs = {};
10435
+ let hasIdentity = false;
10436
+ for (const attr of Array.from(el.attributes)) {
10437
+ if (attr.name === "style") continue;
10438
+ attrs[attr.name] = attr.value;
10439
+ hasIdentity = true;
10440
+ }
10441
+ if (hasIdentity) base["inline"] = JSON.stringify({ tag, attrs });
10442
+ return base;
10443
+ }
10444
+ function baseMarksFromMarkElement(el) {
10303
10445
  const tag = el.tagName.toLowerCase();
10304
10446
  switch (tag) {
10305
10447
  case "strong":
@@ -10376,10 +10518,12 @@ ${err.toString()}`);
10376
10518
  const fragment = options.ydoc.getXmlFragment(fragmentName);
10377
10519
  const awareness = (_c = options.awareness) != null ? _c : (_b = options.provider) == null ? void 0 : _b.awareness;
10378
10520
  let applyingRemote = false;
10521
+ let composing = false;
10379
10522
  const observerHandle = startLocalObserver({
10380
10523
  editable: options.editable,
10381
10524
  fragment,
10382
- isApplyingRemote: () => applyingRemote
10525
+ // `composing` also gates the observer: see the IME composition gate below.
10526
+ isApplyingRemote: () => applyingRemote || composing
10383
10527
  });
10384
10528
  applyingRemote = true;
10385
10529
  try {
@@ -10388,8 +10532,8 @@ ${err.toString()}`);
10388
10532
  observerHandle.drainPending();
10389
10533
  applyingRemote = false;
10390
10534
  }
10391
- const onUpdate = (_update, origin) => {
10392
- if (origin === LOCAL_ORIGIN) return;
10535
+ const renderRemote = () => {
10536
+ observerHandle.flushPending();
10393
10537
  const captured = captureSelection(options.editable);
10394
10538
  applyingRemote = true;
10395
10539
  try {
@@ -10400,13 +10544,96 @@ ${err.toString()}`);
10400
10544
  }
10401
10545
  restoreSelection(options.editable, captured);
10402
10546
  };
10547
+ let pendingRemoteRender = false;
10548
+ let composeTarget = null;
10549
+ const observerOpts = { editable: options.editable, fragment, isApplyingRemote: () => applyingRemote };
10550
+ const resolveTextAnchor = (container, offset) => {
10551
+ if (container.nodeType === 3) return { node: container, offset };
10552
+ if (container.nodeType !== 1) return null;
10553
+ const children = container.childNodes;
10554
+ const before = offset > 0 ? children[offset - 1] : null;
10555
+ if (before && before.nodeType === 3) {
10556
+ const t = before;
10557
+ return { node: t, offset: t.data.length };
10558
+ }
10559
+ const at = children[offset];
10560
+ if (at && at.nodeType === 3) return { node: at, offset: 0 };
10561
+ return null;
10562
+ };
10563
+ const onCompositionStart = () => {
10564
+ var _a2;
10565
+ composeTarget = null;
10566
+ try {
10567
+ const doc2 = options.editable.ownerDocument;
10568
+ const sel = (_a2 = doc2 == null ? void 0 : doc2.defaultView) == null ? void 0 : _a2.getSelection();
10569
+ const range = sel && sel.rangeCount ? sel.getRangeAt(0) : null;
10570
+ const startAnchor = range ? resolveTextAnchor(range.startContainer, range.startOffset) : null;
10571
+ const endAnchor = range ? resolveTextAnchor(range.endContainer, range.endOffset) : null;
10572
+ if (startAnchor && endAnchor && startAnchor.node === endAnchor.node) {
10573
+ const yText = locateYNodeForText(startAnchor.node, observerOpts);
10574
+ if (yText) {
10575
+ composeTarget = {
10576
+ yText,
10577
+ start: createRelativePositionFromTypeIndex(yText, startAnchor.offset),
10578
+ end: createRelativePositionFromTypeIndex(yText, endAnchor.offset)
10579
+ };
10580
+ }
10581
+ }
10582
+ } catch (err) {
10583
+ composeTarget = null;
10584
+ }
10585
+ composing = composeTarget !== null;
10586
+ };
10587
+ const endComposition = (event) => {
10588
+ var _a2;
10589
+ if (!composing) return;
10590
+ composing = false;
10591
+ const target = composeTarget;
10592
+ composeTarget = null;
10593
+ const data = (_a2 = event == null ? void 0 : event.data) != null ? _a2 : "";
10594
+ observerHandle.drainPending();
10595
+ try {
10596
+ if (target) {
10597
+ const start = createAbsolutePositionFromRelativePosition(target.start, options.ydoc);
10598
+ const end = createAbsolutePositionFromRelativePosition(target.end, options.ydoc);
10599
+ if (start && end && start.type === target.yText) {
10600
+ const from2 = Math.min(start.index, end.index);
10601
+ const to = Math.max(start.index, end.index);
10602
+ options.ydoc.transact(() => {
10603
+ if (to > from2) target.yText.delete(from2, to - from2);
10604
+ if (data) target.yText.insert(from2, data);
10605
+ }, LOCAL_ORIGIN);
10606
+ }
10607
+ }
10608
+ } catch (err) {
10609
+ console.error("[crdt-engine] composition commit failed:", err);
10610
+ }
10611
+ pendingRemoteRender = false;
10612
+ renderRemote();
10613
+ };
10614
+ options.editable.addEventListener("compositionstart", onCompositionStart);
10615
+ options.editable.addEventListener("compositionend", endComposition);
10616
+ options.editable.addEventListener("blur", endComposition);
10617
+ const unsubscribeComposition = () => {
10618
+ options.editable.removeEventListener("compositionstart", onCompositionStart);
10619
+ options.editable.removeEventListener("compositionend", endComposition);
10620
+ options.editable.removeEventListener("blur", endComposition);
10621
+ };
10622
+ const onUpdate = (_update, origin) => {
10623
+ if (origin === LOCAL_ORIGIN) return;
10624
+ if (composing) {
10625
+ pendingRemoteRender = true;
10626
+ return;
10627
+ }
10628
+ renderRemote();
10629
+ };
10403
10630
  options.ydoc.on("update", onUpdate);
10404
10631
  const unsubscribeRemote = () => {
10405
10632
  options.ydoc.off("update", onUpdate);
10406
10633
  };
10407
10634
  const unsubscribeLocal = () => observerHandle.disconnect();
10408
10635
  const unsubscribeAwareness = subscribeAwarenessStub(awareness, options.initialAwareness);
10409
- let cleanups = [unsubscribeRemote, unsubscribeLocal, unsubscribeAwareness];
10636
+ let cleanups = [unsubscribeRemote, unsubscribeLocal, unsubscribeAwareness, unsubscribeComposition];
10410
10637
  return {
10411
10638
  ydoc: options.ydoc,
10412
10639
  fragment,