@domternal/core 0.9.0 → 0.9.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/dist/index.cjs CHANGED
@@ -1018,6 +1018,27 @@ var deleteSelection = () => ({ tr, dispatch }) => {
1018
1018
  dispatch(tr);
1019
1019
  return true;
1020
1020
  };
1021
+ var LIST_WRAPPER_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
1022
+ function isEmptyPlaceholderList(node) {
1023
+ if (!LIST_WRAPPER_TYPES.has(node.type.name) || node.childCount !== 1) return false;
1024
+ const item = node.child(0);
1025
+ return item.childCount === 1 && item.child(0).type.name === "paragraph" && item.child(0).content.size === 0;
1026
+ }
1027
+ function stripFitterArtifacts(node) {
1028
+ if (!node.isBlock || node.isLeaf) return node;
1029
+ const children = [];
1030
+ node.forEach((child) => children.push(stripFitterArtifacts(child)));
1031
+ const kept = [];
1032
+ children.forEach((cur, i) => {
1033
+ const next = children[i + 1];
1034
+ if (isEmptyPlaceholderList(cur) && next && LIST_WRAPPER_TYPES.has(next.type.name) && next.type !== cur.type) {
1035
+ return;
1036
+ }
1037
+ kept.push(cur);
1038
+ });
1039
+ const unchanged = kept.length === node.childCount && kept.every((c, i) => c === node.child(i));
1040
+ return unchanged ? node : node.copy(model.Fragment.fromArray(kept));
1041
+ }
1021
1042
  function isJSONContent(content) {
1022
1043
  return typeof content === "object" && content !== null && "type" in content && typeof content.type === "string";
1023
1044
  }
@@ -1040,7 +1061,7 @@ function parseHTMLContent(html, schema, options) {
1040
1061
  const element = document.createElement("div");
1041
1062
  element.innerHTML = html;
1042
1063
  const parser = model.DOMParser.fromSchema(schema);
1043
- return parser.parse(element, options?.parseOptions);
1064
+ return stripFitterArtifacts(parser.parse(element, options?.parseOptions));
1044
1065
  }
