@zipify/wysiwyg 2.0.0-0 → 2.0.0-10

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.
Files changed (118) hide show
  1. package/.eslintrc.js +1 -1
  2. package/config/build/cli.config.js +8 -2
  3. package/config/build/lib.config.js +4 -2
  4. package/dist/cli.js +10 -2
  5. package/dist/wysiwyg.css +53 -37
  6. package/dist/wysiwyg.mjs +2131 -405
  7. package/example/ExampleApp.vue +13 -2
  8. package/lib/Wysiwyg.vue +3 -2
  9. package/lib/__tests__/utils/buildTestExtensions.js +2 -1
  10. package/lib/assets/icons/indicator.svg +4 -0
  11. package/lib/cli/commands/Command.js +39 -0
  12. package/lib/cli/commands/ToJsonCommand.js +46 -0
  13. package/lib/cli/commands/VersionCommand.js +11 -0
  14. package/lib/cli/commands/index.js +2 -0
  15. package/lib/cli/index.js +1 -0
  16. package/lib/components/base/Button.vue +6 -0
  17. package/lib/components/base/dropdown/Dropdown.vue +7 -1
  18. package/lib/components/base/dropdown/DropdownActivator.vue +25 -4
  19. package/lib/components/base/dropdown/__tests__/DropdownActivator.test.js +23 -1
  20. package/lib/components/toolbar/controls/AlignmentControl.vue +12 -1
  21. package/lib/components/toolbar/controls/FontColorControl.vue +14 -0
  22. package/lib/components/toolbar/controls/FontFamilyControl.vue +4 -0
  23. package/lib/components/toolbar/controls/FontSizeControl.vue +6 -1
  24. package/lib/components/toolbar/controls/FontWeightControl.vue +12 -0
  25. package/lib/components/toolbar/controls/ItalicControl.vue +14 -0
  26. package/lib/components/toolbar/controls/LineHeightControl.vue +15 -0
  27. package/lib/components/toolbar/controls/StylePresetControl.vue +1 -1
  28. package/lib/components/toolbar/controls/UnderlineControl.vue +13 -0
  29. package/lib/components/toolbar/controls/__tests__/AlignmentControl.test.js +72 -5
  30. package/lib/components/toolbar/controls/__tests__/FontColorControl.test.js +22 -1
  31. package/lib/components/toolbar/controls/__tests__/FontFamilyControl.test.js +1 -0
  32. package/lib/components/toolbar/controls/__tests__/FontSizeControl.test.js +1 -0
  33. package/lib/components/toolbar/controls/__tests__/FontWeightControl.test.js +1 -0
  34. package/lib/components/toolbar/controls/__tests__/ItalicControl.test.js +23 -1
  35. package/lib/components/toolbar/controls/__tests__/LineHeightControl.test.js +23 -1
  36. package/lib/components/toolbar/controls/__tests__/StylePresetControl.test.js +6 -6
  37. package/lib/components/toolbar/controls/__tests__/UnderlineControl.test.js +25 -1
  38. package/lib/composables/__tests__/useEditor.test.js +1 -1
  39. package/lib/composables/useEditor.js +9 -8
  40. package/lib/directives/__tests__/tooltip.test.js +22 -4
  41. package/lib/directives/tooltip.js +4 -1
  42. package/lib/entry-cli.js +7 -20
  43. package/lib/entry-lib.js +1 -1
  44. package/lib/enums/MarkGroups.js +4 -0
  45. package/lib/enums/TextSettings.js +5 -5
  46. package/lib/enums/index.js +1 -0
  47. package/lib/extensions/BackgroundColor.js +0 -1
  48. package/lib/extensions/FontColor.js +2 -2
  49. package/lib/extensions/FontFamily.js +3 -3
  50. package/lib/extensions/FontSize.js +2 -2
  51. package/lib/extensions/FontStyle.js +2 -2
  52. package/lib/extensions/FontWeight.js +2 -2
  53. package/lib/extensions/Link.js +1 -1
  54. package/lib/extensions/StylePreset.js +9 -3
  55. package/lib/extensions/Superscript.js +5 -2
  56. package/lib/extensions/TextDecoration.js +19 -29
  57. package/lib/extensions/__tests__/Alignment.test.js +2 -2
  58. package/lib/extensions/__tests__/BackgroundColor.test.js +4 -3
  59. package/lib/extensions/__tests__/FontColor.test.js +4 -3
  60. package/lib/extensions/__tests__/FontFamily.test.js +6 -6
  61. package/lib/extensions/__tests__/FontSize.test.js +9 -8
  62. package/lib/extensions/__tests__/FontStyle.test.js +6 -5
  63. package/lib/extensions/__tests__/FontWeight.test.js +2 -2
  64. package/lib/extensions/__tests__/LineHeight.test.js +2 -1
  65. package/lib/extensions/__tests__/StylePreset.test.js +51 -0
  66. package/lib/extensions/__tests__/Superscript.test.js +102 -0
  67. package/lib/extensions/__tests__/TextDecoration.test.js +40 -24
  68. package/lib/extensions/__tests__/__snapshots__/BackgroundColor.test.js.snap +24 -24
  69. package/lib/extensions/__tests__/__snapshots__/FontColor.test.js.snap +1 -1
  70. package/lib/extensions/__tests__/__snapshots__/FontFamily.test.js.snap +19 -23
  71. package/lib/extensions/__tests__/__snapshots__/FontSize.test.js.snap +2 -2
  72. package/lib/extensions/__tests__/__snapshots__/FontStyle.test.js.snap +1 -1
  73. package/lib/extensions/__tests__/__snapshots__/FontWeight.test.js.snap +13 -17
  74. package/lib/extensions/__tests__/__snapshots__/Superscript.test.js.snap +107 -0
  75. package/lib/extensions/__tests__/__snapshots__/TextDecoration.test.js.snap +102 -102
  76. package/lib/extensions/core/Document.js +2 -1
  77. package/lib/extensions/core/Heading.js +2 -1
  78. package/lib/extensions/core/NodeProcessor.js +63 -20
  79. package/lib/extensions/core/Paragraph.js +2 -1
  80. package/lib/extensions/core/TextProcessor.js +0 -5
  81. package/lib/extensions/core/__tests__/NodeProcessor.test.js +364 -11
  82. package/lib/extensions/core/__tests__/TextProcessor.test.js +1 -22
  83. package/lib/extensions/core/__tests__/__snapshots__/NodeProcessor.test.js.snap +309 -0
  84. package/lib/extensions/core/__tests__/__snapshots__/TextProcessor.test.js.snap +7 -27
  85. package/lib/extensions/core/steps/AddNodeMarkStep.js +6 -0
  86. package/lib/extensions/core/steps/AttrStep.js +60 -0
  87. package/lib/extensions/core/steps/RemoveNodeMarkStep.js +6 -0
  88. package/lib/extensions/core/steps/index.js +1 -0
  89. package/lib/extensions/list/List.js +70 -9
  90. package/lib/extensions/list/ListItem.js +2 -2
  91. package/lib/extensions/list/__tests__/List.test.js +26 -17
  92. package/lib/extensions/list/__tests__/__snapshots__/List.test.js.snap +36 -36
  93. package/lib/services/NodeFactory.js +73 -13
  94. package/lib/services/__tests__/NodeFactory.test.js +124 -0
  95. package/lib/services/__tests__/__snapshots__/NodeFactory.test.js.snap +326 -0
  96. package/lib/services/index.js +1 -1
  97. package/lib/services/normalizer/BaseNormalizer.js +11 -0
  98. package/lib/services/{BrowserDomParser.js → normalizer/BrowserDomParser.js} +0 -0
  99. package/lib/services/normalizer/ContentNormalizer.js +24 -0
  100. package/lib/services/normalizer/HtmlNormalizer.js +297 -0
  101. package/lib/services/normalizer/JsonNormalizer.js +82 -0
  102. package/lib/services/{__tests__/ContentNormalizer.test.js → normalizer/__tests__/HtmlNormalizer.test.js} +42 -4
  103. package/lib/services/normalizer/__tests__/JsonNormalizer.test.js +87 -0
  104. package/lib/services/normalizer/__tests__/__snapshots__/JsonNormalizer.test.js.snap +196 -0
  105. package/lib/services/normalizer/index.js +1 -0
  106. package/lib/styles/content.css +8 -0
  107. package/lib/utils/__tests__/findMarkByType.test.js +17 -0
  108. package/lib/utils/__tests__/isMarkAppliedToParent.test.js +53 -0
  109. package/lib/utils/__tests__/isNodeFullySelected.test.js +44 -0
  110. package/lib/utils/__tests__/resolveTextPosition.test.js +39 -0
  111. package/lib/utils/copyMark.js +5 -0
  112. package/lib/utils/index.js +1 -1
  113. package/lib/utils/isMarkAppliedToParent.js +2 -7
  114. package/lib/utils/isNodeFullySelected.js +4 -7
  115. package/lib/utils/resolveTextPosition.js +4 -6
  116. package/package.json +31 -27
  117. package/lib/services/ContentNormalizer.js +0 -194
  118. package/lib/utils/resolveNodePosition.js +0 -6
