@zipify/wysiwyg 1.0.0-dev.1 → 1.0.0-dev.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 (98) hide show
  1. package/.env.example +1 -0
  2. package/.eslintignore +1 -0
  3. package/.release-it.json +9 -0
  4. package/.stylelintignore +1 -0
  5. package/README.md +131 -11
  6. package/config/jest/setupTests.js +7 -1
  7. package/config/webpack/example.config.js +2 -0
  8. package/config/webpack/lib.config.js +48 -0
  9. package/config/webpack/loaders/style-loader.js +3 -1
  10. package/config/webpack/loaders/svg-loader.js +1 -1
  11. package/dist/wysiwyg.css +837 -0
  12. package/dist/wysiwyg.js +38655 -0
  13. package/example/ExampleApp.vue +47 -32
  14. package/example/example.js +26 -0
  15. package/example/presets.js +2 -0
  16. package/lib/Wysiwyg.vue +17 -17
  17. package/lib/assets/icons/alignment-center.svg +3 -0
  18. package/lib/assets/icons/alignment-justify.svg +3 -0
  19. package/lib/assets/icons/alignment-left.svg +3 -0
  20. package/lib/assets/icons/alignment-right.svg +3 -0
  21. package/lib/assets/icons/arrow.svg +3 -0
  22. package/lib/assets/icons/background-color.svg +3 -0
  23. package/lib/assets/icons/case-style.svg +3 -0
  24. package/lib/assets/icons/font-color.svg +5 -0
  25. package/lib/assets/icons/italic.svg +3 -0
  26. package/lib/assets/icons/line-height.svg +3 -0
  27. package/lib/assets/icons/list-circle.svg +3 -0
  28. package/lib/assets/icons/list-decimal.svg +3 -0
  29. package/lib/assets/icons/list-disc.svg +3 -0
  30. package/lib/assets/icons/list-latin.svg +3 -0
  31. package/lib/assets/icons/list-roman.svg +3 -0
  32. package/lib/assets/icons/list-square.svg +3 -0
  33. package/lib/assets/icons/remove-format.svg +3 -0
  34. package/lib/assets/icons/reset-styles.svg +3 -0
  35. package/lib/assets/icons/strike-through.svg +3 -0
  36. package/lib/assets/icons/superscript.svg +3 -0
  37. package/lib/assets/icons/underline.svg +3 -0
  38. package/lib/components/base/Icon.vue +17 -9
  39. package/lib/components/base/__tests__/Icon.test.js +6 -13
  40. package/lib/components/toolbar/Toolbar.vue +1 -1
  41. package/lib/composables/__tests__/useEditor.test.js +12 -3
  42. package/lib/composables/useEditor.js +13 -7
  43. package/lib/composables/useToolbar.js +7 -18
  44. package/lib/extensions/Alignment.js +6 -0
  45. package/lib/extensions/BackgroundColor.js +8 -1
  46. package/lib/extensions/FontColor.js +8 -1
  47. package/lib/extensions/FontFamily.js +7 -0
  48. package/lib/extensions/FontSize.js +12 -0
  49. package/lib/extensions/FontStyle.js +11 -0
  50. package/lib/extensions/FontWeight.js +25 -1
  51. package/lib/extensions/LineHeight.js +17 -0
  52. package/lib/extensions/StylePreset.js +30 -3
  53. package/lib/extensions/TextDecoration.js +11 -0
  54. package/lib/extensions/__tests__/Alignment.test.js +22 -1
  55. package/lib/extensions/__tests__/BackgroundColor.test.js +30 -1
  56. package/lib/extensions/__tests__/CaseStyle.test.js +4 -1
  57. package/lib/extensions/__tests__/FontColor.test.js +30 -1
  58. package/lib/extensions/__tests__/FontFamily.test.js +30 -1
  59. package/lib/extensions/__tests__/FontSize.test.js +30 -1
  60. package/lib/extensions/__tests__/FontStyle.test.js +38 -1
  61. package/lib/extensions/__tests__/FontWeight.test.js +58 -1
  62. package/lib/extensions/__tests__/LineHeight.test.js +41 -1
  63. package/lib/extensions/__tests__/StylePreset.test.js +76 -1
  64. package/lib/extensions/__tests__/TextDecoration.test.js +63 -1
  65. package/lib/extensions/__tests__/__snapshots__/Alignment.test.js.snap +44 -0
  66. package/lib/extensions/__tests__/__snapshots__/BackgroundColor.test.js.snap +91 -0
  67. package/lib/extensions/__tests__/__snapshots__/FontColor.test.js.snap +91 -0
  68. package/lib/extensions/__tests__/__snapshots__/FontFamily.test.js.snap +91 -0
  69. package/lib/extensions/__tests__/__snapshots__/FontSize.test.js.snap +99 -0
  70. package/lib/extensions/__tests__/__snapshots__/FontStyle.test.js.snap +120 -0
  71. package/lib/extensions/__tests__/__snapshots__/FontWeight.test.js.snap +149 -0
  72. package/lib/extensions/__tests__/__snapshots__/LineHeight.test.js.snap +92 -0
  73. package/lib/extensions/__tests__/__snapshots__/StylePreset.test.js.snap +167 -2
  74. package/lib/extensions/__tests__/__snapshots__/TextDecoration.test.js.snap +207 -0
  75. package/lib/extensions/core/__tests__/NodeProcessor.test.js +4 -1
  76. package/lib/extensions/core/__tests__/SelectionProcessor.test.js +9 -4
  77. package/lib/extensions/core/__tests__/TextProcessor.test.js +4 -1
  78. package/lib/extensions/index.js +1 -0
  79. package/lib/extensions/list/List.js +34 -0
  80. package/lib/extensions/list/__tests__/List.test.js +115 -3
  81. package/lib/extensions/list/__tests__/__snapshots__/List.test.js.snap +457 -6
  82. package/lib/services/ContentNormalizer.js +113 -0
  83. package/lib/services/Storage.js +1 -13
  84. package/lib/services/__tests__/ContentNormalizer.test.js +41 -0
  85. package/lib/services/__tests__/FavoriteColors.test.js +20 -0
  86. package/lib/services/__tests__/JsonSerializer.test.js +23 -0
  87. package/lib/services/__tests__/Storage.test.js +79 -0
  88. package/lib/services/index.js +1 -0
  89. package/lib/utils/__tests__/convertColor.test.js +19 -0
  90. package/lib/utils/__tests__/createKeyboardShortcut.test.js +25 -0
  91. package/lib/utils/__tests__/renderInlineSetting.test.js +26 -0
  92. package/lib/utils/convertColor.js +7 -0
  93. package/lib/utils/importIcon.js +12 -0
  94. package/lib/utils/index.js +2 -0
  95. package/lib/utils/renderInlineSetting.js +1 -1
  96. package/package.json +10 -6
  97. package/lib/assets/icons.svg +0 -69
  98. package/lib/composables/__tests__/useToolbar.test.js +0 -56
