@limetech/lime-elements 39.44.6 → 39.45.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/CHANGELOG.md +21 -0
- package/dist/cjs/limel-form.cjs.entry.js +41 -9
- package/dist/cjs/limel-markdown.cjs.entry.js +1 -1
- package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +778 -395
- package/dist/cjs/limel-text-editor-link-menu.cjs.entry.js +6 -6
- package/dist/cjs/{markdown-parser-ChnRAxpZ.js → markdown-parser-YH5HSSgF.js} +36 -56
- package/dist/collection/components/text-editor/link-menu/editor-link-menu.js +6 -6
- package/dist/collection/components/text-editor/prosemirror-adapter/editor-config.js +12 -1
- package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-command-utils/active-state-utils.js +36 -0
- package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-command-utils/list-utils.js +270 -0
- package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-command-utils/node-utils.js +15 -0
- package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-commands.js +81 -69
- package/dist/collection/components/text-editor/prosemirror-adapter/plugins/list-key-handler.js +44 -0
- package/dist/collection/components/text-editor/prosemirror-adapter/prosemirror-adapter.js +4 -3
- package/dist/esm/limel-form.entry.js +41 -9
- package/dist/esm/limel-markdown.entry.js +1 -1
- package/dist/esm/limel-prosemirror-adapter.entry.js +779 -396
- package/dist/esm/limel-text-editor-link-menu.entry.js +6 -6
- package/dist/esm/{markdown-parser-BSQKleVw.js → markdown-parser-DrxxGLF5.js} +36 -56
- package/dist/lime-elements/lime-elements.esm.js +1 -1
- package/dist/lime-elements/p-1f29f482.entry.js +1 -0
- package/dist/lime-elements/{p-3ea159fd.entry.js → p-38393fb4.entry.js} +1 -1
- package/dist/lime-elements/{p-1fb4bfa9.entry.js → p-421612f9.entry.js} +2 -2
- package/dist/lime-elements/{p-B0qhUema.js → p-C9xHOCE_.js} +2 -2
- package/dist/lime-elements/p-d97df2cd.entry.js +1 -0
- package/dist/types/components/text-editor/link-menu/editor-link-menu.d.ts +2 -2
- package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-command-utils/active-state-utils.d.ts +6 -0
- package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-command-utils/list-utils.d.ts +68 -0
- package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-command-utils/node-utils.d.ts +10 -0
- package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-commands.d.ts +10 -0
- package/dist/types/components/text-editor/prosemirror-adapter/plugins/list-key-handler.d.ts +14 -0
- package/package.json +4 -4
- package/dist/lime-elements/p-845cfda7.entry.js +0 -1
- package/dist/lime-elements/p-e8df7a2e.entry.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-
|
|
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
|
-
|
|
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
|
|
13431
|
-
|
|
13432
|
-
|
|
13433
|
-
|
|
13434
|
-
|
|
13435
|
-
|
|
13436
|
-
|
|
13437
|
-
|
|
13438
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
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
|
|
13573
|
-
if (
|
|
13574
|
-
|
|
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
|
-
|
|
13581
|
-
|
|
13582
|
-
|
|
13583
|
-
|
|
13584
|
-
|
|
13585
|
-
|
|
13586
|
-
|
|
13587
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23352
|
-
a wrapping list.
|
|
23685
|
+
[Specs](https://prosemirror.net/docs/ref/#model.MarkSpec) for the marks in the schema.
|
|
23353
23686
|
*/
|
|
23354
|
-
|
|
23355
|
-
|
|
23356
|
-
|
|
23357
|
-
|
|
23358
|
-
|
|
23359
|
-
|
|
23360
|
-
|
|
23361
|
-
|
|
23362
|
-
|
|
23363
|
-
|
|
23364
|
-
|
|
23365
|
-
|
|
23366
|
-
|
|
23367
|
-
}
|
|
23368
|
-
|
|
23369
|
-
|
|
23370
|
-
|
|
23371
|
-
|
|
23372
|
-
|
|
23373
|
-
|
|
23374
|
-
|
|
23375
|
-
|
|
23376
|
-
|
|
23377
|
-
|
|
23378
|
-
|
|
23379
|
-
|
|
23380
|
-
|
|
23381
|
-
|
|
23382
|
-
|
|
23383
|
-
|
|
23384
|
-
|
|
23385
|
-
|
|
23386
|
-
|
|
23387
|
-
|
|
23388
|
-
|
|
23389
|
-
|
|
23390
|
-
|
|
23391
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23411
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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],
|
|
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;
|