@zipify/wysiwyg 2.0.0-2 → 2.0.0-4
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 +2 -2
- package/dist/wysiwyg.mjs +1393 -994
- package/example/ExampleApp.vue +10 -1
- package/lib/entry-cli.js +2 -1
- package/lib/enums/TextSettings.js +1 -1
- package/lib/extensions/BackgroundColor.js +1 -2
- package/lib/extensions/StylePreset.js +1 -1
- package/lib/extensions/__tests__/__snapshots__/BackgroundColor.test.js.snap +25 -25
- package/lib/extensions/core/NodeProcessor.js +12 -1
- package/lib/extensions/list/List.js +24 -7
- package/lib/extensions/list/__tests__/List.test.js +26 -17
- package/lib/extensions/list/__tests__/__snapshots__/List.test.js.snap +36 -36
- package/lib/services/normalizer/HtmlNormalizer.js +23 -0
- package/lib/services/normalizer/JsonNormalizer.js +1 -1
- package/lib/services/normalizer/__tests__/JsonNormalizer.test.js +15 -0
- package/lib/services/normalizer/__tests__/__snapshots__/JsonNormalizer.test.js.snap +37 -0
- package/lib/utils/__tests__/isNodeFullySelected.test.js +21 -30
- package/lib/utils/copyMark.js +5 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/isNodeFullySelected.js +4 -11
- package/package.json +1 -1
package/example/ExampleApp.vue
CHANGED
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
</option>
|
|
13
13
|
</select>
|
|
14
14
|
|
|
15
|
+
<button type="button" class="zw-load-content" @click="saveContent">
|
|
16
|
+
Save Content
|
|
17
|
+
</button>
|
|
18
|
+
|
|
15
19
|
<button type="button" class="zw-load-content" @click="loadContent">
|
|
16
20
|
Load Content
|
|
17
21
|
</button>
|
|
@@ -45,7 +49,7 @@
|
|
|
45
49
|
</template>
|
|
46
50
|
|
|
47
51
|
<script>
|
|
48
|
-
import { computed, onMounted, ref } from 'vue';
|
|
52
|
+
import { computed, onMounted, ref, unref } from 'vue';
|
|
49
53
|
import { Wysiwyg } from '../lib/entry-lib';
|
|
50
54
|
import { FONTS } from './fonts';
|
|
51
55
|
import { PRESETS, renderPresetVariable } from './presets';
|
|
@@ -109,6 +113,10 @@ export default {
|
|
|
109
113
|
window.location.reload();
|
|
110
114
|
}
|
|
111
115
|
|
|
116
|
+
function saveContent() {
|
|
117
|
+
sessionStorage.setItem('wswg-data', JSON.stringify(unref(content)));
|
|
118
|
+
}
|
|
119
|
+
|
|
112
120
|
document.addEventListener('click', (event) => {
|
|
113
121
|
isActive.value = wswgRef.value.$el.contains(event.target);
|
|
114
122
|
});
|
|
@@ -121,6 +129,7 @@ export default {
|
|
|
121
129
|
favoriteColors,
|
|
122
130
|
updateFavoriteColors,
|
|
123
131
|
loadContent,
|
|
132
|
+
saveContent,
|
|
124
133
|
device,
|
|
125
134
|
updatedAt,
|
|
126
135
|
presets,
|
package/lib/entry-cli.js
CHANGED
|
@@ -9,6 +9,7 @@ function rubifyJSON(object) {
|
|
|
9
9
|
const json = JSON.stringify(object, skipNullValue, 2);
|
|
10
10
|
|
|
11
11
|
return json
|
|
12
|
+
.replace(/'/g, '\'')
|
|
12
13
|
.replace(/^[\t ]*"[^:\n\r]+(?<!\\)":/gm, (match) => match.replace(/"/g, ''))
|
|
13
14
|
.replace(/: "(.+)"([,\n])/g, ': \'$1\'$2');
|
|
14
15
|
}
|
|
@@ -19,7 +20,7 @@ program.command('to-json')
|
|
|
19
20
|
.action((html, { config }) => {
|
|
20
21
|
const configPath = resolve(process.cwd(), config);
|
|
21
22
|
const serializer = ContentSerializer.build(require(configPath).editor);
|
|
22
|
-
const json = rubifyJSON(serializer.toJSON(html));
|
|
23
|
+
const json = rubifyJSON(serializer.toJSON(html.replace(/\\"/g, '"')));
|
|
23
24
|
|
|
24
25
|
// eslint-disable-next-line no-console
|
|
25
26
|
console.log(json);
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Mark } from '@tiptap/vue-2';
|
|
2
2
|
import { convertColor, createCommand, renderMark } from '../utils';
|
|
3
|
-
import {
|
|
3
|
+
import { TextSettings } from '../enums';
|
|
4
4
|
|
|
5
5
|
export const BackgroundColor = Mark.create({
|
|
6
6
|
name: TextSettings.BACKGROUND_COLOR,
|
|
7
|
-
group: MarkGroups.SETTINGS,
|
|
8
7
|
|
|
9
8
|
addAttributes: () => ({
|
|
10
9
|
value: { required: true }
|
|
@@ -6,18 +6,18 @@ Object {
|
|
|
6
6
|
Object {
|
|
7
7
|
"content": Array [
|
|
8
8
|
Object {
|
|
9
|
+
"marks": Array [
|
|
10
|
+
Object {
|
|
11
|
+
"attrs": Object {
|
|
12
|
+
"value": "green",
|
|
13
|
+
},
|
|
14
|
+
"type": "background_color",
|
|
15
|
+
},
|
|
16
|
+
],
|
|
9
17
|
"text": "hello world",
|
|
10
18
|
"type": "text",
|
|
11
19
|
},
|
|
12
20
|
],
|
|
13
|
-
"marks": Array [
|
|
14
|
-
Object {
|
|
15
|
-
"attrs": Object {
|
|
16
|
-
"value": "green",
|
|
17
|
-
},
|
|
18
|
-
"type": "background_color",
|
|
19
|
-
},
|
|
20
|
-
],
|
|
21
21
|
"type": "paragraph",
|
|
22
22
|
},
|
|
23
23
|
],
|
|
@@ -31,18 +31,18 @@ Object {
|
|
|
31
31
|
Object {
|
|
32
32
|
"content": Array [
|
|
33
33
|
Object {
|
|
34
|
+
"marks": Array [
|
|
35
|
+
Object {
|
|
36
|
+
"attrs": Object {
|
|
37
|
+
"value": "#FF0000",
|
|
38
|
+
},
|
|
39
|
+
"type": "background_color",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
34
42
|
"text": "test",
|
|
35
43
|
"type": "text",
|
|
36
44
|
},
|
|
37
45
|
],
|
|
38
|
-
"marks": Array [
|
|
39
|
-
Object {
|
|
40
|
-
"attrs": Object {
|
|
41
|
-
"value": "#FF0000",
|
|
42
|
-
},
|
|
43
|
-
"type": "background_color",
|
|
44
|
-
},
|
|
45
|
-
],
|
|
46
46
|
"type": "paragraph",
|
|
47
47
|
},
|
|
48
48
|
],
|
|
@@ -126,18 +126,18 @@ Object {
|
|
|
126
126
|
"type": "text",
|
|
127
127
|
},
|
|
128
128
|
Object {
|
|
129
|
+
"marks": Array [
|
|
130
|
+
Object {
|
|
131
|
+
"attrs": Object {
|
|
132
|
+
"value": "#FF0000",
|
|
133
|
+
},
|
|
134
|
+
"type": "background_color",
|
|
135
|
+
},
|
|
136
|
+
],
|
|
129
137
|
"text": " ipsum",
|
|
130
138
|
"type": "text",
|
|
131
139
|
},
|
|
132
140
|
],
|
|
133
|
-
"marks": Array [
|
|
134
|
-
Object {
|
|
135
|
-
"attrs": Object {
|
|
136
|
-
"value": "#FF0000",
|
|
137
|
-
},
|
|
138
|
-
"type": "background_color",
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
141
|
"type": "paragraph",
|
|
142
142
|
},
|
|
143
143
|
],
|
|
@@ -145,4 +145,4 @@ Object {
|
|
|
145
145
|
}
|
|
146
146
|
`;
|
|
147
147
|
|
|
148
|
-
exports[`rendering should render html 1`] = `"<span style="--zw-background-color:green;" class="zw-style"
|
|
148
|
+
exports[`rendering should render html 1`] = `"<p class="zw-style"><span style="--zw-background-color:green;" class="zw-style">hello world</span></p>"`;
|
|
@@ -66,7 +66,7 @@ export const NodeProcessor = Extension.create({
|
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
if (isNodeFullySelected(
|
|
69
|
+
if (isNodeFullySelected(tr.doc, tr.selection, node, position)) {
|
|
70
70
|
tr.step(new AddNodeMarkStep(position, applyingMark));
|
|
71
71
|
}
|
|
72
72
|
});
|
|
@@ -117,6 +117,17 @@ export const NodeProcessor = Extension.create({
|
|
|
117
117
|
});
|
|
118
118
|
}),
|
|
119
119
|
|
|
120
|
+
removeAllMarks: createCommand(({ state, commands }) => {
|
|
121
|
+
const { tr, doc } = state;
|
|
122
|
+
const { from, to } = tr.selection;
|
|
123
|
+
|
|
124
|
+
doc.nodesBetween(from, to, (node, position) => {
|
|
125
|
+
for (const mark of node.marks) {
|
|
126
|
+
commands._removeNodeMark({ tr, node, position, mark });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}),
|
|
130
|
+
|
|
120
131
|
removeMarks: createCommand(({ state, commands }, marks) => {
|
|
121
132
|
const { tr, doc } = state;
|
|
122
133
|
const { from, to } = tr.selection;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Node, wrappingInputRule } from '@tiptap/vue-2';
|
|
2
2
|
import { computed, unref } from 'vue';
|
|
3
|
-
import { createCommand } from '../../utils';
|
|
3
|
+
import { copyMark, createCommand } from '../../utils';
|
|
4
4
|
import { ListTypes, MarkGroups, NodeTypes } from '../../enums';
|
|
5
|
+
import { AddNodeMarkStep } from '../core/steps';
|
|
5
6
|
import { ListItem } from './ListItem';
|
|
6
7
|
|
|
7
8
|
export const List = Node.create({
|
|
@@ -79,21 +80,37 @@ export const List = Node.create({
|
|
|
79
80
|
|
|
80
81
|
// Remove List
|
|
81
82
|
if (currentType === type) {
|
|
82
|
-
commands.
|
|
83
|
+
commands.removeList();
|
|
83
84
|
return;
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
return chain().applyDefaultPreset()._addList(type).run();
|
|
87
|
-
}),
|
|
88
|
-
|
|
89
|
-
_addList: createCommand(({ chain }, type) => {
|
|
90
87
|
return chain()
|
|
88
|
+
.applyDefaultPreset()
|
|
91
89
|
.toggleList(NodeTypes.LIST, NodeTypes.LIST_ITEM)
|
|
92
90
|
.setBlockAttributes('bullet', { type })
|
|
93
91
|
.run();
|
|
94
92
|
}),
|
|
95
93
|
|
|
96
|
-
removeList: createCommand(({ commands }) => {
|
|
94
|
+
removeList: createCommand(({ commands, state }) => {
|
|
95
|
+
const { tr, doc, selection } = state;
|
|
96
|
+
const from = selection.$from.start();
|
|
97
|
+
const to = selection.$to.end();
|
|
98
|
+
|
|
99
|
+
doc.nodesBetween(from, to, (node, position, parent) => {
|
|
100
|
+
if ([NodeTypes.LIST, NodeTypes.LIST_ITEM].includes(node.type.name)) return;
|
|
101
|
+
if (parent.type.name !== NodeTypes.LIST_ITEM) return false;
|
|
102
|
+
|
|
103
|
+
const addingMarks = parent.marks.filter(function (mark) {
|
|
104
|
+
return !mark.type.isInSet(node.marks);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
for (const mark of addingMarks) {
|
|
108
|
+
tr.step(new AddNodeMarkStep(position, copyMark(mark)));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return false;
|
|
112
|
+
});
|
|
113
|
+
|
|
97
114
|
commands.liftListItem(NodeTypes.LIST_ITEM);
|
|
98
115
|
})
|
|
99
116
|
};
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import { Editor } from '@tiptap/vue-2';
|
|
1
|
+
import { Editor, Mark } from '@tiptap/vue-2';
|
|
2
2
|
import { buildTestExtensions } from '../../../__tests__/utils';
|
|
3
|
-
import { ListTypes } from '../../../enums';
|
|
3
|
+
import { ListTypes, TextSettings } from '../../../enums';
|
|
4
4
|
import { StylePreset } from '../../StylePreset';
|
|
5
5
|
import { ContentNormalizer, NodeFactory } from '../../../services';
|
|
6
6
|
import { List } from '../List';
|
|
7
7
|
|
|
8
|
+
const MockFontWeight = Mark.create({
|
|
9
|
+
name: TextSettings.FONT_WEIGHT,
|
|
10
|
+
renderHTML: () => ['span', {}, 0],
|
|
11
|
+
addAttributes: () => ({ value: { required: true } })
|
|
12
|
+
});
|
|
13
|
+
|
|
8
14
|
function createEditor({ content }) {
|
|
9
15
|
return new Editor({
|
|
10
16
|
content: ContentNormalizer.normalize(content),
|
|
@@ -24,7 +30,8 @@ function createEditor({ content }) {
|
|
|
24
30
|
],
|
|
25
31
|
defaultId: 'regular-1',
|
|
26
32
|
baseClass: 'zw ts-'
|
|
27
|
-
})
|
|
33
|
+
}),
|
|
34
|
+
MockFontWeight
|
|
28
35
|
]
|
|
29
36
|
})
|
|
30
37
|
});
|
|
@@ -73,7 +80,7 @@ describe('apply list', () => {
|
|
|
73
80
|
expect(editor.getJSON()).toMatchSnapshot();
|
|
74
81
|
});
|
|
75
82
|
|
|
76
|
-
test('should
|
|
83
|
+
test('should toggle list', () => {
|
|
77
84
|
const editor = createEditor({
|
|
78
85
|
content: NodeFactory.doc([
|
|
79
86
|
NodeFactory.list(ListTypes.LATIN, [
|
|
@@ -84,12 +91,12 @@ describe('apply list', () => {
|
|
|
84
91
|
});
|
|
85
92
|
|
|
86
93
|
editor.commands.selectAll();
|
|
87
|
-
editor.commands.
|
|
94
|
+
editor.commands.applyList(ListTypes.LATIN);
|
|
88
95
|
|
|
89
96
|
expect(editor.getJSON()).toMatchSnapshot();
|
|
90
97
|
});
|
|
91
98
|
|
|
92
|
-
test('should
|
|
99
|
+
test('should change list type', () => {
|
|
93
100
|
const editor = createEditor({
|
|
94
101
|
content: NodeFactory.doc([
|
|
95
102
|
NodeFactory.list(ListTypes.LATIN, [
|
|
@@ -100,37 +107,39 @@ describe('apply list', () => {
|
|
|
100
107
|
});
|
|
101
108
|
|
|
102
109
|
editor.commands.selectAll();
|
|
103
|
-
editor.commands.applyList(ListTypes.
|
|
110
|
+
editor.commands.applyList(ListTypes.ROMAN);
|
|
104
111
|
|
|
105
112
|
expect(editor.getJSON()).toMatchSnapshot();
|
|
106
113
|
});
|
|
107
114
|
|
|
108
|
-
test('should
|
|
115
|
+
test('should remove preset', () => {
|
|
109
116
|
const editor = createEditor({
|
|
110
117
|
content: NodeFactory.doc([
|
|
111
|
-
NodeFactory.
|
|
112
|
-
|
|
113
|
-
NodeFactory.paragraph('Item 2')
|
|
114
|
-
])
|
|
118
|
+
NodeFactory.paragraph({ preset: { id: 'regular-1' } }, 'Item 1'),
|
|
119
|
+
NodeFactory.paragraph({ preset: { id: 'regular-1' } }, 'Item 2')
|
|
115
120
|
])
|
|
116
121
|
});
|
|
117
122
|
|
|
118
123
|
editor.commands.selectAll();
|
|
119
|
-
editor.commands.applyList(ListTypes.
|
|
124
|
+
editor.commands.applyList(ListTypes.LATIN);
|
|
120
125
|
|
|
121
126
|
expect(editor.getJSON()).toMatchSnapshot();
|
|
122
127
|
});
|
|
128
|
+
});
|
|
123
129
|
|
|
124
|
-
|
|
130
|
+
describe('remove list', () => {
|
|
131
|
+
test('should remove list', () => {
|
|
125
132
|
const editor = createEditor({
|
|
126
133
|
content: NodeFactory.doc([
|
|
127
|
-
NodeFactory.
|
|
128
|
-
|
|
134
|
+
NodeFactory.list(ListTypes.LATIN, [
|
|
135
|
+
NodeFactory.paragraph('Item 1'),
|
|
136
|
+
NodeFactory.paragraph('Item 2')
|
|
137
|
+
])
|
|
129
138
|
])
|
|
130
139
|
});
|
|
131
140
|
|
|
132
141
|
editor.commands.selectAll();
|
|
133
|
-
editor.commands.
|
|
142
|
+
editor.commands.removeList();
|
|
134
143
|
|
|
135
144
|
expect(editor.getJSON()).toMatchSnapshot();
|
|
136
145
|
});
|
|
@@ -93,42 +93,6 @@ Object {
|
|
|
93
93
|
}
|
|
94
94
|
`;
|
|
95
95
|
|
|
96
|
-
exports[`apply list should remove list 1`] = `
|
|
97
|
-
Object {
|
|
98
|
-
"content": Array [
|
|
99
|
-
Object {
|
|
100
|
-
"attrs": Object {
|
|
101
|
-
"preset": Object {
|
|
102
|
-
"id": "regular-1",
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
"content": Array [
|
|
106
|
-
Object {
|
|
107
|
-
"text": "Item 1",
|
|
108
|
-
"type": "text",
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
"type": "paragraph",
|
|
112
|
-
},
|
|
113
|
-
Object {
|
|
114
|
-
"attrs": Object {
|
|
115
|
-
"preset": Object {
|
|
116
|
-
"id": "regular-1",
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
"content": Array [
|
|
120
|
-
Object {
|
|
121
|
-
"text": "Item 2",
|
|
122
|
-
"type": "text",
|
|
123
|
-
},
|
|
124
|
-
],
|
|
125
|
-
"type": "paragraph",
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
"type": "doc",
|
|
129
|
-
}
|
|
130
|
-
`;
|
|
131
|
-
|
|
132
96
|
exports[`apply list should remove preset 1`] = `
|
|
133
97
|
Object {
|
|
134
98
|
"content": Array [
|
|
@@ -701,3 +665,39 @@ Object {
|
|
|
701
665
|
"type": "doc",
|
|
702
666
|
}
|
|
703
667
|
`;
|
|
668
|
+
|
|
669
|
+
exports[`remove list should remove list 1`] = `
|
|
670
|
+
Object {
|
|
671
|
+
"content": Array [
|
|
672
|
+
Object {
|
|
673
|
+
"attrs": Object {
|
|
674
|
+
"preset": Object {
|
|
675
|
+
"id": "regular-1",
|
|
676
|
+
},
|
|
677
|
+
},
|
|
678
|
+
"content": Array [
|
|
679
|
+
Object {
|
|
680
|
+
"text": "Item 1",
|
|
681
|
+
"type": "text",
|
|
682
|
+
},
|
|
683
|
+
],
|
|
684
|
+
"type": "paragraph",
|
|
685
|
+
},
|
|
686
|
+
Object {
|
|
687
|
+
"attrs": Object {
|
|
688
|
+
"preset": Object {
|
|
689
|
+
"id": "regular-1",
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
"content": Array [
|
|
693
|
+
Object {
|
|
694
|
+
"text": "Item 2",
|
|
695
|
+
"type": "text",
|
|
696
|
+
},
|
|
697
|
+
],
|
|
698
|
+
"type": "paragraph",
|
|
699
|
+
},
|
|
700
|
+
],
|
|
701
|
+
"type": "doc",
|
|
702
|
+
}
|
|
703
|
+
`;
|
|
@@ -34,6 +34,7 @@ export class HtmlNormalizer extends BaseNormalizer {
|
|
|
34
34
|
this.#iterateNodes(this.#removeEmptyNodes, this.#isBlockNode);
|
|
35
35
|
this.#iterateNodes(this.#normalizeListItems, (node) => node.tagName === 'LI');
|
|
36
36
|
this.#normalizeBlockTextDecoration();
|
|
37
|
+
this.#normalizeBlockBackgroundColor();
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
get normalizedHTML() {
|
|
@@ -232,6 +233,28 @@ export class HtmlNormalizer extends BaseNormalizer {
|
|
|
232
233
|
};
|
|
233
234
|
}
|
|
234
235
|
|
|
236
|
+
#normalizeBlockBackgroundColor() {
|
|
237
|
+
const blockEls = this.dom.querySelectorAll('[style*="background-color"]:where(p, h1, h2, h3, h4, li)');
|
|
238
|
+
|
|
239
|
+
for (const blockEl of blockEls) {
|
|
240
|
+
this.#moveBackgroundColorToChildren(blockEl);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
#moveBackgroundColorToChildren(blockEl) {
|
|
245
|
+
const blockColor = blockEl.style.backgroundColor;
|
|
246
|
+
|
|
247
|
+
blockEl.style.removeProperty('background-color');
|
|
248
|
+
if (!blockEl.style.cssText) blockEl.removeAttribute('style');
|
|
249
|
+
|
|
250
|
+
for (const childNode of blockEl.childNodes) {
|
|
251
|
+
const textEl = this.#wrapTextNode(blockEl, childNode);
|
|
252
|
+
const color = textEl.style.backgroundColor || blockColor;
|
|
253
|
+
|
|
254
|
+
textEl.style.backgroundColor = color;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
235
258
|
#wrapTextNode(parent, node) {
|
|
236
259
|
if (node.nodeType !== this.#Node.TEXT_NODE) return node;
|
|
237
260
|
|
|
@@ -46,7 +46,7 @@ export class JsonNormalizer extends BaseNormalizer {
|
|
|
46
46
|
|
|
47
47
|
for (const child of node.content) {
|
|
48
48
|
if (!child.marks) return false;
|
|
49
|
-
if (!this.#
|
|
49
|
+
if (!this.#includesMark(child, childMark)) return false;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
return true;
|
|
@@ -67,4 +67,19 @@ describe('normalize json content', () => {
|
|
|
67
67
|
|
|
68
68
|
expect(ContentNormalizer.normalize(input)).toMatchSnapshot();
|
|
69
69
|
});
|
|
70
|
+
|
|
71
|
+
test('should not marge mark with same type', () => {
|
|
72
|
+
const input = NodeFactory.doc([
|
|
73
|
+
NodeFactory.paragraph([
|
|
74
|
+
NodeFactory.text('lorem', [
|
|
75
|
+
NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '700' })
|
|
76
|
+
]),
|
|
77
|
+
NodeFactory.text(' ipsum', [
|
|
78
|
+
NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '400' })
|
|
79
|
+
])
|
|
80
|
+
])
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
expect(ContentNormalizer.normalize(input)).toMatchSnapshot();
|
|
84
|
+
});
|
|
70
85
|
});
|
|
@@ -157,3 +157,40 @@ Object {
|
|
|
157
157
|
"type": "doc",
|
|
158
158
|
}
|
|
159
159
|
`;
|
|
160
|
+
|
|
161
|
+
exports[`normalize json content should not marge mark with same type 1`] = `
|
|
162
|
+
Object {
|
|
163
|
+
"content": Array [
|
|
164
|
+
Object {
|
|
165
|
+
"content": Array [
|
|
166
|
+
Object {
|
|
167
|
+
"marks": Array [
|
|
168
|
+
Object {
|
|
169
|
+
"attrs": Object {
|
|
170
|
+
"value": "700",
|
|
171
|
+
},
|
|
172
|
+
"type": "font_weight",
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
"text": "lorem",
|
|
176
|
+
"type": "text",
|
|
177
|
+
},
|
|
178
|
+
Object {
|
|
179
|
+
"marks": Array [
|
|
180
|
+
Object {
|
|
181
|
+
"attrs": Object {
|
|
182
|
+
"value": "400",
|
|
183
|
+
},
|
|
184
|
+
"type": "font_weight",
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
"text": " ipsum",
|
|
188
|
+
"type": "text",
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
"type": "paragraph",
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
"type": "doc",
|
|
195
|
+
}
|
|
196
|
+
`;
|
|
@@ -1,52 +1,43 @@
|
|
|
1
1
|
import { isNodeFullySelected } from '../isNodeFullySelected';
|
|
2
2
|
|
|
3
|
-
const createResolvedPosition = (attrs = {}) => ({
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const createResolvedPosition = (attrs = {}) => ({ ...attrs });
|
|
4
|
+
const createSelection = (from, to) => ({ from, to });
|
|
5
|
+
|
|
6
|
+
const createNode = (attrs = {}) => ({
|
|
7
|
+
resolve: jest.fn(() => 0),
|
|
6
8
|
...attrs
|
|
7
9
|
});
|
|
8
10
|
|
|
9
|
-
const createNode = (attrs = {}) => ({ ...attrs });
|
|
10
|
-
|
|
11
11
|
describe('is node fully selected', () => {
|
|
12
12
|
test('should return false if selected part of text node', () => {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
);
|
|
13
|
+
const doc = createNode();
|
|
14
|
+
const paragraph = createNode({ nodeSize: 245 });
|
|
15
|
+
|
|
16
|
+
doc.resolve.mockReturnValue(createResolvedPosition({ depth: 0 }));
|
|
17
|
+
|
|
18
|
+
const isSelected = isNodeFullySelected(doc, createSelection(7, 12), paragraph, 0);
|
|
20
19
|
|
|
21
20
|
expect(isSelected).toBe(false);
|
|
22
21
|
});
|
|
23
22
|
|
|
24
23
|
test('should return true if selected paragraph node', () => {
|
|
24
|
+
const doc = createNode();
|
|
25
25
|
const paragraph = createNode({ nodeSize: 245 });
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
0
|
|
31
|
-
);
|
|
26
|
+
|
|
27
|
+
doc.resolve.mockReturnValue(createResolvedPosition({ depth: 0 }));
|
|
28
|
+
|
|
29
|
+
const isSelected = isNodeFullySelected(doc, createSelection(1, 244), paragraph, 0);
|
|
32
30
|
|
|
33
31
|
expect(isSelected).toBe(true);
|
|
34
32
|
});
|
|
35
33
|
|
|
36
34
|
test('should return true if selected list item node', () => {
|
|
35
|
+
const doc = createNode();
|
|
37
36
|
const listItem = createNode({ nodeSize: 247 });
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}),
|
|
43
|
-
createResolvedPosition({
|
|
44
|
-
pos: 246,
|
|
45
|
-
path: [createNode(), 0, 0, createNode(), 0, 1, listItem, 0, 2, createNode(), 0, 3]
|
|
46
|
-
}),
|
|
47
|
-
listItem,
|
|
48
|
-
1
|
|
49
|
-
);
|
|
37
|
+
|
|
38
|
+
doc.resolve.mockReturnValue(createResolvedPosition({ depth: 2 }));
|
|
39
|
+
|
|
40
|
+
const isSelected = isNodeFullySelected(doc, createSelection(3, 246), listItem, 1);
|
|
50
41
|
|
|
51
42
|
expect(isSelected).toBe(true);
|
|
52
43
|
});
|
package/lib/utils/index.js
CHANGED
|
@@ -12,3 +12,4 @@ export { resolveTextPosition } from './resolveTextPosition';
|
|
|
12
12
|
export { isNodeFullySelected } from './isNodeFullySelected';
|
|
13
13
|
export { isMarkAppliedToParent } from './isMarkAppliedToParent';
|
|
14
14
|
export { findMarkByType } from './findMarkByType';
|
|
15
|
+
export { copyMark } from './copyMark';
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
function
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
return nodes.slice(index).reverse().length;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function isNodeFullySelected($from, $to, node, position) {
|
|
9
|
-
const offset = resolveNodeTextOffset($from, node);
|
|
10
|
-
const isFromMatch = $from.pos - offset <= position;
|
|
11
|
-
const isToMatch = $to.pos + offset >= node.nodeSize + position;
|
|
1
|
+
export function isNodeFullySelected(doc, selection, node, position) {
|
|
2
|
+
const offset = doc.resolve(position).depth + 1;
|
|
3
|
+
const isFromMatch = selection.from - offset <= position;
|
|
4
|
+
const isToMatch = selection.to + offset >= node.nodeSize + position;
|
|
12
5
|
|
|
13
6
|
return isFromMatch && isToMatch;
|
|
14
7
|
}
|