@@ -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="zw-list--"
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"
@@ -24,6 +29,7 @@
24
29
  :make-preset-variable="$options.makePresetVariable"
25
30
  :favorite-colors="favoriteColors"
26
31
  :device="device"
32
+ :active="isActive"
27
33
  @updateFavoriteColors="updateFavoriteColors"
28
34
  />
29
35
  <pre class="zw-content-structure" v-html="structurePreview" />
@@ -36,6 +42,16 @@ import { Wysiwyg } from '../lib';
36
42
  import { FONTS } from './fonts';
37
43
  import { PRESETS, renderPresetVariable } from './presets';
38
44
 
45
+ function getInitialContent() {
46
+ const data = sessionStorage.getItem('wswg-data');
47
+
48
+ try {
49
+ return JSON.parse(data);
50
+ } catch (_) {
51
+ return data;
52
+ }
53
+ }
54
+
39
55
  export default {
40
56
  name: 'ExampleApp',
41
57
 
@@ -47,32 +63,11 @@ export default {
47
63
 
48
64
  setup() {
49
65
  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
- });
66
+ const content = ref(getInitialContent());
73
67
  const presets = ref(PRESETS);
74
68
  const device = ref('desktop');
75
69
  const updatedAt = new Date(ZW_UPDATED_AT).toLocaleString('ua-UA');
70
+ const isActive = ref(false);
76
71
 
77
72
  const structurePreview = computed(() => {
78
73
  const json = JSON.stringify(content.value, null, ' ');
@@ -91,6 +86,20 @@ export default {
91
86
 
92
87
  onMounted(() => window.tiptap = wswgRef.value.editor);
93
88
 
89
+ function loadContent() {
90
+ const defaultValue = sessionStorage.getItem('wswg-data');
91
+ const content = prompt('Insert editor content', defaultValue);
92
+
93
+ if (content) {
94
+ sessionStorage.setItem('wswg-data', content);
95
+ window.location.reload();
96
+ }
97
+ }
98
+
99
+ document.body.addEventListener('click', (event) => {
100
+ isActive.value = wswgRef.value.$el.contains(event.target);
101
+ });
102
+
94
103
  return {
95
104
  wswgRef,
96
105
  content,
@@ -98,9 +107,11 @@ export default {
98
107
  structurePreview,
99
108
  favoriteColors,
100
109
  updateFavoriteColors,
110
+ loadContent,
101
111
  device,
102
112
  updatedAt,
103
- presets
113
+ presets,
114
+ isActive
104
115
  };
105
116
  }
106
117
  };
@@ -115,7 +126,11 @@ body {
115
126
  }
116
127
 
117
128
  .zw-device-switcher {
118
- margin-bottom: 50px;
129
+ margin-bottom: 75px;
130
+ margin-right: 20px;
131
+ }
132
+
133
+ .zw-load-content {
119
134
  margin-right: 20px;
120
135
  }
121
136
 
@@ -127,10 +142,10 @@ body {
127
142
  overflow-x: scroll;
128
143
  }
129
144
 
130
- .zw-list--decimal { list-style-type: decimal }
131
- .zw-list--disc { list-style-type: disc }
132
- .zw-list--circle { list-style-type: circle }
133
- .zw-list--square { list-style-type: square }
134
- .zw-list--latin { list-style-type: upper-roman }
135
- .zw-list--roman { list-style-type: upper-latin }
145
+ .zpa-text__list--decimal { list-style-type: decimal }
146
+ .zpa-text__list--disc { list-style-type: disc }
147
+ .zpa-text__list--circle { list-style-type: circle }
148
+ .zpa-text__list--square { list-style-type: square }
149
+ .zpa-text__list--latin { list-style-type: upper-roman }
150
+ .zpa-text__list--roman { list-style-type: upper-latin }
136
151
  </style>
@@ -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)
@@ -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
@@ -1,11 +1,7 @@
1
1
  <template>
2
- <div
3
- class="zw-wysiwyg"
4
- ref="wysiwygRef"
5
- v-out-click="{ onOutClick: hideToolbar, isDisabled: !toolbar.isShow.value }"
6
- >
2
+ <div class="zw-wysiwyg" ref="wysiwygRef">
7
3
  <Toolbar
8
- v-show="toolbar.isShow.value"
4
+ v-show="active"
9
5
  :editor="editor"
10
6
  :device="device"
11
7
  ref="toolbarRef"
@@ -54,8 +50,12 @@ export default {
54
50
 
55
51
  defaultPresetId: {
56
52
  type: [Number, String],
57
- required: false,
58
- default: ''
53
+ required: true
54
+ },
55
+
56
+ basePresetClass: {
57
+ type: String,
58
+ required: true
59
59
  },
60
60
 
61
61
  makePresetVariable: {
@@ -68,6 +68,11 @@ export default {
68
68
  required: true
69
69
  },
70
70
 
71
+ active: {
72
+ type: Boolean,
73
+ required: true
74
+ },
75
+
71
76
  device: {
72
77
  type: String,
73
78
  required: false,
@@ -102,7 +107,8 @@ export default {
102
107
  const toolbar = useToolbar({
103
108
  wrapperRef: wysiwygRef,
104
109
  popperRef: toolbarRef,
105
- offsets: toRef(props, 'toolbarOffsets').value
110
+ isActiveRef: toRef(props, 'active'),
111
+ offsets: props.toolbarOffsets
106
112
  });
107
113
 
108
114
  function onChange(content) {
@@ -113,7 +119,6 @@ export default {
113
119
  const editor = useEditor({
114
120
  content: toRef(props, 'value'),
115
121
  onChange: (content) => onChange(content),
116
- onFocus: () => toolbar.show(),
117
122
 
118
123
  extensions: getExtensions({
119
124
  fonts,
@@ -122,17 +127,12 @@ export default {
122
127
  presetsRef: toRef(props, 'presets'),
123
128
  defaultPresetId: props.defaultPresetId,
124
129
  makePresetVariable: props.makePresetVariable,
130
+ basePresetClass: props.basePresetClass,
125
131
  baseListClass: props.baseListClass,
126
132
  deviceRef: toRef(props, 'device')
127
133
  })
128
134
  });
129
135
 
130
- function hideToolbar() {
131
- if (editor.isFocused) return;
132
-
133
- toolbar.hide();
134
- }
135
-
136
136
  const fontSizes = new Array(MAX_FONT_SIZE - MIN_FONT_SIZE + 1)
137
137
  .fill(0)
138
138
  .map((_, index) => String(index + MIN_FONT_SIZE));
@@ -148,7 +148,7 @@ export default {
148
148
  provide(InjectionTokens.LOCAL_STORAGE, new Storage(localStorage));
149
149
  provide(InjectionTokens.FAVORITE_COLORS, favoriteColors);
150
150
 
151
- return { editor, toolbarRef, wysiwygRef, toolbar, hideToolbar };
151
+ return { editor, toolbarRef, wysiwygRef, toolbar };
152
152
  }
153
153
  };
154
154
  </script>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" fill-rule="evenodd" d="M23 7H5v2h18V7Zm-3 4H8v2h12v-2Zm3 4H5v2h18v-2ZM8 19h12v2H8v-2Z" clip-rule="evenodd"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" fill-rule="evenodd" d="M23 7H5v2h18V7Zm0 4H5v2h18v-2Zm0 4H5v2h18v-2ZM5 19h18v2H5v-2Z" clip-rule="evenodd"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" fill-rule="evenodd" d="M23 7H5v2h18V7Zm-6 4H5v2h12v-2Zm6 4H5v2h18v-2ZM5 19h12v2H5v-2Z" clip-rule="evenodd"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" fill-rule="evenodd" d="M23 7H5v2h18V7Zm0 4H11v2h12v-2Zm0 4H5v2h18v-2Zm-12 4h12v2H11v-2Z" clip-rule="evenodd"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 8 8">
2
+ <path fill="var(--zw-icon-foreground)" fill-rule="evenodd" d="m4 6.934 4-5H0l4 5Z" clip-rule="evenodd"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" fill-rule="evenodd" d="M22 20.62a2.42 2.42 0 0 1-4.84 0c0-1.25 2.42-4.54 2.42-4.54S22 19.37 22 20.62ZM9.92 15.425l1.452-3.951c.071-.182.145-.394.219-.636.074-.242.149-.503.226-.783a17.223 17.223 0 0 0 .454 1.402l1.452 3.968H9.919Zm5.411 4.199c.226-.795.658-1.684 1.184-2.562L12.955 8h-2.269L6 19.93h1.725a.736.736 0 0 0 .474-.157.792.792 0 0 0 .26-.347l.891-2.434h4.941l.891 2.434c.031.079.097.134.148.198Z" clip-rule="evenodd"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" fill-rule="evenodd" d="M16.64 19.93h-1.715a.75.75 0 0 1-.475-.145.82.82 0 0 1-.268-.359l-.89-2.433H8.35l-.891 2.433a.78.78 0 0 1-.26.347.73.73 0 0 1-.475.157H5L9.686 8h2.269l4.686 11.93Zm-7.72-4.505h3.803l-1.452-3.968a18.048 18.048 0 0 1-.219-.623c-.08-.24-.158-.5-.235-.78-.077.28-.152.542-.227.784a8.742 8.742 0 0 1-.218.635L8.92 15.425Zm14.968 4.505h-.915a.987.987 0 0 1-.454-.087c-.11-.058-.192-.175-.248-.35l-.181-.603a7.005 7.005 0 0 1-.631.507c-.206.146-.42.269-.64.368a3.26 3.26 0 0 1-.7.222c-.248.05-.523.075-.826.075-.357 0-.687-.049-.99-.145a2.134 2.134 0 0 1-.78-.433 1.967 1.967 0 0 1-.507-.718 2.545 2.545 0 0 1-.181-.998c0-.319.084-.634.251-.945.168-.31.447-.59.838-.841.39-.25.91-.458 1.559-.623.649-.165 1.455-.258 2.417-.28v-.495c0-.567-.12-.986-.359-1.259-.239-.272-.587-.408-1.043-.408-.33 0-.605.039-.825.116a3.17 3.17 0 0 0-.574.26 25.11 25.11 0 0 1-.45.26.912.912 0 0 1-.453.115.59.59 0 0 1-.355-.107.843.843 0 0 1-.239-.264l-.371-.652c.973-.891 2.148-1.337 3.523-1.337.494 0 .936.081 1.324.244.387.162.716.387.985.676.27.289.475.634.615 1.036.14.401.21.841.21 1.32v5.346Zm-3.96-1.271c.21 0 .402-.02.578-.058.176-.038.342-.096.5-.173.156-.077.307-.172.453-.285a4.13 4.13 0 0 0 .441-.4v-1.427c-.594.027-1.09.078-1.489.153a3.967 3.967 0 0 0-.961.284c-.242.116-.414.25-.516.404a.894.894 0 0 0-.152.504c0 .357.106.613.317.767.212.154.489.231.83.231Z" clip-rule="evenodd"/>
3
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="#fff" stroke="#B3B3B3" d="M4.5 4.5h19v19h-19z"/>
3
+ <path fill="#3B3B3B" d="M26 20.7a2.51 2.51 0 0 1-5 0c0-1.31 2.5-4.7 2.5-4.7s2.5 3.39 2.5 4.7Z"/>
4
+ <path fill="#3B3B3B" fill-rule="evenodd" d="M19.64 19.93h-1.715a.75.75 0 0 1-.475-.145.82.82 0 0 1-.268-.359l-.89-2.433h-4.943l-.89 2.433a.78.78 0 0 1-.26.347.73.73 0 0 1-.475.157H8L12.686 8h2.269l4.686 11.93Zm-7.721-4.505h3.803l-1.452-3.968a18.048 18.048 0 0 1-.219-.623c-.08-.24-.158-.5-.235-.78-.077.28-.152.542-.227.784a8.742 8.742 0 0 1-.218.635l-1.452 3.952Z" clip-rule="evenodd"/>
5
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" d="M18 9V7h-7v2h2.64l-1.22 10H10v2h7v-2h-2.83L15.4 9H18Z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" fill-rule="evenodd" d="m5 10 3-3 3 3H9v8h2l-3 3-3-3h2v-8H5Zm8-3h10v2H13V7Zm10 6H13v2h10v-2Zm0 6H13v2h10v-2Z" clip-rule="evenodd"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <rect width="9" height="9" x="9.5" y="9.5" stroke="var(--zw-icon-foreground)" rx="4.5"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" d="M15.108 18.184V19H10.5v-.816h1.842v-5.862c0-.176.006-.354.018-.534l-1.53 1.314a.375.375 0 0 1-.156.084.373.373 0 0 1-.27-.048.318.318 0 0 1-.084-.078l-.336-.462 2.562-2.214h.87v7.8h1.692Zm1.519.156a.8.8 0 0 1 .054-.294.829.829 0 0 1 .156-.24.77.77 0 0 1 .534-.222.77.77 0 0 1 .696.462c.04.092.06.19.06.294a.744.744 0 0 1-.222.534.692.692 0 0 1-.24.156.73.73 0 0 1-.294.06.73.73 0 0 1-.294-.06.692.692 0 0 1-.396-.39.816.816 0 0 1-.054-.3Z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <rect width="10" height="10" x="9" y="9" fill="var(--zw-icon-foreground)" rx="5"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" d="M12.086 9.802h.834v11.292h-.834V9.802Zm2.592 8.538a.8.8 0 0 1 .054-.294.829.829 0 0 1 .156-.24.77.77 0 0 1 .534-.222.77.77 0 0 1 .696.462c.04.092.06.19.06.294a.744.744 0 0 1-.222.534.692.692 0 0 1-.24.156.73.73 0 0 1-.294.06.73.73 0 0 1-.294-.06.692.692 0 0 1-.396-.39.816.816 0 0 1-.054-.3Z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" d="m13.664 15.808-1.35-3.498a7.11 7.11 0 0 1-.252-.804c-.084.324-.17.594-.258.81l-1.35 3.492h3.21ZM16.088 19h-.9a.387.387 0 0 1-.252-.078.48.48 0 0 1-.144-.198l-.804-2.076H10.13l-.804 2.076a.421.421 0 0 1-.138.192.383.383 0 0 1-.252.084h-.9l3.438-8.598h1.176L16.088 19Zm.7-.66a.8.8 0 0 1 .053-.294.829.829 0 0 1 .156-.24.77.77 0 0 1 .534-.222.77.77 0 0 1 .696.462c.04.092.06.19.06.294a.744.744 0 0 1-.222.534.692.692 0 0 1-.24.156.73.73 0 0 1-.294.06.73.73 0 0 1-.294-.06.692.692 0 0 1-.396-.39.816.816 0 0 1-.054-.3Z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" d="M9 9h10v10H9z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" fill-rule="evenodd" d="M14.089 6.726a1 1 0 0 1 1.425-.003l6.8 6.882a1 1 0 0 1 .007 1.398L17.2 20.3l-.2.2c-2.2 2.1-5.7 2-7.8-.2l-3.516-3.6a1 1 0 0 1 0-1.399l8.405-8.575Zm1.81 12.674c.1 0 .2 0 .2-.1l4.8-5-6.1-6.2-5 5.1 6.1 6.2Z" clip-rule="evenodd"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" d="M14 9.333V6.666l-3.334 3.333L14 13.333v-2.667c2.206 0 4 1.793 4 4s-1.794 4-4 4c-2.207 0-4-1.793-4-4H8.666A5.332 5.332 0 0 0 14 19.999a5.332 5.332 0 0 0 5.333-5.333A5.332 5.332 0 0 0 14 9.333Z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" fill-rule="evenodd" d="m14 12.731.77.27H20v2H8v-2h2.84a2.892 2.892 0 0 1-.46-.71 3.61 3.61 0 0 1-.29-1.52c0-.484.1-.964.29-1.41a3.5 3.5 0 0 1 .83-1.2 4 4 0 0 1 1.35-.84 4.74 4.74 0 0 1 1.83-.32 6 6 0 0 1 2.11.41 5 5 0 0 1 1.68 1l-.46.88a.69.69 0 0 1-.18.22.41.41 0 0 1-.25.07.69.69 0 0 1-.39-.16 5.551 5.551 0 0 0-.56-.36 4.641 4.641 0 0 0-.8-.36 3.44 3.44 0 0 0-1.14-.16 3.16 3.16 0 0 0-1.11.17 2.29 2.29 0 0 0-.8.45 1.87 1.87 0 0 0-.49.67 2.138 2.138 0 0 0-.11.79c-.023.357.08.711.29 1 .206.267.465.489.76.65.338.187.693.34 1.06.46Zm1.99 6.06c.24-.22.427-.49.55-.79.135-.315.2-.657.19-1h1.74a4.58 4.58 0 0 1-.25 1.38 4 4 0 0 1-.91 1.37 4.231 4.231 0 0 1-1.46.92 5.503 5.503 0 0 1-2 .33 6.13 6.13 0 0 1-2.5-.46 5.66 5.66 0 0 1-1.89-1.31l.54-.88a.61.61 0 0 1 .19-.17.45.45 0 0 1 .25-.07.5.5 0 0 1 .28.1c.128.077.251.16.37.25l.46.33c.19.13.391.243.6.34.245.107.5.191.76.25.328.076.664.11 1 .1a3.67 3.67 0 0 0 1.19-.18c.328-.109.63-.282.89-.51Z" clip-rule="evenodd"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" d="M21.985 8.625h-1.75V9.5h2.625v.875h-3.5v-1.75c0-.481.394-.875.875-.875h1.75v-.875H19.36V6h2.625c.481 0 .875.394.875.875v.875a.878.878 0 0 1-.875.875ZM7.88 20h2.327l2.975-4.742h.105L16.262 20h2.328l-4.069-6.361L18.32 7.75h-2.345l-2.687 4.366h-.105L10.48 7.75H8.15l3.78 5.889L7.88 20Z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" fill-rule="evenodd" d="M15.4 16.06a3.3 3.3 0 0 1-1.4.29 3.12 3.12 0 0 1-1.39-.29 2.88 2.88 0 0 1-1.05-.81 3.711 3.711 0 0 1-.65-1.25 5.659 5.659 0 0 1-.22-1.6V7H9v5.41a6.89 6.89 0 0 0 .34 2.22 5.29 5.29 0 0 0 1 1.77c.437.501.975.904 1.58 1.18A5 5 0 0 0 14 18a5 5 0 0 0 2.08-.42 4.61 4.61 0 0 0 1.57-1.18 5.27 5.27 0 0 0 1-1.77 6.89 6.89 0 0 0 .35-2.22V7h-1.69v5.41a5.659 5.659 0 0 1-.22 1.59 3.71 3.71 0 0 1-.69 1.25c-.27.34-.61.617-1 .81ZM20 19H8v2h12v-2Z" clip-rule="evenodd"/>
3
+ </svg>
@@ -1,12 +1,15 @@
1
1
  <template>
2
- <svg v-bind="iconAttrs" class="zw-icon" :class="iconClasses">
3
- <use :href="url" />
4
- </svg>
2
+ <span
3
+ class="zw-icon"
4
+ :class="iconClasses"
5
+ :style="iconStyles"
6
+ v-html="source"
7
+ />
5
8
  </template>
6
9
 
7
10
  <script>
8
11
  import { computed } from '@vue/composition-api';
9
- import sprite from '../../assets/icons.svg';
12
+ import { importIcon } from '../../utils';
10
13
 
11
14
  export default {
12
15
  name: 'Icon',
@@ -31,7 +34,7 @@ export default {
31
34
  },
32
35
 
33
36
  setup(props) {
34
- const url = computed(() => `${sprite}#${props.name}`);
37
+ const source = computed(() => importIcon(props.name));
35
38
 
36
39
  const iconSize = computed(() => {
37
40
  if (isNaN(Number(props.size))) return props.size;
@@ -39,8 +42,13 @@ export default {
39
42
  return `${props.size}px`;
40
43
  });
41
44
 
42
- const iconAttrs = computed(() => {
43
- return props.size ? { width: iconSize.value, height: iconSize.value } : null;
45
+ const iconStyles = computed(() => {
46
+ if (!props.size) return null;
47
+
48
+ return {
49
+ '--zw-icon-width': iconSize.value,
50
+ '--zw-icon-height': iconSize.value
51
+ };
44
52
  });
45
53
 
46
54
  const iconClasses = computed(() => ({
@@ -48,8 +56,8 @@ export default {
48
56
  }));
49
57
 
50
58
  return {
51
- url,
52
- iconAttrs,
59
+ source,
60
+ iconStyles,
53
61
  iconClasses
54
62
  };
55
63
  }
@@ -14,32 +14,25 @@ function createComponent({ name, size, isAutoColor } = {}) {
14
14
  }
15
15
 
16
16
  describe('rendering', () => {
17
- test('should render url to icon', () => {
18
- const wrapper = createComponent({ name: 'close' });
19
- const useWrapper = wrapper.find('use');
20
-
21
- expect(useWrapper.attributes('href')).toBe('http://zipify.com/icons.svg#close');
22
- });
23
-
24
17
  test('should render without size', () => {
25
18
  const wrapper = createComponent({ size: '' });
26
19
 
27
- expect(wrapper.attributes('width')).toBeFalsy();
28
- expect(wrapper.attributes('height')).toBeFalsy();
20
+ expect(wrapper.element).not.toElementHasStyle('--zw-icon-width');
21
+ expect(wrapper.element).not.toElementHasStyle('--zw-icon-height');
29
22
  });
30
23
 
31
24
  test('should render size without units', () => {
32
25
  const wrapper = createComponent({ size: '12' });
33
26
 
34
- expect(wrapper.attributes('width')).toBe('12px');
35
- expect(wrapper.attributes('height')).toBe('12px');
27
+ expect(wrapper.element).toElementHasStyle('--zw-icon-width', '12px');
28
+ expect(wrapper.element).toElementHasStyle('--zw-icon-height', '12px');
36
29
  });
37
30
 
38
31
  test('should percent size', () => {
39
32
  const wrapper = createComponent({ size: '12%' });
40
33
 
41
- expect(wrapper.attributes('width')).toBe('12%');
42
- expect(wrapper.attributes('height')).toBe('12%');
34
+ expect(wrapper.element).toElementHasStyle('--zw-icon-width', '12%');
35
+ expect(wrapper.element).toElementHasStyle('--zw-icon-height', '12%');
43
36
  });
44
37
 
45
38
  test('should render auto color style', () => {
@@ -37,7 +37,7 @@ export default {
37
37
  border-radius: 2px;
38
38
  background-color: rgb(var(--zw-color-n15));
39
39
  color: rgb(var(--zw-color-n70));
40
- z-index: 999;
40
+ z-index: 999999;
41
41
  }
42
42
 
43
43
  .zw-toolbar--enter-active,
@@ -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
- emit('input', content);
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,26 @@
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
- export function useEditor({ content, onChange, onFocus, extensions }) {
5
- const editor = new Editor({
6
- content: content.value,
5
+ export function useEditor({ content, onChange, extensions }) {
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
- onFocus: () => onFocus(),
9
11
  extensions
10
- });
12
+ }));
11
13
 
12
14
  onUnmounted(() => editor.destroy());
13
15
 
14
16
  watch(content, (value) => {
15
17
  const isChanged = JSON.stringify(editor.getJSON()) !== JSON.stringify(value);
16
18
 
17
- if (isChanged) editor.commands.setContent(value, false);
19
+ if (isChanged) {
20
+ const content = normalizer.normalize(value);
21
+
22
+ editor.commands.setContent(content, false);
23
+ }
18
24
  });
19
25
 
20
26
  return editor;
@@ -1,10 +1,9 @@
1
1
  import { createPopper } from '@popperjs/core';
2
- import { onUnmounted, onMounted, ref } from '@vue/composition-api';
2
+ import { onUnmounted, onMounted, watch } from '@vue/composition-api';
3
3
  import { useElementRef } from '../components/base/composables';
4
4
 
5
- export function useToolbar({ wrapperRef, popperRef, offsets }) {
5
+ export function useToolbar({ wrapperRef, popperRef, offsets, isActiveRef }) {
6
6
  let toolbar;
7
- const isShow = ref(false);
8
7
 
9
8
  onMounted(() => {
10
9
  const wrapperEl = useElementRef(wrapperRef).value;
@@ -22,23 +21,13 @@ export function useToolbar({ wrapperRef, popperRef, offsets }) {
22
21
  ]
23
22
  });
24
23
  });
24
+ const update = () => toolbar.update();
25
25
 
26
- onUnmounted(() => {
27
- toolbar.destroy();
26
+ watch(isActiveRef, () => {
27
+ if (isActiveRef.value) update();
28
28
  });
29
29
 
30
- function hide() {
31
- isShow.value = false;
32
- }
30
+ onUnmounted(() => toolbar.destroy());
33
31
 
34
- function show() {
35
- isShow.value = true;
36
- this.update();
37
- }
38
-
39
- function update() {
40
- toolbar.update();
41
- }
42
-
43
- return { toolbar, show, hide, update, isShow };
32
+ return { toolbar, update };
44
33
  }
@@ -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