@zipify/wysiwyg 3.1.2 → 3.1.3-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/cli.js +3 -3
- package/dist/wysiwyg.mjs +2089 -2015
- package/lib/extensions/Link.js +15 -6
- package/lib/extensions/core/index.js +1 -1
- package/lib/extensions/proseMirror/PasteLinkPlugin.js +29 -0
- package/lib/extensions/{core/plugins → proseMirror}/PastePlugin.js +5 -5
- package/lib/extensions/{core/plugins → proseMirror}/PlaceholderPlugin.js +2 -2
- package/lib/extensions/{core/plugins → proseMirror}/ProseMirrorPlugin.js +9 -2
- package/lib/extensions/{core/plugins → proseMirror}/index.js +1 -0
- package/lib/services/NodeFactory.js +1 -1
- package/lib/services/NodeSelector.js +50 -0
- package/lib/services/__tests__/NodeSelector.test.js +129 -0
- package/lib/services/index.js +1 -0
- package/package.json +1 -1
package/lib/extensions/Link.js
CHANGED
|
@@ -3,14 +3,17 @@ import { computed, unref } from 'vue';
|
|
|
3
3
|
import { createCommand } from '../utils';
|
|
4
4
|
import { LinkDestinations, LinkTargets, TextSettings } from '../enums';
|
|
5
5
|
import { NodeFactory } from '../services';
|
|
6
|
+
import { PasteLinkPlugin } from './proseMirror';
|
|
6
7
|
|
|
7
8
|
export const Link = Base.extend({
|
|
8
9
|
name: TextSettings.LINK,
|
|
10
|
+
addPasteRules: null,
|
|
9
11
|
|
|
10
12
|
addOptions() {
|
|
11
13
|
return {
|
|
12
14
|
...this.parent?.(),
|
|
13
|
-
openOnClick: false
|
|
15
|
+
openOnClick: false,
|
|
16
|
+
linkOnPaste: false
|
|
14
17
|
};
|
|
15
18
|
},
|
|
16
19
|
|
|
@@ -52,8 +55,6 @@ export const Link = Base.extend({
|
|
|
52
55
|
|
|
53
56
|
addCommands() {
|
|
54
57
|
return {
|
|
55
|
-
...this.parent?.(),
|
|
56
|
-
|
|
57
58
|
applyLink: createCommand(({ commands, chain }, attributes) => {
|
|
58
59
|
commands.setMeta('preventAutolink', true);
|
|
59
60
|
|
|
@@ -67,7 +68,9 @@ export const Link = Base.extend({
|
|
|
67
68
|
.applyMark(this.name, attributes)
|
|
68
69
|
.expandSelectionToLink()
|
|
69
70
|
.command(({ tr }) => {
|
|
70
|
-
|
|
71
|
+
if (attributes.text) {
|
|
72
|
+
tr.insertText(attributes.text, tr.selection.from, tr.selection.to);
|
|
73
|
+
}
|
|
71
74
|
return true;
|
|
72
75
|
})
|
|
73
76
|
.run();
|
|
@@ -82,11 +85,17 @@ export const Link = Base.extend({
|
|
|
82
85
|
};
|
|
83
86
|
},
|
|
84
87
|
|
|
88
|
+
addProseMirrorPlugins() {
|
|
89
|
+
return [
|
|
90
|
+
...this.parent(),
|
|
91
|
+
PasteLinkPlugin.create(this.editor)
|
|
92
|
+
];
|
|
93
|
+
},
|
|
94
|
+
|
|
85
95
|
renderHTML({ HTMLAttributes: attrs }) {
|
|
86
96
|
const href = attrs.destination === LinkDestinations.BLOCK ? `#${attrs.href}` : attrs.href;
|
|
87
97
|
const presetClass = unref(this.options.basePresetClass) + unref(this.options.preset).id;
|
|
88
|
-
const
|
|
89
|
-
const linkAttrs = { href, target: attrs.target, class: classes };
|
|
98
|
+
const linkAttrs = { href, target: attrs.target, class: `${presetClass} zw-style` };
|
|
90
99
|
|
|
91
100
|
return ['a', linkAttrs, 0];
|
|
92
101
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Extension } from '@tiptap/vue-2';
|
|
2
2
|
import Text from '@tiptap/extension-text';
|
|
3
3
|
import History from '@tiptap/extension-history';
|
|
4
|
+
import { PastePlugin, PlaceholderPlugin } from '../proseMirror';
|
|
4
5
|
import { NodeProcessor } from './NodeProcessor';
|
|
5
6
|
import { TextProcessor } from './TextProcessor';
|
|
6
7
|
import { SelectionProcessor } from './SelectionProcessor';
|
|
7
8
|
import { Document } from './Document';
|
|
8
9
|
import { Paragraph } from './Paragraph';
|
|
9
10
|
import { Heading } from './Heading';
|
|
10
|
-
import { PastePlugin, PlaceholderPlugin } from './plugins';
|
|
11
11
|
|
|
12
12
|
const ProseMirrorPlugins = Extension.create({
|
|
13
13
|
name: 'prose_mirror_plugins',
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { test as testLink } from 'linkifyjs';
|
|
2
|
+
import { NodeSelector } from '../../services';
|
|
3
|
+
import { NodeTypes, TextSettings } from '../../enums';
|
|
4
|
+
import { ProseMirrorPlugin } from './ProseMirrorPlugin';
|
|
5
|
+
|
|
6
|
+
export class PasteLinkPlugin extends ProseMirrorPlugin {
|
|
7
|
+
addProps() {
|
|
8
|
+
return { handlePaste: this._handlePaste };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
_handlePaste(view, event, slice) {
|
|
12
|
+
if (view.state.selection.empty) return false;
|
|
13
|
+
|
|
14
|
+
const textContent = slice.content.textBetween(0, slice.content.size).trim();
|
|
15
|
+
const isLink = testLink(textContent);
|
|
16
|
+
|
|
17
|
+
if (!textContent || !isLink) return false;
|
|
18
|
+
|
|
19
|
+
const pastingLink = NodeSelector.query(slice.content, {
|
|
20
|
+
typeName: NodeTypes.TEXT,
|
|
21
|
+
mark: { typeName: TextSettings.LINK },
|
|
22
|
+
getMark: { typeName: TextSettings.LINK }
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
this.editor.commands.applyLink(pastingLink.attrs);
|
|
26
|
+
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { ContentNormalizer } from '
|
|
2
|
-
import { NodeTypes } from '
|
|
1
|
+
import { ContentNormalizer } from '../../services';
|
|
2
|
+
import { NodeTypes } from '../../enums';
|
|
3
3
|
import { ProseMirrorPlugin } from './ProseMirrorPlugin';
|
|
4
4
|
|
|
5
5
|
export class PastePlugin extends ProseMirrorPlugin {
|
|
6
|
-
|
|
6
|
+
addProps() {
|
|
7
7
|
return {
|
|
8
|
-
transformPastedHTML: this._transformPastedHTML
|
|
9
|
-
handlePaste: this._handlePaste
|
|
8
|
+
transformPastedHTML: this._transformPastedHTML,
|
|
9
|
+
handlePaste: this._handlePaste
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -2,8 +2,8 @@ import { Decoration, DecorationSet } from 'prosemirror-view';
|
|
|
2
2
|
import { ProseMirrorPlugin } from './ProseMirrorPlugin';
|
|
3
3
|
|
|
4
4
|
export class PlaceholderPlugin extends ProseMirrorPlugin {
|
|
5
|
-
|
|
6
|
-
return { decorations: this._buildDecorations
|
|
5
|
+
addProps() {
|
|
6
|
+
return { decorations: this._buildDecorations };
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
_buildDecorations({ doc }) {
|
|
@@ -6,7 +6,7 @@ export class ProseMirrorPlugin {
|
|
|
6
6
|
|
|
7
7
|
return new Plugin({
|
|
8
8
|
key: new PluginKey(this.name),
|
|
9
|
-
props: plugin.
|
|
9
|
+
props: plugin._buildProps()
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -15,7 +15,14 @@ export class ProseMirrorPlugin {
|
|
|
15
15
|
this.editor = editor;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
_buildProps() {
|
|
19
|
+
const props = Object.entries(this.addProps());
|
|
20
|
+
const bound = props.map(([name, handler]) => [name, handler.bind(this)]);
|
|
21
|
+
|
|
22
|
+
return Object.fromEntries(bound);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
addProps() {
|
|
19
26
|
return {};
|
|
20
27
|
}
|
|
21
28
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export class NodeSelector {
|
|
2
|
+
static _instance;
|
|
3
|
+
|
|
4
|
+
static get instance() {
|
|
5
|
+
this._instance ??= new NodeSelector();
|
|
6
|
+
return this._instance;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static query(containerNode, selector) {
|
|
10
|
+
return this.instance.query(containerNode, selector);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
query(containerNode, selector) {
|
|
14
|
+
let found = null;
|
|
15
|
+
|
|
16
|
+
containerNode.descendants((node) => {
|
|
17
|
+
if (found) return false;
|
|
18
|
+
|
|
19
|
+
if (this.matchNode(node, selector)) {
|
|
20
|
+
found = node;
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (!found) return null;
|
|
26
|
+
if (!selector.getMark) return found;
|
|
27
|
+
|
|
28
|
+
return this.getMark(found, selector.getMark);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
matchNode(node, selector) {
|
|
32
|
+
if (selector.typeName && selector.typeName !== node.type.name) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (selector.mark && !this.getMark(node, selector.mark)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getMark(node, selector) {
|
|
44
|
+
return node.marks.find((mark) => this.matchMark(mark, selector)) || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
matchMark(mark, selector) {
|
|
48
|
+
return mark.type.name === selector.typeName;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Schema } from 'prosemirror-model';
|
|
2
|
+
import { NodeFactory } from '../NodeFactory';
|
|
3
|
+
import { NodeSelector } from '../NodeSelector';
|
|
4
|
+
import { NodeTypes, TextSettings } from '../../enums';
|
|
5
|
+
|
|
6
|
+
const createSchema = () => new Schema({
|
|
7
|
+
nodes: {
|
|
8
|
+
[NodeTypes.DOCUMENT]: { content: 'paragraph+' },
|
|
9
|
+
[NodeTypes.PARAGRAPH]: { content: 'text*' },
|
|
10
|
+
[NodeTypes.TEXT]: { inline: true }
|
|
11
|
+
},
|
|
12
|
+
marks: {
|
|
13
|
+
[TextSettings.FONT_SIZE]: {}
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const createSelector = () => new NodeSelector();
|
|
18
|
+
|
|
19
|
+
describe('match node', () => {
|
|
20
|
+
test('should match node by type name', () => {
|
|
21
|
+
const schema = createSchema();
|
|
22
|
+
const selector = createSelector();
|
|
23
|
+
const node = schema.nodeFromJSON(NodeFactory.text('lorem ipsum'));
|
|
24
|
+
const isMatch = selector.matchNode(node, { typeName: NodeTypes.TEXT });
|
|
25
|
+
|
|
26
|
+
expect(isMatch).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('should not match node by type name', () => {
|
|
30
|
+
const schema = createSchema();
|
|
31
|
+
const selector = createSelector();
|
|
32
|
+
const node = schema.nodeFromJSON(NodeFactory.text('lorem ipsum'));
|
|
33
|
+
const isMatch = selector.matchNode(node, { typeName: NodeTypes.PARAGRAPH });
|
|
34
|
+
|
|
35
|
+
expect(isMatch).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('should match node with mark', () => {
|
|
39
|
+
const schema = createSchema();
|
|
40
|
+
const selector = createSelector();
|
|
41
|
+
|
|
42
|
+
const node = schema.nodeFromJSON(NodeFactory.text('lorem ipsum', [
|
|
43
|
+
NodeFactory.mark(TextSettings.FONT_SIZE, {})
|
|
44
|
+
]));
|
|
45
|
+
|
|
46
|
+
const isMatch = selector.matchNode(node, {
|
|
47
|
+
mark: { typeName: TextSettings.FONT_SIZE }
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(isMatch).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('should not match node with another mark', () => {
|
|
54
|
+
const schema = createSchema();
|
|
55
|
+
const selector = createSelector();
|
|
56
|
+
|
|
57
|
+
const node = schema.nodeFromJSON(NodeFactory.text('lorem ipsum', [
|
|
58
|
+
NodeFactory.mark(TextSettings.FONT_SIZE, {})
|
|
59
|
+
]));
|
|
60
|
+
|
|
61
|
+
const isMatch = selector.matchNode(node, {
|
|
62
|
+
mark: { typeName: TextSettings.FONT_FAMILY }
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(isMatch).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('match mark', () => {
|
|
70
|
+
test('should match mark by type name', () => {
|
|
71
|
+
const schema = createSchema();
|
|
72
|
+
const selector = createSelector();
|
|
73
|
+
const mark = schema.markFromJSON(NodeFactory.mark(TextSettings.FONT_SIZE, {}));
|
|
74
|
+
const isMatch = selector.matchMark(mark, { typeName: TextSettings.FONT_SIZE });
|
|
75
|
+
|
|
76
|
+
expect(isMatch).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('should not match mark by type name', () => {
|
|
80
|
+
const schema = createSchema();
|
|
81
|
+
const selector = createSelector();
|
|
82
|
+
const mark = schema.markFromJSON(NodeFactory.mark(TextSettings.FONT_SIZE, {}));
|
|
83
|
+
const isMatch = selector.matchMark(mark, { typeName: TextSettings.FONT_FAMILY });
|
|
84
|
+
|
|
85
|
+
expect(isMatch).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('query node', () => {
|
|
90
|
+
test('should find node', () => {
|
|
91
|
+
const schema = createSchema();
|
|
92
|
+
const selector = createSelector();
|
|
93
|
+
|
|
94
|
+
const doc = schema.nodeFromJSON(NodeFactory.doc([
|
|
95
|
+
NodeFactory.paragraph('lorem ipsum')
|
|
96
|
+
]));
|
|
97
|
+
|
|
98
|
+
const textNode = selector.query(doc, { typeName: NodeTypes.TEXT });
|
|
99
|
+
|
|
100
|
+
expect(textNode.text).toBe('lorem ipsum');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('should find first matching node', () => {
|
|
104
|
+
const schema = createSchema();
|
|
105
|
+
const selector = createSelector();
|
|
106
|
+
|
|
107
|
+
const doc = schema.nodeFromJSON(NodeFactory.doc([
|
|
108
|
+
NodeFactory.paragraph('lorem ipsum 1'),
|
|
109
|
+
NodeFactory.paragraph('lorem ipsum 2')
|
|
110
|
+
]));
|
|
111
|
+
|
|
112
|
+
const textNode = selector.query(doc, { typeName: NodeTypes.TEXT });
|
|
113
|
+
|
|
114
|
+
expect(textNode.text).toBe('lorem ipsum 1');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('should not find any node', () => {
|
|
118
|
+
const schema = createSchema();
|
|
119
|
+
const selector = createSelector();
|
|
120
|
+
|
|
121
|
+
const doc = schema.nodeFromJSON(NodeFactory.doc([
|
|
122
|
+
NodeFactory.paragraph('lorem ipsum 1')
|
|
123
|
+
]));
|
|
124
|
+
|
|
125
|
+
const node = selector.query(doc, { typeName: NodeTypes.LIST });
|
|
126
|
+
|
|
127
|
+
expect(node).toBeNull();
|
|
128
|
+
});
|
|
129
|
+
});
|
package/lib/services/index.js
CHANGED