@limetech/lime-elements 39.44.5 → 39.45.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/cjs/{keycodes-B75-uKOK.js → keycodes-k-EdzcMX.js} +4 -0
  3. package/dist/cjs/lime-elements.cjs.js +1 -1
  4. package/dist/cjs/limel-breadcrumbs_8.cjs.entry.js +20 -1
  5. package/dist/cjs/limel-chip-set.cjs.entry.js +1 -1
  6. package/dist/cjs/limel-chip_2.cjs.entry.js +1 -1
  7. package/dist/cjs/limel-file-viewer.cjs.entry.js +84 -8
  8. package/dist/cjs/limel-markdown.cjs.entry.js +1 -1
  9. package/dist/cjs/limel-picker.cjs.entry.js +1 -1
  10. package/dist/cjs/limel-popover_2.cjs.entry.js +1 -1
  11. package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +778 -395
  12. package/dist/cjs/limel-select.cjs.entry.js +377 -13
  13. package/dist/cjs/limel-text-editor-link-menu.cjs.entry.js +1 -1
  14. package/dist/cjs/loader.cjs.js +1 -1
  15. package/dist/cjs/{markdown-parser-ChnRAxpZ.js → markdown-parser-YH5HSSgF.js} +36 -56
  16. package/dist/collection/components/list/list.js +19 -0
  17. package/dist/collection/components/select/select.js +215 -12
  18. package/dist/collection/components/select/select.template.js +17 -3
  19. package/dist/collection/components/text-editor/prosemirror-adapter/editor-config.js +12 -1
  20. package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-command-utils/active-state-utils.js +36 -0
  21. package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-command-utils/list-utils.js +270 -0
  22. package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-command-utils/node-utils.js +15 -0
  23. package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-commands.js +81 -69
  24. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/list-key-handler.js +44 -0
  25. package/dist/collection/components/text-editor/prosemirror-adapter/prosemirror-adapter.js +4 -3
  26. package/dist/collection/util/keycodes.js +3 -0
  27. package/dist/collection/util/typeahead.js +159 -0
  28. package/dist/esm/{keycodes-rI0IeKpx.js → keycodes-C4_9qUMP.js} +4 -1
  29. package/dist/esm/lime-elements.js +1 -1
  30. package/dist/esm/limel-breadcrumbs_8.entry.js +20 -1
  31. package/dist/esm/limel-chip-set.entry.js +1 -1
  32. package/dist/esm/limel-chip_2.entry.js +1 -1
  33. package/dist/esm/limel-file-viewer.entry.js +84 -8
  34. package/dist/esm/limel-markdown.entry.js +1 -1
  35. package/dist/esm/limel-picker.entry.js +1 -1
  36. package/dist/esm/limel-popover_2.entry.js +1 -1
  37. package/dist/esm/limel-prosemirror-adapter.entry.js +779 -396
  38. package/dist/esm/limel-select.entry.js +378 -14
  39. package/dist/esm/limel-text-editor-link-menu.entry.js +1 -1
  40. package/dist/esm/loader.js +1 -1
  41. package/dist/esm/{markdown-parser-BSQKleVw.js → markdown-parser-DrxxGLF5.js} +36 -56
  42. package/dist/lime-elements/lime-elements.esm.js +1 -1
  43. package/dist/lime-elements/{p-a089b580.entry.js → p-03fd2181.entry.js} +1 -1
  44. package/dist/lime-elements/{p-fc43fb46.entry.js → p-12fe8fe1.entry.js} +6 -6
  45. package/dist/lime-elements/p-1f29f482.entry.js +1 -0
  46. package/dist/lime-elements/{p-bf3c78a8.entry.js → p-2b01c1f2.entry.js} +1 -1
  47. package/dist/lime-elements/{p-758939be.entry.js → p-2e5d5e79.entry.js} +1 -1
  48. package/dist/lime-elements/{p-3ea159fd.entry.js → p-38393fb4.entry.js} +1 -1
  49. package/dist/lime-elements/p-9152d7e1.entry.js +23 -0
  50. package/dist/lime-elements/{p-1e3cdfa1.entry.js → p-9a04a686.entry.js} +1 -1
  51. package/dist/lime-elements/p-C4_9qUMP.js +1 -0
  52. package/dist/lime-elements/{p-B0qhUema.js → p-C9xHOCE_.js} +2 -2
  53. package/dist/lime-elements/{p-6fbf20c6.entry.js → p-e8df7a2e.entry.js} +1 -1
  54. package/dist/lime-elements/p-f51b5651.entry.js +1 -0
  55. package/dist/types/components/select/select.d.ts +76 -1
  56. package/dist/types/components/select/select.template.d.ts +21 -1
  57. package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-command-utils/active-state-utils.d.ts +6 -0
  58. package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-command-utils/list-utils.d.ts +68 -0
  59. package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-command-utils/node-utils.d.ts +10 -0
  60. package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-commands.d.ts +10 -0
  61. package/dist/types/components/text-editor/prosemirror-adapter/plugins/list-key-handler.d.ts +14 -0
  62. package/dist/types/components.d.ts +36 -0
  63. package/dist/types/util/keycodes.d.ts +3 -0
  64. package/dist/types/util/typeahead.d.ts +101 -0
  65. package/package.json +3 -3
  66. package/dist/lime-elements/p-552d0733.entry.js +0 -1
  67. package/dist/lime-elements/p-845cfda7.entry.js +0 -1
  68. package/dist/lime-elements/p-8f4273e4.entry.js +0 -23
  69. package/dist/lime-elements/p-rI0IeKpx.js +0 -1
@@ -2,7 +2,7 @@
2
2
 
3
3
  var index$1 = require('./index-DYg_7kkT.js');
4
4
  var types = require('./types-C7gNcwzE.js');
5
- var markdownParser = require('./markdown-parser-ChnRAxpZ.js');
5
+ var markdownParser = require('./markdown-parser-YH5HSSgF.js');
6
6
  var cloneDeep = require('./cloneDeep-hE40Q8aw.js');
7
7
  var translations = require('./translations-Bnw00bkf.js');
8
8
  var index = require('./index-ETPz91V5.js');
@@ -13403,15 +13403,266 @@ Depending on the detected platform, this will hold
13403
13403
  */
13404
13404
  const baseKeymap = mac$3 ? macBaseKeymap : pcBaseKeymap;
13405
13405
 
