@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
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# create-svelte
|
|
2
|
+
|
|
3
|
+
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
|
4
|
+
|
|
5
|
+
## Creating a project
|
|
6
|
+
|
|
7
|
+
If you're seeing this, you've probably already done this step. Congrats!
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# create a new project in the current directory
|
|
11
|
+
npm create svelte@latest
|
|
12
|
+
|
|
13
|
+
# create a new project in my-app
|
|
14
|
+
npm create svelte@latest my-app
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Developing
|
|
18
|
+
|
|
19
|
+
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run dev
|
|
23
|
+
|
|
24
|
+
# or start the server and open the app in a new browser tab
|
|
25
|
+
npm run dev -- --open
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Building
|
|
29
|
+
|
|
30
|
+
To create a production version of your app:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm run build
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
You can preview the production build with `npm run preview`.
|
|
37
|
+
|
|
38
|
+
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Node } from '@tiptap/core';
|
|
2
|
+
export interface IframeOptions {
|
|
3
|
+
allowFullscreen: boolean;
|
|
4
|
+
HTMLAttributes: {
|
|
5
|
+
[key: string]: any;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
declare module '@tiptap/core' {
|
|
9
|
+
interface Commands<ReturnType> {
|
|
10
|
+
iframe: {
|
|
11
|
+
setIframe: (options: {
|
|
12
|
+
src: string;
|
|
13
|
+
}) => ReturnType;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
declare const _default: Node<IframeOptions, any>;
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { mergeAttributes, Node } from '@tiptap/core';
|
|
2
|
+
export default Node.create({
|
|
3
|
+
name: 'iframe',
|
|
4
|
+
group: 'block',
|
|
5
|
+
atom: true,
|
|
6
|
+
addOptions() {
|
|
7
|
+
return {
|
|
8
|
+
allowFullscreen: true,
|
|
9
|
+
HTMLAttributes: {
|
|
10
|
+
class: 'iframe-wrapper',
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
},
|
|
14
|
+
addAttributes() {
|
|
15
|
+
return {
|
|
16
|
+
src: { default: null },
|
|
17
|
+
frameborder: { default: 0 },
|
|
18
|
+
allowfullscreen: {
|
|
19
|
+
default: this.options.allowFullscreen,
|
|
20
|
+
parseHTML: () => this.options.allowFullscreen,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
parseHTML() {
|
|
25
|
+
return [{
|
|
26
|
+
tag: 'iframe',
|
|
27
|
+
}];
|
|
28
|
+
},
|
|
29
|
+
renderHTML({ HTMLAttributes }) {
|
|
30
|
+
return ['div', this.options.HTMLAttributes, ['iframe', mergeAttributes(HTMLAttributes, { credentialless: true })]];
|
|
31
|
+
},
|
|
32
|
+
addCommands() {
|
|
33
|
+
return {
|
|
34
|
+
setIframe: (options) => ({ tr, dispatch }) => {
|
|
35
|
+
const { selection } = tr;
|
|
36
|
+
const node = this.type.create(options);
|
|
37
|
+
if (dispatch)
|
|
38
|
+
tr.replaceRangeWith(selection.from, selection.to, node);
|
|
39
|
+
return true;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core';
|
|
2
|
+
declare module '@tiptap/core' {
|
|
3
|
+
interface Commands<ReturnType> {
|
|
4
|
+
indent: {
|
|
5
|
+
indent: () => ReturnType;
|
|
6
|
+
outdent: () => ReturnType;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
type IndentOptions = {
|
|
11
|
+
names: Array<string>;
|
|
12
|
+
indentRange: number;
|
|
13
|
+
minIndentLevel: number;
|
|
14
|
+
maxIndentLevel: number;
|
|
15
|
+
defaultIndentLevel: number;
|
|
16
|
+
HTMLAttributes: Record<string, any>;
|
|
17
|
+
};
|
|
18
|
+
export declare const Indent: Extension<IndentOptions, never>;
|
|
19
|
+
export declare const clamp: (val: number, min: number, max: number) => number;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
2
|
+
// Sources:
|
|
3
|
+
// https://github.com/ueberdosis/tiptap/issues/1036#issuecomment-981094752
|
|
4
|
+
// https://github.com/django-tiptap/django_tiptap/blob/main/django_tiptap/templates/forms/tiptap_textarea.html#L453-L602
|
|
5
|
+
import { Extension, isList, } from '@tiptap/core';
|
|
6
|
+
import { TextSelection, Transaction } from 'prosemirror-state';
|
|
7
|
+
export const Indent = Extension.create({
|
|
8
|
+
name: 'indent',
|
|
9
|
+
addOptions() {
|
|
10
|
+
return {
|
|
11
|
+
names: ['heading', 'paragraph'],
|
|
12
|
+
indentRange: 24,
|
|
13
|
+
minIndentLevel: 0,
|
|
14
|
+
maxIndentLevel: 24 * 10,
|
|
15
|
+
defaultIndentLevel: 0,
|
|
16
|
+
HTMLAttributes: {},
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
addGlobalAttributes() {
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
types: this.options.names,
|
|
23
|
+
attributes: {
|
|
24
|
+
indent: {
|
|
25
|
+
default: this.options.defaultIndentLevel,
|
|
26
|
+
renderHTML: (attributes) => ({
|
|
27
|
+
style: `margin-left: ${attributes.indent}px!important;`,
|
|
28
|
+
}),
|
|
29
|
+
parseHTML: (element) => parseInt(element.style.marginLeft, 10) ||
|
|
30
|
+
this.options.defaultIndentLevel,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
},
|
|
36
|
+
addCommands() {
|
|
37
|
+
return {
|
|
38
|
+
indent: () => ({ tr, state, dispatch, editor }) => {
|
|
39
|
+
const { selection } = state;
|
|
40
|
+
tr = tr.setSelection(selection);
|
|
41
|
+
tr = updateIndentLevel(tr, this.options, editor.extensionManager.extensions, 'indent');
|
|
42
|
+
if (tr.docChanged && dispatch) {
|
|
43
|
+
dispatch(tr);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
editor.chain().focus().run();
|
|
47
|
+
return false;
|
|
48
|
+
},
|
|
49
|
+
outdent: () => ({ tr, state, dispatch, editor }) => {
|
|
50
|
+
const { selection } = state;
|
|
51
|
+
tr = tr.setSelection(selection);
|
|
52
|
+
tr = updateIndentLevel(tr, this.options, editor.extensionManager.extensions, 'outdent');
|
|
53
|
+
if (tr.docChanged && dispatch) {
|
|
54
|
+
dispatch(tr);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
editor.chain().focus().run();
|
|
58
|
+
return false;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
addKeyboardShortcuts() {
|
|
63
|
+
return {
|
|
64
|
+
Tab: indent(),
|
|
65
|
+
'Shift-Tab': outdent(false),
|
|
66
|
+
Backspace: outdent(true),
|
|
67
|
+
'Mod-]': indent(),
|
|
68
|
+
'Mod-[': outdent(false),
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
export const clamp = (val, min, max) => {
|
|
73
|
+
if (val < min)
|
|
74
|
+
return min;
|
|
75
|
+
if (val > max)
|
|
76
|
+
return max;
|
|
77
|
+
return val;
|
|
78
|
+
};
|
|
79
|
+
function setNodeIndentMarkup(tr, pos, delta, min, max) {
|
|
80
|
+
if (!tr.doc)
|
|
81
|
+
return tr;
|
|
82
|
+
const node = tr.doc.nodeAt(pos);
|
|
83
|
+
if (!node)
|
|
84
|
+
return tr;
|
|
85
|
+
const indent = clamp((node.attrs.indent || 0) + delta, min, max);
|
|
86
|
+
if (indent === node.attrs.indent)
|
|
87
|
+
return tr;
|
|
88
|
+
const nodeAttrs = { ...node.attrs, indent };
|
|
89
|
+
return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks);
|
|
90
|
+
}
|
|
91
|
+
const updateIndentLevel = (tr, options, extensions, type) => {
|
|
92
|
+
const { doc, selection } = tr;
|
|
93
|
+
if (!doc || !selection)
|
|
94
|
+
return tr;
|
|
95
|
+
if (!(selection instanceof TextSelection))
|
|
96
|
+
return tr;
|
|
97
|
+
const { from, to } = selection;
|
|
98
|
+
doc.nodesBetween(from, to, (node, pos) => {
|
|
99
|
+
if (options.names.includes(node.type.name)) {
|
|
100
|
+
tr = setNodeIndentMarkup(tr, pos, options.indentRange * (type === 'indent' ? 1 : -1), options.minIndentLevel, options.maxIndentLevel);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return !isList(node.type.name, extensions);
|
|
104
|
+
});
|
|
105
|
+
return tr;
|
|
106
|
+
};
|
|
107
|
+
const indent = () => ({ editor }) => {
|
|
108
|
+
if (!isList(editor.state.doc.type.name, editor.extensionManager.extensions))
|
|
109
|
+
return editor.commands.indent();
|
|
110
|
+
return false;
|
|
111
|
+
};
|
|
112
|
+
const outdent = (outdentOnlyAtHead) => ({ editor }) => {
|
|
113
|
+
if (!(isList(editor.state.doc.type.name, editor.extensionManager.extensions) || (outdentOnlyAtHead && editor.state.selection.$head.parentOffset !== 0)))
|
|
114
|
+
return editor.commands.outdent();
|
|
115
|
+
return false;
|
|
116
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { wrappingInputRule } from "@tiptap/core";
|
|
2
|
+
import OrderedListBase from '@tiptap/extension-ordered-list';
|
|
3
|
+
import toggleList from "./toggleList";
|
|
4
|
+
import './korean.scss';
|
|
5
|
+
export default OrderedListBase.extend({
|
|
6
|
+
priority: 20,
|
|
7
|
+
addAttributes() {
|
|
8
|
+
return {
|
|
9
|
+
start: {
|
|
10
|
+
default: 1,
|
|
11
|
+
parseHTML: (element) => {
|
|
12
|
+
return element.hasAttribute('start')
|
|
13
|
+
? parseInt(element.getAttribute('start') || '', 10)
|
|
14
|
+
: 1;
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
type: {
|
|
18
|
+
default: '1',
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
addInputRules() {
|
|
23
|
+
return [
|
|
24
|
+
wrappingInputRule({
|
|
25
|
+
find: /^(\d+)\.\s$/,
|
|
26
|
+
type: this.type,
|
|
27
|
+
getAttributes: match => ({ start: +match[1], type: '1' }),
|
|
28
|
+
joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
|
|
29
|
+
}),
|
|
30
|
+
wrappingInputRule({
|
|
31
|
+
find: /^([i])\.\s$/,
|
|
32
|
+
type: this.type,
|
|
33
|
+
getAttributes: match => ({ start: 1, type: 'i' }),
|
|
34
|
+
joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
|
|
35
|
+
}),
|
|
36
|
+
wrappingInputRule({
|
|
37
|
+
find: /^([I])\.\s$/,
|
|
38
|
+
type: this.type,
|
|
39
|
+
getAttributes: match => ({ start: 1, type: 'I' }),
|
|
40
|
+
joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
|
|
41
|
+
}),
|
|
42
|
+
wrappingInputRule({
|
|
43
|
+
find: /^([A-Z])\.\s$/,
|
|
44
|
+
type: this.type,
|
|
45
|
+
getAttributes: match => ({ start: match[1].charCodeAt(0) - 64, type: 'A' }),
|
|
46
|
+
joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
|
|
47
|
+
}),
|
|
48
|
+
wrappingInputRule({
|
|
49
|
+
find: /^([a-z])\.\s$/,
|
|
50
|
+
type: this.type,
|
|
51
|
+
getAttributes: match => ({ start: match[1].charCodeAt(0) - 96, type: 'a' }),
|
|
52
|
+
joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
|
|
53
|
+
}),
|
|
54
|
+
wrappingInputRule({
|
|
55
|
+
find: /^([ㄱ-ㅎ])\.\s$/,
|
|
56
|
+
type: this.type,
|
|
57
|
+
getAttributes: match => ({
|
|
58
|
+
start: 1 + ['ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'].indexOf(match[1]),
|
|
59
|
+
type: 'kors'
|
|
60
|
+
}),
|
|
61
|
+
joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
|
|
62
|
+
}),
|
|
63
|
+
wrappingInputRule({
|
|
64
|
+
find: /^([가-하])\.\s$/,
|
|
65
|
+
type: this.type,
|
|
66
|
+
getAttributes: match => ({
|
|
67
|
+
start: 1 + ['가', '나', '다', '라', '마', '바', '사', '아', '자', '차', '카', '타', '파', '하'].indexOf(match[1]),
|
|
68
|
+
type: 'korc'
|
|
69
|
+
}),
|
|
70
|
+
joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
|
|
71
|
+
}),
|
|
72
|
+
];
|
|
73
|
+
},
|
|
74
|
+
addCommands() {
|
|
75
|
+
return {
|
|
76
|
+
toggleOrderedList: (option) => (context) => {
|
|
77
|
+
return toggleList(this.name, this.options.itemTypeName, option)(context);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@counter-style kors {
|
|
2
|
+
system: cyclic;
|
|
3
|
+
symbols: "ㄱ." "ㄴ." "ㄷ." "ㄹ." "ㅁ." "ㅂ." "ㅅ." "ㅇ." "ㅈ." "ㅊ." "ㅋ." "ㅌ." "ㅍ." "ㅎ.";
|
|
4
|
+
suffix: " ";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@counter-style korc {
|
|
8
|
+
system: cyclic;
|
|
9
|
+
symbols: "가." "나." "다." "라." "마." "바." "사." "아." "자." "차." "카." "타." "파." "하.";
|
|
10
|
+
suffix: " ";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ProseMirror {
|
|
14
|
+
ol[type="kors"] {
|
|
15
|
+
list-style-type: kors;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
ol[type="korc"] {
|
|
19
|
+
list-style-type: korc;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { canJoin } from 'prosemirror-transform';
|
|
2
|
+
import { getNodeType, findParentNode, isList } from "@tiptap/core";
|
|
3
|
+
const joinListBackwards = (tr, listType) => {
|
|
4
|
+
const list = findParentNode(node => node.type === listType)(tr.selection);
|
|
5
|
+
if (!list)
|
|
6
|
+
return true;
|
|
7
|
+
const before = tr.doc.resolve(Math.max(0, list.pos - 1)).before(list.depth);
|
|
8
|
+
if (before === undefined)
|
|
9
|
+
return true;
|
|
10
|
+
const nodeBefore = tr.doc.nodeAt(before);
|
|
11
|
+
const canJoinBackwards = list.node.type === nodeBefore?.type && canJoin(tr.doc, list.pos);
|
|
12
|
+
if (!canJoinBackwards)
|
|
13
|
+
return true;
|
|
14
|
+
tr.join(list.pos);
|
|
15
|
+
return true;
|
|
16
|
+
};
|
|
17
|
+
const joinListForwards = (tr, listType) => {
|
|
18
|
+
const list = findParentNode(node => node.type === listType)(tr.selection);
|
|
19
|
+
if (!list)
|
|
20
|
+
return true;
|
|
21
|
+
const after = tr.doc.resolve(list.start).after(list.depth);
|
|
22
|
+
if (after === undefined)
|
|
23
|
+
return true;
|
|
24
|
+
const nodeAfter = tr.doc.nodeAt(after);
|
|
25
|
+
const canJoinForwards = list.node.type === nodeAfter?.type && canJoin(tr.doc, after);
|
|
26
|
+
if (!canJoinForwards)
|
|
27
|
+
return true;
|
|
28
|
+
tr.join(after);
|
|
29
|
+
return true;
|
|
30
|
+
};
|
|
31
|
+
export default (listTypeOrName, itemTypeOrName, attrs) => ({ editor, tr, state, dispatch, chain, commands, can }) => {
|
|
32
|
+
const { extensions } = editor.extensionManager;
|
|
33
|
+
const listType = getNodeType(listTypeOrName, state.schema);
|
|
34
|
+
const itemType = getNodeType(itemTypeOrName, state.schema);
|
|
35
|
+
const { selection } = state;
|
|
36
|
+
const { $from, $to } = selection;
|
|
37
|
+
const range = $from.blockRange($to);
|
|
38
|
+
if (!range)
|
|
39
|
+
return false;
|
|
40
|
+
const parentList = findParentNode(node => isList(node.type.name, extensions))(selection);
|
|
41
|
+
if (range.depth >= 1 && parentList && range.depth - parentList.depth <= 1) {
|
|
42
|
+
if (parentList.node.type === listType && parentList?.node?.attrs?.type === attrs?.type)
|
|
43
|
+
return commands.liftListItem(itemType);
|
|
44
|
+
if (isList(parentList.node.type.name, extensions)
|
|
45
|
+
&& listType.validContent(parentList.node.content)
|
|
46
|
+
&& dispatch)
|
|
47
|
+
return chain()
|
|
48
|
+
.command(() => {
|
|
49
|
+
tr.setNodeMarkup(parentList.pos, listType, attrs);
|
|
50
|
+
return true;
|
|
51
|
+
})
|
|
52
|
+
.command(() => joinListBackwards(tr, listType))
|
|
53
|
+
.command(() => joinListForwards(tr, listType))
|
|
54
|
+
.run();
|
|
55
|
+
}
|
|
56
|
+
return chain()
|
|
57
|
+
.command(() => {
|
|
58
|
+
const canWrapInList = can().wrapInList(listType, attrs);
|
|
59
|
+
if (canWrapInList)
|
|
60
|
+
return true;
|
|
61
|
+
return commands.clearNodes();
|
|
62
|
+
})
|
|
63
|
+
.wrapInList(listType, attrs)
|
|
64
|
+
.command(() => joinListBackwards(tr, listType))
|
|
65
|
+
.command(() => joinListForwards(tr, listType))
|
|
66
|
+
.run();
|
|
67
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isColumnSelected, isTableSelected } from "./util";
|
|
2
|
+
import { TableMap } from "prosemirror-tables";
|
|
3
|
+
export const deleteTable = ({ editor }) => {
|
|
4
|
+
const { selection } = editor.state;
|
|
5
|
+
if (!selection || !selection.$anchorCell)
|
|
6
|
+
return false;
|
|
7
|
+
if (isTableSelected(selection))
|
|
8
|
+
return editor.commands.deleteTable();
|
|
9
|
+
const { height, width } = TableMap.get(selection.$anchorCell.node(-1));
|
|
10
|
+
for (let i = width - 1; i >= 0; i--) {
|
|
11
|
+
if (isColumnSelected(i)(selection)) {
|
|
12
|
+
editor.commands.deleteColumn();
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
for (let i = height - 1; i >= 0; i--) {
|
|
17
|
+
if (isColumnSelected(i)(selection)) {
|
|
18
|
+
editor.commands.deleteRow();
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
};
|
|
24
|
+
export default deleteTable;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import BuiltInTable from '@tiptap/extension-table';
|
|
2
|
+
import { Plugin } from 'prosemirror-state';
|
|
3
|
+
import { tableEditing, columnResizing } from 'prosemirror-tables';
|
|
4
|
+
import { Decoration, DecorationSet } from 'prosemirror-view';
|
|
5
|
+
import deleteTable from './deleteTable';
|
|
6
|
+
import './style.scss';
|
|
7
|
+
export default BuiltInTable.extend({
|
|
8
|
+
renderHTML() {
|
|
9
|
+
return [
|
|
10
|
+
'div',
|
|
11
|
+
{ class: 'node-table' },
|
|
12
|
+
['div', { class: `scrollable` }, ['table', { class: `as-table render-wrapper` }, ['tbody', 0]]],
|
|
13
|
+
];
|
|
14
|
+
},
|
|
15
|
+
addProseMirrorPlugins() {
|
|
16
|
+
const { isEditable } = this.editor;
|
|
17
|
+
return [
|
|
18
|
+
tableEditing(),
|
|
19
|
+
columnResizing({}),
|
|
20
|
+
new Plugin({
|
|
21
|
+
props: {
|
|
22
|
+
decorations: (state) => {
|
|
23
|
+
const { doc } = state;
|
|
24
|
+
const decorations = [];
|
|
25
|
+
let index = 0;
|
|
26
|
+
doc.descendants((node, pos) => {
|
|
27
|
+
if (node.type.name !== this.name)
|
|
28
|
+
return;
|
|
29
|
+
const elements = document.getElementsByClassName('as-table');
|
|
30
|
+
const table = elements[index];
|
|
31
|
+
if (!table)
|
|
32
|
+
return;
|
|
33
|
+
if (!isEditable)
|
|
34
|
+
table.classList.add('is-readonly');
|
|
35
|
+
const element = table.parentElement;
|
|
36
|
+
const shadowRight = !!(element && element.scrollWidth > element.clientWidth);
|
|
37
|
+
if (shadowRight)
|
|
38
|
+
decorations.push(Decoration.widget(pos + 1, () => {
|
|
39
|
+
const shadow = document.createElement('div');
|
|
40
|
+
shadow.className = `scrollable-shadow right ${isEditable ? 'is-editable' : ''}`;
|
|
41
|
+
return shadow;
|
|
42
|
+
}));
|
|
43
|
+
index++;
|
|
44
|
+
});
|
|
45
|
+
return DecorationSet.create(doc, decorations);
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
];
|
|
50
|
+
},
|
|
51
|
+
addKeyboardShortcuts() {
|
|
52
|
+
return {
|
|
53
|
+
Tab: () => {
|
|
54
|
+
if (this.editor.commands.goToNextCell()) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (!this.editor.can().addRowAfter()) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return this.editor.chain().addRowAfter().goToNextCell().run();
|
|
61
|
+
},
|
|
62
|
+
'Shift-Tab': () => this.editor.commands.goToPreviousCell(),
|
|
63
|
+
Backspace: deleteTable,
|
|
64
|
+
'Mod-Backspace': deleteTable,
|
|
65
|
+
Delete: deleteTable,
|
|
66
|
+
'Mod-Delete': deleteTable,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
}).configure({ resizable: true, lastColumnResizable: false });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.ProseMirror table {
|
|
2
|
+
td, th {
|
|
3
|
+
position: relative;
|
|
4
|
+
min-width: 100px;
|
|
5
|
+
padding: 4px 8px;
|
|
6
|
+
text-align: left;
|
|
7
|
+
vertical-align: center;
|
|
8
|
+
border: 1px solid var(--primary-light3) !important;
|
|
9
|
+
transition: all 0.1s ease-in-out;
|
|
10
|
+
font-weight: normal;
|
|
11
|
+
|
|
12
|
+
> * {
|
|
13
|
+
margin-bottom: 0;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
$grip-margin: 3px;
|
|
2
|
+
|
|
3
|
+
.ProseMirror table {
|
|
4
|
+
.grip-column {
|
|
5
|
+
position: absolute;
|
|
6
|
+
top: -0.7em;
|
|
7
|
+
left: 0;
|
|
8
|
+
z-index: 10;
|
|
9
|
+
display: block;
|
|
10
|
+
width: calc(100% - 2 * #{$grip-margin});
|
|
11
|
+
height: 0.4em;
|
|
12
|
+
margin: 0 $grip-margin 3px $grip-margin;
|
|
13
|
+
background: var(--primary-light3);
|
|
14
|
+
opacity: 0;
|
|
15
|
+
border-radius: 10px;
|
|
16
|
+
|
|
17
|
+
transition: all 0.1s ease-in-out;
|
|
18
|
+
|
|
19
|
+
&:hover,
|
|
20
|
+
&.selected {
|
|
21
|
+
background: var(--primary-light6);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.grip-row {
|
|
26
|
+
position: absolute;
|
|
27
|
+
top: 0;
|
|
28
|
+
left: -0.7em;
|
|
29
|
+
z-index: 10;
|
|
30
|
+
display: block;
|
|
31
|
+
width: 0.4em;
|
|
32
|
+
height: calc(100% - 2 * #{$grip-margin});
|
|
33
|
+
margin: $grip-margin 3px $grip-margin 0;
|
|
34
|
+
background: var(--primary-light3);
|
|
35
|
+
opacity: 0;
|
|
36
|
+
border-radius: 10px;
|
|
37
|
+
|
|
38
|
+
transition: all 0.1s ease-in-out;
|
|
39
|
+
|
|
40
|
+
&:hover,
|
|
41
|
+
&.selected {
|
|
42
|
+
background: var(--primary-light6);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.grip-table {
|
|
47
|
+
position: absolute;
|
|
48
|
+
top: -0.8em;
|
|
49
|
+
left: -0.8em;
|
|
50
|
+
z-index: 10;
|
|
51
|
+
display: block;
|
|
52
|
+
width: 0.6em;
|
|
53
|
+
height: 0.6em;
|
|
54
|
+
background: var(--primary-light3);
|
|
55
|
+
border-radius: 50%;
|
|
56
|
+
opacity: 0;
|
|
57
|
+
|
|
58
|
+
transition: all 0.1s ease-in-out;
|
|
59
|
+
|
|
60
|
+
&:hover,
|
|
61
|
+
&.selected {
|
|
62
|
+
background: var(--primary-light6);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.editor & {
|
|
67
|
+
.grip-column, .grip-row, .grip-table {
|
|
68
|
+
opacity: 1;
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.ProseMirror {
|
|
2
|
+
table .column-resize-handle {
|
|
3
|
+
position: absolute;
|
|
4
|
+
top: 0;
|
|
5
|
+
right: -2px;
|
|
6
|
+
bottom: -2px;
|
|
7
|
+
width: 4px;
|
|
8
|
+
pointer-events: none;
|
|
9
|
+
background-color: var(--primary-light6);
|
|
10
|
+
opacity: 0;
|
|
11
|
+
|
|
12
|
+
.editor & {
|
|
13
|
+
opacity: 1;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
&.resize-cursor {
|
|
18
|
+
pointer-events: none;
|
|
19
|
+
|
|
20
|
+
.editor & {
|
|
21
|
+
pointer-events: initial;
|
|
22
|
+
cursor: ew-resize;
|
|
23
|
+
cursor: col-resize; /* stylelint-disable declaration-block-no-duplicate-properties */
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|