@prosekit/extensions 0.9.3 → 0.10.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/dist/prosekit-extensions-autocomplete.d.ts +15 -4
- package/dist/prosekit-extensions-commit.js +3 -3
- package/dist/prosekit-extensions-link.js +2 -2
- package/dist/prosekit-extensions-placeholder.js +1 -1
- package/dist/prosekit-extensions-table.d.ts +73 -5
- package/dist/prosekit-extensions-table.js +2 -2
- package/dist/prosekit-extensions-yjs.d.ts +1 -2
- package/dist/shiki-highlighter-chunk.js +8 -4
- package/dist/table/style.css +5 -0
- package/dist/table-dVQqzLlz.js +734 -0
- package/package.json +12 -12
- package/dist/table-DnVliJ6E.js +0 -287
@@ -40,6 +40,19 @@ interface MatchHandlerOptions {
|
|
40
40
|
*/
|
41
41
|
type MatchHandler = (options: MatchHandlerOptions) => void;
|
42
42
|
/**
|
43
|
+
* Options for the {@link CanMatchPredicate} callback.
|
44
|
+
*/
|
45
|
+
interface CanMatchOptions {
|
46
|
+
/**
|
47
|
+
* The editor state.
|
48
|
+
*/
|
49
|
+
state: EditorState;
|
50
|
+
}
|
51
|
+
/**
|
52
|
+
* A predicate to determine if the rule can be applied in the current editor state.
|
53
|
+
*/
|
54
|
+
type CanMatchPredicate = (options: CanMatchOptions) => boolean;
|
55
|
+
/**
|
43
56
|
* Options for creating an {@link AutocompleteRule}
|
44
57
|
*/
|
45
58
|
interface AutocompleteRuleOptions {
|
@@ -65,9 +78,7 @@ interface AutocompleteRuleOptions {
|
|
65
78
|
* state. If not provided, it defaults to only allowing matches in empty
|
66
79
|
* selections that are not inside a code block or code mark.
|
67
80
|
*/
|
68
|
-
canMatch?:
|
69
|
-
state: EditorState;
|
70
|
-
}) => boolean;
|
81
|
+
canMatch?: CanMatchPredicate;
|
71
82
|
}
|
72
83
|
/**
|
73
84
|
* An autocomplete rule that can be used to create an autocomplete extension.
|
@@ -91,4 +102,4 @@ declare class AutocompleteRule {
|
|
91
102
|
//#region src/autocomplete/autocomplete.d.ts
|
92
103
|
declare function defineAutocomplete(rule: AutocompleteRule): Extension;
|
93
104
|
//#endregion
|
94
|
-
export { AutocompleteRule, AutocompleteRuleOptions, MatchHandler, MatchHandlerOptions, defineAutocomplete };
|
105
|
+
export { AutocompleteRule, AutocompleteRuleOptions, CanMatchOptions, CanMatchPredicate, MatchHandler, MatchHandlerOptions, defineAutocomplete };
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { defineDefaultState, definePlugin, jsonFromNode, union } from "@prosekit/core";
|
2
2
|
import { PluginKey, ProseMirrorPlugin } from "@prosekit/pm/state";
|
3
3
|
import { Decoration, DecorationSet } from "@prosekit/pm/view";
|
4
4
|
import { DOMSerializer, Fragment, Slice } from "@prosekit/pm/model";
|
@@ -33,13 +33,13 @@ function decorateDeletionSlice(slice) {
|
|
33
33
|
];
|
34
34
|
}
|
35
35
|
if (openStart > 0 && content.childCount >= 2) {
|
36
|
-
const nodes =
|
36
|
+
const nodes = content.content;
|
37
37
|
const head = Fragment.from(nodes.slice(0, 1));
|
38
38
|
const body = Fragment.from(nodes.slice(1));
|
39
39
|
return [...decorateDeletionSlice(new Slice(head, openStart, openStart)), ...decorateDeletionSlice(new Slice(body, 0, openEnd))];
|
40
40
|
}
|
41
41
|
if (openEnd > 0 && content.childCount >= 2) {
|
42
|
-
const nodes =
|
42
|
+
const nodes = content.content;
|
43
43
|
const body = Fragment.from(nodes.slice(0, -1));
|
44
44
|
const tail = Fragment.from(nodes.slice(-1));
|
45
45
|
return [...decorateDeletionSlice(new Slice(body, openStart, 0)), ...decorateDeletionSlice(new Slice(tail, openEnd, openEnd))];
|
@@ -9,10 +9,10 @@ const TLD_RE_PATTERN = "a(?:a(?:a|rp)|b(?:arth|b(?:ott|vie)?|c|le|ogado|udhabi)|
|
|
9
9
|
const PUNCTUATION_CHAR_PATTERN = "\\.\\,\\;\\!\\?";
|
10
10
|
const STOP_CHAR_PATTERN = "[" + PUNCTUATION_CHAR_PATTERN + "]";
|
11
11
|
const END_CHAR_PATTERN = "[^\\s" + PUNCTUATION_CHAR_PATTERN + "]";
|
12
|
-
const LINK_RE_BASE_PATTERN = "((?:(?:(?:https?:)?\\/\\/)?(?:(?:[a-z0-9\\u00a1-\\uffff][a-z0-9\\u00a1-\\uffff_-]{0,62})?[a-z0-9\\u00a1-\\uffff]\\.)+(?:" + TLD_RE_PATTERN + "))(?::\\d{2,5})?(?:/(?:\\S*
|
12
|
+
const LINK_RE_BASE_PATTERN = "((?:(?:(?:https?:)?\\/\\/)?(?:(?:[a-z0-9\\u00a1-\\uffff][a-z0-9\\u00a1-\\uffff_-]{0,62})?[a-z0-9\\u00a1-\\uffff]\\.)+(?:" + TLD_RE_PATTERN + "))(?::\\d{2,5})?(?:/(?:\\S*[^\\s\\.\\,\\;\\!\\?])?)?(?:\\?(?:\\S*[^\\s\\.\\,\\;\\!\\?]))?(?:\\#(?:\\S*[^\\s\\.\\,\\;\\!\\?])?)?)";
|
13
13
|
const LINK_ENTER_PATTERN = LINK_RE_BASE_PATTERN + STOP_CHAR_PATTERN + "?$";
|
14
14
|
const LINK_INPUT_PATTERN = LINK_RE_BASE_PATTERN + STOP_CHAR_PATTERN + "?\\s$";
|
15
|
-
const LINK_MARK_PATTERN = LINK_RE_BASE_PATTERN + "(?=
|
15
|
+
const LINK_MARK_PATTERN = LINK_RE_BASE_PATTERN + "(?=[\\.\\,\\;\\!\\?]|\\s|$)";
|
16
16
|
const LINK_ENTER_RE = new RegExp(LINK_ENTER_PATTERN, "gi");
|
17
17
|
const LINK_INPUT_RE = new RegExp(LINK_INPUT_PATTERN, "gi");
|
18
18
|
const LINK_MARK_RE = new RegExp(LINK_MARK_PATTERN, "gi");
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { findTable } from "./table-
|
1
|
+
import { findTable } from "./table-dVQqzLlz.js";
|
2
2
|
import { definePlugin, isInCodeBlock, maybeRun } from "@prosekit/core";
|
3
3
|
import { Plugin, PluginKey } from "@prosekit/pm/state";
|
4
4
|
import { Decoration, DecorationSet } from "@prosekit/pm/view";
|
@@ -3,6 +3,72 @@ import { Command, Selection } from "@prosekit/pm/state";
|
|
3
3
|
import { Attrs, ProseMirrorNode, ResolvedPos } from "@prosekit/pm/model";
|
4
4
|
import { CellSelection } from "prosemirror-tables";
|
5
5
|
|
6
|
+
//#region src/table/table-commands/move-table-column.d.ts
|
7
|
+
/**
|
8
|
+
* Options for {@link moveTableColumn}
|
9
|
+
*
|
10
|
+
* @public
|
11
|
+
*/
|
12
|
+
interface MoveTableColumnOptions {
|
13
|
+
/**
|
14
|
+
* The source column index to move from.
|
15
|
+
*/
|
16
|
+
from: number;
|
17
|
+
/**
|
18
|
+
* The destination column index to move to.
|
19
|
+
*/
|
20
|
+
to: number;
|
21
|
+
/**
|
22
|
+
* Whether to select the moved column after the operation.
|
23
|
+
*
|
24
|
+
* @default true
|
25
|
+
*/
|
26
|
+
select?: boolean;
|
27
|
+
/**
|
28
|
+
* Optional position to resolve table from. If not provided, uses the current selection.
|
29
|
+
*/
|
30
|
+
pos?: number;
|
31
|
+
}
|
32
|
+
/**
|
33
|
+
* Move a table column from index `origin` to index `target`.
|
34
|
+
*
|
35
|
+
* @public
|
36
|
+
*/
|
37
|
+
declare function moveTableColumn(options: MoveTableColumnOptions): Command;
|
38
|
+
//#endregion
|
39
|
+
//#region src/table/table-commands/move-table-row.d.ts
|
40
|
+
/**
|
41
|
+
* Options for {@link moveTableRow}
|
42
|
+
*
|
43
|
+
* @public
|
44
|
+
*/
|
45
|
+
interface MoveTableRowOptions {
|
46
|
+
/**
|
47
|
+
* The source row index to move from.
|
48
|
+
*/
|
49
|
+
from: number;
|
50
|
+
/**
|
51
|
+
* The destination row index to move to.
|
52
|
+
*/
|
53
|
+
to: number;
|
54
|
+
/**
|
55
|
+
* Whether to select the moved row after the operation.
|
56
|
+
*
|
57
|
+
* @default true
|
58
|
+
*/
|
59
|
+
select?: boolean;
|
60
|
+
/**
|
61
|
+
* Optional position to resolve table from. If not provided, uses the current selection.
|
62
|
+
*/
|
63
|
+
pos?: number;
|
64
|
+
}
|
65
|
+
/**
|
66
|
+
* Move a table row from index `origin` to index `target`.
|
67
|
+
*
|
68
|
+
* @public
|
69
|
+
*/
|
70
|
+
declare function moveTableRow(options: MoveTableRowOptions): Command;
|
71
|
+
//#endregion
|
6
72
|
//#region src/table/table-commands.d.ts
|
7
73
|
/**
|
8
74
|
* @public
|
@@ -126,6 +192,8 @@ type TableCommandsExtension = Extension<{
|
|
126
192
|
deleteCellSelection: [];
|
127
193
|
mergeTableCells: [];
|
128
194
|
splitTableCell: [];
|
195
|
+
moveTableRow: [options: MoveTableRowOptions];
|
196
|
+
moveTableColumn: [options: MoveTableColumnOptions];
|
129
197
|
};
|
130
198
|
}>;
|
131
199
|
/**
|
@@ -140,9 +208,9 @@ declare function defineTableCommands(): TableCommandsExtension;
|
|
140
208
|
* @public
|
141
209
|
*/
|
142
210
|
interface CellAttrs {
|
143
|
-
colspan
|
144
|
-
rowspan
|
145
|
-
colwidth
|
211
|
+
colspan?: number;
|
212
|
+
rowspan?: number;
|
213
|
+
colwidth?: number[] | null;
|
146
214
|
}
|
147
215
|
/**
|
148
216
|
* @internal
|
@@ -206,7 +274,7 @@ declare function defineTable(): TableExtension;
|
|
206
274
|
*/
|
207
275
|
declare function defineTablePlugins(): PlainExtension;
|
208
276
|
//#endregion
|
209
|
-
//#region src/table/table-utils.d.ts
|
277
|
+
//#region src/table/table-utils/query.d.ts
|
210
278
|
/**
|
211
279
|
* Checks if the given object is a `CellSelection` instance.
|
212
280
|
*
|
@@ -228,4 +296,4 @@ declare function findTable($pos: ResolvedPos): FindParentNodeResult | undefined;
|
|
228
296
|
*/
|
229
297
|
|
230
298
|
//#endregion
|
231
|
-
export { InsertTableOptions, SelectTableCellOptions, SelectTableColumnOptions, SelectTableOptions, SelectTableRowOptions, TableCellSpecExtension, TableCommandsExtension, TableExtension, TableHeaderCellSpecExtension, TableRowSpecExtension, TableSpecExtension, defineTable, defineTableCellSpec, defineTableCommands, defineTableHeaderCellSpec, defineTablePlugins, defineTableRowSpec, defineTableSpec, exitTable, findTable, insertTable, isCellSelection, selectTable, selectTableCell, selectTableColumn, selectTableRow };
|
299
|
+
export { InsertTableOptions, MoveTableColumnOptions, MoveTableRowOptions, SelectTableCellOptions, SelectTableColumnOptions, SelectTableOptions, SelectTableRowOptions, TableCellSpecExtension, TableCommandsExtension, TableExtension, TableHeaderCellSpecExtension, TableRowSpecExtension, TableSpecExtension, defineTable, defineTableCellSpec, defineTableCommands, defineTableHeaderCellSpec, defineTablePlugins, defineTableRowSpec, defineTableSpec, exitTable, findTable, insertTable, isCellSelection, moveTableColumn, moveTableRow, selectTable, selectTableCell, selectTableColumn, selectTableRow };
|
@@ -1,3 +1,3 @@
|
|
1
|
-
import { defineTable, defineTableCellSpec, defineTableCommands, defineTableHeaderCellSpec, defineTablePlugins, defineTableRowSpec, defineTableSpec, exitTable, findTable, insertTable, isCellSelection, selectTable, selectTableCell, selectTableColumn, selectTableRow } from "./table-
|
1
|
+
import { defineTable, defineTableCellSpec, defineTableCommands, defineTableHeaderCellSpec, defineTablePlugins, defineTableRowSpec, defineTableSpec, exitTable, findTable, insertTable, isCellSelection, moveTableColumn, moveTableRow, selectTable, selectTableCell, selectTableColumn, selectTableRow } from "./table-dVQqzLlz.js";
|
2
2
|
|
3
|
-
export { defineTable, defineTableCellSpec, defineTableCommands, defineTableHeaderCellSpec, defineTablePlugins, defineTableRowSpec, defineTableSpec, exitTable, findTable, insertTable, isCellSelection, selectTable, selectTableCell, selectTableColumn, selectTableRow };
|
3
|
+
export { defineTable, defineTableCellSpec, defineTableCommands, defineTableHeaderCellSpec, defineTablePlugins, defineTableRowSpec, defineTableSpec, exitTable, findTable, insertTable, isCellSelection, moveTableColumn, moveTableRow, selectTable, selectTableCell, selectTableColumn, selectTableRow };
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import { Extension, PlainExtension, Union } from "@prosekit/core";
|
2
2
|
import { yCursorPlugin, ySyncPlugin, yUndoPlugin } from "y-prosemirror";
|
3
3
|
import { Awareness } from "y-protocols/awareness";
|
4
|
-
import * as Y$1 from "yjs";
|
5
4
|
import * as Y from "yjs";
|
6
5
|
|
7
6
|
//#region src/yjs/yjs-commands.d.ts
|
@@ -32,7 +31,7 @@ declare function defineYjsCursorPlugin(options: YjsCursorOptions): PlainExtensio
|
|
32
31
|
*/
|
33
32
|
type YjsSyncPluginOptions = NonNullable<Parameters<typeof ySyncPlugin>[1]>;
|
34
33
|
interface YjsSyncOptions extends YjsSyncPluginOptions {
|
35
|
-
fragment: Y
|
34
|
+
fragment: Y.XmlFragment;
|
36
35
|
}
|
37
36
|
declare function defineYjsSyncPlugin(options: YjsSyncOptions): PlainExtension;
|
38
37
|
//#endregion
|
@@ -2,17 +2,21 @@ import { createHighlighter } from "shiki/bundle/full";
|
|
2
2
|
import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
|
3
3
|
|
4
4
|
//#region src/code-block/shiki-highlighter-chunk.ts
|
5
|
+
let highlighterPromise;
|
5
6
|
let highlighter;
|
6
7
|
const loadedLangs = /* @__PURE__ */ new Set();
|
7
8
|
const loadedThemes = /* @__PURE__ */ new Set();
|
8
|
-
|
9
|
-
if (!
|
9
|
+
function ensureHighlighter({ ...options }) {
|
10
|
+
if (!highlighterPromise) {
|
10
11
|
if (!options.engine) {
|
11
12
|
const engine = createJavaScriptRegexEngine({ forgiving: true });
|
12
13
|
options.engine = engine;
|
13
14
|
}
|
14
|
-
|
15
|
+
highlighterPromise = createHighlighter(options).then((createdHighlighter) => {
|
16
|
+
highlighter = createdHighlighter;
|
17
|
+
});
|
15
18
|
}
|
19
|
+
return highlighterPromise;
|
16
20
|
}
|
17
21
|
async function loadLanguages(langs) {
|
18
22
|
for (const lang of langs) {
|
@@ -29,7 +33,7 @@ async function loadThemes(themes) {
|
|
29
33
|
}
|
30
34
|
}
|
31
35
|
function createOrGetHighlighter(options) {
|
32
|
-
if (!highlighter) return { promise:
|
36
|
+
if (!highlighter) return { promise: ensureHighlighter(options) };
|
33
37
|
const langs = options.langs.filter((lang) => !loadedLangs.has(lang));
|
34
38
|
if (langs.length > 0) return { promise: loadLanguages(langs) };
|
35
39
|
const themes = options.themes.filter((theme) => !loadedThemes.has(theme));
|
package/dist/table/style.css
CHANGED
@@ -0,0 +1,734 @@
|
|
1
|
+
import { defaultBlockAt, defineCommands, defineNodeSpec, definePlugin, findParentNode, getNodeType, insertNode, union } from "@prosekit/core";
|
2
|
+
import { TextSelection } from "@prosekit/pm/state";
|
3
|
+
import { CellSelection, TableMap, addColumnAfter, addColumnBefore, addRowAfter, addRowBefore, cellAround, cellNear, columnResizing, deleteCellSelection, deleteColumn, deleteRow, deleteTable, inSameTable, mergeCells, splitCell, tableEditing, tableNodes } from "prosemirror-tables";
|
4
|
+
|
5
|
+
//#region src/table/table-utils/convert-array-of-rows-to-table-node.ts
|
6
|
+
/**
|
7
|
+
* Convert an array of rows to a table node.
|
8
|
+
*
|
9
|
+
* @internal
|
10
|
+
*/
|
11
|
+
function convertArrayOfRowsToTableNode(tableNode, arrayOfNodes) {
|
12
|
+
const rowsPM = [];
|
13
|
+
const map = TableMap.get(tableNode);
|
14
|
+
for (let rowIndex = 0; rowIndex < map.height; rowIndex++) {
|
15
|
+
const row = tableNode.child(rowIndex);
|
16
|
+
const rowCells = [];
|
17
|
+
for (let colIndex = 0; colIndex < map.width; colIndex++) {
|
18
|
+
if (!arrayOfNodes[rowIndex][colIndex]) continue;
|
19
|
+
const cellPos = map.map[rowIndex * map.width + colIndex];
|
20
|
+
const cell = arrayOfNodes[rowIndex][colIndex];
|
21
|
+
const oldCell = tableNode.nodeAt(cellPos);
|
22
|
+
const newCell = oldCell.type.createChecked(Object.assign({}, cell.attrs), cell.content, cell.marks);
|
23
|
+
rowCells.push(newCell);
|
24
|
+
}
|
25
|
+
rowsPM.push(row.type.createChecked(row.attrs, rowCells, row.marks));
|
26
|
+
}
|
27
|
+
const newTable = tableNode.type.createChecked(tableNode.attrs, rowsPM, tableNode.marks);
|
28
|
+
return newTable;
|
29
|
+
}
|
30
|
+
|
31
|
+
//#endregion
|
32
|
+
//#region src/table/table-utils/convert-table-node-to-array-of-rows.ts
|
33
|
+
/**
|
34
|
+
* This function will transform the table node into a matrix of rows and columns
|
35
|
+
* respecting merged cells, for example this table:
|
36
|
+
*
|
37
|
+
* ```
|
38
|
+
* ┌──────┬──────┬─────────────┐
|
39
|
+
* │ A1 │ B1 │ C1 │
|
40
|
+
* ├──────┼──────┴──────┬──────┤
|
41
|
+
* │ A2 │ B2 │ │
|
42
|
+
* ├──────┼─────────────┤ D1 │
|
43
|
+
* │ A3 │ B3 │ C3 │ │
|
44
|
+
* └──────┴──────┴──────┴──────┘
|
45
|
+
* ```
|
46
|
+
*
|
47
|
+
* will be converted to the below:
|
48
|
+
*
|
49
|
+
* ```javascript
|
50
|
+
* [
|
51
|
+
* [A1, B1, C1, null],
|
52
|
+
* [A2, B2, null, D1],
|
53
|
+
* [A3, B3, C3, null],
|
54
|
+
* ]
|
55
|
+
* ```
|
56
|
+
* @internal
|
57
|
+
*/
|
58
|
+
function convertTableNodeToArrayOfRows(tableNode) {
|
59
|
+
const map = TableMap.get(tableNode);
|
60
|
+
const rows = [];
|
61
|
+
const rowCount = map.height;
|
62
|
+
const colCount = map.width;
|
63
|
+
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
64
|
+
const row = [];
|
65
|
+
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
66
|
+
let cellIndex = rowIndex * colCount + colIndex;
|
67
|
+
let cellPos = map.map[cellIndex];
|
68
|
+
if (rowIndex > 0) {
|
69
|
+
const topCellIndex = cellIndex - colCount;
|
70
|
+
const topCellPos = map.map[topCellIndex];
|
71
|
+
if (cellPos === topCellPos) {
|
72
|
+
row.push(null);
|
73
|
+
continue;
|
74
|
+
}
|
75
|
+
}
|
76
|
+
if (colIndex > 0) {
|
77
|
+
const leftCellIndex = cellIndex - 1;
|
78
|
+
const leftCellPos = map.map[leftCellIndex];
|
79
|
+
if (cellPos === leftCellPos) {
|
80
|
+
row.push(null);
|
81
|
+
continue;
|
82
|
+
}
|
83
|
+
}
|
84
|
+
row.push(tableNode.nodeAt(cellPos));
|
85
|
+
}
|
86
|
+
rows.push(row);
|
87
|
+
}
|
88
|
+
return rows;
|
89
|
+
}
|
90
|
+
|
91
|
+
//#endregion
|
92
|
+
//#region src/table/table-utils/query.ts
|
93
|
+
/**
|
94
|
+
* Checks if the given object is a `CellSelection` instance.
|
95
|
+
*
|
96
|
+
* @public
|
97
|
+
*/
|
98
|
+
function isCellSelection(value) {
|
99
|
+
return value instanceof CellSelection;
|
100
|
+
}
|
101
|
+
/**
|
102
|
+
* Find the closest table node.
|
103
|
+
*
|
104
|
+
* @internal
|
105
|
+
*/
|
106
|
+
function findTable($pos) {
|
107
|
+
return findParentNode((node) => node.type.spec.tableRole === "table", $pos);
|
108
|
+
}
|
109
|
+
/**
|
110
|
+
* Try to find the anchor and head cell in the same table by using the given
|
111
|
+
* anchor and head as hit points, or fallback to the selection's anchor and
|
112
|
+
* head.
|
113
|
+
*
|
114
|
+
* @internal
|
115
|
+
*/
|
116
|
+
function findCellRange(selection, anchorHit, headHit) {
|
117
|
+
if (anchorHit == null && headHit == null && isCellSelection(selection)) return [selection.$anchorCell, selection.$headCell];
|
118
|
+
const anchor = anchorHit ?? headHit ?? selection.anchor;
|
119
|
+
const head = headHit ?? anchorHit ?? selection.head;
|
120
|
+
const doc = selection.$head.doc;
|
121
|
+
const $anchorCell = findCellPos(doc, anchor);
|
122
|
+
const $headCell = findCellPos(doc, head);
|
123
|
+
if ($anchorCell && $headCell && inSameTable($anchorCell, $headCell)) return [$anchorCell, $headCell];
|
124
|
+
}
|
125
|
+
/**
|
126
|
+
* Try to find a resolved pos of a cell by using the given pos as a hit point.
|
127
|
+
*
|
128
|
+
* @internal
|
129
|
+
*/
|
130
|
+
function findCellPos(doc, pos) {
|
131
|
+
const $pos = doc.resolve(pos);
|
132
|
+
return cellAround($pos) || cellNear($pos);
|
133
|
+
}
|
134
|
+
|
135
|
+
//#endregion
|
136
|
+
//#region src/table/table-utils/get-cells-in-column.ts
|
137
|
+
/**
|
138
|
+
* Returns an array of cells in a column(s), where `columnIndex` could be a column index or an array of column indexes.
|
139
|
+
*
|
140
|
+
* @internal
|
141
|
+
*/
|
142
|
+
function getCellsInColumn(columnIndexes, selection) {
|
143
|
+
const table = findTable(selection.$from);
|
144
|
+
if (!table) return;
|
145
|
+
const map = TableMap.get(table.node);
|
146
|
+
const indexes = Array.isArray(columnIndexes) ? columnIndexes : [columnIndexes];
|
147
|
+
return indexes.filter((index) => index >= 0 && index <= map.width - 1).flatMap((index) => {
|
148
|
+
const cells = map.cellsInRect({
|
149
|
+
left: index,
|
150
|
+
right: index + 1,
|
151
|
+
top: 0,
|
152
|
+
bottom: map.height
|
153
|
+
});
|
154
|
+
return cells.map((nodePos) => {
|
155
|
+
const node = table.node.nodeAt(nodePos);
|
156
|
+
const pos = nodePos + table.start;
|
157
|
+
return {
|
158
|
+
pos,
|
159
|
+
start: pos + 1,
|
160
|
+
node,
|
161
|
+
depth: table.depth + 2
|
162
|
+
};
|
163
|
+
});
|
164
|
+
});
|
165
|
+
}
|
166
|
+
|
167
|
+
//#endregion
|
168
|
+
//#region src/table/table-utils/get-cells-in-row.ts
|
169
|
+
/**
|
170
|
+
* Returns an array of cells in a row(s), where `rowIndex` could be a row index or an array of row indexes.
|
171
|
+
*
|
172
|
+
* @internal
|
173
|
+
*/
|
174
|
+
function getCellsInRow(rowIndex, selection) {
|
175
|
+
const table = findTable(selection.$from);
|
176
|
+
if (!table) return;
|
177
|
+
const map = TableMap.get(table.node);
|
178
|
+
const indexes = Array.isArray(rowIndex) ? rowIndex : [rowIndex];
|
179
|
+
return indexes.filter((index) => index >= 0 && index <= map.height - 1).flatMap((index) => {
|
180
|
+
const cells = map.cellsInRect({
|
181
|
+
left: 0,
|
182
|
+
right: map.width,
|
183
|
+
top: index,
|
184
|
+
bottom: index + 1
|
185
|
+
});
|
186
|
+
return cells.map((nodePos) => {
|
187
|
+
const node = table.node.nodeAt(nodePos);
|
188
|
+
const pos = nodePos + table.start;
|
189
|
+
return {
|
190
|
+
pos,
|
191
|
+
start: pos + 1,
|
192
|
+
node,
|
193
|
+
depth: table.depth + 2
|
194
|
+
};
|
195
|
+
});
|
196
|
+
});
|
197
|
+
}
|
198
|
+
|
199
|
+
//#endregion
|
200
|
+
//#region src/table/table-utils/get-selection-range-in-column.ts
|
201
|
+
/**
|
202
|
+
* Returns a range of rectangular selection spanning all merged cells around a
|
203
|
+
* column at index `columnIndex`.
|
204
|
+
*
|
205
|
+
* Original implementation from Atlassian (Apache License 2.0)
|
206
|
+
*
|
207
|
+
* https://bitbucket.org/atlassian/atlassian-frontend-mirror/src/5f91cb871e8248bc3bae5ddc30bb9fd9200fadbb/editor/editor-tables/src/utils/get-selection-range-in-column.ts#editor/editor-tables/src/utils/get-selection-range-in-column.ts
|
208
|
+
*
|
209
|
+
* @internal
|
210
|
+
*/
|
211
|
+
function getSelectionRangeInColumn(tr, startColIndex, endColIndex = startColIndex) {
|
212
|
+
let startIndex = startColIndex;
|
213
|
+
let endIndex = endColIndex;
|
214
|
+
for (let i = startColIndex; i >= 0; i--) {
|
215
|
+
const cells = getCellsInColumn(i, tr.selection);
|
216
|
+
if (cells) cells.forEach((cell) => {
|
217
|
+
const maybeEndIndex = cell.node.attrs.colspan + i - 1;
|
218
|
+
if (maybeEndIndex >= startIndex) startIndex = i;
|
219
|
+
if (maybeEndIndex > endIndex) endIndex = maybeEndIndex;
|
220
|
+
});
|
221
|
+
}
|
222
|
+
for (let i = startColIndex; i <= endIndex; i++) {
|
223
|
+
const cells = getCellsInColumn(i, tr.selection);
|
224
|
+
if (cells) cells.forEach((cell) => {
|
225
|
+
const maybeEndIndex = cell.node.attrs.colspan + i - 1;
|
226
|
+
if (cell.node.attrs.colspan > 1 && maybeEndIndex > endIndex) endIndex = maybeEndIndex;
|
227
|
+
});
|
228
|
+
}
|
229
|
+
const indexes = [];
|
230
|
+
for (let i = startIndex; i <= endIndex; i++) {
|
231
|
+
const maybeCells = getCellsInColumn(i, tr.selection);
|
232
|
+
if (maybeCells && maybeCells.length > 0) indexes.push(i);
|
233
|
+
}
|
234
|
+
startIndex = indexes[0];
|
235
|
+
endIndex = indexes[indexes.length - 1];
|
236
|
+
const firstSelectedColumnCells = getCellsInColumn(startIndex, tr.selection);
|
237
|
+
const firstRowCells = getCellsInRow(0, tr.selection);
|
238
|
+
if (!firstSelectedColumnCells || !firstRowCells) return;
|
239
|
+
const $anchor = tr.doc.resolve(firstSelectedColumnCells[firstSelectedColumnCells.length - 1].pos);
|
240
|
+
let headCell;
|
241
|
+
for (let i = endIndex; i >= startIndex; i--) {
|
242
|
+
const columnCells = getCellsInColumn(i, tr.selection);
|
243
|
+
if (columnCells && columnCells.length > 0) {
|
244
|
+
for (let j = firstRowCells.length - 1; j >= 0; j--) if (firstRowCells[j].pos === columnCells[0].pos) {
|
245
|
+
headCell = columnCells[0];
|
246
|
+
break;
|
247
|
+
}
|
248
|
+
if (headCell) break;
|
249
|
+
}
|
250
|
+
}
|
251
|
+
if (!headCell) return;
|
252
|
+
const $head = tr.doc.resolve(headCell.pos);
|
253
|
+
return {
|
254
|
+
$anchor,
|
255
|
+
$head,
|
256
|
+
indexes
|
257
|
+
};
|
258
|
+
}
|
259
|
+
|
260
|
+
//#endregion
|
261
|
+
//#region src/table/table-utils/move-row-in-array-of-rows.ts
|
262
|
+
/**
|
263
|
+
* Move a row in an array of rows.
|
264
|
+
*
|
265
|
+
* @internal
|
266
|
+
*/
|
267
|
+
function moveRowInArrayOfRows(rows, indexesOrigin, indexesTarget, directionOverride) {
|
268
|
+
const direction = indexesOrigin[0] > indexesTarget[0] ? -1 : 1;
|
269
|
+
const rowsExtracted = rows.splice(indexesOrigin[0], indexesOrigin.length);
|
270
|
+
const positionOffset = rowsExtracted.length % 2 === 0 ? 1 : 0;
|
271
|
+
let target;
|
272
|
+
if (directionOverride === -1 && direction === 1) target = indexesTarget[0] - 1;
|
273
|
+
else if (directionOverride === 1 && direction === -1) target = indexesTarget[indexesTarget.length - 1] - positionOffset + 1;
|
274
|
+
else target = direction === -1 ? indexesTarget[0] : indexesTarget[indexesTarget.length - 1] - positionOffset;
|
275
|
+
rows.splice(target, 0, ...rowsExtracted);
|
276
|
+
return rows;
|
277
|
+
}
|
278
|
+
|
279
|
+
//#endregion
|
280
|
+
//#region src/table/table-utils/transpose.ts
|
281
|
+
/**
|
282
|
+
* Transposes a 2D array by flipping columns to rows.
|
283
|
+
*
|
284
|
+
* Transposition is a familiar algebra concept where the matrix is flipped
|
285
|
+
* along its diagonal. For more details, see:
|
286
|
+
* https://en.wikipedia.org/wiki/Transpose
|
287
|
+
*
|
288
|
+
* @example
|
289
|
+
* ```javascript
|
290
|
+
* const arr = [
|
291
|
+
* ['a1', 'a2', 'a3'],
|
292
|
+
* ['b1', 'b2', 'b3'],
|
293
|
+
* ['c1', 'c2', 'c3'],
|
294
|
+
* ['d1', 'd2', 'd3'],
|
295
|
+
* ];
|
296
|
+
*
|
297
|
+
* const result = transpose(arr);
|
298
|
+
* result === [
|
299
|
+
* ['a1', 'b1', 'c1', 'd1'],
|
300
|
+
* ['a2', 'b2', 'c2', 'd2'],
|
301
|
+
* ['a3', 'b3', 'c3', 'd3'],
|
302
|
+
* ]
|
303
|
+
* ```
|
304
|
+
*/
|
305
|
+
function transpose(array) {
|
306
|
+
return array[0].map((_, i) => {
|
307
|
+
return array.map((column) => column[i]);
|
308
|
+
});
|
309
|
+
}
|
310
|
+
|
311
|
+
//#endregion
|
312
|
+
//#region src/table/table-utils/move-column.ts
|
313
|
+
/**
|
314
|
+
* Move a column from index `origin` to index `target`.
|
315
|
+
*
|
316
|
+
* @internal
|
317
|
+
*/
|
318
|
+
function moveColumn(moveColParams) {
|
319
|
+
const { tr, originIndex, targetIndex, select, pos } = moveColParams;
|
320
|
+
const $pos = tr.doc.resolve(pos);
|
321
|
+
const table = findTable($pos);
|
322
|
+
if (!table) return false;
|
323
|
+
const indexesOriginColumn = getSelectionRangeInColumn(tr, originIndex)?.indexes;
|
324
|
+
const indexesTargetColumn = getSelectionRangeInColumn(tr, targetIndex)?.indexes;
|
325
|
+
if (!indexesOriginColumn || !indexesTargetColumn) return false;
|
326
|
+
if (indexesOriginColumn.includes(targetIndex)) return false;
|
327
|
+
const newTable = moveTableColumn$1(table.node, indexesOriginColumn, indexesTargetColumn, 0);
|
328
|
+
tr.replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
|
329
|
+
if (!select) return true;
|
330
|
+
const map = TableMap.get(newTable);
|
331
|
+
const start = table.start;
|
332
|
+
const index = targetIndex;
|
333
|
+
const lastCell = map.positionAt(map.height - 1, index, newTable);
|
334
|
+
const $lastCell = tr.doc.resolve(start + lastCell);
|
335
|
+
const firstCell = map.positionAt(0, index, newTable);
|
336
|
+
const $firstCell = tr.doc.resolve(start + firstCell);
|
337
|
+
tr.setSelection(CellSelection.colSelection($lastCell, $firstCell));
|
338
|
+
return true;
|
339
|
+
}
|
340
|
+
function moveTableColumn$1(table, indexesOrigin, indexesTarget, direction) {
|
341
|
+
let rows = transpose(convertTableNodeToArrayOfRows(table));
|
342
|
+
rows = moveRowInArrayOfRows(rows, indexesOrigin, indexesTarget, direction);
|
343
|
+
rows = transpose(rows);
|
344
|
+
return convertArrayOfRowsToTableNode(table, rows);
|
345
|
+
}
|
346
|
+
|
347
|
+
//#endregion
|
348
|
+
//#region src/table/table-utils/get-selection-range-in-row.ts
|
349
|
+
/**
|
350
|
+
* Returns a range of rectangular selection spanning all merged cells around a
|
351
|
+
* row at index `rowIndex`.
|
352
|
+
*
|
353
|
+
* Original implementation from Atlassian (Apache License 2.0)
|
354
|
+
*
|
355
|
+
* https://bitbucket.org/atlassian/atlassian-frontend-mirror/src/5f91cb871e8248bc3bae5ddc30bb9fd9200fadbb/editor/editor-tables/src/utils/get-selection-range-in-row.ts#editor/editor-tables/src/utils/get-selection-range-in-row.ts
|
356
|
+
*
|
357
|
+
* @internal
|
358
|
+
*/
|
359
|
+
function getSelectionRangeInRow(tr, startRowIndex, endRowIndex = startRowIndex) {
|
360
|
+
let startIndex = startRowIndex;
|
361
|
+
let endIndex = endRowIndex;
|
362
|
+
for (let i = startRowIndex; i >= 0; i--) {
|
363
|
+
const cells = getCellsInRow(i, tr.selection);
|
364
|
+
if (cells) cells.forEach((cell) => {
|
365
|
+
const maybeEndIndex = cell.node.attrs.rowspan + i - 1;
|
366
|
+
if (maybeEndIndex >= startIndex) startIndex = i;
|
367
|
+
if (maybeEndIndex > endIndex) endIndex = maybeEndIndex;
|
368
|
+
});
|
369
|
+
}
|
370
|
+
for (let i = startRowIndex; i <= endIndex; i++) {
|
371
|
+
const cells = getCellsInRow(i, tr.selection);
|
372
|
+
if (cells) cells.forEach((cell) => {
|
373
|
+
const maybeEndIndex = cell.node.attrs.rowspan + i - 1;
|
374
|
+
if (cell.node.attrs.rowspan > 1 && maybeEndIndex > endIndex) endIndex = maybeEndIndex;
|
375
|
+
});
|
376
|
+
}
|
377
|
+
const indexes = [];
|
378
|
+
for (let i = startIndex; i <= endIndex; i++) {
|
379
|
+
const maybeCells = getCellsInRow(i, tr.selection);
|
380
|
+
if (maybeCells && maybeCells.length > 0) indexes.push(i);
|
381
|
+
}
|
382
|
+
startIndex = indexes[0];
|
383
|
+
endIndex = indexes[indexes.length - 1];
|
384
|
+
const firstSelectedRowCells = getCellsInRow(startIndex, tr.selection);
|
385
|
+
const firstColumnCells = getCellsInColumn(0, tr.selection);
|
386
|
+
if (!firstSelectedRowCells || !firstColumnCells) return;
|
387
|
+
const $anchor = tr.doc.resolve(firstSelectedRowCells[firstSelectedRowCells.length - 1].pos);
|
388
|
+
let headCell;
|
389
|
+
for (let i = endIndex; i >= startIndex; i--) {
|
390
|
+
const rowCells = getCellsInRow(i, tr.selection);
|
391
|
+
if (rowCells && rowCells.length > 0) {
|
392
|
+
for (let j = firstColumnCells.length - 1; j >= 0; j--) if (firstColumnCells[j].pos === rowCells[0].pos) {
|
393
|
+
headCell = rowCells[0];
|
394
|
+
break;
|
395
|
+
}
|
396
|
+
if (headCell) break;
|
397
|
+
}
|
398
|
+
}
|
399
|
+
if (!headCell) return;
|
400
|
+
const $head = tr.doc.resolve(headCell.pos);
|
401
|
+
return {
|
402
|
+
$anchor,
|
403
|
+
$head,
|
404
|
+
indexes
|
405
|
+
};
|
406
|
+
}
|
407
|
+
|
408
|
+
//#endregion
|
409
|
+
//#region src/table/table-utils/move-row.ts
|
410
|
+
/**
|
411
|
+
* Move a row from index `origin` to index `target`.
|
412
|
+
*
|
413
|
+
* @internal
|
414
|
+
*/
|
415
|
+
function moveRow(moveRowParams) {
|
416
|
+
const { tr, originIndex, targetIndex, select, pos } = moveRowParams;
|
417
|
+
const $pos = tr.doc.resolve(pos);
|
418
|
+
const table = findTable($pos);
|
419
|
+
if (!table) return false;
|
420
|
+
const indexesOriginRow = getSelectionRangeInRow(tr, originIndex)?.indexes;
|
421
|
+
const indexesTargetRow = getSelectionRangeInRow(tr, targetIndex)?.indexes;
|
422
|
+
if (!indexesOriginRow || !indexesTargetRow) return false;
|
423
|
+
if (indexesOriginRow.includes(targetIndex)) return false;
|
424
|
+
const newTable = moveTableRow$1(table.node, indexesOriginRow, indexesTargetRow, 0);
|
425
|
+
tr.replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
|
426
|
+
if (!select) return true;
|
427
|
+
const map = TableMap.get(newTable);
|
428
|
+
const start = table.start;
|
429
|
+
const index = targetIndex;
|
430
|
+
const lastCell = map.positionAt(index, map.width - 1, newTable);
|
431
|
+
const $lastCell = tr.doc.resolve(start + lastCell);
|
432
|
+
const firstCell = map.positionAt(index, 0, newTable);
|
433
|
+
const $firstCell = tr.doc.resolve(start + firstCell);
|
434
|
+
tr.setSelection(CellSelection.rowSelection($lastCell, $firstCell));
|
435
|
+
return true;
|
436
|
+
}
|
437
|
+
function moveTableRow$1(table, indexesOrigin, indexesTarget, direction) {
|
438
|
+
let rows = convertTableNodeToArrayOfRows(table);
|
439
|
+
rows = moveRowInArrayOfRows(rows, indexesOrigin, indexesTarget, direction);
|
440
|
+
return convertArrayOfRowsToTableNode(table, rows);
|
441
|
+
}
|
442
|
+
|
443
|
+
//#endregion
|
444
|
+
//#region src/table/table-commands/move-table-column.ts
|
445
|
+
/**
|
446
|
+
* Move a table column from index `origin` to index `target`.
|
447
|
+
*
|
448
|
+
* @public
|
449
|
+
*/
|
450
|
+
function moveTableColumn(options) {
|
451
|
+
return (state, dispatch) => {
|
452
|
+
const { from: originIndex, to: targetIndex, select = true, pos = state.selection.from } = options;
|
453
|
+
const tr = state.tr;
|
454
|
+
if (moveColumn({
|
455
|
+
tr,
|
456
|
+
originIndex,
|
457
|
+
targetIndex,
|
458
|
+
select,
|
459
|
+
pos
|
460
|
+
})) {
|
461
|
+
dispatch?.(tr);
|
462
|
+
return true;
|
463
|
+
}
|
464
|
+
return false;
|
465
|
+
};
|
466
|
+
}
|
467
|
+
|
468
|
+
//#endregion
|
469
|
+
//#region src/table/table-commands/move-table-row.ts
|
470
|
+
/**
|
471
|
+
* Move a table row from index `origin` to index `target`.
|
472
|
+
*
|
473
|
+
* @public
|
474
|
+
*/
|
475
|
+
function moveTableRow(options) {
|
476
|
+
return (state, dispatch) => {
|
477
|
+
const { from: originIndex, to: targetIndex, select = true, pos = state.selection.from } = options;
|
478
|
+
const tr = state.tr;
|
479
|
+
if (moveRow({
|
480
|
+
tr,
|
481
|
+
originIndex,
|
482
|
+
targetIndex,
|
483
|
+
select,
|
484
|
+
pos
|
485
|
+
})) {
|
486
|
+
dispatch?.(tr);
|
487
|
+
return true;
|
488
|
+
}
|
489
|
+
return false;
|
490
|
+
};
|
491
|
+
}
|
492
|
+
|
493
|
+
//#endregion
|
494
|
+
//#region src/table/table-commands.ts
|
495
|
+
function createEmptyTable(schema, row, col, header) {
|
496
|
+
const tableType = getNodeType(schema, "table");
|
497
|
+
const tableRowType = getNodeType(schema, "tableRow");
|
498
|
+
const tableCellType = getNodeType(schema, "tableCell");
|
499
|
+
const tableHeaderCellType = getNodeType(schema, "tableHeaderCell");
|
500
|
+
if (header) {
|
501
|
+
const headerCell = tableHeaderCellType.createAndFill();
|
502
|
+
const headerCells = repeat(headerCell, col);
|
503
|
+
const headerRow = tableRowType.createAndFill(null, headerCells);
|
504
|
+
const bodyCell = tableCellType.createAndFill();
|
505
|
+
const bodyCells = repeat(bodyCell, col);
|
506
|
+
const bodyRow = tableRowType.createAndFill(null, bodyCells);
|
507
|
+
const bodyRows = repeat(bodyRow, row - 1);
|
508
|
+
return tableType.createAndFill(null, [headerRow, ...bodyRows]);
|
509
|
+
} else {
|
510
|
+
const bodyCell = tableCellType.createAndFill();
|
511
|
+
const bodyCells = repeat(bodyCell, col);
|
512
|
+
const bodyRow = tableRowType.createAndFill(null, bodyCells);
|
513
|
+
const bodyRows = repeat(bodyRow, row);
|
514
|
+
return tableType.createAndFill(null, bodyRows);
|
515
|
+
}
|
516
|
+
}
|
517
|
+
function repeat(node, length) {
|
518
|
+
return Array(length).fill(node);
|
519
|
+
}
|
520
|
+
/**
|
521
|
+
* Insert a table node with the given number of rows and columns, and optionally
|
522
|
+
* a header row.
|
523
|
+
*
|
524
|
+
* @param options
|
525
|
+
*
|
526
|
+
* @public
|
527
|
+
*/
|
528
|
+
function insertTable(options) {
|
529
|
+
return (state, dispatch, view) => {
|
530
|
+
const { row, col, header = false } = options;
|
531
|
+
const table = createEmptyTable(state.schema, row, col, header);
|
532
|
+
return insertNode({ node: table })(state, dispatch, view);
|
533
|
+
};
|
534
|
+
}
|
535
|
+
/**
|
536
|
+
* When the selection is in a table node, create a default block after the table
|
537
|
+
* table, and move the cursor there.
|
538
|
+
*
|
539
|
+
* @public
|
540
|
+
*/
|
541
|
+
const exitTable = (state, dispatch) => {
|
542
|
+
const { $head, $anchor } = state.selection;
|
543
|
+
if (!$head.sameParent($anchor)) return false;
|
544
|
+
let tableStart = -1;
|
545
|
+
let tableDepth = -1;
|
546
|
+
for (let depth = $head.depth; depth >= 0; depth--) {
|
547
|
+
const node$1 = $head.node(depth);
|
548
|
+
if (node$1.type.spec.tableRole === "table") {
|
549
|
+
tableStart = $head.before(depth);
|
550
|
+
tableDepth = depth;
|
551
|
+
}
|
552
|
+
}
|
553
|
+
if (tableStart < 0 || tableDepth <= 0) return false;
|
554
|
+
const above = $head.node(tableDepth - 1);
|
555
|
+
const after = $head.indexAfter(tableDepth - 1);
|
556
|
+
const type = defaultBlockAt(above.contentMatchAt(after));
|
557
|
+
const node = type?.createAndFill();
|
558
|
+
if (!type || !node || !above.canReplaceWith(after, after, type)) return false;
|
559
|
+
if (dispatch) {
|
560
|
+
const pos = $head.after(tableDepth);
|
561
|
+
const tr = state.tr.replaceWith(pos, pos, node);
|
562
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve(pos), 1));
|
563
|
+
dispatch(tr.scrollIntoView());
|
564
|
+
}
|
565
|
+
return true;
|
566
|
+
};
|
567
|
+
/**
|
568
|
+
* @public
|
569
|
+
*/
|
570
|
+
function selectTableColumn(options) {
|
571
|
+
return (state, dispatch) => {
|
572
|
+
const range = findCellRange(state.selection, options?.anchor, options?.head);
|
573
|
+
if (!range) return false;
|
574
|
+
if (dispatch) {
|
575
|
+
const [$anchorCell, $headCell] = range;
|
576
|
+
const selection = CellSelection.colSelection($anchorCell, $headCell);
|
577
|
+
dispatch(state.tr.setSelection(selection));
|
578
|
+
}
|
579
|
+
return true;
|
580
|
+
};
|
581
|
+
}
|
582
|
+
/**
|
583
|
+
* @public
|
584
|
+
*/
|
585
|
+
function selectTableRow(options) {
|
586
|
+
return (state, dispatch) => {
|
587
|
+
const range = findCellRange(state.selection, options?.anchor, options?.head);
|
588
|
+
if (!range) return false;
|
589
|
+
if (dispatch) {
|
590
|
+
const [$anchorCell, $headCell] = range;
|
591
|
+
const selection = CellSelection.rowSelection($anchorCell, $headCell);
|
592
|
+
dispatch(state.tr.setSelection(selection));
|
593
|
+
}
|
594
|
+
return true;
|
595
|
+
};
|
596
|
+
}
|
597
|
+
/**
|
598
|
+
* @public
|
599
|
+
*/
|
600
|
+
function selectTableCell(options) {
|
601
|
+
return (state, dispatch) => {
|
602
|
+
const $cellPos = findCellPos(state.doc, options?.pos ?? state.selection.anchor);
|
603
|
+
if (!$cellPos) return false;
|
604
|
+
if (dispatch) {
|
605
|
+
const selection = new CellSelection($cellPos);
|
606
|
+
dispatch(state.tr.setSelection(selection));
|
607
|
+
}
|
608
|
+
return true;
|
609
|
+
};
|
610
|
+
}
|
611
|
+
/**
|
612
|
+
* @public
|
613
|
+
*/
|
614
|
+
function selectTable(options) {
|
615
|
+
return (state, dispatch) => {
|
616
|
+
const $pos = options?.pos ? state.doc.resolve(options.pos) : state.selection.$anchor;
|
617
|
+
const table = findTable($pos);
|
618
|
+
if (!table) return false;
|
619
|
+
const map = TableMap.get(table.node);
|
620
|
+
if (map.map.length === 0) return false;
|
621
|
+
if (dispatch) {
|
622
|
+
let tr = state.tr;
|
623
|
+
const firstCellPosInTable = map.map[0];
|
624
|
+
const lastCellPosInTable = map.map[map.map.length - 1];
|
625
|
+
const firstCellPos = table.pos + firstCellPosInTable + 1;
|
626
|
+
const lastCellPos = table.pos + lastCellPosInTable + 1;
|
627
|
+
const $firstCellPos = tr.doc.resolve(firstCellPos);
|
628
|
+
const $lastCellPos = tr.doc.resolve(lastCellPos);
|
629
|
+
const selection = new CellSelection($firstCellPos, $lastCellPos);
|
630
|
+
tr = tr.setSelection(selection);
|
631
|
+
dispatch?.(tr);
|
632
|
+
}
|
633
|
+
return true;
|
634
|
+
};
|
635
|
+
}
|
636
|
+
/**
|
637
|
+
* Adds commands for working with `table` nodes.
|
638
|
+
*
|
639
|
+
* @public
|
640
|
+
*/
|
641
|
+
function defineTableCommands() {
|
642
|
+
return defineCommands({
|
643
|
+
insertTable,
|
644
|
+
exitTable: () => exitTable,
|
645
|
+
selectTable,
|
646
|
+
selectTableCell,
|
647
|
+
selectTableColumn,
|
648
|
+
selectTableRow,
|
649
|
+
addTableColumnBefore: () => addColumnBefore,
|
650
|
+
addTableColumnAfter: () => addColumnAfter,
|
651
|
+
addTableRowAbove: () => addRowBefore,
|
652
|
+
addTableRowBelow: () => addRowAfter,
|
653
|
+
deleteTable: () => deleteTable,
|
654
|
+
deleteTableColumn: () => deleteColumn,
|
655
|
+
deleteTableRow: () => deleteRow,
|
656
|
+
deleteCellSelection: () => deleteCellSelection,
|
657
|
+
mergeTableCells: () => mergeCells,
|
658
|
+
splitTableCell: () => splitCell,
|
659
|
+
moveTableRow,
|
660
|
+
moveTableColumn
|
661
|
+
});
|
662
|
+
}
|
663
|
+
|
664
|
+
//#endregion
|
665
|
+
//#region src/table/table-plugins.ts
|
666
|
+
/**
|
667
|
+
* @public
|
668
|
+
*/
|
669
|
+
function defineTablePlugins() {
|
670
|
+
return definePlugin([tableEditing(), columnResizing()]);
|
671
|
+
}
|
672
|
+
|
673
|
+
//#endregion
|
674
|
+
//#region src/table/table-spec.ts
|
675
|
+
const cellContent = "block+";
|
676
|
+
const cellAttrs = {
|
677
|
+
colspan: { default: 1 },
|
678
|
+
rowspan: { default: 1 },
|
679
|
+
colwidth: { default: null }
|
680
|
+
};
|
681
|
+
const specs = tableNodes({
|
682
|
+
tableGroup: "block",
|
683
|
+
cellContent,
|
684
|
+
cellAttributes: {}
|
685
|
+
});
|
686
|
+
/**
|
687
|
+
* @internal
|
688
|
+
*/
|
689
|
+
function defineTableSpec() {
|
690
|
+
return defineNodeSpec({
|
691
|
+
...specs["table"],
|
692
|
+
content: "tableRow+",
|
693
|
+
name: "table"
|
694
|
+
});
|
695
|
+
}
|
696
|
+
/**
|
697
|
+
* @internal
|
698
|
+
*/
|
699
|
+
function defineTableRowSpec() {
|
700
|
+
return defineNodeSpec({
|
701
|
+
...specs["table_row"],
|
702
|
+
content: "(tableCell | tableHeaderCell)*",
|
703
|
+
name: "tableRow"
|
704
|
+
});
|
705
|
+
}
|
706
|
+
/**
|
707
|
+
* @internal
|
708
|
+
*/
|
709
|
+
function defineTableCellSpec() {
|
710
|
+
return defineNodeSpec({
|
711
|
+
...specs["table_cell"],
|
712
|
+
name: "tableCell",
|
713
|
+
attrs: cellAttrs
|
714
|
+
});
|
715
|
+
}
|
716
|
+
function defineTableHeaderCellSpec() {
|
717
|
+
return defineNodeSpec({
|
718
|
+
...specs["table_header"],
|
719
|
+
name: "tableHeaderCell",
|
720
|
+
attrs: cellAttrs
|
721
|
+
});
|
722
|
+
}
|
723
|
+
|
724
|
+
//#endregion
|
725
|
+
//#region src/table/table.ts
|
726
|
+
/**
|
727
|
+
* @public
|
728
|
+
*/
|
729
|
+
function defineTable() {
|
730
|
+
return union(defineTableSpec(), defineTableRowSpec(), defineTableCellSpec(), defineTableHeaderCellSpec(), defineTablePlugins(), defineTableCommands());
|
731
|
+
}
|
732
|
+
|
733
|
+
//#endregion
|
734
|
+
export { defineTable, defineTableCellSpec, defineTableCommands, defineTableHeaderCellSpec, defineTablePlugins, defineTableRowSpec, defineTableSpec, exitTable, findTable, insertTable, isCellSelection, moveTableColumn, moveTableRow, selectTable, selectTableCell, selectTableColumn, selectTableRow };
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@prosekit/extensions",
|
3
3
|
"type": "module",
|
4
|
-
"version": "0.
|
4
|
+
"version": "0.10.0",
|
5
5
|
"private": false,
|
6
6
|
"description": "A collection of common extensions for ProseKit",
|
7
7
|
"author": {
|
@@ -206,9 +206,9 @@
|
|
206
206
|
"prosemirror-highlight": "^0.13.0",
|
207
207
|
"prosemirror-search": "^1.1.0",
|
208
208
|
"prosemirror-tables": "^1.7.1",
|
209
|
-
"shiki": "^3.
|
210
|
-
"@prosekit/
|
211
|
-
"@prosekit/
|
209
|
+
"shiki": "^3.7.0",
|
210
|
+
"@prosekit/pm": "^0.1.11",
|
211
|
+
"@prosekit/core": "^0.8.3"
|
212
212
|
},
|
213
213
|
"peerDependencies": {
|
214
214
|
"loro-crdt": ">= 0.16.7",
|
@@ -231,20 +231,20 @@
|
|
231
231
|
}
|
232
232
|
},
|
233
233
|
"devDependencies": {
|
234
|
-
"@vitest/browser": "^3.2.
|
234
|
+
"@vitest/browser": "^3.2.4",
|
235
235
|
"just-pick": "^4.2.0",
|
236
|
-
"loro-crdt": "^1.5.
|
236
|
+
"loro-crdt": "^1.5.9",
|
237
237
|
"loro-prosemirror": "^0.2.3",
|
238
|
-
"prettier": "^3.
|
239
|
-
"tsdown": "^0.12.
|
238
|
+
"prettier": "^3.6.0",
|
239
|
+
"tsdown": "^0.12.9",
|
240
240
|
"type-fest": "^4.41.0",
|
241
241
|
"typescript": "~5.8.3",
|
242
|
-
"vitest": "^3.2.
|
243
|
-
"y-prosemirror": "^1.3.
|
242
|
+
"vitest": "^3.2.4",
|
243
|
+
"y-prosemirror": "^1.3.6",
|
244
244
|
"y-protocols": "^1.0.6",
|
245
245
|
"yjs": "^13.6.27",
|
246
|
-
"@prosekit/config-
|
247
|
-
"@prosekit/config-
|
246
|
+
"@prosekit/config-vitest": "0.0.0",
|
247
|
+
"@prosekit/config-tsdown": "0.0.0"
|
248
248
|
},
|
249
249
|
"publishConfig": {
|
250
250
|
"dev": {}
|
package/dist/table-DnVliJ6E.js
DELETED
@@ -1,287 +0,0 @@
|
|
1
|
-
import { defaultBlockAt, defineCommands, defineNodeSpec, definePlugin, findParentNode, getNodeType, insertNode, union } from "@prosekit/core";
|
2
|
-
import { TextSelection } from "@prosekit/pm/state";
|
3
|
-
import { CellSelection, TableMap, addColumnAfter, addColumnBefore, addRowAfter, addRowBefore, cellAround, cellNear, columnResizing, deleteCellSelection, deleteColumn, deleteRow, deleteTable, inSameTable, mergeCells, splitCell, tableEditing, tableNodes } from "prosemirror-tables";
|
4
|
-
|
5
|
-
//#region src/table/table-utils.ts
|
6
|
-
/**
|
7
|
-
* Checks if the given object is a `CellSelection` instance.
|
8
|
-
*
|
9
|
-
* @public
|
10
|
-
*/
|
11
|
-
function isCellSelection(value) {
|
12
|
-
return value instanceof CellSelection;
|
13
|
-
}
|
14
|
-
/**
|
15
|
-
* Find the closest table node.
|
16
|
-
*
|
17
|
-
* @internal
|
18
|
-
*/
|
19
|
-
function findTable($pos) {
|
20
|
-
return findParentNode((node) => node.type.spec.tableRole === "table", $pos);
|
21
|
-
}
|
22
|
-
/**
|
23
|
-
* Try to find the anchor and head cell in the same table by using the given
|
24
|
-
* anchor and head as hit points, or fallback to the selection's anchor and
|
25
|
-
* head.
|
26
|
-
*
|
27
|
-
* @internal
|
28
|
-
*/
|
29
|
-
function findCellRange(selection, anchorHit, headHit) {
|
30
|
-
if (anchorHit == null && headHit == null && isCellSelection(selection)) return [selection.$anchorCell, selection.$headCell];
|
31
|
-
const anchor = anchorHit ?? headHit ?? selection.anchor;
|
32
|
-
const head = headHit ?? anchorHit ?? selection.head;
|
33
|
-
const doc = selection.$head.doc;
|
34
|
-
const $anchorCell = findCellPos(doc, anchor);
|
35
|
-
const $headCell = findCellPos(doc, head);
|
36
|
-
if ($anchorCell && $headCell && inSameTable($anchorCell, $headCell)) return [$anchorCell, $headCell];
|
37
|
-
}
|
38
|
-
/**
|
39
|
-
* Try to find a resolved pos of a cell by using the given pos as a hit point.
|
40
|
-
*
|
41
|
-
* @internal
|
42
|
-
*/
|
43
|
-
function findCellPos(doc, pos) {
|
44
|
-
const $pos = doc.resolve(pos);
|
45
|
-
return cellAround($pos) || cellNear($pos);
|
46
|
-
}
|
47
|
-
|
48
|
-
//#endregion
|
49
|
-
//#region src/table/table-commands.ts
|
50
|
-
function createEmptyTable(schema, row, col, header) {
|
51
|
-
const tableType = getNodeType(schema, "table");
|
52
|
-
const tableRowType = getNodeType(schema, "tableRow");
|
53
|
-
const tableCellType = getNodeType(schema, "tableCell");
|
54
|
-
const tableHeaderCellType = getNodeType(schema, "tableHeaderCell");
|
55
|
-
if (header) {
|
56
|
-
const headerCell = tableHeaderCellType.createAndFill();
|
57
|
-
const headerCells = repeat(headerCell, col);
|
58
|
-
const headerRow = tableRowType.createAndFill(null, headerCells);
|
59
|
-
const bodyCell = tableCellType.createAndFill();
|
60
|
-
const bodyCells = repeat(bodyCell, col);
|
61
|
-
const bodyRow = tableRowType.createAndFill(null, bodyCells);
|
62
|
-
const bodyRows = repeat(bodyRow, row - 1);
|
63
|
-
return tableType.createAndFill(null, [headerRow, ...bodyRows]);
|
64
|
-
} else {
|
65
|
-
const bodyCell = tableCellType.createAndFill();
|
66
|
-
const bodyCells = repeat(bodyCell, col);
|
67
|
-
const bodyRow = tableRowType.createAndFill(null, bodyCells);
|
68
|
-
const bodyRows = repeat(bodyRow, row);
|
69
|
-
return tableType.createAndFill(null, bodyRows);
|
70
|
-
}
|
71
|
-
}
|
72
|
-
function repeat(node, length) {
|
73
|
-
return Array(length).fill(node);
|
74
|
-
}
|
75
|
-
/**
|
76
|
-
* Insert a table node with the given number of rows and columns, and optionally
|
77
|
-
* a header row.
|
78
|
-
*
|
79
|
-
* @param options
|
80
|
-
*
|
81
|
-
* @public
|
82
|
-
*/
|
83
|
-
function insertTable(options) {
|
84
|
-
return (state, dispatch, view) => {
|
85
|
-
const { row, col, header = false } = options;
|
86
|
-
const table = createEmptyTable(state.schema, row, col, header);
|
87
|
-
return insertNode({ node: table })(state, dispatch, view);
|
88
|
-
};
|
89
|
-
}
|
90
|
-
/**
|
91
|
-
* When the selection is in a table node, create a default block after the table
|
92
|
-
* table, and move the cursor there.
|
93
|
-
*
|
94
|
-
* @public
|
95
|
-
*/
|
96
|
-
const exitTable = (state, dispatch) => {
|
97
|
-
const { $head, $anchor } = state.selection;
|
98
|
-
if (!$head.sameParent($anchor)) return false;
|
99
|
-
let tableStart = -1;
|
100
|
-
let tableDepth = -1;
|
101
|
-
for (let depth = $head.depth; depth >= 0; depth--) {
|
102
|
-
const node$1 = $head.node(depth);
|
103
|
-
if (node$1.type.spec.tableRole === "table") {
|
104
|
-
tableStart = $head.before(depth);
|
105
|
-
tableDepth = depth;
|
106
|
-
}
|
107
|
-
}
|
108
|
-
if (tableStart < 0 || tableDepth <= 0) return false;
|
109
|
-
const above = $head.node(tableDepth - 1);
|
110
|
-
const after = $head.indexAfter(tableDepth - 1);
|
111
|
-
const type = defaultBlockAt(above.contentMatchAt(after));
|
112
|
-
const node = type?.createAndFill();
|
113
|
-
if (!type || !node || !above.canReplaceWith(after, after, type)) return false;
|
114
|
-
if (dispatch) {
|
115
|
-
const pos = $head.after(tableDepth);
|
116
|
-
const tr = state.tr.replaceWith(pos, pos, node);
|
117
|
-
tr.setSelection(TextSelection.near(tr.doc.resolve(pos), 1));
|
118
|
-
dispatch(tr.scrollIntoView());
|
119
|
-
}
|
120
|
-
return true;
|
121
|
-
};
|
122
|
-
/**
|
123
|
-
* @public
|
124
|
-
*/
|
125
|
-
function selectTableColumn(options) {
|
126
|
-
return (state, dispatch) => {
|
127
|
-
const range = findCellRange(state.selection, options?.anchor, options?.head);
|
128
|
-
if (!range) return false;
|
129
|
-
if (dispatch) {
|
130
|
-
const [$anchorCell, $headCell] = range;
|
131
|
-
const selection = CellSelection.colSelection($anchorCell, $headCell);
|
132
|
-
dispatch(state.tr.setSelection(selection));
|
133
|
-
}
|
134
|
-
return true;
|
135
|
-
};
|
136
|
-
}
|
137
|
-
/**
|
138
|
-
* @public
|
139
|
-
*/
|
140
|
-
function selectTableRow(options) {
|
141
|
-
return (state, dispatch) => {
|
142
|
-
const range = findCellRange(state.selection, options?.anchor, options?.head);
|
143
|
-
if (!range) return false;
|
144
|
-
if (dispatch) {
|
145
|
-
const [$anchorCell, $headCell] = range;
|
146
|
-
const selection = CellSelection.rowSelection($anchorCell, $headCell);
|
147
|
-
dispatch(state.tr.setSelection(selection));
|
148
|
-
}
|
149
|
-
return true;
|
150
|
-
};
|
151
|
-
}
|
152
|
-
/**
|
153
|
-
* @public
|
154
|
-
*/
|
155
|
-
function selectTableCell(options) {
|
156
|
-
return (state, dispatch) => {
|
157
|
-
const $cellPos = findCellPos(state.doc, options?.pos ?? state.selection.anchor);
|
158
|
-
if (!$cellPos) return false;
|
159
|
-
if (dispatch) {
|
160
|
-
const selection = new CellSelection($cellPos);
|
161
|
-
dispatch(state.tr.setSelection(selection));
|
162
|
-
}
|
163
|
-
return true;
|
164
|
-
};
|
165
|
-
}
|
166
|
-
/**
|
167
|
-
* @public
|
168
|
-
*/
|
169
|
-
function selectTable(options) {
|
170
|
-
return (state, dispatch) => {
|
171
|
-
const $pos = options?.pos ? state.doc.resolve(options.pos) : state.selection.$anchor;
|
172
|
-
const table = findTable($pos);
|
173
|
-
if (!table) return false;
|
174
|
-
const map = TableMap.get(table.node);
|
175
|
-
if (map.map.length === 0) return false;
|
176
|
-
if (dispatch) {
|
177
|
-
let tr = state.tr;
|
178
|
-
const firstCellPosInTable = map.map[0];
|
179
|
-
const lastCellPosInTable = map.map[map.map.length - 1];
|
180
|
-
const firstCellPos = table.pos + firstCellPosInTable + 1;
|
181
|
-
const lastCellPos = table.pos + lastCellPosInTable + 1;
|
182
|
-
const $firstCellPos = tr.doc.resolve(firstCellPos);
|
183
|
-
const $lastCellPos = tr.doc.resolve(lastCellPos);
|
184
|
-
const selection = new CellSelection($firstCellPos, $lastCellPos);
|
185
|
-
tr = tr.setSelection(selection);
|
186
|
-
dispatch?.(tr);
|
187
|
-
}
|
188
|
-
return true;
|
189
|
-
};
|
190
|
-
}
|
191
|
-
/**
|
192
|
-
* Adds commands for working with `table` nodes.
|
193
|
-
*
|
194
|
-
* @public
|
195
|
-
*/
|
196
|
-
function defineTableCommands() {
|
197
|
-
return defineCommands({
|
198
|
-
insertTable,
|
199
|
-
exitTable: () => exitTable,
|
200
|
-
selectTable,
|
201
|
-
selectTableCell,
|
202
|
-
selectTableColumn,
|
203
|
-
selectTableRow,
|
204
|
-
addTableColumnBefore: () => addColumnBefore,
|
205
|
-
addTableColumnAfter: () => addColumnAfter,
|
206
|
-
addTableRowAbove: () => addRowBefore,
|
207
|
-
addTableRowBelow: () => addRowAfter,
|
208
|
-
deleteTable: () => deleteTable,
|
209
|
-
deleteTableColumn: () => deleteColumn,
|
210
|
-
deleteTableRow: () => deleteRow,
|
211
|
-
deleteCellSelection: () => deleteCellSelection,
|
212
|
-
mergeTableCells: () => mergeCells,
|
213
|
-
splitTableCell: () => splitCell
|
214
|
-
});
|
215
|
-
}
|
216
|
-
|
217
|
-
//#endregion
|
218
|
-
//#region src/table/table-plugins.ts
|
219
|
-
/**
|
220
|
-
* @public
|
221
|
-
*/
|
222
|
-
function defineTablePlugins() {
|
223
|
-
return definePlugin([tableEditing(), columnResizing()]);
|
224
|
-
}
|
225
|
-
|
226
|
-
//#endregion
|
227
|
-
//#region src/table/table-spec.ts
|
228
|
-
const cellContent = "block+";
|
229
|
-
const cellAttrs = {
|
230
|
-
colspan: { default: 1 },
|
231
|
-
rowspan: { default: 1 },
|
232
|
-
colwidth: { default: null }
|
233
|
-
};
|
234
|
-
const specs = tableNodes({
|
235
|
-
tableGroup: "block",
|
236
|
-
cellContent,
|
237
|
-
cellAttributes: {}
|
238
|
-
});
|
239
|
-
/**
|
240
|
-
* @internal
|
241
|
-
*/
|
242
|
-
function defineTableSpec() {
|
243
|
-
return defineNodeSpec({
|
244
|
-
...specs["table"],
|
245
|
-
content: "tableRow+",
|
246
|
-
name: "table"
|
247
|
-
});
|
248
|
-
}
|
249
|
-
/**
|
250
|
-
* @internal
|
251
|
-
*/
|
252
|
-
function defineTableRowSpec() {
|
253
|
-
return defineNodeSpec({
|
254
|
-
...specs["table_row"],
|
255
|
-
content: "(tableCell | tableHeaderCell)*",
|
256
|
-
name: "tableRow"
|
257
|
-
});
|
258
|
-
}
|
259
|
-
/**
|
260
|
-
* @internal
|
261
|
-
*/
|
262
|
-
function defineTableCellSpec() {
|
263
|
-
return defineNodeSpec({
|
264
|
-
...specs["table_cell"],
|
265
|
-
name: "tableCell",
|
266
|
-
attrs: cellAttrs
|
267
|
-
});
|
268
|
-
}
|
269
|
-
function defineTableHeaderCellSpec() {
|
270
|
-
return defineNodeSpec({
|
271
|
-
...specs["table_header"],
|
272
|
-
name: "tableHeaderCell",
|
273
|
-
attrs: cellAttrs
|
274
|
-
});
|
275
|
-
}
|
276
|
-
|
277
|
-
//#endregion
|
278
|
-
//#region src/table/table.ts
|
279
|
-
/**
|
280
|
-
* @public
|
281
|
-
*/
|
282
|
-
function defineTable() {
|
283
|
-
return union(defineTableSpec(), defineTableRowSpec(), defineTableCellSpec(), defineTableHeaderCellSpec(), defineTablePlugins(), defineTableCommands());
|
284
|
-
}
|
285
|
-
|
286
|
-
//#endregion
|
287
|
-
export { defineTable, defineTableCellSpec, defineTableCommands, defineTableHeaderCellSpec, defineTablePlugins, defineTableRowSpec, defineTableSpec, exitTable, findTable, insertTable, isCellSelection, selectTable, selectTableCell, selectTableColumn, selectTableRow };
|