13406
+ const olDOM = ["ol", 0], ulDOM = ["ul", 0], liDOM = ["li", 0];
13407
+ /**
13408
+ An ordered list [node spec](https://prosemirror.net/docs/ref/#model.NodeSpec). Has a single
13409
+ attribute, `order`, which determines the number at which the list
13410
+ starts counting, and defaults to 1. Represented as an `<ol>`
13411
+ element.
13412
+ */
13413
+ const orderedList = {
13414
+ attrs: { order: { default: 1 } },
13415
+ parseDOM: [{ tag: "ol", getAttrs(dom) {
13416
+ return { order: dom.hasAttribute("start") ? +dom.getAttribute("start") : 1 };
13417
+ } }],
13418
+ toDOM(node) {
13419
+ return node.attrs.order == 1 ? olDOM : ["ol", { start: node.attrs.order }, 0];
13420
+ }
13421
+ };
13422
+ /**
13423
+ A bullet list node spec, represented in the DOM as `<ul>`.
13424
+ */
13425
+ const bulletList = {
13426
+ parseDOM: [{ tag: "ul" }],
13427
+ toDOM() { return ulDOM; }
13428
+ };
13429
+ /**
13430
+ A list item (`<li>`) spec.
13431
+ */
13432
+ const listItem = {
13433
+ parseDOM: [{ tag: "li" }],
13434
+ toDOM() { return liDOM; },
13435
+ defining: true
13436
+ };
13437
+ function add(obj, props) {
13438
+ let copy = {};
13439
+ for (let prop in obj)
13440
+ copy[prop] = obj[prop];
13441
+ for (let prop in props)
13442
+ copy[prop] = props[prop];
13443
+ return copy;
13444
+ }
13445
+ /**
13446
+ Convenience function for adding list-related node types to a map
13447
+ specifying the nodes for a schema. Adds
13448
+ [`orderedList`](https://prosemirror.net/docs/ref/#schema-list.orderedList) as `"ordered_list"`,
13449
+ [`bulletList`](https://prosemirror.net/docs/ref/#schema-list.bulletList) as `"bullet_list"`, and
13450
+ [`listItem`](https://prosemirror.net/docs/ref/#schema-list.listItem) as `"list_item"`.
13451
+
13452
+ `itemContent` determines the content expression for the list items.
13453
+ If you want the commands defined in this module to apply to your
13454
+ list structure, it should have a shape like `"paragraph block*"` or
13455
+ `"paragraph (ordered_list | bullet_list)*"`. `listGroup` can be
13456
+ given to assign a group name to the list node types, for example
13457
+ `"block"`.
13458
+ */
13459
+ function addListNodes(nodes, itemContent, listGroup) {
13460
+ return nodes.append({
13461
+ ordered_list: add(orderedList, { content: "list_item+", group: listGroup }),
13462
+ bullet_list: add(bulletList, { content: "list_item+", group: listGroup }),
13463
+ list_item: add(listItem, { content: itemContent })
13464
+ });
13465
+ }
13466
+ /**
13467
+ Returns a command function that wraps the selection in a list with
13468
+ the given type an attributes. If `dispatch` is null, only return a
13469
+ value to indicate whether this is possible, but don't actually
13470
+ perform the change.
13471
+ */
13472
+ function wrapInList(listType, attrs = null) {
13473
+ return function (state, dispatch) {
13474
+ let { $from, $to } = state.selection;
13475
+ let range = $from.blockRange($to), doJoin = false, outerRange = range;
13476
+ if (!range)
13477
+ return false;
13478
+ // This is at the top of an existing list item
13479
+ if (range.depth >= 2 && $from.node(range.depth - 1).type.compatibleContent(listType) && range.startIndex == 0) {
13480
+ // Don't do anything if this is the top of the list
13481
+ if ($from.index(range.depth - 1) == 0)
13482
+ return false;
13483
+ let $insert = state.doc.resolve(range.start - 2);
13484
+ outerRange = new NodeRange($insert, $insert, range.depth);
13485
+ if (range.endIndex < range.parent.childCount)
13486
+ range = new NodeRange($from, state.doc.resolve($to.end(range.depth)), range.depth);
13487
+ doJoin = true;
13488
+ }
13489
+ let wrap = findWrapping(outerRange, listType, attrs, range);
13490
+ if (!wrap)
13491
+ return false;
13492
+ if (dispatch)
13493
+ dispatch(doWrapInList(state.tr, range, wrap, doJoin, listType).scrollIntoView());
13494
+ return true;
13495
+ };
13496
+ }
13497
+ function doWrapInList(tr, range, wrappers, joinBefore, listType) {
13498
+ let content = Fragment.empty;
13499
+ for (let i = wrappers.length - 1; i >= 0; i--)
13500
+ content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content));
13501
+ tr.step(new ReplaceAroundStep(range.start - (joinBefore ? 2 : 0), range.end, range.start, range.end, new Slice(content, 0, 0), wrappers.length, true));
13502
+ let found = 0;
13503
+ for (let i = 0; i < wrappers.length; i++)
13504
+ if (wrappers[i].type == listType)
13505
+ found = i + 1;
13506
+ let splitDepth = wrappers.length - found;
13507
+ let splitPos = range.start + wrappers.length - (joinBefore ? 2 : 0), parent = range.parent;
13508
+ for (let i = range.startIndex, e = range.endIndex, first = true; i < e; i++, first = false) {
13509
+ if (!first && canSplit(tr.doc, splitPos, splitDepth)) {
13510
+ tr.split(splitPos, splitDepth);
13511
+ splitPos += 2 * splitDepth;
13512
+ }
13513
+ splitPos += parent.child(i).nodeSize;
13514
+ }
13515
+ return tr;
13516
+ }
13517
+ /**
13518
+ Build a command that splits a non-empty textblock at the top level
13519
+ of a list item by also splitting that list item.
13520
+ */
13521
+ function splitListItem(itemType, itemAttrs) {
13522
+ return function (state, dispatch) {
13523
+ let { $from, $to, node } = state.selection;
13524
+ if ((node && node.isBlock) || $from.depth < 2 || !$from.sameParent($to))
13525
+ return false;
13526
+ let grandParent = $from.node(-1);
13527
+ if (grandParent.type != itemType)
13528
+ return false;
13529
+ if ($from.parent.content.size == 0 && $from.node(-1).childCount == $from.indexAfter(-1)) {
13530
+ // In an empty block. If this is a nested list, the wrapping
13531
+ // list item should be split. Otherwise, bail out and let next
13532
+ // command handle lifting.
13533
+ if ($from.depth == 3 || $from.node(-3).type != itemType ||
13534
+ $from.index(-2) != $from.node(-2).childCount - 1)
13535
+ return false;
13536
+ if (dispatch) {
13537
+ let wrap = Fragment.empty;
13538
+ let depthBefore = $from.index(-1) ? 1 : $from.index(-2) ? 2 : 3;
13539
+ // Build a fragment containing empty versions of the structure
13540
+ // from the outer list item to the parent node of the cursor
13541
+ for (let d = $from.depth - depthBefore; d >= $from.depth - 3; d--)
13542
+ wrap = Fragment.from($from.node(d).copy(wrap));
13543
+ let depthAfter = $from.indexAfter(-1) < $from.node(-2).childCount ? 1
13544
+ : $from.indexAfter(-2) < $from.node(-3).childCount ? 2 : 3;
13545
+ // Add a second list item with an empty default start node
13546
+ wrap = wrap.append(Fragment.from(itemType.createAndFill()));
13547
+ let start = $from.before($from.depth - (depthBefore - 1));
13548
+ let tr = state.tr.replace(start, $from.after(-depthAfter), new Slice(wrap, 4 - depthBefore, 0));
13549
+ let sel = -1;
13550
+ tr.doc.nodesBetween(start, tr.doc.content.size, (node, pos) => {
13551
+ if (sel > -1)
13552
+ return false;
13553
+ if (node.isTextblock && node.content.size == 0)
13554
+ sel = pos + 1;
13555
+ });
13556
+ if (sel > -1)
13557
+ tr.setSelection(Selection.near(tr.doc.resolve(sel)));
13558
+ dispatch(tr.scrollIntoView());
13559
+ }
13560
+ return true;
13561
+ }
13562
+ let nextType = $to.pos == $from.end() ? grandParent.contentMatchAt(0).defaultType : null;
13563
+ let tr = state.tr.delete($from.pos, $to.pos);
13564
+ let types = nextType ? [null, { type: nextType }] : undefined;
13565
+ if (!canSplit(tr.doc, $from.pos, 2, types))
13566
+ return false;
13567
+ if (dispatch)
13568
+ dispatch(tr.split($from.pos, 2, types).scrollIntoView());
13569
+ return true;
13570
+ };
13571
+ }
13572
+ /**
13573
+ Create a command to lift the list item around the selection up into
13574
+ a wrapping list.
13575
+ */
13576
+ function liftListItem(itemType) {
13577
+ return function (state, dispatch) {
13578
+ let { $from, $to } = state.selection;
13579
+ let range = $from.blockRange($to, node => node.childCount > 0 && node.firstChild.type == itemType);
13580
+ if (!range)
13581
+ return false;
13582
+ if (!dispatch)
13583
+ return true;
13584
+ if ($from.node(range.depth - 1).type == itemType) // Inside a parent list
13585
+ return liftToOuterList(state, dispatch, itemType, range);
13586
+ else // Outer list node
13587
+ return liftOutOfList(state, dispatch, range);
13588
+ };
13589
+ }
13590
+ function liftToOuterList(state, dispatch, itemType, range) {
13591
+ let tr = state.tr, end = range.end, endOfList = range.$to.end(range.depth);
13592
+ if (end < endOfList) {
13593
+ // There are siblings after the lifted items, which must become
13594
+ // children of the last item
13595
+ tr.step(new ReplaceAroundStep(end - 1, endOfList, end, endOfList, new Slice(Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true));
13596
+ range = new NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth);
13597
+ }
13598
+ const target = liftTarget(range);
13599
+ if (target == null)
13600
+ return false;
13601
+ tr.lift(range, target);
13602
+ let after = tr.mapping.map(end, -1) - 1;
13603
+ if (canJoin(tr.doc, after))
13604
+ tr.join(after);
13605
+ dispatch(tr.scrollIntoView());
13606
+ return true;
13607
+ }
13608
+ function liftOutOfList(state, dispatch, range) {
13609
+ let tr = state.tr, list = range.parent;
13610
+ // Merge the list items into a single big item
13611
+ for (let pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) {
13612
+ pos -= list.child(i).nodeSize;
13613
+ tr.delete(pos - 1, pos + 1);
13614
+ }
13615
+ let $start = tr.doc.resolve(range.start), item = $start.nodeAfter;
13616
+ if (tr.mapping.map(range.end) != range.start + $start.nodeAfter.nodeSize)
13617
+ return false;
13618
+ let atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount;
13619
+ let parent = $start.node(-1), indexBefore = $start.index(-1);
13620
+ if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1, item.content.append(atEnd ? Fragment.empty : Fragment.from(list))))
13621
+ return false;
13622
+ let start = $start.pos, end = start + item.nodeSize;
13623
+ // Strip off the surrounding list. At the sides where we're not at
13624
+ // the end of the list, the existing list is closed. At sides where
13625
+ // this is the end, it is overwritten to its end.
13626
+ tr.step(new ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1, new Slice((atStart ? Fragment.empty : Fragment.from(list.copy(Fragment.empty)))
13627
+ .append(atEnd ? Fragment.empty : Fragment.from(list.copy(Fragment.empty))), atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1));
13628
+ dispatch(tr.scrollIntoView());
13629
+ return true;
13630
+ }
13631
+ /**
13632
+ Create a command to sink the list item around the selection down
13633
+ into an inner list.
13634
+ */
13635
+ function sinkListItem(itemType) {
13636
+ return function (state, dispatch) {
13637
+ let { $from, $to } = state.selection;
13638
+ let range = $from.blockRange($to, node => node.childCount > 0 && node.firstChild.type == itemType);
13639
+ if (!range)
13640
+ return false;
13641
+ let startIndex = range.startIndex;
13642
+ if (startIndex == 0)
13643
+ return false;
13644
+ let parent = range.parent, nodeBefore = parent.child(startIndex - 1);
13645
+ if (nodeBefore.type != itemType)
13646
+ return false;
13647
+ if (dispatch) {
13648
+ let nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type;
13649
+ let inner = Fragment.from(nestedBefore ? itemType.create() : null);
13650
+ let slice = new Slice(Fragment.from(itemType.create(null, Fragment.from(parent.type.create(null, inner)))), nestedBefore ? 3 : 1, 0);
13651
+ let before = range.start, after = range.end;
13652
+ dispatch(state.tr.step(new ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after, before, after, slice, 1, true))
13653
+ .scrollIntoView());
13654
+ }
13655
+ return true;
13656
+ };
13657
+ }
13658
+
13406
13659
  const setActiveMethodForMark = (command, markType) => {
13407
13660
  command.active = (state) => {
13408
13661
  const { from, $from, to, empty } = state.selection;
13409
13662
  if (empty) {
13410
13663
  return !!markType.isInSet(state.storedMarks || $from.marks());
13411
13664
  }
13412
- else {
13413
- return state.doc.rangeHasMark(from, to, markType);
13414
- }
13665
+ return state.doc.rangeHasMark(from, to, markType);
13415
13666
  };
13416
13667
  };
