@domternal/core 0.14.0 → 0.15.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/dist/index.js CHANGED
@@ -1139,7 +1139,8 @@ function markInputRule(options) {
1139
1139
  return null;
1140
1140
  }
1141
1141
  const { tr } = state;
1142
- tr.replaceWith(start, end, state.schema.text(textContent));
1142
+ const marks = tr.storedMarks ?? tr.doc.resolve(start).marksAcross(tr.doc.resolve(end));
1143
+ tr.replaceWith(start, end, state.schema.text(textContent, marks));
1143
1144
  tr.addMark(start, start + textContent.length, type.create(attributes ?? void 0));
1144
1145
  tr.removeStoredMark(type);
1145
1146
  return tr;
@@ -1231,7 +1232,10 @@ function textInputRule(options) {
1231
1232
  const { find: find2, replace, undoable } = options;
1232
1233
  return new InputRule(
1233
1234
  find2,
1234
- (state, _match, start, end) => state.tr.replaceWith(start, end, state.schema.text(replace)),
1235
+ // insertText inherits the replaced range's marks (stored marks first,
1236
+ // then marksAcross), so a replacement inside marked text keeps the
1237
+ // marks instead of punching an unmarked hole into them.
1238
+ (state, _match, start, end) => state.tr.insertText(replace, start, end),
1235
1239
  undoable !== void 0 ? { undoable } : {}
1236
1240
  );
1237
1241
  }