@@ -0,0 +1,82 @@
1
+ import { isEqual } from 'lodash';
2
+ import { NodeTypes, TextSettings } from '../../enums';
3
+ import { BaseNormalizer } from './BaseNormalizer';
4
+
5
+ export class JsonNormalizer extends BaseNormalizer {
6
+ normalize() {
7
+ this.#iterateNodes(this.#bubbleMarks);
8
+ return this.content;
9
+ }
10
+
11
+ #iterateNodes(handler) {
12
+ this.#iterateChildNodes(this.content, handler);
13
+ }
14
+
15
+ #iterateChildNodes(node, handler) {
16
+ for (const child of node.content) {
17
+ child.content && this.#iterateChildNodes(child, handler);
18
+ handler.call(this, child);
19
+ }
20
+ }
21
+
22
+ #bubbleMarks(node) {
23
+ if (!node.content) return;
24
+ if (node.type === NodeTypes.LIST) return;
25
+
26
+ for (const child of node.content) {
27
+ if (!child.marks) continue;
28
+
29
+ for (const childMark of child.marks.slice()) {
30
+ if (this.#includesMark(node, childMark)) {
31
+ this.#removeMark(child, childMark);
32
+ continue;
33
+ }
34
+
35
+ if (this.#canBubbleMark(node, childMark)) {
36
+ this.#removeMark(child, childMark);
37
+ this.#addMark(node, childMark);
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ #canBubbleMark(node, childMark) {
44
+ if (TextSettings.inlineMarks.includes(childMark.type)) return false;
45
+ if (this.#includesMarkType(node, childMark.type)) return false;
46
+
47
+ for (const child of node.content) {
48
+ if (!child.content && node.type === NodeTypes.LIST_ITEM) continue;
49
+ if (!child.marks) return false;
50
+ if (!this.#includesMark(child, childMark)) return false;
51
+ }
52
+
53
+ return true;
54
+ }
55
+
56
+ #includesMark(node, checkingMark) {
57
+ return node.marks?.some((mark) => isEqual(mark, checkingMark)) ?? false;
58
+ }
59
+
60
+ #includesMarkType(node, type) {
61
+ return node.marks?.some((mark) => mark.type === type) ?? false;
62
+ }
63
+
64
+ #removeMark(node, mark) {
65
+ if (!node.marks) return;
66
+
67
+ const index = this.#findMarkIndexByType(node, mark.type);
68
+
69
+ if (index >= 0) node.marks.splice(index, 1);
70
+ if (!node.marks.length) delete node.marks;
71
+ }
72
+
73
+ #addMark(node, mark) {
74
+ this.#removeMark(node, mark);
75
+ node.marks ??= [];
76
+ node.marks.push(mark);
77
+ }
78
+
79
+ #findMarkIndexByType(node, type) {
80
+ return node.marks?.findIndex((mark) => mark.type === type) ?? null;
81
+ }
82
+ }
@@ -1,11 +1,18 @@
1
1
  import { ContentNormalizer } from '../ContentNormalizer';
