goodteditor-ui 1.0.62 → 1.0.64
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/.prettierrc +8 -5
- package/package.json +1 -1
- package/src/components/ui/Select.vue +63 -53
- package/src/components/ui/WysiwygEditor/WysiwygEditor.md +2 -16
- package/src/components/ui/WysiwygEditor/WysiwygEditor.vue +13 -1
- package/src/components/ui/WysiwygEditor/extensions/bubble-menu.js +11 -1
- package/src/components/ui/WysiwygEditor/renders/ColorPicker.vue +6 -0
- package/src/components/ui/WysiwygEditor/renders/InputAuto.vue +7 -1
- package/src/components/ui/WysiwygEditor/renders/InputUnits.vue +1 -1
- package/src/components/ui/WysiwygEditor/renders/Select.vue +8 -1
- package/src/components/ui/WysiwygEditor/renders/ToolbarPopover.vue +6 -0
package/.prettierrc
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
{
|
|
2
|
-
"printWidth":
|
|
2
|
+
"printWidth": 120,
|
|
3
3
|
"tabWidth": 4,
|
|
4
4
|
"useTabs": false,
|
|
5
5
|
"semi": true,
|
|
6
6
|
"singleQuote": true,
|
|
7
|
-
"trailingComma": "
|
|
7
|
+
"trailingComma": "none",
|
|
8
8
|
"bracketSpacing": true,
|
|
9
|
-
"
|
|
10
|
-
"arrowParens": "
|
|
9
|
+
"bracketSameLine": true,
|
|
10
|
+
"arrowParens": "always",
|
|
11
11
|
"requirePragma": false,
|
|
12
12
|
"insertPragma": false,
|
|
13
|
-
"proseWrap": "preserve"
|
|
13
|
+
"proseWrap": "preserve",
|
|
14
|
+
"vueIndentScriptAndStyle": false,
|
|
15
|
+
"endOfLine": "lf",
|
|
16
|
+
"htmlWhitespaceSensitivity": "ignore"
|
|
14
17
|
}
|
package/package.json
CHANGED
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
@keydown.prevent="onKeyDown"
|
|
6
6
|
@click="onClick"
|
|
7
7
|
tabindex="0"
|
|
8
|
-
:data-popover="popoverTargetId"
|
|
9
|
-
>
|
|
8
|
+
:data-popover="popoverTargetId">
|
|
10
9
|
<div class="ui-select-label u-select-none">
|
|
11
10
|
<template v-if="multiple">
|
|
12
11
|
<template v-if="optionsSelected.length">
|
|
@@ -24,9 +23,8 @@
|
|
|
24
23
|
option,
|
|
25
24
|
value: getOptionValue(option),
|
|
26
25
|
label: getOptionLabel(option),
|
|
27
|
-
deselectOption
|
|
28
|
-
}"
|
|
29
|
-
>
|
|
26
|
+
deselectOption
|
|
27
|
+
}">
|
|
30
28
|
<ui-badge
|
|
31
29
|
class="mar-none mar-right-2"
|
|
32
30
|
theme="primary"
|
|
@@ -34,8 +32,7 @@
|
|
|
34
32
|
:key="index"
|
|
35
33
|
removable
|
|
36
34
|
@click.native.stop
|
|
37
|
-
@remove="deselectOption(option)"
|
|
38
|
-
>
|
|
35
|
+
@remove="deselectOption(option)">
|
|
39
36
|
<span>{{ getOptionLabel(option) }}</span>
|
|
40
37
|
</ui-badge>
|
|
41
38
|
</slot>
|
|
@@ -63,9 +60,9 @@
|
|
|
63
60
|
option: optionsSelected[0],
|
|
64
61
|
value: getOptionValue(optionsSelected[0]),
|
|
65
62
|
label: getOptionLabel(optionsSelected[0]),
|
|
63
|
+
index: getOptionIndex(optionsSelected[0])
|
|
66
64
|
}"
|
|
67
|
-
v-if="optionsSelected.length"
|
|
68
|
-
>
|
|
65
|
+
v-if="optionsSelected.length">
|
|
69
66
|
{{ getOptionLabel(optionsSelected[0]) }}
|
|
70
67
|
</slot>
|
|
71
68
|
<div class="ui-select-placeholder events-none" v-else>
|
|
@@ -102,8 +99,7 @@
|
|
|
102
99
|
@select-option="onDatalistSelectOption"
|
|
103
100
|
v-bind="{ size, options, class: datalistCssClass }"
|
|
104
101
|
:cursorIndex.sync="dataListCursorIndex"
|
|
105
|
-
ref="datalist"
|
|
106
|
-
>
|
|
102
|
+
ref="datalist">
|
|
107
103
|
<template #header>
|
|
108
104
|
<!--
|
|
109
105
|
@slot Dropdown header slot
|
|
@@ -139,37 +135,53 @@
|
|
|
139
135
|
toggleOption,
|
|
140
136
|
// legacy
|
|
141
137
|
optionIndex: index,
|
|
142
|
-
isOptionSelected: isOptionSelected(option)
|
|
143
|
-
}"
|
|
144
|
-
>
|
|
138
|
+
isOptionSelected: isOptionSelected(option)
|
|
139
|
+
}">
|
|
145
140
|
<li
|
|
146
141
|
:class="{
|
|
147
142
|
active: isOptionSelected(option),
|
|
148
|
-
'bg-grey-lighter': index == cursorIndex
|
|
143
|
+
'bg-grey-lighter': index == cursorIndex
|
|
149
144
|
}"
|
|
150
145
|
:key="index"
|
|
151
146
|
:title="getOptionLabel(option)"
|
|
152
|
-
@click.stop="toggleOption(option)"
|
|
153
|
-
>
|
|
147
|
+
@click.stop="toggleOption(option)">
|
|
154
148
|
<div class="row row-collapse" v-if="multiple">
|
|
155
149
|
<div class="col col-vmid text-truncate">
|
|
156
|
-
<div
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
150
|
+
<div class="text-truncate" style="min-height: calc(var(--line-height) * 1em)">
|
|
151
|
+
<slot
|
|
152
|
+
name="option-label"
|
|
153
|
+
v-bind="{
|
|
154
|
+
option,
|
|
155
|
+
value: getOptionValue(option),
|
|
156
|
+
label: getOptionLabel(option),
|
|
157
|
+
index,
|
|
158
|
+
isSelected: isOptionSelected(option),
|
|
159
|
+
cursorIndex
|
|
160
|
+
}">
|
|
161
|
+
{{ getOptionLabel(option) }}
|
|
162
|
+
</slot>
|
|
161
163
|
</div>
|
|
162
164
|
</div>
|
|
163
165
|
<div class="col col-auto col-vmid" v-if="isOptionSelected(option)">
|
|
164
166
|
<i class="mdi mdi-check" style="line-height: 1"></i>
|
|
165
167
|
</div>
|
|
166
168
|
</div>
|
|
167
|
-
<div
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
169
|
+
<div class="text-truncate" style="min-height: calc(var(--line-height) * 1rem)" v-else>
|
|
170
|
+
<!--
|
|
171
|
+
@slot select option label slot
|
|
172
|
+
-->
|
|
173
|
+
<slot
|
|
174
|
+
name="option-label"
|
|
175
|
+
v-bind="{
|
|
176
|
+
option,
|
|
177
|
+
value: getOptionValue(option),
|
|
178
|
+
label: getOptionLabel(option),
|
|
179
|
+
index,
|
|
180
|
+
isSelected: isOptionSelected(option),
|
|
181
|
+
cursorIndex
|
|
182
|
+
}">
|
|
183
|
+
{{ getOptionLabel(option) }}
|
|
184
|
+
</slot>
|
|
173
185
|
</div>
|
|
174
186
|
</li>
|
|
175
187
|
</slot>
|
|
@@ -188,12 +200,14 @@
|
|
|
188
200
|
.ui-select {
|
|
189
201
|
display: inline-flex;
|
|
190
202
|
align-items: center;
|
|
203
|
+
|
|
191
204
|
&-label {
|
|
192
205
|
white-space: nowrap;
|
|
193
206
|
text-overflow: ellipsis;
|
|
194
207
|
overflow: hidden;
|
|
195
208
|
flex: 1 0 0;
|
|
196
209
|
}
|
|
210
|
+
|
|
197
211
|
&-placeholder {
|
|
198
212
|
input {
|
|
199
213
|
border: none;
|
|
@@ -217,7 +231,7 @@ export default {
|
|
|
217
231
|
components: {
|
|
218
232
|
UiBadge,
|
|
219
233
|
UiDatalist,
|
|
220
|
-
UiPopover
|
|
234
|
+
UiPopover
|
|
221
235
|
},
|
|
222
236
|
mixins: [FormComponent, WithPopover],
|
|
223
237
|
props: {
|
|
@@ -227,7 +241,7 @@ export default {
|
|
|
227
241
|
value: {
|
|
228
242
|
default() {
|
|
229
243
|
return null;
|
|
230
|
-
}
|
|
244
|
+
}
|
|
231
245
|
},
|
|
232
246
|
/**
|
|
233
247
|
* Options. Array of Objects (option objects)
|
|
@@ -236,21 +250,21 @@ export default {
|
|
|
236
250
|
type: Array,
|
|
237
251
|
default() {
|
|
238
252
|
return [];
|
|
239
|
-
}
|
|
253
|
+
}
|
|
240
254
|
},
|
|
241
255
|
/**
|
|
242
256
|
* Datalist css classes (optional)
|
|
243
257
|
*/
|
|
244
258
|
datalistCssClass: {
|
|
245
259
|
type: [String, Array],
|
|
246
|
-
default: ''
|
|
260
|
+
default: ''
|
|
247
261
|
},
|
|
248
262
|
/**
|
|
249
263
|
* Allow multiple selection
|
|
250
264
|
*/
|
|
251
265
|
multiple: {
|
|
252
266
|
type: Boolean,
|
|
253
|
-
default: false
|
|
267
|
+
default: false
|
|
254
268
|
},
|
|
255
269
|
/**
|
|
256
270
|
* Defines whether 'value' is the option value field or option object
|
|
@@ -258,30 +272,30 @@ export default {
|
|
|
258
272
|
*/
|
|
259
273
|
valueObjects: {
|
|
260
274
|
type: Boolean,
|
|
261
|
-
default: false
|
|
275
|
+
default: false
|
|
262
276
|
},
|
|
263
277
|
/**
|
|
264
278
|
* Defines the 'value' field of the option Object
|
|
265
279
|
*/
|
|
266
280
|
valueField: {
|
|
267
281
|
type: String,
|
|
268
|
-
default: 'value'
|
|
282
|
+
default: 'value'
|
|
269
283
|
},
|
|
270
284
|
/**
|
|
271
285
|
* Defines the 'label' field of the option Object
|
|
272
286
|
*/
|
|
273
287
|
labelField: {
|
|
274
288
|
type: String,
|
|
275
|
-
default: 'label'
|
|
289
|
+
default: 'label'
|
|
276
290
|
},
|
|
277
291
|
autoWidth: {
|
|
278
|
-
default: true
|
|
279
|
-
}
|
|
292
|
+
default: true
|
|
293
|
+
}
|
|
280
294
|
},
|
|
281
295
|
data() {
|
|
282
296
|
return {
|
|
283
297
|
optionsSelected: [],
|
|
284
|
-
dataListCursorIndex: -1
|
|
298
|
+
dataListCursorIndex: -1
|
|
285
299
|
};
|
|
286
300
|
},
|
|
287
301
|
computed: {
|
|
@@ -291,27 +305,27 @@ export default {
|
|
|
291
305
|
cssClassExt() {
|
|
292
306
|
let obj = { 'u-select-none': this.readonly };
|
|
293
307
|
return { ...this.cssClass, ...obj };
|
|
294
|
-
}
|
|
308
|
+
}
|
|
295
309
|
},
|
|
296
310
|
watch: {
|
|
297
311
|
value: {
|
|
298
312
|
handler(model) {
|
|
299
313
|
this.importModel(model);
|
|
300
314
|
},
|
|
301
|
-
immediate: true
|
|
315
|
+
immediate: true
|
|
302
316
|
},
|
|
303
317
|
options() {
|
|
304
318
|
this.importModel(this.value);
|
|
305
|
-
}
|
|
319
|
+
}
|
|
306
320
|
},
|
|
307
321
|
methods: {
|
|
308
322
|
importModel(model) {
|
|
309
323
|
let ci = -1;
|
|
310
324
|
let tmp = [];
|
|
311
325
|
model = this.multiple ? (Array.isArray(model) ? model : [model]) : [model];
|
|
312
|
-
model.forEach(modelItem => {
|
|
326
|
+
model.forEach((modelItem) => {
|
|
313
327
|
let modelItemValue = this.valueObjects ? this.getOptionValue(modelItem) : modelItem;
|
|
314
|
-
let optionIndex = this.options.findIndex(optionItem => {
|
|
328
|
+
let optionIndex = this.options.findIndex((optionItem) => {
|
|
315
329
|
let optionItemValue = this.getOptionValue(optionItem);
|
|
316
330
|
return optionItemValue === modelItemValue;
|
|
317
331
|
});
|
|
@@ -324,7 +338,7 @@ export default {
|
|
|
324
338
|
this.optionsSelected = tmp;
|
|
325
339
|
},
|
|
326
340
|
exportModel() {
|
|
327
|
-
let model = this.optionsSelected.map(option =>
|
|
341
|
+
let model = this.optionsSelected.map((option) =>
|
|
328
342
|
this.valueObjects ? option : this.getOptionValue(option)
|
|
329
343
|
);
|
|
330
344
|
if (this.multiple) {
|
|
@@ -356,14 +370,10 @@ export default {
|
|
|
356
370
|
return value === undefined ? option : value;
|
|
357
371
|
},
|
|
358
372
|
getOptionIndex(option) {
|
|
359
|
-
return this.options.findIndex(
|
|
360
|
-
o => this.getOptionValue(o) === this.getOptionValue(option)
|
|
361
|
-
);
|
|
373
|
+
return this.options.findIndex((o) => this.getOptionValue(o) === this.getOptionValue(option));
|
|
362
374
|
},
|
|
363
375
|
isOptionSelected(option) {
|
|
364
|
-
return !!this.optionsSelected.find(
|
|
365
|
-
o => this.getOptionValue(o) === this.getOptionValue(option)
|
|
366
|
-
);
|
|
376
|
+
return !!this.optionsSelected.find((o) => this.getOptionValue(o) === this.getOptionValue(option));
|
|
367
377
|
},
|
|
368
378
|
selectOption(option) {
|
|
369
379
|
if (this.isOptionSelected(option)) {
|
|
@@ -383,7 +393,7 @@ export default {
|
|
|
383
393
|
return;
|
|
384
394
|
}
|
|
385
395
|
this.optionsSelected = this.optionsSelected.filter(
|
|
386
|
-
o => this.getOptionValue(o) !== this.getOptionValue(option)
|
|
396
|
+
(o) => this.getOptionValue(o) !== this.getOptionValue(option)
|
|
387
397
|
);
|
|
388
398
|
this.triggerModelChange();
|
|
389
399
|
},
|
|
@@ -432,4 +442,4 @@ export default {
|
|
|
432
442
|
}
|
|
433
443
|
}
|
|
434
444
|
};
|
|
435
|
-
</script>
|
|
445
|
+
</script>
|
|
@@ -2,8 +2,7 @@ Simple example
|
|
|
2
2
|
```vue
|
|
3
3
|
<template>
|
|
4
4
|
<div class="pad-l5">
|
|
5
|
-
<
|
|
6
|
-
<ui-wysiwyg-editor v-model="model" :autofocus="false" :editorContent="editorContent"></ui-wysiwyg-editor>
|
|
5
|
+
<ui-wysiwyg-editor v-model="model" :autofocus="false" :editorContent="{ class: ['h-100', 'pad-3', 'scroll-y'] }"></ui-wysiwyg-editor>
|
|
7
6
|
</div>
|
|
8
7
|
</template>
|
|
9
8
|
<script>
|
|
@@ -11,23 +10,10 @@ import UiWysiwygEditor from './WysiwygEditor.vue';
|
|
|
11
10
|
export default {
|
|
12
11
|
components: { UiWysiwygEditor },
|
|
13
12
|
data: () => ({
|
|
14
|
-
toggled: false,
|
|
15
13
|
model: `<p>This WYSIWYG is based on
|
|
16
14
|
<a target="_blank" rel="noopener noreferrer nofollow" href="https://tiptap.dev/" class="color-link">tiptap</a>
|
|
17
15
|
editor framework.</p>`
|
|
18
|
-
})
|
|
19
|
-
computed: {
|
|
20
|
-
editorContent() {
|
|
21
|
-
if (!this.toggled) {
|
|
22
|
-
return {
|
|
23
|
-
class: ['h-100', 'pad-3', 'scroll-y']
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return {
|
|
27
|
-
class: ['h-100', 'pad-3', 'scroll-y', 'color-red']
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
},
|
|
16
|
+
})
|
|
31
17
|
};
|
|
32
18
|
</script>
|
|
33
19
|
```
|
|
@@ -54,6 +54,7 @@ import { buildToolGroups, bindContext } from './utils';
|
|
|
54
54
|
import { DefaultTools, WysiwygAutofocus, EMPTY_PTAG_REGEXP } from './constants';
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
+
* @typedef {import('@tiptap/core').EditorEvents} EditorEvents
|
|
57
58
|
* @typedef {Omit<import('@tiptap/extension-bubble-menu').BubbleMenuOptions, 'element'>} BubbleMenuOptions
|
|
58
59
|
* @typedef {import('vue').PropOptions.<Boolean|BubbleMenuOptions>} BubbleMenuOptionsProp
|
|
59
60
|
* @typedef {import('./constants').ITool} ITool
|
|
@@ -202,8 +203,18 @@ export default {
|
|
|
202
203
|
},
|
|
203
204
|
onUpdate: () => {
|
|
204
205
|
this.onInput();
|
|
206
|
+
// case when the editor lost focus bc the editor menu form element (e.g. input)
|
|
207
|
+
// intercepted it & the editor value was updated from that element later
|
|
208
|
+
if (!this.editor.isFocused) {
|
|
209
|
+
this.onChange();
|
|
210
|
+
}
|
|
205
211
|
},
|
|
206
212
|
onBlur: () => {
|
|
213
|
+
const isSame = this.content === this.value;
|
|
214
|
+
|
|
215
|
+
if (isSame) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
207
218
|
this.onChange();
|
|
208
219
|
},
|
|
209
220
|
onSelectionUpdate: onSelectionUpdateDebounced
|
|
@@ -231,7 +242,8 @@ export default {
|
|
|
231
242
|
this.$emit('change', this.content);
|
|
232
243
|
},
|
|
233
244
|
/**
|
|
234
|
-
* @param {
|
|
245
|
+
* @param {EditorEvents.selectionUpdate} selectionUpdateEvent
|
|
246
|
+
* @param {EditorEvents.selectionUpdate.transaction} selectionUpdateEvent.transaction
|
|
235
247
|
*/
|
|
236
248
|
onSelectionUpdate({ transaction }) {
|
|
237
249
|
this.caretPosition = transaction.curSelection.to;
|
|
@@ -11,11 +11,20 @@ import BubbleMenuToExtend from '@tiptap/extension-bubble-menu';
|
|
|
11
11
|
*/
|
|
12
12
|
export const BubbleMenu = ({
|
|
13
13
|
element = null,
|
|
14
|
-
tippyOptions = {
|
|
14
|
+
tippyOptions = {
|
|
15
|
+
maxWidth: 'none',
|
|
16
|
+
appendTo: () => document.body,
|
|
17
|
+
onClickOutside: (instance) => {
|
|
18
|
+
instance.hide();
|
|
19
|
+
},
|
|
20
|
+
},
|
|
15
21
|
} = {}) => {
|
|
16
22
|
const {
|
|
17
23
|
maxWidth = 'none',
|
|
18
24
|
appendTo = () => document.body,
|
|
25
|
+
onClickOutside = (instance) => {
|
|
26
|
+
instance.hide();
|
|
27
|
+
},
|
|
19
28
|
...restOptions
|
|
20
29
|
} = tippyOptions;
|
|
21
30
|
|
|
@@ -24,6 +33,7 @@ export const BubbleMenu = ({
|
|
|
24
33
|
tippyOptions: {
|
|
25
34
|
maxWidth,
|
|
26
35
|
appendTo,
|
|
36
|
+
onClickOutside,
|
|
27
37
|
...restOptions
|
|
28
38
|
},
|
|
29
39
|
});
|
|
@@ -23,6 +23,12 @@ import { useRender, RenderMixinTypes } from './mixins';
|
|
|
23
23
|
export default {
|
|
24
24
|
components: { ColorPicker, Popover },
|
|
25
25
|
mixins: [useRender(), WithPopover],
|
|
26
|
+
props: {
|
|
27
|
+
appendToBody: {
|
|
28
|
+
type: Boolean,
|
|
29
|
+
default: false
|
|
30
|
+
}
|
|
31
|
+
},
|
|
26
32
|
computed: {
|
|
27
33
|
value() {
|
|
28
34
|
return this.tool.getValue();
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :title="title" class="autocomplete-tool">
|
|
3
3
|
<input-autocomplete
|
|
4
|
-
v-bind="{
|
|
4
|
+
v-bind="{
|
|
5
|
+
value,
|
|
6
|
+
options: tool.options,
|
|
7
|
+
disabled: !isEnabled,
|
|
8
|
+
size: 'small',
|
|
9
|
+
appendToBody: false
|
|
10
|
+
}"
|
|
5
11
|
@input="onInput" />
|
|
6
12
|
</div>
|
|
7
13
|
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :title="title" class="input-units-tool">
|
|
3
3
|
<input-units
|
|
4
|
-
v-bind="{ value, units, size: 'small', disabled: !isEnabled }"
|
|
4
|
+
v-bind="{ value, units, size: 'small', disabled: !isEnabled, appendToBody: false }"
|
|
5
5
|
@change="onChange" />
|
|
6
6
|
</div>
|
|
7
7
|
</template>
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :title="title" class="select-tool">
|
|
3
3
|
<ui-select
|
|
4
|
-
v-bind="{
|
|
4
|
+
v-bind="{
|
|
5
|
+
value,
|
|
6
|
+
options,
|
|
7
|
+
valueObjects,
|
|
8
|
+
disabled: !isEnabled,
|
|
9
|
+
size: 'small',
|
|
10
|
+
appendToBody: false
|
|
11
|
+
}"
|
|
5
12
|
class="w-100"
|
|
6
13
|
@change="onChange" />
|
|
7
14
|
</div>
|
|
@@ -32,6 +32,12 @@ import { useRender, RenderMixinTypes } from './mixins';
|
|
|
32
32
|
export default {
|
|
33
33
|
components: { Popover, UiDatalist },
|
|
34
34
|
mixins: [useRender(), WithPopover],
|
|
35
|
+
props: {
|
|
36
|
+
appendToBody: {
|
|
37
|
+
type: Boolean,
|
|
38
|
+
default: false
|
|
39
|
+
}
|
|
40
|
+
},
|
|
35
41
|
methods: {
|
|
36
42
|
...RenderMixinTypes,
|
|
37
43
|
onCommandExecuted() {
|