@zipify/wysiwyg 1.0.0-dev.4 → 1.0.0-dev.5
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/.release-it.json +0 -1
- package/README.md +3 -1
- package/config/jest/setupTests.js +3 -1
- package/example/ExampleApp.vue +39 -31
- package/example/example.js +26 -0
- package/example/presets.js +2 -0
- package/lib/Wysiwyg.vue +6 -0
- package/lib/composables/__tests__/useEditor.test.js +12 -3
- package/lib/composables/useEditor.js +12 -5
- package/lib/extensions/Alignment.js +6 -0
- package/lib/extensions/BackgroundColor.js +8 -1
- package/lib/extensions/FontColor.js +8 -1
- package/lib/extensions/FontFamily.js +7 -0
- package/lib/extensions/FontSize.js +12 -0
- package/lib/extensions/FontStyle.js +11 -0
- package/lib/extensions/FontWeight.js +25 -1
- package/lib/extensions/LineHeight.js +17 -0
- package/lib/extensions/StylePreset.js +30 -3
- package/lib/extensions/TextDecoration.js +11 -0
- package/lib/extensions/__tests__/Alignment.test.js +22 -1
- package/lib/extensions/__tests__/BackgroundColor.test.js +30 -1
- package/lib/extensions/__tests__/CaseStyle.test.js +4 -1
- package/lib/extensions/__tests__/FontColor.test.js +30 -1
- package/lib/extensions/__tests__/FontFamily.test.js +30 -1
- package/lib/extensions/__tests__/FontSize.test.js +30 -1
- package/lib/extensions/__tests__/FontStyle.test.js +38 -1
- package/lib/extensions/__tests__/FontWeight.test.js +58 -1
- package/lib/extensions/__tests__/LineHeight.test.js +41 -1
- package/lib/extensions/__tests__/StylePreset.test.js +76 -1
- package/lib/extensions/__tests__/TextDecoration.test.js +63 -1
- package/lib/extensions/__tests__/__snapshots__/Alignment.test.js.snap +44 -0
- package/lib/extensions/__tests__/__snapshots__/BackgroundColor.test.js.snap +91 -0
- package/lib/extensions/__tests__/__snapshots__/FontColor.test.js.snap +91 -0
- package/lib/extensions/__tests__/__snapshots__/FontFamily.test.js.snap +91 -0
- package/lib/extensions/__tests__/__snapshots__/FontSize.test.js.snap +99 -0
- package/lib/extensions/__tests__/__snapshots__/FontStyle.test.js.snap +120 -0
- package/lib/extensions/__tests__/__snapshots__/FontWeight.test.js.snap +149 -0
- package/lib/extensions/__tests__/__snapshots__/LineHeight.test.js.snap +92 -0
- package/lib/extensions/__tests__/__snapshots__/StylePreset.test.js.snap +167 -2
- package/lib/extensions/__tests__/__snapshots__/TextDecoration.test.js.snap +207 -0
- package/lib/extensions/core/__tests__/NodeProcessor.test.js +4 -1
- package/lib/extensions/core/__tests__/SelectionProcessor.test.js +9 -4
- package/lib/extensions/core/__tests__/TextProcessor.test.js +4 -1
- package/lib/extensions/index.js +1 -0
- package/lib/extensions/list/List.js +34 -0
- package/lib/extensions/list/__tests__/List.test.js +115 -3
- package/lib/extensions/list/__tests__/__snapshots__/List.test.js.snap +457 -6
- package/lib/services/ContentNormalizer.js +113 -0
- package/lib/services/Storage.js +1 -13
- package/lib/services/__tests__/ContentNormalizer.test.js +41 -0
- package/lib/services/__tests__/FavoriteColors.test.js +20 -0
- package/lib/services/__tests__/JsonSerializer.test.js +23 -0
- package/lib/services/__tests__/Storage.test.js +79 -0
- package/lib/services/index.js +1 -0
- package/lib/utils/__tests__/convertColor.test.js +19 -0
- package/lib/utils/__tests__/createKeyboardShortcut.test.js +25 -0
- package/lib/utils/__tests__/renderInlineSetting.test.js +26 -0
- package/lib/utils/convertColor.js +7 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/renderInlineSetting.js +1 -1
- package/package.json +1 -1
package/.release-it.json
CHANGED
package/README.md
CHANGED
|
@@ -56,6 +56,7 @@ export default {
|
|
|
56
56
|
| value | true | - | |
|
|
57
57
|
| presets | true | - | Array of [Style-Presets](#Style-Preset) |
|
|
58
58
|
| default-preset-id | true | - | Id of any preset |
|
|
59
|
+
| base-preset-class | true | - | Base class of preset |
|
|
59
60
|
| make-preset-variable | true | - | [Generates](#Generate-Preset-Variable) name of preset variable |
|
|
60
61
|
| fonts | true | - | Array of [fonts](#Font) |
|
|
61
62
|
| device | false | `'desktop'` | Active device type. can be 'mobile', 'tablet' or 'desktop' |
|
|
@@ -72,6 +73,7 @@ interface StylePreset {
|
|
|
72
73
|
name: string;
|
|
73
74
|
hidden: boolean;
|
|
74
75
|
node?: PresetNode;
|
|
76
|
+
fallbackClass?: string;
|
|
75
77
|
common: CommonSettings;
|
|
76
78
|
desktop: DeviceSettings;
|
|
77
79
|
tablet: DeviceSettings;
|
|
@@ -88,7 +90,7 @@ interface CommonSettings {
|
|
|
88
90
|
font_weight: string;
|
|
89
91
|
color: string;
|
|
90
92
|
font_style: 'italic' | 'normal';
|
|
91
|
-
text_decoration: 'none' | '
|
|
93
|
+
text_decoration: 'none' | 'underline';
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
interface DeviceSettings {
|
package/example/ExampleApp.vue
CHANGED
|
@@ -12,11 +12,16 @@
|
|
|
12
12
|
</option>
|
|
13
13
|
</select>
|
|
14
14
|
|
|
15
|
+
<button type="button" class="zw-load-content" @click="loadContent">
|
|
16
|
+
Load Content
|
|
17
|
+
</button>
|
|
18
|
+
|
|
15
19
|
<span>Deployed at {{ updatedAt }}</span>
|
|
16
20
|
|
|
17
21
|
<Wysiwyg
|
|
18
22
|
v-model="content"
|
|
19
|
-
base-list-class="
|
|
23
|
+
base-list-class="zpa-text__list--"
|
|
24
|
+
base-preset-class="zp ts-"
|
|
20
25
|
default-preset-id="regular-1"
|
|
21
26
|
ref="wswgRef"
|
|
22
27
|
:fonts="fonts"
|
|
@@ -36,6 +41,16 @@ import { Wysiwyg } from '../lib';
|
|
|
36
41
|
import { FONTS } from './fonts';
|
|
37
42
|
import { PRESETS, renderPresetVariable } from './presets';
|
|
38
43
|
|
|
44
|
+
function getInitialContent() {
|
|
45
|
+
const data = sessionStorage.getItem('wswg-data');
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(data);
|
|
49
|
+
} catch (_) {
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
39
54
|
export default {
|
|
40
55
|
name: 'ExampleApp',
|
|
41
56
|
|
|
@@ -47,29 +62,7 @@ export default {
|
|
|
47
62
|
|
|
48
63
|
setup() {
|
|
49
64
|
const wswgRef = ref(null);
|
|
50
|
-
const content = ref(
|
|
51
|
-
type: 'doc',
|
|
52
|
-
content: [
|
|
53
|
-
{
|
|
54
|
-
type: 'paragraph',
|
|
55
|
-
content: [
|
|
56
|
-
{
|
|
57
|
-
type: 'text',
|
|
58
|
-
text: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cupiditate deleniti doloremque doloribus nobis praesentium repellendus soluta. Ab distinctio eos eveniet quaerat quia recusandae repellendus. Error optio perferendis qui sapiente sequi.'
|
|
59
|
-
}
|
|
60
|
-
]
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
type: 'paragraph',
|
|
64
|
-
content: [
|
|
65
|
-
{
|
|
66
|
-
type: 'text',
|
|
67
|
-
text: 'Ab et laborum molestias omnis provident soluta suscipit tenetur voluptatum! Accusamus aliquam asperiores blanditiis dolorum enim, inventore ipsa molestias, omnis optio quod rem repellendus reprehenderit saepe, suscipit tempora ullam voluptatibus?'
|
|
68
|
-
}
|
|
69
|
-
]
|
|
70
|
-
}
|
|
71
|
-
]
|
|
72
|
-
});
|
|
65
|
+
const content = ref(getInitialContent());
|
|
73
66
|
const presets = ref(PRESETS);
|
|
74
67
|
const device = ref('desktop');
|
|
75
68
|
const updatedAt = new Date(ZW_UPDATED_AT).toLocaleString('ua-UA');
|
|
@@ -91,6 +84,16 @@ export default {
|
|
|
91
84
|
|
|
92
85
|
onMounted(() => window.tiptap = wswgRef.value.editor);
|
|
93
86
|
|
|
87
|
+
function loadContent() {
|
|
88
|
+
const defaultValue = sessionStorage.getItem('wswg-data');
|
|
89
|
+
const content = prompt('Insert editor content', defaultValue);
|
|
90
|
+
|
|
91
|
+
if (content) {
|
|
92
|
+
sessionStorage.setItem('wswg-data', content);
|
|
93
|
+
window.location.reload();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
94
97
|
return {
|
|
95
98
|
wswgRef,
|
|
96
99
|
content,
|
|
@@ -98,6 +101,7 @@ export default {
|
|
|
98
101
|
structurePreview,
|
|
99
102
|
favoriteColors,
|
|
100
103
|
updateFavoriteColors,
|
|
104
|
+
loadContent,
|
|
101
105
|
device,
|
|
102
106
|
updatedAt,
|
|
103
107
|
presets
|
|
@@ -115,7 +119,11 @@ body {
|
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
.zw-device-switcher {
|
|
118
|
-
margin-bottom:
|
|
122
|
+
margin-bottom: 75px;
|
|
123
|
+
margin-right: 20px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.zw-load-content {
|
|
119
127
|
margin-right: 20px;
|
|
120
128
|
}
|
|
121
129
|
|
|
@@ -127,10 +135,10 @@ body {
|
|
|
127
135
|
overflow-x: scroll;
|
|
128
136
|
}
|
|
129
137
|
|
|
130
|
-
.
|
|
131
|
-
.
|
|
132
|
-
.
|
|
133
|
-
.
|
|
134
|
-
.
|
|
135
|
-
.
|
|
138
|
+
.zpa-text__list--decimal { list-style-type: decimal }
|
|
139
|
+
.zpa-text__list--disc { list-style-type: disc }
|
|
140
|
+
.zpa-text__list--circle { list-style-type: circle }
|
|
141
|
+
.zpa-text__list--square { list-style-type: square }
|
|
142
|
+
.zpa-text__list--latin { list-style-type: upper-roman }
|
|
143
|
+
.zpa-text__list--roman { list-style-type: upper-latin }
|
|
136
144
|
</style>
|
package/example/example.js
CHANGED
|
@@ -12,6 +12,32 @@ import { tooltipManager } from './tooltip';
|
|
|
12
12
|
|
|
13
13
|
Vue.use(CompositionAPI);
|
|
14
14
|
|
|
15
|
+
if (!sessionStorage.getItem('wswg-data')) {
|
|
16
|
+
sessionStorage.setItem('wswg-data', JSON.stringify({
|
|
17
|
+
type: 'doc',
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: 'paragraph',
|
|
21
|
+
content: [
|
|
22
|
+
{
|
|
23
|
+
type: 'text',
|
|
24
|
+
text: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cupiditate deleniti doloremque doloribus nobis praesentium repellendus soluta. Ab distinctio eos eveniet quaerat quia recusandae repellendus. Error optio perferendis qui sapiente sequi.'
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: 'paragraph',
|
|
30
|
+
content: [
|
|
31
|
+
{
|
|
32
|
+
type: 'text',
|
|
33
|
+
text: 'Ab et laborum molestias omnis provident soluta suscipit tenetur voluptatum! Accusamus aliquam asperiores blanditiis dolorum enim, inventore ipsa molestias, omnis optio quod rem repellendus reprehenderit saepe, suscipit tempora ullam voluptatibus?'
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
|
|
15
41
|
new Vue({
|
|
16
42
|
el: '[data-app]',
|
|
17
43
|
render: (h) => h(ExampleApp)
|
package/example/presets.js
CHANGED
|
@@ -175,6 +175,7 @@ export const PRESETS = [
|
|
|
175
175
|
{
|
|
176
176
|
'id': 'regular-2',
|
|
177
177
|
'name': 'Regular 2',
|
|
178
|
+
'fallbackClass': 'zpa-regular2',
|
|
178
179
|
'desktop': {
|
|
179
180
|
'alignment': 'left',
|
|
180
181
|
'line_height': '1.43',
|
|
@@ -201,6 +202,7 @@ export const PRESETS = [
|
|
|
201
202
|
{
|
|
202
203
|
'id': 'regular-3',
|
|
203
204
|
'name': 'Regular 3',
|
|
205
|
+
'fallbackClass': 'zpa-regular3',
|
|
204
206
|
'desktop': {
|
|
205
207
|
'alignment': 'left',
|
|
206
208
|
'line_height': '1.43',
|
package/lib/Wysiwyg.vue
CHANGED
|
@@ -57,6 +57,11 @@ export default {
|
|
|
57
57
|
required: true
|
|
58
58
|
},
|
|
59
59
|
|
|
60
|
+
basePresetClass: {
|
|
61
|
+
type: String,
|
|
62
|
+
required: true
|
|
63
|
+
},
|
|
64
|
+
|
|
60
65
|
makePresetVariable: {
|
|
61
66
|
type: Function,
|
|
62
67
|
required: true
|
|
@@ -121,6 +126,7 @@ export default {
|
|
|
121
126
|
presetsRef: toRef(props, 'presets'),
|
|
122
127
|
defaultPresetId: props.defaultPresetId,
|
|
123
128
|
makePresetVariable: props.makePresetVariable,
|
|
129
|
+
basePresetClass: props.basePresetClass,
|
|
124
130
|
baseListClass: props.baseListClass,
|
|
125
131
|
deviceRef: toRef(props, 'device')
|
|
126
132
|
})
|
|
@@ -7,20 +7,21 @@ import { NodeFactory } from '../../__tests__/utils';
|
|
|
7
7
|
|
|
8
8
|
const TestComponent = {
|
|
9
9
|
name: 'Test',
|
|
10
|
+
|
|
10
11
|
render() {
|
|
11
12
|
return h(EditorContent, {
|
|
12
13
|
props: { editor: this.editor }
|
|
13
14
|
});
|
|
14
15
|
},
|
|
16
|
+
|
|
15
17
|
props: ['value'],
|
|
16
18
|
|
|
17
19
|
setup(props, { emit }) {
|
|
18
20
|
const editor = useEditor({
|
|
19
21
|
content: toRef(props, 'value'),
|
|
20
22
|
extensions: CORE_EXTENSIONS,
|
|
21
|
-
onChange: (content) =>
|
|
22
|
-
|
|
23
|
-
}
|
|
23
|
+
onChange: (content) => emit('input', content),
|
|
24
|
+
onFocus: () => emit('focus')
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
return { editor };
|
|
@@ -64,4 +65,12 @@ describe('life cycle', () => {
|
|
|
64
65
|
|
|
65
66
|
expect(editor.isDestroyed).toBe(true);
|
|
66
67
|
});
|
|
68
|
+
|
|
69
|
+
test('should update content on value change', async () => {
|
|
70
|
+
const { editor, wrapper } = createComponent({ content: '<p>old</p>' });
|
|
71
|
+
|
|
72
|
+
await wrapper.setProps({ value: '<p>new</p>' });
|
|
73
|
+
|
|
74
|
+
expect(editor.getHTML()).toBe('<p class="zw-style">new</p>');
|
|
75
|
+
});
|
|
67
76
|
});
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
import { Editor } from '@tiptap/vue-2';
|
|
2
|
-
import { onUnmounted, watch } from '@vue/composition-api';
|
|
2
|
+
import { onUnmounted, watch, reactive } from '@vue/composition-api';
|
|
3
|
+
import { ContentNormalizer } from '../services';
|
|
3
4
|
|
|
4
5
|
export function useEditor({ content, onChange, onFocus, extensions }) {
|
|
5
|
-
const
|
|
6
|
-
|
|
6
|
+
const normalizer = new ContentNormalizer();
|
|
7
|
+
|
|
8
|
+
const editor = reactive(new Editor({
|
|
9
|
+
content: normalizer.normalize(content.value),
|
|
7
10
|
onUpdate: () => onChange(editor.getJSON()),
|
|
8
11
|
onFocus: () => onFocus(),
|
|
9
12
|
extensions
|
|
10
|
-
});
|
|
13
|
+
}));
|
|
11
14
|
|
|
12
15
|
onUnmounted(() => editor.destroy());
|
|
13
16
|
|
|
14
17
|
watch(content, (value) => {
|
|
15
18
|
const isChanged = JSON.stringify(editor.getJSON()) !== JSON.stringify(value);
|
|
16
19
|
|
|
17
|
-
if (isChanged)
|
|
20
|
+
if (isChanged) {
|
|
21
|
+
const content = normalizer.normalize(value);
|
|
22
|
+
|
|
23
|
+
editor.commands.setContent(content, false);
|
|
24
|
+
}
|
|
18
25
|
});
|
|
19
26
|
|
|
20
27
|
return editor;
|
|
@@ -19,6 +19,12 @@ export const Alignment = Extension.create({
|
|
|
19
19
|
[TextSettings.ALIGNMENT]: {
|
|
20
20
|
isRequired: false,
|
|
21
21
|
|
|
22
|
+
parseHTML(el) {
|
|
23
|
+
const value = el.style.textAlign;
|
|
24
|
+
|
|
25
|
+
return value ? { desktop: value, tablet: value, mobile: value } : null;
|
|
26
|
+
},
|
|
27
|
+
|
|
22
28
|
renderHTML(attrs) {
|
|
23
29
|
if (!attrs.alignment) return null;
|
|
24
30
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Mark } from '@tiptap/vue-2';
|
|
2
2
|
import { computed } from '@vue/composition-api';
|
|
3
|
-
import { createCommand, renderMark } from '../utils';
|
|
3
|
+
import { convertColor, createCommand, renderMark } from '../utils';
|
|
4
4
|
import { TextSettings } from '../enums';
|
|
5
5
|
|
|
6
6
|
export const BackgroundColor = Mark.create({
|
|
@@ -22,6 +22,13 @@ export const BackgroundColor = Mark.create({
|
|
|
22
22
|
};
|
|
23
23
|
},
|
|
24
24
|
|
|
25
|
+
parseHTML: () => [
|
|
26
|
+
{
|
|
27
|
+
style: 'background-color',
|
|
28
|
+
getAttrs: (value) => ({ value: convertColor(value) })
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
|
|
25
32
|
renderHTML({ HTMLAttributes: attrs }) {
|
|
26
33
|
return renderMark({ background_color: attrs.value });
|
|
27
34
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Mark } from '@tiptap/vue-2';
|
|
2
2
|
import { computed } from '@vue/composition-api';
|
|
3
|
-
import { createCommand, renderMark } from '../utils';
|
|
3
|
+
import { convertColor, createCommand, renderMark } from '../utils';
|
|
4
4
|
import { TextSettings } from '../enums';
|
|
5
5
|
|
|
6
6
|
export const FontColor = Mark.create({
|
|
@@ -30,6 +30,13 @@ export const FontColor = Mark.create({
|
|
|
30
30
|
};
|
|
31
31
|
},
|
|
32
32
|
|
|
33
|
+
parseHTML: () => [
|
|
34
|
+
{
|
|
35
|
+
style: 'color',
|
|
36
|
+
getAttrs: (value) => ({ value: convertColor(value) })
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
|
|
33
40
|
renderHTML({ HTMLAttributes: attrs }) {
|
|
34
41
|
return renderMark({ font_color: attrs.value });
|
|
35
42
|
}
|
|
@@ -56,6 +56,13 @@ export const FontFamily = Mark.create({
|
|
|
56
56
|
};
|
|
57
57
|
},
|
|
58
58
|
|
|
59
|
+
parseHTML: () => [
|
|
60
|
+
{
|
|
61
|
+
style: 'font-family',
|
|
62
|
+
getAttrs: (value) => ({ value })
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
|
|
59
66
|
renderHTML({ HTMLAttributes: attrs }) {
|
|
60
67
|
return renderMark({ font_family: attrs.value });
|
|
61
68
|
}
|
|
@@ -62,6 +62,18 @@ export const FontSize = Mark.create({
|
|
|
62
62
|
'Mod-Shift--': createKeyboardShortcut('decreaseFontSize')
|
|
63
63
|
}),
|
|
64
64
|
|
|
65
|
+
parseHTML: () => [
|
|
66
|
+
{
|
|
67
|
+
style: 'font-size',
|
|
68
|
+
|
|
69
|
+
getAttrs: (input) => {
|
|
70
|
+
const value = String(parseInt(input));
|
|
71
|
+
|
|
72
|
+
return { desktop: value, tablet: value, mobile: value };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
|
|
65
77
|
renderHTML({ HTMLAttributes: attrs }) {
|
|
66
78
|
const addUnits = (value) => value ? `${value}px` : null;
|
|
67
79
|
|
|
@@ -56,6 +56,17 @@ export const FontStyle = Mark.create({
|
|
|
56
56
|
'Mod-I': createKeyboardShortcut('toggleItalic')
|
|
57
57
|
}),
|
|
58
58
|
|
|
59
|
+
parseHTML: () => [
|
|
60
|
+
{
|
|
61
|
+
tag: 'i',
|
|
62
|
+
attrs: { italic: true }
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
style: 'font-style',
|
|
66
|
+
getAttrs: (value) => ({ italic: value.includes('italic') })
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
|
|
59
70
|
renderHTML() {
|
|
60
71
|
return renderMark({ font_style: 'italic' });
|
|
61
72
|
}
|
|
@@ -33,8 +33,17 @@ export const FontWeight = Mark.create({
|
|
|
33
33
|
|
|
34
34
|
getFontWeight: createCommand(({ editor, commands }) => {
|
|
35
35
|
const defaultValue = commands.getDefaultFontWeight();
|
|
36
|
+
const font = commands.getFont();
|
|
36
37
|
|
|
37
|
-
return computed(() =>
|
|
38
|
+
return computed(() => {
|
|
39
|
+
const weight = editor.getAttributes(this.name)?.value ?? defaultValue.value;
|
|
40
|
+
|
|
41
|
+
if (font.value.isWeightSupported(weight)) {
|
|
42
|
+
return weight;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return font.value.findClosestWeight(weight);
|
|
46
|
+
});
|
|
38
47
|
}),
|
|
39
48
|
|
|
40
49
|
getDefaultFontWeight: createCommand(({ commands }) => {
|
|
@@ -50,6 +59,21 @@ export const FontWeight = Mark.create({
|
|
|
50
59
|
'Mod-B': createKeyboardShortcut('toggleBold')
|
|
51
60
|
}),
|
|
52
61
|
|
|
62
|
+
parseHTML: () => [
|
|
63
|
+
{
|
|
64
|
+
tag: 'b',
|
|
65
|
+
attrs: { value: '700' }
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
tag: 'strong',
|
|
69
|
+
attrs: { value: '700' }
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
style: 'font-weight',
|
|
73
|
+
getAttrs: (value) => ({ value })
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
|
|
53
77
|
renderHTML({ HTMLAttributes: attrs }) {
|
|
54
78
|
return renderMark({ font_weight: attrs.value });
|
|
55
79
|
}
|
|
@@ -19,6 +19,23 @@ export const LineHeight = Extension.create({
|
|
|
19
19
|
[TextSettings.LINE_HEIGHT]: {
|
|
20
20
|
isRequired: false,
|
|
21
21
|
|
|
22
|
+
parseHTML(element) {
|
|
23
|
+
const value = element.style.lineHeight;
|
|
24
|
+
|
|
25
|
+
if (!value) return null;
|
|
26
|
+
|
|
27
|
+
if (!value.includes('px')) {
|
|
28
|
+
return { desktop: value, tablet: value, mobile: value };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// element is not connected to window so getComputedStyle is not working on element
|
|
32
|
+
const childFontSize = element.firstElementChild.style.fontSize;
|
|
33
|
+
const fontSize = childFontSize || window.getComputedStyle(document.body).fontSize;
|
|
34
|
+
const relative = (parseFloat(value) / parseFloat(fontSize)).toFixed(2);
|
|
35
|
+
|
|
36
|
+
return { desktop: relative, tablet: relative, mobile: relative };
|
|
37
|
+
},
|
|
38
|
+
|
|
22
39
|
renderHTML(attrs) {
|
|
23
40
|
if (!attrs.line_height) return null;
|
|
24
41
|
|
|
@@ -4,6 +4,12 @@ import { Heading } from '@tiptap/extension-heading';
|
|
|
4
4
|
import { createCommand } from '../utils';
|
|
5
5
|
import { Devices, NodeTypes, TextSettings } from '../enums';
|
|
6
6
|
|
|
7
|
+
function makePresetClass(base, preset) {
|
|
8
|
+
const baseClass = base.split(' ').map((part) => `.${part}`).join('');
|
|
9
|
+
|
|
10
|
+
return baseClass + preset.id;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
export const StylePreset = Extension.create({
|
|
8
14
|
name: 'style_preset',
|
|
9
15
|
|
|
@@ -17,6 +23,7 @@ export const StylePreset = Extension.create({
|
|
|
17
23
|
addOptions: () => ({
|
|
18
24
|
presetsRef: null,
|
|
19
25
|
defaultId: null,
|
|
26
|
+
baseClass: null,
|
|
20
27
|
makeVariable: null
|
|
21
28
|
}),
|
|
22
29
|
|
|
@@ -31,11 +38,30 @@ export const StylePreset = Extension.create({
|
|
|
31
38
|
attributes: {
|
|
32
39
|
preset: {
|
|
33
40
|
isRequired: false,
|
|
34
|
-
default:
|
|
41
|
+
default: null,
|
|
42
|
+
|
|
43
|
+
parseHTML: (element) => {
|
|
44
|
+
const presets = this.options.presetsRef.value;
|
|
45
|
+
|
|
46
|
+
for (const { id, node, fallbackClass } of presets) {
|
|
47
|
+
const isFallback = fallbackClass && element.classList.contains(fallbackClass);
|
|
48
|
+
|
|
49
|
+
if (isFallback) return { id };
|
|
50
|
+
|
|
51
|
+
const className = makePresetClass(this.options.baseClass, { id });
|
|
52
|
+
|
|
53
|
+
if (element.matches(className)) return { id };
|
|
54
|
+
if (element.tagName === `H${node?.level}`) return { id };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Matches all paragraphs without classes to default preset
|
|
58
|
+
return element.tagName === 'P' ? { id: this.options.defaultId } : null;
|
|
59
|
+
},
|
|
60
|
+
|
|
35
61
|
renderHTML: (attrs) => {
|
|
36
62
|
if (!attrs.preset) return null;
|
|
37
63
|
|
|
38
|
-
return { class:
|
|
64
|
+
return { class: this.options.baseClass + attrs.preset.id };
|
|
39
65
|
}
|
|
40
66
|
}
|
|
41
67
|
}
|
|
@@ -147,7 +173,8 @@ export const StylePreset = Extension.create({
|
|
|
147
173
|
this.storage.presetStyleEl.dataset.zwStyles = '';
|
|
148
174
|
|
|
149
175
|
for (const preset of this.options.presetsRef.value) {
|
|
150
|
-
const
|
|
176
|
+
const className = makePresetClass(this.options.baseClass, preset);
|
|
177
|
+
const css = [` ${className} {`];
|
|
151
178
|
|
|
152
179
|
for (const device of Devices.values) {
|
|
153
180
|
for (const setting of Object.keys(preset[device])) {
|
|
@@ -86,6 +86,17 @@ export const TextDecoration = Mark.create({
|
|
|
86
86
|
'Mod-U': createKeyboardShortcut('toggleUnderline')
|
|
87
87
|
}),
|
|
88
88
|
|
|
89
|
+
parseHTML: () => [
|
|
90
|
+
{
|
|
91
|
+
style: 'text-decoration-line',
|
|
92
|
+
|
|
93
|
+
getAttrs: (value) => ({
|
|
94
|
+
underline: value.includes('underline'),
|
|
95
|
+
strike_through: value.includes('line-through')
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
|
|
89
100
|
renderHTML({ HTMLAttributes: attrs }) {
|
|
90
101
|
const decorations = [];
|
|
91
102
|
|
|
@@ -6,6 +6,7 @@ import { CORE_EXTENSIONS } from '../core';
|
|
|
6
6
|
import { Alignment } from '../Alignment';
|
|
7
7
|
import { DeviceManager } from '../DeviceManager';
|
|
8
8
|
import { Alignments } from '../../enums';
|
|
9
|
+
import { ContentNormalizer } from '../../services';
|
|
9
10
|
|
|
10
11
|
const MockStylePreset = Extension.create({
|
|
11
12
|
name: 'style_preset',
|
|
@@ -22,8 +23,10 @@ const MockStylePreset = Extension.create({
|
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
function createEditor({ content }) {
|
|
26
|
+
const normalizer = new ContentNormalizer();
|
|
27
|
+
|
|
25
28
|
return new Editor({
|
|
26
|
-
content,
|
|
29
|
+
content: normalizer.normalize(content),
|
|
27
30
|
extensions: CORE_EXTENSIONS.concat(
|
|
28
31
|
MockStylePreset,
|
|
29
32
|
DeviceManager.configure({ deviceRef: ref('desktop') }),
|
|
@@ -105,3 +108,21 @@ describe('rendering', () => {
|
|
|
105
108
|
expect(editor.getHTML()).toMatchSnapshot();
|
|
106
109
|
});
|
|
107
110
|
});
|
|
111
|
+
|
|
112
|
+
describe('parsing html', () => {
|
|
113
|
+
test('should get alignment from text', () => {
|
|
114
|
+
const editor = createEditor({
|
|
115
|
+
content: '<p style="text-align:center">test</p>'
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(editor.getJSON()).toMatchSnapshot();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('should set null if no alignment', () => {
|
|
122
|
+
const editor = createEditor({
|
|
123
|
+
content: '<p>test</p>'
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(editor.getJSON()).toMatchSnapshot();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -4,6 +4,7 @@ import { NodeFactory } from '../../__tests__/utils';
|
|
|
4
4
|
import { createCommand } from '../../utils';
|
|
5
5
|
import { CORE_EXTENSIONS } from '../core';
|
|
6
6
|
import { BackgroundColor } from '../BackgroundColor';
|
|
7
|
+
import { ContentNormalizer } from '../../services';
|
|
7
8
|
|
|
8
9
|
const MockStylePreset = Extension.create({
|
|
9
10
|
name: 'style_preset',
|
|
@@ -18,8 +19,10 @@ const MockStylePreset = Extension.create({
|
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
function createEditor({ content }) {
|
|
22
|
+
const normalizer = new ContentNormalizer();
|
|
23
|
+
|
|
21
24
|
return new Editor({
|
|
22
|
-
content,
|
|
25
|
+
content: normalizer.normalize(content),
|
|
23
26
|
extensions: CORE_EXTENSIONS.concat(MockStylePreset, BackgroundColor)
|
|
24
27
|
});
|
|
25
28
|
}
|
|
@@ -73,3 +76,29 @@ describe('rendering', () => {
|
|
|
73
76
|
expect(editor.getHTML()).toMatchSnapshot();
|
|
74
77
|
});
|
|
75
78
|
});
|
|
79
|
+
|
|
80
|
+
describe('parsing html', () => {
|
|
81
|
+
test('should get value from paragraph', () => {
|
|
82
|
+
const editor = createEditor({
|
|
83
|
+
content: '<p style="background-color: red">test</p>'
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(editor.getJSON()).toMatchSnapshot();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('should get value from text', () => {
|
|
90
|
+
const editor = createEditor({
|
|
91
|
+
content: '<p><span style="background-color: red">lorem</span> ipsum</p>'
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(editor.getJSON()).toMatchSnapshot();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('should merge paragraph and text settings', () => {
|
|
98
|
+
const editor = createEditor({
|
|
99
|
+
content: '<p style="background-color: red"><span style="background-color: #000">lorem</span> ipsum</p>'
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(editor.getJSON()).toMatchSnapshot();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -3,10 +3,13 @@ import { NodeFactory } from '../../__tests__/utils';
|
|
|
3
3
|
import { CaseStyles } from '../../enums';
|
|
4
4
|
import { CORE_EXTENSIONS } from '../core';
|
|
5
5
|
import { CaseStyle } from '../CaseStyle';
|
|
6
|
+
import { ContentNormalizer } from '../../services';
|
|
6
7
|
|
|
7
8
|
function createEditor({ content }) {
|
|
9
|
+
const normalizer = new ContentNormalizer();
|
|
10
|
+
|
|
8
11
|
return new Editor({
|
|
9
|
-
content,
|
|
12
|
+
content: normalizer.normalize(content),
|
|
10
13
|
extensions: CORE_EXTENSIONS.concat(CaseStyle)
|
|
11
14
|
});
|
|
12
15
|
}
|