@milkdown/preset-gfm 6.1.5 → 6.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/auto-link.d.ts.map +1 -1
- package/lib/footnote/definition.d.ts +1 -5
- package/lib/footnote/definition.d.ts.map +1 -1
- package/lib/footnote/reference.d.ts +1 -5
- package/lib/footnote/reference.d.ts.map +1 -1
- package/lib/index.d.ts +2 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/index.es.js +1972 -903
- package/lib/index.es.js.map +1 -1
- package/lib/strike-through.d.ts +1 -5
- package/lib/strike-through.d.ts.map +1 -1
- package/lib/supported-keys.d.ts +1 -0
- package/lib/supported-keys.d.ts.map +1 -1
- package/lib/table/command.d.ts +1 -1
- package/lib/table/command.d.ts.map +1 -1
- package/lib/table/nodes/index.d.ts +1 -24
- package/lib/table/nodes/index.d.ts.map +1 -1
- package/lib/table/operator-plugin/actions.d.ts +1 -1
- package/lib/table/operator-plugin/actions.d.ts.map +1 -1
- package/lib/table/operator-plugin/calc-pos.d.ts.map +1 -1
- package/lib/table/operator-plugin/helper.d.ts +1 -1
- package/lib/table/operator-plugin/helper.d.ts.map +1 -1
- package/lib/table/operator-plugin/index.d.ts +1 -1
- package/lib/table/operator-plugin/index.d.ts.map +1 -1
- package/lib/table/operator-plugin/widget.d.ts +4 -4
- package/lib/table/operator-plugin/widget.d.ts.map +1 -1
- package/lib/table/plugin/auto-insert-zero-space.d.ts +3 -0
- package/lib/table/plugin/auto-insert-zero-space.d.ts.map +1 -0
- package/lib/table/plugin/cell-selection.d.ts +38 -0
- package/lib/table/plugin/cell-selection.d.ts.map +1 -0
- package/lib/table/plugin/column-resizing.d.ts +17 -0
- package/lib/table/plugin/column-resizing.d.ts.map +1 -0
- package/lib/table/plugin/commands.d.ts +30 -0
- package/lib/table/plugin/commands.d.ts.map +1 -0
- package/lib/table/plugin/copy-paste.d.ts +13 -0
- package/lib/table/plugin/copy-paste.d.ts.map +1 -0
- package/lib/table/plugin/fix-tables.d.ts +6 -0
- package/lib/table/plugin/fix-tables.d.ts.map +1 -0
- package/lib/table/plugin/index.d.ts +4 -0
- package/lib/table/plugin/index.d.ts.map +1 -0
- package/lib/table/plugin/schema.d.ts +4 -0
- package/lib/table/plugin/schema.d.ts.map +1 -0
- package/lib/table/plugin/table-editing.d.ts +9 -0
- package/lib/table/plugin/table-editing.d.ts.map +1 -0
- package/lib/table/plugin/table-map.d.ts +44 -0
- package/lib/table/plugin/table-map.d.ts.map +1 -0
- package/lib/table/plugin/table-view.d.ts +15 -0
- package/lib/table/plugin/table-view.d.ts.map +1 -0
- package/lib/table/plugin/types.d.ts +15 -0
- package/lib/table/plugin/types.d.ts.map +1 -0
- package/lib/table/plugin/util.d.ts +16 -0
- package/lib/table/plugin/util.d.ts.map +1 -0
- package/lib/table/utils.d.ts +6 -6
- package/lib/table/utils.d.ts.map +1 -1
- package/lib/task-list-item.d.ts +1 -5
- package/lib/task-list-item.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/auto-link.ts +4 -3
- package/src/footnote/definition.ts +3 -1
- package/src/footnote/reference.ts +2 -1
- package/src/index.ts +1 -1
- package/src/table/command.ts +3 -3
- package/src/table/nodes/index.ts +7 -31
- package/src/table/operator-plugin/actions.ts +4 -4
- package/src/table/operator-plugin/calc-pos.ts +4 -2
- package/src/table/operator-plugin/helper.ts +2 -1
- package/src/table/operator-plugin/index.ts +1 -1
- package/src/table/operator-plugin/style.ts +12 -12
- package/src/table/operator-plugin/widget.ts +4 -14
- package/src/table/plugin/auto-insert-zero-space.ts +51 -0
- package/src/table/plugin/cell-selection.ts +352 -0
- package/src/table/plugin/column-resizing.ts +260 -0
- package/src/table/plugin/commands.ts +551 -0
- package/src/table/plugin/copy-paste.ts +306 -0
- package/src/table/plugin/fix-tables.ts +117 -0
- package/src/table/plugin/index.ts +4 -0
- package/src/table/plugin/schema.ts +114 -0
- package/src/table/plugin/table-editing.ts +275 -0
- package/src/table/plugin/table-map.ts +280 -0
- package/src/table/plugin/table-view.ts +76 -0
- package/src/table/plugin/types.ts +16 -0
- package/src/table/plugin/util.ts +107 -0
- package/src/table/utils.ts +31 -22
- package/src/task-list-item.ts +4 -2
- package/lib/table/nodes/schema.d.ts +0 -2
- package/lib/table/nodes/schema.d.ts.map +0 -1
- package/src/table/nodes/schema.ts +0 -16
package/src/index.ts
CHANGED
package/src/table/command.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
-
import { Command } from '@milkdown/prose/commands';
|
|
3
2
|
import { Node, NodeType } from '@milkdown/prose/model';
|
|
4
|
-
import { Selection } from '@milkdown/prose/state';
|
|
5
|
-
|
|
3
|
+
import { Command, Selection } from '@milkdown/prose/state';
|
|
4
|
+
|
|
5
|
+
import { isInTable } from './plugin/util';
|
|
6
6
|
|
|
7
7
|
export const exitTable =
|
|
8
8
|
(node: NodeType): Command =>
|
package/src/table/nodes/index.ts
CHANGED
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
import { createCmd, createCmdKey, MarkdownNode, schemaCtx } from '@milkdown/core';
|
|
3
3
|
import { InputRule } from '@milkdown/prose/inputrules';
|
|
4
4
|
import { NodeType } from '@milkdown/prose/model';
|
|
5
|
-
import {
|
|
6
|
-
import { columnResizing, goToNextCell, tableEditing } from '@milkdown/prose/tables';
|
|
5
|
+
import { Selection, TextSelection } from '@milkdown/prose/state';
|
|
7
6
|
import { createPlugin, createShortcut } from '@milkdown/utils';
|
|
8
7
|
|
|
9
8
|
import { exitTable } from '../command';
|
|
10
9
|
import { operatorPlugin } from '../operator-plugin';
|
|
10
|
+
import { autoInsertZeroSpace } from '../plugin/auto-insert-zero-space';
|
|
11
|
+
import { columnResizing } from '../plugin/column-resizing';
|
|
12
|
+
import { goToNextCell } from '../plugin/commands';
|
|
13
|
+
import { schema } from '../plugin/schema';
|
|
14
|
+
import { tableEditing } from '../plugin/table-editing';
|
|
11
15
|
import { createTable } from '../utils';
|
|
12
|
-
import { schema } from './schema';
|
|
13
16
|
|
|
14
17
|
export const SupportedKeys = {
|
|
15
18
|
NextCell: 'NextCell',
|
|
@@ -25,8 +28,6 @@ export const NextCell = createCmdKey('NextCell');
|
|
|
25
28
|
export const BreakTable = createCmdKey('BreakTable');
|
|
26
29
|
export const InsertTable = createCmdKey('InsertTable');
|
|
27
30
|
|
|
28
|
-
export const TableContentFilterPluginKey = new PluginKey('MILKDOWN_TABLE_CONTENT_FILTER');
|
|
29
|
-
|
|
30
31
|
export const table = createPlugin<Keys, Record<string, unknown>, keyof typeof schema>((utils) => {
|
|
31
32
|
return {
|
|
32
33
|
schema: () => ({
|
|
@@ -166,32 +167,7 @@ export const table = createPlugin<Keys, Record<string, unknown>, keyof typeof sc
|
|
|
166
167
|
[SupportedKeys.ExitTable]: createShortcut(BreakTable, 'Mod-Enter'),
|
|
167
168
|
},
|
|
168
169
|
prosePlugins: (_, ctx) => {
|
|
169
|
-
return [
|
|
170
|
-
operatorPlugin(ctx, utils),
|
|
171
|
-
columnResizing({}),
|
|
172
|
-
tableEditing(),
|
|
173
|
-
new Plugin({
|
|
174
|
-
key: TableContentFilterPluginKey,
|
|
175
|
-
filterTransaction: (tr, state) => {
|
|
176
|
-
const isInsertHr = tr.getMeta('hardbreak');
|
|
177
|
-
const [step] = tr.steps;
|
|
178
|
-
if (isInsertHr && step) {
|
|
179
|
-
const { from } = step as unknown as { from: number };
|
|
180
|
-
const $from = state.doc.resolve(from);
|
|
181
|
-
let curDepth = $from.depth;
|
|
182
|
-
let canApply = true;
|
|
183
|
-
while (curDepth > 0) {
|
|
184
|
-
if ($from.node(curDepth).type.name === 'table') {
|
|
185
|
-
canApply = false;
|
|
186
|
-
}
|
|
187
|
-
curDepth--;
|
|
188
|
-
}
|
|
189
|
-
return canApply;
|
|
190
|
-
}
|
|
191
|
-
return true;
|
|
192
|
-
},
|
|
193
|
-
}),
|
|
194
|
-
];
|
|
170
|
+
return [operatorPlugin(ctx, utils), autoInsertZeroSpace(), columnResizing(), tableEditing()];
|
|
195
171
|
},
|
|
196
172
|
};
|
|
197
173
|
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
2
|
import { Ctx, ThemeIcon, themeManagerCtx } from '@milkdown/core';
|
|
3
|
-
import { Command } from '@milkdown/prose/
|
|
3
|
+
import { Command } from '@milkdown/prose/state';
|
|
4
|
+
import { EditorView } from '@milkdown/prose/view';
|
|
5
|
+
|
|
4
6
|
import {
|
|
5
7
|
addColumnAfter,
|
|
6
8
|
addColumnBefore,
|
|
@@ -10,9 +12,7 @@ import {
|
|
|
10
12
|
isInTable,
|
|
11
13
|
selectedRect,
|
|
12
14
|
setCellAttr,
|
|
13
|
-
} from '
|
|
14
|
-
import { EditorView } from '@milkdown/prose/view';
|
|
15
|
-
|
|
15
|
+
} from '../plugin';
|
|
16
16
|
import { addRowWithAlignment } from '../utils';
|
|
17
17
|
import { getCellSelection, isFirstRowSelected } from './helper';
|
|
18
18
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
2
|
|
|
3
|
+
import { missingRootElement } from '@milkdown/exception';
|
|
3
4
|
import { calculateNodePosition } from '@milkdown/prose';
|
|
4
|
-
import { CellSelection } from '@milkdown/prose/tables';
|
|
5
5
|
import { EditorView } from '@milkdown/prose/view';
|
|
6
6
|
|
|
7
|
+
import { CellSelection } from '../plugin';
|
|
8
|
+
|
|
7
9
|
export const calculatePosition = (view: EditorView, dom: HTMLElement) => {
|
|
8
10
|
const { selection } = view.state as unknown as { selection: CellSelection };
|
|
9
11
|
const isCol = selection.isColSelection();
|
|
@@ -12,7 +14,7 @@ export const calculatePosition = (view: EditorView, dom: HTMLElement) => {
|
|
|
12
14
|
calculateNodePosition(view, dom, (selected, target, parent) => {
|
|
13
15
|
const $editor = dom.parentElement;
|
|
14
16
|
if (!$editor) {
|
|
15
|
-
throw
|
|
17
|
+
throw missingRootElement();
|
|
16
18
|
}
|
|
17
19
|
let left = !isRow
|
|
18
20
|
? selected.left - parent.left + (selected.width - target.width) / 2
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
-
import { CellSelection, TableMap } from '@milkdown/prose/tables';
|
|
3
2
|
import { EditorView } from '@milkdown/prose/view';
|
|
4
3
|
|
|
4
|
+
import { CellSelection } from '../plugin';
|
|
5
|
+
import { TableMap } from '../plugin/table-map';
|
|
5
6
|
import { Item } from './actions';
|
|
6
7
|
|
|
7
8
|
export const getCellSelection = (view: EditorView): CellSelection => view.state.selection as unknown as CellSelection;
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { Ctx } from '@milkdown/core';
|
|
4
4
|
import { Plugin, PluginKey } from '@milkdown/prose/state';
|
|
5
|
-
import { CellSelection } from '@milkdown/prose/tables';
|
|
6
5
|
import { Decoration, DecorationSet } from '@milkdown/prose/view';
|
|
7
6
|
import { Utils } from '@milkdown/utils';
|
|
8
7
|
|
|
8
|
+
import { CellSelection } from '../plugin';
|
|
9
9
|
import { CellPos, getCellsInColumn, getCellsInRow } from '../utils';
|
|
10
10
|
import { createActions } from './actions';
|
|
11
11
|
import { calculatePosition } from './calc-pos';
|
|
@@ -10,8 +10,8 @@ export const injectStyle = (themeManager: ThemeManager, { css, injectGlobal }: E
|
|
|
10
10
|
.milkdown {
|
|
11
11
|
.tableWrapper {
|
|
12
12
|
table {
|
|
13
|
-
width: calc(100% -
|
|
14
|
-
margin:
|
|
13
|
+
width: calc(100% - 32px) !important;
|
|
14
|
+
margin: 16px 0 16px 16px !important;
|
|
15
15
|
|
|
16
16
|
.milkdown-cell-left,
|
|
17
17
|
.milkdown-cell-point,
|
|
@@ -37,24 +37,24 @@ export const injectStyle = (themeManager: ThemeManager, { css, injectGlobal }: E
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
.milkdown-cell-left {
|
|
40
|
-
left:
|
|
40
|
+
left: -14px;
|
|
41
41
|
top: 0;
|
|
42
42
|
bottom: 0;
|
|
43
|
-
width:
|
|
43
|
+
width: 8px;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
.milkdown-cell-top {
|
|
47
47
|
left: 0;
|
|
48
48
|
right: 0;
|
|
49
|
-
top:
|
|
50
|
-
height:
|
|
49
|
+
top: -14px;
|
|
50
|
+
height: 8px;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
.milkdown-cell-point {
|
|
54
|
-
left:
|
|
55
|
-
top:
|
|
56
|
-
width:
|
|
57
|
-
height:
|
|
54
|
+
left: -18px;
|
|
55
|
+
top: -18px;
|
|
56
|
+
width: 16px;
|
|
57
|
+
height: 16px;
|
|
58
58
|
|
|
59
59
|
.icon {
|
|
60
60
|
position: absolute;
|
|
@@ -90,8 +90,8 @@ export const injectStyle = (themeManager: ThemeManager, { css, injectGlobal }: E
|
|
|
90
90
|
position: relative;
|
|
91
91
|
color: ${palette('solid', 0.87)};
|
|
92
92
|
|
|
93
|
-
width:
|
|
94
|
-
line-height:
|
|
93
|
+
width: 48px;
|
|
94
|
+
line-height: 48px;
|
|
95
95
|
text-align: center;
|
|
96
96
|
transition: all 0.4s ease-in-out;
|
|
97
97
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
2
|
|
|
3
3
|
import { Ctx, ThemeIcon, themeManagerCtx } from '@milkdown/core';
|
|
4
|
-
import { Decoration
|
|
4
|
+
import { Decoration } from '@milkdown/prose/view';
|
|
5
5
|
|
|
6
6
|
import { CellPos, selectLine, selectTable } from '../utils';
|
|
7
7
|
import { ToolTipPos } from './constant';
|
|
@@ -21,19 +21,9 @@ const calculateClassName = (pos: ToolTipPos) => {
|
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
export function createWidget(ctx: Ctx, cell: CellPos, pos: ToolTipPos.Point): Decoration
|
|
25
|
-
export function createWidget(
|
|
26
|
-
|
|
27
|
-
cell: CellPos,
|
|
28
|
-
pos: ToolTipPos.Left,
|
|
29
|
-
index: number,
|
|
30
|
-
): Decoration<WidgetDecorationSpec>;
|
|
31
|
-
export function createWidget(
|
|
32
|
-
ctx: Ctx,
|
|
33
|
-
cell: CellPos,
|
|
34
|
-
pos: ToolTipPos.Top,
|
|
35
|
-
index: number,
|
|
36
|
-
): Decoration<WidgetDecorationSpec>;
|
|
24
|
+
export function createWidget(ctx: Ctx, cell: CellPos, pos: ToolTipPos.Point): Decoration;
|
|
25
|
+
export function createWidget(ctx: Ctx, cell: CellPos, pos: ToolTipPos.Left, index: number): Decoration;
|
|
26
|
+
export function createWidget(ctx: Ctx, cell: CellPos, pos: ToolTipPos.Top, index: number): Decoration;
|
|
37
27
|
export function createWidget(ctx: Ctx, cell: CellPos, pos: ToolTipPos, index = 0) {
|
|
38
28
|
return Decoration.widget(cell.pos + 1, (view) => {
|
|
39
29
|
const div = document.createElement('div');
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
+
import { browser } from '@milkdown/prose';
|
|
3
|
+
import { Node } from '@milkdown/prose/model';
|
|
4
|
+
import { Plugin, PluginKey } from '@milkdown/prose/state';
|
|
5
|
+
|
|
6
|
+
import { isInTable } from './util';
|
|
7
|
+
|
|
8
|
+
const isEmptyParagraph = (node: Node) => {
|
|
9
|
+
return node.type.name === 'paragraph' && node.nodeSize === 2;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const isParagraph = (node: Node) => {
|
|
13
|
+
return node.type.name === 'paragraph';
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const pluginKey = new PluginKey('plugin_autoInsertZeroSpace');
|
|
17
|
+
|
|
18
|
+
export const autoInsertZeroSpace = () => {
|
|
19
|
+
return new Plugin({
|
|
20
|
+
key: pluginKey,
|
|
21
|
+
props: {
|
|
22
|
+
handleDOMEvents: {
|
|
23
|
+
compositionstart(view) {
|
|
24
|
+
const { state, dispatch } = view;
|
|
25
|
+
const { tr, selection } = state;
|
|
26
|
+
const { $from } = selection;
|
|
27
|
+
if (browser.safari && isInTable(state) && selection.empty && isEmptyParagraph($from.parent)) {
|
|
28
|
+
dispatch(tr.insertText('\u2060', $from.start()));
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
},
|
|
32
|
+
compositionend(view) {
|
|
33
|
+
const { state, dispatch } = view;
|
|
34
|
+
const { tr, selection } = state;
|
|
35
|
+
const { $from } = selection;
|
|
36
|
+
|
|
37
|
+
if (
|
|
38
|
+
browser.safari &&
|
|
39
|
+
isInTable(state) &&
|
|
40
|
+
selection.empty &&
|
|
41
|
+
isParagraph($from.parent) &&
|
|
42
|
+
$from.parent.textContent.startsWith('\u2060')
|
|
43
|
+
) {
|
|
44
|
+
dispatch(tr.delete($from.start(), $from.start() + 1));
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
};
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
+
|
|
3
|
+
import { Fragment, Node, ResolvedPos, Slice } from '@milkdown/prose/model';
|
|
4
|
+
import {
|
|
5
|
+
EditorState,
|
|
6
|
+
NodeSelection,
|
|
7
|
+
Selection,
|
|
8
|
+
SelectionRange,
|
|
9
|
+
TextSelection,
|
|
10
|
+
Transaction,
|
|
11
|
+
} from '@milkdown/prose/state';
|
|
12
|
+
import { Mappable } from '@milkdown/prose/transform';
|
|
13
|
+
import { Decoration, DecorationSet } from '@milkdown/prose/view';
|
|
14
|
+
|
|
15
|
+
import { TableMap } from './table-map';
|
|
16
|
+
import { inSameTable, pointsAtCell, removeColSpan, setAttr } from './util';
|
|
17
|
+
|
|
18
|
+
// ::- A [`Selection`](http://prosemirror.net/docs/ref/#state.Selection)
|
|
19
|
+
// subclass that represents a cell selection spanning part of a table.
|
|
20
|
+
// With the plugin enabled, these will be created when the user
|
|
21
|
+
// selects across cells, and will be drawn by giving selected cells a
|
|
22
|
+
// `selectedCell` CSS class.
|
|
23
|
+
export class CellSelection extends Selection {
|
|
24
|
+
// :: (ResolvedPos, ?ResolvedPos)
|
|
25
|
+
// A table selection is identified by its anchor and head cells. The
|
|
26
|
+
// positions given to this constructor should point _before_ two
|
|
27
|
+
// cells in the same table. They may be the same, to select a single
|
|
28
|
+
// cell.
|
|
29
|
+
constructor(public $anchorCell: ResolvedPos, public $headCell = $anchorCell) {
|
|
30
|
+
const table = $anchorCell.node(-1),
|
|
31
|
+
map = TableMap.get(table),
|
|
32
|
+
start = $anchorCell.start(-1);
|
|
33
|
+
const rect = map.rectBetween($anchorCell.pos - start, $headCell.pos - start);
|
|
34
|
+
const doc = $anchorCell.node(0);
|
|
35
|
+
const cells = map.cellsInRect(rect).filter((p) => p != $headCell.pos - start);
|
|
36
|
+
// Make the head cell the first range, so that it counts as the
|
|
37
|
+
// primary part of the selection
|
|
38
|
+
cells.unshift($headCell.pos - start);
|
|
39
|
+
const ranges = cells.map((pos) => {
|
|
40
|
+
const cell = table.nodeAt(pos) as Node,
|
|
41
|
+
from = pos + start + 1;
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
return new (SelectionRange as any)(doc.resolve(from), doc.resolve(from + cell.content.size));
|
|
44
|
+
});
|
|
45
|
+
super(ranges[0].$from, ranges[0].$to, ranges);
|
|
46
|
+
// :: ResolvedPos
|
|
47
|
+
// A resolved position pointing _in front of_ the anchor cell (the one
|
|
48
|
+
// that doesn't move when extending the selection).
|
|
49
|
+
this.$anchorCell = $anchorCell;
|
|
50
|
+
// :: ResolvedPos
|
|
51
|
+
// A resolved position pointing in front of the head cell (the one
|
|
52
|
+
// moves when extending the selection).
|
|
53
|
+
this.$headCell = $headCell;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
map(doc: Node, mapping: Mappable): Selection {
|
|
57
|
+
const $anchorCell = doc.resolve(mapping.map(this.$anchorCell.pos));
|
|
58
|
+
const $headCell = doc.resolve(mapping.map(this.$headCell.pos));
|
|
59
|
+
if (pointsAtCell($anchorCell) && pointsAtCell($headCell) && inSameTable($anchorCell, $headCell)) {
|
|
60
|
+
const tableChanged = this.$anchorCell.node(-1) != $anchorCell.node(-1);
|
|
61
|
+
if (tableChanged && this.isRowSelection()) return CellSelection.rowSelection($anchorCell, $headCell);
|
|
62
|
+
else if (tableChanged && this.isColSelection()) return CellSelection.colSelection($anchorCell, $headCell);
|
|
63
|
+
else return new CellSelection($anchorCell, $headCell);
|
|
64
|
+
}
|
|
65
|
+
return TextSelection.between($anchorCell, $headCell);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// :: () → Slice
|
|
69
|
+
// Returns a rectangular slice of table rows containing the selected
|
|
70
|
+
// cells.
|
|
71
|
+
override content(): Slice {
|
|
72
|
+
const table = this.$anchorCell.node(-1),
|
|
73
|
+
map = TableMap.get(table),
|
|
74
|
+
start = this.$anchorCell.start(-1);
|
|
75
|
+
const rect = map.rectBetween(this.$anchorCell.pos - start, this.$headCell.pos - start);
|
|
76
|
+
const seen: Record<number, boolean> = {},
|
|
77
|
+
rows = [];
|
|
78
|
+
for (let row = rect.top; row < rect.bottom; row++) {
|
|
79
|
+
const rowContent = [];
|
|
80
|
+
for (let index = row * map.width + rect.left, col = rect.left; col < rect.right; col++, index++) {
|
|
81
|
+
const pos = map.map[index] as number;
|
|
82
|
+
if (!seen[pos]) {
|
|
83
|
+
seen[pos] = true;
|
|
84
|
+
const cellRect = map.findCell(pos);
|
|
85
|
+
let cell = table.nodeAt(pos) as Node;
|
|
86
|
+
const extraLeft = rect.left - cellRect.left,
|
|
87
|
+
extraRight = cellRect.right - rect.right;
|
|
88
|
+
if (extraLeft > 0 || extraRight > 0) {
|
|
89
|
+
let attrs = cell.attrs;
|
|
90
|
+
if (extraLeft > 0) attrs = removeColSpan(attrs, 0, extraLeft);
|
|
91
|
+
if (extraRight > 0) attrs = removeColSpan(attrs, attrs['colspan'] - extraRight, extraRight);
|
|
92
|
+
if (cellRect.left < rect.left) cell = cell.type.createAndFill(attrs) as Node;
|
|
93
|
+
else cell = cell.type.create(attrs, cell.content);
|
|
94
|
+
}
|
|
95
|
+
if (cellRect.top < rect.top || cellRect.bottom > rect.bottom) {
|
|
96
|
+
const attrs = setAttr(
|
|
97
|
+
cell.attrs,
|
|
98
|
+
'rowspan',
|
|
99
|
+
Math.min(cellRect.bottom, rect.bottom) - Math.max(cellRect.top, rect.top),
|
|
100
|
+
);
|
|
101
|
+
if (cellRect.top < rect.top) cell = cell.type.createAndFill(attrs) as Node;
|
|
102
|
+
else cell = cell.type.create(attrs, cell.content);
|
|
103
|
+
}
|
|
104
|
+
rowContent.push(cell);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
rows.push(table.child(row).copy(Fragment.from(rowContent)));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const fragment = this.isColSelection() && this.isRowSelection() ? table : rows;
|
|
111
|
+
return new Slice(Fragment.from(fragment), 1, 1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
override replace(tr: Transaction, content = Slice.empty) {
|
|
115
|
+
const mapFrom = tr.steps.length,
|
|
116
|
+
ranges = this.ranges;
|
|
117
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
118
|
+
const { $from, $to } = ranges[i] as SelectionRange,
|
|
119
|
+
mapping = tr.mapping.slice(mapFrom);
|
|
120
|
+
tr.replace(mapping.map($from.pos), mapping.map($to.pos), i ? Slice.empty : content);
|
|
121
|
+
}
|
|
122
|
+
const sel = Selection.findFrom(tr.doc.resolve(tr.mapping.slice(mapFrom).map(this.to)), -1);
|
|
123
|
+
if (sel) tr.setSelection(sel);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override replaceWith(tr: Transaction, node: Node) {
|
|
127
|
+
this.replace(tr, new Slice(Fragment.from(node), 0, 0));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
forEachCell(f: (node: Node, index: number) => void) {
|
|
131
|
+
const table = this.$anchorCell.node(-1),
|
|
132
|
+
map = TableMap.get(table),
|
|
133
|
+
start = this.$anchorCell.start(-1);
|
|
134
|
+
const cells = map.cellsInRect(map.rectBetween(this.$anchorCell.pos - start, this.$headCell.pos - start));
|
|
135
|
+
for (let i = 0; i < cells.length; i++)
|
|
136
|
+
f(table.nodeAt(cells[i] as number) as Node, start + (cells[i] as number));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// :: () → bool
|
|
140
|
+
// True if this selection goes all the way from the top to the
|
|
141
|
+
// bottom of the table.
|
|
142
|
+
isColSelection() {
|
|
143
|
+
const anchorTop = this.$anchorCell.index(-1),
|
|
144
|
+
headTop = this.$headCell.index(-1);
|
|
145
|
+
if (Math.min(anchorTop, headTop) > 0) return false;
|
|
146
|
+
const anchorBot = anchorTop + (this.$anchorCell.nodeAfter as Node).attrs['rowspan'],
|
|
147
|
+
headBot = headTop + (this.$headCell.nodeAfter as Node).attrs['rowspan'];
|
|
148
|
+
return Math.max(anchorBot, headBot) == this.$headCell.node(-1).childCount;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// :: (ResolvedPos, ?ResolvedPos) → CellSelection
|
|
152
|
+
// Returns the smallest column selection that covers the given anchor
|
|
153
|
+
// and head cell.
|
|
154
|
+
static colSelection($anchorCell: ResolvedPos, $headCell = $anchorCell) {
|
|
155
|
+
const map = TableMap.get($anchorCell.node(-1)),
|
|
156
|
+
start = $anchorCell.start(-1);
|
|
157
|
+
const anchorRect = map.findCell($anchorCell.pos - start),
|
|
158
|
+
headRect = map.findCell($headCell.pos - start);
|
|
159
|
+
const doc = $anchorCell.node(0);
|
|
160
|
+
if (anchorRect.top <= headRect.top) {
|
|
161
|
+
if (anchorRect.top > 0) {
|
|
162
|
+
const left = map.map[anchorRect.left] as number;
|
|
163
|
+
$anchorCell = doc.resolve(start + left);
|
|
164
|
+
}
|
|
165
|
+
if (headRect.bottom < map.height) {
|
|
166
|
+
const pos = map.map[map.width * (map.height - 1) + headRect.right - 1] as number;
|
|
167
|
+
$headCell = doc.resolve(start + pos);
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
if (headRect.top > 0) {
|
|
171
|
+
const left = map.map[anchorRect.left] as number;
|
|
172
|
+
$headCell = doc.resolve(start + left);
|
|
173
|
+
}
|
|
174
|
+
if (anchorRect.bottom < map.height) {
|
|
175
|
+
const pos = map.map[map.width * (map.height - 1) + anchorRect.right - 1] as number;
|
|
176
|
+
$anchorCell = doc.resolve(start + pos);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return new CellSelection($anchorCell, $headCell);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// :: () → bool
|
|
183
|
+
// True if this selection goes all the way from the left to the
|
|
184
|
+
// right of the table.
|
|
185
|
+
isRowSelection() {
|
|
186
|
+
const map = TableMap.get(this.$anchorCell.node(-1)),
|
|
187
|
+
start = this.$anchorCell.start(-1);
|
|
188
|
+
const anchorLeft = map.colCount(this.$anchorCell.pos - start),
|
|
189
|
+
headLeft = map.colCount(this.$headCell.pos - start);
|
|
190
|
+
if (Math.min(anchorLeft, headLeft) > 0) return false;
|
|
191
|
+
const anchorRight = anchorLeft + (this.$anchorCell.nodeAfter as Node).attrs['colspan'],
|
|
192
|
+
headRight = headLeft + (this.$headCell.nodeAfter as Node).attrs['colspan'];
|
|
193
|
+
return Math.max(anchorRight, headRight) == map.width;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
eq(other: Selection): boolean {
|
|
197
|
+
return (
|
|
198
|
+
other instanceof CellSelection &&
|
|
199
|
+
other.$anchorCell.pos == this.$anchorCell.pos &&
|
|
200
|
+
other.$headCell.pos == this.$headCell.pos
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// :: (ResolvedPos, ?ResolvedPos) → CellSelection
|
|
205
|
+
// Returns the smallest row selection that covers the given anchor
|
|
206
|
+
// and head cell.
|
|
207
|
+
static rowSelection($anchorCell: ResolvedPos, $headCell = $anchorCell) {
|
|
208
|
+
const map = TableMap.get($anchorCell.node(-1)),
|
|
209
|
+
start = $anchorCell.start(-1);
|
|
210
|
+
const anchorRect = map.findCell($anchorCell.pos - start),
|
|
211
|
+
headRect = map.findCell($headCell.pos - start);
|
|
212
|
+
const doc = $anchorCell.node(0);
|
|
213
|
+
if (anchorRect.left <= headRect.left) {
|
|
214
|
+
if (anchorRect.left > 0) {
|
|
215
|
+
const pos = map.map[anchorRect.top * map.width] as number;
|
|
216
|
+
$anchorCell = doc.resolve(start + pos);
|
|
217
|
+
}
|
|
218
|
+
if (headRect.right < map.width) {
|
|
219
|
+
const pos = map.map[map.width * (headRect.top + 1) - 1] as number;
|
|
220
|
+
$headCell = doc.resolve(start + pos);
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
if (headRect.left > 0) {
|
|
224
|
+
const pos = map.map[headRect.top * map.width] as number;
|
|
225
|
+
$headCell = doc.resolve(start + pos);
|
|
226
|
+
}
|
|
227
|
+
if (anchorRect.right < map.width) {
|
|
228
|
+
const pos = map.map[map.width * (anchorRect.top + 1) - 1] as number;
|
|
229
|
+
|
|
230
|
+
$anchorCell = doc.resolve(start + pos);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return new CellSelection($anchorCell, $headCell);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
toJSON() {
|
|
237
|
+
return {
|
|
238
|
+
type: 'cell',
|
|
239
|
+
anchor: this.$anchorCell.pos,
|
|
240
|
+
head: this.$headCell.pos,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
245
|
+
static override fromJSON(doc: Node, json: any) {
|
|
246
|
+
return new CellSelection(doc.resolve(json.anchor), doc.resolve(json.head));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// :: (Node, number, ?number) → CellSelection
|
|
250
|
+
static create(doc: Node, anchorCell: number, headCell = anchorCell) {
|
|
251
|
+
return new CellSelection(doc.resolve(anchorCell), doc.resolve(headCell));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
override getBookmark() {
|
|
255
|
+
return new CellBookmark(this.$anchorCell.pos, this.$headCell.pos);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
CellSelection.prototype.visible = false;
|
|
260
|
+
|
|
261
|
+
Selection.jsonID('cell', CellSelection);
|
|
262
|
+
|
|
263
|
+
class CellBookmark {
|
|
264
|
+
constructor(public anchor: number, public head: number) {
|
|
265
|
+
this.anchor = anchor;
|
|
266
|
+
this.head = head;
|
|
267
|
+
}
|
|
268
|
+
map(mapping: Mappable) {
|
|
269
|
+
return new CellBookmark(mapping.map(this.anchor), mapping.map(this.head));
|
|
270
|
+
}
|
|
271
|
+
resolve(doc: Node): Selection {
|
|
272
|
+
const $anchorCell = doc.resolve(this.anchor),
|
|
273
|
+
$headCell = doc.resolve(this.head);
|
|
274
|
+
if (
|
|
275
|
+
$anchorCell.parent.type.spec['tableRole'] == 'row' &&
|
|
276
|
+
$headCell.parent.type.spec['tableRole'] == 'row' &&
|
|
277
|
+
$anchorCell.index() < $anchorCell.parent.childCount &&
|
|
278
|
+
$headCell.index() < $headCell.parent.childCount &&
|
|
279
|
+
inSameTable($anchorCell, $headCell)
|
|
280
|
+
)
|
|
281
|
+
return new CellSelection($anchorCell, $headCell);
|
|
282
|
+
else return Selection.near($headCell, 1);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function drawCellSelection(state: EditorState) {
|
|
287
|
+
if (!(state.selection instanceof CellSelection)) return null;
|
|
288
|
+
const cells: Decoration[] = [];
|
|
289
|
+
state.selection.forEachCell((node, pos) => {
|
|
290
|
+
cells.push(Decoration.node(pos, pos + node.nodeSize, { class: 'selectedCell' }));
|
|
291
|
+
});
|
|
292
|
+
return DecorationSet.create(state.doc, cells);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function isCellBoundarySelection({ $from, $to }: Selection) {
|
|
296
|
+
if ($from.pos == $to.pos || $from.pos < $from.pos - 6) return false; // Cheap elimination
|
|
297
|
+
let afterFrom = $from.pos,
|
|
298
|
+
beforeTo = $to.pos,
|
|
299
|
+
depth = $from.depth;
|
|
300
|
+
for (; depth >= 0; depth--, afterFrom++) if ($from.after(depth + 1) < $from.end(depth)) break;
|
|
301
|
+
for (let d = $to.depth; d >= 0; d--, beforeTo--) if ($to.before(d + 1) > $to.start(d)) break;
|
|
302
|
+
return afterFrom == beforeTo && /row|table/.test($from.node(depth).type.spec['tableRole']);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function isTextSelectionAcrossCells({ $from, $to }: Selection) {
|
|
306
|
+
let fromCellBoundaryNode;
|
|
307
|
+
let toCellBoundaryNode;
|
|
308
|
+
|
|
309
|
+
for (let i = $from.depth; i > 0; i--) {
|
|
310
|
+
const node = $from.node(i);
|
|
311
|
+
if (node.type.spec['tableRole'] === 'cell' || node.type.spec['tableRole'] === 'header_cell') {
|
|
312
|
+
fromCellBoundaryNode = node;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
for (let i = $to.depth; i > 0; i--) {
|
|
318
|
+
const node = $to.node(i);
|
|
319
|
+
if (node.type.spec['tableRole'] === 'cell' || node.type.spec['tableRole'] === 'header_cell') {
|
|
320
|
+
toCellBoundaryNode = node;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return fromCellBoundaryNode !== toCellBoundaryNode && $to.parentOffset === 0;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function normalizeSelection(state: EditorState, tr: Transaction | undefined, allowTableNodeSelection: boolean) {
|
|
329
|
+
const sel = (tr || state).selection,
|
|
330
|
+
doc = (tr || state).doc;
|
|
331
|
+
let normalize, role;
|
|
332
|
+
if (sel instanceof NodeSelection && (role = sel.node.type.spec['tableRole'])) {
|
|
333
|
+
if (role == 'cell' || role == 'header_cell') {
|
|
334
|
+
normalize = CellSelection.create(doc, sel.from);
|
|
335
|
+
} else if (role == 'row') {
|
|
336
|
+
const $cell = doc.resolve(sel.from + 1);
|
|
337
|
+
normalize = CellSelection.rowSelection($cell, $cell);
|
|
338
|
+
} else if (!allowTableNodeSelection) {
|
|
339
|
+
const map = TableMap.get(sel.node),
|
|
340
|
+
start = sel.from + 1;
|
|
341
|
+
const pos = map.map[map.width * map.height - 1] as number;
|
|
342
|
+
const lastCell = start + pos;
|
|
343
|
+
normalize = CellSelection.create(doc, start + 1, lastCell);
|
|
344
|
+
}
|
|
345
|
+
} else if (sel instanceof TextSelection && isCellBoundarySelection(sel)) {
|
|
346
|
+
normalize = TextSelection.create(doc, sel.from);
|
|
347
|
+
} else if (sel instanceof TextSelection && isTextSelectionAcrossCells(sel)) {
|
|
348
|
+
normalize = TextSelection.create(doc, sel.$from.start(), sel.$from.end());
|
|
349
|
+
}
|
|
350
|
+
if (normalize) (tr || (tr = state.tr)).setSelection(normalize);
|
|
351
|
+
return tr;
|
|
352
|
+
}
|