13417
13668
  const setActiveMethodForNode = (command, nodeType, level) => {
@@ -13424,24 +13675,292 @@ const setActiveMethodForNode = (command, nodeType, level) => {
13424
13675
  }
13425
13676
  return true;
13426
13677
  }
13427
- return false;
13428
- };
13678
+ return false;
13679
+ };
13680
+ };
13681
+ const setActiveMethodForWrap = (command, nodeType) => {
13682
+ command.active = (state) => {
13683
+ const { from, to } = state.selection;
13684
+ let found = false;
13685
+ state.doc.nodesBetween(from, to, (node) => {
13686
+ if (node.type === nodeType) {
13687
+ found = true;
13688
+ }
13689
+ return !found;
13690
+ });
13691
+ return found;
13692
+ };
13693
+ };
13694
+
13695
+ var __rest = (undefined && undefined.__rest) || function (s, e) {
13696
+ var t = {};
13697
+ for (var p in s)
13698
+ if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
13699
+ t[p] = s[p];
13700
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
13701
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
13702
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
13703
+ t[p[i]] = s[p[i]];
13704
+ }
13705
+ return t;
13706
+ };
13707
+ /**
13708
+ * Whether the selection touches top-level blocks that cannot become list
13709
+ * items (list_item requires a leading paragraph, so headings, blockquotes,
13710
+ * code blocks and tables are not listable).
13711
+ *
13712
+ * @param state - the current editor state
13713
+ * @returns true when a non-listable block intersects the selection
13714
+ */
13715
+ const selectionHasNonListableBlock = (state) => {
13716
+ const { from, to } = state.selection;
13717
+ const { schema } = state;
13718
+ let found = false;
13719
+ state.doc.nodesBetween(from, to, (node, _pos, parent) => {
13720
+ if (found) {
13721
+ return false;
13722
+ }
13723
+ const isTopLevelBlock = (parent === null || parent === void 0 ? void 0 : parent.type) === schema.topNodeType;
13724
+ const listable = node.type === schema.nodes.paragraph ||
13725
+ node.type === schema.nodes.list_item ||
13726
+ isListNode(node, schema);
13727
+ if (isTopLevelBlock && !listable) {
13728
+ found = true;
13729
+ return false;
13730
+ }
13731
+ return !isTopLevelBlock || isListNode(node, schema);
13732
+ });
13733
+ return found;
13734
+ };
13735
+ /**
13736
+ * Classifies the selection for the list command ladder.
13737
+ *
13738
+ * @param state - the current editor state
13739
+ * @param listType - the list type the command targets
13740
+ * @returns the ladder branch to take, plus the innermost list depth when
13741
+ * the selection sits inside a list
13742
+ */
13743
+ const resolveListContext = (state, listType) => {
13744
+ const { $from, $to } = state.selection;
13745
+ const fromDepth = innermostListDepth(state);
13746
+ const sameTopLevelBlock = state.selection.empty ||
13747
+ $from.sameParent($to) ||
13748
+ $from.node(1) === $to.node(1);
13749
+ if (fromDepth !== null && sameTopLevelBlock) {
13750
+ return listContextAt($from, fromDepth, listType);
13751
+ }
13752
+ if (fromDepth === null && sameTopLevelBlock) {
13753
+ return { kind: 'no-list', listDepth: null, singleBlock: true };
13754
+ }
13755
+ // The range spans multiple top-level blocks: uniform paragraphs mean a
13756
+ // plain wrap; lists that are all of the target type mean a toggle;
13757
+ // any other list in the mix means unification.
13758
+ const listTypes = new Set();
13759
+ let sawParagraph = false;
13760
+ const startIndex = $from.index(0);
13761
+ const endIndex = Math.min($to.index(0), state.doc.childCount - 1);
13762
+ for (let i = startIndex; i <= endIndex; i++) {
13763
+ const child = state.doc.child(i);
13764
+ if (isListNode(child, state.schema)) {
13765
+ listTypes.add(child.type);
13766
+ }
13767
+ else if (child.type === state.schema.nodes.paragraph) {
13768
+ sawParagraph = true;
13769
+ }
13770
+ }
13771
+ const uniformTargetLists = !sawParagraph && listTypes.size === 1 && listTypes.has(listType);
13772
+ if (uniformTargetLists && fromDepth !== null) {
13773
+ return listContextAt($from, fromDepth, listType);
13774
+ }
13775
+ if (listTypes.size > 0) {
13776
+ return { kind: 'mixed', listDepth: null };
13777
+ }
13778
+ return { kind: 'no-list', listDepth: null, singleBlock: false };
13779
+ };
13780
+ /**
13781
+ * Converts the innermost list around the selection to the target list type.
13782
+ * bullet_list and ordered_list share the list_item+ content model, so the
13783
+ * node can change type in place.
13784
+ *
13785
+ * @param state - the current editor state
13786
+ * @param listDepth - depth of the list node, from resolveListContext
13787
+ * @param targetType - the list type to convert to
13788
+ * @param dispatch - the dispatch function; omit for a dry-run capability check
13789
+ * @returns true when the conversion applies
13790
+ */
13791
+ const convertInnermostList = (state, listDepth, targetType, dispatch) => {
13792
+ const { $from } = state.selection;
13793
+ const pos = $from.before(listDepth);
13794
+ const node = state.doc.nodeAt(pos);
13795
+ if (!node) {
13796
+ return false;
13797
+ }
13798
+ if (dispatch) {
13799
+ dispatch(state.tr
13800
+ .setNodeMarkup(pos, targetType, listAttrsFor(targetType, node.attrs, state.schema))
13801
+ .scrollIntoView());
13802
+ }
13803
+ return true;
13804
+ };
13805
+ /**
13806
+ * Turns a mixed top-level selection (paragraphs and lists) into a single
13807
+ * list of the target type: paragraph runs are wrapped, lists are converted
13808
+ * in place, and adjacent same-type lists are joined. Nesting inside
13809
+ * converted lists is preserved.
13810
+ *
13811
+ * @param state - the current editor state
13812
+ * @param targetType - the list type to unify into
13813
+ * @param dispatch - the dispatch function; omit for a dry-run capability check
13814
+ * @returns true when the unification applies
13815
+ */
13816
+ const unifyToList = (state, targetType, dispatch) => {
13817
+ const { $from, $to } = state.selection;
13818
+ if ($from.depth === 0 || $to.depth === 0) {
13819
+ return false;
13820
+ }
13821
+ if (dispatch === undefined) {
13822
+ return true;
13823
+ }
13824
+ const tr = state.tr;
13825
+ convertListsAndWrapRuns(tr, state, targetType, $from.index(0), $to.index(0));
13826
+ joinAdjacentLists(tr, targetType, tr.mapping.map($from.before(1)), mapKeepBefore(tr, $to.after(1)));
13827
+ dispatch(tr.scrollIntoView());
13828
+ return true;
13829
+ };
13830
+ /**
13831
+ * Wraps a dispatch so that after a list is wrapped around the selection,
13832
+ * adjacent top-level lists of the same type are joined with it.
13833
+ *
13834
+ * @param state - the editor state the wrap command runs on
13835
+ * @param targetType - the list type being wrapped
13836
+ * @param dispatch - the dispatch function; omitted for a dry-run capability check
13837
+ * @returns the wrapped dispatch, or undefined when no dispatch was given
13838
+ */
13839
+ const joinAdjacentListsOnDispatch = (state, targetType, dispatch) => {
13840
+ if (!dispatch) {
13841
+ return undefined;
13842
+ }
13843
+ const { $from, $to } = state.selection;
13844
+ return (tr) => {
13845
+ // The new list's outer boundaries: the start maps before the
13846
+ // inserted wrapping, the end maps after it.
13847
+ const start = mapKeepBefore(tr, $from.before(1));
13848
+ const end = tr.mapping.map($to.after(1));
13849
+ // Back to front, so the earlier boundary stays valid after a join.
13850
+ joinSameTypeBoundary(tr, targetType, end);
13851
+ joinSameTypeBoundary(tr, targetType, start);
13852
+ dispatch(tr);
13853
+ };
13854
+ };
13855
+ const joinSameTypeBoundary = (tr, targetType, boundary) => {
13856
+ var _a, _b;
13857
+ if (boundary <= 0 || boundary >= tr.doc.content.size) {
13858
+ return;
13859
+ }
13860
+ const $boundary = tr.doc.resolve(boundary);
13861
+ if (((_a = $boundary.nodeBefore) === null || _a === void 0 ? void 0 : _a.type) === targetType &&
13862
+ ((_b = $boundary.nodeAfter) === null || _b === void 0 ? void 0 : _b.type) === targetType) {
13863
+ tr.join(boundary);
13864
+ }
13865
+ };
13866
+ const isListNode = (node, schema) => node.type === schema.nodes.bullet_list ||
13867
+ node.type === schema.nodes.ordered_list;
13868
+ const innermostListDepth = (state) => {
13869
+ const { $from } = state.selection;
13870
+ for (let depth = $from.depth; depth > 0; depth--) {
13871
+ if (isListNode($from.node(depth), state.schema)) {
13872
+ return depth;
13873
+ }
13874
+ }
13875
+ return null;
13876
+ };
13877
+ const listContextAt = ($from, listDepth, listType) => ({
13878
+ kind: $from.node(listDepth).type === listType ? 'same-type' : 'other-type',
13879
+ listDepth: listDepth,
13880
+ });
13881
+ const listAttrsFor = (targetType, attrs, schema) => {
13882
+ var _a;
13883
+ if (targetType === schema.nodes.ordered_list) {
13884
+ return Object.assign(Object.assign({}, attrs), { order: (_a = attrs.order) !== null && _a !== void 0 ? _a : 1 });
13885
+ }
13886
+ const withoutOrder = __rest(attrs, ["order"]);
13887
+ return withoutOrder;
13888
+ };
13889
+ const mapKeepBefore = (tr, position) =>
13890
+ // ProseMirror's Mapping.map takes (pos, assoc); the unicorn rule
13891
+ // misreads it as Array#map with a `this` argument.
13892
+ // eslint-disable-next-line unicorn/no-array-method-this-argument
13893
+ tr.mapping.map(position, -1);
13894
+ const wrapRunInList = (tr, targetType, runStart, runEnd) => {
13895
+ const $start = tr.doc.resolve(tr.mapping.map(runStart) + 1);
13896
+ const $end = tr.doc.resolve(mapKeepBefore(tr, runEnd) - 1);
13897
+ const range = new NodeRange($start, $end, 0);
13898
+ const wrapping = findWrapping(range, targetType);
13899
+ if (!wrapping) {
13900
+ return;
13901
+ }
13902
+ tr.wrap(range, wrapping);
13903
+ // tr.wrap puts the whole run inside a single list_item; splitting at
13904
+ // each block boundary gives one item per block, the same shape
13905
+ // wrapInList produces.
13906
+ const listIndex = wrapping.findIndex((entry) => entry.type === targetType);
13907
+ const splitDepth = wrapping.length - (listIndex + 1);
13908
+ let splitPos = range.start + wrapping.length;
13909
+ for (let i = range.startIndex; i < range.endIndex; i++) {
13910
+ if (i > range.startIndex && canSplit(tr.doc, splitPos, splitDepth)) {
13911
+ tr.split(splitPos, splitDepth);
13912
+ splitPos += 2 * splitDepth;
13913
+ }
13914
+ splitPos += range.parent.child(i).nodeSize;
13915
+ }
13916
+ };
13917
+ const convertListsAndWrapRuns = (tr, state, targetType, startIndex, endIndex) => {
13918
+ // Positions computed against the pre-transform doc are mapped through
13919
+ // tr.mapping when applied.
13920
+ const doc = state.doc;
13921
+ let runStart = null;
13922
+ let runEnd = null;
13923
+ let pos = 0;
13924
+ for (let i = 0; i < doc.childCount; i++) {
13925
+ const child = doc.child(i);
13926
+ const inRange = i >= startIndex && i <= endIndex;
13927
+ const isList = isListNode(child, state.schema);
13928
+ if (inRange && child.type === state.schema.nodes.paragraph) {
13929
+ runStart = runStart !== null && runStart !== void 0 ? runStart : pos;
13930
+ runEnd = pos + child.nodeSize;
13931
+ }
13932
+ else if (runStart !== null && runEnd !== null) {
13933
+ wrapRunInList(tr, targetType, runStart, runEnd);
13934
+ runStart = null;
13935
+ runEnd = null;
13936
+ }
13937
+ if (inRange && isList && child.type !== targetType) {
13938
+ tr.setNodeMarkup(tr.mapping.map(pos), targetType, listAttrsFor(targetType, child.attrs, state.schema));
13939
+ }
13940
+ pos += child.nodeSize;
13941
+ }
13942
+ if (runStart !== null && runEnd !== null) {
13943
+ wrapRunInList(tr, targetType, runStart, runEnd);
13944
+ }
13429
13945
  };