@@ -1810,6 +1814,8 @@ var Mark = class _Mark extends Extension {
1810
1814
  if (this.config.group !== void 0) spec.group = this.config.group;
1811
1815
  if (this.config.spanning !== void 0)
1812
1816
  spec.spanning = this.config.spanning;
1817
+ if (this.config.keepOnDuplicate !== void 0)
1818
+ spec["keepOnDuplicate"] = this.config.keepOnDuplicate;
1813
1819
  const attributeSpecs = callOrReturn(this.config.addAttributes, this);
1814
1820
  if (attributeSpecs) {
1815
1821
  spec.attrs = buildProseMirrorAttrs(attributeSpecs);
@@ -1847,9 +1853,9 @@ var Mark = class _Mark extends Extension {
1847
1853
  const renderFn = this.config.renderHTML;
1848
1854
  const attrSpecs = attributeSpecs;
1849
1855
  const markInstance = this;
1850
- spec.toDOM = (mark, _inline) => {
1851
- const htmlAttrs = attrSpecs ? buildHTMLAttributes(mark.attrs, attrSpecs) : {};
1852
- return renderFn.call(markInstance, { mark, HTMLAttributes: htmlAttrs });
1856
+ spec.toDOM = (mark2, _inline) => {
1857
+ const htmlAttrs = attrSpecs ? buildHTMLAttributes(mark2.attrs, attrSpecs) : {};
1858
+ return renderFn.call(markInstance, { mark: mark2, HTMLAttributes: htmlAttrs });
1853
1859
  };
1854
1860
  }
1855
1861
  return spec;
@@ -1942,8 +1948,8 @@ var setMark = (markName, attributes) => ({ state, tr, dispatch }) => {
1942
1948
  const from = firstRange.$from.pos;
1943
1949
  const existingMark = tr.storedMarks?.find((m) => m.type === markType) ?? state.storedMarks?.find((m) => m.type === markType) ?? tr.doc.resolve(from).marks().find((m) => m.type === markType) ?? null;
1944
1950
  const mergedAttrs = existingMark ? { ...existingMark.attrs, ...attributes } : attributes;
1945
- const mark = markType.create(mergedAttrs);
1946
- tr.addStoredMark(mark);
1951
+ const mark2 = markType.create(mergedAttrs);
1952
+ tr.addStoredMark(mark2);
1947
1953
  dispatch(tr);
1948
1954
  return true;
1949
1955
  }
@@ -2570,12 +2576,12 @@ var updateAttributes = (typeOrName, attributes) => ({ state, tr, dispatch }) =>
2570
2576
  const markType = state.schema.marks[typeOrName];
2571
2577
  tr.doc.nodesBetween(from, to, (node, pos) => {
2572
2578
  if (!node.isInline) return;
2573
- const mark = markType.isInSet(node.marks);
2574
- if (mark) {
2579
+ const mark2 = markType.isInSet(node.marks);
2580
+ if (mark2) {
2575
2581
  markChanges.push({
2576
2582
  pos,
2577
2583
  nodeSize: node.nodeSize,
2578
- attrs: { ...mark.attrs, ...attributes }
2584
+ attrs: { ...mark2.attrs, ...attributes }
2579
2585
  });
2580
2586
  }
2581
2587
  });
@@ -2621,12 +2627,12 @@ var resetAttributes = (typeOrName, attributeName) => ({ state, tr, dispatch }) =
2621
2627
  const defaultValue = markType.spec.attrs?.[attributeName]?.default;
2622
2628
  tr.doc.nodesBetween(from, to, (node, pos) => {
2623
2629
  if (!node.isInline) return;
2624
- const mark = markType.isInSet(node.marks);
2625
- if (mark) {
2630
+ const mark2 = markType.isInSet(node.marks);
2631
+ if (mark2) {
2626
2632
  markChanges.push({
2627
2633
  pos,
2628
2634
  nodeSize: node.nodeSize,
2629
- attrs: { ...mark.attrs, [attributeName]: defaultValue }
2635
+ attrs: { ...mark2.attrs, [attributeName]: defaultValue }
2630
2636
  });
2631
2637
  }
2632
2638
  });
@@ -3509,6 +3515,12 @@ var Editor = class _Editor extends EventEmitter {
3509
3515
  * True while EditorView's constructor runs; see buildViewDispatch.
3510
3516
  */
3511
3517
  _isViewConstructing = false;
3518
+ /**
3519
+ * The `.dm-editor` host this editor painted `dm-notion-mode` onto because
3520
+ * of `preset: 'notion'`. Tracked so destroy() removes only a class the
3521
+ * editor itself added, never one the consumer wrote.
3522
+ */
3523
+ _presetClassHost = null;
3512
3524
  /**
3513
3525
  * Creates a new Editor instance
3514
3526
  *
@@ -3553,6 +3565,43 @@ var Editor = class _Editor extends EventEmitter {
3553
3565
  get isEditable() {
3554
3566
  return this.view ? this.view.editable : this.options.editable ?? true;
3555
3567
  }
3568
+ /**
3569
+ * The resolved editing-experience preset.
3570
+ *
3571
+ * The `preset` option wins when provided (so an explicit 'classic' can
3572
+ * opt out of everything). Otherwise a `dm-notion-mode` class on or above
3573
+ * the view counts as 'notion': consumers that predate the option declare
3574
+ * Notion mode with the theme class alone, and behavior must follow what
3575
+ * the user actually sees. Resolved on every read, not cached, so a class
3576
+ * toggled at runtime is picked up.
3577
+ */
3578
+ get preset() {
3579
+ if (this.options.preset) {
3580
+ return this.options.preset;
3581
+ }
3582
+ if (this.view?.dom.closest(".dm-notion-mode")) {
3583
+ return "notion";
3584
+ }
3585
+ return "classic";
3586
+ }
3587
+ /**
3588
+ * Paints `dm-notion-mode` on the `.dm-editor` host when the editor was
3589
+ * created with `preset: 'notion'`. Runs during creation, and framework
3590
+ * wrappers call it again after adopting the view's DOM: they construct
3591
+ * the editor in a detached element, so the creation-time run cannot see
3592
+ * the host yet. Idempotent; a no-op for any other preset. Only a class
3593
+ * added here is removed again on destroy.
3594
+ */
3595
+ adoptPresetClass() {
3596
+ if (this.options.preset !== "notion" || this._presetClassHost) {
3597
+ return;
3598
+ }
3599
+ const host = this.view.dom.closest(".dm-editor");
3600
+ if (host && !host.classList.contains("dm-notion-mode")) {
3601
+ host.classList.add("dm-notion-mode");
3602
+ this._presetClassHost = host;
3603
+ }
3604
+ }
3556
3605
  /**
3557
3606
  * Checks if the editor content is empty
3558
3607
  */
@@ -3638,11 +3687,11 @@ var Editor = class _Editor extends EventEmitter {
3638
3687
  if (markType) {
3639
3688
  if (selection.empty) {
3640
3689
  const storedMarks = state.storedMarks ?? $from.marks();
3641
- const hasMark = storedMarks.some((mark) => mark.type === markType);
3690
+ const hasMark = storedMarks.some((mark2) => mark2.type === markType);
3642
3691
  if (!hasMark) return false;
3643
3692
  if (attrs) {
3644
- const mark = storedMarks.find((m) => m.type === markType);
3645
- return mark ? this.matchAttributes(mark.attrs, attrs) : false;
3693
+ const mark2 = storedMarks.find((m) => m.type === markType);
3694
+ return mark2 ? this.matchAttributes(mark2.attrs, attrs) : false;
3646
3695
  }
3647
3696
  return true;
3648
3697
  }
@@ -3717,8 +3766,8 @@ var Editor = class _Editor extends EventEmitter {
3717
3766
  const markType = schema.marks[name];
3718
3767
  if (markType) {
3719
3768
  const marks = state.storedMarks ?? $from.marks();
3720
- const mark = marks.find((m) => m.type === markType);
3721
- return mark ? { ...mark.attrs } : {};
3769
+ const mark2 = marks.find((m) => m.type === markType);
3770
+ return mark2 ? { ...mark2.attrs } : {};
3722
3771
  }
3723
3772
  const nodeType = schema.nodes[name];
3724
3773
  if (nodeType) {
@@ -3899,6 +3948,10 @@ var Editor = class _Editor extends EventEmitter {
3899
3948
  }
3900
3949
  this.emit("destroy");
3901
3950
  this.options.onDestroy?.();
3951
+ if (this._presetClassHost) {
3952
+ this._presetClassHost.classList.remove("dm-notion-mode");
3953
+ this._presetClassHost = null;
3954
+ }
3902
3955
  this.view.destroy();
3903
3956
  this._extensionManager.destroy();
3904
3957
  this.removeAllListeners();
@@ -4001,6 +4054,7 @@ var Editor = class _Editor extends EventEmitter {
4001
4054
  }
4002
4055
  });
4003
4056
  this._isViewConstructing = false;
4057
+ this.adoptPresetClass();
4004
4058
  this.emit("mount", { editor: this, view: this.view });
4005
4059
  this.options.onMount?.({ editor: this, view: this.view });
4006
4060
  this.commandManager = new CommandManager(this);
@@ -4237,7 +4291,6 @@ function refocusEditorAfterCommand(view) {
4237
4291
  }
4238
4292
 
4239
4293
  // src/utils/defaultBubbleContexts.ts
4240
- var NOTION_MODE_CLASS = "dm-notion-mode";
4241
4294
  var NOTION_TEXT_CONTEXT = Object.freeze([
4242
4295
  // `ai` leads (Notion's "Ask AI"); skipped with its leading separator when
4243
4296
  // the pro extension is absent, exactly like `mathInline`.
@@ -4265,8 +4318,7 @@ var STANDARD_TEXT_CONTEXT = Object.freeze([
4265
4318
  "link"
4266
4319
  ]);
4267
4320
  function defaultBubbleContexts(editor) {
4268
- const inNotionMode = editor.view.dom.closest("." + NOTION_MODE_CLASS) !== null;
4269
- const text = inNotionMode ? NOTION_TEXT_CONTEXT : STANDARD_TEXT_CONTEXT;
4321
+ const text = editor.preset === "notion" ? NOTION_TEXT_CONTEXT : STANDARD_TEXT_CONTEXT;
4270
4322
  return { text: [...text] };
4271
4323
  }
4272
4324
 
@@ -5025,7 +5077,8 @@ function groupFloatingMenuItems(items) {
5025
5077
  if (!list) {
5026
5078
  list = [];
5027
5079
  map.set(name, list);
5028
- order.push(name);
5080
+ if (name === "") order.unshift(name);
5081
+ else order.push(name);
5029
5082
  }
5030
5083
  list.push(item);
5031
5084
  }
@@ -5559,7 +5612,8 @@ var defaultIcons = {
5559
5612
  plus: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z"/></svg>',
5560
5613
  dotsSixVertical: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M108,60A16,16,0,1,1,92,44,16,16,0,0,1,108,60Zm56,16A16,16,0,1,0,148,60,16,16,0,0,0,164,76ZM92,112a16,16,0,1,0,16,16A16,16,0,0,0,92,112Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,112ZM92,180a16,16,0,1,0,16,16A16,16,0,0,0,92,180Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,180Z"/></svg>',
5561
5614
  dotsThree: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M140,128a12,12,0,1,1-12-12A12,12,0,0,1,140,128ZM64,116a12,12,0,1,0,12,12A12,12,0,0,0,64,116Zm128,0a12,12,0,1,0,12,12A12,12,0,0,0,192,116Z"/></svg>',
5562
- copy: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"/></svg>'
5615
+ copy: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"/></svg>',
5616
+ printer: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M214.67,72H200V40a8,8,0,0,0-8-8H64a8,8,0,0,0-8,8V72H41.33C27.36,72,16,82.77,16,96v80a8,8,0,0,0,8,8H56v32a8,8,0,0,0,8,8H192a8,8,0,0,0,8-8V184h32a8,8,0,0,0,8-8V96C240,82.77,228.64,72,214.67,72ZM72,48H184V72H72ZM184,208H72V160H184Zm40-40H200V152a8,8,0,0,0-8-8H64a8,8,0,0,0-8,8v16H32V96c0-4.34,4.28-8,9.33-8H214.67c5.05,0,9.33,3.66,9.33,8Zm-24-52a12,12,0,1,1-12-12A12,12,0,0,1,200,116Z"/></svg>'
5563
5617
  };
5564
5618
 
5565
5619
  // src/nodes/Document.ts
@@ -7091,6 +7145,7 @@ var TaskList = Node2.create({
7091
7145
  // src/marks/Bold.ts
7092
7146
  var Bold = Mark.create({
7093
7147
  name: "bold",
7148
+ group: "formatting",
7094
7149
  addOptions() {
7095
7150
  return {
7096
7151
  HTMLAttributes: {}
@@ -7168,6 +7223,7 @@ var Bold = Mark.create({
7168
7223
  // src/marks/Italic.ts
7169
7224
  var Italic = Mark.create({
7170
7225
  name: "italic",
7226
+ group: "formatting",
7171
7227
  addOptions() {
7172
7228
  return {
7173
7229
  HTMLAttributes: {}
@@ -7247,6 +7303,7 @@ var Italic = Mark.create({
7247
7303
  // src/marks/Underline.ts
7248
7304
  var Underline = Mark.create({
7249
7305
  name: "underline",
7306
+ group: "formatting",
7250
7307
  addOptions() {
7251
7308
  return {
7252
7309
  HTMLAttributes: {}
@@ -7299,6 +7356,7 @@ var Underline = Mark.create({
7299
7356
  // src/marks/Strike.ts
7300
7357
  var Strike = Mark.create({
7301
7358
  name: "strike",
7359
+ group: "formatting",
7302
7360
  addOptions() {
7303
7361
  return {
7304
7362
  HTMLAttributes: {}
@@ -7370,9 +7428,14 @@ var Code = Mark.create({
7370
7428
  HTMLAttributes: {}
7371
7429
  };
7372
7430
  },
7373
- // Code mark is exclusive - it cannot be combined with other marks
7374
- // ProseMirror uses '_' to mean "exclude all marks"
7375
- excludes: "_",
7431
+ // Code cannot be combined with other FORMATTING marks (bold, italic,
7432
+ // color...), but semantic marks outside the group (link, a comment
7433
+ // thread anchor) survive: code excludes the 'formatting' group instead
7434
+ // of '_' (everything). Third-party formatting marks opt into the same
7435
+ // exclusion by declaring group: 'formatting'. Code is in the group
7436
+ // itself, which also keeps it self-exclusive.
7437
+ group: "formatting",
7438
+ excludes: "formatting",
7376
7439
  // Code should not span across multiple nodes
7377
7440
  spanning: false,
7378
7441
  parseHTML() {
@@ -7860,6 +7923,7 @@ var Link = Mark.create({
7860
7923
  // src/marks/Subscript.ts
7861
7924
  var Subscript = Mark.create({
7862
7925
  name: "subscript",
7926
+ group: "formatting",
7863
7927
  // Mutual exclusion handled in toggle commands (not schema)
7864
7928
  // so can() dry-run works correctly for toolbar disabled state
7865
7929
  excludes: "",
@@ -7921,6 +7985,7 @@ var Subscript = Mark.create({
7921
7985
  // src/marks/Superscript.ts
7922
7986
  var Superscript = Mark.create({
7923
7987
  name: "superscript",
7988
+ group: "formatting",
7924
7989
  // Mutual exclusion handled in toggle commands (not schema)
7925
7990
  // so can() dry-run works correctly for toolbar disabled state
7926
7991
  excludes: "",
@@ -7982,6 +8047,7 @@ var Superscript = Mark.create({
7982
8047
  // src/marks/TextStyle.ts
7983
8048
  var TextStyle = Mark.create({
7984
8049
  name: "textStyle",
8050
+ group: "formatting",
7985
8051
  // Lower priority so it renders after other marks
7986
8052
  priority: 101,
7987
8053
  addOptions() {
@@ -8038,7 +8104,7 @@ var TextStyle = Mark.create({
8038
8104
  let hasEmptyTextStyle = false;
8039
8105
  tr.doc.nodesBetween(from, to, (node) => {
8040
8106
  const textStyleMark = node.marks.find(
8041
- (mark) => mark.type.name === this.name
8107
+ (mark2) => mark2.type.name === this.name
8042
8108
  );
8043
8109
  if (textStyleMark) {
8044
8110
  const hasNonNullAttr = Object.values(textStyleMark.attrs).some(
@@ -8052,9 +8118,9 @@ var TextStyle = Mark.create({
8052
8118
  if (!hasEmptyTextStyle) return false;
8053
8119
  if (dispatch) {
8054
8120
  tr.doc.nodesBetween(from, to, (node, pos) => {
8055
- const mark = node.marks.find((m) => m.type === markType);
8056
- if (mark) {
8057
- const hasActiveAttr = Object.values(mark.attrs).some(
8121
+ const mark2 = node.marks.find((m) => m.type === markType);
8122
+ if (mark2) {
8123
+ const hasActiveAttr = Object.values(mark2.attrs).some(
8058
8124
  (v) => v !== null && v !== void 0
8059
8125
  );
8060
8126
  if (!hasActiveAttr) {
@@ -8559,11 +8625,7 @@ var Typography = Extension.create({
8559
8625
  rules.push(
8560
8626
  new InputRule(/"([^"]+)"$/, (state, match, start, end) => {
8561
8627
  const text = match[1] ?? "";
8562
- return state.tr.replaceWith(
8563
- start,
8564
- end,
8565
- state.schema.text(openDoubleQuote + text + closeDoubleQuote)
8566
- );
8628
+ return state.tr.insertText(openDoubleQuote + text + closeDoubleQuote, start, end);
8567
8629
  })
8568
8630
  );
8569
8631
  rules.push(
@@ -8571,12 +8633,10 @@ var Typography = Extension.create({
8571
8633
  const text = match[1] ?? "";
8572
8634
  const prefix = match[0].charAt(0);
8573
8635
  const hasPrefix = prefix !== "'";
8574
- return state.tr.replaceWith(
8636
+ return state.tr.insertText(
8637
+ hasPrefix ? prefix + openSingleQuote + text + closeSingleQuote : openSingleQuote + text + closeSingleQuote,
8575
8638
  start,
8576
- end,
8577
- state.schema.text(
8578
- hasPrefix ? prefix + openSingleQuote + text + closeSingleQuote : openSingleQuote + text + closeSingleQuote
8579
- )
8639
+ end
8580
8640
  );
8581
8641
  })
8582
8642
  );
@@ -8841,6 +8901,9 @@ function generateUUID() {
8841
8901
  });
8842
8902
  }
8843
8903
  var uniqueIDPluginKey = new PluginKey("uniqueID");
8904
+ function isWithin(from, to, ranges) {
8905
+ return ranges.some((range) => from >= range.from && to <= range.to);
8906
+ }
8844
8907
  var UniqueID = Extension.create({
8845
8908
  name: "uniqueID",
8846
8909
  addOptions() {
@@ -8890,12 +8953,27 @@ var UniqueID = Extension.create({
8890
8953
  addProseMirrorPlugins() {
8891
8954
  const { types, attributeName, generateID, filterDuplicates } = this.options;
8892
8955
  const editor = this.editor;
8956
+ const replacedRanges = (view) => {
8957
+ if (view?.dragging) return [];
8958
+ const ranges = view?.state?.selection?.ranges;
8959
+ if (ranges === void 0) return [];
8960
+ const replaced = [];
8961
+ for (const range of ranges) {
8962
+ if (range.$to.pos > range.$from.pos) {
8963
+ replaced.push({ from: range.$from.pos, to: range.$to.pos });
8964
+ }
8965
+ }
8966
+ return replaced;
8967
+ };
8893
8968
  const transformPastedSlice = (slice, view) => {
8894
8969
  if (view?.dragging?.move === true) return slice;
8895
8970
  const existingIDs = /* @__PURE__ */ new Set();
8896
- editor?.state.doc.descendants((node) => {
8971
+ const replaced = replacedRanges(view);
8972
+ editor?.state.doc.descendants((node, pos) => {
8973
+ if (isWithin(pos, pos + node.nodeSize, replaced)) return false;
8897
8974
  const id = node.attrs[attributeName];
8898
8975
  if (id) existingIDs.add(id);
8976
+ return true;
8899
8977
  });
8900
8978
  const transformNode = (node) => {
8901
8979
  if (!types.includes(node.type.name)) {
@@ -9745,13 +9823,13 @@ var Highlight = Extension.create({
9745
9823
  let hasHighlight = false;
9746
9824
  if (empty) {
9747
9825
  const marks = state.storedMarks ?? state.doc.resolve(from).marks();
9748
- const mark = markType.isInSet(marks);
9749
- hasHighlight = !!mark?.attrs["backgroundColor"] || !!mark?.attrs["backgroundColorToken"];
9826
+ const mark2 = markType.isInSet(marks);
9827
+ hasHighlight = !!mark2?.attrs["backgroundColor"] || !!mark2?.attrs["backgroundColorToken"];
9750
9828
  } else {
9751
9829
  state.doc.nodesBetween(from, to, (node) => {
9752
9830
  if (hasHighlight) return false;
9753
- const mark = markType.isInSet(node.marks);
9754
- if (mark?.attrs["backgroundColor"] || mark?.attrs["backgroundColorToken"]) {
9831
+ const mark2 = markType.isInSet(node.marks);
9832
+ if (mark2?.attrs["backgroundColor"] || mark2?.attrs["backgroundColorToken"]) {
9755
9833
  hasHighlight = true;
9756
9834
  return false;
9757
9835
  }
@@ -9798,7 +9876,8 @@ var Highlight = Extension.create({
9798
9876
  const content = match[1];
9799
9877
  if (!content) return null;
9800
9878
  const { tr } = state;
9801
- tr.replaceWith(start, end, state.schema.text(content));
9879
+ const marks = tr.storedMarks ?? tr.doc.resolve(start).marksAcross(tr.doc.resolve(end));
9880
+ tr.replaceWith(start, end, state.schema.text(content, marks));
9802
9881
  tr.addMark(
9803
9882
  start,
9804
9883
  start + content.length,
@@ -10094,6 +10173,153 @@ var ClearFormatting = Extension.create({
10094
10173
  ];
10095
10174
  }
10096
10175
  });
10176
+
10177
+ // src/extensions/Print.ts
10178
+ var ROOT_CLASS = "dm-print-root";
10179
+ var ANCESTOR_CLASS = "dm-print-ancestor";
10180
+ var PRINTING_CLASS = "dm-printing";
10181
+ var marked = /* @__PURE__ */ new Set();
10182
+ var printing = false;
10183
+ var Print = Extension.create({
10184
+ name: "print",
10185
+ addOptions() {
10186
+ return {
10187
+ toolbar: true,
10188
+ root: null,
10189
+ isolateNativePrint: false
10190
+ };
10191
+ },
10192
+ addStorage() {
10193
+ return { cleanup: null };
10194
+ },
10195
+ addCommands() {
10196
+ return {
10197
+ printDocument: () => ({ dispatch }) => {
10198
+ if (!dispatch) return true;
10199
+ const editor = this.editor;
10200
+ if (!editor || typeof window === "undefined") return false;
10201
+ const root = resolveRoot(editor, this.options.root);
10202
+ if (!root) return false;
10203
+ mark(root);
10204
+ try {
10205
+ emit(editor, "beforePrint", { root });
10206
+ window.print();
10207
+ } finally {
10208
+ unmark();
10209
+ emit(editor, "afterPrint", void 0);
10210
+ }
10211
+ return true;
10212
+ }
10213
+ };
10214
+ },
10215
+ addToolbarItems() {
10216
+ if (!this.options.toolbar) return [];
10217
+ return [
10218
+ {
10219
+ type: "button",
10220
+ name: "print",
10221
+ command: "printDocument",
10222
+ icon: "printer",
10223
+ label: "Print",
10224
+ shortcut: "Mod-P",
10225
+ group: "document",
10226
+ priority: 100,
10227
+ // Reading a document out to paper is not editing it, so the button
10228
+ // stays live in a read-only editor.
10229
+ allowReadOnly: true
10230
+ }
10231
+ ];
10232
+ },
10233
+ addKeyboardShortcuts() {
10234
+ return {
10235
+ // Only bound while the caret is in the editor, which is exactly when
10236
+ // the reader means "print this document" rather than "print this
10237
+ // page". Everywhere else the browser's own Ctrl/Cmd+P is untouched.
10238
+ "Mod-p": () => this.editor?.commands.printDocument() ?? false
10239
+ };
10240
+ },
10241
+ onCreate() {
10242
+ if (!this.options.isolateNativePrint) return;
10243
+ if (typeof window === "undefined") return;
10244
+ const editor = this.editor;
10245
+ if (!editor) return;
10246
+ const resolve = this.options.root;
10247
+ const before = () => {
10248
+ if (printing) return;
10249
+ const root = resolveRoot(editor, resolve);
10250
+ if (!root) return;
10251
+ mark(root);
10252
+ emit(editor, "beforePrint", { root });
10253
+ };
10254
+ const after = () => {
10255
+ if (!printing) return;
10256
+ unmark();
10257
+ emit(editor, "afterPrint", void 0);
10258
+ };
10259
+ window.addEventListener("beforeprint", before);
10260
+ window.addEventListener("afterprint", after);
10261
+ let detachMedia = null;
10262
+ const onMediaChange = (event) => {
10263
+ if (event.matches) before();
10264
+ else after();
10265
+ };
10266
+ if (typeof window.matchMedia === "function") {
10267
+ const media = window.matchMedia("print");
10268
+ if (typeof media.addEventListener === "function") {
10269
+ media.addEventListener("change", onMediaChange);
10270
+ detachMedia = () => {
10271
+ media.removeEventListener("change", onMediaChange);
10272
+ };
10273
+ }
10274
+ }
10275
+ this.storage.cleanup = () => {
10276
+ window.removeEventListener("beforeprint", before);
10277
+ window.removeEventListener("afterprint", after);
10278
+ detachMedia?.();
10279
+ };
10280
+ },
10281
+ onDestroy() {
10282
+ this.storage.cleanup?.();
10283
+ this.storage.cleanup = null;
10284
+ unmark();
10285
+ }
10286
+ });
10287
+ function resolveRoot(editor, resolve) {
10288
+ if (resolve) return resolve(editor);
10289
+ const dom = editor.view.dom;
10290
+ return dom.closest(".dm-editor") ?? dom;
10291
+ }
10292
+ function mark(root) {
10293
+ root.classList.add(ROOT_CLASS);
10294
+ marked.add(root);
10295
+ let node = parentOf(root);
10296
+ while (node) {
10297
+ node.classList.add(ANCESTOR_CLASS);
10298
+ marked.add(node);
10299
+ node = parentOf(node);
10300
+ }
10301
+ document.body.classList.add(PRINTING_CLASS);
10302
+ printing = true;
10303
+ }
10304
+ function parentOf(node) {
10305
+ if (node.parentElement) return node.parentElement;
10306
+ if (typeof ShadowRoot === "undefined") return null;
10307
+ const root = node.getRootNode();
10308
+ return root instanceof ShadowRoot ? root.host : null;
10309
+ }
10310
+ function unmark() {
10311
+ printing = false;
10312
+ if (typeof document === "undefined") return;
10313
+ document.body.classList.remove(PRINTING_CLASS);
10314
+ for (const el of marked) {
10315
+ el.classList.remove(ROOT_CLASS, ANCESTOR_CLASS);
10316
+ }
10317
+ marked.clear();
10318
+ }
10319
+ function emit(editor, name, payload) {
10320
+ const bus = editor;
10321
+ bus.emit?.(name, payload);
10322
+ }
10097
10323
  var linkPopoverPluginKey = new PluginKey("linkPopover");
10098
10324
  function linkPopoverPlugin({ editor, markType, protocols }) {
10099
10325
  const el = document.createElement("div");
@@ -10431,9 +10657,10 @@ function createBubbleMenuPlugin(options) {
10431
10657
  const onDocumentMousedown = (e) => {
10432
10658
  const target = e.target;
10433
10659
  if (!target) return;
10660
+ if (!target.isConnected) return;
10434
10661
  if (element.contains(target)) return;
10435
10662
  if (editor.view.dom.contains(target)) return;
10436
- if (target instanceof HTMLElement && target.closest("[data-dm-editor-ui]")) return;
10663
+ if (target instanceof Element && target.closest("[data-dm-editor-ui]")) return;
10437
10664
  hideMenu();
10438
10665
  suppressed = true;
10439
10666
  };
@@ -10667,8 +10894,8 @@ var StarterKit = Extension.create({
10667
10894
  });
10668
10895
 
10669
10896
  // src/index.ts
10670
- var VERSION = "0.14.0";
10897
+ var VERSION = "0.15.0";
10671
10898
 
10672
- export { BaseKeymap, BlockColor, Blockquote, Bold, BubbleMenu, BulletList, CanChecker, ChainBuilder, CharacterCount, ClearFormatting, Code, CodeBlock, CommandManager, DEFAULT_BLOCK_COLORS, DEFAULT_BLOCK_COLOR_TYPES, DEFAULT_HIGHLIGHT_COLORS, DEFAULT_NOTION_COLOR_PALETTE, DEFAULT_TEXT_COLORS, Document, Dropcursor, Editor, EventEmitter, Extension, ExtensionConfigurationError, ExtensionManager, FLOATING_MENU_META, FLOATING_MENU_NO_FOCUS, FloatingMenuController, Focus, FontFamily, FontSize, Gapcursor, HardBreak, Heading, Highlight, History, HorizontalRule, InvisibleChars, Italic, LIST_ITEM_TYPE_NAMES, LineHeight, Link, LinkPopover, ListIndent, ListItem, ListKeymap, Mark, Node2 as Node, NotionColorPicker, OrderedList, Paragraph, Placeholder, Selection5 as Selection, SelectionDecoration, StarterKit, Strike, Subscript, Superscript, TaskItem, TaskList, Text, TextAlign, TextColor, TextStyle, ToolbarController, TrailingNode, Typography, Underline, UniqueID, VERSION, announce, applyInlineStyles, autolinkPlugin, autolinkPluginKey, blur, bubbleMenuPluginKey, buildCommandProps, builtInCommands, callOrReturn, characterCountPluginKey, clearContent, copyThemeClass, createAccumulatingDispatch, createBubbleMenuPlugin, createCanChecker, createChainBuilder, createDocument, createFloatingMenuPlugin, defaultBlockAt, defaultBubbleContexts, defaultFloatingMenuShouldShow, defaultIcons, deleteSelection, findChildren, findListItemAncestorDepth, findParentNode, floatingMenuPluginKey, focus, focusPluginKey, generateHTML, generateJSON, generateText, getListItemCursorContext, getMarkRange, groupFloatingMenuItems, hideFloatingMenu, indentBlockAsListChild, inlineStyles, insertAsListItemChild, insertChildrenZoneSibling, insertContent, insertText, invisibleCharsPluginKey, isDocumentEmpty, isInListItemLabel, isInsideListItem, isNodeEmpty, isValidUrl, lift, liftCurrentListItem, liftEmptyChildrenZoneParagraph, linkClickPlugin, linkClickPluginKey, linkExitPlugin, linkExitPluginKey, linkPastePlugin, linkPastePluginKey, markInputRule, markInputRulePatterns, nodeInputRule, outdentBlockFromListItem, placeholderPluginKey, positionFloating, positionFloatingOnce, refocusEditorAfterCommand, resetAttributes, selectAll, selectNodeBackward, selectionDecorationPluginKey, setBlockType, setContent, setMark, showFloatingMenu, splitListForInsert, stripInlineColorConflicts, textInputRule, textblockTypeInputRule, toggleBlockType, toggleList, toggleMark, toggleWrap, uniqueIDPluginKey, unsetAllMarks, unsetMark, updateAttributes, wrapIn, wrappingInputRule, writeToClipboard };
10899
+ export { BaseKeymap, BlockColor, Blockquote, Bold, BubbleMenu, BulletList, CanChecker, ChainBuilder, CharacterCount, ClearFormatting, Code, CodeBlock, CommandManager, DEFAULT_BLOCK_COLORS, DEFAULT_BLOCK_COLOR_TYPES, DEFAULT_HIGHLIGHT_COLORS, DEFAULT_NOTION_COLOR_PALETTE, DEFAULT_TEXT_COLORS, Document, Dropcursor, Editor, EventEmitter, Extension, ExtensionConfigurationError, ExtensionManager, FLOATING_MENU_META, FLOATING_MENU_NO_FOCUS, FloatingMenuController, Focus, FontFamily, FontSize, Gapcursor, HardBreak, Heading, Highlight, History, HorizontalRule, InvisibleChars, Italic, LIST_ITEM_TYPE_NAMES, LineHeight, Link, LinkPopover, ListIndent, ListItem, ListKeymap, Mark, Node2 as Node, NotionColorPicker, OrderedList, Paragraph, Placeholder, Print, Selection5 as Selection, SelectionDecoration, StarterKit, Strike, Subscript, Superscript, TaskItem, TaskList, Text, TextAlign, TextColor, TextStyle, ToolbarController, TrailingNode, Typography, Underline, UniqueID, VERSION, announce, applyInlineStyles, autolinkPlugin, autolinkPluginKey, blur, bubbleMenuPluginKey, buildCommandProps, builtInCommands, callOrReturn, characterCountPluginKey, clearContent, copyThemeClass, createAccumulatingDispatch, createBubbleMenuPlugin, createCanChecker, createChainBuilder, createDocument, createFloatingMenuPlugin, defaultBlockAt, defaultBubbleContexts, defaultFloatingMenuShouldShow, defaultIcons, deleteSelection, findChildren, findListItemAncestorDepth, findParentNode, floatingMenuPluginKey, focus, focusPluginKey, generateHTML, generateJSON, generateText, getListItemCursorContext, getMarkRange, groupFloatingMenuItems, hideFloatingMenu, indentBlockAsListChild, inlineStyles, insertAsListItemChild, insertChildrenZoneSibling, insertContent, insertText, invisibleCharsPluginKey, isDocumentEmpty, isInListItemLabel, isInsideListItem, isNodeEmpty, isValidUrl, lift, liftCurrentListItem, liftEmptyChildrenZoneParagraph, linkClickPlugin, linkClickPluginKey, linkExitPlugin, linkExitPluginKey, linkPastePlugin, linkPastePluginKey, markInputRule, markInputRulePatterns, nodeInputRule, outdentBlockFromListItem, placeholderPluginKey, positionFloating, positionFloatingOnce, refocusEditorAfterCommand, resetAttributes, selectAll, selectNodeBackward, selectionDecorationPluginKey, setBlockType, setContent, setMark, showFloatingMenu, splitListForInsert, stripInlineColorConflicts, textInputRule, textblockTypeInputRule, toggleBlockType, toggleList, toggleMark, toggleWrap, uniqueIDPluginKey, unsetAllMarks, unsetMark, updateAttributes, wrapIn, wrappingInputRule, writeToClipboard };
10673
10900
  //# sourceMappingURL=index.js.map
10674
10901
  //# sourceMappingURL=index.js.map