2
- import { NodeFactory } from '../NodeFactory';
3
2
 
4
3
  describe('normalize text content', () => {
5
- test('should ignore json content', () => {
6
- const content = NodeFactory.doc([NodeFactory.paragraph('Test')]);
4
+ test('should wrap root nodes in paragraphs', () => {
5
+ const input = '<strong>lorem</strong>ipsum';
6
+ const output = '<p><strong>lorem</strong>ipsum</p>';
7
7
 
8
- expect(ContentNormalizer.normalize(content)).toBe(content);
8
+ expect(ContentNormalizer.normalize(input)).toBe(output);
9
+ });
10
+
11
+ test('should not wrap paragraph', () => {
12
+ const input = 'lorem<p>ipsum</p>hello';
13
+ const output = '<p>lorem</p><p>ipsum</p><p>hello</p>';
14
+
15
+ expect(ContentNormalizer.normalize(input)).toBe(output);
9
16
  });
10
17
 
11
18
  test('should wrap list content in paragraph', () => {
@@ -107,4 +114,35 @@ describe('normalize text content', () => {
107
114
 
108
115
  expect(ContentNormalizer.normalize(input)).toBe(output);
109
116
  });
117
+
118
+ test('should ignore text decoration on text nodes', () => {
119
+ const input = '<p><span style="text-decoration: underline;">lorem ipsum</span></p>';
120
+ const output = '<p><span style="text-decoration: underline;">lorem ipsum</span></p>';
121
+
122
+ expect(ContentNormalizer.normalize(input)).toBe(output);
123
+ });
124
+
125
+ test('should ignore none text decoration', () => {
126
+ const input = '<p style="text-decoration: none;">lorem ipsum</p>';
127
+ const output = '<p>lorem ipsum</p>';
128
+
129
+ expect(ContentNormalizer.normalize(input)).toBe(output);
130
+ });
131
+
132
+ test('should move text decoration from block to text only nodes', () => {
133
+ const input = '<p style="text-decoration: underline;">lorem ipsum</p>';
134
+ const output = '<p><span style="text-decoration: underline;">lorem ipsum</span></p>';
135
+
136
+ expect(ContentNormalizer.normalize(input)).toBe(output);
137
+ });
138
+
139
+ test('should move text decoration from block to mixed content', () => {
140
+ const input = '<p style="text-decoration: underline;"><span style="text-decoration: line-through;">lorem</span> ipsum</p>';
141
+ const output = '<p>' +
142
+ '<span style="text-decoration: underline line-through;">lorem</span>' +
143
+ '<span style="text-decoration: underline;"> ipsum</span>' +
144
+ '</p>';
145
+
146
+ expect(ContentNormalizer.normalize(input)).toBe(output);
147
+ });
110
148
  });
@@ -0,0 +1,87 @@
1
+ import { NodeFactory } from '../../NodeFactory';
2
+ import { ListTypes, TextSettings } from '../../../enums';
3
+ import { ContentNormalizer } from '../ContentNormalizer';
4
+
5
+ describe('normalize json content', () => {
6
+ test('should bubble mark from single text node to paragraph', () => {
7
+ const input = NodeFactory.doc([
8
+ NodeFactory.paragraph([
9
+ NodeFactory.text('lorem ipsum', [
10
+ NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '700' })
11
+ ])
12
+ ])
13
+ ]);
14
+
15
+ expect(ContentNormalizer.normalize(input)).toMatchSnapshot();
16
+ });
17
+
18
+ test('should bubble mark from two text nodes to paragraph', () => {
19
+ const input = NodeFactory.doc([
20
+ NodeFactory.paragraph([
21
+ NodeFactory.text('lorem', [
22
+ NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '700' })
23
+ ]),
24
+ NodeFactory.text(' ipsum', [
25
+ NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '700' }),
26
+ NodeFactory.mark(TextSettings.FONT_COLOR, { value: '#FF0000' })
27
+ ])
28
+ ])
29
+ ]);
30
+
31
+ expect(ContentNormalizer.normalize(input)).toMatchSnapshot();
32
+ });
33
+
34
+ test('should bubble mark from text to list item', () => {
35
+ const input = NodeFactory.doc([
36
+ NodeFactory.list(ListTypes.DISC, [
37
+ NodeFactory.paragraph([
38
+ NodeFactory.text('lorem ipsum', [
39
+ NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '700' })
40
+ ])
41
+ ])
42
+ ])
43
+ ]);
44
+
45
+ expect(ContentNormalizer.normalize(input)).toMatchSnapshot();
46
+ });
47
+
48
+ test('should bubble two marks', () => {
49
+ const input = NodeFactory.doc([
50
+ NodeFactory.paragraph([
51
+ NodeFactory.text('hello world', [
52
+ NodeFactory.mark(TextSettings.FONT_FAMILY, { value: 'Bungee' }),
53
+ NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '800' })
54
+ ])
55
+ ])
56
+ ]);
57
+
58
+ expect(ContentNormalizer.normalize(input)).toMatchSnapshot();
59
+ });
60
+
61
+ test('should not bubble inline marks', () => {
62
+ const input = NodeFactory.doc([
63
+ NodeFactory.paragraph([
64
+ NodeFactory.text('hello world', [
65
+ NodeFactory.mark(TextSettings.TEXT_DECORATION, { underline: true })
66
+ ])
67
+ ])
68
+ ]);
69
+
70
+ expect(ContentNormalizer.normalize(input)).toMatchSnapshot();
71
+ });
72
+
73
+ test('should not marge mark with same type', () => {
74
+ const input = NodeFactory.doc([
75
+ NodeFactory.paragraph([
76
+ NodeFactory.text('lorem', [
77
+ NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '700' })
78
+ ]),
79
+ NodeFactory.text(' ipsum', [
80
+ NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '400' })
81
+ ])
82
+ ])
83
+ ]);
84
+
85
+ expect(ContentNormalizer.normalize(input)).toMatchSnapshot();
86
+ });
87
+ });
@@ -0,0 +1,196 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`normalize json content should bubble mark from single text node to paragraph 1`] = `
4
+ Object {
5
+ "content": Array [
6
+ Object {
7
+ "content": Array [
8
+ Object {
9
+ "text": "lorem ipsum",
10
+ "type": "text",
11
+ },
12
+ ],
13
+ "marks": Array [
14
+ Object {
15
+ "attrs": Object {
16
+ "value": "700",
17
+ },
18
+ "type": "font_weight",
19
+ },
20
+ ],
21
+ "type": "paragraph",
22
+ },
23
+ ],
24
+ "type": "doc",
25
+ }
26
+ `;
27
+
28
+ exports[`normalize json content should bubble mark from text to list item 1`] = `
29
+ Object {
30
+ "content": Array [
31
+ Object {
32
+ "attrs": Object {
33
+ "bullet": Object {
34
+ "type": "disc",
35
+ },
36
+ },
37
+ "content": Array [
38
+ Object {
39
+ "content": Array [
40
+ Object {
41
+ "content": Array [
42
+ Object {
43
+ "text": "lorem ipsum",
44
+ "type": "text",
45
+ },
46
+ ],
47
+ "type": "paragraph",
48
+ },
49
+ ],
50
+ "marks": Array [
51
+ Object {
52
+ "attrs": Object {
53
+ "value": "700",
54
+ },
55
+ "type": "font_weight",
56
+ },
57
+ ],
58
+ "type": "listItem",
59
+ },
60
+ ],
61
+ "type": "list",
62
+ },
63
+ ],
64
+ "type": "doc",
65
+ }
66
+ `;
67
+
68
+ exports[`normalize json content should bubble mark from two text nodes to paragraph 1`] = `
69
+ Object {
70
+ "content": Array [
71
+ Object {
72
+ "content": Array [
73
+ Object {
74
+ "text": "lorem",
75
+ "type": "text",
76
+ },
77
+ Object {
78
+ "marks": Array [
79
+ Object {
80
+ "attrs": Object {
81
+ "value": "#FF0000",
82
+ },
83
+ "type": "font_color",
84
+ },
85
+ ],
86
+ "text": " ipsum",
87
+ "type": "text",
88
+ },
89
+ ],
90
+ "marks": Array [
91
+ Object {
92
+ "attrs": Object {
93
+ "value": "700",
94
+ },
95
+ "type": "font_weight",
96
+ },
97
+ ],
98
+ "type": "paragraph",
99
+ },
100
+ ],
101
+ "type": "doc",
102
+ }
103
+ `;
104
+
105
+ exports[`normalize json content should bubble two marks 1`] = `
106
+ Object {
107
+ "content": Array [
108
+ Object {
109
+ "content": Array [
110
+ Object {
111
+ "text": "hello world",
112
+ "type": "text",
113
+ },
114
+ ],
115
+ "marks": Array [
116
+ Object {
117
+ "attrs": Object {
118
+ "value": "Bungee",
119
+ },
120
+ "type": "font_family",
121
+ },
122
+ Object {
123
+ "attrs": Object {
124
+ "value": "800",
125
+ },
126
+ "type": "font_weight",
127
+ },
128
+ ],
129
+ "type": "paragraph",
130
+ },
131
+ ],
132
+ "type": "doc",
133
+ }
134
+ `;
135
+
136
+ exports[`normalize json content should not bubble inline marks 1`] = `
137
+ Object {
138
+ "content": Array [
139
+ Object {
140
+ "content": Array [
141
+ Object {
142
+ "marks": Array [
143
+ Object {
144
+ "attrs": Object {
145
+ "underline": true,
146
+ },
147
+ "type": "text_decoration",
148
+ },
149
+ ],
150
+ "text": "hello world",
151
+ "type": "text",
152
+ },
153
+ ],
154
+ "type": "paragraph",
155
+ },
156
+ ],
157
+ "type": "doc",
158
+ }
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
+ `;
@@ -0,0 +1 @@
1
+ export { ContentNormalizer } from './ContentNormalizer';
@@ -112,6 +112,14 @@ img.ProseMirror-separator {
112
112
  animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
113
113
  }
