@limetech/lime-elements 39.44.5 → 39.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/cjs/{keycodes-B75-uKOK.js → keycodes-k-EdzcMX.js} +4 -0
- package/dist/cjs/lime-elements.cjs.js +1 -1
- package/dist/cjs/limel-breadcrumbs_8.cjs.entry.js +20 -1
- package/dist/cjs/limel-chip-set.cjs.entry.js +1 -1
- package/dist/cjs/limel-chip_2.cjs.entry.js +1 -1
- package/dist/cjs/limel-file-viewer.cjs.entry.js +84 -8
- package/dist/cjs/limel-markdown.cjs.entry.js +1 -1
- package/dist/cjs/limel-picker.cjs.entry.js +1 -1
- package/dist/cjs/limel-popover_2.cjs.entry.js +1 -1
- package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +778 -395
- package/dist/cjs/limel-select.cjs.entry.js +377 -13
- package/dist/cjs/limel-text-editor-link-menu.cjs.entry.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/{markdown-parser-ChnRAxpZ.js → markdown-parser-YH5HSSgF.js} +36 -56
- package/dist/collection/components/list/list.js +19 -0
- package/dist/collection/components/select/select.js +215 -12
- package/dist/collection/components/select/select.template.js +17 -3
- package/dist/collection/components/text-editor/prosemirror-adapter/editor-config.js +12 -1
- package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-command-utils/active-state-utils.js +36 -0
- package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-command-utils/list-utils.js +270 -0
- package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-command-utils/node-utils.js +15 -0
- package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-commands.js +81 -69
- package/dist/collection/components/text-editor/prosemirror-adapter/plugins/list-key-handler.js +44 -0
- package/dist/collection/components/text-editor/prosemirror-adapter/prosemirror-adapter.js +4 -3
- package/dist/collection/util/keycodes.js +3 -0
- package/dist/collection/util/typeahead.js +159 -0
- package/dist/esm/{keycodes-rI0IeKpx.js → keycodes-C4_9qUMP.js} +4 -1
- package/dist/esm/lime-elements.js +1 -1
- package/dist/esm/limel-breadcrumbs_8.entry.js +20 -1
- package/dist/esm/limel-chip-set.entry.js +1 -1
- package/dist/esm/limel-chip_2.entry.js +1 -1
- package/dist/esm/limel-file-viewer.entry.js +84 -8
- package/dist/esm/limel-markdown.entry.js +1 -1
- package/dist/esm/limel-picker.entry.js +1 -1
- package/dist/esm/limel-popover_2.entry.js +1 -1
- package/dist/esm/limel-prosemirror-adapter.entry.js +779 -396
- package/dist/esm/limel-select.entry.js +378 -14
- package/dist/esm/limel-text-editor-link-menu.entry.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/{markdown-parser-BSQKleVw.js → markdown-parser-DrxxGLF5.js} +36 -56
- package/dist/lime-elements/lime-elements.esm.js +1 -1
- package/dist/lime-elements/{p-a089b580.entry.js → p-03fd2181.entry.js} +1 -1
- package/dist/lime-elements/{p-fc43fb46.entry.js → p-12fe8fe1.entry.js} +6 -6
- package/dist/lime-elements/p-1f29f482.entry.js +1 -0
- package/dist/lime-elements/{p-bf3c78a8.entry.js → p-2b01c1f2.entry.js} +1 -1
- package/dist/lime-elements/{p-758939be.entry.js → p-2e5d5e79.entry.js} +1 -1
- package/dist/lime-elements/{p-3ea159fd.entry.js → p-38393fb4.entry.js} +1 -1
- package/dist/lime-elements/p-9152d7e1.entry.js +23 -0
- package/dist/lime-elements/{p-1e3cdfa1.entry.js → p-9a04a686.entry.js} +1 -1
- package/dist/lime-elements/p-C4_9qUMP.js +1 -0
- package/dist/lime-elements/{p-B0qhUema.js → p-C9xHOCE_.js} +2 -2
- package/dist/lime-elements/{p-6fbf20c6.entry.js → p-e8df7a2e.entry.js} +1 -1
- package/dist/lime-elements/p-f51b5651.entry.js +1 -0
- package/dist/types/components/select/select.d.ts +76 -1
- package/dist/types/components/select/select.template.d.ts +21 -1
- package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-command-utils/active-state-utils.d.ts +6 -0
- package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-command-utils/list-utils.d.ts +68 -0
- package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-command-utils/node-utils.d.ts +10 -0
- package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-commands.d.ts +10 -0
- package/dist/types/components/text-editor/prosemirror-adapter/plugins/list-key-handler.d.ts +14 -0
- package/dist/types/components.d.ts +36 -0
- package/dist/types/util/keycodes.d.ts +3 -0
- package/dist/types/util/typeahead.d.ts +101 -0
- package/package.json +3 -3
- package/dist/lime-elements/p-552d0733.entry.js +0 -1
- package/dist/lime-elements/p-845cfda7.entry.js +0 -1
- package/dist/lime-elements/p-8f4273e4.entry.js +0 -23
- package/dist/lime-elements/p-rI0IeKpx.js +0 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s)
|
|
4
|
+
if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
import { NodeRange, } from "prosemirror-model";
|
|
14
|
+
import { canSplit, findWrapping } from "prosemirror-transform";
|
|
15
|
+
/**
|
|
16
|
+
* Whether the selection touches top-level blocks that cannot become list
|
|
17
|
+
* items (list_item requires a leading paragraph, so headings, blockquotes,
|
|
18
|
+
* code blocks and tables are not listable).
|
|
19
|
+
*
|
|
20
|
+
* @param state - the current editor state
|
|
21
|
+
* @returns true when a non-listable block intersects the selection
|
|
22
|
+
*/
|
|
23
|
+
export const selectionHasNonListableBlock = (state) => {
|
|
24
|
+
const { from, to } = state.selection;
|
|
25
|
+
const { schema } = state;
|
|
26
|
+
let found = false;
|
|
27
|
+
state.doc.nodesBetween(from, to, (node, _pos, parent) => {
|
|
28
|
+
if (found) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const isTopLevelBlock = (parent === null || parent === void 0 ? void 0 : parent.type) === schema.topNodeType;
|
|
32
|
+
const listable = node.type === schema.nodes.paragraph ||
|
|
33
|
+
node.type === schema.nodes.list_item ||
|
|
34
|
+
isListNode(node, schema);
|
|
35
|
+
if (isTopLevelBlock && !listable) {
|
|
36
|
+
found = true;
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return !isTopLevelBlock || isListNode(node, schema);
|
|
40
|
+
});
|
|
41
|
+
return found;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Classifies the selection for the list command ladder.
|
|
45
|
+
*
|
|
46
|
+
* @param state - the current editor state
|
|
47
|
+
* @param listType - the list type the command targets
|
|
48
|
+
* @returns the ladder branch to take, plus the innermost list depth when
|
|
49
|
+
* the selection sits inside a list
|
|
50
|
+
*/
|
|
51
|
+
export const resolveListContext = (state, listType) => {
|
|
52
|
+
const { $from, $to } = state.selection;
|
|
53
|
+
const fromDepth = innermostListDepth(state);
|
|
54
|
+
const sameTopLevelBlock = state.selection.empty ||
|
|
55
|
+
$from.sameParent($to) ||
|
|
56
|
+
$from.node(1) === $to.node(1);
|
|
57
|
+
if (fromDepth !== null && sameTopLevelBlock) {
|
|
58
|
+
return listContextAt($from, fromDepth, listType);
|
|
59
|
+
}
|
|
60
|
+
if (fromDepth === null && sameTopLevelBlock) {
|
|
61
|
+
return { kind: 'no-list', listDepth: null, singleBlock: true };
|
|
62
|
+
}
|
|
63
|
+
// The range spans multiple top-level blocks: uniform paragraphs mean a
|
|
64
|
+
// plain wrap; lists that are all of the target type mean a toggle;
|
|
65
|
+
// any other list in the mix means unification.
|
|
66
|
+
const listTypes = new Set();
|
|
67
|
+
let sawParagraph = false;
|
|
68
|
+
const startIndex = $from.index(0);
|
|
69
|
+
const endIndex = Math.min($to.index(0), state.doc.childCount - 1);
|
|
70
|
+
for (let i = startIndex; i <= endIndex; i++) {
|
|
71
|
+
const child = state.doc.child(i);
|
|
72
|
+
if (isListNode(child, state.schema)) {
|
|
73
|
+
listTypes.add(child.type);
|
|
74
|
+
}
|
|
75
|
+
else if (child.type === state.schema.nodes.paragraph) {
|
|
76
|
+
sawParagraph = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const uniformTargetLists = !sawParagraph && listTypes.size === 1 && listTypes.has(listType);
|
|
80
|
+
if (uniformTargetLists && fromDepth !== null) {
|
|
81
|
+
return listContextAt($from, fromDepth, listType);
|
|
82
|
+
}
|
|
83
|
+
if (listTypes.size > 0) {
|
|
84
|
+
return { kind: 'mixed', listDepth: null };
|
|
85
|
+
}
|
|
86
|
+
return { kind: 'no-list', listDepth: null, singleBlock: false };
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Converts the innermost list around the selection to the target list type.
|
|
90
|
+
* bullet_list and ordered_list share the list_item+ content model, so the
|
|
91
|
+
* node can change type in place.
|
|
92
|
+
*
|
|
93
|
+
* @param state - the current editor state
|
|
94
|
+
* @param listDepth - depth of the list node, from resolveListContext
|
|
95
|
+
* @param targetType - the list type to convert to
|
|
96
|
+
* @param dispatch - the dispatch function; omit for a dry-run capability check
|
|
97
|
+
* @returns true when the conversion applies
|
|
98
|
+
*/
|
|
99
|
+
export const convertInnermostList = (state, listDepth, targetType, dispatch) => {
|
|
100
|
+
const { $from } = state.selection;
|
|
101
|
+
const pos = $from.before(listDepth);
|
|
102
|
+
const node = state.doc.nodeAt(pos);
|
|
103
|
+
if (!node) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
if (dispatch) {
|
|
107
|
+
dispatch(state.tr
|
|
108
|
+
.setNodeMarkup(pos, targetType, listAttrsFor(targetType, node.attrs, state.schema))
|
|
109
|
+
.scrollIntoView());
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Turns a mixed top-level selection (paragraphs and lists) into a single
|
|
115
|
+
* list of the target type: paragraph runs are wrapped, lists are converted
|
|
116
|
+
* in place, and adjacent same-type lists are joined. Nesting inside
|
|
117
|
+
* converted lists is preserved.
|
|
118
|
+
*
|
|
119
|
+
* @param state - the current editor state
|
|
120
|
+
* @param targetType - the list type to unify into
|
|
121
|
+
* @param dispatch - the dispatch function; omit for a dry-run capability check
|
|
122
|
+
* @returns true when the unification applies
|
|
123
|
+
*/
|
|
124
|
+
export const unifyToList = (state, targetType, dispatch) => {
|
|
125
|
+
const { $from, $to } = state.selection;
|
|
126
|
+
if ($from.depth === 0 || $to.depth === 0) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (dispatch === undefined) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
const tr = state.tr;
|
|
133
|
+
convertListsAndWrapRuns(tr, state, targetType, $from.index(0), $to.index(0));
|
|
134
|
+
joinAdjacentLists(tr, targetType, tr.mapping.map($from.before(1)), mapKeepBefore(tr, $to.after(1)));
|
|
135
|
+
dispatch(tr.scrollIntoView());
|
|
136
|
+
return true;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Wraps a dispatch so that after a list is wrapped around the selection,
|
|
140
|
+
* adjacent top-level lists of the same type are joined with it.
|
|
141
|
+
*
|
|
142
|
+
* @param state - the editor state the wrap command runs on
|
|
143
|
+
* @param targetType - the list type being wrapped
|
|
144
|
+
* @param dispatch - the dispatch function; omitted for a dry-run capability check
|
|
145
|
+
* @returns the wrapped dispatch, or undefined when no dispatch was given
|
|
146
|
+
*/
|
|
147
|
+
export const joinAdjacentListsOnDispatch = (state, targetType, dispatch) => {
|
|
148
|
+
if (!dispatch) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
const { $from, $to } = state.selection;
|
|
152
|
+
return (tr) => {
|
|
153
|
+
// The new list's outer boundaries: the start maps before the
|
|
154
|
+
// inserted wrapping, the end maps after it.
|
|
155
|
+
const start = mapKeepBefore(tr, $from.before(1));
|
|
156
|
+
const end = tr.mapping.map($to.after(1));
|
|
157
|
+
// Back to front, so the earlier boundary stays valid after a join.
|
|
158
|
+
joinSameTypeBoundary(tr, targetType, end);
|
|
159
|
+
joinSameTypeBoundary(tr, targetType, start);
|
|
160
|
+
dispatch(tr);
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
const joinSameTypeBoundary = (tr, targetType, boundary) => {
|
|
164
|
+
var _a, _b;
|
|
165
|
+
if (boundary <= 0 || boundary >= tr.doc.content.size) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const $boundary = tr.doc.resolve(boundary);
|
|
169
|
+
if (((_a = $boundary.nodeBefore) === null || _a === void 0 ? void 0 : _a.type) === targetType &&
|
|
170
|
+
((_b = $boundary.nodeAfter) === null || _b === void 0 ? void 0 : _b.type) === targetType) {
|
|
171
|
+
tr.join(boundary);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const isListNode = (node, schema) => node.type === schema.nodes.bullet_list ||
|
|
175
|
+
node.type === schema.nodes.ordered_list;
|
|
176
|
+
const innermostListDepth = (state) => {
|
|
177
|
+
const { $from } = state.selection;
|
|
178
|
+
for (let depth = $from.depth; depth > 0; depth--) {
|
|
179
|
+
if (isListNode($from.node(depth), state.schema)) {
|
|
180
|
+
return depth;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
};
|
|
185
|
+
const listContextAt = ($from, listDepth, listType) => ({
|
|
186
|
+
kind: $from.node(listDepth).type === listType ? 'same-type' : 'other-type',
|
|
187
|
+
listDepth: listDepth,
|
|
188
|
+
});
|
|
189
|
+
const listAttrsFor = (targetType, attrs, schema) => {
|
|
190
|
+
var _a;
|
|
191
|
+
if (targetType === schema.nodes.ordered_list) {
|
|
192
|
+
return Object.assign(Object.assign({}, attrs), { order: (_a = attrs.order) !== null && _a !== void 0 ? _a : 1 });
|
|
193
|
+
}
|
|
194
|
+
const { order } = attrs, withoutOrder = __rest(attrs, ["order"]);
|
|
195
|
+
return withoutOrder;
|
|
196
|
+
};
|
|
197
|
+
const mapKeepBefore = (tr, position) =>
|
|
198
|
+
// ProseMirror's Mapping.map takes (pos, assoc); the unicorn rule
|
|
199
|
+
// misreads it as Array#map with a `this` argument.
|
|
200
|
+
// eslint-disable-next-line unicorn/no-array-method-this-argument
|
|
201
|
+
tr.mapping.map(position, -1);
|
|
202
|
+
const wrapRunInList = (tr, targetType, runStart, runEnd) => {
|
|
203
|
+
const $start = tr.doc.resolve(tr.mapping.map(runStart) + 1);
|
|
204
|
+
const $end = tr.doc.resolve(mapKeepBefore(tr, runEnd) - 1);
|
|
205
|
+
const range = new NodeRange($start, $end, 0);
|
|
206
|
+
const wrapping = findWrapping(range, targetType);
|
|
207
|
+
if (!wrapping) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
tr.wrap(range, wrapping);
|
|
211
|
+
// tr.wrap puts the whole run inside a single list_item; splitting at
|
|
212
|
+
// each block boundary gives one item per block, the same shape
|
|
213
|
+
// wrapInList produces.
|
|
214
|
+
const listIndex = wrapping.findIndex((entry) => entry.type === targetType);
|
|
215
|
+
const splitDepth = wrapping.length - (listIndex + 1);
|
|
216
|
+
let splitPos = range.start + wrapping.length;
|
|
217
|
+
for (let i = range.startIndex; i < range.endIndex; i++) {
|
|
218
|
+
if (i > range.startIndex && canSplit(tr.doc, splitPos, splitDepth)) {
|
|
219
|
+
tr.split(splitPos, splitDepth);
|
|
220
|
+
splitPos += 2 * splitDepth;
|
|
221
|
+
}
|
|
222
|
+
splitPos += range.parent.child(i).nodeSize;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const convertListsAndWrapRuns = (tr, state, targetType, startIndex, endIndex) => {
|
|
226
|
+
// Positions computed against the pre-transform doc are mapped through
|
|
227
|
+
// tr.mapping when applied.
|
|
228
|
+
const doc = state.doc;
|
|
229
|
+
let runStart = null;
|
|
230
|
+
let runEnd = null;
|
|
231
|
+
let pos = 0;
|
|
232
|
+
for (let i = 0; i < doc.childCount; i++) {
|
|
233
|
+
const child = doc.child(i);
|
|
234
|
+
const inRange = i >= startIndex && i <= endIndex;
|
|
235
|
+
const isList = isListNode(child, state.schema);
|
|
236
|
+
if (inRange && child.type === state.schema.nodes.paragraph) {
|
|
237
|
+
runStart = runStart !== null && runStart !== void 0 ? runStart : pos;
|
|
238
|
+
runEnd = pos + child.nodeSize;
|
|
239
|
+
}
|
|
240
|
+
else if (runStart !== null && runEnd !== null) {
|
|
241
|
+
wrapRunInList(tr, targetType, runStart, runEnd);
|
|
242
|
+
runStart = null;
|
|
243
|
+
runEnd = null;
|
|
244
|
+
}
|
|
245
|
+
if (inRange && isList && child.type !== targetType) {
|
|
246
|
+
tr.setNodeMarkup(tr.mapping.map(pos), targetType, listAttrsFor(targetType, child.attrs, state.schema));
|
|
247
|
+
}
|
|
248
|
+
pos += child.nodeSize;
|
|
249
|
+
}
|
|
250
|
+
if (runStart !== null && runEnd !== null) {
|
|
251
|
+
wrapRunInList(tr, targetType, runStart, runEnd);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
const joinAdjacentLists = (tr, targetType, mappedFrom, mappedTo) => {
|
|
255
|
+
// Scan the transformed doc's top-level boundaries back to front, so
|
|
256
|
+
// earlier join positions stay valid as joins shrink the doc.
|
|
257
|
+
const boundaries = [];
|
|
258
|
+
let boundary = 0;
|
|
259
|
+
for (let i = 0; i < tr.doc.childCount; i++) {
|
|
260
|
+
boundary += tr.doc.child(i).nodeSize;
|
|
261
|
+
boundaries.push(boundary);
|
|
262
|
+
}
|
|
263
|
+
for (let i = boundaries.length - 1; i >= 0; i--) {
|
|
264
|
+
const joinPos = boundaries[i];
|
|
265
|
+
if (joinPos <= mappedFrom || joinPos > mappedTo + 1) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
joinSameTypeBoundary(tr, targetType, joinPos);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds the depth of the nearest ancestor of a given node type.
|
|
3
|
+
*
|
|
4
|
+
* @param $pos - The resolved position in the document.
|
|
5
|
+
* @param type - The node type to search for.
|
|
6
|
+
* @returns The depth at which the node is found, or null if not found.
|
|
7
|
+
*/
|
|
8
|
+
export const findAncestorDepthOfType = ($pos, type) => {
|
|
9
|
+
for (let depth = $pos.depth; depth > 0; depth--) {
|
|
10
|
+
if ($pos.node(depth).type === type) {
|
|
11
|
+
return depth;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
@@ -1,47 +1,10 @@
|
|
|
1
1
|
import { toggleMark, setBlockType, wrapIn, lift } from "prosemirror-commands";
|
|
2
|
-
import {
|
|
3
|
-
import { TextSelection } from "prosemirror-state";
|
|
2
|
+
import { wrapInList, liftListItem } from "prosemirror-schema-list";
|
|
3
|
+
import { AllSelection, EditorState, TextSelection, } from "prosemirror-state";
|
|
4
4
|
import { EditorMenuTypes, LevelMapping } from "./types";
|
|
5
5
|
import { getLinkAttributes } from "../plugins/link/utils";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const { from, $from, to, empty } = state.selection;
|
|
9
|
-
if (empty) {
|
|
10
|
-
return !!markType.isInSet(state.storedMarks || $from.marks());
|
|
11
|
-
}
|
|
12
|
-
else {
|
|
13
|
-
return state.doc.rangeHasMark(from, to, markType);
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
};
|
|
17
|
-
const setActiveMethodForNode = (command, nodeType, level) => {
|
|
18
|
-
command.active = (state) => {
|
|
19
|
-
const { $from } = state.selection;
|
|
20
|
-
const node = $from.node($from.depth);
|
|
21
|
-
if (node && node.type.name === nodeType.name) {
|
|
22
|
-
if (nodeType.name === LevelMapping.Heading && level) {
|
|
23
|
-
return node.attrs.level === level;
|
|
24
|
-
}
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
return false;
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
const setActiveMethodForWrap = (command, nodeType) => {
|
|
31
|
-
command.active = (state) => {
|
|
32
|
-
const { from, to } = state.selection;
|
|
33
|
-
for (let pos = from; pos <= to; pos++) {
|
|
34
|
-
const resolvedPos = state.doc.resolve(pos);
|
|
35
|
-
for (let i = resolvedPos.depth; i > 0; i--) {
|
|
36
|
-
const node = resolvedPos.node(i);
|
|
37
|
-
if (node && node.type.name === nodeType.name) {
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return false;
|
|
43
|
-
};
|
|
44
|
-
};
|
|
6
|
+
import { setActiveMethodForMark, setActiveMethodForNode, setActiveMethodForWrap, } from "./menu-command-utils/active-state-utils";
|
|
7
|
+
import { resolveListContext, convertInnermostList, joinAdjacentListsOnDispatch, selectionHasNonListableBlock, unifyToList, } from "./menu-command-utils/list-utils";
|
|
45
8
|
const createInsertLinkCommand = (schema, _, link) => {
|
|
46
9
|
const command = (state, dispatch) => {
|
|
47
10
|
const { from, to } = state.selection;
|
|
@@ -101,11 +64,13 @@ const toggleNodeType = (schema, type, attrs = {}, shouldWrap = false) => {
|
|
|
101
64
|
const paragraphType = schema.nodes.paragraph;
|
|
102
65
|
return (state, dispatch) => {
|
|
103
66
|
const { $from, $to } = state.selection;
|
|
104
|
-
const hasActiveWrap = $from.node($from.depth - 1).type === nodeType;
|
|
105
67
|
if (state.selection instanceof TextSelection &&
|
|
106
68
|
// Ensure selection is within the same parent block
|
|
107
69
|
// We don't want toggling block types across multiple blocks
|
|
108
70
|
$from.sameParent($from.doc.resolve($to.pos))) {
|
|
71
|
+
// Resolved only under the TextSelection guard: an AllSelection
|
|
72
|
+
// resolves at depth 0, where there is no parent node to read.
|
|
73
|
+
const hasActiveWrap = $from.node($from.depth - 1).type === nodeType;
|
|
109
74
|
if ($from.parent.type === nodeType) {
|
|
110
75
|
if (dispatch) {
|
|
111
76
|
dispatch(state.tr.setBlockType($from.pos, $to.pos, paragraphType));
|
|
@@ -162,40 +127,85 @@ const createWrapInCommand = (schema, nodeType) => {
|
|
|
162
127
|
setActiveMethodForWrap(command, type);
|
|
163
128
|
return command;
|
|
164
129
|
};
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
130
|
+
// Select-all produces an AllSelection, whose endpoints resolve at the
|
|
131
|
+
// document level where list commands cannot operate. Re-anchoring it as
|
|
132
|
+
// a TextSelection over the same content lets the list ladder (and the
|
|
133
|
+
// delegated prosemirror-schema-list commands) treat select-all like any
|
|
134
|
+
// other full-content selection.
|
|
135
|
+
const withTextSelection = (state) => {
|
|
136
|
+
if (!(state.selection instanceof AllSelection)) {
|
|
137
|
+
return state;
|
|
138
|
+
}
|
|
139
|
+
const selection = TextSelection.between(state.doc.resolve(0), state.doc.resolve(state.doc.content.size));
|
|
140
|
+
// A plugin-free scratch state on the shared doc: only feed it
|
|
141
|
+
// commands driven by doc and selection.
|
|
142
|
+
return EditorState.create({
|
|
143
|
+
doc: state.doc,
|
|
144
|
+
selection: selection,
|
|
145
|
+
storedMarks: state.storedMarks,
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Creates a command for toggling list types.
|
|
150
|
+
*
|
|
151
|
+
* @param schema - The ProseMirror schema.
|
|
152
|
+
* @param listTypeName - The name of the list type to toggle.
|
|
153
|
+
* @returns A command for toggling list types.
|
|
154
|
+
*/
|
|
155
|
+
export const createListCommand = (schema, listTypeName) => {
|
|
156
|
+
const type = schema.nodes[listTypeName];
|
|
157
|
+
if (!type) {
|
|
158
|
+
throw new Error(`List type "${listTypeName}" not found in schema`);
|
|
159
|
+
}
|
|
160
|
+
const itemType = schema.nodes.list_item;
|
|
161
|
+
if (!itemType) {
|
|
162
|
+
throw new Error('Node type "list_item" not found in schema');
|
|
163
|
+
}
|
|
164
|
+
// The keymap invokes the command directly, so the command body applies
|
|
165
|
+
// the same applicability rules as `allowed` to keep keyboard and
|
|
166
|
+
// toolbar behavior identical.
|
|
167
|
+
const command = (state, dispatch) => {
|
|
168
|
+
state = withTextSelection(state);
|
|
169
|
+
if (!(state.selection instanceof TextSelection)) {
|
|
170
170
|
return false;
|
|
171
171
|
}
|
|
172
|
-
const
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
if (dispatch) {
|
|
176
|
-
dispatch(state.tr.wrap(range, wrapping).scrollIntoView());
|
|
177
|
-
}
|
|
178
|
-
return true;
|
|
172
|
+
const context = resolveListContext(state, type);
|
|
173
|
+
if (context.kind === 'same-type') {
|
|
174
|
+
return liftListItem(itemType)(state, dispatch);
|
|
179
175
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
176
|
+
if (context.kind === 'other-type') {
|
|
177
|
+
return convertInnermostList(state, context.listDepth, type, dispatch);
|
|
178
|
+
}
|
|
179
|
+
if (context.kind === 'no-list') {
|
|
180
|
+
// For a single block the schema decides, through the wrap
|
|
181
|
+
// command itself; multi-block selections keep the stricter
|
|
182
|
+
// gate so unify cannot half-apply.
|
|
183
|
+
if (!context.singleBlock && selectionHasNonListableBlock(state)) {
|
|
184
|
+
return false;
|
|
188
185
|
}
|
|
186
|
+
return wrapInList(type)(state, joinAdjacentListsOnDispatch(state, type, dispatch));
|
|
187
|
+
}
|
|
188
|
+
if (selectionHasNonListableBlock(state)) {
|
|
189
189
|
return false;
|
|
190
190
|
}
|
|
191
|
+
return unifyToList(state, type, dispatch);
|
|
192
|
+
};
|
|
193
|
+
command.allowed = (state) => {
|
|
194
|
+
state = withTextSelection(state);
|
|
195
|
+
if (!(state.selection instanceof TextSelection)) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
const context = resolveListContext(state, type);
|
|
199
|
+
// Inside a list, toggling or converting is always applicable, even
|
|
200
|
+
// when a non-listable ancestor wraps the list.
|
|
201
|
+
if (context.kind === 'same-type' || context.kind === 'other-type') {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
if (context.kind === 'no-list' && context.singleBlock) {
|
|
205
|
+
return wrapInList(type)(state);
|
|
206
|
+
}
|
|
207
|
+
return !selectionHasNonListableBlock(state);
|
|
191
208
|
};
|
|
192
|
-
};
|
|
193
|
-
const createListCommand = (schema, listType) => {
|
|
194
|
-
const type = schema.nodes[listType];
|
|
195
|
-
if (!type) {
|
|
196
|
-
throw new Error(`List type "${listType}" not found in schema`);
|
|
197
|
-
}
|
|
198
|
-
const command = toggleList(type);
|
|
199
209
|
setActiveMethodForWrap(command, type);
|
|
200
210
|
return command;
|
|
201
211
|
};
|
|
@@ -232,6 +242,8 @@ export class MenuCommandFactory {
|
|
|
232
242
|
'Mod-Shift-1': this.getCommand(EditorMenuTypes.HeaderLevel1),
|
|
233
243
|
'Mod-Shift-2': this.getCommand(EditorMenuTypes.HeaderLevel2),
|
|
234
244
|
'Mod-Shift-3': this.getCommand(EditorMenuTypes.HeaderLevel3),
|
|
245
|
+
'Mod-Shift-7': this.getCommand(EditorMenuTypes.OrderedList),
|
|
246
|
+
'Mod-Shift-8': this.getCommand(EditorMenuTypes.BulletList),
|
|
235
247
|
'Mod-Shift-X': this.getCommand(EditorMenuTypes.Strikethrough),
|
|
236
248
|
'Mod-`': this.getCommand(EditorMenuTypes.Code),
|
|
237
249
|
'Mod-Shift-C': this.getCommand(EditorMenuTypes.CodeBlock),
|
package/dist/collection/components/text-editor/prosemirror-adapter/plugins/list-key-handler.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
2
|
+
import { sinkListItem, liftListItem } from "prosemirror-schema-list";
|
|
3
|
+
import { findAncestorDepthOfType } from "../menu/menu-command-utils/node-utils";
|
|
4
|
+
export const listKeyHandlerPluginKey = new PluginKey('listKeyHandlerPlugin');
|
|
5
|
+
/**
|
|
6
|
+
* Creates a plugin for handling Tab and Shift+Tab inside list items:
|
|
7
|
+
* Tab indents the item and Shift+Tab outdents it. Enter and Backspace
|
|
8
|
+
* are handled by the base keymap (splitListItem and joinBackward), which
|
|
9
|
+
* runs before this plugin.
|
|
10
|
+
*
|
|
11
|
+
* @param schema - The document schema
|
|
12
|
+
* @returns A ProseMirror plugin for handling list-specific key events
|
|
13
|
+
*/
|
|
14
|
+
export function createListKeyHandlerPlugin(schema) {
|
|
15
|
+
return new Plugin({
|
|
16
|
+
key: listKeyHandlerPluginKey,
|
|
17
|
+
props: {
|
|
18
|
+
handleKeyDown: (view, event) => {
|
|
19
|
+
if (event.key !== 'Tab') {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const { state } = view;
|
|
23
|
+
const { $from } = state.selection;
|
|
24
|
+
const inListItem = findAncestorDepthOfType($from, schema.nodes.list_item) !==
|
|
25
|
+
null;
|
|
26
|
+
if (!inListItem) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
// Inside a list item, Tab and Shift+Tab are always handled
|
|
30
|
+
// so focus never leaves the editor mid-list, even when the
|
|
31
|
+
// indent or outdent itself cannot apply.
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
if (event.shiftKey) {
|
|
34
|
+
liftListItem(schema.nodes.list_item)(state, view.dispatch);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
// Sinking fails on first items (no preceding sibling to
|
|
38
|
+
// become the parent); Tab is still swallowed.
|
|
39
|
+
sinkListItem(schema.nodes.list_item)(state, view.dispatch);
|
|
40
|
+
return true;
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -87,13 +87,14 @@ export class ProsemirrorAdapter {
|
|
|
87
87
|
return newItem;
|
|
88
88
|
};
|
|
89
89
|
this.updateActiveActionBarItems = (activeTypes, allowedTypes) => {
|
|
90
|
-
|
|
90
|
+
this.actionBarItems = getTextEditorMenuItems()
|
|
91
|
+
.map(this.getTranslatedItem)
|
|
92
|
+
.map((item) => {
|
|
91
93
|
if (isItem(item)) {
|
|
92
|
-
return Object.assign(Object.assign({}, item), { selected: activeTypes[item.value],
|
|
94
|
+
return Object.assign(Object.assign({}, item), { selected: activeTypes[item.value], disabled: !allowedTypes[item.value] });
|
|
93
95
|
}
|
|
94
96
|
return item;
|
|
95
97
|
});
|
|
96
|
-
this.actionBarItems = newItems.filter((item) => isItem(item) ? item.allowed : true);
|
|
97
98
|
};
|
|
98
99
|
this.handleTransaction = (transaction) => {
|
|
99
100
|
this.transactionFired = true;
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
export const TAB = 'Tab';
|
|
4
4
|
export const ENTER = 'Enter';
|
|
5
5
|
export const ESCAPE = 'Escape';
|
|
6
|
+
/** `KeyboardEvent.code` for the space bar. NOT a `KeyboardEvent.key`. */
|
|
6
7
|
export const SPACE = 'Space';
|
|
8
|
+
/** `KeyboardEvent.key` for the space bar. */
|
|
9
|
+
export const SPACEBAR = ' ';
|
|
7
10
|
export const BACKSPACE = 'Backspace';
|
|
8
11
|
export const DELETE = 'Delete';
|
|
9
12
|
export const ARROW_UP = 'ArrowUp';
|