13430
- const setActiveMethodForWrap = (command, nodeType) => {
13431
- command.active = (state) => {
13432
- const { from, to } = state.selection;
13433
- for (let pos = from; pos <= to; pos++) {
13434
- const resolvedPos = state.doc.resolve(pos);
13435
- for (let i = resolvedPos.depth; i > 0; i--) {
13436
- const node = resolvedPos.node(i);
13437
- if (node && node.type.name === nodeType.name) {
13438
- return true;
13439
- }
13440
- }
13946
+ const joinAdjacentLists = (tr, targetType, mappedFrom, mappedTo) => {
13947
+ // Scan the transformed doc's top-level boundaries back to front, so
13948
+ // earlier join positions stay valid as joins shrink the doc.
13949
+ const boundaries = [];
13950
+ let boundary = 0;
13951
+ for (let i = 0; i < tr.doc.childCount; i++) {
13952
+ boundary += tr.doc.child(i).nodeSize;
13953
+ boundaries.push(boundary);
13954
+ }
13955
+ for (let i = boundaries.length - 1; i >= 0; i--) {
13956
+ const joinPos = boundaries[i];
13957
+ if (joinPos <= mappedFrom || joinPos > mappedTo + 1) {
13958
+ continue;
13441
13959
  }
13442
- return false;
13443
- };
13960
+ joinSameTypeBoundary(tr, targetType, joinPos);
13961
+ }
13444
13962
  };
13963
+
13445
13964
  const createInsertLinkCommand = (schema, _, link) => {
13446
13965
  const command = (state, dispatch) => {
13447
13966
  const { from, to } = state.selection;
@@ -13501,11 +14020,13 @@ const toggleNodeType = (schema, type, attrs = {}, shouldWrap = false) => {
13501
14020
  const paragraphType = schema.nodes.paragraph;
13502
14021
  return (state, dispatch) => {
13503
14022
  const { $from, $to } = state.selection;
13504
- const hasActiveWrap = $from.node($from.depth - 1).type === nodeType;
13505
14023
  if (state.selection instanceof TextSelection &&
13506
14024
  // Ensure selection is within the same parent block
13507
14025
  // We don't want toggling block types across multiple blocks
13508
14026
  $from.sameParent($from.doc.resolve($to.pos))) {
14027
+ // Resolved only under the TextSelection guard: an AllSelection
14028
+ // resolves at depth 0, where there is no parent node to read.
14029
+ const hasActiveWrap = $from.node($from.depth - 1).type === nodeType;
13509
14030
  if ($from.parent.type === nodeType) {
13510
14031
  if (dispatch) {
13511
14032
  dispatch(state.tr.setBlockType($from.pos, $to.pos, paragraphType));
@@ -13562,40 +14083,85 @@ const createWrapInCommand = (schema, nodeType) => {
13562
14083
  setActiveMethodForWrap(command, type);
13563
14084
  return command;
13564
14085
  };
13565
- const toggleList = (listType) => {
13566
- return (state, dispatch) => {
13567
- const { $from, $to } = state.selection;
13568
- const range = $from.blockRange($to);
13569
- if (!range) {
14086
+ // Select-all produces an AllSelection, whose endpoints resolve at the
14087
+ // document level where list commands cannot operate. Re-anchoring it as
14088
+ // a TextSelection over the same content lets the list ladder (and the
14089
+ // delegated prosemirror-schema-list commands) treat select-all like any
14090
+ // other full-content selection.
14091
+ const withTextSelection = (state) => {
14092
+ if (!(state.selection instanceof AllSelection)) {
14093
+ return state;
14094
+ }
14095
+ const selection = TextSelection.between(state.doc.resolve(0), state.doc.resolve(state.doc.content.size));
14096
+ // A plugin-free scratch state on the shared doc: only feed it
14097
+ // commands driven by doc and selection.
14098
+ return EditorState.create({
14099
+ doc: state.doc,
14100
+ selection: selection,
14101
+ storedMarks: state.storedMarks,
14102
+ });
14103
+ };
14104
+ /**
14105
+ * Creates a command for toggling list types.
14106
+ *
14107
+ * @param schema - The ProseMirror schema.
14108
+ * @param listTypeName - The name of the list type to toggle.
14109
+ * @returns A command for toggling list types.
14110
+ */
14111
+ const createListCommand = (schema, listTypeName) => {
14112
+ const type = schema.nodes[listTypeName];
14113
+ if (!type) {
14114
+ throw new Error(`List type "${listTypeName}" not found in schema`);
14115
+ }
14116
+ const itemType = schema.nodes.list_item;
14117
+ if (!itemType) {
14118
+ throw new Error('Node type "list_item" not found in schema');
14119
+ }
14120
+ // The keymap invokes the command directly, so the command body applies
14121
+ // the same applicability rules as `allowed` to keep keyboard and
14122
+ // toolbar behavior identical.
14123
+ const command = (state, dispatch) => {
14124
+ state = withTextSelection(state);
14125
+ if (!(state.selection instanceof TextSelection)) {
13570
14126
  return false;
13571
14127
  }
13572
- const wrapping = range && findWrapping(range, listType);
13573
- if (wrapping) {
13574
- // Wrap the selection in a list
13575
- if (dispatch) {
13576
- dispatch(state.tr.wrap(range, wrapping).scrollIntoView());
13577
- }
13578
- return true;
14128
+ const context = resolveListContext(state, type);
14129
+ if (context.kind === 'same-type') {
14130
+ return liftListItem(itemType)(state, dispatch);
13579
14131
  }
13580
- else {
13581
- // Check if we are in a list item and lift out of the list
13582
- const liftRange = range && liftTarget(range);
13583
- if (liftRange !== null) {
13584
- if (dispatch) {
13585
- dispatch(state.tr.lift(range, liftRange).scrollIntoView());
13586
- }
13587
- return true;
14132
+ if (context.kind === 'other-type') {
14133
+ return convertInnermostList(state, context.listDepth, type, dispatch);
14134
+ }
14135
+ if (context.kind === 'no-list') {
14136
+ // For a single block the schema decides, through the wrap
14137
+ // command itself; multi-block selections keep the stricter
14138
+ // gate so unify cannot half-apply.
14139
+ if (!context.singleBlock && selectionHasNonListableBlock(state)) {
14140
+ return false;
13588
14141
  }
14142
+ return wrapInList(type)(state, joinAdjacentListsOnDispatch(state, type, dispatch));
14143
+ }
14144
+ if (selectionHasNonListableBlock(state)) {
13589
14145
  return false;
13590
14146
  }
14147
+ return unifyToList(state, type, dispatch);
14148
+ };
14149
+ command.allowed = (state) => {
14150
+ state = withTextSelection(state);
14151
+ if (!(state.selection instanceof TextSelection)) {
14152
+ return false;
14153
+ }
14154
+ const context = resolveListContext(state, type);
14155
+ // Inside a list, toggling or converting is always applicable, even
14156
+ // when a non-listable ancestor wraps the list.
14157
+ if (context.kind === 'same-type' || context.kind === 'other-type') {
14158
+ return true;
14159
+ }
14160
+ if (context.kind === 'no-list' && context.singleBlock) {
14161
+ return wrapInList(type)(state);
14162
+ }
14163
+ return !selectionHasNonListableBlock(state);
13591
14164
  };
13592
- };
13593
- const createListCommand = (schema, listType) => {
13594
- const type = schema.nodes[listType];
13595
- if (!type) {
13596
- throw new Error(`List type "${listType}" not found in schema`);
13597
- }
13598
- const command = toggleList(type);
13599
14165
  setActiveMethodForWrap(command, type);
13600
14166
  return command;
13601
14167
  };
@@ -13632,6 +14198,8 @@ class MenuCommandFactory {
13632
14198
  'Mod-Shift-1': this.getCommand(types.EditorMenuTypes.HeaderLevel1),
13633
14199
  'Mod-Shift-2': this.getCommand(types.EditorMenuTypes.HeaderLevel2),
13634
14200
  'Mod-Shift-3': this.getCommand(types.EditorMenuTypes.HeaderLevel3),
14201
+ 'Mod-Shift-7': this.getCommand(types.EditorMenuTypes.OrderedList),
14202
+ 'Mod-Shift-8': this.getCommand(types.EditorMenuTypes.BulletList),
13635
14203
  'Mod-Shift-X': this.getCommand(types.EditorMenuTypes.Strikethrough),
13636
14204
  'Mod-`': this.getCommand(types.EditorMenuTypes.Code),
13637
14205
  'Mod-Shift-C': this.getCommand(types.EditorMenuTypes.CodeBlock),
@@ -23083,356 +23651,103 @@ const nodes = {
23083
23651
  `alt`, and `href` attributes. The latter two default to the empty
23084
23652
  string.
23085
23653
  */
23086
- image: {
23087
- inline: true,
23088
- attrs: {
23089
- src: { validate: "string" },
23090
- alt: { default: null, validate: "string|null" },
23091
- title: { default: null, validate: "string|null" }
23092
- },
23093
- group: "inline",
23094
- draggable: true,
23095
- parseDOM: [{ tag: "img[src]", getAttrs(dom) {
23096
- return {
23097
- src: dom.getAttribute("src"),
23098
- title: dom.getAttribute("title"),
23099
- alt: dom.getAttribute("alt")
23100
- };
23101
- } }],
23102
- toDOM(node) { let { src, alt, title } = node.attrs; return ["img", { src, alt, title }]; }
23103
- },
23104
- /**
23105
- A hard line break, represented in the DOM as `<br>`.
23106
- */
23107
- hard_break: {
23108
- inline: true,
23109
- group: "inline",
23110
- selectable: false,
23111
- parseDOM: [{ tag: "br" }],
23112
- toDOM() { return brDOM; }
23113
- }
23114
- };
23115
- const emDOM = ["em", 0], strongDOM = ["strong", 0], codeDOM = ["code", 0];
23116
- /**
23117
- [Specs](https://prosemirror.net/docs/ref/#model.MarkSpec) for the marks in the schema.
23118
- */
23119
- const marks = {
23120
- /**
23121
- A link. Has `href` and `title` attributes. `title`
23122
- defaults to the empty string. Rendered and parsed as an `<a>`
23123
- element.
23124
- */
23125
- link: {
23126
- attrs: {
23127
- href: { validate: "string" },
23128
- title: { default: null, validate: "string|null" }
23129
- },
23130
- inclusive: false,
23131
- parseDOM: [{ tag: "a[href]", getAttrs(dom) {
23132
- return { href: dom.getAttribute("href"), title: dom.getAttribute("title") };
23133
- } }],
23134
- toDOM(node) { let { href, title } = node.attrs; return ["a", { href, title }, 0]; }
23135
- },
23136
- /**
23137
- An emphasis mark. Rendered as an `<em>` element. Has parse rules
23138
- that also match `<i>` and `font-style: italic`.
23139
- */
23140
- em: {
23141
- parseDOM: [
23142
- { tag: "i" }, { tag: "em" },
23143
- { style: "font-style=italic" },
23144
- { style: "font-style=normal", clearMark: m => m.type.name == "em" }
23145
- ],
23146
- toDOM() { return emDOM; }
23147
- },
23148
- /**
23149
- A strong mark. Rendered as `<strong>`, parse rules also match
23150
- `<b>` and `font-weight: bold`.
23151
- */
23152
- strong: {
23153
- parseDOM: [
23154
- { tag: "strong" },
23155
- // This works around a Google Docs misbehavior where
23156
- // pasted content will be inexplicably wrapped in `<b>`
23157
- // tags with a font-weight normal.
23158
- { tag: "b", getAttrs: (node) => node.style.fontWeight != "normal" && null },
23159
- { style: "font-weight=400", clearMark: m => m.type.name == "strong" },
23160
- { style: "font-weight", getAttrs: (value) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null },
23161
- ],
23162
- toDOM() { return strongDOM; }
23163
- },
23164
- /**
23165
- Code font mark. Represented as a `<code>` element.
23166
- */
23167
- code: {
23168
- code: true,
23169
- parseDOM: [{ tag: "code" }],
23170
- toDOM() { return codeDOM; }
23171
- }
23172
- };
23173
- /**
23174
- This schema roughly corresponds to the document schema used by
23175
- [CommonMark](http://commonmark.org/), minus the list elements,
23176
- which are defined in the [`prosemirror-schema-list`](https://prosemirror.net/docs/ref/#schema-list)
23177
- module.
23178
-
23179
- To reuse elements from this schema, extend or read from its
23180
- `spec.nodes` and `spec.marks` [properties](https://prosemirror.net/docs/ref/#model.Schema.spec).
23181
- */
23182
- const schema = new Schema({ nodes, marks });
23183
-
23184
- const olDOM = ["ol", 0], ulDOM = ["ul", 0], liDOM = ["li", 0];
23185
- /**
23186
- An ordered list [node spec](https://prosemirror.net/docs/ref/#model.NodeSpec). Has a single
23187
- attribute, `order`, which determines the number at which the list
23188
- starts counting, and defaults to 1. Represented as an `<ol>`
23189
- element.
23190
- */
23191
- const orderedList = {
23192
- attrs: { order: { default: 1 } },
23193
- parseDOM: [{ tag: "ol", getAttrs(dom) {
23194
- return { order: dom.hasAttribute("start") ? +dom.getAttribute("start") : 1 };
23195
- } }],
23196
- toDOM(node) {
23197
- return node.attrs.order == 1 ? olDOM : ["ol", { start: node.attrs.order }, 0];
23198
- }
23199
- };
23200
- /**
23201
- A bullet list node spec, represented in the DOM as `<ul>`.
23202
- */
23203
- const bulletList = {
23204
- parseDOM: [{ tag: "ul" }],
23205
- toDOM() { return ulDOM; }
23206
- };
23207
- /**
23208
- A list item (`<li>`) spec.
23209
- */
23210
- const listItem = {
23211
- parseDOM: [{ tag: "li" }],
23212
- toDOM() { return liDOM; },
23213
- defining: true
23214
- };
23215
- function add(obj, props) {
23216
- let copy = {};
23217
- for (let prop in obj)
23218
- copy[prop] = obj[prop];
23219
- for (let prop in props)
23220
- copy[prop] = props[prop];
23221
- return copy;
23222
- }
23223
- /**
23224
- Convenience function for adding list-related node types to a map
23225
- specifying the nodes for a schema. Adds
23226
- [`orderedList`](https://prosemirror.net/docs/ref/#schema-list.orderedList) as `"ordered_list"`,
23227
- [`bulletList`](https://prosemirror.net/docs/ref/#schema-list.bulletList) as `"bullet_list"`, and
23228
- [`listItem`](https://prosemirror.net/docs/ref/#schema-list.listItem) as `"list_item"`.
23229
-
23230
- `itemContent` determines the content expression for the list items.
23231
- If you want the commands defined in this module to apply to your
23232
- list structure, it should have a shape like `"paragraph block*"` or
23233
- `"paragraph (ordered_list | bullet_list)*"`. `listGroup` can be
23234
- given to assign a group name to the list node types, for example
23235
- `"block"`.
23236
- */
23237
- function addListNodes(nodes, itemContent, listGroup) {
23238
- return nodes.append({
23239
- ordered_list: add(orderedList, { content: "list_item+", group: listGroup }),
23240
- bullet_list: add(bulletList, { content: "list_item+", group: listGroup }),
23241
- list_item: add(listItem, { content: itemContent })
23242
- });
23243
- }
23244
- /**
23245
- Returns a command function that wraps the selection in a list with
23246
- the given type an attributes. If `dispatch` is null, only return a
23247
- value to indicate whether this is possible, but don't actually
23248
- perform the change.
23249
- */
23250
- function wrapInList(listType, attrs = null) {
23251
- return function (state, dispatch) {
23252
- let { $from, $to } = state.selection;
23253
- let range = $from.blockRange($to), doJoin = false, outerRange = range;
23254
- if (!range)
23255
- return false;
23256
- // This is at the top of an existing list item
23257
- if (range.depth >= 2 && $from.node(range.depth - 1).type.compatibleContent(listType) && range.startIndex == 0) {
23258
- // Don't do anything if this is the top of the list
23259
- if ($from.index(range.depth - 1) == 0)
23260
- return false;
23261
- let $insert = state.doc.resolve(range.start - 2);
23262
- outerRange = new NodeRange($insert, $insert, range.depth);
23263
- if (range.endIndex < range.parent.childCount)
23264
- range = new NodeRange($from, state.doc.resolve($to.end(range.depth)), range.depth);
23265
- doJoin = true;
23266
- }
23267
- let wrap = findWrapping(outerRange, listType, attrs, range);
23268
- if (!wrap)
23269
- return false;
23270
- if (dispatch)
23271
- dispatch(doWrapInList(state.tr, range, wrap, doJoin, listType).scrollIntoView());
23272
- return true;
23273
- };
23274
- }
23275
- function doWrapInList(tr, range, wrappers, joinBefore, listType) {
23276
- let content = Fragment.empty;
23277
- for (let i = wrappers.length - 1; i >= 0; i--)
23278
- content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content));
23279
- tr.step(new ReplaceAroundStep(range.start - (joinBefore ? 2 : 0), range.end, range.start, range.end, new Slice(content, 0, 0), wrappers.length, true));
23280
- let found = 0;
23281
- for (let i = 0; i < wrappers.length; i++)
23282
- if (wrappers[i].type == listType)
23283
- found = i + 1;
23284
- let splitDepth = wrappers.length - found;
23285
- let splitPos = range.start + wrappers.length - (joinBefore ? 2 : 0), parent = range.parent;
23286
- for (let i = range.startIndex, e = range.endIndex, first = true; i < e; i++, first = false) {
23287
- if (!first && canSplit(tr.doc, splitPos, splitDepth)) {
23288
- tr.split(splitPos, splitDepth);
23289
- splitPos += 2 * splitDepth;
23290
- }
23291
- splitPos += parent.child(i).nodeSize;
23654
+ image: {
23655
+ inline: true,
23656
+ attrs: {
23657
+ src: { validate: "string" },
23658
+ alt: { default: null, validate: "string|null" },
23659
+ title: { default: null, validate: "string|null" }
23660
+ },
23661
+ group: "inline",
23662
+ draggable: true,
23663
+ parseDOM: [{ tag: "img[src]", getAttrs(dom) {
23664
+ return {
23665
+ src: dom.getAttribute("src"),
23666
+ title: dom.getAttribute("title"),
23667
+ alt: dom.getAttribute("alt")
23668
+ };
23669
+ } }],
23670
+ toDOM(node) { let { src, alt, title } = node.attrs; return ["img", { src, alt, title }]; }
23671
+ },
23672
+ /**
23673
+ A hard line break, represented in the DOM as `<br>`.
23674
+ */
23675
+ hard_break: {
23676
+ inline: true,
23677
+ group: "inline",
23678
+ selectable: false,
23679
+ parseDOM: [{ tag: "br" }],
23680
+ toDOM() { return brDOM; }
23292
23681
  }
23293
- return tr;
23294
- }
23295
- /**
23296
- Build a command that splits a non-empty textblock at the top level
23297
- of a list item by also splitting that list item.
23298
- */
23299
- function splitListItem(itemType, itemAttrs) {
23300
- return function (state, dispatch) {
23301
- let { $from, $to, node } = state.selection;
23302
- if ((node && node.isBlock) || $from.depth < 2 || !$from.sameParent($to))
23303
- return false;
23304
- let grandParent = $from.node(-1);
23305
- if (grandParent.type != itemType)
23306
- return false;
23307
- if ($from.parent.content.size == 0 && $from.node(-1).childCount == $from.indexAfter(-1)) {
23308
- // In an empty block. If this is a nested list, the wrapping
23309
- // list item should be split. Otherwise, bail out and let next
23310
- // command handle lifting.
23311
- if ($from.depth == 3 || $from.node(-3).type != itemType ||
23312
- $from.index(-2) != $from.node(-2).childCount - 1)
23313
- return false;
23314
- if (dispatch) {
23315
- let wrap = Fragment.empty;
23316
- let depthBefore = $from.index(-1) ? 1 : $from.index(-2) ? 2 : 3;
23317
- // Build a fragment containing empty versions of the structure
23318
- // from the outer list item to the parent node of the cursor
23319
- for (let d = $from.depth - depthBefore; d >= $from.depth - 3; d--)
23320
- wrap = Fragment.from($from.node(d).copy(wrap));
23321
- let depthAfter = $from.indexAfter(-1) < $from.node(-2).childCount ? 1
23322
- : $from.indexAfter(-2) < $from.node(-3).childCount ? 2 : 3;
23323
- // Add a second list item with an empty default start node
23324
- wrap = wrap.append(Fragment.from(itemType.createAndFill()));
23325
- let start = $from.before($from.depth - (depthBefore - 1));
23326
- let tr = state.tr.replace(start, $from.after(-depthAfter), new Slice(wrap, 4 - depthBefore, 0));
23327
- let sel = -1;
23328
- tr.doc.nodesBetween(start, tr.doc.content.size, (node, pos) => {
23329
- if (sel > -1)
23330
- return false;
23331
- if (node.isTextblock && node.content.size == 0)
23332
- sel = pos + 1;
23333
- });
23334
- if (sel > -1)
23335
- tr.setSelection(Selection.near(tr.doc.resolve(sel)));
23336
- dispatch(tr.scrollIntoView());
23337
- }
23338
- return true;
23339
- }
23340
- let nextType = $to.pos == $from.end() ? grandParent.contentMatchAt(0).defaultType : null;
23341
- let tr = state.tr.delete($from.pos, $to.pos);
23342
- let types = nextType ? [null, { type: nextType }] : undefined;
23343
- if (!canSplit(tr.doc, $from.pos, 2, types))
23344
- return false;
23345
- if (dispatch)
23346
- dispatch(tr.split($from.pos, 2, types).scrollIntoView());
23347
- return true;
23348
- };
23349
- }
23682
+ };
23683
+ const emDOM = ["em", 0], strongDOM = ["strong", 0], codeDOM = ["code", 0];
23350
23684
  /**
23351
- Create a command to lift the list item around the selection up into
23352
- a wrapping list.
23685
+ [Specs](https://prosemirror.net/docs/ref/#model.MarkSpec) for the marks in the schema.
23353
23686
  */
23354
- function liftListItem(itemType) {
23355
- return function (state, dispatch) {
23356
- let { $from, $to } = state.selection;
23357
- let range = $from.blockRange($to, node => node.childCount > 0 && node.firstChild.type == itemType);
23358
- if (!range)
23359
- return false;
23360
- if (!dispatch)
23361
- return true;
23362
- if ($from.node(range.depth - 1).type == itemType) // Inside a parent list
23363
- return liftToOuterList(state, dispatch, itemType, range);
23364
- else // Outer list node
23365
- return liftOutOfList(state, dispatch, range);
23366
- };
23367
- }
23368
- function liftToOuterList(state, dispatch, itemType, range) {
23369
- let tr = state.tr, end = range.end, endOfList = range.$to.end(range.depth);
23370
- if (end < endOfList) {
23371
- // There are siblings after the lifted items, which must become
23372
- // children of the last item
23373
- tr.step(new ReplaceAroundStep(end - 1, endOfList, end, endOfList, new Slice(Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true));
23374
- range = new NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth);
23375
- }
23376
- const target = liftTarget(range);
23377
- if (target == null)
23378
- return false;
23379
- tr.lift(range, target);
23380
- let after = tr.mapping.map(end, -1) - 1;
23381
- if (canJoin(tr.doc, after))
23382
- tr.join(after);
23383
- dispatch(tr.scrollIntoView());
23384
- return true;
23385
- }
23386
- function liftOutOfList(state, dispatch, range) {
23387
- let tr = state.tr, list = range.parent;
23388
- // Merge the list items into a single big item
23389
- for (let pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) {
23390
- pos -= list.child(i).nodeSize;
23391
- tr.delete(pos - 1, pos + 1);
23687
+ const marks = {
23688
+ /**
23689
+ A link. Has `href` and `title` attributes. `title`
23690
+ defaults to the empty string. Rendered and parsed as an `<a>`
23691
+ element.
23692
+ */
23693
+ link: {
23694
+ attrs: {
23695
+ href: { validate: "string" },
23696
+ title: { default: null, validate: "string|null" }
23697
+ },
23698
+ inclusive: false,
23699
+ parseDOM: [{ tag: "a[href]", getAttrs(dom) {
23700
+ return { href: dom.getAttribute("href"), title: dom.getAttribute("title") };
23701
+ } }],
23702
+ toDOM(node) { let { href, title } = node.attrs; return ["a", { href, title }, 0]; }
23703
+ },
23704
+ /**
23705
+ An emphasis mark. Rendered as an `<em>` element. Has parse rules
23706
+ that also match `<i>` and `font-style: italic`.
23707
+ */
23708
+ em: {
23709
+ parseDOM: [
23710
+ { tag: "i" }, { tag: "em" },
23711
+ { style: "font-style=italic" },
23712
+ { style: "font-style=normal", clearMark: m => m.type.name == "em" }
23713
+ ],
23714
+ toDOM() { return emDOM; }
23715
+ },
23716
+ /**
23717
+ A strong mark. Rendered as `<strong>`, parse rules also match
23718
+ `<b>` and `font-weight: bold`.
23719
+ */
23720
+ strong: {
23721
+ parseDOM: [
23722
+ { tag: "strong" },
23723
+ // This works around a Google Docs misbehavior where
23724
+ // pasted content will be inexplicably wrapped in `<b>`
23725
+ // tags with a font-weight normal.
23726
+ { tag: "b", getAttrs: (node) => node.style.fontWeight != "normal" && null },
23727
+ { style: "font-weight=400", clearMark: m => m.type.name == "strong" },
23728
+ { style: "font-weight", getAttrs: (value) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null },
23729
+ ],
23730
+ toDOM() { return strongDOM; }
23731
+ },
23732
+ /**
23733
+ Code font mark. Represented as a `<code>` element.
23734
+ */
23735
+ code: {
23736
+ code: true,
23737
+ parseDOM: [{ tag: "code" }],
23738
+ toDOM() { return codeDOM; }
23392
23739
  }
23393
- let $start = tr.doc.resolve(range.start), item = $start.nodeAfter;
23394
- if (tr.mapping.map(range.end) != range.start + $start.nodeAfter.nodeSize)
23395
- return false;
23396
- let atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount;
23397
- let parent = $start.node(-1), indexBefore = $start.index(-1);
23398
- if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1, item.content.append(atEnd ? Fragment.empty : Fragment.from(list))))
23399
- return false;
23400
- let start = $start.pos, end = start + item.nodeSize;
23401
- // Strip off the surrounding list. At the sides where we're not at
23402
- // the end of the list, the existing list is closed. At sides where
23403
- // this is the end, it is overwritten to its end.
23404
- tr.step(new ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1, new Slice((atStart ? Fragment.empty : Fragment.from(list.copy(Fragment.empty)))
23405
- .append(atEnd ? Fragment.empty : Fragment.from(list.copy(Fragment.empty))), atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1));
23406
- dispatch(tr.scrollIntoView());
23407
- return true;
23408
- }
23740
+ };
23409
23741
  /**
23410
- Create a command to sink the list item around the selection down
23411
- into an inner list.
23742
+ This schema roughly corresponds to the document schema used by
23743
+ [CommonMark](http://commonmark.org/), minus the list elements,
23744
+ which are defined in the [`prosemirror-schema-list`](https://prosemirror.net/docs/ref/#schema-list)
23745
+ module.
23746
+
23747
+ To reuse elements from this schema, extend or read from its
23748
+ `spec.nodes` and `spec.marks` [properties](https://prosemirror.net/docs/ref/#model.Schema.spec).
23412
23749
  */
23413
- function sinkListItem(itemType) {
23414
- return function (state, dispatch) {
23415
- let { $from, $to } = state.selection;
23416
- let range = $from.blockRange($to, node => node.childCount > 0 && node.firstChild.type == itemType);
23417
- if (!range)
23418
- return false;
23419
- let startIndex = range.startIndex;
23420
- if (startIndex == 0)
23421
- return false;
23422
- let parent = range.parent, nodeBefore = parent.child(startIndex - 1);
23423
- if (nodeBefore.type != itemType)
23424
- return false;
23425
- if (dispatch) {
23426
- let nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type;
23427
- let inner = Fragment.from(nestedBefore ? itemType.create() : null);
23428
- let slice = new Slice(Fragment.from(itemType.create(null, Fragment.from(parent.type.create(null, inner)))), nestedBefore ? 3 : 1, 0);
23429
- let before = range.start, after = range.end;
23430
- dispatch(state.tr.step(new ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after, before, after, slice, 1, true))
23431
- .scrollIntoView());
23432
- }
23433
- return true;
23434
- };
23435
- }
23750
+ const schema = new Schema({ nodes, marks });
23436
23751
 
23437
23752
  var base = {
23438
23753
  8: "Backspace",
@@ -25990,6 +26305,64 @@ const createActionBarInteractionPlugin = (menuCommandFactory) => {
25990
26305
  });
25991
26306
  };
25992
26307
 
26308
+ /**
26309
+ * Finds the depth of the nearest ancestor of a given node type.
26310
+ *
26311
+ * @param $pos - The resolved position in the document.
26312
+ * @param type - The node type to search for.
26313
+ * @returns The depth at which the node is found, or null if not found.
26314
+ */
26315
+ const findAncestorDepthOfType = ($pos, type) => {
26316
+ for (let depth = $pos.depth; depth > 0; depth--) {
26317
+ if ($pos.node(depth).type === type) {
26318
+ return depth;
26319
+ }
26320
+ }
26321
+ return null;
26322
+ };
26323
+
26324
+ const listKeyHandlerPluginKey = new PluginKey('listKeyHandlerPlugin');
26325
+ /**
26326
+ * Creates a plugin for handling Tab and Shift+Tab inside list items:
26327
+ * Tab indents the item and Shift+Tab outdents it. Enter and Backspace
26328
+ * are handled by the base keymap (splitListItem and joinBackward), which
26329
+ * runs before this plugin.
26330
+ *
26331
+ * @param schema - The document schema
26332
+ * @returns A ProseMirror plugin for handling list-specific key events
26333
+ */
26334
+ function createListKeyHandlerPlugin(schema) {
26335
+ return new Plugin({
26336
+ key: listKeyHandlerPluginKey,
26337
+ props: {
26338
+ handleKeyDown: (view, event) => {
26339
+ if (event.key !== 'Tab') {
26340
+ return false;
26341
+ }
26342
+ const { state } = view;
26343
+ const { $from } = state.selection;
26344
+ const inListItem = findAncestorDepthOfType($from, schema.nodes.list_item) !==
26345
+ null;
26346
+ if (!inListItem) {
26347
+ return false;
26348
+ }
26349
+ // Inside a list item, Tab and Shift+Tab are always handled
26350
+ // so focus never leaves the editor mid-list, even when the
26351
+ // indent or outdent itself cannot apply.
26352
+ event.preventDefault();
26353
+ if (event.shiftKey) {
26354
+ liftListItem(schema.nodes.list_item)(state, view.dispatch);
26355
+ return true;
26356
+ }
26357
+ // Sinking fails on first items (no preceding sibling to
26358
+ // become the parent); Tab is still swallowed.
26359
+ sinkListItem(schema.nodes.list_item)(state, view.dispatch);
26360
+ return true;
26361
+ },
26362
+ },
26363
+ });
26364
+ }
26365
+
25993
26366
  const createHtmlInserter = (view, contentConverter, trigger, dispatchTransaction) => {
25994
26367
  const schema = view.state.schema;
25995
26368
  return async (input) => {
@@ -27539,7 +27912,15 @@ function buildEditorSchema(options) {
27539
27912
  function buildEditorPlugins(options) {
27540
27913
  const { schema, menuCommandFactory, contentConverter, language, contentType, triggerCharacters, inlineImages, onNewLinkSelection, onImagePasted, onActiveItemsChange, } = options;
27541
27914
  return [
27542
- ...exampleSetup({ schema: schema}),
27915
+ // exampleSetup binds Shift-Ctrl-8/9 to a plain wrapInList. On
27916
+ // Windows/Linux those are the same physical keys as our
27917
+ // Mod-Shift-8/7 list commands, and exampleSetup's keymap runs
27918
+ // first, so its bindings are suppressed to let the context-aware
27919
+ // list commands own the shortcuts on every platform.
27920
+ ...exampleSetup({
27921
+ schema: schema,
27922
+ mapKeys: { 'Shift-Ctrl-8': false, 'Shift-Ctrl-9': false },
27923
+ }),
27543
27924
  keymap(menuCommandFactory.buildKeymap()),
27544
27925
  createTriggerPlugin(triggerCharacters, contentConverter),
27545
27926
  createLinkPlugin(onNewLinkSelection),
@@ -27547,6 +27928,7 @@ function buildEditorPlugins(options) {
27547
27928
  createImageViewPlugin(language),
27548
27929
  createMenuStateTrackingPlugin(types.editorMenuTypesArray, menuCommandFactory, onActiveItemsChange),
27549
27930
  createActionBarInteractionPlugin(menuCommandFactory),
27931
+ createListKeyHandlerPlugin(schema),
27550
27932
  ...getTableEditingPlugins(contentType === 'html'),
27551
27933
  ];
27552
27934
  }
@@ -27734,13 +28116,14 @@ const ProsemirrorAdapter = class {
27734
28116
  return newItem;
27735
28117
  };
27736
28118
  this.updateActiveActionBarItems = (activeTypes, allowedTypes) => {
27737
- const newItems = getTextEditorMenuItems().map((item) => {
28119
+ this.actionBarItems = getTextEditorMenuItems()
28120
+ .map(this.getTranslatedItem)
28121
+ .map((item) => {
27738
28122
  if (isItem.isItem(item)) {
27739
- return Object.assign(Object.assign({}, item), { selected: activeTypes[item.value], allowed: allowedTypes[item.value] });
28123
+ return Object.assign(Object.assign({}, item), { selected: activeTypes[item.value], disabled: !allowedTypes[item.value] });
27740
28124
  }
27741
28125
  return item;
27742
28126
  });
27743
- this.actionBarItems = newItems.filter((item) => isItem.isItem(item) ? item.allowed : true);
27744
28127
  };
27745
28128
  this.handleTransaction = (transaction) => {
27746
28129
  this.transactionFired = true;