goodteditor-ui 1.0.43 → 1.0.45
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/index.js +3 -1
- package/package.json +2 -1
- package/src/components/ui/InputTags.vue +4 -4
- package/src/components/ui/WysiwygEditor/constants.js +24 -15
- package/src/components/ui/WysiwygEditor/extensions/bubble-menu.js +18 -0
- package/src/components/ui/WysiwygEditor/extensions/index.d.ts +3 -31
- package/src/components/ui/WysiwygEditor/extensions/index.js +79 -31
- package/src/components/ui/WysiwygEditor/renders/Button.vue +2 -2
- package/src/components/ui/WysiwygEditor/renders/ColorPicker.vue +2 -1
- package/src/components/ui/WysiwygEditor/renders/Image.vue +5 -4
- package/src/components/ui/WysiwygEditor/renders/Link.vue +3 -2
- package/src/components/ui/WysiwygEditor/renders/ToolbarPopover.vue +2 -1
- package/src/components/ui/WysiwygEditor/utils.js +10 -38
- package/src/components/ui/WysiwygEditor.vue +78 -42
package/index.js
CHANGED
|
@@ -22,14 +22,16 @@ import Select from './src/components/ui/Select.vue';
|
|
|
22
22
|
import TimePicker from './src/components/ui/TimePicker.vue';
|
|
23
23
|
import Tooltip from './src/components/ui/Tooltip.vue';
|
|
24
24
|
import Grid from './src/components/ui/Grid.vue';
|
|
25
|
-
import WysiwygEditor from './src/components/ui/WysiwygEditor.vue';
|
|
25
|
+
import WysiwygEditor, { ToolType as WysiwygTool } from './src/components/ui/WysiwygEditor.vue';
|
|
26
26
|
// utils stuff
|
|
27
27
|
import FormComponent from './src/components/ui/utils/FormComponent';
|
|
28
28
|
|
|
29
29
|
const Utils = { FormComponent };
|
|
30
|
+
const Constants = { WysiwygTool };
|
|
30
31
|
|
|
31
32
|
export {
|
|
32
33
|
Utils,
|
|
34
|
+
Constants,
|
|
33
35
|
Avatar,
|
|
34
36
|
Badge,
|
|
35
37
|
Collapse,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "goodteditor-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.45",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"homepage": "https://goodt-ui.netlify.app/",
|
|
6
6
|
"scripts": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@popperjs/core": "2.11.2",
|
|
15
|
+
"@tiptap/extension-bubble-menu": "^2.2.4",
|
|
15
16
|
"@tiptap/extension-color": "2.0.x",
|
|
16
17
|
"@tiptap/extension-font-family": "2.0.x",
|
|
17
18
|
"@tiptap/extension-image": "2.0.x",
|
|
@@ -247,10 +247,10 @@ export default {
|
|
|
247
247
|
* @param {KeyboardEvent} e
|
|
248
248
|
*/
|
|
249
249
|
onInputKeydown(e) {
|
|
250
|
-
switch (e.
|
|
250
|
+
switch (e.key) {
|
|
251
|
+
case ',':
|
|
252
|
+
case ';':
|
|
251
253
|
case 'Tab':
|
|
252
|
-
case 'Comma':
|
|
253
|
-
case 'Semicolon':
|
|
254
254
|
case 'Enter': {
|
|
255
255
|
this.onInputEnter(e);
|
|
256
256
|
return;
|
|
@@ -265,7 +265,7 @@ export default {
|
|
|
265
265
|
return;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
switch (e.
|
|
268
|
+
switch (e.key) {
|
|
269
269
|
case 'Backspace': {
|
|
270
270
|
if (this.tagSelected === null) {
|
|
271
271
|
this.selectTag();
|
|
@@ -255,19 +255,28 @@ export const createLinkTool = ({ getValue = () => null, ...toolOptions }) => ({
|
|
|
255
255
|
});
|
|
256
256
|
|
|
257
257
|
export const DefaultTools = [
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
]
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
258
|
+
{
|
|
259
|
+
title: 'Оформление',
|
|
260
|
+
group: [ToolType.PARAGRAPH_STYLE, ToolType.TEXT_COLOR, ToolType.FONT_SIZE, ToolType.FONT_FAMILY, ToolType.BLOCKQUOTE]
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
title: 'Форматирование',
|
|
264
|
+
group: [ToolType.BOLD, ToolType.ITALIC, ToolType.UNDERLINE, ToolType.STRIKE, ToolType.CODE]
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
title: 'Выравнивание',
|
|
268
|
+
group: [ToolType.TEXT_ALIGN_LEFT, ToolType.TEXT_ALIGN_CENTER, ToolType.TEXT_ALIGN_RIGHT]
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
title: 'Нумерация',
|
|
272
|
+
group: [ToolType.ORDERED_LIST, ToolType.BULLET_LIST]
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
title: 'Вставка',
|
|
276
|
+
group: [ToolType.IMAGE, ToolType.LINK, ToolType.TABLE]
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
title: '',
|
|
280
|
+
group: [ToolType.CLEAR_FORMATTING, ToolType.HARD_BREAK]
|
|
281
|
+
}
|
|
273
282
|
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import BubbleMenuToExtend from '@tiptap/extension-bubble-menu';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('@tiptap/extension-bubble-menu').BubbleMenuOptions} BubbleMenuOptions
|
|
5
|
+
* @typedef {import('@tiptap/core').Extension} Extension
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {BubbleMenuOptions} options
|
|
10
|
+
* @return {Extension<BubbleMenuOptions, any>}
|
|
11
|
+
*/
|
|
12
|
+
export const BubbleMenu = ({
|
|
13
|
+
element,
|
|
14
|
+
tippyOptions = { maxWidth: 'none', zIndex: 1000 },
|
|
15
|
+
}) => BubbleMenuToExtend.configure({
|
|
16
|
+
element,
|
|
17
|
+
tippyOptions,
|
|
18
|
+
});
|
|
@@ -1,32 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export { History } from '@tiptap/extension-history';
|
|
4
|
-
export { Dropcursor } from '@tiptap/extension-dropcursor';
|
|
5
|
-
export { Gapcursor } from '@tiptap/extension-gapcursor';
|
|
6
|
-
export { Color } from '@tiptap/extension-color';
|
|
7
|
-
export { FontFamily } from '@tiptap/extension-font-family';
|
|
8
|
-
export { HardBreak } from '@tiptap/extension-hard-break';
|
|
1
|
+
import { Extension } from '@tiptap/core';
|
|
2
|
+
import { BubbleMenuOptions } from '@tiptap/extension-bubble-menu';
|
|
9
3
|
|
|
10
|
-
export {
|
|
11
|
-
export { TableRow } from './table-row';
|
|
12
|
-
export { TableCell } from './table-cell';
|
|
13
|
-
export { TableHeader } from './table-header';
|
|
14
|
-
export { Heading } from './heading';
|
|
15
|
-
export { Code } from './code';
|
|
16
|
-
export { CodeBlock } from './code-block';
|
|
17
|
-
export { Link } from './link';
|
|
18
|
-
export { TextAlign } from './text-align';
|
|
19
|
-
export { FontSize } from './font-size';
|
|
20
|
-
export { Paragraph } from './paragraph';
|
|
21
|
-
export { Formatting } from './formatting';
|
|
22
|
-
export { TextStyle } from './text-style';
|
|
23
|
-
export { Blockquote } from './blockquote';
|
|
24
|
-
export { Bold } from './bold';
|
|
25
|
-
export { Italic } from './italic';
|
|
26
|
-
export { Underline } from './underline';
|
|
27
|
-
export { Strike } from './strike';
|
|
28
|
-
export { OrderedList } from './ordered-list';
|
|
29
|
-
export { BulletList } from './bullet-list';
|
|
30
|
-
export { ListItem } from './list-item';
|
|
31
|
-
export { Image } from './image';
|
|
32
|
-
export { HorizontalRule } from './horizontal-rule';
|
|
4
|
+
export function resolveExtensions(options: { bubbleMenu: BubbleMenuOptions }): (Node<any, any>|Extension<any, any>)[]
|
|
@@ -1,32 +1,80 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { Document } from '@tiptap/extension-document';
|
|
2
|
+
import { Text } from '@tiptap/extension-text';
|
|
3
|
+
import { History } from '@tiptap/extension-history';
|
|
4
|
+
import { Dropcursor } from '@tiptap/extension-dropcursor';
|
|
5
|
+
import { Gapcursor } from '@tiptap/extension-gapcursor';
|
|
6
|
+
import { Color } from '@tiptap/extension-color';
|
|
7
|
+
import { FontFamily } from '@tiptap/extension-font-family';
|
|
8
|
+
import { HardBreak } from '@tiptap/extension-hard-break';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
10
|
+
import { Table } from './table';
|
|
11
|
+
import { TableRow } from './table-row';
|
|
12
|
+
import { TableCell } from './table-cell';
|
|
13
|
+
import { TableHeader } from './table-header';
|
|
14
|
+
import { Heading } from './heading';
|
|
15
|
+
import { Code } from './code';
|
|
16
|
+
import { CodeBlock } from './code-block';
|
|
17
|
+
import { Link } from './link';
|
|
18
|
+
import { TextAlign } from './text-align';
|
|
19
|
+
import { FontSize } from './font-size';
|
|
20
|
+
import { Paragraph } from './paragraph';
|
|
21
|
+
import { Formatting } from './formatting';
|
|
22
|
+
import { TextStyle } from './text-style';
|
|
23
|
+
import { Blockquote } from './blockquote';
|
|
24
|
+
import { Bold } from './bold';
|
|
25
|
+
import { Italic } from './italic';
|
|
26
|
+
import { Underline } from './underline';
|
|
27
|
+
import { Strike } from './strike';
|
|
28
|
+
import { OrderedList } from './ordered-list';
|
|
29
|
+
import { BulletList } from './bullet-list';
|
|
30
|
+
import { ListItem } from './list-item';
|
|
31
|
+
import { Image } from './image';
|
|
32
|
+
import { HorizontalRule } from './horizontal-rule';
|
|
33
|
+
import { BubbleMenu } from './bubble-menu';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {import('@tiptap/core').Extension} Extension
|
|
37
|
+
* @typedef {import('@tiptap/extension-bubble-menu').BubbleMenuOptions} BubbleMenuOptions
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @param {{bubbleMenu: BubbleMenuOptions}} options
|
|
43
|
+
* @return {Array<Node<any, any>|Extension<any,any>>}
|
|
44
|
+
*/
|
|
45
|
+
export function resolveExtensions(options) {
|
|
46
|
+
return [
|
|
47
|
+
Document,
|
|
48
|
+
Text,
|
|
49
|
+
History,
|
|
50
|
+
Dropcursor,
|
|
51
|
+
Gapcursor,
|
|
52
|
+
Color,
|
|
53
|
+
FontFamily,
|
|
54
|
+
HardBreak,
|
|
55
|
+
Table,
|
|
56
|
+
TableRow,
|
|
57
|
+
TableCell,
|
|
58
|
+
TableHeader,
|
|
59
|
+
Heading,
|
|
60
|
+
Code,
|
|
61
|
+
CodeBlock,
|
|
62
|
+
Link,
|
|
63
|
+
TextAlign,
|
|
64
|
+
FontSize,
|
|
65
|
+
Paragraph,
|
|
66
|
+
Formatting,
|
|
67
|
+
TextStyle,
|
|
68
|
+
Blockquote,
|
|
69
|
+
Bold,
|
|
70
|
+
Italic,
|
|
71
|
+
Underline,
|
|
72
|
+
Strike,
|
|
73
|
+
OrderedList,
|
|
74
|
+
BulletList,
|
|
75
|
+
ListItem,
|
|
76
|
+
Image,
|
|
77
|
+
HorizontalRule,
|
|
78
|
+
BubbleMenu(options.bubbleMenu)
|
|
79
|
+
]
|
|
80
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :title="title">
|
|
3
3
|
<button
|
|
4
|
-
:class="{ 'btn-outline': !isActive }"
|
|
4
|
+
:class="{ 'btn-outline': !isActive, 'btn-primary': isActive }"
|
|
5
5
|
:disabled="!isEnabled"
|
|
6
|
-
class="btn btn-small btn-icon
|
|
6
|
+
class="btn btn-small btn-icon"
|
|
7
7
|
@click.stop="onClick">
|
|
8
8
|
<div class="icon">
|
|
9
9
|
<i :class="icon" class="mdi"></i>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :data-popover="popoverTargetId" :title="title">
|
|
3
3
|
<button
|
|
4
|
-
class="
|
|
4
|
+
:class="{ 'btn-outline': !popoverShow, 'btn-primary': popoverShow }"
|
|
5
|
+
class="btn btn-small btn-icon"
|
|
5
6
|
@click="togglePopover">
|
|
6
7
|
<div class="icon">
|
|
7
8
|
<i class="mdi" :class="icon"></i>
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
<template #button="{ togglePopover }">
|
|
4
4
|
<button
|
|
5
5
|
:title="title"
|
|
6
|
-
class="
|
|
6
|
+
:class="{ 'btn-outline': !isPopoverShown, 'btn-primary': isPopoverShown }"
|
|
7
|
+
class="btn btn-small btn-icon"
|
|
7
8
|
@click="togglePopover">
|
|
8
9
|
<div class="icon">
|
|
9
10
|
<i class="mdi" :class="icon"></i>
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
<div class="col col-vmid col-12-12">
|
|
55
56
|
<ui-select
|
|
56
57
|
v-model="image.align"
|
|
57
|
-
:options="AlignOptions"
|
|
58
|
+
:options="$options.static.AlignOptions"
|
|
58
59
|
size="small"
|
|
59
60
|
class="w-100"
|
|
60
61
|
@change="onChange" />
|
|
@@ -70,7 +71,7 @@
|
|
|
70
71
|
<div class="col col-vmid col-12-12">
|
|
71
72
|
<input-units
|
|
72
73
|
v-model="image.width"
|
|
73
|
-
:units="SizeUnits"
|
|
74
|
+
:units="$options.static.SizeUnits"
|
|
74
75
|
:disabled="image.isResponsive"
|
|
75
76
|
size="small"
|
|
76
77
|
@change="onChange">
|
|
@@ -86,7 +87,7 @@
|
|
|
86
87
|
<div class="col col-vmid col-12-12">
|
|
87
88
|
<input-units
|
|
88
89
|
v-model="image.height"
|
|
89
|
-
:units="SizeUnits"
|
|
90
|
+
:units="$options.static.SizeUnits"
|
|
90
91
|
:disabled="image.isResponsive"
|
|
91
92
|
size="small"
|
|
92
93
|
@change="onChange">
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
<template #button="{ togglePopover }">
|
|
4
4
|
<button
|
|
5
5
|
:title="title"
|
|
6
|
-
class="
|
|
6
|
+
:class="{ 'btn-outline': !isPopoverShown, 'btn-primary': isPopoverShown }"
|
|
7
|
+
class="btn btn-small btn-icon"
|
|
7
8
|
@click="togglePopover">
|
|
8
9
|
<div class="icon">
|
|
9
10
|
<i class="mdi" :class="icon"></i>
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
<div class="col col-vmid col-12-12">
|
|
26
27
|
<ui-select
|
|
27
28
|
v-model="target"
|
|
28
|
-
:options="TargetTypeOptions"
|
|
29
|
+
:options="$options.static.TargetTypeOptions"
|
|
29
30
|
class="w-100"
|
|
30
31
|
size="small" />
|
|
31
32
|
</div>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :data-popover="popoverTargetId" :title="title">
|
|
3
3
|
<button
|
|
4
|
-
class="
|
|
4
|
+
:class="{ 'btn-outline': !popoverShow, 'btn-primary': popoverShow }"
|
|
5
|
+
class="btn btn-small btn-icon"
|
|
5
6
|
@click="togglePopover">
|
|
6
7
|
<div class="icon">
|
|
7
8
|
<i class="mdi" :class="icon"></i>
|
|
@@ -3,49 +3,21 @@ import { ToolsMap } from './tools-and-commands';
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {import('./WysiwygEditor').ToolDef} Tool
|
|
5
5
|
* @typedef {import('./WysiwygEditor').CommandDef} Command
|
|
6
|
+
* @typedef {Object} ToolGroup
|
|
7
|
+
* @property {string} title
|
|
8
|
+
* @property {string[]} group
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
|
-
* @param {
|
|
10
|
-
* @
|
|
11
|
-
* @return boolean
|
|
12
|
+
* @param {ToolGroup[]} toolGroups
|
|
13
|
+
* @return {{title:string, group: Tool[]}[]}
|
|
12
14
|
*/
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return tool?.name === toolName;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @param {Tool[][]} tools
|
|
23
|
-
* @return Tool[][]
|
|
24
|
-
*/
|
|
25
|
-
export const buildToolGroups = (tools) => {
|
|
26
|
-
const toolsFlatted = tools.flat();
|
|
27
|
-
|
|
28
|
-
if (toolsFlatted.length === 0) {
|
|
29
|
-
return [Object.values(ToolsMap)];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const availableTools = Object
|
|
33
|
-
.values(ToolsMap)
|
|
34
|
-
.filter(({ name }) => toolsFlatted.some((tool) => findTool(tool, name)));
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @param {Tool[]} toolsGroup
|
|
38
|
-
* @return Tool[]
|
|
39
|
-
*/
|
|
40
|
-
const populateToolsGroup = (toolsGroup) =>
|
|
41
|
-
toolsGroup.reduce((acc, tool) => {
|
|
42
|
-
const { name: toolName = tool, ...rest } = typeof tool === 'string' ? {} : tool;
|
|
43
|
-
const foundTool = availableTools.find(({ name }) => findTool(tool, name));
|
|
44
|
-
|
|
45
|
-
return [...acc, { ...foundTool, ...rest }];
|
|
46
|
-
}, []);
|
|
15
|
+
export const buildToolGroups = (toolGroups) => {
|
|
16
|
+
const toolsFlattened = new Set(toolGroups.flatMap(({ group }) => group));
|
|
17
|
+
const availableTools = Object.values(ToolsMap).filter(({ name }) => toolsFlattened.has(name));
|
|
18
|
+
const populateToolsGroup = (tools)=> availableTools.filter(({ name }) => tools.has(name));
|
|
47
19
|
|
|
48
|
-
return
|
|
20
|
+
return toolGroups.reduce((acc, { title, group }) => [...acc, { title, group: populateToolsGroup(new Set(group)) }], []);
|
|
49
21
|
};
|
|
50
22
|
|
|
51
23
|
/**
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<grid class="editor" v-bind="GridProps">
|
|
2
|
+
<grid class="editor" v-bind="$options.static.GridProps">
|
|
3
3
|
<template #editor-toolbar="{ style }">
|
|
4
|
-
<div :style="style" class="editor-toolbar">
|
|
4
|
+
<div :style="style" ref="menu" :class="{ 'bubble': bubbleMenu }" class="editor-toolbar">
|
|
5
5
|
<!--
|
|
6
6
|
@slot Toolbar slot
|
|
7
|
-
@binding {Tool[]
|
|
8
|
-
@binding {Function}
|
|
9
|
-
@binding {Function} buildToolBinds build tool binds function(tool:Tool)
|
|
7
|
+
@binding {title: string, group: Tool[]} toolGroups array of using tools
|
|
8
|
+
@binding {Function} buildToolBinds build tool binds function(tool:Tool)
|
|
10
9
|
-->
|
|
11
10
|
<slot
|
|
12
11
|
v-if="editor != null"
|
|
13
12
|
name="toolbar"
|
|
14
|
-
v-bind="{ toolGroups,
|
|
13
|
+
v-bind="{ toolGroups, buildToolBinds }">
|
|
15
14
|
<div class="row row-gap-l1">
|
|
16
15
|
<div
|
|
17
|
-
v-for="(
|
|
16
|
+
v-for="({ title, group }, groupIndex) of toolGroups"
|
|
18
17
|
:key="groupIndex"
|
|
19
18
|
class="col col-vmid col-auto">
|
|
20
19
|
<div class="d-flex flex-col flex-v-center">
|
|
@@ -22,22 +21,22 @@
|
|
|
22
21
|
<!--
|
|
23
22
|
@slot Group header slot
|
|
24
23
|
@binding {number} groupIndex index of the tools group
|
|
25
|
-
@binding {
|
|
24
|
+
@binding {string} title tools group title
|
|
26
25
|
-->
|
|
27
|
-
<slot name="group-header" v-bind="{ groupIndex,
|
|
28
|
-
<div class="text-small text-center">{{ resolveGroupTitle(groupIndex) }}</div>
|
|
26
|
+
<slot name="group-header" v-bind="{ groupIndex, title }">
|
|
27
|
+
<div class="text-small text-center">{{ resolveGroupTitle(title, groupIndex) }}</div>
|
|
29
28
|
</slot>
|
|
30
29
|
</div>
|
|
31
30
|
<div>
|
|
32
31
|
<div
|
|
33
|
-
v-for="tool of
|
|
32
|
+
v-for="tool of group"
|
|
34
33
|
:key="tool.name"
|
|
35
34
|
class="tool d-inline-block">
|
|
36
35
|
<!--
|
|
37
36
|
@slot Tool slot
|
|
38
37
|
@binding {Tool} tool tool's already bound to editor context
|
|
39
38
|
-->
|
|
40
|
-
<slot name="tool" v-bind="buildToolBinds(tool)">
|
|
39
|
+
<slot name="tool" v-bind="{ tool: buildToolBinds(tool) }">
|
|
41
40
|
<component :is="tool.render" :tool="buildToolBinds(tool)" />
|
|
42
41
|
</slot>
|
|
43
42
|
</div>
|
|
@@ -63,16 +62,23 @@ import { Editor, EditorContent } from '@tiptap/vue-2';
|
|
|
63
62
|
|
|
64
63
|
import { debounce } from './utils/Helpers';
|
|
65
64
|
import Grid from './Grid.vue';
|
|
66
|
-
import
|
|
65
|
+
import { resolveExtensions } from './WysiwygEditor/extensions';
|
|
67
66
|
import { buildToolGroups, bindContext } from './WysiwygEditor/utils';
|
|
68
|
-
import { DefaultTools
|
|
67
|
+
import { DefaultTools } from './WysiwygEditor/constants';
|
|
68
|
+
import Popup from '@/components/ui/Popup.vue';
|
|
69
|
+
|
|
70
|
+
export { ToolType } from './WysiwygEditor/constants';
|
|
69
71
|
|
|
70
72
|
/**
|
|
73
|
+
* @typedef {Omit<import('@tiptap/extension-bubble-menu').BubbleMenuOptions, 'element'>} BubbleMenuOptions
|
|
74
|
+
* @property {boolean} isEnabled
|
|
75
|
+
* @typedef {import('vue').PropOptions.<BubbleMenuOptions>} BubbleMenuOptionsProp
|
|
71
76
|
* @typedef {import('./WysiwygEditor/WysiwygEditor').ToolDef} Tool
|
|
72
77
|
*/
|
|
73
78
|
|
|
74
79
|
export default {
|
|
75
80
|
components: {
|
|
81
|
+
Popup,
|
|
76
82
|
Grid,
|
|
77
83
|
EditorContent
|
|
78
84
|
},
|
|
@@ -85,22 +91,12 @@ export default {
|
|
|
85
91
|
default: '',
|
|
86
92
|
},
|
|
87
93
|
/**
|
|
88
|
-
*
|
|
94
|
+
* array of using tools: { title: string, group: string[] }
|
|
89
95
|
*/
|
|
90
96
|
tools: {
|
|
91
97
|
type: Array,
|
|
92
98
|
default: () => [],
|
|
93
|
-
validator: (tools) => tools.
|
|
94
|
-
typeof tool === 'string' || (tool != null && typeof tool === 'object' && Array.isArray(tool) === false)
|
|
95
|
-
)
|
|
96
|
-
},
|
|
97
|
-
/**
|
|
98
|
-
* editor tool groups titles
|
|
99
|
-
*/
|
|
100
|
-
groupsTitles: {
|
|
101
|
-
type: Array,
|
|
102
|
-
default: () => [],
|
|
103
|
-
validator: (titles) => titles.every((title) => typeof title === 'string')
|
|
99
|
+
validator: (tools) => tools.every((tool) => typeof tool === 'object' && Array.isArray(tool) === false)
|
|
104
100
|
},
|
|
105
101
|
/**
|
|
106
102
|
* @public
|
|
@@ -109,16 +105,29 @@ export default {
|
|
|
109
105
|
getImageUrl: {
|
|
110
106
|
type: Function,
|
|
111
107
|
default: async () => {}
|
|
108
|
+
},
|
|
109
|
+
/**
|
|
110
|
+
* bubble menu options
|
|
111
|
+
* @type {BubbleMenuOptionsProp}
|
|
112
|
+
* @see See [Bubble menu settings](https://tiptap.dev/docs/editor/api/extensions/bubble-menu#settings)
|
|
113
|
+
*/
|
|
114
|
+
bubbleMenu: {
|
|
115
|
+
type: Object,
|
|
116
|
+
default: () => ({
|
|
117
|
+
isEnabled: false
|
|
118
|
+
})
|
|
119
|
+
},
|
|
120
|
+
/**
|
|
121
|
+
* whether the outline of the focused content editor should be visible
|
|
122
|
+
*/
|
|
123
|
+
focusVisible: {
|
|
124
|
+
type: Boolean,
|
|
125
|
+
default: true
|
|
112
126
|
}
|
|
113
127
|
},
|
|
114
128
|
data: () => ({
|
|
115
129
|
/** @type {Editor} */
|
|
116
130
|
editor: null,
|
|
117
|
-
/** @type {Tool[][]} */
|
|
118
|
-
appliedTools: null,
|
|
119
|
-
/** @type {string[]} */
|
|
120
|
-
appliedGroupsTitles: null,
|
|
121
|
-
/** @type {number} */
|
|
122
131
|
caretPosition: 0
|
|
123
132
|
}),
|
|
124
133
|
computed: {
|
|
@@ -129,10 +138,20 @@ export default {
|
|
|
129
138
|
return this.editor?.getHTML() ?? '';
|
|
130
139
|
},
|
|
131
140
|
/**
|
|
132
|
-
* @return {Tool[][]}
|
|
141
|
+
* @return {{title: string, group: Tool[]}[]}
|
|
133
142
|
*/
|
|
134
143
|
toolGroups() {
|
|
135
|
-
|
|
144
|
+
const tools = this.tools.length === 0 ? DefaultTools : this.tools;
|
|
145
|
+
return buildToolGroups(tools);
|
|
146
|
+
},
|
|
147
|
+
editorClass() {
|
|
148
|
+
const classes = ['pad-3', 'scroll-y'];
|
|
149
|
+
|
|
150
|
+
if (this.focusVisible === false) {
|
|
151
|
+
classes.push('focus-outline-none');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return classes.join(' ');
|
|
136
155
|
}
|
|
137
156
|
},
|
|
138
157
|
static: {
|
|
@@ -158,16 +177,22 @@ export default {
|
|
|
158
177
|
},
|
|
159
178
|
created() {
|
|
160
179
|
this.onSelectionUpdateDebounced = debounce(this.onSelectionUpdate, 300);
|
|
161
|
-
|
|
162
|
-
|
|
180
|
+
},
|
|
181
|
+
mounted() {
|
|
182
|
+
const { isEnabled: isBubbleMenuEnabled, ...bubbleMenuOptions } = this.bubbleMenu;
|
|
163
183
|
|
|
164
184
|
this.editor = new Editor({
|
|
165
185
|
content: this.value,
|
|
166
|
-
extensions:
|
|
186
|
+
extensions: resolveExtensions({
|
|
187
|
+
bubbleMenu: {
|
|
188
|
+
element: isBubbleMenuEnabled ? this.$refs.menu : null,
|
|
189
|
+
...bubbleMenuOptions
|
|
190
|
+
}
|
|
191
|
+
}),
|
|
167
192
|
autofocus: 'end',
|
|
168
193
|
editorProps: {
|
|
169
194
|
attributes: {
|
|
170
|
-
class:
|
|
195
|
+
class: this.editorClass,
|
|
171
196
|
style: 'min-height: 100%; height: 0;'
|
|
172
197
|
}
|
|
173
198
|
},
|
|
@@ -220,20 +245,20 @@ export default {
|
|
|
220
245
|
return tool;
|
|
221
246
|
},
|
|
222
247
|
get tools() {
|
|
223
|
-
|
|
248
|
+
return toolGroups.flatMap(({ group }) => group);
|
|
224
249
|
},
|
|
225
250
|
change: this.change,
|
|
226
|
-
getImageUrl: this.getImageUrl
|
|
251
|
+
getImageUrl: this.getImageUrl,
|
|
227
252
|
});
|
|
228
253
|
|
|
229
254
|
return bindContext(tool, context);
|
|
230
255
|
},
|
|
231
256
|
/**
|
|
257
|
+
* @param {string} title
|
|
232
258
|
* @param {number} groupIndex
|
|
233
259
|
* @return string
|
|
234
260
|
*/
|
|
235
|
-
resolveGroupTitle(groupIndex) {
|
|
236
|
-
const title = this.appliedGroupsTitles[groupIndex];
|
|
261
|
+
resolveGroupTitle(title, groupIndex) {
|
|
237
262
|
return typeof title === 'string' ? title : `Group ${groupIndex + 1}`;
|
|
238
263
|
}
|
|
239
264
|
}
|
|
@@ -241,7 +266,14 @@ export default {
|
|
|
241
266
|
</script>
|
|
242
267
|
<style lang="less" scoped>
|
|
243
268
|
.editor {
|
|
244
|
-
&-toolbar {
|
|
269
|
+
&-toolbar {
|
|
270
|
+
&.bubble {
|
|
271
|
+
padding: 1rem;
|
|
272
|
+
background-color: var(--color-white);
|
|
273
|
+
border-radius: var(--border-radius);
|
|
274
|
+
box-shadow: 0 1px 4px -2px rgba(0, 0, 0, 0.25);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
245
277
|
|
|
246
278
|
&-content {
|
|
247
279
|
min-height: 7.5rem;
|
|
@@ -259,6 +291,10 @@ export default {
|
|
|
259
291
|
}
|
|
260
292
|
|
|
261
293
|
::v-deep .ProseMirror {
|
|
294
|
+
&.focus-outline-none:focus-visible {
|
|
295
|
+
outline: none;
|
|
296
|
+
}
|
|
297
|
+
|
|
262
298
|
& .ProseMirror-selectednode {
|
|
263
299
|
outline: 2px solid var(--color-focus);
|
|
264
300
|
}
|