1045
1066
  function createDocument(content, schema, options) {
1046
1067
  if (content === null || content === void 0 || content === "") {
@@ -4143,12 +4164,12 @@ function defaultBubbleContexts(editor) {
4143
4164
 
4144
4165
  // src/utils/insertAsListItemChild.ts
4145
4166
  var LIST_ITEM_TYPES3 = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
4146
- var LIST_WRAPPER_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
4167
+ var LIST_WRAPPER_TYPES2 = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
4147
4168
  function insertAsListItemChild(args) {
4148
4169
  const { tr, wrapperPos, targetItemPos, blockNode, sourceRange, childIndex } = args;
4149
4170
  if (wrapperPos < 0 || wrapperPos >= tr.doc.content.size) return { ok: false };
4150
4171
  const wrapper = tr.doc.nodeAt(wrapperPos);
4151
- if (!wrapper || !LIST_WRAPPER_TYPES.has(wrapper.type.name)) return { ok: false };
4172
+ if (!wrapper || !LIST_WRAPPER_TYPES2.has(wrapper.type.name)) return { ok: false };
4152
4173
  if (wrapper.childCount === 0) return { ok: false };
4153
4174
  let targetItem;
4154
4175
  let targetItemStart;
@@ -5929,6 +5950,50 @@ var CodeBlock = Node2.create({
5929
5950
  ];
5930
5951
  }
5931
5952
  });
5953
+ var LIST_ITEM_TYPES4 = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
5954
+ function liftCrossTypeListItem(state$1, dispatch) {
5955
+ const { $from } = state$1.selection;
5956
+ if (!state$1.selection.empty) return false;
5957
+ let itemDepth = -1;
5958
+ for (let d = $from.depth; d > 0; d--) {
5959
+ if (LIST_ITEM_TYPES4.has($from.node(d).type.name)) {
5960
+ itemDepth = d;
5961
+ break;
5962
+ }
5963
+ }
5964
+ if (itemDepth < 4) return false;
5965
+ const item = $from.node(itemDepth);
5966
+ const wrapper = $from.node(itemDepth - 1);
5967
+ const parentItem = $from.node(itemDepth - 2);
5968
+ if (!LIST_ITEM_TYPES4.has(parentItem.type.name)) return false;
5969
+ if (parentItem.type === item.type) return false;
5970
+ const parentWrapper = $from.node(itemDepth - 3);
5971
+ const grandParent = $from.node(itemDepth - 4);
5972
+ const gpIndex = $from.index(itemDepth - 4);
5973
+ if (!grandParent.canReplaceWith(gpIndex + 1, gpIndex + 1, wrapper.type)) return false;
5974
+ if (!dispatch) return true;
5975
+ const onlyChild = wrapper.childCount === 1;
5976
+ const removeFrom = onlyChild ? $from.before(itemDepth - 1) : $from.before(itemDepth);
5977
+ const removeTo = onlyChild ? $from.after(itemDepth - 1) : $from.after(itemDepth);
5978
+ const parentIsLast = $from.index(itemDepth - 3) === parentWrapper.childCount - 1;
5979
+ const tr = state$1.tr;
5980
+ tr.delete(removeFrom, removeTo);
5981
+ const newList = wrapper.type.create(wrapper.attrs, item);
5982
+ if (parentIsLast) {
5983
+ const insertAt = tr.mapping.map($from.after(itemDepth - 3));
5984
+ tr.insert(insertAt, newList);
5985
+ tr.setSelection(state.Selection.near(tr.doc.resolve(insertAt + 2)));
5986
+ } else {
5987
+ const splitAt = tr.mapping.map($from.after(itemDepth - 2));
5988
+ tr.split(splitAt, 1);
5989
+ tr.insert(splitAt + 1, newList);
5990
+ tr.setSelection(state.Selection.near(tr.doc.resolve(splitAt + 2)));
5991
+ }
5992
+ dispatch(tr.scrollIntoView());
5993
+ return true;
5994
+ }
5995
+
5996
+ // src/extensions/ListKeymap.ts
5932
5997
  var LIST_GROUP_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
5933
5998
  function getListItemContext(editor, listItemName) {
5934
5999
  const { state, view } = editor;
@@ -5970,6 +6035,7 @@ var ListKeymap = Extension.create({
5970
6035
  if (!this.editor) return false;
5971
6036
  const ctx = getListItemContext(this.editor, this.options.listItem);
5972
6037
  if (!ctx) return false;
6038
+ if (liftCrossTypeListItem(ctx.state, ctx.view.dispatch)) return true;
5973
6039
  return schemaList.liftListItem(ctx.listItemType)(ctx.state, ctx.view.dispatch);
5974
6040
  },
5975
6041
  // Backspace at start of list item to lift
@@ -6023,6 +6089,7 @@ var ListKeymap = Extension.create({
6023
6089
  if (firstChild?.isTextblock) {
6024
6090
  const posInListItem = $from.pos - $from.start(listItemDepth);
6025
6091
  if (posInListItem <= 1) {
6092
+ if (liftCrossTypeListItem(state$1, view.dispatch)) return true;
6026
6093
  return schemaList.liftListItem(listItemType)(state$1, view.dispatch);
6027
6094
  }
6028
6095
  }
@@ -6069,7 +6136,22 @@ var ListItem = Node2.create({
6069
6136
  if (commands.splitBlock(state$1, view.dispatch)) return true;
6070
6137
  }
6071
6138
  }
6072
- if (schemaList.splitListItem(this.nodeType)(state$1, view.dispatch)) return true;
6139
+ const item = $from.node(-1);
6140
+ const hasChildren = item.childCount > 1;
6141
+ const atEnd = $from.parentOffset === $from.parent.content.size;
6142
+ const labelEmpty = $from.parent.content.size === 0;
6143
+ if (!ctx?.isInChildrenZone && hasChildren && atEnd && !labelEmpty) {
6144
+ const newItem = this.nodeType.createAndFill();
6145
+ if (newItem) {
6146
+ const insertAt = $from.after($from.depth - 1);
6147
+ const tr = state$1.tr.insert(insertAt, newItem);
6148
+ tr.setSelection(state.Selection.near(tr.doc.resolve(insertAt + 2)));
6149
+ view.dispatch(tr.scrollIntoView());
6150
+ return true;
6151
+ }
6152
+ }
6153
+ const skipSplit = !ctx?.isInChildrenZone && labelEmpty && hasChildren;
6154
+ if (!skipSplit && schemaList.splitListItem(this.nodeType)(state$1, view.dispatch)) return true;
6073
6155
  const listDepth = $from.depth - 2;
6074
6156
  const taskItemType = state$1.schema.nodes["taskItem"];
6075
6157
  if ($from.parent.content.size === 0 && listDepth > 0 && taskItemType && $from.node(listDepth - 1).type === taskItemType) {
@@ -6078,9 +6160,9 @@ var ListItem = Node2.create({
6078
6160
  tr.delete($from.before(delDepth), $from.after(delDepth));
6079
6161
  const taskItemDepth = listDepth - 1;
6080
6162
  const end = tr.mapping.map($from.after(taskItemDepth));
6081
- const item = taskItemType.createAndFill();
6082
- if (item) {
6083
- tr.insert(end, item);
6163
+ const item2 = taskItemType.createAndFill();
6164
+ if (item2) {
6165
+ tr.insert(end, item2);
6084
6166
  tr.setSelection(state.Selection.near(tr.doc.resolve(end + 2)));
6085
6167
  view.dispatch(tr.scrollIntoView());
6086
6168
  return true;
@@ -6209,16 +6291,20 @@ var OrderedList = Node2.create({
6209
6291
  return {
6210
6292
  start: {
6211
6293
  default: 1,
6294
+ // Clamp to a finite integer >= 1. Without this, a malformed
6295
+ // `start="abc"` parses to NaN; `JSON.stringify(NaN)` is `null`, so
6296
+ // getJSON() serializes `start: null` and a save/load cycle
6297
+ // permanently dirties the document (reloads as `<ol start="null">`).
6212
6298
  parseHTML: (element) => {
6213
- const start = element.getAttribute("start");
6214
- return start ? parseInt(start, 10) : 1;
6299
+ const n = parseInt(element.getAttribute("start") ?? "", 10);
6300
+ return Number.isFinite(n) && n >= 1 ? Math.floor(n) : 1;
6215
6301
  },
6216
6302
  renderHTML: (attributes) => {
6217
6303
  const start = attributes["start"];
6218
- if (start === 1) {
6304
+ if (!Number.isFinite(start) || start <= 1) {
6219
6305
  return {};
6220
6306
  }
6221
- return { start: String(start) };
6307
+ return { start: String(Math.floor(start)) };
6222
6308
  }
6223
6309
  }
6224
6310
  };
@@ -6297,8 +6383,8 @@ var OrderedList = Node2.create({
6297
6383
  guard: notInsideList,
6298
6384
  joinForward: true,
6299
6385
  getAttributes: (match) => {
6300
- const num = match[1];
6301
- return { start: num ? parseInt(num, 10) : 1 };
6386
+ const n = parseInt(match[1] ?? "", 10);
6387
+ return { start: Number.isFinite(n) && n >= 1 ? Math.floor(n) : 1 };
6302
6388
  }
6303
6389
  })
6304
6390
  ];
@@ -6570,7 +6656,10 @@ var TaskItem = Node2.create({
6570
6656
  keepOnSplit: false,
6571
6657
  parseHTML: (element) => {
6572
6658
  const dataChecked = element.getAttribute("data-checked");
6573
- return dataChecked === "true" || dataChecked === "";
6659
+ if (dataChecked !== null) {
6660
+ return dataChecked.toLowerCase() === "true" || dataChecked === "";
6661
+ }
6662
+ return element.querySelector('input[type="checkbox"]')?.hasAttribute("checked") ?? false;
6574
6663
  },
6575
6664
  renderHTML: (attributes) => ({
6576
6665
  "data-checked": attributes["checked"] ? "true" : "false"
@@ -6583,6 +6672,14 @@ var TaskItem = Node2.create({
6583
6672
  {
6584
6673
  tag: `li[data-type="${this.name}"]`,
6585
6674
  priority: 51
6675
+ },
6676
+ // GFM / markdown task lists: `<li class="task-list-item">` carries no
6677
+ // data-type. Priority 51 keeps it ahead of the generic `li` (listItem,
6678
+ // 50); class-scoped so it never swallows ordinary bullet items. The
6679
+ // `checked` attribute is derived from the descendant `<input>` above.
6680
+ {
6681
+ tag: "li.task-list-item",
6682
+ priority: 51
6586
6683
  }
6587
6684
  ];
6588
6685
  },
@@ -6668,7 +6765,22 @@ var TaskItem = Node2.create({
6668
6765
  return true;
6669
6766
  }
6670
6767
  }
6671
- if (schemaList.splitListItem(this.nodeType, { checked: false })(state$1, view.dispatch)) return true;
6768
+ const item = $from.node(-1);
6769
+ const hasChildren = item.childCount > 1;
6770
+ const labelEmpty = $from.parent.content.size === 0;
6771
+ const atEnd = $from.parentOffset === $from.parent.content.size;
6772
+ if (!ctx?.isInChildrenZone && hasChildren && atEnd && !labelEmpty) {
6773
+ const newItem = this.nodeType.createAndFill({ checked: false });
6774
+ if (newItem) {
6775
+ const insertAt = $from.after($from.depth - 1);
6776
+ const tr = state$1.tr.insert(insertAt, newItem);
6777
+ tr.setSelection(state.Selection.near(tr.doc.resolve(insertAt + 2)));
6778
+ view.dispatch(tr.scrollIntoView());
6779
+ return true;
6780
+ }
6781
+ }
6782
+ const skipSplit = !ctx?.isInChildrenZone && labelEmpty && hasChildren;
6783
+ if (!skipSplit && schemaList.splitListItem(this.nodeType, { checked: false })(state$1, view.dispatch)) return true;
6672
6784
  if ($from.parent.content.size === 0) {
6673
6785
  const listItemType = state$1.schema.nodes["listItem"];
6674
6786
  if (listItemType) {
@@ -6715,9 +6827,11 @@ var TaskItem = Node2.create({
6715
6827
  },
6716
6828
  "Shift-Tab": () => {
6717
6829
  if (!this.editor || !this.nodeType) return false;
6718
- const { $from } = this.editor.state.selection;
6830
+ const { state, view } = this.editor;
6831
+ const { $from } = state.selection;
6719
6832
  if ($from.depth < 1 || $from.node(-1).type !== this.nodeType) return false;
6720
- return schemaList.liftListItem(this.nodeType)(this.editor.state, this.editor.view.dispatch);
6833
+ if (liftCrossTypeListItem(state, view.dispatch)) return true;
6834
+ return schemaList.liftListItem(this.nodeType)(state, view.dispatch);
6721
6835
  },
6722
6836
  Backspace: () => {
6723
6837
  if (!this.editor || !this.nodeType) return false;
@@ -6739,6 +6853,7 @@ var TaskItem = Node2.create({
6739
6853
  }
6740
6854
  if (taskItemDepth === -1) return false;
6741
6855
  if ($from.index(taskItemDepth) !== 0) return false;
6856
+ if (liftCrossTypeListItem(state, view.dispatch)) return true;
6742
6857
  return schemaList.liftListItem(this.nodeType)(state, view.dispatch);
6743
6858
  },
6744
6859
  "Mod-Enter": () => {
@@ -6765,6 +6880,13 @@ var TaskList = Node2.create({
6765
6880
  tag: `ul[data-type="${this.name}"]`,
6766
6881
  priority: 51
6767
6882
  // Higher priority than regular bulletList
6883
+ },
6884
+ // GFM / markdown task lists: `<ul class="contains-task-list">`. Priority
6885
+ // 51 keeps it ahead of the generic `ul` (bulletList, 50); class-scoped so
6886
+ // it only matches real GFM task-list containers, not ordinary bullets.
6887
+ {
6888
+ tag: "ul.contains-task-list",
6889
+ priority: 51
6768
6890
  }
6769
6891
  ];
6770
6892
  },
@@ -8025,12 +8147,12 @@ var Placeholder = Extension.create({
8025
8147
  ];
8026
8148
  }
8027
8149
  });
8028
- var LIST_ITEM_TYPES4 = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
8029
- var LIST_WRAPPER_TYPES2 = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
8150
+ var LIST_ITEM_TYPES5 = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
8151
+ var LIST_WRAPPER_TYPES3 = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
8030
8152
  function isCursorInsideListItem(state) {
8031
8153
  const { $from } = state.selection;
8032
8154
  for (let d = $from.depth; d > 0; d--) {
8033
- if (LIST_ITEM_TYPES4.has($from.node(d).type.name)) return true;
8155
+ if (LIST_ITEM_TYPES5.has($from.node(d).type.name)) return true;
8034
8156
  }
8035
8157
  return false;
8036
8158
  }
@@ -8061,7 +8183,7 @@ function indentBlockAsListChild(state$1, dispatch) {
8061
8183
  }
8062
8184
  if (blockIndex === 0) return false;
8063
8185
  const prevSibling = state$1.doc.child(blockIndex - 1);
8064
- if (!LIST_WRAPPER_TYPES2.has(prevSibling.type.name)) return false;
8186
+ if (!LIST_WRAPPER_TYPES3.has(prevSibling.type.name)) return false;
8065
8187
  let wrapperPos = 0;
8066
8188
  for (let i = 0; i < blockIndex - 1; i++) {
8067
8189
  wrapperPos += state$1.doc.child(i).nodeSize;
@@ -8085,7 +8207,7 @@ function outdentBlockFromListItem(state$1, dispatch) {
8085
8207
  const { $from } = selection;
8086
8208
  let listItemDepth = -1;
8087
8209
  for (let d = $from.depth; d > 0; d--) {
8088
- if (LIST_ITEM_TYPES4.has($from.node(d).type.name)) {
8210
+ if (LIST_ITEM_TYPES5.has($from.node(d).type.name)) {
8089
8211
  listItemDepth = d;
8090
8212
  break;
8091
8213
  }
@@ -8865,7 +8987,7 @@ var BlockColor = Extension.create({
8865
8987
  };
8866
8988
  }
8867
8989
  });
8868
- var Selection4 = Extension.create({
8990
+ var Selection5 = Extension.create({
8869
8991
  name: "selection",
8870
8992
  addStorage() {
8871
8993
  return {
@@ -10419,7 +10541,7 @@ exports.NotionColorPicker = NotionColorPicker;
10419
10541
  exports.OrderedList = OrderedList;
10420
10542
  exports.Paragraph = Paragraph;
10421
10543
  exports.Placeholder = Placeholder;
10422
- exports.Selection = Selection4;
10544
+ exports.Selection = Selection5;
10423
10545
  exports.SelectionDecoration = SelectionDecoration;
10424
10546
  exports.StarterKit = StarterKit;
10425
10547
  exports.Strike = Strike;