prosemirror-flat-list
Version:
Powerful list support for ProseMirror
1,579 lines (1,527 loc) • 56.2 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key2 of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key2) && key2 !== except)
__defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
ListDOMSerializer: () => ListDOMSerializer,
backspaceCommand: () => backspaceCommand,
createDedentListCommand: () => createDedentListCommand,
createIndentListCommand: () => createIndentListCommand,
createListClipboardPlugin: () => createListClipboardPlugin,
createListEventPlugin: () => createListEventPlugin,
createListNodeView: () => createListNodeView,
createListPlugins: () => createListPlugins,
createListRenderingPlugin: () => createListRenderingPlugin,
createListSpec: () => createListSpec,
createMoveListCommand: () => createMoveListCommand,
createParseDomRules: () => createParseDomRules,
createSafariInputMethodWorkaroundPlugin: () => createSafariInputMethodWorkaroundPlugin,
createSplitListCommand: () => createSplitListCommand,
createToggleCollapsedCommand: () => createToggleCollapsedCommand,
createToggleListCommand: () => createToggleListCommand,
createUnwrapListCommand: () => createUnwrapListCommand,
createWrapInListCommand: () => createWrapInListCommand,
defaultAttributesGetter: () => defaultAttributesGetter,
defaultListClickHandler: () => defaultListClickHandler,
defaultMarkerGetter: () => defaultMarkerGetter,
deleteCommand: () => deleteCommand,
doSplitList: () => doSplitList,
enterCommand: () => enterCommand,
enterWithoutLift: () => enterWithoutLift,
findListsRange: () => findListsRange,
flatListGroup: () => flatListGroup,
getListType: () => getListType,
handleListMarkerMouseDown: () => handleListMarkerMouseDown,
isCollapsedListNode: () => isCollapsedListNode,
isListNode: () => isListNode,
isListType: () => isListType,
isListsRange: () => isListsRange,
joinCollapsedListBackward: () => joinCollapsedListBackward,
joinListElements: () => joinListElements,
joinListUp: () => joinListUp,
listInputRules: () => listInputRules,
listKeymap: () => listKeymap,
listToDOM: () => listToDOM,
migrateDocJSON: () => migrateDocJSON,
parseInteger: () => parseInteger,
protectCollapsed: () => protectCollapsed,
rangeToString: () => rangeToString,
setSafeSelection: () => setSafeSelection,
unwrapListSlice: () => unwrapListSlice,
wrappingListInputRule: () => wrappingListInputRule
});
module.exports = __toCommonJS(src_exports);
// src/commands/dedent-list.ts
var import_prosemirror_model2 = require("prosemirror-model");
var import_prosemirror_transform3 = require("prosemirror-transform");
// src/utils/auto-fix-list.ts
var import_prosemirror_transform = require("prosemirror-transform");
// src/utils/parse-integer.ts
function parseInteger(attr) {
if (attr == null) return null;
const int = Number.parseInt(attr, 10);
if (Number.isInteger(int)) return int;
return null;
}
// src/schema/parse-dom.ts
function createParseDomRules() {
return [
{
tag: "div[data-list-kind]",
getAttrs: (element) => {
if (typeof element === "string") {
return {};
}
return {
kind: element.getAttribute("data-list-kind") || "bullet",
order: parseInteger(element.getAttribute("data-list-order")),
checked: element.hasAttribute("data-list-checked"),
collapsed: element.hasAttribute("data-list-collapsed")
};
}
},
{
tag: "div[data-list]",
getAttrs: (element) => {
if (typeof element === "string") {
return {};
}
return {
kind: element.getAttribute("data-list-kind") || "bullet",
order: parseInteger(element.getAttribute("data-list-order")),
checked: element.hasAttribute("data-list-checked"),
collapsed: element.hasAttribute("data-list-collapsed")
};
}
},
{
tag: "ul > li",
getAttrs: (element) => {
var _a;
if (typeof element !== "string") {
let checkbox = element.firstChild;
for (let i = 0; i < 3 && checkbox; i++) {
if (["INPUT", "UL", "OL", "LI"].includes(checkbox.nodeName)) {
break;
}
checkbox = checkbox.firstChild;
}
if (checkbox && checkbox.nodeName === "INPUT" && checkbox.getAttribute("type") === "checkbox") {
return {
kind: "task",
checked: checkbox.hasAttribute("checked")
};
}
if (element.hasAttribute("data-task-list-item") || element.getAttribute("data-list-kind") === "task") {
return {
kind: "task",
checked: element.hasAttribute("data-list-checked") || element.hasAttribute("data-checked")
};
}
if (element.hasAttribute("data-toggle-list-item") || element.getAttribute("data-list-kind") === "toggle") {
return {
kind: "toggle",
collapsed: element.hasAttribute("data-list-collapsed")
};
}
if (((_a = element.firstChild) == null ? void 0 : _a.nodeType) === 3) {
const textContent = element.firstChild.textContent;
if (textContent && /^\[[\sx|]]\s{1,2}/.test(textContent)) {
element.firstChild.textContent = textContent.replace(
/^\[[\sx|]]\s{1,2}/,
""
);
return {
kind: "task",
checked: textContent.startsWith("[x]")
};
}
}
}
return {
kind: "bullet"
};
}
},
{
tag: "ol > li",
getAttrs: (element) => {
if (typeof element === "string") {
return {
kind: "ordered"
};
}
return {
kind: "ordered",
order: parseInteger(element.getAttribute("data-list-order"))
};
}
},
{
// This rule is for handling nested lists copied from Dropbox Paper. It's
// technically invalid HTML structure.
tag: ":is(ul, ol) > :is(ul, ol)",
getAttrs: () => {
return {
kind: "bullet"
};
}
}
];
}
// src/schema/to-dom.ts
function listToDOM(options) {
var _a;
const {
node,
nativeList = false,
getMarkers = defaultMarkerGetter,
getAttributes = defaultAttributesGetter
} = options;
const attrs = node.attrs;
const markerHidden = ((_a = node.firstChild) == null ? void 0 : _a.type) === node.type;
const markers = markerHidden ? null : getMarkers(node);
const domAttrs = getAttributes(node);
const contentContainer = ["div", { class: "list-content" }, 0];
const markerContainer = markers && [
"div",
{
class: "list-marker list-marker-click-target",
// Set `contenteditable` to `false` so that the cursor won't be
// moved into the mark container when clicking on it.
contenteditable: "false"
},
...markers
];
if (nativeList) {
const listTag = attrs.kind === "ordered" ? "ol" : "ul";
if (markerContainer) {
return [listTag, ["li", domAttrs, markerContainer, contentContainer]];
} else {
return [listTag, ["li", domAttrs, 0]];
}
} else {
if (markerContainer) {
return ["div", domAttrs, markerContainer, contentContainer];
} else {
return ["div", domAttrs, contentContainer];
}
}
}
function defaultMarkerGetter(node) {
const attrs = node.attrs;
switch (attrs.kind) {
case "task":
return [
[
"label",
[
"input",
{ type: "checkbox", checked: attrs.checked ? "" : void 0 }
]
]
];
case "toggle":
return [];
default:
return null;
}
}
function defaultAttributesGetter(node) {
var _a;
const attrs = node.attrs;
const markerHidden = ((_a = node.firstChild) == null ? void 0 : _a.type) === node.type;
const markerType = markerHidden ? void 0 : attrs.kind || "bullet";
const domAttrs = {
class: "prosemirror-flat-list",
"data-list-kind": markerType,
"data-list-order": attrs.order != null ? String(attrs.order) : void 0,
"data-list-checked": attrs.checked ? "" : void 0,
"data-list-collapsed": attrs.collapsed ? "" : void 0,
"data-list-collapsable": node.childCount >= 2 ? "" : void 0,
style: attrs.order != null ? `--prosemirror-flat-list-order: ${attrs.order};` : void 0
};
return domAttrs;
}
// src/schema/node-spec.ts
var flatListGroup = "flatList";
function createListSpec() {
return {
content: "block+",
group: `${flatListGroup} block`,
definingForContent: true,
definingAsContext: false,
attrs: {
kind: {
default: "bullet"
},
order: {
default: null
},
checked: {
default: false
},
collapsed: {
default: false
}
},
toDOM: (node) => {
return listToDOM({ node });
},
parseDOM: createParseDomRules()
};
}
// src/utils/get-list-type-name.ts
var key = "PROSEMIRROR_FLAT_LIST_TYPE_NAME";
function getListTypeName(schema) {
let name = schema.cached[key];
if (!name) {
for (const type of Object.values(schema.nodes)) {
if ((type.spec.group || "").split(" ").includes(flatListGroup)) {
name = type.name;
break;
}
}
if (!name) {
throw new TypeError(
"[prosemirror-flat-list] Unable to find a flat list type in the schema"
);
}
schema.cached[key] = name;
return name;
}
return name;
}
// src/utils/is-list-type.ts
function isListType(type) {
return getListTypeName(type.schema) === type.name;
}
// src/utils/is-list-node.ts
function isListNode(node) {
if (!node) return false;
return isListType(node.type);
}
// src/utils/patch-command.ts
function patchCommand(patch) {
const withPatch = (command) => {
const patchedCommand = (state, dispatch, view) => {
return command(
state,
dispatch ? (tr) => dispatch(patch(tr)) : void 0,
view
);
};
return patchedCommand;
};
return withPatch;
}
// src/utils/auto-fix-list.ts
function* getTransactionRanges(tr) {
const ranges = [];
let i = 0;
while (true) {
for (; i < tr.mapping.maps.length; i++) {
const map = tr.mapping.maps[i];
for (let j = 0; j < ranges.length; j++) {
ranges[j] = map.map(ranges[j]);
}
map.forEach(
(_oldStart, _oldEnd, newStart, newEnd) => ranges.push(newStart, newEnd)
);
}
yield ranges;
}
}
function findBoundaries(positions, doc, prediction) {
const boundaries = /* @__PURE__ */ new Set();
const joinable = [];
for (const pos of positions) {
const $pos = doc.resolve(pos);
for (let depth = $pos.depth; depth >= 0; depth--) {
const boundary = $pos.before(depth + 1);
if (boundaries.has(boundary)) {
break;
}
boundaries.add(boundary);
const index = $pos.index(depth);
const parent = $pos.node(depth);
const before = parent.maybeChild(index - 1);
if (!before) continue;
const after = parent.maybeChild(index);
if (!after) continue;
if (prediction(before, after, parent, index)) {
joinable.push(boundary);
}
}
}
return joinable.sort((a, b) => b - a);
}
function isListJoinable(before, after) {
return isListNode(before) && isListNode(after) && isListNode(after.firstChild);
}
function isListSplitable(before, after, parent, index) {
if (index === 1 && isListNode(parent) && isListNode(before) && !isListNode(after)) {
return true;
}
return false;
}
function fixList(tr) {
const ranges = getTransactionRanges(tr);
const joinable = findBoundaries(ranges.next().value, tr.doc, isListJoinable);
for (const pos of joinable) {
if ((0, import_prosemirror_transform.canJoin)(tr.doc, pos)) {
tr.join(pos);
}
}
const splitable = findBoundaries(ranges.next().value, tr.doc, isListSplitable);
for (const pos of splitable) {
if ((0, import_prosemirror_transform.canSplit)(tr.doc, pos)) {
tr.split(pos);
}
}
return tr;
}
var withAutoFixList = patchCommand(fixList);
// src/utils/block-boundary.ts
function atStartBlockBoundary($pos, depth) {
for (let d = depth; d <= $pos.depth; d++) {
if ($pos.node(d).isTextblock) {
continue;
}
const index = $pos.index(d);
if (index !== 0) {
return false;
}
}
return true;
}
function atEndBlockBoundary($pos, depth) {
for (let d = depth; d <= $pos.depth; d++) {
if ($pos.node(d).isTextblock) {
continue;
}
const index = $pos.index(d);
if (index !== $pos.node(d).childCount - 1) {
return false;
}
}
return true;
}
// src/utils/get-list-type.ts
function getListType(schema) {
return schema.nodes[getListTypeName(schema)];
}
// src/utils/list-range.ts
var import_prosemirror_model = require("prosemirror-model");
function findListsRange($from, $to = $from) {
if ($to.pos < $from.pos) {
return findListsRange($to, $from);
}
let range = $from.blockRange($to);
while (range) {
if (isListsRange(range)) {
return range;
}
if (range.depth <= 0) {
break;
}
range = new import_prosemirror_model.NodeRange($from, $to, range.depth - 1);
}
return null;
}
function isListsRange(range) {
const { startIndex, endIndex, parent } = range;
for (let i = startIndex; i < endIndex; i++) {
if (!isListNode(parent.child(i))) {
return false;
}
}
return true;
}
// src/utils/map-pos.ts
function mapPos(tr, pos) {
let nextStepIndex = tr.steps.length;
const getPos = () => {
if (nextStepIndex < tr.steps.length) {
const mapping = tr.mapping.slice(nextStepIndex);
nextStepIndex = tr.steps.length;
pos = mapping.map(pos);
}
return pos;
};
return getPos;
}
// src/utils/safe-lift.ts
var import_prosemirror_transform2 = require("prosemirror-transform");
function safeLift(tr, range) {
const target = (0, import_prosemirror_transform2.liftTarget)(range);
if (target == null) {
return false;
}
tr.lift(range, target);
return true;
}
function safeLiftFromTo(tr, from, to) {
const $from = tr.doc.resolve(from);
const $to = tr.doc.resolve(to);
const range = $from.blockRange($to);
if (!range) return false;
return safeLift(tr, range);
}
// src/utils/zoom-in-range.ts
function zoomInRange(range) {
const { $from, $to, depth, start, end } = range;
const doc = $from.doc;
const deeper = ($from.pos > start ? $from : doc.resolve(start + 1)).blockRange($to.pos < end ? $to : doc.resolve(end - 1));
if (deeper && deeper.depth > depth) {
return deeper;
}
return null;
}
// src/commands/set-safe-selection.ts
var import_prosemirror_state = require("prosemirror-state");
// src/utils/is-collapsed-list-node.ts
function isCollapsedListNode(node) {
return !!(isListNode(node) && node.attrs.collapsed);
}
// src/utils/set-node-attributes.ts
function setNodeAttributes(tr, pos, oldAttrs, newAttrs) {
let needUpdate = false;
for (const key2 of Object.keys(newAttrs)) {
if (newAttrs[key2] !== oldAttrs[key2]) {
tr.setNodeAttribute(pos, key2, newAttrs[key2]);
needUpdate = true;
}
}
return needUpdate;
}
// src/utils/set-list-attributes.ts
function setListAttributes(tr, pos, attrs) {
const $pos = tr.doc.resolve(pos);
const node = $pos.nodeAfter;
if (node && isListNode(node)) {
const oldAttrs = node.attrs;
const newAttrs = { ...oldAttrs, ...attrs };
return setNodeAttributes(tr, pos, oldAttrs, newAttrs);
}
return false;
}
// src/commands/set-safe-selection.ts
function moveOutOfCollapsed($pos, minDepth) {
for (let depth = minDepth; depth <= $pos.depth; depth++) {
if (isCollapsedListNode($pos.node(depth)) && $pos.index(depth) >= 1) {
const before = $pos.posAtIndex(1, depth);
const $before = $pos.doc.resolve(before);
return import_prosemirror_state.TextSelection.near($before, -1);
}
}
return null;
}
function setSafeSelection(tr) {
const { $from, $to, to } = tr.selection;
const selection = moveOutOfCollapsed($from, 0) || moveOutOfCollapsed($to, $from.sharedDepth(to));
if (selection) {
tr.setSelection(selection);
}
return tr;
}
var withSafeSelection = patchCommand(setSafeSelection);
function getCollapsedPosition($pos, minDepth) {
for (let depth = minDepth; depth <= $pos.depth; depth++) {
if (isCollapsedListNode($pos.node(depth)) && $pos.index(depth) >= 1) {
return $pos.before(depth);
}
}
return null;
}
function setVisibleSelection(tr) {
var _a;
const { $from, $to, to } = tr.selection;
const pos = (_a = getCollapsedPosition($from, 0)) != null ? _a : getCollapsedPosition($to, $from.sharedDepth(to));
if (pos != null) {
tr.doc.resolve(pos);
setListAttributes(tr, pos, { collapsed: false });
}
return tr;
}
var withVisibleSelection = patchCommand(setVisibleSelection);
// src/commands/dedent-list.ts
function createDedentListCommand(options) {
const dedentListCommand = (state, dispatch) => {
const tr = state.tr;
const $from = (options == null ? void 0 : options.from) == null ? tr.selection.$from : tr.doc.resolve(options.from);
const $to = (options == null ? void 0 : options.to) == null ? tr.selection.$to : tr.doc.resolve(options.to);
const range = findListsRange($from, $to);
if (!range) return false;
if (dedentRange(range, tr)) {
dispatch == null ? void 0 : dispatch(tr);
return true;
}
return false;
};
return withVisibleSelection(withAutoFixList(dedentListCommand));
}
function dedentRange(range, tr, startBoundary, endBoundary) {
const { depth, $from, $to } = range;
startBoundary = startBoundary || atStartBlockBoundary($from, depth + 1);
if (!startBoundary) {
const { startIndex, endIndex } = range;
if (endIndex - startIndex === 1) {
const contentRange = zoomInRange(range);
return contentRange ? dedentRange(contentRange, tr) : false;
} else {
return splitAndDedentRange(range, tr, startIndex + 1);
}
}
endBoundary = endBoundary || atEndBlockBoundary($to, depth + 1);
if (!endBoundary) {
fixEndBoundary(range, tr);
const endOfParent = $to.end(depth);
range = new import_prosemirror_model2.NodeRange(
tr.doc.resolve($from.pos),
tr.doc.resolve(endOfParent),
depth
);
return dedentRange(range, tr, void 0, true);
}
if (range.startIndex === 0 && range.endIndex === range.parent.childCount && isListNode(range.parent)) {
return dedentNodeRange(new import_prosemirror_model2.NodeRange($from, $to, depth - 1), tr);
}
return dedentNodeRange(range, tr);
}
function splitAndDedentRange(range, tr, splitIndex) {
const { $from, $to, depth } = range;
const splitPos = $from.posAtIndex(splitIndex, depth);
const range1 = $from.blockRange(tr.doc.resolve(splitPos - 1));
if (!range1) return false;
const getRange2From = mapPos(tr, splitPos + 1);
const getRange2To = mapPos(tr, $to.pos);
dedentRange(range1, tr, void 0, true);
let range2 = tr.doc.resolve(getRange2From()).blockRange(tr.doc.resolve(getRange2To()));
if (range2 && range2.depth >= depth) {
range2 = new import_prosemirror_model2.NodeRange(range2.$from, range2.$to, depth);
dedentRange(range2, tr, true, void 0);
}
return true;
}
function dedentNodeRange(range, tr) {
if (isListNode(range.parent)) {
return safeLiftRange(tr, range);
} else if (isListsRange(range)) {
return dedentOutOfList(tr, range);
} else {
return safeLiftRange(tr, range);
}
}
function safeLiftRange(tr, range) {
if (moveRangeSiblings(tr, range)) {
const $from = tr.doc.resolve(range.$from.pos);
const $to = tr.doc.resolve(range.$to.pos);
range = new import_prosemirror_model2.NodeRange($from, $to, range.depth);
}
return safeLift(tr, range);
}
function moveRangeSiblings(tr, range) {
const listType = getListType(tr.doc.type.schema);
const { $to, depth, end, parent, endIndex } = range;
const endOfParent = $to.end(depth);
if (end < endOfParent) {
const lastChild = parent.maybeChild(endIndex - 1);
if (!lastChild) return false;
const canAppend = endIndex < parent.childCount && lastChild.canReplace(
lastChild.childCount,
lastChild.childCount,
parent.content,
endIndex,
parent.childCount
);
if (canAppend) {
tr.step(
new import_prosemirror_transform3.ReplaceAroundStep(
end - 1,
endOfParent,
end,
endOfParent,
new import_prosemirror_model2.Slice(import_prosemirror_model2.Fragment.from(listType.create(null)), 1, 0),
0,
true
)
);
return true;
} else {
tr.step(
new import_prosemirror_transform3.ReplaceAroundStep(
end,
endOfParent,
end,
endOfParent,
new import_prosemirror_model2.Slice(import_prosemirror_model2.Fragment.from(listType.create(null)), 0, 0),
1,
true
)
);
return true;
}
}
return false;
}
function fixEndBoundary(range, tr) {
if (range.endIndex - range.startIndex >= 2) {
range = new import_prosemirror_model2.NodeRange(
range.$to.doc.resolve(
range.$to.posAtIndex(range.endIndex - 1, range.depth)
),
range.$to,
range.depth
);
}
const contentRange = zoomInRange(range);
if (contentRange) {
fixEndBoundary(contentRange, tr);
range = new import_prosemirror_model2.NodeRange(
tr.doc.resolve(range.$from.pos),
tr.doc.resolve(range.$to.pos),
range.depth
);
}
moveRangeSiblings(tr, range);
}
function dedentOutOfList(tr, range) {
const { startIndex, endIndex, parent } = range;
const getRangeStart = mapPos(tr, range.start);
const getRangeEnd = mapPos(tr, range.end);
for (let end2 = getRangeEnd(), i = endIndex - 1; i > startIndex; i--) {
end2 -= parent.child(i).nodeSize;
tr.delete(end2 - 1, end2 + 1);
}
const $start = tr.doc.resolve(getRangeStart());
const listNode = $start.nodeAfter;
if (!listNode) return false;
const start = range.start;
const end = start + listNode.nodeSize;
if (getRangeEnd() !== end) return false;
if (!$start.parent.canReplace(
startIndex,
startIndex + 1,
import_prosemirror_model2.Fragment.from(listNode)
)) {
return false;
}
tr.step(
new import_prosemirror_transform3.ReplaceAroundStep(
start,
end,
start + 1,
end - 1,
new import_prosemirror_model2.Slice(import_prosemirror_model2.Fragment.empty, 0, 0),
0,
true
)
);
return true;
}
// src/commands/enter-without-lift.ts
var import_prosemirror_commands = require("prosemirror-commands");
var enterWithoutLift = (0, import_prosemirror_commands.chainCommands)(
import_prosemirror_commands.newlineInCode,
import_prosemirror_commands.createParagraphNear,
import_prosemirror_commands.splitBlock
);
// src/commands/indent-list.ts
var import_prosemirror_model3 = require("prosemirror-model");
var import_prosemirror_transform4 = require("prosemirror-transform");
// src/utils/in-collapsed-list.ts
function inCollapsedList($pos) {
for (let depth = $pos.depth; depth >= 0; depth--) {
const node = $pos.node(depth);
if (isListNode(node)) {
const attrs = node.attrs;
if (attrs.collapsed) {
return true;
}
}
}
return false;
}
// src/commands/indent-list.ts
function createIndentListCommand(options) {
const indentListCommand = (state, dispatch) => {
const tr = state.tr;
const $from = (options == null ? void 0 : options.from) == null ? tr.selection.$from : tr.doc.resolve(options.from);
const $to = (options == null ? void 0 : options.to) == null ? tr.selection.$to : tr.doc.resolve(options.to);
const range = findListsRange($from, $to) || $from.blockRange($to);
if (!range) return false;
if (indentRange(range, tr)) {
dispatch == null ? void 0 : dispatch(tr);
return true;
}
return false;
};
return withVisibleSelection(withAutoFixList(indentListCommand));
}
function indentRange(range, tr, startBoundary, endBoundary) {
const { depth, $from, $to } = range;
startBoundary = startBoundary || atStartBlockBoundary($from, depth + 1);
if (!startBoundary) {
const { startIndex, endIndex } = range;
if (endIndex - startIndex === 1) {
const contentRange = zoomInRange(range);
return contentRange ? indentRange(contentRange, tr) : false;
} else {
return splitAndIndentRange(range, tr, startIndex + 1);
}
}
endBoundary = endBoundary || atEndBlockBoundary($to, depth + 1);
if (!endBoundary && !inCollapsedList($to)) {
const { startIndex, endIndex } = range;
if (endIndex - startIndex === 1) {
const contentRange = zoomInRange(range);
return contentRange ? indentRange(contentRange, tr) : false;
} else {
return splitAndIndentRange(range, tr, endIndex - 1);
}
}
return indentNodeRange(range, tr);
}
function splitAndIndentRange(range, tr, splitIndex) {
const { $from, $to, depth } = range;
const splitPos = $from.posAtIndex(splitIndex, depth);
const range1 = $from.blockRange(tr.doc.resolve(splitPos - 1));
if (!range1) return false;
const getRange2From = mapPos(tr, splitPos + 1);
const getRange2To = mapPos(tr, $to.pos);
indentRange(range1, tr, void 0, true);
const range2 = tr.doc.resolve(getRange2From()).blockRange(tr.doc.resolve(getRange2To()));
if (range2) {
indentRange(range2, tr, true, void 0);
}
return true;
}
function indentNodeRange(range, tr) {
const listType = getListType(tr.doc.type.schema);
const { parent, startIndex } = range;
const prevChild = startIndex >= 1 && parent.child(startIndex - 1);
if (prevChild && isListNode(prevChild)) {
const { start, end } = range;
tr.step(
new import_prosemirror_transform4.ReplaceAroundStep(
start - 1,
end,
start,
end,
new import_prosemirror_model3.Slice(import_prosemirror_model3.Fragment.from(listType.create(null)), 1, 0),
0,
true
)
);
return true;
}
const isParentListNode = isListNode(parent);
const isFirstChildListNode = isListNode(parent.maybeChild(startIndex));
if (startIndex === 0 && isParentListNode || isFirstChildListNode) {
const { start, end } = range;
const listAttrs = isFirstChildListNode ? parent.child(startIndex).attrs : isParentListNode ? parent.attrs : null;
tr.step(
new import_prosemirror_transform4.ReplaceAroundStep(
start,
end,
start,
end,
new import_prosemirror_model3.Slice(import_prosemirror_model3.Fragment.from(listType.create(listAttrs)), 0, 0),
1,
true
)
);
return true;
}
return false;
}
// src/commands/join-collapsed-backward.ts
var import_prosemirror_state3 = require("prosemirror-state");
// src/utils/at-textblock-start.ts
function atTextblockStart(state, view) {
const { $cursor } = state.selection;
if (!$cursor || (view ? !view.endOfTextblock("backward", state) : $cursor.parentOffset > 0))
return null;
return $cursor;
}
// src/commands/join-textblocks-around.ts
var import_prosemirror_model4 = require("prosemirror-model");
var import_prosemirror_state2 = require("prosemirror-state");
var import_prosemirror_transform5 = require("prosemirror-transform");
function joinTextblocksAround(tr, $cut, dispatch) {
let before = $cut.nodeBefore, beforeText = before, beforePos = $cut.pos - 1;
for (; !beforeText.isTextblock; beforePos--) {
if (beforeText.type.spec.isolating) return false;
let child = beforeText.lastChild;
if (!child) return false;
beforeText = child;
}
let after = $cut.nodeAfter, afterText = after, afterPos = $cut.pos + 1;
for (; !afterText.isTextblock; afterPos++) {
if (afterText.type.spec.isolating) return false;
let child = afterText.firstChild;
if (!child) return false;
afterText = child;
}
let step = (0, import_prosemirror_transform5.replaceStep)(tr.doc, beforePos, afterPos, import_prosemirror_model4.Slice.empty);
if (!step || step.from != beforePos || step instanceof import_prosemirror_transform5.ReplaceStep && step.slice.size >= afterPos - beforePos) return false;
if (dispatch) {
tr.step(step);
tr.setSelection(import_prosemirror_state2.TextSelection.create(tr.doc, beforePos));
dispatch(tr.scrollIntoView());
}
return true;
}
// src/commands/join-collapsed-backward.ts
var joinCollapsedListBackward = (state, dispatch, view) => {
const $cursor = atTextblockStart(state, view);
if (!$cursor) return false;
const $cut = findCutBefore($cursor);
if (!$cut) return false;
const { nodeBefore, nodeAfter } = $cut;
if (nodeBefore && nodeAfter && isListNode(nodeBefore) && nodeBefore.attrs.collapsed && nodeAfter.isBlock) {
const tr = state.tr;
const listPos = $cut.pos - nodeBefore.nodeSize;
tr.delete($cut.pos, $cut.pos + nodeAfter.nodeSize);
const insert = listPos + 1 + nodeBefore.child(0).nodeSize;
tr.insert(insert, nodeAfter);
const $insert = tr.doc.resolve(insert);
tr.setSelection(import_prosemirror_state3.TextSelection.near($insert));
if (joinTextblocksAround(tr, $insert, dispatch)) {
return true;
}
}
return false;
};
function findCutBefore($pos) {
if (!$pos.parent.type.spec.isolating)
for (let i = $pos.depth - 1; i >= 0; i--) {
if ($pos.index(i) > 0) return $pos.doc.resolve($pos.before(i + 1));
if ($pos.node(i).type.spec.isolating) break;
}
return null;
}
// src/commands/join-list-up.ts
var import_prosemirror_model5 = require("prosemirror-model");
var joinListUp = (state, dispatch, view) => {
const $cursor = atTextblockStart(state, view);
if (!$cursor) return false;
const { depth } = $cursor;
if (depth < 2) return false;
const listDepth = depth - 1;
const listNode = $cursor.node(listDepth);
if (!isListNode(listNode)) return false;
const indexInList = $cursor.index(listDepth);
if (indexInList === 0) {
if (dispatch) {
liftListContent(state, dispatch, $cursor);
}
return true;
}
if (indexInList === listNode.childCount - 1) {
if (dispatch) {
liftParent(state, dispatch, $cursor);
}
return true;
}
return false;
};
function liftListContent(state, dispatch, $cursor) {
const tr = state.tr;
const listDepth = $cursor.depth - 1;
const range = new import_prosemirror_model5.NodeRange(
$cursor,
tr.doc.resolve($cursor.end(listDepth)),
listDepth
);
if (safeLift(tr, range)) {
dispatch(tr);
}
}
function liftParent(state, dispatch, $cursor) {
const tr = state.tr;
const range = $cursor.blockRange();
if (range && safeLift(tr, range)) {
dispatch(tr);
}
}
// src/commands/keymap.ts
var import_prosemirror_commands3 = require("prosemirror-commands");
// src/commands/protect-collapsed.ts
var protectCollapsed = (state, dispatch) => {
const tr = state.tr;
let found = false;
const { from, to } = state.selection;
state.doc.nodesBetween(from, to, (node, pos, parent, index) => {
if (found && !dispatch) {
return false;
}
if (parent && isCollapsedListNode(parent) && index >= 1) {
found = true;
if (!dispatch) {
return false;
}
const $pos = state.doc.resolve(pos);
tr.setNodeAttribute($pos.before($pos.depth), "collapsed", false);
}
});
if (found) {
dispatch == null ? void 0 : dispatch(tr);
}
return found;
};
// src/commands/split-list.ts
var import_prosemirror_commands2 = require("prosemirror-commands");
var import_prosemirror_model6 = require("prosemirror-model");
var import_prosemirror_state5 = require("prosemirror-state");
var import_prosemirror_transform6 = require("prosemirror-transform");
// src/utils/create-and-fill.ts
function createAndFill(type, attrs, content, marks) {
const node = type.createAndFill(attrs, content, marks);
if (!node) {
throw new RangeError(`Failed to create '${type.name}' node`);
}
node.check();
return node;
}
// src/utils/is-node-selection.ts
function isNodeSelection(selection) {
return Boolean(selection.node);
}
// src/utils/is-block-node-selection.ts
function isBlockNodeSelection(selection) {
return isNodeSelection(selection) && selection.node.type.isBlock;
}
// src/utils/is-text-selection.ts
var import_prosemirror_state4 = require("prosemirror-state");
function isTextSelection(value) {
return Boolean(value && value instanceof import_prosemirror_state4.TextSelection);
}
// src/commands/split-list.ts
function createSplitListCommand() {
return withAutoFixList(
(0, import_prosemirror_commands2.chainCommands)(splitBlockNodeSelectionInListCommand, splitListCommand)
);
}
function deriveListAttributes(listNode) {
return { kind: listNode.attrs.kind };
}
var splitBlockNodeSelectionInListCommand = (state, dispatch) => {
if (!isBlockNodeSelection(state.selection)) {
return false;
}
const selection = state.selection;
const { $to, node } = selection;
const parent = $to.parent;
if (isListNode(node) || !isListNode(parent) || parent.childCount !== 1 || parent.firstChild !== node) {
return false;
}
const listType = parent.type;
const nextList = listType.createAndFill(deriveListAttributes(parent));
if (!nextList) {
return false;
}
if (dispatch) {
const tr = state.tr;
const cutPoint = $to.pos;
tr.replace(
cutPoint,
cutPoint,
new import_prosemirror_model6.Slice(import_prosemirror_model6.Fragment.fromArray([listType.create(), nextList]), 1, 1)
);
const newSelection = import_prosemirror_state5.TextSelection.near(tr.doc.resolve(cutPoint));
if (isTextSelection(newSelection)) {
tr.setSelection(newSelection);
dispatch(tr);
}
}
return true;
};
var splitListCommand = (state, dispatch) => {
if (isBlockNodeSelection(state.selection)) {
return false;
}
const { $from, $to } = state.selection;
if (!$from.sameParent($to)) {
return false;
}
if ($from.depth < 2) {
return false;
}
const listDepth = $from.depth - 1;
const listNode = $from.node(listDepth);
if (!isListNode(listNode)) {
return false;
}
const parent = $from.parent;
const indexInList = $from.index(listDepth);
const parentEmpty = parent.content.size === 0;
if (indexInList === 0) {
if (parentEmpty) {
const $listEnd = state.doc.resolve($from.end(listDepth));
const listParentDepth = listDepth - 1;
const listParent = $from.node(listParentDepth);
const indexInListParent = $from.index(listParentDepth);
const isLastChildInListParent = indexInListParent === listParent.childCount - 1;
const range = isLastChildInListParent ? new import_prosemirror_model6.NodeRange($from, $listEnd, listParentDepth) : new import_prosemirror_model6.NodeRange($from, $listEnd, listDepth);
const tr = state.tr;
if (range && dedentNodeRange(range, tr)) {
dispatch == null ? void 0 : dispatch(tr);
return true;
}
return false;
} else {
return doSplitList(state, listNode, dispatch);
}
} else {
if (parentEmpty) {
return enterWithoutLift(state, dispatch);
} else {
return false;
}
}
};
function doSplitList(state, listNode, dispatch) {
const tr = state.tr;
const listType = listNode.type;
const attrs = listNode.attrs;
const newAttrs = deriveListAttributes(listNode);
tr.delete(tr.selection.from, tr.selection.to);
const { $from, $to } = tr.selection;
const { parentOffset } = $to;
const atStart = parentOffset == 0;
const atEnd = parentOffset == $to.parent.content.size;
if (atStart) {
if (dispatch) {
const pos = $from.before(-1);
tr.insert(pos, createAndFill(listType, newAttrs));
dispatch(tr.scrollIntoView());
}
return true;
}
if (atEnd && attrs.collapsed) {
if (dispatch) {
const pos = $from.after(-1);
tr.insert(pos, createAndFill(listType, newAttrs));
tr.setSelection(import_prosemirror_state5.Selection.near(tr.doc.resolve(pos)));
dispatch(tr.scrollIntoView());
}
return true;
}
const nextType = atEnd ? listNode.contentMatchAt(0).defaultType : void 0;
const typesAfter = [
{ type: listType, attrs: newAttrs },
nextType ? { type: nextType } : null
];
if (!(0, import_prosemirror_transform6.canSplit)(tr.doc, $from.pos, 2, typesAfter)) {
return false;
}
dispatch == null ? void 0 : dispatch(tr.split($from.pos, 2, typesAfter).scrollIntoView());
return true;
}
// src/commands/keymap.ts
var enterCommand = (0, import_prosemirror_commands3.chainCommands)(
protectCollapsed,
createSplitListCommand()
);
var backspaceCommand = (0, import_prosemirror_commands3.chainCommands)(
protectCollapsed,
import_prosemirror_commands3.deleteSelection,
joinListUp,
joinCollapsedListBackward,
import_prosemirror_commands3.joinTextblockBackward,
import_prosemirror_commands3.selectNodeBackward
);
var deleteCommand = (0, import_prosemirror_commands3.chainCommands)(
protectCollapsed,
import_prosemirror_commands3.deleteSelection,
import_prosemirror_commands3.joinTextblockForward,
import_prosemirror_commands3.selectNodeForward
);
var listKeymap = {
Enter: enterCommand,
Backspace: backspaceCommand,
Delete: deleteCommand,
"Mod-[": createDedentListCommand(),
"Mod-]": createIndentListCommand()
};
// src/utils/cut-by-index.ts
function cutByIndex(fragment, from, to) {
return fragment.cutByIndex(from, to);
}
// src/commands/move-list.ts
function createMoveListCommand(direction) {
const moveList = (state, dispatch) => {
const tr = state.tr;
if (doMoveList(tr, direction, true, !!dispatch)) {
dispatch == null ? void 0 : dispatch(tr);
return true;
}
return false;
};
return withAutoFixList(moveList);
}
function doMoveList(tr, direction, canDedent, dispatch) {
const { $from, $to } = tr.selection;
const range = findListsRange($from, $to);
if (!range) return false;
const { parent, depth, startIndex, endIndex } = range;
if (direction === "up") {
if (startIndex >= 2 || startIndex === 1 && isListNode(parent.child(0))) {
const before = cutByIndex(parent.content, startIndex - 1, startIndex);
const selected = cutByIndex(parent.content, startIndex, endIndex);
if (parent.canReplace(startIndex - 1, endIndex, selected.append(before))) {
if (dispatch) {
tr.insert($from.posAtIndex(endIndex, depth), before);
tr.delete(
$from.posAtIndex(startIndex - 1, depth),
$from.posAtIndex(startIndex, depth)
);
}
return true;
} else {
return false;
}
} else if (canDedent && isListNode(parent)) {
return safeLift(tr, range) && doMoveList(tr, direction, false, dispatch);
} else {
return false;
}
} else {
if (endIndex < parent.childCount) {
const selected = cutByIndex(parent.content, startIndex, endIndex);
const after = cutByIndex(parent.content, endIndex, endIndex + 1);
if (parent.canReplace(startIndex, endIndex + 1, after.append(selected))) {
if (dispatch) {
tr.delete(
$from.posAtIndex(endIndex, depth),
$from.posAtIndex(endIndex + 1, depth)
);
tr.insert($from.posAtIndex(startIndex, depth), after);
}
return true;
} else {
return false;
}
} else if (canDedent && isListNode(parent)) {
return safeLift(tr, range) && doMoveList(tr, direction, false, dispatch);
} else {
return false;
}
}
}
// src/commands/toggle-collapsed.ts
function createToggleCollapsedCommand(options = {}) {
const { collapsed = void 0, isToggleable = defaultIsToggleable } = options;
const toggleCollapsed = (state, dispatch) => {
const { $from } = state.selection;
for (let depth = $from.depth; depth >= 0; depth--) {
const node = $from.node(depth);
if (isListNode(node) && isToggleable(node)) {
if (dispatch) {
const pos = $from.before(depth);
const attrs = node.attrs;
const tr = state.tr;
tr.setNodeAttribute(pos, "collapsed", collapsed != null ? collapsed : !attrs.collapsed);
dispatch(setSafeSelection(tr));
}
return true;
}
}
return false;
};
return toggleCollapsed;
}
function defaultIsToggleable(node) {
const attrs = node.attrs;
return attrs.kind === "toggle" && node.childCount >= 2 && !isListNode(node.firstChild);
}
// src/commands/toggle-list.ts
var import_prosemirror_commands4 = require("prosemirror-commands");
// src/commands/unwrap-list.ts
function createUnwrapListCommand(options) {
const kind = options == null ? void 0 : options.kind;
const unwrapList = (state, dispatch) => {
const selection = state.selection;
if (isNodeSelection(selection) && isTargetList(selection.node, kind)) {
if (dispatch) {
const tr = state.tr;
safeLiftFromTo(tr, tr.selection.from + 1, tr.selection.to - 1);
dispatch(tr.scrollIntoView());
}
return true;
}
const range = selection.$from.blockRange(selection.$to);
if (range && isTargetListsRange(range, kind)) {
const tr = state.tr;
if (dedentOutOfList(tr, range)) {
dispatch == null ? void 0 : dispatch(tr);
return true;
}
}
if (range && isTargetList(range.parent, kind)) {
if (dispatch) {
const tr = state.tr;
safeLiftFromTo(
tr,
range.$from.start(range.depth),
range.$to.end(range.depth)
);
dispatch(tr.scrollIntoView());
}
return true;
}
return false;
};
return unwrapList;
}
function isTargetList(node, kind) {
if (isListNode(node)) {
if (kind) {
return node.attrs.kind === kind;
}
return true;
}
return false;
}
function isTargetListsRange(range, kind) {
const { startIndex, endIndex, parent } = range;
for (let i = startIndex; i < endIndex; i++) {
if (!isTargetList(parent.child(i), kind)) {
return false;
}
}
return true;
}
// src/commands/wrap-in-list.ts
var import_prosemirror_model7 = require("prosemirror-model");
var import_prosemirror_transform7 = require("prosemirror-transform");
function createWrapInListCommand(getAttrs) {
const wrapInList = (state, dispatch) => {
const { $from, $to } = state.selection;
let range = $from.blockRange($to);
if (!range) {
return false;
}
if (rangeAllowInlineContent(range) && isListNode(range.parent) && range.depth > 0 && range.startIndex === 0) {
range = new import_prosemirror_model7.NodeRange($from, $to, range.depth - 1);
}
const attrs = typeof getAttrs === "function" ? getAttrs(range) : getAttrs;
if (!attrs) {
return false;
}
const { parent, startIndex, endIndex, depth } = range;
const tr = state.tr;
const listType = getListType(state.schema);
for (let i = endIndex - 1; i >= startIndex; i--) {
const node = parent.child(i);
if (isListNode(node)) {
const oldAttrs = node.attrs;
const newAttrs = { ...oldAttrs, ...attrs };
setNodeAttributes(tr, $from.posAtIndex(i, depth), oldAttrs, newAttrs);
} else {
const beforeNode = $from.posAtIndex(i, depth);
const afterNode = $from.posAtIndex(i + 1, depth);
let nodeStart = beforeNode + 1;
let nodeEnd = afterNode - 1;
if (nodeStart > nodeEnd) {
;
[nodeStart, nodeEnd] = [nodeEnd, nodeStart];
}
const range2 = new import_prosemirror_model7.NodeRange(
tr.doc.resolve(nodeStart),
tr.doc.resolve(nodeEnd),
depth
);
const wrapping = (0, import_prosemirror_transform7.findWrapping)(range2, listType, attrs);
if (wrapping) {
tr.wrap(range2, wrapping);
}
}
}
dispatch == null ? void 0 : dispatch(tr);
return true;
};
return wrapInList;
}
function rangeAllowInlineContent(range) {
const { parent, startIndex, endIndex } = range;
for (let i = startIndex; i < endIndex; i++) {
if (parent.child(i).inlineContent) {
return true;
}
}
return false;
}
// src/commands/toggle-list.ts
function createToggleListCommand(attrs) {
const unwrapList = createUnwrapListCommand({ kind: attrs.kind });
const wrapInList = createWrapInListCommand(attrs);
return (0, import_prosemirror_commands4.chainCommands)(unwrapList, wrapInList);
}
// src/dom-events.ts
function handleListMarkerMouseDown({
view,
event,
onListClick = defaultListClickHandler
}) {
const target = event.target;
if (target == null ? void 0 : target.closest(".list-marker-click-target")) {
event.preventDefault();
const pos = view.posAtDOM(target, -10, -10);
return handleMouseDown(pos, onListClick)(
view.state,
(tr) => view.dispatch(tr)
);
}
return false;
}
function handleMouseDown(pos, onListClick) {
const mouseDown = (state, dispatch) => {
const tr = state.tr;
const $pos = tr.doc.resolve(pos);
const list = $pos.parent;
if (!isListNode(list)) {
return false;
}
const listPos = $pos.before($pos.depth);
const attrs = onListClick(list);
if (setNodeAttributes(tr, listPos, list.attrs, attrs)) {
dispatch == null ? void 0 : dispatch(tr);
}
return true;
};
return withSafeSelection(mouseDown);
}
var defaultListClickHandler = (node) => {
const attrs = node.attrs;
if (attrs.kind === "task") {
return { ...attrs, checked: !attrs.checked };
} else if (attrs.kind === "toggle") {
return { ...attrs, collapsed: !attrs.collapsed };
} else {
return attrs;
}
};
// src/input-rule.ts
var import_prosemirror_inputrules = require("prosemirror-inputrules");
var import_prosemirror_transform8 = require("prosemirror-transform");
function wrappingListInputRule(regexp, getAttrs) {
return new import_prosemirror_inputrules.InputRule(
regexp,
(state, match, start, end) => {
const tr = state.tr;
tr.deleteRange(start, end);
const $pos = tr.selection.$from;
const listNode = $pos.index(-1) === 0 && $pos.node(-1);
if (listNode && isListNode(listNode)) {
const oldAttrs = listNode.attrs;
const newAttrs2 = typeof getAttrs === "function" ? getAttrs({ match, attributes: oldAttrs }) : getAttrs;
const entries = Object.entries(newAttrs2).filter(([key2, value]) => {
return oldAttrs[key2] !== value;
});
if (entries.length === 0) {
return null;
} else {
const pos = $pos.before(-1);
for (const [key2, value] of entries) {
tr.setNodeAttribute(pos, key2, value);
}
return tr;
}
}
const $start = tr.doc.resolve(start);
const range = $start.blockRange();
if (!range) {
return null;
}
const newAttrs = typeof getAttrs === "function" ? getAttrs({ match }) : getAttrs;
const wrapping = (0, import_prosemirror_transform8.findWrapping)(range, getListType(state.schema), newAttrs);
if (!wrapping) {
return null;
}
return tr.wrap(range, wrapping);
}
);
}
var listInputRules = [
wrappingListInputRule(/^\s?([*-])\s$/, {
kind: "bullet",
collapsed: false
}),
wrappingListInputRule(/^\s?(\d+)\.\s$/, ({ match }) => {
const order = parseInteger(match[1]);
return {
kind: "ordered",
collapsed: false,
order: order != null && order >= 2 ? order : null
};
}),
wrappingListInputRule(/^\s?\[([\sXx]?)]\s$/, ({ match }) => {
return {
kind: "task",
checked: ["x", "X"].includes(match[1]),
collapsed: false
};
}),
wrappingListInputRule(/^\s?>>\s$/, {
kind: "toggle"
})
];
// src/migrate.ts
function migrateNodes(nodes) {
var _a, _b, _c;
const content = [];
let updated = false;
for (const node of nodes) {
if (node.type === "bullet_list" || node.type === "bulletList") {
updated = true;
for (const child of (_a = node.content) != null ? _a : []) {
content.push(migrateNode(child, { kind: "bullet" })[0]);
}
} else if (node.type === "ordered_list" || node.type === "orderedList") {
updated = true;
for (const child of (_b = node.content) != null ? _b : []) {
content.push(migrateNode(child, { kind: "ordered" })[0]);
}
} else if (node.type === "task_list" || node.type === "taskList") {
updated = true;
for (const child of (_c = node.content) != null ? _c : []) {
content.push(migrateNode(child, { kind: "task" })[0]);
}
} else {
content.push(node);
}
}
return [content, updated];
}
function migrateNode(node, { kind } = {}) {
var _a;
if (node.type === "list_item" || node.type === "listItem" || node.type === "taskListItem") {
return [
{
...node,
type: "list",
attrs: {
collapsed: Boolean((_a = node.attrs) == null ? void 0 : _a.closed),
...node.attrs,
kind: kind != null ? kind : "bullet"
},
content: node.content ? migrateNod