114
114
 
115
+ .ProseMirror span {
116
+ display: block;
117
+ }
118
+
119
+ .ProseMirror :where(p, h1, h2, h3, h4) span {
120
+ display: initial;
121
+ }
122
+
115
123
  @keyframes ProseMirror-cursor-blink {
116
124
 
117
125
  to {
@@ -0,0 +1,17 @@
1
+ import { findMarkByType } from '../findMarkByType';
2
+
3
+ describe('find mark by type', () => {
4
+ test('should find mark by type name', () => {
5
+ const searchingMark = { type: { name: 'font_weight' } };
6
+ const marks = [{ type: { name: 'font_size' } }, searchingMark];
7
+
8
+ expect(findMarkByType(marks, 'font_weight')).toBe(searchingMark);
9
+ });
10
+
11
+ test('should find mark by type object', () => {
12
+ const searchingMark = { type: { name: 'font_weight' } };
13
+ const marks = [{ type: { name: 'font_size' } }, searchingMark];
14
+
15
+ expect(findMarkByType(marks, { name: 'font_weight' })).toBe(searchingMark);
16
+ });
17
+ });
@@ -0,0 +1,53 @@
1
+ import { isMarkAppliedToParent } from '../isMarkAppliedToParent';
2
+
3
+ const createMark = (attrs = {}) => ({
4
+ isInSet(set) {
5
+ return set.includes(this);
6
+ },
7
+ ...attrs
8
+ });
9
+
10
+ const createNode = (attrs = {}) => ({
11
+ resolve: jest.fn(),
12
+ path: [],
13
+ marks: [],
14
+ ...attrs
15
+ });
16
+
17
+ describe('is mark applied to parent', () => {
18
+ test('should return true if applied to parent', () => {
19
+ const checkingMark = createMark();
20
+ const doc = createNode();
21
+
22
+ doc.resolve.mockReturnValue({
23
+ path: [
24
+ createNode({ marks: [checkingMark] }),
25
+ 0,
26
+ 0,
27
+ createNode({ marks: [createMark()] }),
28
+ 0,
29
+ 1
30
+ ]
31
+ });
32
+
33
+ expect(isMarkAppliedToParent(doc, 0, checkingMark)).toBe(true);
34
+ });
35
+
36
+ test('should return false if not applied to parent', () => {
37
+ const checkingMark = createMark();
38
+ const doc = createNode();
39
+
40
+ doc.resolve.mockReturnValue({
41
+ path: [
42
+ createNode({ marks: [createMark()] }),
43
+ 0,
44
+ 0,
45
+ createNode({ marks: [createMark()] }),
46
+ 0,
47
+ 1
48
+ ]
49
+ });
50
+
51
+ expect(isMarkAppliedToParent(doc, 0, checkingMark)).toBe(false);
52
+ });
53
+ });
@@ -0,0 +1,44 @@
1
+ import { isNodeFullySelected } from '../isNodeFullySelected';
2
+
3
+ const createResolvedPosition = (attrs = {}) => ({ ...attrs });
4
+ const createSelection = (from, to) => ({ from, to });
5
+
6
+ const createNode = (attrs = {}) => ({
7
+ resolve: jest.fn(() => 0),
8
+ ...attrs
9
+ });
10
+
11
+ describe('is node fully selected', () => {
12
+ test('should return false if selected part of text node', () => {
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);
19
+
20
+ expect(isSelected).toBe(false);
21
+ });
22
+
23
+ test('should return true if selected paragraph node', () => {
24
+ const doc = createNode();
25
+ const paragraph = createNode({ nodeSize: 245 });
26
+
27
+ doc.resolve.mockReturnValue(createResolvedPosition({ depth: 0 }));
28
+
29
+ const isSelected = isNodeFullySelected(doc, createSelection(1, 244), paragraph, 0);
30
+
31
+ expect(isSelected).toBe(true);
32
+ });
33
+
34
+ test('should return true if selected list item node', () => {
35
+ const doc = createNode();
36
+ const listItem = createNode({ nodeSize: 247 });
37
+
38
+ doc.resolve.mockReturnValue(createResolvedPosition({ depth: 2 }));
39
+
40
+ const isSelected = isNodeFullySelected(doc, createSelection(3, 246), listItem, 1);
41
+
42
+ expect(isSelected).toBe(true);
43
+ });
44
+ });
@@ -0,0 +1,39 @@
1
+ import { resolveTextPosition } from '../resolveTextPosition';
2
+
3
+ const createPosition = (pos) => ({ pos });
4
+ const createNode = (attrs) => ({ nodeSize: 0, ...attrs });
5
+
6
+ describe('resolve text position', () => {
7
+ test('should resolve position in node range', () => {
8
+ const position = resolveTextPosition(
9
+ createPosition(2),
10
+ createPosition(5),
11
+ createNode({ nodeSize: 10 }),
12
+ 1
13
+ );
14
+
15
+ expect(position).toEqual({ from: 2, to: 5 });
16
+ });
17
+
18
+ test('should limit start value by node start position', () => {
19
+ const position = resolveTextPosition(
20
+ createPosition(2),
21
+ createPosition(5),
22
+ createNode({ nodeSize: 10 }),
23
+ 3
24
+ );
25
+
26
+ expect(position).toEqual({ from: 3, to: 5 });
27
+ });
28
+
29
+ test('should limit start value by node end position', () => {
30
+ const position = resolveTextPosition(
31
+ createPosition(2),
32
+ createPosition(15),
33
+ createNode({ nodeSize: 10 }),
34
+ 1
35
+ );
36
+
37
+ expect(position).toEqual({ from: 2, to: 11 });
38
+ });
39
+ });
@@ -0,0 +1,5 @@
1
+ import { cloneDeep } from 'lodash';
2
+
3
+ export function copyMark(mark) {
4
+ return mark.type.create(cloneDeep(mark.attrs));
5
+ }
@@ -8,8 +8,8 @@ export { convertFontSize } from './convertFontSize';
8
8
  export { convertAlignment } from './convertAlignment';
9
9
  export { importIcon } from './importIcon';
10
10
  export { isWysiwygContent, markWysiwygContent, unmarkWysiwygContent } from './isWysiwygContent';
11
- export { resolveNodePosition } from './resolveNodePosition';
12
11
  export { resolveTextPosition } from './resolveTextPosition';
13
12
  export { isNodeFullySelected } from './isNodeFullySelected';
14
13
  export { isMarkAppliedToParent } from './isMarkAppliedToParent';
15
14
  export { findMarkByType } from './findMarkByType';
15
+ export { copyMark } from './copyMark';
@@ -1,14 +1,9 @@
1
- const DEFAULT_COMPARATOR = (parent, child) => child.eq(parent);
2
-
3
- export function isMarkAppliedToParent({ doc }, position, checkingMark, comparator = DEFAULT_COMPARATOR) {
1
+ export function isMarkAppliedToParent(doc, position, checkingMark) {
4
2
  const steps = doc.resolve(position).path.reverse();
5
3
 
6
4
  for (const step of steps) {
7
5
  if (typeof step === 'number') continue;
8
-
9
- for (const mark of step.marks) {
10
- if (comparator(mark, checkingMark)) return true;
11
- }
6
+ if (checkingMark.isInSet(step.marks)) return true;
12
7
  }
13
8
 
14
9
  return false;
@@ -1,10 +1,7 @@
1
- import { resolveNodePosition } from './resolveNodePosition';
2
-
3
- export function isNodeFullySelected($from, $to, node, position) {
4
- const fromPosition = resolveNodePosition($from, node, -1);
5
- const toPosition = resolveNodePosition($to, node, 1);
6
- const isFromMatch = fromPosition <= position;
7
- const isToMatch = toPosition >= 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;
8
5
 
9
6
  return isFromMatch && isToMatch;
10
7
  }
@@ -1,6 +1,4 @@
1
- export function resolveTextPosition($from, $to, node, position) {
2
- return {
3
- from: Math.max(position, $from.pos),
4
- to: Math.min(position + node.nodeSize, $to.pos)
5
- };
6
- }
1
+ export const resolveTextPosition = ($from, $to, node, position) => ({
2
+ from: Math.max(position, $from.pos),
3
+ to: Math.min(position + node.nodeSize, $to.pos)
4
+ });