@domternal/core 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -10
- package/dist/index.cjs +1409 -218
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1795 -1335
- package/dist/index.d.ts +1795 -1335
- package/dist/index.js +1388 -218
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Plugin, PluginKey,
|
|
1
|
+
import { Plugin, PluginKey, TextSelection, Selection, NodeSelection, AllSelection, EditorState } from '@domternal/pm/state';
|
|
2
2
|
export { PluginKey } from '@domternal/pm/state';
|
|
3
3
|
import { DecorationSet, Decoration, EditorView } from '@domternal/pm/view';
|
|
4
4
|
import { Slice, Fragment, Schema, Node as Node$1, DOMSerializer, DOMParser } from '@domternal/pm/model';
|
|
5
5
|
import { keymap } from '@domternal/pm/keymap';
|
|
6
|
-
import { chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock,
|
|
6
|
+
import { splitBlock, chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, baseKeymap, selectNodeBackward as selectNodeBackward$1 } from '@domternal/pm/commands';
|
|
7
7
|
import { InputRule } from '@domternal/pm/inputrules';
|
|
8
|
-
import {
|
|
8
|
+
import { canJoin, findWrapping, liftTarget } from '@domternal/pm/transform';
|
|
9
9
|
import { liftListItem, sinkListItem, splitListItem, wrapRangeInList } from '@domternal/pm/schema-list';
|
|
10
10
|
import { offset, flip, shift, autoUpdate, hide, computePosition } from '@floating-ui/dom';
|
|
11
11
|
import { find } from 'linkifyjs';
|
|
@@ -270,6 +270,10 @@ var ExtensionManager = class {
|
|
|
270
270
|
* Cached toolbar items (collected lazily)
|
|
271
271
|
*/
|
|
272
272
|
_toolbarItems = null;
|
|
273
|
+
/**
|
|
274
|
+
* Cached floating-menu items (collected lazily)
|
|
275
|
+
*/
|
|
276
|
+
_floatingMenuItems = null;
|
|
273
277
|
/**
|
|
274
278
|
* Cached node views (collected lazily)
|
|
275
279
|
*/
|
|
@@ -343,6 +347,14 @@ var ExtensionManager = class {
|
|
|
343
347
|
this._toolbarItems ??= this.collectToolbarItems();
|
|
344
348
|
return this._toolbarItems;
|
|
345
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Gets floating-menu items from all extensions
|
|
352
|
+
* Cached after first call
|
|
353
|
+
*/
|
|
354
|
+
get floatingMenuItems() {
|
|
355
|
+
this._floatingMenuItems ??= this.collectFloatingMenuItems();
|
|
356
|
+
return this._floatingMenuItems;
|
|
357
|
+
}
|
|
346
358
|
/**
|
|
347
359
|
* Gets node views from all Node extensions that define addNodeView
|
|
348
360
|
*/
|
|
@@ -359,6 +371,7 @@ var ExtensionManager = class {
|
|
|
359
371
|
this._plugins = null;
|
|
360
372
|
this._commands = null;
|
|
361
373
|
this._toolbarItems = null;
|
|
374
|
+
this._floatingMenuItems = null;
|
|
362
375
|
this._nodeViews = null;
|
|
363
376
|
}
|
|
364
377
|
// === Extension Processing ===
|
|
@@ -405,7 +418,7 @@ var ExtensionManager = class {
|
|
|
405
418
|
});
|
|
406
419
|
}
|
|
407
420
|
/**
|
|
408
|
-
* Detects duplicate extension names
|
|
421
|
+
* Detects duplicate extension names.
|
|
409
422
|
* @throws Error if duplicate names found
|
|
410
423
|
*/
|
|
411
424
|
detectConflicts() {
|
|
@@ -706,6 +719,25 @@ var ExtensionManager = class {
|
|
|
706
719
|
}
|
|
707
720
|
return items;
|
|
708
721
|
}
|
|
722
|
+
/**
|
|
723
|
+
* Collects floating-menu items from all extensions via `addFloatingMenuItems()`.
|
|
724
|
+
*/
|
|
725
|
+
collectFloatingMenuItems() {
|
|
726
|
+
const items = [];
|
|
727
|
+
for (const ext of this._extensions) {
|
|
728
|
+
const addItems = ext.config.addFloatingMenuItems;
|
|
729
|
+
if (addItems) {
|
|
730
|
+
const extItems = this.safeCall(
|
|
731
|
+
() => callOrReturn(addItems, ext),
|
|
732
|
+
`${ext.name}.addFloatingMenuItems`
|
|
733
|
+
);
|
|
734
|
+
if (extItems && extItems.length > 0) {
|
|
735
|
+
items.push(...extItems);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return items;
|
|
740
|
+
}
|
|
709
741
|
/**
|
|
710
742
|
* Collects node views from all Node extensions.
|
|
711
743
|
* Returns a map of node name to NodeViewConstructor for EditorView.
|
|
@@ -1476,7 +1508,7 @@ var Extension = class _Extension {
|
|
|
1476
1508
|
* // Shallow merge behavior with nested objects:
|
|
1477
1509
|
* // Given: options = { nested: { a: 1, b: 2 } }
|
|
1478
1510
|
* // configure({ nested: { b: 3 } })
|
|
1479
|
-
* // Result: { nested: { b: 3 } }
|
|
1511
|
+
* // Result: { nested: { b: 3 } } - 'a' is lost!
|
|
1480
1512
|
* // To preserve nested values, spread manually:
|
|
1481
1513
|
* // configure({ nested: { ...original.options.nested, b: 3 } })
|
|
1482
1514
|
*/
|
|
@@ -1630,25 +1662,12 @@ var Mark = class _Mark extends Extension {
|
|
|
1630
1662
|
return new _Mark(config);
|
|
1631
1663
|
}
|
|
1632
1664
|
/**
|
|
1633
|
-
* Creates a new mark with merged options
|
|
1634
|
-
*
|
|
1635
|
-
*
|
|
1636
|
-
* **Note:** Options are merged shallowly using object spread (`...`).
|
|
1637
|
-
* Nested objects are replaced entirely, not deeply merged.
|
|
1638
|
-
*
|
|
1639
|
-
* @param options - Options to merge with existing options
|
|
1640
|
-
* @returns New mark instance with merged options
|
|
1665
|
+
* Creates a new mark with merged options. Original mark is not modified.
|
|
1666
|
+
* Options merge shallowly (object spread); see {@link Extension.configure}
|
|
1667
|
+
* for the nested-object gotcha and a workaround.
|
|
1641
1668
|
*
|
|
1642
1669
|
* @example
|
|
1643
1670
|
* const CustomBold = Bold.configure({ HTMLAttributes: { class: 'custom-bold' } });
|
|
1644
|
-
*
|
|
1645
|
-
* @example
|
|
1646
|
-
* // Shallow merge behavior with nested objects:
|
|
1647
|
-
* // Given: options = { HTMLAttributes: { class: 'a', id: 'b' } }
|
|
1648
|
-
* // configure({ HTMLAttributes: { class: 'c' } })
|
|
1649
|
-
* // Result: { HTMLAttributes: { class: 'c' } } — 'id' is lost!
|
|
1650
|
-
* // To preserve nested values, spread manually:
|
|
1651
|
-
* // configure({ HTMLAttributes: { ...original.options.HTMLAttributes, class: 'c' } })
|
|
1652
1671
|
*/
|
|
1653
1672
|
configure(options) {
|
|
1654
1673
|
const { isFormatting, ...restOptions } = options;
|
|
@@ -1923,6 +1942,40 @@ var unsetAllMarks = () => ({ state, tr, dispatch, editor }) => {
|
|
|
1923
1942
|
dispatch(tr);
|
|
1924
1943
|
return true;
|
|
1925
1944
|
};
|
|
1945
|
+
|
|
1946
|
+
// src/utils/listItemAncestor.ts
|
|
1947
|
+
var LIST_ITEM_TYPE_NAMES = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
|
|
1948
|
+
function findListItemAncestorDepth($pos) {
|
|
1949
|
+
for (let d = $pos.depth; d >= 1; d--) {
|
|
1950
|
+
if (LIST_ITEM_TYPE_NAMES.has($pos.node(d).type.name)) return d;
|
|
1951
|
+
}
|
|
1952
|
+
return -1;
|
|
1953
|
+
}
|
|
1954
|
+
function isInsideListItem($pos) {
|
|
1955
|
+
return findListItemAncestorDepth($pos) !== -1;
|
|
1956
|
+
}
|
|
1957
|
+
function isInListItemLabel($pos) {
|
|
1958
|
+
const d = findListItemAncestorDepth($pos);
|
|
1959
|
+
if (d === -1) return false;
|
|
1960
|
+
return $pos.index(d) === 0;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// src/utils/liftCurrentListItem.ts
|
|
1964
|
+
function liftCurrentListItem(state, tr) {
|
|
1965
|
+
if (!tr.selection.empty) return false;
|
|
1966
|
+
if (tr.steps.length !== 0) return false;
|
|
1967
|
+
const { $from } = tr.selection;
|
|
1968
|
+
const listItemDepth = findListItemAncestorDepth($from);
|
|
1969
|
+
if (listItemDepth === -1) return false;
|
|
1970
|
+
if ($from.index(listItemDepth) !== 0) return false;
|
|
1971
|
+
const listItemType = $from.node(listItemDepth).type;
|
|
1972
|
+
return liftListItem(listItemType)(state, (liftTr) => {
|
|
1973
|
+
for (const step of liftTr.steps) tr.step(step);
|
|
1974
|
+
if (liftTr.selectionSet) tr.setSelection(liftTr.selection);
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
// src/commands/nodeCommands.ts
|
|
1926
1979
|
var setBlockType = (nodeName, attributes) => ({ state, tr, dispatch }) => {
|
|
1927
1980
|
const nodeType = state.schema.nodes[nodeName];
|
|
1928
1981
|
if (!nodeType) {
|
|
@@ -1946,7 +1999,26 @@ var setBlockType = (nodeName, attributes) => ({ state, tr, dispatch }) => {
|
|
|
1946
1999
|
});
|
|
1947
2000
|
return found;
|
|
1948
2001
|
});
|
|
1949
|
-
if (!canApply)
|
|
2002
|
+
if (!canApply) {
|
|
2003
|
+
const liftOk = liftCurrentListItem(state, tr);
|
|
2004
|
+
if (!liftOk) return false;
|
|
2005
|
+
const $reFrom = tr.doc.resolve(tr.selection.from);
|
|
2006
|
+
if ($reFrom.depth < 1) return false;
|
|
2007
|
+
const blockParent = $reFrom.node($reFrom.depth - 1);
|
|
2008
|
+
const blockIndex = $reFrom.index($reFrom.depth - 1);
|
|
2009
|
+
if (!blockParent.canReplaceWith(blockIndex, blockIndex + 1, nodeType)) {
|
|
2010
|
+
return false;
|
|
2011
|
+
}
|
|
2012
|
+
if (!dispatch) return true;
|
|
2013
|
+
tr.setBlockType(
|
|
2014
|
+
tr.selection.from,
|
|
2015
|
+
tr.selection.to,
|
|
2016
|
+
nodeType,
|
|
2017
|
+
(node) => ({ ...node.attrs, ...attributes ?? {} })
|
|
2018
|
+
);
|
|
2019
|
+
dispatch(tr.scrollIntoView());
|
|
2020
|
+
return true;
|
|
2021
|
+
}
|
|
1950
2022
|
if (!dispatch) return true;
|
|
1951
2023
|
for (const range of tr.selection.ranges) {
|
|
1952
2024
|
const from = range.$from.pos;
|
|
@@ -1991,9 +2063,22 @@ var wrapIn = (nodeName, attributes) => ({ state, tr, dispatch }) => {
|
|
|
1991
2063
|
const range = $from.blockRange($to);
|
|
1992
2064
|
if (!range) return false;
|
|
1993
2065
|
const wrapping = findWrapping(range, nodeType, attributes);
|
|
1994
|
-
if (
|
|
2066
|
+
if (wrapping) {
|
|
2067
|
+
if (!dispatch) return true;
|
|
2068
|
+
tr.wrap(range, wrapping).scrollIntoView();
|
|
2069
|
+
dispatch(tr);
|
|
2070
|
+
return true;
|
|
2071
|
+
}
|
|
2072
|
+
const liftOk = liftCurrentListItem(state, tr);
|
|
2073
|
+
if (!liftOk) return false;
|
|
2074
|
+
const $reFrom = tr.doc.resolve(tr.selection.from);
|
|
2075
|
+
const $reTo = tr.doc.resolve(tr.selection.to);
|
|
2076
|
+
const reRange = $reFrom.blockRange($reTo);
|
|
2077
|
+
if (!reRange) return false;
|
|
2078
|
+
const reWrapping = findWrapping(reRange, nodeType, attributes);
|
|
2079
|
+
if (!reWrapping) return false;
|
|
1995
2080
|
if (!dispatch) return true;
|
|
1996
|
-
tr.wrap(
|
|
2081
|
+
tr.wrap(reRange, reWrapping).scrollIntoView();
|
|
1997
2082
|
dispatch(tr);
|
|
1998
2083
|
return true;
|
|
1999
2084
|
};
|
|
@@ -2088,6 +2173,30 @@ var lift = () => ({ tr, dispatch }) => {
|
|
|
2088
2173
|
dispatch(tr);
|
|
2089
2174
|
return true;
|
|
2090
2175
|
};
|
|
2176
|
+
|
|
2177
|
+
// src/utils/listItemCursorContext.ts
|
|
2178
|
+
var LIST_ITEM_TYPES = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
|
|
2179
|
+
function getListItemCursorContext($from) {
|
|
2180
|
+
if ($from.parent.type.name !== "paragraph") return null;
|
|
2181
|
+
const itemDepth = $from.depth - 1;
|
|
2182
|
+
if (itemDepth < 1) return null;
|
|
2183
|
+
const itemNode = $from.node(itemDepth);
|
|
2184
|
+
if (!LIST_ITEM_TYPES.has(itemNode.type.name)) return null;
|
|
2185
|
+
const wrapperDepth = itemDepth - 1;
|
|
2186
|
+
const childIndex = $from.index(itemDepth);
|
|
2187
|
+
return {
|
|
2188
|
+
itemDepth,
|
|
2189
|
+
itemPos: $from.before(itemDepth),
|
|
2190
|
+
wrapperPos: $from.before(wrapperDepth),
|
|
2191
|
+
isInChildrenZone: childIndex > 0,
|
|
2192
|
+
paragraphIsEmpty: $from.parent.content.size === 0,
|
|
2193
|
+
isLastChild: childIndex === itemNode.childCount - 1,
|
|
2194
|
+
childIndex,
|
|
2195
|
+
itemIndexInWrapper: $from.index(wrapperDepth)
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
// src/commands/listCommands.ts
|
|
2091
2200
|
function joinListBackwards(tr, listType) {
|
|
2092
2201
|
const { $from } = tr.selection;
|
|
2093
2202
|
for (let d = $from.depth; d >= 0; d--) {
|
|
@@ -2234,6 +2343,19 @@ var toggleList = (listNodeName, listItemNodeName, attributes) => ({ state, tr, d
|
|
|
2234
2343
|
return true;
|
|
2235
2344
|
}
|
|
2236
2345
|
const { from, to } = tr.selection;
|
|
2346
|
+
if (from === to) {
|
|
2347
|
+
const ctx = getListItemCursorContext(tr.selection.$from);
|
|
2348
|
+
if (ctx?.isInChildrenZone) {
|
|
2349
|
+
if (!dispatch) return true;
|
|
2350
|
+
const blockRange = tr.selection.$from.blockRange();
|
|
2351
|
+
if (blockRange && wrapRangeInList(tr, blockRange, listType, attributes)) {
|
|
2352
|
+
joinListBackwards(tr, listType);
|
|
2353
|
+
joinListForwards(tr, listType);
|
|
2354
|
+
dispatch(tr.scrollIntoView());
|
|
2355
|
+
return true;
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2237
2359
|
const contentBlocks = collectListContext(tr.doc, from, to);
|
|
2238
2360
|
const allInTargetList = contentBlocks.length > 0 && contentBlocks.every((b) => b.inTargetList);
|
|
2239
2361
|
const allInSomeList = contentBlocks.length > 0 && contentBlocks.every((b) => b.inSomeList);
|
|
@@ -2892,8 +3014,8 @@ var CommandManager = class {
|
|
|
2892
3014
|
};
|
|
2893
3015
|
}
|
|
2894
3016
|
/**
|
|
2895
|
-
* Single commands that execute immediately
|
|
2896
|
-
* Uses Proxy to dynamically generate command methods
|
|
3017
|
+
* Single commands that execute immediately.
|
|
3018
|
+
* Uses a Proxy to dynamically generate command methods.
|
|
2897
3019
|
*
|
|
2898
3020
|
* @example
|
|
2899
3021
|
* editor.commands.focus('end');
|
|
@@ -3372,12 +3494,18 @@ var Editor = class extends EventEmitter {
|
|
|
3372
3494
|
return this._extensionManager.storage;
|
|
3373
3495
|
}
|
|
3374
3496
|
/**
|
|
3375
|
-
*
|
|
3376
|
-
* Used by framework toolbar components to auto-generate UI.
|
|
3497
|
+
* Toolbar items registered by all extensions.
|
|
3377
3498
|
*/
|
|
3378
3499
|
get toolbarItems() {
|
|
3379
3500
|
return this._extensionManager.toolbarItems;
|
|
3380
3501
|
}
|
|
3502
|
+
/**
|
|
3503
|
+
* Floating-menu items registered by all extensions, rendered as the
|
|
3504
|
+
* block-insert menu shown on empty paragraphs.
|
|
3505
|
+
*/
|
|
3506
|
+
get floatingMenuItems() {
|
|
3507
|
+
return this._extensionManager.floatingMenuItems;
|
|
3508
|
+
}
|
|
3381
3509
|
// === Active State Methods ===
|
|
3382
3510
|
/**
|
|
3383
3511
|
* Checks if a node or mark is currently active
|
|
@@ -3628,9 +3756,8 @@ var Editor = class extends EventEmitter {
|
|
|
3628
3756
|
}
|
|
3629
3757
|
// === Dynamic Plugin Management ===
|
|
3630
3758
|
/**
|
|
3631
|
-
* Registers a ProseMirror plugin dynamically at runtime
|
|
3632
|
-
*
|
|
3633
|
-
* plugins after the editor is created.
|
|
3759
|
+
* Registers a ProseMirror plugin dynamically at runtime, after the editor
|
|
3760
|
+
* has been created. Safe to call repeatedly with the same plugin key.
|
|
3634
3761
|
*/
|
|
3635
3762
|
registerPlugin(plugin) {
|
|
3636
3763
|
if (plugin.spec.key?.get(this.view.state)) return;
|
|
@@ -3745,7 +3872,7 @@ var Editor = class extends EventEmitter {
|
|
|
3745
3872
|
...this.options.editable ?? true ? {} : { "aria-readonly": "true" }
|
|
3746
3873
|
}),
|
|
3747
3874
|
...Object.keys(nodeViews).length > 0 ? { nodeViews } : {},
|
|
3748
|
-
// Clipboard transform
|
|
3875
|
+
// Clipboard transform - apply user-provided transform (e.g. inlineStyles) on copy/cut
|
|
3749
3876
|
...this.options.clipboardHTMLTransform ? this.buildClipboardSerializer(this.options.clipboardHTMLTransform, this._extensionManager.schema) : {},
|
|
3750
3877
|
// Handle focus/blur events
|
|
3751
3878
|
handleDOMEvents: {
|
|
@@ -3878,8 +4005,225 @@ function positionFloatingOnce(reference, floating, options) {
|
|
|
3878
4005
|
});
|
|
3879
4006
|
}
|
|
3880
4007
|
|
|
4008
|
+
// src/utils/clipboard.ts
|
|
4009
|
+
async function writeToClipboard(text) {
|
|
4010
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
4011
|
+
try {
|
|
4012
|
+
await navigator.clipboard.writeText(text);
|
|
4013
|
+
return true;
|
|
4014
|
+
} catch {
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
if (typeof document === "undefined") return false;
|
|
4018
|
+
const textarea = document.createElement("textarea");
|
|
4019
|
+
textarea.value = text;
|
|
4020
|
+
textarea.style.position = "fixed";
|
|
4021
|
+
textarea.style.top = "-1000px";
|
|
4022
|
+
textarea.style.left = "-1000px";
|
|
4023
|
+
textarea.style.opacity = "0";
|
|
4024
|
+
textarea.setAttribute("readonly", "");
|
|
4025
|
+
document.body.appendChild(textarea);
|
|
4026
|
+
try {
|
|
4027
|
+
textarea.select();
|
|
4028
|
+
textarea.setSelectionRange(0, text.length);
|
|
4029
|
+
return document.execCommand("copy");
|
|
4030
|
+
} catch {
|
|
4031
|
+
return false;
|
|
4032
|
+
} finally {
|
|
4033
|
+
textarea.remove();
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4037
|
+
// src/utils/defaultBubbleContexts.ts
|
|
4038
|
+
var NOTION_MODE_CLASS = "dm-notion-mode";
|
|
4039
|
+
var NOTION_TEXT_CONTEXT = Object.freeze([
|
|
4040
|
+
"bold",
|
|
4041
|
+
"italic",
|
|
4042
|
+
"underline",
|
|
4043
|
+
"strike",
|
|
4044
|
+
"code",
|
|
4045
|
+
"|",
|
|
4046
|
+
"link",
|
|
4047
|
+
"|",
|
|
4048
|
+
"textAlign"
|
|
4049
|
+
]);
|
|
4050
|
+
var STANDARD_TEXT_CONTEXT = Object.freeze([
|
|
4051
|
+
"bold",
|
|
4052
|
+
"italic",
|
|
4053
|
+
"underline",
|
|
4054
|
+
"strike",
|
|
4055
|
+
"code",
|
|
4056
|
+
"|",
|
|
4057
|
+
"link"
|
|
4058
|
+
]);
|
|
4059
|
+
function defaultBubbleContexts(editor) {
|
|
4060
|
+
const inNotionMode = editor.view.dom.closest("." + NOTION_MODE_CLASS) !== null;
|
|
4061
|
+
const text = inNotionMode ? NOTION_TEXT_CONTEXT : STANDARD_TEXT_CONTEXT;
|
|
4062
|
+
return { text: [...text] };
|
|
4063
|
+
}
|
|
4064
|
+
|
|
4065
|
+
// src/utils/insertAsListItemChild.ts
|
|
4066
|
+
var LIST_ITEM_TYPES2 = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
|
|
4067
|
+
var LIST_WRAPPER_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
|
|
4068
|
+
function insertAsListItemChild(args) {
|
|
4069
|
+
const { tr, wrapperPos, targetItemPos, blockNode, sourceRange } = args;
|
|
4070
|
+
if (wrapperPos < 0 || wrapperPos >= tr.doc.content.size) return { ok: false };
|
|
4071
|
+
const wrapper = tr.doc.nodeAt(wrapperPos);
|
|
4072
|
+
if (!wrapper || !LIST_WRAPPER_TYPES.has(wrapper.type.name)) return { ok: false };
|
|
4073
|
+
if (wrapper.childCount === 0) return { ok: false };
|
|
4074
|
+
let targetItem;
|
|
4075
|
+
let targetItemStart;
|
|
4076
|
+
if (targetItemPos !== void 0) {
|
|
4077
|
+
if (targetItemPos < 0 || targetItemPos >= tr.doc.content.size) return { ok: false };
|
|
4078
|
+
const candidate = tr.doc.nodeAt(targetItemPos);
|
|
4079
|
+
if (!candidate || !LIST_ITEM_TYPES2.has(candidate.type.name)) return { ok: false };
|
|
4080
|
+
if (targetItemPos < wrapperPos + 1 || targetItemPos >= wrapperPos + wrapper.nodeSize) {
|
|
4081
|
+
return { ok: false };
|
|
4082
|
+
}
|
|
4083
|
+
targetItem = candidate;
|
|
4084
|
+
targetItemStart = targetItemPos;
|
|
4085
|
+
} else {
|
|
4086
|
+
const last = wrapper.lastChild;
|
|
4087
|
+
if (!last || !LIST_ITEM_TYPES2.has(last.type.name)) return { ok: false };
|
|
4088
|
+
let pos = wrapperPos + 1;
|
|
4089
|
+
for (let i = 0; i < wrapper.childCount - 1; i++) {
|
|
4090
|
+
pos += wrapper.child(i).nodeSize;
|
|
4091
|
+
}
|
|
4092
|
+
targetItem = last;
|
|
4093
|
+
targetItemStart = pos;
|
|
4094
|
+
}
|
|
4095
|
+
if (!targetItem.canReplaceWith(targetItem.childCount, targetItem.childCount, blockNode.type)) {
|
|
4096
|
+
return { ok: false };
|
|
4097
|
+
}
|
|
4098
|
+
const targetItemContentEnd = targetItemStart + targetItem.nodeSize - 1;
|
|
4099
|
+
if (sourceRange) {
|
|
4100
|
+
const { from, to } = sourceRange;
|
|
4101
|
+
if (targetItemContentEnd >= from && targetItemContentEnd <= to) {
|
|
4102
|
+
return { ok: false };
|
|
4103
|
+
}
|
|
4104
|
+
tr.delete(from, to);
|
|
4105
|
+
const adjustedInsertPos = targetItemContentEnd > from ? targetItemContentEnd - (to - from) : targetItemContentEnd;
|
|
4106
|
+
tr.insert(adjustedInsertPos, blockNode);
|
|
4107
|
+
return { ok: true, insertedAt: adjustedInsertPos };
|
|
4108
|
+
}
|
|
4109
|
+
tr.insert(targetItemContentEnd, blockNode);
|
|
4110
|
+
return { ok: true, insertedAt: targetItemContentEnd };
|
|
4111
|
+
}
|
|
4112
|
+
function liftEmptyChildrenZoneParagraph(state, dispatch, ctx) {
|
|
4113
|
+
if (!ctx.isInChildrenZone || !ctx.paragraphIsEmpty) return false;
|
|
4114
|
+
const { $from } = state.selection;
|
|
4115
|
+
if ($from.parent.type.name !== "paragraph") return false;
|
|
4116
|
+
if ($from.parent.content.size !== 0) return false;
|
|
4117
|
+
const paragraphType = state.schema.nodes["paragraph"];
|
|
4118
|
+
if (!paragraphType) return false;
|
|
4119
|
+
const range = $from.blockRange();
|
|
4120
|
+
if (range) {
|
|
4121
|
+
const target = liftTarget(range);
|
|
4122
|
+
if (target !== null) {
|
|
4123
|
+
if (!dispatch) return true;
|
|
4124
|
+
const tr2 = state.tr.lift(range, target);
|
|
4125
|
+
const oldParaStart = $from.before($from.depth);
|
|
4126
|
+
const newParaStart = tr2.mapping.map(oldParaStart);
|
|
4127
|
+
tr2.setSelection(TextSelection.create(tr2.doc, newParaStart + 1));
|
|
4128
|
+
dispatch(tr2.scrollIntoView());
|
|
4129
|
+
return true;
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
const paraStart = $from.before($from.depth);
|
|
4133
|
+
const paraEnd = $from.after($from.depth);
|
|
4134
|
+
const itemNode = state.doc.nodeAt(ctx.itemPos);
|
|
4135
|
+
if (!itemNode) return false;
|
|
4136
|
+
const itemEnd = ctx.itemPos + itemNode.nodeSize;
|
|
4137
|
+
const wrapperNode = state.doc.nodeAt(ctx.wrapperPos);
|
|
4138
|
+
if (!wrapperNode) return false;
|
|
4139
|
+
const wrapperEnd = ctx.wrapperPos + wrapperNode.nodeSize;
|
|
4140
|
+
const isLastItem = ctx.itemIndexInWrapper === wrapperNode.childCount - 1;
|
|
4141
|
+
const wrapperParentDepth = ctx.itemDepth - 2;
|
|
4142
|
+
if (wrapperParentDepth < 0) return false;
|
|
4143
|
+
const wrapperParent = $from.node(wrapperParentDepth);
|
|
4144
|
+
const wrapperIndexInParent = $from.index(wrapperParentDepth);
|
|
4145
|
+
if (!wrapperParent.canReplaceWith(
|
|
4146
|
+
wrapperIndexInParent + 1,
|
|
4147
|
+
wrapperIndexInParent + 1,
|
|
4148
|
+
paragraphType
|
|
4149
|
+
)) {
|
|
4150
|
+
return false;
|
|
4151
|
+
}
|
|
4152
|
+
if (!dispatch) return true;
|
|
4153
|
+
const newPara = paragraphType.create();
|
|
4154
|
+
const tr = state.tr;
|
|
4155
|
+
if (isLastItem) {
|
|
4156
|
+
tr.delete(paraStart, paraEnd);
|
|
4157
|
+
const insertPos = tr.mapping.map(wrapperEnd);
|
|
4158
|
+
tr.insert(insertPos, newPara);
|
|
4159
|
+
tr.setSelection(TextSelection.create(tr.doc, insertPos + 1));
|
|
4160
|
+
} else {
|
|
4161
|
+
tr.delete(paraStart, paraEnd);
|
|
4162
|
+
const splitAt = tr.mapping.map(itemEnd);
|
|
4163
|
+
tr.split(splitAt, 1);
|
|
4164
|
+
const insertPos = splitAt + 1;
|
|
4165
|
+
tr.insert(insertPos, newPara);
|
|
4166
|
+
tr.setSelection(TextSelection.create(tr.doc, insertPos + 1));
|
|
4167
|
+
}
|
|
4168
|
+
dispatch(tr.scrollIntoView());
|
|
4169
|
+
return true;
|
|
4170
|
+
}
|
|
4171
|
+
function insertChildrenZoneSibling(state, dispatch, ctx) {
|
|
4172
|
+
if (!ctx.isInChildrenZone || !ctx.paragraphIsEmpty) return false;
|
|
4173
|
+
const paragraphType = state.schema.nodes["paragraph"];
|
|
4174
|
+
if (!paragraphType) return false;
|
|
4175
|
+
const { $from } = state.selection;
|
|
4176
|
+
if ($from.parent.type.name !== "paragraph") return false;
|
|
4177
|
+
if ($from.parent.content.size !== 0) return false;
|
|
4178
|
+
if (!dispatch) return true;
|
|
4179
|
+
const insertPos = $from.after($from.depth);
|
|
4180
|
+
const tr = state.tr.insert(insertPos, paragraphType.create());
|
|
4181
|
+
tr.setSelection(TextSelection.create(tr.doc, insertPos + 1));
|
|
4182
|
+
dispatch(tr.scrollIntoView());
|
|
4183
|
+
return true;
|
|
4184
|
+
}
|
|
4185
|
+
function splitListForInsert(state, tr) {
|
|
4186
|
+
if (!tr.selection.empty) return null;
|
|
4187
|
+
if (tr.steps.length !== 0) return null;
|
|
4188
|
+
const { $from } = tr.selection;
|
|
4189
|
+
const listItemDepth = findListItemAncestorDepth($from);
|
|
4190
|
+
if (listItemDepth === -1) return null;
|
|
4191
|
+
if ($from.index(listItemDepth) !== 0) return null;
|
|
4192
|
+
const listDepth = listItemDepth - 1;
|
|
4193
|
+
const listItemNode = $from.node(listItemDepth);
|
|
4194
|
+
const labelParagraph = listItemNode.firstChild;
|
|
4195
|
+
const labelIsEmpty = listItemNode.childCount === 1 && (labelParagraph?.content.size ?? 0) === 0;
|
|
4196
|
+
if (labelIsEmpty) {
|
|
4197
|
+
const listItemType = listItemNode.type;
|
|
4198
|
+
let safetyLimit = 10;
|
|
4199
|
+
while (safetyLimit-- > 0) {
|
|
4200
|
+
const stepState = state.apply(tr);
|
|
4201
|
+
const liftOk = liftListItem(listItemType)(stepState, (liftTr) => {
|
|
4202
|
+
for (const step of liftTr.steps) tr.step(step);
|
|
4203
|
+
if (liftTr.selectionSet) tr.setSelection(liftTr.selection);
|
|
4204
|
+
});
|
|
4205
|
+
if (!liftOk) break;
|
|
4206
|
+
if (!isInsideListItem(tr.selection.$from)) break;
|
|
4207
|
+
}
|
|
4208
|
+
const $afterLift = tr.selection.$from;
|
|
4209
|
+
if (isInsideListItem($afterLift)) return null;
|
|
4210
|
+
return { from: $afterLift.before(), to: $afterLift.after() };
|
|
4211
|
+
}
|
|
4212
|
+
const listNode = $from.node(listDepth);
|
|
4213
|
+
const indexInList = $from.index(listDepth);
|
|
4214
|
+
const isLast = indexInList === listNode.childCount - 1;
|
|
4215
|
+
if (isLast) {
|
|
4216
|
+
const pos2 = $from.after(listDepth);
|
|
4217
|
+
return { from: pos2, to: pos2 };
|
|
4218
|
+
}
|
|
4219
|
+
const splitPos = $from.after(listItemDepth);
|
|
4220
|
+
tr.split(splitPos, 1);
|
|
4221
|
+
const pos = splitPos + 1;
|
|
4222
|
+
return { from: pos, to: pos };
|
|
4223
|
+
}
|
|
4224
|
+
|
|
3881
4225
|
// src/Node.ts
|
|
3882
|
-
var
|
|
4226
|
+
var Node2 = class _Node extends Extension {
|
|
3883
4227
|
/**
|
|
3884
4228
|
* Node type identifier
|
|
3885
4229
|
* Distinguishes nodes from extensions and marks
|
|
@@ -3941,25 +4285,12 @@ var Node = class _Node extends Extension {
|
|
|
3941
4285
|
return new _Node(config);
|
|
3942
4286
|
}
|
|
3943
4287
|
/**
|
|
3944
|
-
* Creates a new node with merged options
|
|
3945
|
-
*
|
|
3946
|
-
*
|
|
3947
|
-
* **Note:** Options are merged shallowly using object spread (`...`).
|
|
3948
|
-
* Nested objects are replaced entirely, not deeply merged.
|
|
3949
|
-
*
|
|
3950
|
-
* @param options - Options to merge with existing options
|
|
3951
|
-
* @returns New node instance with merged options
|
|
4288
|
+
* Creates a new node with merged options. Original node is not modified.
|
|
4289
|
+
* Options merge shallowly (object spread); see {@link Extension.configure}
|
|
4290
|
+
* for the nested-object gotcha and a workaround.
|
|
3952
4291
|
*
|
|
3953
4292
|
* @example
|
|
3954
4293
|
* const CustomParagraph = Paragraph.configure({ HTMLAttributes: { class: 'custom' } });
|
|
3955
|
-
*
|
|
3956
|
-
* @example
|
|
3957
|
-
* // Shallow merge behavior with nested objects:
|
|
3958
|
-
* // Given: options = { HTMLAttributes: { class: 'a', id: 'b' } }
|
|
3959
|
-
* // configure({ HTMLAttributes: { class: 'c' } })
|
|
3960
|
-
* // Result: { HTMLAttributes: { class: 'c' } } — 'id' is lost!
|
|
3961
|
-
* // To preserve nested values, spread manually:
|
|
3962
|
-
* // configure({ HTMLAttributes: { ...original.options.HTMLAttributes, class: 'c' } })
|
|
3963
4294
|
*/
|
|
3964
4295
|
configure(options) {
|
|
3965
4296
|
const newConfig = {
|
|
@@ -4133,7 +4464,7 @@ var ToolbarController = class _ToolbarController {
|
|
|
4133
4464
|
_activeMap = /* @__PURE__ */ new Map();
|
|
4134
4465
|
/** Disabled state for each button (keyed by item.name) */
|
|
4135
4466
|
_disabledMap = /* @__PURE__ */ new Map();
|
|
4136
|
-
/** Expanded state for emitEvent buttons
|
|
4467
|
+
/** Expanded state for emitEvent buttons - true when their panel is open */
|
|
4137
4468
|
_expandedMap = /* @__PURE__ */ new Map();
|
|
4138
4469
|
/** Currently open dropdown name (null = none) */
|
|
4139
4470
|
_openDropdown = null;
|
|
@@ -4468,6 +4799,262 @@ var ToolbarController = class _ToolbarController {
|
|
|
4468
4799
|
}
|
|
4469
4800
|
};
|
|
4470
4801
|
|
|
4802
|
+
// src/utils/groupFloatingMenuItems.ts
|
|
4803
|
+
function groupFloatingMenuItems(items) {
|
|
4804
|
+
const map = /* @__PURE__ */ new Map();
|
|
4805
|
+
const order = [];
|
|
4806
|
+
for (const item of items) {
|
|
4807
|
+
const name = item.group ?? "";
|
|
4808
|
+
let list = map.get(name);
|
|
4809
|
+
if (!list) {
|
|
4810
|
+
list = [];
|
|
4811
|
+
map.set(name, list);
|
|
4812
|
+
order.push(name);
|
|
4813
|
+
}
|
|
4814
|
+
list.push(item);
|
|
4815
|
+
}
|
|
4816
|
+
const groups = [];
|
|
4817
|
+
for (const name of order) {
|
|
4818
|
+
const list = (map.get(name) ?? []).slice();
|
|
4819
|
+
list.sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
|
|
4820
|
+
groups.push({ name, items: list });
|
|
4821
|
+
}
|
|
4822
|
+
return groups;
|
|
4823
|
+
}
|
|
4824
|
+
|
|
4825
|
+
// src/FloatingMenuController.ts
|
|
4826
|
+
var FLOATING_MENU_NO_FOCUS = -1;
|
|
4827
|
+
var FloatingMenuController = class _FloatingMenuController {
|
|
4828
|
+
/**
|
|
4829
|
+
* Resolves an `items` option (array | function) against the editor's
|
|
4830
|
+
* default floating-menu items. Exposed as static so the plugin can
|
|
4831
|
+
* resolve once at init without constructing a controller.
|
|
4832
|
+
*/
|
|
4833
|
+
static resolveItems(editor, override) {
|
|
4834
|
+
const defaults = editor.floatingMenuItems;
|
|
4835
|
+
if (!override) return defaults;
|
|
4836
|
+
if (Array.isArray(override)) return override;
|
|
4837
|
+
try {
|
|
4838
|
+
return override(defaults, editor);
|
|
4839
|
+
} catch {
|
|
4840
|
+
return defaults;
|
|
4841
|
+
}
|
|
4842
|
+
}
|
|
4843
|
+
/**
|
|
4844
|
+
* Executes a floating-menu item's command on the editor.
|
|
4845
|
+
* String commands are dispatched via `editor.commands[name](...args)`;
|
|
4846
|
+
* function commands are called directly.
|
|
4847
|
+
*/
|
|
4848
|
+
static executeItem(editor, item) {
|
|
4849
|
+
if (typeof item.command === "function") {
|
|
4850
|
+
item.command(editor);
|
|
4851
|
+
return;
|
|
4852
|
+
}
|
|
4853
|
+
const commands = editor.commands;
|
|
4854
|
+
const cmd = commands[item.command];
|
|
4855
|
+
if (!cmd) return;
|
|
4856
|
+
if (item.commandArgs?.length) {
|
|
4857
|
+
cmd(...item.commandArgs);
|
|
4858
|
+
} else {
|
|
4859
|
+
cmd();
|
|
4860
|
+
}
|
|
4861
|
+
}
|
|
4862
|
+
editor;
|
|
4863
|
+
onChange;
|
|
4864
|
+
override;
|
|
4865
|
+
transactionHandler = null;
|
|
4866
|
+
_groups = [];
|
|
4867
|
+
_flatItems = [];
|
|
4868
|
+
_disabledMap = /* @__PURE__ */ new Map();
|
|
4869
|
+
_focusedIndex = FLOATING_MENU_NO_FOCUS;
|
|
4870
|
+
constructor(editor, onChange, override) {
|
|
4871
|
+
this.editor = editor;
|
|
4872
|
+
this.onChange = onChange;
|
|
4873
|
+
this.override = override;
|
|
4874
|
+
this.rebuild();
|
|
4875
|
+
}
|
|
4876
|
+
// === Getters ===
|
|
4877
|
+
get groups() {
|
|
4878
|
+
return this._groups;
|
|
4879
|
+
}
|
|
4880
|
+
get flatItems() {
|
|
4881
|
+
return this._flatItems;
|
|
4882
|
+
}
|
|
4883
|
+
get disabledMap() {
|
|
4884
|
+
return this._disabledMap;
|
|
4885
|
+
}
|
|
4886
|
+
get focusedIndex() {
|
|
4887
|
+
return this._focusedIndex;
|
|
4888
|
+
}
|
|
4889
|
+
/** True when keyboard focus is inside the menu (at least one item focused). */
|
|
4890
|
+
get isEntered() {
|
|
4891
|
+
return this._focusedIndex >= 0;
|
|
4892
|
+
}
|
|
4893
|
+
get itemCount() {
|
|
4894
|
+
return this._flatItems.length;
|
|
4895
|
+
}
|
|
4896
|
+
// === State Methods ===
|
|
4897
|
+
isDisabled(item) {
|
|
4898
|
+
return this._disabledMap.get(item.name) ?? false;
|
|
4899
|
+
}
|
|
4900
|
+
/**
|
|
4901
|
+
* Executes an item's command, then closes the menu keyboard focus.
|
|
4902
|
+
* Callers should refocus the editor after calling this.
|
|
4903
|
+
*/
|
|
4904
|
+
execute(item) {
|
|
4905
|
+
if (this.isDisabled(item)) return;
|
|
4906
|
+
_FloatingMenuController.executeItem(this.editor, item);
|
|
4907
|
+
this._focusedIndex = FLOATING_MENU_NO_FOCUS;
|
|
4908
|
+
this.onChange();
|
|
4909
|
+
}
|
|
4910
|
+
/**
|
|
4911
|
+
* Rebuilds items from the editor. Call when the editor's extensions
|
|
4912
|
+
* change (rare) or on explicit refresh. Notification is delegated to
|
|
4913
|
+
* `updateDisabledStates` which fires `onChange` only when a disabled
|
|
4914
|
+
* state flipped - wrappers that need to react to pure group-structure
|
|
4915
|
+
* changes do so by bumping their own render signal after constructing
|
|
4916
|
+
* / re-using the controller (see framework wrapper usage).
|
|
4917
|
+
*/
|
|
4918
|
+
rebuild() {
|
|
4919
|
+
const items = _FloatingMenuController.resolveItems(this.editor, this.override);
|
|
4920
|
+
this._groups = this.groupItems(items);
|
|
4921
|
+
this._flatItems = this._groups.flatMap((g) => g.items);
|
|
4922
|
+
if (this._focusedIndex >= this._flatItems.length) {
|
|
4923
|
+
this._focusedIndex = FLOATING_MENU_NO_FOCUS;
|
|
4924
|
+
}
|
|
4925
|
+
this.updateDisabledStates();
|
|
4926
|
+
}
|
|
4927
|
+
// === Keyboard Navigation (roving tabindex) ===
|
|
4928
|
+
/** Enter keyboard focus on the menu (first item). Called by the plugin's keymap. */
|
|
4929
|
+
enterMenu() {
|
|
4930
|
+
if (this._flatItems.length === 0) {
|
|
4931
|
+
this._focusedIndex = FLOATING_MENU_NO_FOCUS;
|
|
4932
|
+
return this._focusedIndex;
|
|
4933
|
+
}
|
|
4934
|
+
this._focusedIndex = 0;
|
|
4935
|
+
this.onChange();
|
|
4936
|
+
return this._focusedIndex;
|
|
4937
|
+
}
|
|
4938
|
+
/** Leave keyboard focus (Escape). Refocus of the editor is the caller's job. */
|
|
4939
|
+
leaveMenu() {
|
|
4940
|
+
if (this._focusedIndex === FLOATING_MENU_NO_FOCUS) return;
|
|
4941
|
+
this._focusedIndex = FLOATING_MENU_NO_FOCUS;
|
|
4942
|
+
this.onChange();
|
|
4943
|
+
}
|
|
4944
|
+
/** ArrowDown - wrap to first at end. */
|
|
4945
|
+
next() {
|
|
4946
|
+
if (this._flatItems.length === 0) return FLOATING_MENU_NO_FOCUS;
|
|
4947
|
+
const cur = this._focusedIndex < 0 ? -1 : this._focusedIndex;
|
|
4948
|
+
this._focusedIndex = (cur + 1) % this._flatItems.length;
|
|
4949
|
+
this.onChange();
|
|
4950
|
+
return this._focusedIndex;
|
|
4951
|
+
}
|
|
4952
|
+
/** ArrowUp - wrap to last at start. */
|
|
4953
|
+
prev() {
|
|
4954
|
+
if (this._flatItems.length === 0) return FLOATING_MENU_NO_FOCUS;
|
|
4955
|
+
const len = this._flatItems.length;
|
|
4956
|
+
const cur = this._focusedIndex < 0 ? len : this._focusedIndex;
|
|
4957
|
+
this._focusedIndex = (cur - 1 + len) % len;
|
|
4958
|
+
this.onChange();
|
|
4959
|
+
return this._focusedIndex;
|
|
4960
|
+
}
|
|
4961
|
+
first() {
|
|
4962
|
+
if (this._flatItems.length === 0) return FLOATING_MENU_NO_FOCUS;
|
|
4963
|
+
this._focusedIndex = 0;
|
|
4964
|
+
this.onChange();
|
|
4965
|
+
return this._focusedIndex;
|
|
4966
|
+
}
|
|
4967
|
+
last() {
|
|
4968
|
+
if (this._flatItems.length === 0) return FLOATING_MENU_NO_FOCUS;
|
|
4969
|
+
this._focusedIndex = this._flatItems.length - 1;
|
|
4970
|
+
this.onChange();
|
|
4971
|
+
return this._focusedIndex;
|
|
4972
|
+
}
|
|
4973
|
+
/** Set focused index directly (e.g. on pointer hover). */
|
|
4974
|
+
setFocusedIndex(index) {
|
|
4975
|
+
if (index < 0 || index >= this._flatItems.length) return;
|
|
4976
|
+
if (this._focusedIndex === index) return;
|
|
4977
|
+
this._focusedIndex = index;
|
|
4978
|
+
this.onChange();
|
|
4979
|
+
}
|
|
4980
|
+
/** Get focused item (or null). */
|
|
4981
|
+
focusedItem() {
|
|
4982
|
+
return this._flatItems[this._focusedIndex] ?? null;
|
|
4983
|
+
}
|
|
4984
|
+
/** Get flat index of item by name (for wrappers binding roving tabindex). */
|
|
4985
|
+
getFlatIndex(name) {
|
|
4986
|
+
return this._flatItems.findIndex((i) => i.name === name);
|
|
4987
|
+
}
|
|
4988
|
+
// === Lifecycle ===
|
|
4989
|
+
/** Subscribes to editor transactions for disabled-state tracking. */
|
|
4990
|
+
subscribe() {
|
|
4991
|
+
this.transactionHandler = () => {
|
|
4992
|
+
this.updateDisabledStates();
|
|
4993
|
+
};
|
|
4994
|
+
this.editor.on("transaction", this.transactionHandler);
|
|
4995
|
+
}
|
|
4996
|
+
/** Unsubscribes and clears internal state. */
|
|
4997
|
+
destroy() {
|
|
4998
|
+
if (this.transactionHandler) {
|
|
4999
|
+
this.editor.off("transaction", this.transactionHandler);
|
|
5000
|
+
this.transactionHandler = null;
|
|
5001
|
+
}
|
|
5002
|
+
this._groups = [];
|
|
5003
|
+
this._flatItems = [];
|
|
5004
|
+
this._disabledMap.clear();
|
|
5005
|
+
this._focusedIndex = FLOATING_MENU_NO_FOCUS;
|
|
5006
|
+
}
|
|
5007
|
+
// === Internal ===
|
|
5008
|
+
/**
|
|
5009
|
+
* Groups items by `group` preserving insertion order, then sorts by
|
|
5010
|
+
* priority (higher first) within each group. Delegates to the shared
|
|
5011
|
+
* utility so `SlashCommand`'s renderer and this controller always
|
|
5012
|
+
* produce identical ordering.
|
|
5013
|
+
*/
|
|
5014
|
+
groupItems(items) {
|
|
5015
|
+
return groupFloatingMenuItems(items);
|
|
5016
|
+
}
|
|
5017
|
+
/**
|
|
5018
|
+
* Updates disabled state for each item. Uses custom predicate when
|
|
5019
|
+
* provided; otherwise tries a dry-run against `editor.can()[command]`.
|
|
5020
|
+
* Only notifies on change to avoid noisy re-renders.
|
|
5021
|
+
*/
|
|
5022
|
+
updateDisabledStates() {
|
|
5023
|
+
let changed = false;
|
|
5024
|
+
let canProxy = null;
|
|
5025
|
+
try {
|
|
5026
|
+
canProxy = this.editor.can();
|
|
5027
|
+
} catch {
|
|
5028
|
+
canProxy = null;
|
|
5029
|
+
}
|
|
5030
|
+
for (const item of this._flatItems) {
|
|
5031
|
+
const was = this._disabledMap.get(item.name) ?? false;
|
|
5032
|
+
let now = false;
|
|
5033
|
+
if (item.isDisabled) {
|
|
5034
|
+
try {
|
|
5035
|
+
now = item.isDisabled(this.editor);
|
|
5036
|
+
} catch {
|
|
5037
|
+
now = false;
|
|
5038
|
+
}
|
|
5039
|
+
} else if (typeof item.command === "string" && canProxy) {
|
|
5040
|
+
try {
|
|
5041
|
+
const canCmd = canProxy[item.command];
|
|
5042
|
+
if (canCmd) {
|
|
5043
|
+
now = item.commandArgs?.length ? !canCmd(...item.commandArgs) : !canCmd();
|
|
5044
|
+
}
|
|
5045
|
+
} catch {
|
|
5046
|
+
now = false;
|
|
5047
|
+
}
|
|
5048
|
+
}
|
|
5049
|
+
if (was !== now) {
|
|
5050
|
+
this._disabledMap.set(item.name, now);
|
|
5051
|
+
changed = true;
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
if (changed) this.onChange();
|
|
5055
|
+
}
|
|
5056
|
+
};
|
|
5057
|
+
|
|
4471
5058
|
// src/icons/phosphor.ts
|
|
4472
5059
|
var defaultIcons = {
|
|
4473
5060
|
// --- Format: Inline ---
|
|
@@ -4527,24 +5114,29 @@ var defaultIcons = {
|
|
|
4527
5114
|
arrowClockwise: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64l-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,0,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60L224,85.8V56a8,8,0,1,1,16,0Z"/></svg>',
|
|
4528
5115
|
// --- Table ---
|
|
4529
5116
|
table: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,48H32a8,8,0,0,0-8,8V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A8,8,0,0,0,224,48ZM40,112H80v32H40Zm56,0H216v32H96ZM216,64V96H40V64ZM40,160H80v32H40Zm176,32H96V160H216v32Z"/></svg>',
|
|
4530
|
-
gridNine: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,48H40A16,16,0,0,0,24,64V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V64A16,16,0,0,0,216,48ZM104,144V112h48v32Zm48,16v32H104V160ZM40,112H88v32H40Zm64-16V64h48V96Zm64,16h48v32H168Zm48-16H168V64h48ZM88,64V96H40V64ZM40,160H88v32H40Zm176,32H168V160h48v32Z"/></svg>'
|
|
5117
|
+
gridNine: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,48H40A16,16,0,0,0,24,64V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V64A16,16,0,0,0,216,48ZM104,144V112h48v32Zm48,16v32H104V160ZM40,112H88v32H40Zm64-16V64h48V96Zm64,16h48v32H168Zm48-16H168V64h48ZM88,64V96H40V64ZM40,160H88v32H40Zm176,32H168V160h48v32Z"/></svg>',
|
|
5118
|
+
// --- Block Handle / Context Menu ---
|
|
5119
|
+
plus: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z"/></svg>',
|
|
5120
|
+
dotsSixVertical: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M108,60A16,16,0,1,1,92,44,16,16,0,0,1,108,60Zm56,16A16,16,0,1,0,148,60,16,16,0,0,0,164,76ZM92,112a16,16,0,1,0,16,16A16,16,0,0,0,92,112Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,112ZM92,180a16,16,0,1,0,16,16A16,16,0,0,0,92,180Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,180Z"/></svg>',
|
|
5121
|
+
dotsThree: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M140,128a12,12,0,1,1-12-12A12,12,0,0,1,140,128ZM64,116a12,12,0,1,0,12,12A12,12,0,0,0,64,116Zm128,0a12,12,0,1,0,12,12A12,12,0,0,0,192,116Z"/></svg>',
|
|
5122
|
+
copy: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"/></svg>'
|
|
4531
5123
|
};
|
|
4532
5124
|
|
|
4533
5125
|
// src/nodes/Document.ts
|
|
4534
|
-
var Document =
|
|
5126
|
+
var Document = Node2.create({
|
|
4535
5127
|
name: "doc",
|
|
4536
5128
|
topNode: true,
|
|
4537
5129
|
content: "block+"
|
|
4538
5130
|
});
|
|
4539
5131
|
|
|
4540
5132
|
// src/nodes/Text.ts
|
|
4541
|
-
var Text =
|
|
5133
|
+
var Text = Node2.create({
|
|
4542
5134
|
name: "text",
|
|
4543
5135
|
group: "inline"
|
|
4544
5136
|
});
|
|
4545
5137
|
|
|
4546
5138
|
// src/nodes/Paragraph.ts
|
|
4547
|
-
var Paragraph =
|
|
5139
|
+
var Paragraph = Node2.create({
|
|
4548
5140
|
name: "paragraph",
|
|
4549
5141
|
group: "block",
|
|
4550
5142
|
content: "inline*",
|
|
@@ -4577,7 +5169,7 @@ var Paragraph = Node.create({
|
|
|
4577
5169
|
};
|
|
4578
5170
|
}
|
|
4579
5171
|
});
|
|
4580
|
-
var Heading =
|
|
5172
|
+
var Heading = Node2.create({
|
|
4581
5173
|
name: "heading",
|
|
4582
5174
|
group: "block",
|
|
4583
5175
|
content: "inline*",
|
|
@@ -4640,6 +5232,33 @@ var Heading = Node.create({
|
|
|
4640
5232
|
return editor?.commands["toggleHeading"]?.({ level }) ?? false;
|
|
4641
5233
|
};
|
|
4642
5234
|
});
|
|
5235
|
+
shortcuts["Enter"] = () => {
|
|
5236
|
+
if (!editor) return false;
|
|
5237
|
+
const { state, view } = editor;
|
|
5238
|
+
const { selection } = state;
|
|
5239
|
+
if (!selection.empty) return false;
|
|
5240
|
+
const { $from } = selection;
|
|
5241
|
+
if ($from.parent.type.name !== "heading") return false;
|
|
5242
|
+
const paragraphType = state.schema.nodes["paragraph"];
|
|
5243
|
+
if (!paragraphType) return false;
|
|
5244
|
+
if ($from.parent.content.size === 0) {
|
|
5245
|
+
view.dispatch(
|
|
5246
|
+
state.tr.setNodeMarkup($from.before($from.depth), paragraphType).scrollIntoView()
|
|
5247
|
+
);
|
|
5248
|
+
return true;
|
|
5249
|
+
}
|
|
5250
|
+
if ($from.parentOffset !== $from.parent.content.size) return false;
|
|
5251
|
+
const after = $from.after($from.depth);
|
|
5252
|
+
const $after = state.doc.resolve(after);
|
|
5253
|
+
const indexAfter = $after.index();
|
|
5254
|
+
if (!$after.parent.canReplaceWith(indexAfter, indexAfter, paragraphType)) {
|
|
5255
|
+
return false;
|
|
5256
|
+
}
|
|
5257
|
+
const tr = state.tr.insert(after, paragraphType.create());
|
|
5258
|
+
tr.setSelection(TextSelection.create(tr.doc, after + 1));
|
|
5259
|
+
view.dispatch(tr.scrollIntoView());
|
|
5260
|
+
return true;
|
|
5261
|
+
};
|
|
4643
5262
|
return shortcuts;
|
|
4644
5263
|
},
|
|
4645
5264
|
addToolbarItems() {
|
|
@@ -4681,6 +5300,30 @@ var Heading = Node.create({
|
|
|
4681
5300
|
}
|
|
4682
5301
|
];
|
|
4683
5302
|
},
|
|
5303
|
+
addFloatingMenuItems() {
|
|
5304
|
+
const iconMap = {
|
|
5305
|
+
1: "textHOne",
|
|
5306
|
+
2: "textHTwo",
|
|
5307
|
+
3: "textHThree"
|
|
5308
|
+
};
|
|
5309
|
+
const descriptionMap = {
|
|
5310
|
+
1: "Big section heading",
|
|
5311
|
+
2: "Medium section heading",
|
|
5312
|
+
3: "Small section heading"
|
|
5313
|
+
};
|
|
5314
|
+
return this.options.levels.filter((level) => level <= 3).map((level) => ({
|
|
5315
|
+
name: `heading-${String(level)}`,
|
|
5316
|
+
label: `Heading ${String(level)}`,
|
|
5317
|
+
description: descriptionMap[level] ?? "Section heading",
|
|
5318
|
+
icon: iconMap[level] ?? "textH",
|
|
5319
|
+
group: "Basic",
|
|
5320
|
+
priority: 210 - level * 10,
|
|
5321
|
+
keywords: ["heading", `h${String(level)}`, "title"],
|
|
5322
|
+
shortcut: "#".repeat(level) + " ",
|
|
5323
|
+
command: "toggleHeading",
|
|
5324
|
+
commandArgs: [{ level }]
|
|
5325
|
+
}));
|
|
5326
|
+
},
|
|
4684
5327
|
addProseMirrorPlugins() {
|
|
4685
5328
|
const { options, editor } = this;
|
|
4686
5329
|
const codeToLevel = {};
|
|
@@ -4754,7 +5397,7 @@ var Heading = Node.create({
|
|
|
4754
5397
|
});
|
|
4755
5398
|
|
|
4756
5399
|
// src/nodes/Blockquote.ts
|
|
4757
|
-
var Blockquote =
|
|
5400
|
+
var Blockquote = Node2.create({
|
|
4758
5401
|
name: "blockquote",
|
|
4759
5402
|
group: "block",
|
|
4760
5403
|
content: "block+",
|
|
@@ -4807,17 +5450,32 @@ var Blockquote = Node.create({
|
|
|
4807
5450
|
}
|
|
4808
5451
|
];
|
|
4809
5452
|
},
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
5453
|
+
addFloatingMenuItems() {
|
|
5454
|
+
return [
|
|
5455
|
+
{
|
|
5456
|
+
name: "blockquote",
|
|
5457
|
+
label: "Quote",
|
|
5458
|
+
description: "Capture a quote",
|
|
5459
|
+
icon: "quotes",
|
|
5460
|
+
group: "Basic",
|
|
5461
|
+
priority: 170,
|
|
5462
|
+
keywords: ["quote", "blockquote", "citation"],
|
|
5463
|
+
shortcut: "> ",
|
|
5464
|
+
command: "toggleBlockquote"
|
|
5465
|
+
}
|
|
5466
|
+
];
|
|
5467
|
+
},
|
|
5468
|
+
addInputRules() {
|
|
5469
|
+
const { nodeType } = this;
|
|
5470
|
+
if (!nodeType) {
|
|
5471
|
+
return [];
|
|
5472
|
+
}
|
|
4815
5473
|
return [
|
|
4816
5474
|
wrappingInputRule({ find: /^\s*>\s$/, type: nodeType })
|
|
4817
5475
|
];
|
|
4818
5476
|
}
|
|
4819
5477
|
});
|
|
4820
|
-
var CodeBlock =
|
|
5478
|
+
var CodeBlock = Node2.create({
|
|
4821
5479
|
name: "codeBlock",
|
|
4822
5480
|
group: "block",
|
|
4823
5481
|
content: "text*",
|
|
@@ -4937,6 +5595,21 @@ var CodeBlock = Node.create({
|
|
|
4937
5595
|
}
|
|
4938
5596
|
];
|
|
4939
5597
|
},
|
|
5598
|
+
addFloatingMenuItems() {
|
|
5599
|
+
return [
|
|
5600
|
+
{
|
|
5601
|
+
name: "code-block",
|
|
5602
|
+
label: "Code block",
|
|
5603
|
+
description: "Capture a code snippet",
|
|
5604
|
+
icon: "codeBlock",
|
|
5605
|
+
group: "Basic",
|
|
5606
|
+
priority: 160,
|
|
5607
|
+
keywords: ["code", "snippet", "pre"],
|
|
5608
|
+
shortcut: "``` ",
|
|
5609
|
+
command: "toggleCodeBlock"
|
|
5610
|
+
}
|
|
5611
|
+
];
|
|
5612
|
+
},
|
|
4940
5613
|
addInputRules() {
|
|
4941
5614
|
const { nodeType } = this;
|
|
4942
5615
|
if (!nodeType) {
|
|
@@ -4954,6 +5627,7 @@ var CodeBlock = Node.create({
|
|
|
4954
5627
|
];
|
|
4955
5628
|
}
|
|
4956
5629
|
});
|
|
5630
|
+
var LIST_GROUP_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
|
|
4957
5631
|
function getListItemContext(editor, listItemName) {
|
|
4958
5632
|
const { state, view } = editor;
|
|
4959
5633
|
const listItemType = state.schema.nodes[listItemName];
|
|
@@ -5003,6 +5677,35 @@ var ListKeymap = Extension.create({
|
|
|
5003
5677
|
const { state, view } = editor;
|
|
5004
5678
|
const { $from, empty } = state.selection;
|
|
5005
5679
|
if (!empty || $from.parentOffset !== 0) return false;
|
|
5680
|
+
if ($from.parent.type.name === "paragraph" && $from.parent.content.size === 0 && $from.depth >= 1) {
|
|
5681
|
+
const containerDepth = $from.depth - 1;
|
|
5682
|
+
const idx = $from.index(containerDepth);
|
|
5683
|
+
if (idx > 0) {
|
|
5684
|
+
const container = $from.node(containerDepth);
|
|
5685
|
+
const prev = container.child(idx - 1);
|
|
5686
|
+
if (LIST_GROUP_TYPES.has(prev.type.name)) {
|
|
5687
|
+
const paraStart = $from.before($from.depth);
|
|
5688
|
+
const paraEnd = $from.after($from.depth);
|
|
5689
|
+
const listEnd = paraStart;
|
|
5690
|
+
const listStart = listEnd - prev.nodeSize;
|
|
5691
|
+
let lastTextblockEnd = -1;
|
|
5692
|
+
state.doc.nodesBetween(listStart, listEnd, (n, p) => {
|
|
5693
|
+
if (n.isTextblock) lastTextblockEnd = p + 1 + n.content.size;
|
|
5694
|
+
return true;
|
|
5695
|
+
});
|
|
5696
|
+
if (lastTextblockEnd !== -1) {
|
|
5697
|
+
const tr = state.tr.delete(paraStart, paraEnd);
|
|
5698
|
+
const next = idx + 1 < container.childCount ? container.child(idx + 1) : null;
|
|
5699
|
+
if (next?.type === prev.type && canJoin(tr.doc, paraStart)) {
|
|
5700
|
+
tr.join(paraStart);
|
|
5701
|
+
}
|
|
5702
|
+
tr.setSelection(TextSelection.create(tr.doc, lastTextblockEnd));
|
|
5703
|
+
view.dispatch(tr.scrollIntoView());
|
|
5704
|
+
return true;
|
|
5705
|
+
}
|
|
5706
|
+
}
|
|
5707
|
+
}
|
|
5708
|
+
}
|
|
5006
5709
|
const listItemType = state.schema.nodes[this.options.listItem];
|
|
5007
5710
|
if (!listItemType) return false;
|
|
5008
5711
|
let listItemDepth = -1;
|
|
@@ -5028,9 +5731,11 @@ var ListKeymap = Extension.create({
|
|
|
5028
5731
|
});
|
|
5029
5732
|
|
|
5030
5733
|
// src/nodes/ListItem.ts
|
|
5031
|
-
var ListItem =
|
|
5734
|
+
var ListItem = Node2.create({
|
|
5032
5735
|
name: "listItem",
|
|
5033
|
-
|
|
5736
|
+
// Notion-strict: paragraph must be the first child (the "label" line aligned
|
|
5737
|
+
// with the bullet); additional blocks render below as nested children.
|
|
5738
|
+
content: "paragraph block*",
|
|
5034
5739
|
defining: true,
|
|
5035
5740
|
addOptions() {
|
|
5036
5741
|
return {
|
|
@@ -5053,6 +5758,15 @@ var ListItem = Node.create({
|
|
|
5053
5758
|
const { state, view } = this.editor;
|
|
5054
5759
|
const { $from } = state.selection;
|
|
5055
5760
|
if ($from.depth < 1 || $from.node(-1).type !== this.nodeType) return false;
|
|
5761
|
+
if ($from.parent.type.name !== "paragraph") return false;
|
|
5762
|
+
const ctx = getListItemCursorContext($from);
|
|
5763
|
+
if (ctx?.isInChildrenZone) {
|
|
5764
|
+
if (ctx.paragraphIsEmpty) {
|
|
5765
|
+
if (insertChildrenZoneSibling(state, view.dispatch, ctx)) return true;
|
|
5766
|
+
} else {
|
|
5767
|
+
if (splitBlock(state, view.dispatch)) return true;
|
|
5768
|
+
}
|
|
5769
|
+
}
|
|
5056
5770
|
if (splitListItem(this.nodeType)(state, view.dispatch)) return true;
|
|
5057
5771
|
const listDepth = $from.depth - 2;
|
|
5058
5772
|
const taskItemType = state.schema.nodes["taskItem"];
|
|
@@ -5071,13 +5785,25 @@ var ListItem = Node.create({
|
|
|
5071
5785
|
}
|
|
5072
5786
|
}
|
|
5073
5787
|
return liftListItem(this.nodeType)(state, view.dispatch);
|
|
5788
|
+
},
|
|
5789
|
+
Backspace: () => {
|
|
5790
|
+
if (!this.editor || !this.nodeType) return false;
|
|
5791
|
+
const { state, view } = this.editor;
|
|
5792
|
+
const { $from, empty } = state.selection;
|
|
5793
|
+
if (!empty || $from.parentOffset !== 0) return false;
|
|
5794
|
+
if ($from.parent.content.size !== 0) return false;
|
|
5795
|
+
const ctx = getListItemCursorContext($from);
|
|
5796
|
+
if (ctx && ctx.isInChildrenZone && ctx.paragraphIsEmpty) {
|
|
5797
|
+
if (liftEmptyChildrenZoneParagraph(state, view.dispatch, ctx)) return true;
|
|
5798
|
+
}
|
|
5799
|
+
return false;
|
|
5074
5800
|
}
|
|
5075
5801
|
};
|
|
5076
5802
|
}
|
|
5077
5803
|
});
|
|
5078
5804
|
|
|
5079
5805
|
// src/nodes/BulletList.ts
|
|
5080
|
-
var BulletList =
|
|
5806
|
+
var BulletList = Node2.create({
|
|
5081
5807
|
name: "bulletList",
|
|
5082
5808
|
group: "block list",
|
|
5083
5809
|
content: "listItem+",
|
|
@@ -5116,6 +5842,24 @@ var BulletList = Node.create({
|
|
|
5116
5842
|
}
|
|
5117
5843
|
];
|
|
5118
5844
|
},
|
|
5845
|
+
addFloatingMenuItems() {
|
|
5846
|
+
return [
|
|
5847
|
+
{
|
|
5848
|
+
name: "bullet-list",
|
|
5849
|
+
label: "Bulleted list",
|
|
5850
|
+
description: "Create a simple bulleted list",
|
|
5851
|
+
icon: "listBullets",
|
|
5852
|
+
group: "Lists",
|
|
5853
|
+
priority: 200,
|
|
5854
|
+
keywords: ["bullet", "list", "unordered", "ul"],
|
|
5855
|
+
shortcut: "- ",
|
|
5856
|
+
command: "toggleBulletList",
|
|
5857
|
+
// Don't offer "Bulleted list" while cursor is already inside one,
|
|
5858
|
+
// otherwise picking it lifts the user out of the list.
|
|
5859
|
+
hideWhenInside: ["bulletList"]
|
|
5860
|
+
}
|
|
5861
|
+
];
|
|
5862
|
+
},
|
|
5119
5863
|
addKeyboardShortcuts() {
|
|
5120
5864
|
const { editor } = this;
|
|
5121
5865
|
return {
|
|
@@ -5144,7 +5888,7 @@ var BulletList = Node.create({
|
|
|
5144
5888
|
});
|
|
5145
5889
|
|
|
5146
5890
|
// src/nodes/OrderedList.ts
|
|
5147
|
-
var OrderedList =
|
|
5891
|
+
var OrderedList = Node2.create({
|
|
5148
5892
|
name: "orderedList",
|
|
5149
5893
|
group: "block list",
|
|
5150
5894
|
content: "listItem+",
|
|
@@ -5201,6 +5945,22 @@ var OrderedList = Node.create({
|
|
|
5201
5945
|
}
|
|
5202
5946
|
];
|
|
5203
5947
|
},
|
|
5948
|
+
addFloatingMenuItems() {
|
|
5949
|
+
return [
|
|
5950
|
+
{
|
|
5951
|
+
name: "ordered-list",
|
|
5952
|
+
label: "Numbered list",
|
|
5953
|
+
description: "Create a numbered list",
|
|
5954
|
+
icon: "listNumbers",
|
|
5955
|
+
group: "Lists",
|
|
5956
|
+
priority: 190,
|
|
5957
|
+
keywords: ["ordered", "numbered", "list", "ol", "1."],
|
|
5958
|
+
shortcut: "1. ",
|
|
5959
|
+
command: "toggleOrderedList",
|
|
5960
|
+
hideWhenInside: ["orderedList"]
|
|
5961
|
+
}
|
|
5962
|
+
];
|
|
5963
|
+
},
|
|
5204
5964
|
addKeyboardShortcuts() {
|
|
5205
5965
|
const { editor } = this;
|
|
5206
5966
|
return {
|
|
@@ -5231,7 +5991,7 @@ var OrderedList = Node.create({
|
|
|
5231
5991
|
];
|
|
5232
5992
|
}
|
|
5233
5993
|
});
|
|
5234
|
-
var HorizontalRule =
|
|
5994
|
+
var HorizontalRule = Node2.create({
|
|
5235
5995
|
name: "horizontalRule",
|
|
5236
5996
|
group: "block",
|
|
5237
5997
|
addOptions() {
|
|
@@ -5252,19 +6012,31 @@ var HorizontalRule = Node.create({
|
|
|
5252
6012
|
const { $from } = tr.selection;
|
|
5253
6013
|
const parent = $from.parent;
|
|
5254
6014
|
if (!parent.isTextblock) return false;
|
|
6015
|
+
const paragraphType = state.schema.nodes["paragraph"];
|
|
6016
|
+
const hrNode = this.nodeType.create();
|
|
6017
|
+
const trailingParagraph = paragraphType?.create();
|
|
6018
|
+
const nodes = trailingParagraph ? [hrNode, trailingParagraph] : [hrNode];
|
|
6019
|
+
const listRange = splitListForInsert(state, tr);
|
|
6020
|
+
if (listRange) {
|
|
6021
|
+
if (!dispatch) return true;
|
|
6022
|
+
tr.replaceWith(listRange.from, listRange.to, nodes);
|
|
6023
|
+
const sel = TextSelection.findFrom(
|
|
6024
|
+
tr.doc.resolve(listRange.from + 1),
|
|
6025
|
+
1
|
|
6026
|
+
);
|
|
6027
|
+
if (sel) tr.setSelection(sel);
|
|
6028
|
+
dispatch(tr.scrollIntoView());
|
|
6029
|
+
return true;
|
|
6030
|
+
}
|
|
5255
6031
|
if (dispatch) {
|
|
5256
6032
|
if (parent.content.size === 0 && parent.type.name === "paragraph") {
|
|
5257
6033
|
const from = $from.before();
|
|
5258
6034
|
const to = $from.after();
|
|
5259
|
-
const paragraph = state.schema.nodes["paragraph"]?.create();
|
|
5260
|
-
const nodes = paragraph ? [this.nodeType.create(), paragraph] : [this.nodeType.create()];
|
|
5261
6035
|
tr.replaceWith(from, to, nodes);
|
|
5262
6036
|
const sel = TextSelection.findFrom(tr.doc.resolve(from + 1), 1);
|
|
5263
6037
|
if (sel) tr.setSelection(sel);
|
|
5264
6038
|
} else {
|
|
5265
6039
|
const end = $from.after();
|
|
5266
|
-
const paragraph = state.schema.nodes["paragraph"]?.create();
|
|
5267
|
-
const nodes = paragraph ? [this.nodeType.create(), paragraph] : [this.nodeType.create()];
|
|
5268
6040
|
tr.insert(end, nodes);
|
|
5269
6041
|
const sel = TextSelection.findFrom(tr.doc.resolve(end + 1), 1);
|
|
5270
6042
|
if (sel) tr.setSelection(sel);
|
|
@@ -5288,6 +6060,21 @@ var HorizontalRule = Node.create({
|
|
|
5288
6060
|
}
|
|
5289
6061
|
];
|
|
5290
6062
|
},
|
|
6063
|
+
addFloatingMenuItems() {
|
|
6064
|
+
return [
|
|
6065
|
+
{
|
|
6066
|
+
name: "horizontal-rule",
|
|
6067
|
+
label: "Divider",
|
|
6068
|
+
description: "Insert a horizontal rule",
|
|
6069
|
+
icon: "minus",
|
|
6070
|
+
group: "Basic",
|
|
6071
|
+
priority: 150,
|
|
6072
|
+
keywords: ["divider", "hr", "line", "separator", "horizontal rule"],
|
|
6073
|
+
shortcut: "--- ",
|
|
6074
|
+
command: "setHorizontalRule"
|
|
6075
|
+
}
|
|
6076
|
+
];
|
|
6077
|
+
},
|
|
5291
6078
|
addInputRules() {
|
|
5292
6079
|
const { nodeType } = this;
|
|
5293
6080
|
if (!nodeType) {
|
|
@@ -5318,7 +6105,7 @@ var HorizontalRule = Node.create({
|
|
|
5318
6105
|
});
|
|
5319
6106
|
|
|
5320
6107
|
// src/nodes/HardBreak.ts
|
|
5321
|
-
var HardBreak =
|
|
6108
|
+
var HardBreak = Node2.create({
|
|
5322
6109
|
name: "hardBreak",
|
|
5323
6110
|
group: "inline",
|
|
5324
6111
|
inline: true,
|
|
@@ -5378,9 +6165,84 @@ var HardBreak = Node.create({
|
|
|
5378
6165
|
};
|
|
5379
6166
|
}
|
|
5380
6167
|
});
|
|
5381
|
-
|
|
6168
|
+
|
|
6169
|
+
// src/nodes/TaskItemNodeView.ts
|
|
6170
|
+
var TaskItemNodeView = class {
|
|
6171
|
+
dom;
|
|
6172
|
+
contentDOM;
|
|
6173
|
+
label;
|
|
6174
|
+
input;
|
|
6175
|
+
view;
|
|
6176
|
+
nodeType;
|
|
6177
|
+
getPos;
|
|
6178
|
+
node;
|
|
6179
|
+
constructor({ options, node, view, getPos }) {
|
|
6180
|
+
this.view = view;
|
|
6181
|
+
this.nodeType = node.type;
|
|
6182
|
+
this.node = node;
|
|
6183
|
+
this.getPos = getPos;
|
|
6184
|
+
this.dom = document.createElement("li");
|
|
6185
|
+
for (const [key, value] of Object.entries(options.HTMLAttributes)) {
|
|
6186
|
+
if (value !== null && value !== void 0) {
|
|
6187
|
+
this.dom.setAttribute(key, typeof value === "string" ? value : JSON.stringify(value));
|
|
6188
|
+
}
|
|
6189
|
+
}
|
|
6190
|
+
this.dom.setAttribute("data-type", "taskItem");
|
|
6191
|
+
this.dom.setAttribute("data-checked", node.attrs["checked"] ? "true" : "false");
|
|
6192
|
+
this.label = document.createElement("label");
|
|
6193
|
+
this.label.setAttribute("contenteditable", "false");
|
|
6194
|
+
this.input = document.createElement("input");
|
|
6195
|
+
this.input.type = "checkbox";
|
|
6196
|
+
this.input.checked = !!node.attrs["checked"];
|
|
6197
|
+
this.input.disabled = !view.editable;
|
|
6198
|
+
this.input.setAttribute("aria-label", "Task status");
|
|
6199
|
+
this.label.appendChild(this.input);
|
|
6200
|
+
this.dom.appendChild(this.label);
|
|
6201
|
+
this.contentDOM = document.createElement("div");
|
|
6202
|
+
this.dom.appendChild(this.contentDOM);
|
|
6203
|
+
this.input.addEventListener("change", this.handleChange);
|
|
6204
|
+
}
|
|
6205
|
+
handleChange = (event) => {
|
|
6206
|
+
event.preventDefault();
|
|
6207
|
+
if (!this.view.editable) {
|
|
6208
|
+
this.input.checked = !!this.node.attrs["checked"];
|
|
6209
|
+
return;
|
|
6210
|
+
}
|
|
6211
|
+
const pos = this.getPos();
|
|
6212
|
+
if (pos === void 0) return;
|
|
6213
|
+
const { state, dispatch } = this.view;
|
|
6214
|
+
const tr = state.tr.setNodeMarkup(pos, void 0, {
|
|
6215
|
+
...this.node.attrs,
|
|
6216
|
+
checked: this.input.checked
|
|
6217
|
+
});
|
|
6218
|
+
dispatch(tr);
|
|
6219
|
+
};
|
|
6220
|
+
update(node) {
|
|
6221
|
+
if (node.type !== this.nodeType) return false;
|
|
6222
|
+
this.node = node;
|
|
6223
|
+
const checked = !!node.attrs["checked"];
|
|
6224
|
+
this.dom.setAttribute("data-checked", checked ? "true" : "false");
|
|
6225
|
+
this.input.checked = checked;
|
|
6226
|
+
this.input.disabled = !this.view.editable;
|
|
6227
|
+
return true;
|
|
6228
|
+
}
|
|
6229
|
+
stopEvent(event) {
|
|
6230
|
+
return event.target instanceof Node && this.label.contains(event.target);
|
|
6231
|
+
}
|
|
6232
|
+
ignoreMutation(mutation) {
|
|
6233
|
+
return mutation.target instanceof Node && this.label.contains(mutation.target);
|
|
6234
|
+
}
|
|
6235
|
+
destroy() {
|
|
6236
|
+
this.input.removeEventListener("change", this.handleChange);
|
|
6237
|
+
}
|
|
6238
|
+
};
|
|
6239
|
+
|
|
6240
|
+
// src/nodes/TaskItem.ts
|
|
6241
|
+
var TaskItem = Node2.create({
|
|
5382
6242
|
name: "taskItem",
|
|
5383
|
-
|
|
6243
|
+
// Paragraph must be the first child so flex alignment binds to the label
|
|
6244
|
+
// baseline; a heading-first child would visually break the checkbox.
|
|
6245
|
+
content: "paragraph block*",
|
|
5384
6246
|
defining: true,
|
|
5385
6247
|
addOptions() {
|
|
5386
6248
|
return {
|
|
@@ -5434,6 +6296,10 @@ var TaskItem = Node.create({
|
|
|
5434
6296
|
["div", 0]
|
|
5435
6297
|
];
|
|
5436
6298
|
},
|
|
6299
|
+
addNodeView() {
|
|
6300
|
+
const options = this.options;
|
|
6301
|
+
return (node, view, getPos) => new TaskItemNodeView({ options, node, view, getPos });
|
|
6302
|
+
},
|
|
5437
6303
|
addCommands() {
|
|
5438
6304
|
const { name } = this;
|
|
5439
6305
|
return {
|
|
@@ -5469,7 +6335,16 @@ var TaskItem = Node.create({
|
|
|
5469
6335
|
const { state, view } = this.editor;
|
|
5470
6336
|
const { $from } = state.selection;
|
|
5471
6337
|
if ($from.depth < 1 || $from.node(-1).type !== this.nodeType) return false;
|
|
5472
|
-
if (
|
|
6338
|
+
if ($from.parent.type.name !== "paragraph") return false;
|
|
6339
|
+
const ctx = getListItemCursorContext($from);
|
|
6340
|
+
if (ctx?.isInChildrenZone) {
|
|
6341
|
+
if (ctx.paragraphIsEmpty) {
|
|
6342
|
+
if (insertChildrenZoneSibling(state, view.dispatch, ctx)) return true;
|
|
6343
|
+
} else {
|
|
6344
|
+
if (splitBlock(state, view.dispatch)) return true;
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
6347
|
+
if (splitListItem(this.nodeType, { checked: false })(state, view.dispatch)) return true;
|
|
5473
6348
|
if ($from.parent.content.size === 0) {
|
|
5474
6349
|
const listItemType = state.schema.nodes["listItem"];
|
|
5475
6350
|
if (listItemType) {
|
|
@@ -5525,6 +6400,12 @@ var TaskItem = Node.create({
|
|
|
5525
6400
|
const { state, view } = this.editor;
|
|
5526
6401
|
const { $from, empty } = state.selection;
|
|
5527
6402
|
if (!empty || $from.parentOffset !== 0) return false;
|
|
6403
|
+
if ($from.parent.content.size === 0) {
|
|
6404
|
+
const ctx = getListItemCursorContext($from);
|
|
6405
|
+
if (ctx && ctx.isInChildrenZone && ctx.paragraphIsEmpty) {
|
|
6406
|
+
if (liftEmptyChildrenZoneParagraph(state, view.dispatch, ctx)) return true;
|
|
6407
|
+
}
|
|
6408
|
+
}
|
|
5528
6409
|
let taskItemDepth = -1;
|
|
5529
6410
|
for (let d = $from.depth; d > 0; d--) {
|
|
5530
6411
|
if ($from.node(d).type === this.nodeType) {
|
|
@@ -5544,7 +6425,7 @@ var TaskItem = Node.create({
|
|
|
5544
6425
|
});
|
|
5545
6426
|
|
|
5546
6427
|
// src/nodes/TaskList.ts
|
|
5547
|
-
var TaskList =
|
|
6428
|
+
var TaskList = Node2.create({
|
|
5548
6429
|
name: "taskList",
|
|
5549
6430
|
group: "block list",
|
|
5550
6431
|
content: "taskItem+",
|
|
@@ -5605,6 +6486,22 @@ var TaskList = Node.create({
|
|
|
5605
6486
|
}
|
|
5606
6487
|
];
|
|
5607
6488
|
},
|
|
6489
|
+
addFloatingMenuItems() {
|
|
6490
|
+
return [
|
|
6491
|
+
{
|
|
6492
|
+
name: "task-list",
|
|
6493
|
+
label: "To-do list",
|
|
6494
|
+
description: "Track tasks with a checkbox list",
|
|
6495
|
+
icon: "listChecks",
|
|
6496
|
+
group: "Lists",
|
|
6497
|
+
priority: 180,
|
|
6498
|
+
keywords: ["todo", "task", "checkbox", "check"],
|
|
6499
|
+
shortcut: "[ ] ",
|
|
6500
|
+
command: "toggleTaskList",
|
|
6501
|
+
hideWhenInside: ["taskList"]
|
|
6502
|
+
}
|
|
6503
|
+
];
|
|
6504
|
+
},
|
|
5608
6505
|
addExtensions() {
|
|
5609
6506
|
return [TaskItem];
|
|
5610
6507
|
},
|
|
@@ -6530,7 +7427,8 @@ var TextStyle = Mark.create({
|
|
|
6530
7427
|
getAttrs: (element) => {
|
|
6531
7428
|
if (typeof element === "string") return false;
|
|
6532
7429
|
const hasStyles = element.hasAttribute("style");
|
|
6533
|
-
|
|
7430
|
+
const hasColorTokens = element.hasAttribute("data-text-color") || element.hasAttribute("data-bg-color");
|
|
7431
|
+
if (!hasStyles && !hasColorTokens) return false;
|
|
6534
7432
|
return {};
|
|
6535
7433
|
}
|
|
6536
7434
|
},
|
|
@@ -6799,6 +7697,123 @@ var Placeholder = Extension.create({
|
|
|
6799
7697
|
];
|
|
6800
7698
|
}
|
|
6801
7699
|
});
|
|
7700
|
+
var LIST_ITEM_TYPES3 = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
|
|
7701
|
+
var LIST_WRAPPER_TYPES2 = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
|
|
7702
|
+
function isCursorInsideListItem(state) {
|
|
7703
|
+
const { $from } = state.selection;
|
|
7704
|
+
for (let d = $from.depth; d > 0; d--) {
|
|
7705
|
+
if (LIST_ITEM_TYPES3.has($from.node(d).type.name)) return true;
|
|
7706
|
+
}
|
|
7707
|
+
return false;
|
|
7708
|
+
}
|
|
7709
|
+
function indentBlockAsListChild(state, dispatch) {
|
|
7710
|
+
const { selection } = state;
|
|
7711
|
+
let blockIndex;
|
|
7712
|
+
let blockNode;
|
|
7713
|
+
let blockStart;
|
|
7714
|
+
let blockEnd;
|
|
7715
|
+
if (selection instanceof NodeSelection) {
|
|
7716
|
+
const node = selection.node;
|
|
7717
|
+
const $pos = selection.$from;
|
|
7718
|
+
if ($pos.depth !== 0) return false;
|
|
7719
|
+
if (isCursorInsideListItem(state)) return false;
|
|
7720
|
+
blockIndex = $pos.index(0);
|
|
7721
|
+
blockNode = node;
|
|
7722
|
+
blockStart = selection.from;
|
|
7723
|
+
blockEnd = selection.to;
|
|
7724
|
+
} else {
|
|
7725
|
+
if (!selection.empty) return false;
|
|
7726
|
+
if (isCursorInsideListItem(state)) return false;
|
|
7727
|
+
const { $from } = selection;
|
|
7728
|
+
if ($from.depth !== 1) return false;
|
|
7729
|
+
blockIndex = $from.index(0);
|
|
7730
|
+
blockNode = $from.node(1);
|
|
7731
|
+
blockStart = $from.before(1);
|
|
7732
|
+
blockEnd = $from.after(1);
|
|
7733
|
+
}
|
|
7734
|
+
if (blockIndex === 0) return false;
|
|
7735
|
+
const prevSibling = state.doc.child(blockIndex - 1);
|
|
7736
|
+
if (!LIST_WRAPPER_TYPES2.has(prevSibling.type.name)) return false;
|
|
7737
|
+
let wrapperPos = 0;
|
|
7738
|
+
for (let i = 0; i < blockIndex - 1; i++) {
|
|
7739
|
+
wrapperPos += state.doc.child(i).nodeSize;
|
|
7740
|
+
}
|
|
7741
|
+
const tr = state.tr;
|
|
7742
|
+
const result = insertAsListItemChild({
|
|
7743
|
+
tr,
|
|
7744
|
+
wrapperPos,
|
|
7745
|
+
blockNode,
|
|
7746
|
+
sourceRange: { from: blockStart, to: blockEnd }
|
|
7747
|
+
});
|
|
7748
|
+
if (!result.ok || result.insertedAt === void 0) return false;
|
|
7749
|
+
if (!dispatch) return true;
|
|
7750
|
+
tr.setSelection(Selection.near(tr.doc.resolve(result.insertedAt + 1)));
|
|
7751
|
+
dispatch(tr.scrollIntoView());
|
|
7752
|
+
return true;
|
|
7753
|
+
}
|
|
7754
|
+
function outdentBlockFromListItem(state, dispatch) {
|
|
7755
|
+
const { selection } = state;
|
|
7756
|
+
if (!selection.empty) return false;
|
|
7757
|
+
const { $from } = selection;
|
|
7758
|
+
let listItemDepth = -1;
|
|
7759
|
+
for (let d = $from.depth; d > 0; d--) {
|
|
7760
|
+
if (LIST_ITEM_TYPES3.has($from.node(d).type.name)) {
|
|
7761
|
+
listItemDepth = d;
|
|
7762
|
+
break;
|
|
7763
|
+
}
|
|
7764
|
+
}
|
|
7765
|
+
if (listItemDepth === -1) return false;
|
|
7766
|
+
const blockDepth = listItemDepth + 1;
|
|
7767
|
+
if ($from.depth < blockDepth) return false;
|
|
7768
|
+
const listItem = $from.node(listItemDepth);
|
|
7769
|
+
const blockIndexInItem = $from.index(listItemDepth);
|
|
7770
|
+
if (blockIndexInItem === 0) return false;
|
|
7771
|
+
if (blockIndexInItem !== listItem.childCount - 1) return false;
|
|
7772
|
+
const wrapperDepth = listItemDepth - 1;
|
|
7773
|
+
if (wrapperDepth < 0) return false;
|
|
7774
|
+
const liIndexInWrapper = $from.index(wrapperDepth);
|
|
7775
|
+
const wrapper = $from.node(wrapperDepth);
|
|
7776
|
+
if (liIndexInWrapper !== wrapper.childCount - 1) return false;
|
|
7777
|
+
if (wrapperDepth - 1 < 0) return false;
|
|
7778
|
+
const wrapperParent = $from.node(wrapperDepth - 1);
|
|
7779
|
+
const wrapperIndexInParent = $from.index(wrapperDepth - 1);
|
|
7780
|
+
const blockNode = $from.node(blockDepth);
|
|
7781
|
+
if (!wrapperParent.canReplaceWith(
|
|
7782
|
+
wrapperIndexInParent + 1,
|
|
7783
|
+
wrapperIndexInParent + 1,
|
|
7784
|
+
blockNode.type
|
|
7785
|
+
)) {
|
|
7786
|
+
return false;
|
|
7787
|
+
}
|
|
7788
|
+
if (!dispatch) return true;
|
|
7789
|
+
const blockStart = $from.before(blockDepth);
|
|
7790
|
+
const blockEnd = $from.after(blockDepth);
|
|
7791
|
+
const wrapperEnd = $from.after(wrapperDepth);
|
|
7792
|
+
const tr = state.tr;
|
|
7793
|
+
const blockSize = blockEnd - blockStart;
|
|
7794
|
+
tr.delete(blockStart, blockEnd);
|
|
7795
|
+
const insertAt = wrapperEnd - blockSize;
|
|
7796
|
+
tr.insert(insertAt, blockNode);
|
|
7797
|
+
tr.setSelection(Selection.near(tr.doc.resolve(insertAt + 1)));
|
|
7798
|
+
dispatch(tr.scrollIntoView());
|
|
7799
|
+
return true;
|
|
7800
|
+
}
|
|
7801
|
+
var ListIndent = Extension.create({
|
|
7802
|
+
name: "listIndent",
|
|
7803
|
+
addKeyboardShortcuts() {
|
|
7804
|
+
const { editor } = this;
|
|
7805
|
+
return {
|
|
7806
|
+
Tab: () => {
|
|
7807
|
+
if (!editor) return false;
|
|
7808
|
+
return indentBlockAsListChild(editor.state, editor.view.dispatch);
|
|
7809
|
+
},
|
|
7810
|
+
"Shift-Tab": () => {
|
|
7811
|
+
if (!editor) return false;
|
|
7812
|
+
return outdentBlockFromListItem(editor.state, editor.view.dispatch);
|
|
7813
|
+
}
|
|
7814
|
+
};
|
|
7815
|
+
}
|
|
7816
|
+
});
|
|
6802
7817
|
var characterCountPluginKey = new PluginKey("characterCount");
|
|
6803
7818
|
var CharacterCount = Extension.create({
|
|
6804
7819
|
name: "characterCount",
|
|
@@ -7225,6 +8240,10 @@ var LineHeight = Extension.create({
|
|
|
7225
8240
|
}
|
|
7226
8241
|
});
|
|
7227
8242
|
function generateUUID() {
|
|
8243
|
+
if (typeof globalThis !== "undefined") {
|
|
8244
|
+
const c = globalThis.crypto;
|
|
8245
|
+
if (c && typeof c.randomUUID === "function") return c.randomUUID();
|
|
8246
|
+
}
|
|
7228
8247
|
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
7229
8248
|
const r = Math.random() * 16 | 0;
|
|
7230
8249
|
const v = c === "x" ? r : r & 3 | 8;
|
|
@@ -7310,15 +8329,31 @@ var UniqueID = Extension.create({
|
|
|
7310
8329
|
);
|
|
7311
8330
|
};
|
|
7312
8331
|
const assignMissingIDs = (doc, tr) => {
|
|
8332
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7313
8333
|
doc.descendants((node, pos) => {
|
|
7314
8334
|
if (!types.includes(node.type.name)) return;
|
|
7315
8335
|
const existingID = node.attrs[attributeName];
|
|
7316
8336
|
if (!existingID) {
|
|
7317
|
-
|
|
8337
|
+
let id = generateID();
|
|
8338
|
+
while (seen.has(id)) id = generateID();
|
|
8339
|
+
seen.add(id);
|
|
8340
|
+
tr.setNodeMarkup(tr.mapping.map(pos), void 0, {
|
|
8341
|
+
...node.attrs,
|
|
8342
|
+
[attributeName]: id
|
|
8343
|
+
});
|
|
8344
|
+
return;
|
|
8345
|
+
}
|
|
8346
|
+
if (seen.has(existingID)) {
|
|
8347
|
+
let id = generateID();
|
|
8348
|
+
while (seen.has(id)) id = generateID();
|
|
8349
|
+
seen.add(id);
|
|
8350
|
+
tr.setNodeMarkup(tr.mapping.map(pos), void 0, {
|
|
7318
8351
|
...node.attrs,
|
|
7319
|
-
[attributeName]:
|
|
8352
|
+
[attributeName]: id
|
|
7320
8353
|
});
|
|
8354
|
+
return;
|
|
7321
8355
|
}
|
|
8356
|
+
seen.add(existingID);
|
|
7322
8357
|
});
|
|
7323
8358
|
};
|
|
7324
8359
|
return [
|
|
@@ -7355,7 +8390,148 @@ var UniqueID = Extension.create({
|
|
|
7355
8390
|
];
|
|
7356
8391
|
}
|
|
7357
8392
|
});
|
|
7358
|
-
|
|
8393
|
+
|
|
8394
|
+
// src/extensions/BlockColor.ts
|
|
8395
|
+
var DEFAULT_BLOCK_COLORS = [
|
|
8396
|
+
"gray",
|
|
8397
|
+
"brown",
|
|
8398
|
+
"orange",
|
|
8399
|
+
"yellow",
|
|
8400
|
+
"green",
|
|
8401
|
+
"blue",
|
|
8402
|
+
"purple",
|
|
8403
|
+
"pink",
|
|
8404
|
+
"red"
|
|
8405
|
+
];
|
|
8406
|
+
var DEFAULT_BLOCK_COLOR_TYPES = [
|
|
8407
|
+
"paragraph",
|
|
8408
|
+
"heading",
|
|
8409
|
+
"blockquote",
|
|
8410
|
+
"bulletList",
|
|
8411
|
+
"orderedList",
|
|
8412
|
+
"taskList",
|
|
8413
|
+
"listItem",
|
|
8414
|
+
"taskItem"
|
|
8415
|
+
];
|
|
8416
|
+
function stripInlineColorConflicts(tr, state, from, to, which) {
|
|
8417
|
+
const textStyleType = state.schema.marks["textStyle"];
|
|
8418
|
+
if (!textStyleType) return;
|
|
8419
|
+
const inlineKeys = [];
|
|
8420
|
+
if (which === "text" || which === "both") inlineKeys.push("color", "colorToken");
|
|
8421
|
+
if (which === "bg" || which === "both") inlineKeys.push("backgroundColor", "backgroundColorToken");
|
|
8422
|
+
state.doc.nodesBetween(from, to, (node, pos) => {
|
|
8423
|
+
if (!node.isText) return true;
|
|
8424
|
+
const existing = node.marks.find((m) => m.type === textStyleType);
|
|
8425
|
+
if (!existing) return false;
|
|
8426
|
+
const hasConflict = inlineKeys.some((k) => existing.attrs[k] !== null && existing.attrs[k] !== void 0);
|
|
8427
|
+
if (!hasConflict) return false;
|
|
8428
|
+
const start = Math.max(pos, from);
|
|
8429
|
+
const end = Math.min(pos + node.nodeSize, to);
|
|
8430
|
+
const newAttrs = { ...existing.attrs };
|
|
8431
|
+
for (const k of inlineKeys) newAttrs[k] = null;
|
|
8432
|
+
const stillUsed = Object.values(newAttrs).some((v) => v !== null && v !== void 0);
|
|
8433
|
+
tr.removeMark(start, end, existing);
|
|
8434
|
+
if (stillUsed) tr.addMark(start, end, textStyleType.create(newAttrs));
|
|
8435
|
+
return false;
|
|
8436
|
+
});
|
|
8437
|
+
}
|
|
8438
|
+
var BlockColor = Extension.create({
|
|
8439
|
+
name: "blockColor",
|
|
8440
|
+
addOptions() {
|
|
8441
|
+
return {
|
|
8442
|
+
types: DEFAULT_BLOCK_COLOR_TYPES,
|
|
8443
|
+
bgColors: DEFAULT_BLOCK_COLORS,
|
|
8444
|
+
textColors: DEFAULT_BLOCK_COLORS
|
|
8445
|
+
};
|
|
8446
|
+
},
|
|
8447
|
+
// Defensive fallback: an empty `bgColors`/`textColors` array would render
|
|
8448
|
+
// a broken Colors UI (section title with only the null-reset swatch).
|
|
8449
|
+
// Replace empties with the default palette so users who pass `{}` as an
|
|
8450
|
+
// override still get a working picker. Runs AFTER `addOptions()` merges
|
|
8451
|
+
// user config with defaults, so this only fires for explicit empty arrays.
|
|
8452
|
+
onBeforeCreate() {
|
|
8453
|
+
if (this.options.bgColors.length === 0) this.options.bgColors = DEFAULT_BLOCK_COLORS;
|
|
8454
|
+
if (this.options.textColors.length === 0) this.options.textColors = DEFAULT_BLOCK_COLORS;
|
|
8455
|
+
},
|
|
8456
|
+
addGlobalAttributes() {
|
|
8457
|
+
return [
|
|
8458
|
+
{
|
|
8459
|
+
types: this.options.types,
|
|
8460
|
+
attributes: {
|
|
8461
|
+
bgColor: {
|
|
8462
|
+
default: null,
|
|
8463
|
+
parseHTML: (element) => element.getAttribute("data-bg-color"),
|
|
8464
|
+
renderHTML: (attributes) => {
|
|
8465
|
+
const v = attributes["bgColor"];
|
|
8466
|
+
if (!v) return null;
|
|
8467
|
+
return { "data-bg-color": v };
|
|
8468
|
+
}
|
|
8469
|
+
},
|
|
8470
|
+
textColor: {
|
|
8471
|
+
default: null,
|
|
8472
|
+
parseHTML: (element) => element.getAttribute("data-text-color"),
|
|
8473
|
+
renderHTML: (attributes) => {
|
|
8474
|
+
const v = attributes["textColor"];
|
|
8475
|
+
if (!v) return null;
|
|
8476
|
+
return { "data-text-color": v };
|
|
8477
|
+
}
|
|
8478
|
+
}
|
|
8479
|
+
}
|
|
8480
|
+
}
|
|
8481
|
+
];
|
|
8482
|
+
},
|
|
8483
|
+
addCommands() {
|
|
8484
|
+
const types = this.options.types;
|
|
8485
|
+
const bgColors = this.options.bgColors;
|
|
8486
|
+
const textColors = this.options.textColors;
|
|
8487
|
+
function findTargetPos(state) {
|
|
8488
|
+
const { $from } = state.selection;
|
|
8489
|
+
for (let depth = $from.depth; depth >= 0; depth--) {
|
|
8490
|
+
const node = $from.node(depth);
|
|
8491
|
+
if (types.includes(node.type.name)) {
|
|
8492
|
+
return depth === 0 ? 0 : $from.before(depth);
|
|
8493
|
+
}
|
|
8494
|
+
}
|
|
8495
|
+
return null;
|
|
8496
|
+
}
|
|
8497
|
+
function setAttr(attr, palette) {
|
|
8498
|
+
return (color) => ({ state, dispatch }) => {
|
|
8499
|
+
if (color !== null && !palette.includes(color)) return false;
|
|
8500
|
+
const pos = findTargetPos(state);
|
|
8501
|
+
if (pos === null) return false;
|
|
8502
|
+
const node = state.doc.nodeAt(pos);
|
|
8503
|
+
if (!node) return false;
|
|
8504
|
+
if (dispatch) {
|
|
8505
|
+
const tr = state.tr.setNodeMarkup(pos, void 0, { ...node.attrs, [attr]: color });
|
|
8506
|
+
stripInlineColorConflicts(tr, state, pos, pos + node.nodeSize, attr === "textColor" ? "text" : "bg");
|
|
8507
|
+
dispatch(tr);
|
|
8508
|
+
}
|
|
8509
|
+
return true;
|
|
8510
|
+
};
|
|
8511
|
+
}
|
|
8512
|
+
return {
|
|
8513
|
+
setBlockBgColor: setAttr("bgColor", bgColors),
|
|
8514
|
+
setBlockTextColor: setAttr("textColor", textColors),
|
|
8515
|
+
unsetBlockColors: () => ({ state, dispatch }) => {
|
|
8516
|
+
const pos = findTargetPos(state);
|
|
8517
|
+
if (pos === null) return false;
|
|
8518
|
+
const node = state.doc.nodeAt(pos);
|
|
8519
|
+
if (!node) return false;
|
|
8520
|
+
if (dispatch) {
|
|
8521
|
+
const tr = state.tr.setNodeMarkup(pos, void 0, {
|
|
8522
|
+
...node.attrs,
|
|
8523
|
+
bgColor: null,
|
|
8524
|
+
textColor: null
|
|
8525
|
+
});
|
|
8526
|
+
stripInlineColorConflicts(tr, state, pos, pos + node.nodeSize, "both");
|
|
8527
|
+
dispatch(tr);
|
|
8528
|
+
}
|
|
8529
|
+
return true;
|
|
8530
|
+
}
|
|
8531
|
+
};
|
|
8532
|
+
}
|
|
8533
|
+
});
|
|
8534
|
+
var Selection4 = Extension.create({
|
|
7359
8535
|
name: "selection",
|
|
7360
8536
|
addStorage() {
|
|
7361
8537
|
return {
|
|
@@ -7745,9 +8921,19 @@ var TextColor = Extension.create({
|
|
|
7745
8921
|
},
|
|
7746
8922
|
renderHTML: (attributes) => {
|
|
7747
8923
|
const color = attributes["color"];
|
|
7748
|
-
|
|
8924
|
+
const token = attributes["colorToken"];
|
|
8925
|
+
if (!color || token) return null;
|
|
7749
8926
|
return { style: `color: ${color}` };
|
|
7750
8927
|
}
|
|
8928
|
+
},
|
|
8929
|
+
colorToken: {
|
|
8930
|
+
default: null,
|
|
8931
|
+
parseHTML: (element) => element.getAttribute("data-text-color"),
|
|
8932
|
+
renderHTML: (attributes) => {
|
|
8933
|
+
const token = attributes["colorToken"];
|
|
8934
|
+
if (!token) return null;
|
|
8935
|
+
return { "data-text-color": token };
|
|
8936
|
+
}
|
|
7751
8937
|
}
|
|
7752
8938
|
}
|
|
7753
8939
|
}
|
|
@@ -7755,13 +8941,28 @@ var TextColor = Extension.create({
|
|
|
7755
8941
|
},
|
|
7756
8942
|
addCommands() {
|
|
7757
8943
|
return {
|
|
8944
|
+
// Hex-based color. Mutual exclusion: clears the named token so the
|
|
8945
|
+
// legacy hex picker and Notion-style picker can't write conflicting
|
|
8946
|
+
// values to the same mark.
|
|
7758
8947
|
setTextColor: (color) => ({ commands }) => {
|
|
7759
|
-
return commands.setMark("textStyle", { color });
|
|
8948
|
+
return commands.setMark("textStyle", { color, colorToken: null });
|
|
7760
8949
|
},
|
|
7761
8950
|
unsetTextColor: () => ({ commands }) => {
|
|
7762
8951
|
if (!commands.setMark("textStyle", { color: null })) return false;
|
|
7763
8952
|
commands.removeEmptyTextStyle();
|
|
7764
8953
|
return true;
|
|
8954
|
+
},
|
|
8955
|
+
// Named-token color. Mutual exclusion: clears the hex `color` so the
|
|
8956
|
+
// theme-aware data attribute is the only source of truth.
|
|
8957
|
+
setTextColorToken: (token) => ({ commands }) => {
|
|
8958
|
+
if (!commands.setMark("textStyle", { colorToken: token, color: null })) return false;
|
|
8959
|
+
if (token === null) commands.removeEmptyTextStyle();
|
|
8960
|
+
return true;
|
|
8961
|
+
},
|
|
8962
|
+
unsetTextColorToken: () => ({ commands }) => {
|
|
8963
|
+
if (!commands.setMark("textStyle", { colorToken: null })) return false;
|
|
8964
|
+
commands.removeEmptyTextStyle();
|
|
8965
|
+
return true;
|
|
7765
8966
|
}
|
|
7766
8967
|
};
|
|
7767
8968
|
},
|
|
@@ -7863,9 +9064,19 @@ var Highlight = Extension.create({
|
|
|
7863
9064
|
},
|
|
7864
9065
|
renderHTML: (attributes) => {
|
|
7865
9066
|
const bg = attributes["backgroundColor"];
|
|
7866
|
-
|
|
9067
|
+
const token = attributes["backgroundColorToken"];
|
|
9068
|
+
if (!bg || token) return null;
|
|
7867
9069
|
return { style: `background-color: ${bg}` };
|
|
7868
9070
|
}
|
|
9071
|
+
},
|
|
9072
|
+
backgroundColorToken: {
|
|
9073
|
+
default: null,
|
|
9074
|
+
parseHTML: (element) => element.getAttribute("data-bg-color"),
|
|
9075
|
+
renderHTML: (attributes) => {
|
|
9076
|
+
const token = attributes["backgroundColorToken"];
|
|
9077
|
+
if (!token) return null;
|
|
9078
|
+
return { "data-bg-color": token };
|
|
9079
|
+
}
|
|
7869
9080
|
}
|
|
7870
9081
|
}
|
|
7871
9082
|
}
|
|
@@ -7874,9 +9085,12 @@ var Highlight = Extension.create({
|
|
|
7874
9085
|
addCommands() {
|
|
7875
9086
|
const defaultColor = this.options.defaultColor;
|
|
7876
9087
|
return {
|
|
9088
|
+
// Hex-based highlight. Mutual exclusion: clears any named bg token so
|
|
9089
|
+
// the legacy hex picker and the Notion-style picker can't write
|
|
9090
|
+
// conflicting values to the same mark.
|
|
7877
9091
|
setHighlight: (attributes) => ({ commands }) => {
|
|
7878
9092
|
const color = attributes?.color ?? defaultColor;
|
|
7879
|
-
return commands.setMark("textStyle", { backgroundColor: color });
|
|
9093
|
+
return commands.setMark("textStyle", { backgroundColor: color, backgroundColorToken: null });
|
|
7880
9094
|
},
|
|
7881
9095
|
unsetHighlight: () => ({ commands }) => {
|
|
7882
9096
|
if (!commands.setMark("textStyle", { backgroundColor: null })) return false;
|
|
@@ -7892,12 +9106,12 @@ var Highlight = Extension.create({
|
|
|
7892
9106
|
if (empty) {
|
|
7893
9107
|
const marks = state.storedMarks ?? state.doc.resolve(from).marks();
|
|
7894
9108
|
const mark = markType.isInSet(marks);
|
|
7895
|
-
hasHighlight = !!mark?.attrs["backgroundColor"];
|
|
9109
|
+
hasHighlight = !!mark?.attrs["backgroundColor"] || !!mark?.attrs["backgroundColorToken"];
|
|
7896
9110
|
} else {
|
|
7897
9111
|
state.doc.nodesBetween(from, to, (node) => {
|
|
7898
9112
|
if (hasHighlight) return false;
|
|
7899
9113
|
const mark = markType.isInSet(node.marks);
|
|
7900
|
-
if (mark?.attrs["backgroundColor"]) {
|
|
9114
|
+
if (mark?.attrs["backgroundColor"] || mark?.attrs["backgroundColorToken"]) {
|
|
7901
9115
|
hasHighlight = true;
|
|
7902
9116
|
return false;
|
|
7903
9117
|
}
|
|
@@ -7905,11 +9119,26 @@ var Highlight = Extension.create({
|
|
|
7905
9119
|
});
|
|
7906
9120
|
}
|
|
7907
9121
|
if (hasHighlight) {
|
|
7908
|
-
commands.setMark("textStyle", { backgroundColor: null });
|
|
9122
|
+
commands.setMark("textStyle", { backgroundColor: null, backgroundColorToken: null });
|
|
7909
9123
|
commands.removeEmptyTextStyle();
|
|
7910
9124
|
return true;
|
|
7911
9125
|
}
|
|
7912
|
-
return commands.setMark("textStyle", { backgroundColor: color });
|
|
9126
|
+
return commands.setMark("textStyle", { backgroundColor: color, backgroundColorToken: null });
|
|
9127
|
+
},
|
|
9128
|
+
// Named-token highlight. Mutual exclusion: clears the hex
|
|
9129
|
+
// backgroundColor so the theme-aware data attribute is the sole source
|
|
9130
|
+
// of truth.
|
|
9131
|
+
setBackgroundColorToken: (token) => ({ commands }) => {
|
|
9132
|
+
if (!commands.setMark("textStyle", { backgroundColorToken: token, backgroundColor: null })) {
|
|
9133
|
+
return false;
|
|
9134
|
+
}
|
|
9135
|
+
if (token === null) commands.removeEmptyTextStyle();
|
|
9136
|
+
return true;
|
|
9137
|
+
},
|
|
9138
|
+
unsetBackgroundColorToken: () => ({ commands }) => {
|
|
9139
|
+
if (!commands.setMark("textStyle", { backgroundColorToken: null })) return false;
|
|
9140
|
+
commands.removeEmptyTextStyle();
|
|
9141
|
+
return true;
|
|
7913
9142
|
}
|
|
7914
9143
|
};
|
|
7915
9144
|
},
|
|
@@ -8153,6 +9382,61 @@ var FontSize = Extension.create({
|
|
|
8153
9382
|
}
|
|
8154
9383
|
});
|
|
8155
9384
|
|
|
9385
|
+
// src/extensions/NotionColorPicker.ts
|
|
9386
|
+
var DEFAULT_NOTION_COLOR_PALETTE = Object.freeze([
|
|
9387
|
+
"gray",
|
|
9388
|
+
"brown",
|
|
9389
|
+
"orange",
|
|
9390
|
+
"yellow",
|
|
9391
|
+
"green",
|
|
9392
|
+
"blue",
|
|
9393
|
+
"purple",
|
|
9394
|
+
"pink",
|
|
9395
|
+
"red"
|
|
9396
|
+
]);
|
|
9397
|
+
var NotionColorPicker = Extension.create({
|
|
9398
|
+
name: "notionColorPicker",
|
|
9399
|
+
// The picker writes textStyle.colorToken / textStyle.backgroundColorToken,
|
|
9400
|
+
// so TextStyle must be present. TextColor and Highlight are recommended
|
|
9401
|
+
// (they own those attribute schemas) but not strictly required: a host app
|
|
9402
|
+
// could supply its own attribute providers if it really wants to.
|
|
9403
|
+
dependencies: ["textStyle"],
|
|
9404
|
+
addOptions() {
|
|
9405
|
+
return {
|
|
9406
|
+
palette: DEFAULT_NOTION_COLOR_PALETTE
|
|
9407
|
+
};
|
|
9408
|
+
},
|
|
9409
|
+
addStorage() {
|
|
9410
|
+
return {
|
|
9411
|
+
isOpen: false
|
|
9412
|
+
};
|
|
9413
|
+
},
|
|
9414
|
+
addToolbarItems() {
|
|
9415
|
+
return [
|
|
9416
|
+
{
|
|
9417
|
+
type: "button",
|
|
9418
|
+
name: "notionColor",
|
|
9419
|
+
// emitEvent takes priority over `command` at click time. The `command`
|
|
9420
|
+
// field is required by the ToolbarButton schema, so we point it at a
|
|
9421
|
+
// harmless built-in that fires only if a host disables the popover UI.
|
|
9422
|
+
command: "focus",
|
|
9423
|
+
// The "A with an underline" glyph is the closest match in our icon
|
|
9424
|
+
// set; the framework wrapper can repaint the underline to reflect
|
|
9425
|
+
// the current selection's color.
|
|
9426
|
+
icon: "textAUnderline",
|
|
9427
|
+
label: "Text and background color",
|
|
9428
|
+
emitEvent: "notionColorOpen",
|
|
9429
|
+
group: "textStyle",
|
|
9430
|
+
priority: 250,
|
|
9431
|
+
// Bubble-menu-only: the legacy hex pickers (TextColor / Highlight)
|
|
9432
|
+
// stay in the main toolbar; this token-based picker is a Notion-mode
|
|
9433
|
+
// affordance surfaced beside the text-format buttons in the bubble.
|
|
9434
|
+
toolbar: false
|
|
9435
|
+
}
|
|
9436
|
+
];
|
|
9437
|
+
}
|
|
9438
|
+
});
|
|
9439
|
+
|
|
8156
9440
|
// src/extensions/ClearFormatting.ts
|
|
8157
9441
|
var ClearFormatting = Extension.create({
|
|
8158
9442
|
name: "clearFormatting",
|
|
@@ -8241,9 +9525,17 @@ function linkPopoverPlugin({ editor, markType, protocols }) {
|
|
|
8241
9525
|
return new DOMRect(coords.left, coords.top, 0, coords.bottom - coords.top);
|
|
8242
9526
|
}
|
|
8243
9527
|
};
|
|
9528
|
+
if (anchorElement) {
|
|
9529
|
+
const editorEl = anchorElement.closest(".dm-editor");
|
|
9530
|
+
if (editorEl && el.parentElement !== editorEl) {
|
|
9531
|
+
editorEl.appendChild(el);
|
|
9532
|
+
}
|
|
9533
|
+
} else if (el.parentElement !== document.body) {
|
|
9534
|
+
document.body.appendChild(el);
|
|
9535
|
+
}
|
|
8244
9536
|
cleanupFloating?.();
|
|
8245
9537
|
cleanupFloating = positionFloating(reference, el, {
|
|
8246
|
-
placement: "bottom",
|
|
9538
|
+
placement: anchorElement ? "bottom-start" : "bottom",
|
|
8247
9539
|
offsetValue: 4
|
|
8248
9540
|
});
|
|
8249
9541
|
input.focus();
|
|
@@ -8500,6 +9792,7 @@ function createBubbleMenuPlugin(options) {
|
|
|
8500
9792
|
if (!target) return;
|
|
8501
9793
|
if (element.contains(target)) return;
|
|
8502
9794
|
if (editor.view.dom.contains(target)) return;
|
|
9795
|
+
if (target instanceof HTMLElement && target.closest("[data-dm-editor-ui]")) return;
|
|
8503
9796
|
hideMenu();
|
|
8504
9797
|
suppressed = true;
|
|
8505
9798
|
};
|
|
@@ -8576,8 +9869,10 @@ function createBubbleMenuPlugin(options) {
|
|
|
8576
9869
|
});
|
|
8577
9870
|
};
|
|
8578
9871
|
const onBlur = ({ event }) => {
|
|
8579
|
-
|
|
8580
|
-
|
|
9872
|
+
const related = event.relatedTarget;
|
|
9873
|
+
if (related instanceof HTMLElement) {
|
|
9874
|
+
if (element.contains(related)) return;
|
|
9875
|
+
if (related.closest("[data-dm-editor-ui]")) return;
|
|
8581
9876
|
}
|
|
8582
9877
|
hideMenu();
|
|
8583
9878
|
};
|
|
@@ -8677,132 +9972,6 @@ var BubbleMenu = Extension.create({
|
|
|
8677
9972
|
];
|
|
8678
9973
|
}
|
|
8679
9974
|
});
|
|
8680
|
-
var floatingMenuPluginKey = new PluginKey("floatingMenu");
|
|
8681
|
-
function defaultShouldShow2({
|
|
8682
|
-
editor,
|
|
8683
|
-
state
|
|
8684
|
-
}) {
|
|
8685
|
-
if (!editor.isEditable) return false;
|
|
8686
|
-
const { selection } = state;
|
|
8687
|
-
const { $from, empty } = selection;
|
|
8688
|
-
if (!empty) return false;
|
|
8689
|
-
if ($from.parent.type.name !== "paragraph") return false;
|
|
8690
|
-
if ($from.parent.content.size !== 0) return false;
|
|
8691
|
-
if ($from.parentOffset !== 0) return false;
|
|
8692
|
-
return true;
|
|
8693
|
-
}
|
|
8694
|
-
function createFloatingMenuPlugin(options) {
|
|
8695
|
-
const {
|
|
8696
|
-
pluginKey,
|
|
8697
|
-
editor,
|
|
8698
|
-
element,
|
|
8699
|
-
shouldShow = defaultShouldShow2,
|
|
8700
|
-
offset: offset2 = 0
|
|
8701
|
-
} = options;
|
|
8702
|
-
if (!element.getAttribute("role")) {
|
|
8703
|
-
element.setAttribute("role", "toolbar");
|
|
8704
|
-
element.setAttribute("aria-label", "Floating menu");
|
|
8705
|
-
}
|
|
8706
|
-
let cleanupFloating = null;
|
|
8707
|
-
const updatePosition = (view) => {
|
|
8708
|
-
const { selection } = view.state;
|
|
8709
|
-
const { $from } = selection;
|
|
8710
|
-
const depth = $from.depth;
|
|
8711
|
-
const startPos = $from.start(depth);
|
|
8712
|
-
const domNode = view.nodeDOM(startPos - 1);
|
|
8713
|
-
if (domNode instanceof HTMLElement) {
|
|
8714
|
-
cleanupFloating?.();
|
|
8715
|
-
cleanupFloating = positionFloatingOnce(domNode, element, {
|
|
8716
|
-
placement: "bottom-start",
|
|
8717
|
-
offsetValue: offset2
|
|
8718
|
-
});
|
|
8719
|
-
element.setAttribute("data-show", "");
|
|
8720
|
-
}
|
|
8721
|
-
};
|
|
8722
|
-
const hideMenu = () => {
|
|
8723
|
-
cleanupFloating?.();
|
|
8724
|
-
cleanupFloating = null;
|
|
8725
|
-
element.removeAttribute("data-show");
|
|
8726
|
-
};
|
|
8727
|
-
hideMenu();
|
|
8728
|
-
return new Plugin({
|
|
8729
|
-
key: pluginKey,
|
|
8730
|
-
view: (editorView) => {
|
|
8731
|
-
const editorEl = editorView.dom.closest(".dm-editor");
|
|
8732
|
-
if (editorEl && element.parentElement !== editorEl) {
|
|
8733
|
-
editorEl.appendChild(element);
|
|
8734
|
-
}
|
|
8735
|
-
const onFocus = () => {
|
|
8736
|
-
const visible = shouldShow({
|
|
8737
|
-
editor,
|
|
8738
|
-
view: editor.view,
|
|
8739
|
-
state: editor.view.state
|
|
8740
|
-
});
|
|
8741
|
-
if (visible) {
|
|
8742
|
-
updatePosition(editor.view);
|
|
8743
|
-
} else {
|
|
8744
|
-
hideMenu();
|
|
8745
|
-
}
|
|
8746
|
-
};
|
|
8747
|
-
const onBlur = ({ event }) => {
|
|
8748
|
-
if (event.relatedTarget && element.contains(event.relatedTarget)) {
|
|
8749
|
-
return;
|
|
8750
|
-
}
|
|
8751
|
-
hideMenu();
|
|
8752
|
-
};
|
|
8753
|
-
editor.on("focus", onFocus);
|
|
8754
|
-
editor.on("blur", onBlur);
|
|
8755
|
-
return {
|
|
8756
|
-
update: (view) => {
|
|
8757
|
-
const visible = shouldShow({
|
|
8758
|
-
editor,
|
|
8759
|
-
view,
|
|
8760
|
-
state: view.state
|
|
8761
|
-
});
|
|
8762
|
-
if (visible) {
|
|
8763
|
-
updatePosition(view);
|
|
8764
|
-
} else {
|
|
8765
|
-
hideMenu();
|
|
8766
|
-
}
|
|
8767
|
-
},
|
|
8768
|
-
destroy: () => {
|
|
8769
|
-
hideMenu();
|
|
8770
|
-
editor.off("focus", onFocus);
|
|
8771
|
-
editor.off("blur", onBlur);
|
|
8772
|
-
}
|
|
8773
|
-
};
|
|
8774
|
-
}
|
|
8775
|
-
});
|
|
8776
|
-
}
|
|
8777
|
-
var FloatingMenu = Extension.create({
|
|
8778
|
-
name: "floatingMenu",
|
|
8779
|
-
addOptions() {
|
|
8780
|
-
return {
|
|
8781
|
-
element: null,
|
|
8782
|
-
shouldShow: defaultShouldShow2,
|
|
8783
|
-
offset: 0
|
|
8784
|
-
};
|
|
8785
|
-
},
|
|
8786
|
-
addProseMirrorPlugins() {
|
|
8787
|
-
const { element, shouldShow, offset: offset2 } = this.options;
|
|
8788
|
-
if (!element) {
|
|
8789
|
-
return [];
|
|
8790
|
-
}
|
|
8791
|
-
const editor = this.editor;
|
|
8792
|
-
if (!editor) {
|
|
8793
|
-
return [];
|
|
8794
|
-
}
|
|
8795
|
-
return [
|
|
8796
|
-
createFloatingMenuPlugin({
|
|
8797
|
-
pluginKey: floatingMenuPluginKey,
|
|
8798
|
-
editor,
|
|
8799
|
-
element,
|
|
8800
|
-
shouldShow,
|
|
8801
|
-
offset: offset2
|
|
8802
|
-
})
|
|
8803
|
-
];
|
|
8804
|
-
}
|
|
8805
|
-
});
|
|
8806
9975
|
|
|
8807
9976
|
// src/extensions/StarterKit.ts
|
|
8808
9977
|
var StarterKit = Extension.create({
|
|
@@ -8845,6 +10014,7 @@ var StarterKit = Extension.create({
|
|
|
8845
10014
|
maybeAdd(Gapcursor, this.options.gapcursor);
|
|
8846
10015
|
maybeAdd(TrailingNode, this.options.trailingNode);
|
|
8847
10016
|
maybeAdd(ListKeymap, this.options.listKeymap);
|
|
10017
|
+
maybeAdd(ListIndent, this.options.listIndent);
|
|
8848
10018
|
maybeAdd(LinkPopover, this.options.linkPopover);
|
|
8849
10019
|
maybeAdd(SelectionDecoration, this.options.selectionDecoration);
|
|
8850
10020
|
return extensions;
|
|
@@ -8854,6 +10024,6 @@ var StarterKit = Extension.create({
|
|
|
8854
10024
|
// src/index.ts
|
|
8855
10025
|
var VERSION = "0.1.0";
|
|
8856
10026
|
|
|
8857
|
-
export { BaseKeymap, Blockquote, Bold, BubbleMenu, BulletList, CanChecker, ChainBuilder, CharacterCount, ClearFormatting, Code, CodeBlock, CommandManager, DEFAULT_HIGHLIGHT_COLORS, DEFAULT_TEXT_COLORS, Document, Dropcursor, Editor, EventEmitter, Extension, ExtensionManager,
|
|
10027
|
+
export { BaseKeymap, BlockColor, Blockquote, Bold, BubbleMenu, BulletList, CanChecker, ChainBuilder, CharacterCount, ClearFormatting, Code, CodeBlock, CommandManager, DEFAULT_BLOCK_COLORS, DEFAULT_BLOCK_COLOR_TYPES, DEFAULT_HIGHLIGHT_COLORS, DEFAULT_NOTION_COLOR_PALETTE, DEFAULT_TEXT_COLORS, Document, Dropcursor, Editor, EventEmitter, Extension, ExtensionManager, FLOATING_MENU_NO_FOCUS, FloatingMenuController, Focus, FontFamily, FontSize, Gapcursor, HardBreak, Heading, Highlight, History, HorizontalRule, InvisibleChars, Italic, LIST_ITEM_TYPE_NAMES, LineHeight, Link, LinkPopover, ListIndent, ListItem, ListKeymap, Mark, Node2 as Node, NotionColorPicker, OrderedList, Paragraph, Placeholder, Selection4 as Selection, SelectionDecoration, StarterKit, Strike, Subscript, Superscript, TaskItem, TaskList, Text, TextAlign, TextColor, TextStyle, ToolbarController, TrailingNode, Typography, Underline, UniqueID, VERSION, applyInlineStyles, autolinkPlugin, autolinkPluginKey, blur, bubbleMenuPluginKey, buildCommandProps, builtInCommands, callOrReturn, characterCountPluginKey, clearContent, createAccumulatingDispatch, createBubbleMenuPlugin, createCanChecker, createChainBuilder, createDocument, defaultBlockAt, defaultBubbleContexts, defaultIcons, deleteSelection, findChildren, findListItemAncestorDepth, findParentNode, focus, focusPluginKey, generateHTML, generateJSON, generateText, getListItemCursorContext, getMarkRange, groupFloatingMenuItems, indentBlockAsListChild, inlineStyles, insertAsListItemChild, insertChildrenZoneSibling, insertContent, insertText, invisibleCharsPluginKey, isDocumentEmpty, isInListItemLabel, isInsideListItem, isNodeEmpty, isValidUrl, lift, liftCurrentListItem, liftEmptyChildrenZoneParagraph, linkClickPlugin, linkClickPluginKey, linkExitPlugin, linkExitPluginKey, linkPastePlugin, linkPastePluginKey, markInputRule, markInputRulePatterns, nodeInputRule, outdentBlockFromListItem, placeholderPluginKey, positionFloating, positionFloatingOnce, resetAttributes, selectAll, selectNodeBackward, selectionDecorationPluginKey, setBlockType, setContent, setMark, splitListForInsert, stripInlineColorConflicts, textInputRule, textblockTypeInputRule, toggleBlockType, toggleList, toggleMark, toggleWrap, uniqueIDPluginKey, unsetAllMarks, unsetMark, updateAttributes, wrapIn, wrappingInputRule, writeToClipboard };
|
|
8858
10028
|
//# sourceMappingURL=index.js.map
|
|
8859
10029
|
//# sourceMappingURL=index.js.map
|