goodteditor-ui 1.0.20 → 1.0.21
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/package.json +1 -1
- package/src/components/ui/WysiwygEditor/WysiwygEditor.d.ts +5 -5
- package/src/components/ui/WysiwygEditor/constants.js +13 -4
- package/src/components/ui/WysiwygEditor/extensions/image.js +17 -13
- package/src/components/ui/WysiwygEditor/extensions/text-align.js +2 -1
- package/src/components/ui/WysiwygEditor/renders/Button.vue +12 -10
- package/src/components/ui/WysiwygEditor/renders/ColorPicker.vue +1 -2
- package/src/components/ui/WysiwygEditor/renders/InputAuto.vue +5 -4
- package/src/components/ui/WysiwygEditor/renders/InputBrowse.vue +2 -2
- package/src/components/ui/WysiwygEditor/renders/InputUnits.vue +5 -4
- package/src/components/ui/WysiwygEditor/renders/Link.vue +87 -0
- package/src/components/ui/WysiwygEditor/renders/Select.vue +6 -4
- package/src/components/ui/WysiwygEditor/renders/ToolbarPopover.vue +1 -2
- package/src/components/ui/WysiwygEditor/renders/index.d.ts +1 -0
- package/src/components/ui/WysiwygEditor/renders/index.js +1 -0
- package/src/components/ui/WysiwygEditor/tools-and-commands.js +36 -41
- package/src/components/ui/WysiwygEditor.vue +12 -1
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Button, Select, InputUnits, ColorPicker, ToolbarPopover, InputAuto, InputBrowse } from './renders';
|
|
1
|
+
import { Button, Select, InputUnits, ColorPicker, ToolbarPopover, InputAuto, InputBrowse, Link } from './renders';
|
|
2
2
|
|
|
3
3
|
export type Render = Readonly<{
|
|
4
4
|
BUTTON: Button;
|
|
@@ -8,6 +8,7 @@ export type Render = Readonly<{
|
|
|
8
8
|
TOOLBAR_POPOVER: ToolbarPopover;
|
|
9
9
|
INPUT_AUTO: InputAuto;
|
|
10
10
|
INPUT_BROWSE: InputBrowse;
|
|
11
|
+
LINK: Link;
|
|
11
12
|
}>;
|
|
12
13
|
|
|
13
14
|
export type CommandDef = {
|
|
@@ -23,10 +24,10 @@ export type ToolDef = {
|
|
|
23
24
|
render: Render;
|
|
24
25
|
title: String;
|
|
25
26
|
icon: String;
|
|
26
|
-
exec: (value?: String) => void;
|
|
27
|
+
exec: (value?: String | Record<String, any>) => void;
|
|
27
28
|
isActive: () => Boolean;
|
|
28
29
|
isEnabled: () => Boolean;
|
|
29
|
-
getValue?: () => String
|
|
30
|
+
getValue?: () => String | Record<String, any>;
|
|
30
31
|
options?: Array<String | ToolDef | CommandDef>;
|
|
31
32
|
units?: Array<String>
|
|
32
33
|
};
|
|
@@ -114,6 +115,5 @@ export type ToolType = Readonly<{
|
|
|
114
115
|
ALIGN_V_BOT: 'alignVBot',
|
|
115
116
|
TOGGLE_BORDERS: 'toggleBorders',
|
|
116
117
|
TOGGLE_ZEBRA: 'toggleZebra',
|
|
117
|
-
INSERT_IMAGE: 'insertImage'
|
|
118
|
-
INSERT_LINK: 'insertLink'
|
|
118
|
+
INSERT_IMAGE: 'insertImage'
|
|
119
119
|
}>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Button, Select, InputUnits, ColorPicker, ToolbarPopover, InputAuto, InputBrowse } from './renders';
|
|
1
|
+
import { Button, Select, InputUnits, ColorPicker, ToolbarPopover, InputAuto, InputBrowse, Link } from './renders';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {import('./WysiwygEditor').NodeType} NodeType
|
|
@@ -116,8 +116,7 @@ export const ToolType = Object.freeze({
|
|
|
116
116
|
TOGGLE_BORDERS: 'toggleBorders',
|
|
117
117
|
TOGGLE_ZEBRA: 'toggleZebra',
|
|
118
118
|
/** image options */
|
|
119
|
-
INSERT_IMAGE: 'insertImage'
|
|
120
|
-
INSERT_LINK: 'insertLink'
|
|
119
|
+
INSERT_IMAGE: 'insertImage'
|
|
121
120
|
});
|
|
122
121
|
|
|
123
122
|
/**
|
|
@@ -148,7 +147,8 @@ export const Render = Object.freeze({
|
|
|
148
147
|
SELECT: Select,
|
|
149
148
|
TOOLBAR_POPOVER: ToolbarPopover,
|
|
150
149
|
INPUT_AUTO: InputAuto,
|
|
151
|
-
INPUT_BROWSE: InputBrowse
|
|
150
|
+
INPUT_BROWSE: InputBrowse,
|
|
151
|
+
LINK: Link
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
/**
|
|
@@ -236,6 +236,15 @@ export const createInputBrowseTool = ({ getValue = () => null, ...toolOptions })
|
|
|
236
236
|
getValue
|
|
237
237
|
});
|
|
238
238
|
|
|
239
|
+
/**
|
|
240
|
+
* @param {Tool} tool
|
|
241
|
+
* @return Tool
|
|
242
|
+
*/
|
|
243
|
+
export const createLinkTool = ({ getValue = () => null, ...toolOptions }) => ({
|
|
244
|
+
...createBaseTool({ render: Render.LINK, ...toolOptions }),
|
|
245
|
+
getValue
|
|
246
|
+
});
|
|
247
|
+
|
|
239
248
|
export const DefaultTools = [
|
|
240
249
|
[ToolType.PARAGRAPH_STYLE, ToolType.TEXT_COLOR, ToolType.FONT_SIZE, ToolType.FONT_FAMILY, ToolType.BLOCKQUOTE],
|
|
241
250
|
[ToolType.BOLD, ToolType.ITALIC, ToolType.UNDERLINE, ToolType.STRIKE, ToolType.CODE],
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { Image as ImageToExtend } from '@tiptap/extension-image';
|
|
2
2
|
|
|
3
|
-
export const Image = ImageToExtend
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
3
|
+
export const Image = ImageToExtend
|
|
4
|
+
.extend({
|
|
5
|
+
addAttributes() {
|
|
6
|
+
return {
|
|
7
|
+
...this.parent?.(),
|
|
8
|
+
class: {
|
|
9
|
+
default: 'responsive',
|
|
10
|
+
},
|
|
11
|
+
style: {
|
|
12
|
+
default: null,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
.configure({
|
|
18
|
+
inline: true,
|
|
19
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TextAlign as TextAlignToExtend } from '@tiptap/extension-text-align';
|
|
2
|
+
import { NodeType, MarkType } from '../constants';
|
|
2
3
|
|
|
3
4
|
export const TextAlign = TextAlignToExtend.configure({
|
|
4
|
-
types: [
|
|
5
|
+
types: [NodeType.HEADING, NodeType.PARAGRAPH, MarkType.LINK, NodeType.CODE_BLOCK, NodeType.BLOCKQUOTE]
|
|
5
6
|
});
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
<div :title="title">
|
|
3
|
+
<button
|
|
4
|
+
:class="{ 'btn-outline': !isActive, 'btn-primary': isActive }"
|
|
5
|
+
:disabled="!isEnabled"
|
|
6
|
+
class="btn btn-small btn-icon"
|
|
7
|
+
@click.stop="onClick">
|
|
8
|
+
<div class="icon">
|
|
9
|
+
<i :class="icon" class="mdi"></i>
|
|
10
|
+
</div>
|
|
11
|
+
</button>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
12
14
|
</template>
|
|
13
15
|
|
|
14
16
|
<script>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
<div :title="title" class="autocomplete-tool">
|
|
3
|
+
<input-autocomplete
|
|
4
|
+
v-bind="{ value, options: tool.options, disabled: !isEnabled, size: 'small' }"
|
|
5
|
+
@input="onInput" />
|
|
6
|
+
</div>
|
|
6
7
|
</template>
|
|
7
8
|
|
|
8
9
|
<script>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="form-control form-control-icon-right w-12-12">
|
|
2
|
+
<div :title="title" class="form-control form-control-icon-right w-12-12">
|
|
3
3
|
<input-autocomplete
|
|
4
|
-
v-bind="{ value,
|
|
4
|
+
v-bind="{ value, disabled: !isEnabled, size: 'small' }"
|
|
5
5
|
@change="onChange" />
|
|
6
6
|
<div class="icon">
|
|
7
7
|
<i class="mdi mdi-folder cursor-pointer color-grey" @click="browse" />
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<input-units
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
<div :title="title" class="input-units-tool">
|
|
3
|
+
<input-units
|
|
4
|
+
v-bind="{ value, units, size: 'small', disabled: !isEnabled }"
|
|
5
|
+
@change="onChange" />
|
|
6
|
+
</div>
|
|
6
7
|
</template>
|
|
7
8
|
|
|
8
9
|
<script>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :data-popover="popoverTargetId" :title="title">
|
|
3
|
+
<button class="btn btn-small btn-outline btn-icon" @click="togglePopover">
|
|
4
|
+
<div class="icon">
|
|
5
|
+
<i class="mdi" :class="icon"></i>
|
|
6
|
+
</div>
|
|
7
|
+
</button>
|
|
8
|
+
<popover :show="popoverShow" v-bind="popoverOptions">
|
|
9
|
+
<div class="dropdown pad-l1 pad-top-l2">
|
|
10
|
+
<div class="close pos-abs pos-top-right mar-2" @click="togglePopover">
|
|
11
|
+
<i class="mdi mdi-close color-grey" />
|
|
12
|
+
</div>
|
|
13
|
+
<div class="row row-hgap-3 mar-bot-l1">
|
|
14
|
+
<div class="col col-vmid text-truncate">
|
|
15
|
+
<div class="form-label form-label-xsmall text-truncate">Url</div>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="col col-vmid col-12-12">
|
|
18
|
+
<input-autocomplete v-model="url" class="w-100" size="small" />
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="row row-hgap-3 mar-bot-l1">
|
|
22
|
+
<div class="col col-vmid text-truncate">
|
|
23
|
+
<div class="form-label form-label-xsmall text-truncate">Target</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="col col-vmid col-12-12">
|
|
26
|
+
<ui-select
|
|
27
|
+
v-model="target"
|
|
28
|
+
:options="$options.static.TargetTypeOptions"
|
|
29
|
+
class="w-100"
|
|
30
|
+
size="small" />
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<button class="btn btn-outline btn-small w-100" @click="execute">Применить</button>
|
|
34
|
+
</div>
|
|
35
|
+
</popover>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script>
|
|
40
|
+
import Popover from '../../Popover.vue';
|
|
41
|
+
import InputAutocomplete from '../../InputAutocomplete.vue';
|
|
42
|
+
import UiSelect from '../../Select.vue';
|
|
43
|
+
import WithPopover from '../../utils/WithPopover';
|
|
44
|
+
import { useRender } from './mixins';
|
|
45
|
+
|
|
46
|
+
const TargetType = {
|
|
47
|
+
BLANK: '_blank',
|
|
48
|
+
SELF: '_self'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default {
|
|
52
|
+
components: {
|
|
53
|
+
Popover,
|
|
54
|
+
InputAutocomplete,
|
|
55
|
+
UiSelect
|
|
56
|
+
},
|
|
57
|
+
mixins: [useRender(), WithPopover],
|
|
58
|
+
data: () => ({
|
|
59
|
+
url: '',
|
|
60
|
+
target: null
|
|
61
|
+
}),
|
|
62
|
+
static: {
|
|
63
|
+
TargetTypeOptions: [
|
|
64
|
+
{ label: 'blank', value: TargetType.BLANK },
|
|
65
|
+
{ label: 'self', value: TargetType.SELF }
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
watch: {
|
|
69
|
+
popoverShow(isShown) {
|
|
70
|
+
if (isShown) {
|
|
71
|
+
const { url = '', target = TargetType.BLANK } = this.tool.getValue();
|
|
72
|
+
this.url = url;
|
|
73
|
+
this.target = target;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
methods: {
|
|
78
|
+
execute() {
|
|
79
|
+
const { tool, url, target } = this;
|
|
80
|
+
tool.exec({ url, target });
|
|
81
|
+
|
|
82
|
+
this.popoverShow = false;
|
|
83
|
+
this.emitExecuted();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
</script>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
<div :title="title" class="select-tool">
|
|
3
|
+
<ui-select
|
|
4
|
+
v-bind="{ value, options, valueObjects, disabled: !isEnabled, size: 'small' }"
|
|
5
|
+
class="w-100"
|
|
6
|
+
@change="onChange" />
|
|
7
|
+
</div>
|
|
6
8
|
</template>
|
|
7
9
|
|
|
8
10
|
<script>
|
|
@@ -5,3 +5,4 @@ export { default as ColorPicker } from './ColorPicker.vue';
|
|
|
5
5
|
export { default as ToolbarPopover } from './ToolbarPopover.vue';
|
|
6
6
|
export { default as InputAuto } from './InputAuto.vue';
|
|
7
7
|
export { default as InputBrowse } from './InputBrowse.vue';
|
|
8
|
+
export { default as Link } from './Link.vue';
|
|
@@ -5,3 +5,4 @@ export { default as ColorPicker } from './ColorPicker.vue';
|
|
|
5
5
|
export { default as ToolbarPopover } from './ToolbarPopover.vue';
|
|
6
6
|
export { default as InputAuto } from './InputAuto.vue';
|
|
7
7
|
export { default as InputBrowse } from './InputBrowse.vue';
|
|
8
|
+
export { default as Link } from './Link.vue';
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
createSelectTool,
|
|
11
11
|
createToolbarPopoverTool,
|
|
12
12
|
createInputAutoTool,
|
|
13
|
-
createInputBrowseTool
|
|
13
|
+
createInputBrowseTool,
|
|
14
|
+
createLinkTool
|
|
14
15
|
} from './constants';
|
|
15
16
|
|
|
16
17
|
export const CommandMap = {
|
|
@@ -32,7 +33,7 @@ export const CommandMap = {
|
|
|
32
33
|
}),
|
|
33
34
|
[CommandType.HEADING_1]: createCommand({
|
|
34
35
|
name: CommandType.HEADING_1,
|
|
35
|
-
title: 'Заголовок 1',
|
|
36
|
+
title: 'Заголовок 1 (h1)',
|
|
36
37
|
exec() {
|
|
37
38
|
this.editor
|
|
38
39
|
.chain()
|
|
@@ -47,7 +48,7 @@ export const CommandMap = {
|
|
|
47
48
|
}),
|
|
48
49
|
[CommandType.HEADING_2]: createCommand({
|
|
49
50
|
name: CommandType.HEADING_2,
|
|
50
|
-
title: 'Заголовок 2',
|
|
51
|
+
title: 'Заголовок 2 (h2)',
|
|
51
52
|
exec() {
|
|
52
53
|
this.editor
|
|
53
54
|
.chain()
|
|
@@ -62,7 +63,7 @@ export const CommandMap = {
|
|
|
62
63
|
}),
|
|
63
64
|
[CommandType.HEADING_3]: createCommand({
|
|
64
65
|
name: CommandType.HEADING_3,
|
|
65
|
-
title: 'Заголовок 3',
|
|
66
|
+
title: 'Заголовок 3 (h3)',
|
|
66
67
|
exec() {
|
|
67
68
|
this.editor
|
|
68
69
|
.chain()
|
|
@@ -77,7 +78,7 @@ export const CommandMap = {
|
|
|
77
78
|
}),
|
|
78
79
|
[CommandType.HEADING_4]: createCommand({
|
|
79
80
|
name: CommandType.HEADING_4,
|
|
80
|
-
title: 'Заголовок 4',
|
|
81
|
+
title: 'Заголовок 4 (h4)',
|
|
81
82
|
exec() {
|
|
82
83
|
this.editor
|
|
83
84
|
.chain()
|
|
@@ -407,40 +408,6 @@ export const ImageOptionsMap = {
|
|
|
407
408
|
})
|
|
408
409
|
};
|
|
409
410
|
|
|
410
|
-
export const LinkOptionsMap = {
|
|
411
|
-
[ToolType.INSERT_LINK]: createInputAutoTool({
|
|
412
|
-
name: ToolType.INSERT_LINK,
|
|
413
|
-
title: 'Вставить ссылку',
|
|
414
|
-
exec(url) {
|
|
415
|
-
const { editor } = this;
|
|
416
|
-
|
|
417
|
-
if (url === '') {
|
|
418
|
-
editor
|
|
419
|
-
.chain()
|
|
420
|
-
.focus()
|
|
421
|
-
.extendMarkRange(MarkType.LINK)
|
|
422
|
-
.unsetLink()
|
|
423
|
-
.run();
|
|
424
|
-
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
editor
|
|
429
|
-
.chain()
|
|
430
|
-
.focus()
|
|
431
|
-
.extendMarkRange(MarkType.LINK)
|
|
432
|
-
.setLink({ href: url })
|
|
433
|
-
.run();
|
|
434
|
-
},
|
|
435
|
-
getValue() {
|
|
436
|
-
return this.editor.getAttributes(MarkType.LINK).href;
|
|
437
|
-
},
|
|
438
|
-
isEnabled() {
|
|
439
|
-
return this.editor.can().setLink();
|
|
440
|
-
},
|
|
441
|
-
})
|
|
442
|
-
}
|
|
443
|
-
|
|
444
411
|
export const ToolsMap = {
|
|
445
412
|
[ToolType.UNDO]: createButtonTool({
|
|
446
413
|
name: ToolType.UNDO,
|
|
@@ -642,11 +609,39 @@ export const ToolsMap = {
|
|
|
642
609
|
return this.editor.can().setHorizontalRule();
|
|
643
610
|
}
|
|
644
611
|
}),
|
|
645
|
-
[ToolType.LINK]:
|
|
612
|
+
[ToolType.LINK]: createLinkTool({
|
|
646
613
|
name: ToolType.LINK,
|
|
647
614
|
icon: 'link-plus',
|
|
648
615
|
title: 'Ссылка',
|
|
649
|
-
|
|
616
|
+
getValue() {
|
|
617
|
+
const linkAttrs = this.editor.getAttributes(MarkType.LINK);
|
|
618
|
+
const { href: url, target } = linkAttrs;
|
|
619
|
+
return { url, target };
|
|
620
|
+
},
|
|
621
|
+
isEnabled() {
|
|
622
|
+
return this.editor.can().setLink();
|
|
623
|
+
},
|
|
624
|
+
exec({ url, target }) {
|
|
625
|
+
const { editor } = this;
|
|
626
|
+
|
|
627
|
+
if (url === '') {
|
|
628
|
+
editor
|
|
629
|
+
.chain()
|
|
630
|
+
.focus()
|
|
631
|
+
.extendMarkRange(MarkType.LINK)
|
|
632
|
+
.unsetLink()
|
|
633
|
+
.run();
|
|
634
|
+
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
editor
|
|
639
|
+
.chain()
|
|
640
|
+
.focus()
|
|
641
|
+
.extendMarkRange(MarkType.LINK)
|
|
642
|
+
.setLink({ href: url, ...(target && { target }) })
|
|
643
|
+
.run();
|
|
644
|
+
}
|
|
650
645
|
}),
|
|
651
646
|
[ToolType.BOLD]: createButtonTool({
|
|
652
647
|
name: ToolType.BOLD,
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
</div>
|
|
50
50
|
</template>
|
|
51
51
|
<template #editor-content="{ style }">
|
|
52
|
-
<div class="editor-content pad-v-l1"
|
|
52
|
+
<div :style="style" class="editor-content pad-v-l1" >
|
|
53
53
|
<div class="h-100">
|
|
54
54
|
<editor-content :editor="editor" class="h-100"/>
|
|
55
55
|
</div>
|
|
@@ -257,4 +257,15 @@ export default {
|
|
|
257
257
|
margin-right: 0.25rem;
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
|
+
|
|
261
|
+
::v-deep .ProseMirror {
|
|
262
|
+
& .ProseMirror-selectednode {
|
|
263
|
+
outline: 2px solid var(--color-focus);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
li > p {
|
|
267
|
+
display: inline-block;
|
|
268
|
+
margin: 0;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
260
271
|
</style>
|