@seorii/tiptap 0.0.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/README.md +38 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/plugin/iframe.d.ts +18 -0
- package/dist/plugin/iframe.js +43 -0
- package/dist/plugin/indent.d.ts +20 -0
- package/dist/plugin/indent.js +116 -0
- package/dist/plugin/orderedlist/index.d.ts +3 -0
- package/dist/plugin/orderedlist/index.js +81 -0
- package/dist/plugin/orderedlist/korean.scss +22 -0
- package/dist/plugin/orderedlist/toggleList.d.ts +2 -0
- package/dist/plugin/orderedlist/toggleList.js +67 -0
- package/dist/plugin/table/deleteTable.d.ts +3 -0
- package/dist/plugin/table/deleteTable.js +24 -0
- package/dist/plugin/table/index.d.ts +3 -0
- package/dist/plugin/table/index.js +69 -0
- package/dist/plugin/table/style/cell.scss +16 -0
- package/dist/plugin/table/style/grip.scss +72 -0
- package/dist/plugin/table/style/resize.scss +28 -0
- package/dist/plugin/table/style/table.scss +83 -0
- package/dist/plugin/table/style/theme.scss +6 -0
- package/dist/plugin/table/style.scss +5 -0
- package/dist/plugin/table/tableCell/index.d.ts +2 -0
- package/dist/plugin/table/tableCell/index.js +80 -0
- package/dist/plugin/table/tableHeader/index.d.ts +2 -0
- package/dist/plugin/table/tableHeader/index.js +59 -0
- package/dist/plugin/table/tableRow/index.d.ts +2 -0
- package/dist/plugin/table/tableRow/index.js +4 -0
- package/dist/plugin/table/util.d.ts +46 -0
- package/dist/plugin/table/util.js +195 -0
- package/dist/tiptap/TipTap.svelte +144 -0
- package/dist/tiptap/TipTap.svelte.d.ts +19 -0
- package/dist/tiptap/index.d.ts +2 -0
- package/dist/tiptap/index.js +3 -0
- package/dist/tiptap/tiptap.d.ts +3 -0
- package/dist/tiptap/tiptap.js +56 -0
- package/package.json +83 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
.ProseMirror {
|
|
2
|
+
.tableWrapper {
|
|
3
|
+
position: relative;
|
|
4
|
+
margin-top: 0.75em;
|
|
5
|
+
scrollbar-width: thin;
|
|
6
|
+
scrollbar-color: transparent transparent;
|
|
7
|
+
transition: all 0.1s ease-in-out;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.scrollable {
|
|
11
|
+
padding-left: 1em;
|
|
12
|
+
margin-left: -1em;
|
|
13
|
+
overflow: auto hidden;
|
|
14
|
+
border-left: 1px solid transparent;
|
|
15
|
+
border-right: 1px solid transparent;
|
|
16
|
+
transition: border 250ms ease-in-out 0s;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.scrollable-shadow {
|
|
20
|
+
position: absolute;
|
|
21
|
+
top: 0;
|
|
22
|
+
bottom: 0;
|
|
23
|
+
left: -1em;
|
|
24
|
+
width: 16px;
|
|
25
|
+
transition: box-shadow 250ms ease-in-out 0s;
|
|
26
|
+
border-width: 0 0 0 1em;
|
|
27
|
+
border-style: solid;
|
|
28
|
+
border-color: transparent;
|
|
29
|
+
border-image: initial;
|
|
30
|
+
pointer-events: none;
|
|
31
|
+
|
|
32
|
+
&.left {
|
|
33
|
+
box-shadow: 16px 0 16px -16px inset rgb(0 0 0 / 25%);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&.right {
|
|
37
|
+
right: 0;
|
|
38
|
+
left: auto;
|
|
39
|
+
box-shadow: rgb(0 0 0 / 25%) -16px 0 16px -16px inset;
|
|
40
|
+
|
|
41
|
+
&.is-editable {
|
|
42
|
+
&::after {
|
|
43
|
+
position: absolute;
|
|
44
|
+
top: 0;
|
|
45
|
+
right: 0;
|
|
46
|
+
width: 1em;
|
|
47
|
+
height: 1em;
|
|
48
|
+
background-color: var(--semi-color-nav-bg);
|
|
49
|
+
content: '';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
table {
|
|
56
|
+
margin-top: 1em;
|
|
57
|
+
border-radius: 4px;
|
|
58
|
+
border-collapse: collapse;
|
|
59
|
+
box-sizing: border-box;
|
|
60
|
+
width: 100% !important;
|
|
61
|
+
|
|
62
|
+
h1, h2, h3, p {
|
|
63
|
+
margin: 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
&.is-readonly {
|
|
67
|
+
margin-top: 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.editor {
|
|
73
|
+
.ProseMirror {
|
|
74
|
+
.tableWrapper {
|
|
75
|
+
padding: 10px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.selectedCell {
|
|
79
|
+
border-style: double;
|
|
80
|
+
background: var(--primary-light3);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { TableCell as BuiltInTableCell } from '@tiptap/extension-table-cell';
|
|
2
|
+
import { Plugin } from 'prosemirror-state';
|
|
3
|
+
import { Decoration, DecorationSet } from 'prosemirror-view';
|
|
4
|
+
import { getCellsInColumn, isRowSelected, isTableSelected, selectRow, selectTable } from '../util';
|
|
5
|
+
export default BuiltInTableCell.extend({
|
|
6
|
+
addAttributes() {
|
|
7
|
+
return {
|
|
8
|
+
colspan: { default: 1 },
|
|
9
|
+
rowspan: { default: 1 },
|
|
10
|
+
colwidth: {
|
|
11
|
+
default: null,
|
|
12
|
+
parseHTML: (element) => {
|
|
13
|
+
const colwidth = element.getAttribute('colwidth');
|
|
14
|
+
return colwidth ? colwidth.split(',').map((item) => parseInt(item, 10)) : null;
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
style: { default: null },
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
addProseMirrorPlugins() {
|
|
21
|
+
const { isEditable } = this.editor;
|
|
22
|
+
return [
|
|
23
|
+
new Plugin({
|
|
24
|
+
props: {
|
|
25
|
+
decorations: (state) => {
|
|
26
|
+
if (!isEditable) {
|
|
27
|
+
//return DecorationSet.empty;
|
|
28
|
+
}
|
|
29
|
+
const { doc, selection } = state;
|
|
30
|
+
const decorations = [];
|
|
31
|
+
const cells = getCellsInColumn(0)(selection);
|
|
32
|
+
if (cells) {
|
|
33
|
+
cells.forEach(({ pos }, index) => {
|
|
34
|
+
if (index === 0) {
|
|
35
|
+
decorations.push(Decoration.widget(pos + 1, () => {
|
|
36
|
+
let className = 'grip-table';
|
|
37
|
+
const selected = isTableSelected(selection);
|
|
38
|
+
if (selected)
|
|
39
|
+
className += ' selected';
|
|
40
|
+
const grip = document.createElement('a');
|
|
41
|
+
grip.className = className;
|
|
42
|
+
grip.addEventListener('mousedown', (event) => {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
event.stopImmediatePropagation();
|
|
45
|
+
this.editor.view.dispatch(selectTable(this.editor.state.tr));
|
|
46
|
+
// this.options.onSelectTable(state);
|
|
47
|
+
});
|
|
48
|
+
return grip;
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
decorations.push(Decoration.widget(pos + 1, () => {
|
|
52
|
+
const rowSelected = isRowSelected(index)(selection);
|
|
53
|
+
let className = 'grip-row';
|
|
54
|
+
if (rowSelected) {
|
|
55
|
+
className += ' selected';
|
|
56
|
+
}
|
|
57
|
+
if (index === 0) {
|
|
58
|
+
className += ' first';
|
|
59
|
+
}
|
|
60
|
+
if (index === cells.length - 1) {
|
|
61
|
+
className += ' last';
|
|
62
|
+
}
|
|
63
|
+
const grip = document.createElement('a');
|
|
64
|
+
grip.className = className;
|
|
65
|
+
grip.addEventListener('mousedown', (event) => {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
event.stopImmediatePropagation();
|
|
68
|
+
this.editor.view.dispatch(selectRow(index)(this.editor.state.tr));
|
|
69
|
+
});
|
|
70
|
+
return grip;
|
|
71
|
+
}));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return DecorationSet.create(doc, decorations);
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
];
|
|
79
|
+
},
|
|
80
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { TableHeader as BuiltInTableHeader } from '@tiptap/extension-table-header';
|
|
2
|
+
import { Plugin } from 'prosemirror-state';
|
|
3
|
+
import { Decoration, DecorationSet } from 'prosemirror-view';
|
|
4
|
+
import { getCellsInRow, isColumnSelected, selectColumn } from '../util';
|
|
5
|
+
export default BuiltInTableHeader.extend({
|
|
6
|
+
addAttributes() {
|
|
7
|
+
return {
|
|
8
|
+
colspan: { default: 1 },
|
|
9
|
+
rowspan: { default: 1 },
|
|
10
|
+
colwidth: {
|
|
11
|
+
default: null,
|
|
12
|
+
parseHTML: (element) => {
|
|
13
|
+
const colwidth = element.getAttribute('colwidth');
|
|
14
|
+
return colwidth ? colwidth.split(',').map((item) => parseInt(item, 10)) : null;
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
style: { default: null },
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
addProseMirrorPlugins() {
|
|
21
|
+
const { isEditable } = this.editor;
|
|
22
|
+
return [
|
|
23
|
+
new Plugin({
|
|
24
|
+
props: {
|
|
25
|
+
decorations: (state) => {
|
|
26
|
+
if (!isEditable)
|
|
27
|
+
return DecorationSet.empty;
|
|
28
|
+
const { doc, selection } = state;
|
|
29
|
+
const decorations = [];
|
|
30
|
+
const cells = getCellsInRow(0)(selection);
|
|
31
|
+
if (cells) {
|
|
32
|
+
cells.forEach(({ pos }, index) => {
|
|
33
|
+
decorations.push(Decoration.widget(pos + 1, () => {
|
|
34
|
+
const colSelected = isColumnSelected(index)(selection);
|
|
35
|
+
let className = 'grip-column';
|
|
36
|
+
if (colSelected)
|
|
37
|
+
className += ' selected';
|
|
38
|
+
if (index === 0)
|
|
39
|
+
className += ' first';
|
|
40
|
+
else if (index === cells.length - 1)
|
|
41
|
+
className += ' last';
|
|
42
|
+
const grip = document.createElement('a');
|
|
43
|
+
grip.className = className;
|
|
44
|
+
grip.addEventListener('mousedown', (event) => {
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
event.stopImmediatePropagation();
|
|
47
|
+
this.editor.view.dispatch(selectColumn(index)(this.editor.state.tr));
|
|
48
|
+
});
|
|
49
|
+
return grip;
|
|
50
|
+
}));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return DecorationSet.create(doc, decorations);
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
];
|
|
58
|
+
},
|
|
59
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Node, ResolvedPos } from 'prosemirror-model';
|
|
2
|
+
import type { Selection, Transaction } from 'prosemirror-state';
|
|
3
|
+
import { CellSelection, type TableRect } from 'prosemirror-tables';
|
|
4
|
+
import type { EditorState } from "prosemirror-state";
|
|
5
|
+
export declare const isRectSelected: (rect: any) => (selection: CellSelection) => boolean;
|
|
6
|
+
export declare const findTable: (selection: Selection) => {
|
|
7
|
+
pos: number;
|
|
8
|
+
start: number;
|
|
9
|
+
depth: number;
|
|
10
|
+
node: Node;
|
|
11
|
+
} | undefined;
|
|
12
|
+
export declare const isCellSelection: (selection: any) => boolean;
|
|
13
|
+
export declare const isColumnSelected: (columnIndex: number) => (selection: any) => boolean;
|
|
14
|
+
export declare const isRowSelected: (rowIndex: number) => (selection: any) => boolean;
|
|
15
|
+
export declare const isTableSelected: (selection: any) => boolean;
|
|
16
|
+
export declare const getCellsInColumn: (columnIndex: number | number[]) => (selection: Selection) => {
|
|
17
|
+
pos: number;
|
|
18
|
+
start: number;
|
|
19
|
+
node: Node | null | undefined;
|
|
20
|
+
}[] | undefined;
|
|
21
|
+
export declare const getCellsInRow: (rowIndex: number | number[]) => (selection: Selection) => {
|
|
22
|
+
pos: number;
|
|
23
|
+
start: number;
|
|
24
|
+
node: Node | null | undefined;
|
|
25
|
+
}[] | undefined;
|
|
26
|
+
export declare const getCellsInTable: (selection: Selection) => {
|
|
27
|
+
pos: number;
|
|
28
|
+
start: number;
|
|
29
|
+
node: Node | null;
|
|
30
|
+
}[] | undefined;
|
|
31
|
+
export declare const findParentNodeClosestToPos: ($pos: ResolvedPos, predicate: (node: Node) => boolean) => {
|
|
32
|
+
pos: number;
|
|
33
|
+
start: number;
|
|
34
|
+
depth: number;
|
|
35
|
+
node: Node;
|
|
36
|
+
} | undefined;
|
|
37
|
+
export declare const findCellClosestToPos: ($pos: ResolvedPos) => {
|
|
38
|
+
pos: number;
|
|
39
|
+
start: number;
|
|
40
|
+
depth: number;
|
|
41
|
+
node: Node;
|
|
42
|
+
} | undefined;
|
|
43
|
+
export declare const selectColumn: (index: number) => (tr: Transaction) => Transaction;
|
|
44
|
+
export declare const selectRow: (index: number) => (tr: Transaction) => Transaction;
|
|
45
|
+
export declare const selectTable: (tr: Transaction) => Transaction;
|
|
46
|
+
export declare function tableRect(state: EditorState): TableRect;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { findParentNode } from '@tiptap/core';
|
|
2
|
+
import { CellSelection, selectionCell, TableMap } from 'prosemirror-tables';
|
|
3
|
+
export const isRectSelected = (rect) => (selection) => {
|
|
4
|
+
const map = TableMap.get(selection.$anchorCell.node(-1));
|
|
5
|
+
const start = selection.$anchorCell.start(-1);
|
|
6
|
+
const cells = map.cellsInRect(rect);
|
|
7
|
+
const selectedCells = map.cellsInRect(map.rectBetween(selection.$anchorCell.pos - start, selection.$headCell.pos - start));
|
|
8
|
+
for (let i = 0, count = cells.length; i < count; i++) {
|
|
9
|
+
if (selectedCells.indexOf(cells[i]) === -1) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return true;
|
|
14
|
+
};
|
|
15
|
+
export const findTable = (selection) => findParentNode((node) => node.type.spec.tableRole && node.type.spec.tableRole === 'table')(selection);
|
|
16
|
+
export const isCellSelection = (selection) => {
|
|
17
|
+
return selection instanceof CellSelection;
|
|
18
|
+
};
|
|
19
|
+
export const isColumnSelected = (columnIndex) => (selection) => {
|
|
20
|
+
if (isCellSelection(selection)) {
|
|
21
|
+
const map = TableMap.get(selection.$anchorCell.node(-1));
|
|
22
|
+
return isRectSelected({
|
|
23
|
+
left: columnIndex,
|
|
24
|
+
right: columnIndex + 1,
|
|
25
|
+
top: 0,
|
|
26
|
+
bottom: map.height,
|
|
27
|
+
})(selection);
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
31
|
+
export const isRowSelected = (rowIndex) => (selection) => {
|
|
32
|
+
if (isCellSelection(selection)) {
|
|
33
|
+
const map = TableMap.get(selection.$anchorCell.node(-1));
|
|
34
|
+
return isRectSelected({
|
|
35
|
+
left: 0,
|
|
36
|
+
right: map.width,
|
|
37
|
+
top: rowIndex,
|
|
38
|
+
bottom: rowIndex + 1,
|
|
39
|
+
})(selection);
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
};
|
|
43
|
+
export const isTableSelected = (selection) => {
|
|
44
|
+
if (isCellSelection(selection)) {
|
|
45
|
+
const map = TableMap.get(selection.$anchorCell.node(-1));
|
|
46
|
+
return isRectSelected({
|
|
47
|
+
left: 0,
|
|
48
|
+
right: map.width,
|
|
49
|
+
top: 0,
|
|
50
|
+
bottom: map.height,
|
|
51
|
+
})(selection);
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
};
|
|
55
|
+
export const getCellsInColumn = (columnIndex) => (selection) => {
|
|
56
|
+
const table = findTable(selection);
|
|
57
|
+
if (table) {
|
|
58
|
+
const map = TableMap.get(table.node);
|
|
59
|
+
const indexes = Array.isArray(columnIndex) ? columnIndex : Array.from([columnIndex]);
|
|
60
|
+
return indexes.reduce((acc, index) => {
|
|
61
|
+
if (index >= 0 && index <= map.width - 1) {
|
|
62
|
+
const cells = map.cellsInRect({
|
|
63
|
+
left: index,
|
|
64
|
+
right: index + 1,
|
|
65
|
+
top: 0,
|
|
66
|
+
bottom: map.height,
|
|
67
|
+
});
|
|
68
|
+
return acc.concat(cells.map((nodePos) => {
|
|
69
|
+
const node = table.node.nodeAt(nodePos);
|
|
70
|
+
const pos = nodePos + table.start;
|
|
71
|
+
return { pos, start: pos + 1, node };
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
return acc;
|
|
75
|
+
}, []);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
export const getCellsInRow = (rowIndex) => (selection) => {
|
|
79
|
+
const table = findTable(selection);
|
|
80
|
+
if (table) {
|
|
81
|
+
const map = TableMap.get(table.node);
|
|
82
|
+
const indexes = Array.isArray(rowIndex) ? rowIndex : Array.from([rowIndex]);
|
|
83
|
+
return indexes.reduce((acc, index) => {
|
|
84
|
+
if (index >= 0 && index <= map.height - 1) {
|
|
85
|
+
const cells = map.cellsInRect({
|
|
86
|
+
left: 0,
|
|
87
|
+
right: map.width,
|
|
88
|
+
top: index,
|
|
89
|
+
bottom: index + 1,
|
|
90
|
+
});
|
|
91
|
+
return acc.concat(cells.map((nodePos) => {
|
|
92
|
+
const node = table.node.nodeAt(nodePos);
|
|
93
|
+
const pos = nodePos + table.start;
|
|
94
|
+
return { pos, start: pos + 1, node };
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
return acc;
|
|
98
|
+
}, []);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
export const getCellsInTable = (selection) => {
|
|
102
|
+
const table = findTable(selection);
|
|
103
|
+
if (table) {
|
|
104
|
+
const map = TableMap.get(table.node);
|
|
105
|
+
const cells = map.cellsInRect({
|
|
106
|
+
left: 0,
|
|
107
|
+
right: map.width,
|
|
108
|
+
top: 0,
|
|
109
|
+
bottom: map.height,
|
|
110
|
+
});
|
|
111
|
+
return cells.map((nodePos) => {
|
|
112
|
+
const node = table.node.nodeAt(nodePos);
|
|
113
|
+
const pos = nodePos + table.start;
|
|
114
|
+
return { pos, start: pos + 1, node };
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
export const findParentNodeClosestToPos = ($pos, predicate) => {
|
|
119
|
+
for (let i = $pos.depth; i > 0; i--) {
|
|
120
|
+
const node = $pos.node(i);
|
|
121
|
+
if (predicate(node)) {
|
|
122
|
+
return {
|
|
123
|
+
pos: i > 0 ? $pos.before(i) : 0,
|
|
124
|
+
start: $pos.start(i),
|
|
125
|
+
depth: i,
|
|
126
|
+
node,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
export const findCellClosestToPos = ($pos) => {
|
|
132
|
+
const predicate = (node) => node.type.spec.tableRole && /cell/i.test(node.type.spec.tableRole);
|
|
133
|
+
return findParentNodeClosestToPos($pos, predicate);
|
|
134
|
+
};
|
|
135
|
+
const select = (type) => (index) => (tr) => {
|
|
136
|
+
const table = findTable(tr.selection);
|
|
137
|
+
const isRowSelection = type === 'row';
|
|
138
|
+
if (table) {
|
|
139
|
+
const map = TableMap.get(table.node);
|
|
140
|
+
// Check if the index is valid
|
|
141
|
+
if (index >= 0 && index < (isRowSelection ? map.height : map.width)) {
|
|
142
|
+
const left = isRowSelection ? 0 : index;
|
|
143
|
+
const top = isRowSelection ? index : 0;
|
|
144
|
+
const right = isRowSelection ? map.width : index + 1;
|
|
145
|
+
const bottom = isRowSelection ? index + 1 : map.height;
|
|
146
|
+
const cellsInFirstRow = map.cellsInRect({
|
|
147
|
+
left,
|
|
148
|
+
top,
|
|
149
|
+
right: isRowSelection ? right : left + 1,
|
|
150
|
+
bottom: isRowSelection ? top + 1 : bottom,
|
|
151
|
+
});
|
|
152
|
+
const cellsInLastRow = bottom - top === 1
|
|
153
|
+
? cellsInFirstRow
|
|
154
|
+
: map.cellsInRect({
|
|
155
|
+
left: isRowSelection ? left : right - 1,
|
|
156
|
+
top: isRowSelection ? bottom - 1 : top,
|
|
157
|
+
right,
|
|
158
|
+
bottom,
|
|
159
|
+
});
|
|
160
|
+
const head = table.start + cellsInFirstRow[0];
|
|
161
|
+
const anchor = table.start + cellsInLastRow[cellsInLastRow.length - 1];
|
|
162
|
+
const $head = tr.doc.resolve(head);
|
|
163
|
+
const $anchor = tr.doc.resolve(anchor);
|
|
164
|
+
// @ts-ignore
|
|
165
|
+
return tr.setSelection(new CellSelection($anchor, $head));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return tr;
|
|
169
|
+
};
|
|
170
|
+
export const selectColumn = select('column');
|
|
171
|
+
export const selectRow = select('row');
|
|
172
|
+
export const selectTable = (tr) => {
|
|
173
|
+
const table = findTable(tr.selection);
|
|
174
|
+
if (table) {
|
|
175
|
+
const { map } = TableMap.get(table.node);
|
|
176
|
+
if (map && map.length) {
|
|
177
|
+
const head = table.start + map[0];
|
|
178
|
+
const anchor = table.start + map[map.length - 1];
|
|
179
|
+
const $head = tr.doc.resolve(head);
|
|
180
|
+
const $anchor = tr.doc.resolve(anchor);
|
|
181
|
+
// @ts-ignore
|
|
182
|
+
return tr.setSelection(new CellSelection($anchor, $head));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return tr;
|
|
186
|
+
};
|
|
187
|
+
export function tableRect(state) {
|
|
188
|
+
const sel = state.selection;
|
|
189
|
+
const $pos = selectionCell(state);
|
|
190
|
+
const table = $pos.node(-1);
|
|
191
|
+
const tableStart = $pos.start(-1);
|
|
192
|
+
const map = TableMap.get(table);
|
|
193
|
+
const rect = map.rectBetween(sel.$anchorCell.pos - tableStart, sel.$headCell.pos - tableStart);
|
|
194
|
+
return { ...rect, tableStart, map, table };
|
|
195
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<script>import { browser } from "$app/environment";
|
|
2
|
+
import { onMount, setContext } from 'svelte';
|
|
3
|
+
import { Card, Input } from "nunui";
|
|
4
|
+
import { writable } from "svelte/store";
|
|
5
|
+
import "tiptap-katex/style.css";
|
|
6
|
+
import sanitizeHtml from 'sanitize-html';
|
|
7
|
+
const san = (body, force = false) => (editor || force) ? body : sanitizeHtml(body, {
|
|
8
|
+
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img', 'math-inline', 'iframe', 'tiptap-file']),
|
|
9
|
+
allowedStyles: '*', allowedAttributes: {
|
|
10
|
+
'*': ['style', 'class'],
|
|
11
|
+
a: ['href', 'name', 'target'],
|
|
12
|
+
img: ['src', 'srcset', 'alt', 'title', 'width', 'height', 'loading'],
|
|
13
|
+
iframe: ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
|
14
|
+
'tiptap-file': ['id']
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
export let body = '', editor = false, style = '';
|
|
18
|
+
const tiptap = setContext('editor', writable(null));
|
|
19
|
+
let element, _body = san(body, true), fullscreen = false, html = false, mounted = false;
|
|
20
|
+
$: _san = san(body);
|
|
21
|
+
$: if (_san !== _body && $tiptap)
|
|
22
|
+
$tiptap?.commands.setContent(_body = _san);
|
|
23
|
+
if (browser)
|
|
24
|
+
onMount(() => {
|
|
25
|
+
mounted = true;
|
|
26
|
+
import('./tiptap').then(({ default: tt }) => {
|
|
27
|
+
if (!mounted)
|
|
28
|
+
return;
|
|
29
|
+
$tiptap = tt(element, _body, {
|
|
30
|
+
editable: editor,
|
|
31
|
+
onTransaction: () => $tiptap = $tiptap,
|
|
32
|
+
});
|
|
33
|
+
$tiptap.on('update', ({ editor: tiptap }) => _body = body = editor ? tiptap.getHTML() : body);
|
|
34
|
+
});
|
|
35
|
+
return () => {
|
|
36
|
+
mounted = false;
|
|
37
|
+
$tiptap?.destroy?.();
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<Card outlined={editor}
|
|
43
|
+
style="max-width:100%;margin-top:0;{editor ? '' : 'box-shadow:none;padding:0;border-radius:0;'}{style}">
|
|
44
|
+
<main class:fullscreen class:editor>
|
|
45
|
+
<div class="wrapper">
|
|
46
|
+
<div bind:this={element} class:hide={html} class="target"></div>
|
|
47
|
+
{#if !$tiptap}
|
|
48
|
+
로드 중...
|
|
49
|
+
{/if}
|
|
50
|
+
<div class:hide={!html}>
|
|
51
|
+
<Input multiline fullWidth placeholder="HTML" bind:value={body}/>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</main>
|
|
55
|
+
</Card>
|
|
56
|
+
|
|
57
|
+
<style>main {
|
|
58
|
+
position: relative;
|
|
59
|
+
overscroll-behavior: none;
|
|
60
|
+
}
|
|
61
|
+
main.fullscreen {
|
|
62
|
+
z-index: 9999999;
|
|
63
|
+
position: fixed;
|
|
64
|
+
top: 0;
|
|
65
|
+
left: 0;
|
|
66
|
+
background: var(--surface);
|
|
67
|
+
padding: 82px 12px 12px 12px;
|
|
68
|
+
width: calc(100% - 24px);
|
|
69
|
+
height: calc(100% - 94px);
|
|
70
|
+
}
|
|
71
|
+
main .wrapper {
|
|
72
|
+
position: relative;
|
|
73
|
+
}
|
|
74
|
+
main .wrapper .hide {
|
|
75
|
+
display: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.target > :global(div) > :global(*:first-child) {
|
|
79
|
+
margin-top: 0 !important;
|
|
80
|
+
}
|
|
81
|
+
.target > :global(div) > :global(*:last-child) {
|
|
82
|
+
margin-bottom: 0 !important;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.editor :global(.ProseMirror-selectednode img) {
|
|
86
|
+
transition: all 0.2s ease-in-out;
|
|
87
|
+
filter: drop-shadow(0 0 0.75rem var(--primary-light13));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.editor .iframe-wrapper.ProseMirror-selectednode {
|
|
91
|
+
outline: 3px solid var(--primary);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
div > :global(div) {
|
|
95
|
+
outline: none !important;
|
|
96
|
+
}
|
|
97
|
+
div > :global(div) :global(.ProseMirror) :global(p.is-editor-empty:first-child::before) {
|
|
98
|
+
color: #adb5bd;
|
|
99
|
+
content: attr(data-placeholder);
|
|
100
|
+
float: left;
|
|
101
|
+
height: 0;
|
|
102
|
+
pointer-events: none;
|
|
103
|
+
}
|
|
104
|
+
div > :global(div) :global(img) {
|
|
105
|
+
transition: all 0.2s ease-in-out;
|
|
106
|
+
max-width: 100%;
|
|
107
|
+
border-radius: 12px;
|
|
108
|
+
position: relative;
|
|
109
|
+
}
|
|
110
|
+
div > :global(div) :global(pre) {
|
|
111
|
+
background: var(--primary-light1);
|
|
112
|
+
padding: 12px;
|
|
113
|
+
border-radius: 12px;
|
|
114
|
+
max-width: 100%;
|
|
115
|
+
}
|
|
116
|
+
div > :global(div) :global(table) {
|
|
117
|
+
border-collapse: collapse;
|
|
118
|
+
width: 100%;
|
|
119
|
+
margin: 8px 0;
|
|
120
|
+
border: 1px solid var(--primary-light1);
|
|
121
|
+
border-radius: 12px;
|
|
122
|
+
}
|
|
123
|
+
div > :global(div) :global(table) :global(th), div > :global(div) :global(table) :global(td) {
|
|
124
|
+
padding: 8px;
|
|
125
|
+
border: 1px solid var(--primary-light1);
|
|
126
|
+
}
|
|
127
|
+
div > :global(div) :global(.math-render) {
|
|
128
|
+
cursor: initial;
|
|
129
|
+
}
|
|
130
|
+
div > :global(div) :global(.iframe-wrapper) {
|
|
131
|
+
position: relative;
|
|
132
|
+
padding-bottom: 12px;
|
|
133
|
+
overflow: hidden;
|
|
134
|
+
width: 100%;
|
|
135
|
+
height: 600px;
|
|
136
|
+
border-radius: 12px;
|
|
137
|
+
}
|
|
138
|
+
div > :global(div) :global(.iframe-wrapper) :global(iframe) {
|
|
139
|
+
position: absolute;
|
|
140
|
+
top: 0;
|
|
141
|
+
left: 0;
|
|
142
|
+
width: 100%;
|
|
143
|
+
height: 100%;
|
|
144
|
+
}</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SvelteComponentTyped } from "svelte";
|
|
2
|
+
import "tiptap-katex/style.css";
|
|
3
|
+
declare const __propDef: {
|
|
4
|
+
props: {
|
|
5
|
+
body?: string | undefined;
|
|
6
|
+
editor?: boolean | undefined;
|
|
7
|
+
style?: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
events: {
|
|
10
|
+
[evt: string]: CustomEvent<any>;
|
|
11
|
+
};
|
|
12
|
+
slots: {};
|
|
13
|
+
};
|
|
14
|
+
export type TipTapProps = typeof __propDef.props;
|
|
15
|
+
export type TipTapEvents = typeof __propDef.events;
|
|
16
|
+
export type TipTapSlots = typeof __propDef.slots;
|
|
17
|
+
export default class TipTap extends SvelteComponentTyped<TipTapProps, TipTapEvents, TipTapSlots> {
|
|
18
|
+
}
|
|
19
|
+
export {};
|