goodteditor-ui 1.0.17 → 1.0.19
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 +2 -0
- package/package.json +15 -2
- package/src/components/ui/WysiwygEditor/WysiwygEditor.d.ts +119 -0
- package/src/components/ui/WysiwygEditor/constants.js +255 -0
- package/src/components/ui/WysiwygEditor/extensions/blockquote.js +15 -0
- package/src/components/ui/WysiwygEditor/extensions/bold.js +15 -0
- package/src/components/ui/WysiwygEditor/extensions/bullet-list.js +15 -0
- package/src/components/ui/WysiwygEditor/extensions/code-block.js +13 -0
- package/src/components/ui/WysiwygEditor/extensions/code.js +13 -0
- package/src/components/ui/WysiwygEditor/extensions/font-size.js +34 -0
- package/src/components/ui/WysiwygEditor/extensions/formatting.js +14 -0
- package/src/components/ui/WysiwygEditor/extensions/heading.js +13 -0
- package/src/components/ui/WysiwygEditor/extensions/horizontal-rule.js +15 -0
- package/src/components/ui/WysiwygEditor/extensions/image.js +15 -0
- package/src/components/ui/WysiwygEditor/extensions/index.d.ts +32 -0
- package/src/components/ui/WysiwygEditor/extensions/index.js +32 -0
- package/src/components/ui/WysiwygEditor/extensions/italic.js +15 -0
- package/src/components/ui/WysiwygEditor/extensions/link.js +16 -0
- package/src/components/ui/WysiwygEditor/extensions/list-item.js +15 -0
- package/src/components/ui/WysiwygEditor/extensions/ordered-list.js +15 -0
- package/src/components/ui/WysiwygEditor/extensions/paragraph.js +23 -0
- package/src/components/ui/WysiwygEditor/extensions/strike.js +15 -0
- package/src/components/ui/WysiwygEditor/extensions/table-cell.js +13 -0
- package/src/components/ui/WysiwygEditor/extensions/table-header.js +15 -0
- package/src/components/ui/WysiwygEditor/extensions/table-row.js +15 -0
- package/src/components/ui/WysiwygEditor/extensions/table.js +29 -0
- package/src/components/ui/WysiwygEditor/extensions/text-align.js +5 -0
- package/src/components/ui/WysiwygEditor/extensions/text-style.js +15 -0
- package/src/components/ui/WysiwygEditor/extensions/underline.js +15 -0
- package/src/components/ui/WysiwygEditor/index.d.ts +4 -0
- package/src/components/ui/WysiwygEditor/index.js +4 -0
- package/src/components/ui/WysiwygEditor/renders/Button.vue +26 -0
- package/src/components/ui/WysiwygEditor/renders/ColorPicker.vue +42 -0
- package/src/components/ui/WysiwygEditor/renders/InputAuto.vue +33 -0
- package/src/components/ui/WysiwygEditor/renders/InputBrowse.vue +35 -0
- package/src/components/ui/WysiwygEditor/renders/InputUnits.vue +37 -0
- package/src/components/ui/WysiwygEditor/renders/Select.vue +45 -0
- package/src/components/ui/WysiwygEditor/renders/ToolbarPopover.vue +50 -0
- package/src/components/ui/WysiwygEditor/renders/index.d.ts +7 -0
- package/src/components/ui/WysiwygEditor/renders/index.js +7 -0
- package/src/components/ui/WysiwygEditor/renders/mixins/RenderMixin.js +39 -0
- package/src/components/ui/WysiwygEditor/renders/mixins/index.js +1 -0
- package/src/components/ui/WysiwygEditor/tools-and-commands.js +709 -0
- package/src/components/ui/WysiwygEditor/utils.js +72 -0
- package/src/components/ui/WysiwygEditor.md +18 -0
- package/src/components/ui/WysiwygEditor.vue +260 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ToolsMap } from './tools-and-commands';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('./WysiwygEditor').ToolDef} Tool
|
|
5
|
+
* @typedef {import('./WysiwygEditor').CommandDef} Command
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {Tool | string} tool
|
|
10
|
+
* @param {string} toolName
|
|
11
|
+
* @return boolean
|
|
12
|
+
*/
|
|
13
|
+
const findTool = (tool, toolName) => {
|
|
14
|
+
if (typeof tool === 'string') {
|
|
15
|
+
return toolName === tool;
|
|
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
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
return tools.reduce((acc, toolsGroup) => [...acc, populateToolsGroup(toolsGroup)], []);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {Tool|Command} value
|
|
53
|
+
* @param {Record<string, any>} context
|
|
54
|
+
* @return {Tool|Command}
|
|
55
|
+
*/
|
|
56
|
+
export const bindContext = (value, context) => {
|
|
57
|
+
if (value == null || typeof value !== 'object') {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return Object.entries(value).reduce((acc, [propName, propValue]) => {
|
|
62
|
+
if (typeof propValue === 'function') {
|
|
63
|
+
return { ...acc, [propName]: propValue.bind(context) };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (Array.isArray(propValue)) {
|
|
67
|
+
return { ...acc, [propName]: propValue.map((childProp) => bindContext(childProp, context)) };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { ...acc, [propName]: propValue };
|
|
71
|
+
}, {});
|
|
72
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
```vue
|
|
2
|
+
<template>
|
|
3
|
+
<div class="pad-l5">
|
|
4
|
+
<ui-wysiwyg-editor v-model="model"></ui-wysiwyg-editor>
|
|
5
|
+
</div>
|
|
6
|
+
</template>
|
|
7
|
+
<script>
|
|
8
|
+
import UiWysiwygEditor from './WysiwygEditor.vue';
|
|
9
|
+
export default {
|
|
10
|
+
components: { UiWysiwygEditor },
|
|
11
|
+
data: () => ({
|
|
12
|
+
model: `<p>This WYSIWYG is based on
|
|
13
|
+
<a target="_blank" rel="noopener noreferrer nofollow" href="https://tiptap.dev/" class="color-link">tiptap</a>
|
|
14
|
+
editor framework.</p>`
|
|
15
|
+
})
|
|
16
|
+
};
|
|
17
|
+
</script>
|
|
18
|
+
```
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<grid class="editor" v-bind="$options.static.GridProps">
|
|
3
|
+
<template #editor-toolbar="{ style }">
|
|
4
|
+
<div :style="style" class="editor-toolbar">
|
|
5
|
+
<!--
|
|
6
|
+
@slot Toolbar slot
|
|
7
|
+
@binding {Tool[][]} toolGroups 2d matrix of using tools split into groups
|
|
8
|
+
@binding {Function} resolveGroupTitle resolve group title function(index:number)
|
|
9
|
+
@binding {Function} buildToolBinds build tool binds function(tool:Tool)
|
|
10
|
+
-->
|
|
11
|
+
<slot
|
|
12
|
+
v-if="editor != null"
|
|
13
|
+
name="toolbar"
|
|
14
|
+
v-bind="{ toolGroups, resolveGroupTitle, buildToolBinds }">
|
|
15
|
+
<div class="row row-gap-l1">
|
|
16
|
+
<div
|
|
17
|
+
v-for="(toolsGroup, groupIndex) of toolGroups"
|
|
18
|
+
:key="groupIndex"
|
|
19
|
+
class="col col-vmid col-auto">
|
|
20
|
+
<div class="d-flex flex-col flex-v-center">
|
|
21
|
+
<div class="group-header">
|
|
22
|
+
<!--
|
|
23
|
+
@slot Group header slot
|
|
24
|
+
@binding {number} groupIndex index of the tools group
|
|
25
|
+
@binding {Function} resolveGroupTitle resolve group title function(index:number)
|
|
26
|
+
-->
|
|
27
|
+
<slot name="group-header" v-bind="{ groupIndex, resolveGroupTitle }">
|
|
28
|
+
<div class="text-small text-center">{{ resolveGroupTitle(groupIndex) }}</div>
|
|
29
|
+
</slot>
|
|
30
|
+
</div>
|
|
31
|
+
<div>
|
|
32
|
+
<div
|
|
33
|
+
v-for="tool of toolsGroup"
|
|
34
|
+
:key="tool.name"
|
|
35
|
+
class="tool d-inline-block">
|
|
36
|
+
<!--
|
|
37
|
+
@slot Tool slot
|
|
38
|
+
@binding {Tool} tool tool's already bound to editor context
|
|
39
|
+
-->
|
|
40
|
+
<slot name="tool" v-bind="buildToolBinds(tool)">
|
|
41
|
+
<component :is="tool.render" :tool="buildToolBinds(tool)" />
|
|
42
|
+
</slot>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</slot>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
51
|
+
<template #editor-content="{ style }">
|
|
52
|
+
<div class="editor-content pad-v-l1" :style="style">
|
|
53
|
+
<div class="h-100">
|
|
54
|
+
<editor-content :editor="editor" class="h-100"/>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</template>
|
|
58
|
+
</grid>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<script>
|
|
62
|
+
import { Editor, EditorContent } from '@tiptap/vue-2';
|
|
63
|
+
|
|
64
|
+
import { debounce } from './utils/Helpers';
|
|
65
|
+
import Grid from './Grid.vue';
|
|
66
|
+
import * as Extensions from './WysiwygEditor/extensions';
|
|
67
|
+
import { buildToolGroups, bindContext } from './WysiwygEditor/utils';
|
|
68
|
+
import { DefaultTools, DefaultToolGroupsTitles } from './WysiwygEditor/constants';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @typedef {import('./WysiwygEditor/WysiwygEditor').ToolDef} Tool
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
export default {
|
|
75
|
+
components: {
|
|
76
|
+
Grid,
|
|
77
|
+
EditorContent
|
|
78
|
+
},
|
|
79
|
+
props: {
|
|
80
|
+
/**
|
|
81
|
+
* @model
|
|
82
|
+
*/
|
|
83
|
+
value: {
|
|
84
|
+
type: String,
|
|
85
|
+
default: '',
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* 2d matrix of using tools
|
|
89
|
+
*/
|
|
90
|
+
tools: {
|
|
91
|
+
type: Array,
|
|
92
|
+
default: () => [],
|
|
93
|
+
validator: (tools) => tools.flat().every((tool) =>
|
|
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')
|
|
104
|
+
},
|
|
105
|
+
/**
|
|
106
|
+
* @public
|
|
107
|
+
* func allows to receive image url from the environment
|
|
108
|
+
*/
|
|
109
|
+
getImageUrl: {
|
|
110
|
+
type: Function,
|
|
111
|
+
default: async () => {}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
data: () => ({
|
|
115
|
+
/** @type {Editor} */
|
|
116
|
+
editor: null,
|
|
117
|
+
/** @type {Tool[][]} */
|
|
118
|
+
appliedTools: null,
|
|
119
|
+
/** @type {string[]} */
|
|
120
|
+
appliedGroupsTitles: null,
|
|
121
|
+
/** @type {number} */
|
|
122
|
+
caretPosition: 0
|
|
123
|
+
}),
|
|
124
|
+
computed: {
|
|
125
|
+
/**
|
|
126
|
+
* @return {string}
|
|
127
|
+
*/
|
|
128
|
+
content() {
|
|
129
|
+
return this.editor?.getHTML() ?? '';
|
|
130
|
+
},
|
|
131
|
+
/**
|
|
132
|
+
* @return {Tool[][]}
|
|
133
|
+
*/
|
|
134
|
+
toolGroups() {
|
|
135
|
+
return buildToolGroups(this.appliedTools);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
static: {
|
|
139
|
+
GridProps: {
|
|
140
|
+
areas: [['editor-toolbar'], ['editor-content']],
|
|
141
|
+
rows: ['auto', '1fr'],
|
|
142
|
+
gap: ['0.5rem', 0]
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
watch: {
|
|
146
|
+
/**
|
|
147
|
+
* @param {string} value
|
|
148
|
+
*/
|
|
149
|
+
value(value) {
|
|
150
|
+
const isSame = this.content === value;
|
|
151
|
+
|
|
152
|
+
if (isSame) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this.editor.commands.setContent(value, false);
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
created() {
|
|
160
|
+
this.onSelectionUpdateDebounced = debounce(this.onSelectionUpdate, 300);
|
|
161
|
+
this.appliedTools = this.tools.length === 0 ? DefaultTools : this.tools;
|
|
162
|
+
this.appliedGroupsTitles = this.groupsTitles.length === 0 ? DefaultToolGroupsTitles : this.groupsTitles;
|
|
163
|
+
|
|
164
|
+
this.editor = new Editor({
|
|
165
|
+
content: this.value,
|
|
166
|
+
extensions: Object.values(Extensions),
|
|
167
|
+
autofocus: 'end',
|
|
168
|
+
editorProps: {
|
|
169
|
+
attributes: {
|
|
170
|
+
class: 'pad-3 scroll-y',
|
|
171
|
+
style: 'min-height: 100%; height: 0;'
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
onUpdate: () => {
|
|
175
|
+
this.onInput();
|
|
176
|
+
this.onChange();
|
|
177
|
+
},
|
|
178
|
+
onSelectionUpdate: this.onSelectionUpdateDebounced
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
activated() {
|
|
182
|
+
this.editor.commands.focus(this.caretPosition);
|
|
183
|
+
},
|
|
184
|
+
beforeDestroy() {
|
|
185
|
+
this.editor?.destroy();
|
|
186
|
+
},
|
|
187
|
+
methods: {
|
|
188
|
+
onInput() {
|
|
189
|
+
/**
|
|
190
|
+
* Input event
|
|
191
|
+
* @property {string} value
|
|
192
|
+
*/
|
|
193
|
+
this.$emit('input', this.content);
|
|
194
|
+
},
|
|
195
|
+
onChange() {
|
|
196
|
+
/**
|
|
197
|
+
* change event
|
|
198
|
+
* @property {string} value
|
|
199
|
+
*/
|
|
200
|
+
this.$emit('change', this.content);
|
|
201
|
+
},
|
|
202
|
+
/**
|
|
203
|
+
* @param {Transaction} transaction
|
|
204
|
+
*/
|
|
205
|
+
onSelectionUpdate({ transaction }) {
|
|
206
|
+
this.caretPosition = transaction.curSelection.to;
|
|
207
|
+
},
|
|
208
|
+
/**
|
|
209
|
+
* @param {Tool} tool
|
|
210
|
+
* @return Tool
|
|
211
|
+
*/
|
|
212
|
+
buildToolBinds(tool) {
|
|
213
|
+
const { editor, toolGroups } = this;
|
|
214
|
+
|
|
215
|
+
const context = Object.freeze({
|
|
216
|
+
get editor() {
|
|
217
|
+
return editor;
|
|
218
|
+
},
|
|
219
|
+
get tool() {
|
|
220
|
+
return tool;
|
|
221
|
+
},
|
|
222
|
+
get tools() {
|
|
223
|
+
return toolGroups.flat();
|
|
224
|
+
},
|
|
225
|
+
change: this.change,
|
|
226
|
+
getImageUrl: this.getImageUrl
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return bindContext(tool, context);
|
|
230
|
+
},
|
|
231
|
+
/**
|
|
232
|
+
* @param {number} groupIndex
|
|
233
|
+
* @return string
|
|
234
|
+
*/
|
|
235
|
+
resolveGroupTitle(groupIndex) {
|
|
236
|
+
const title = this.appliedGroupsTitles[groupIndex];
|
|
237
|
+
return typeof title === 'string' ? title : `Group ${groupIndex + 1}`;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
</script>
|
|
242
|
+
<style lang="less" scoped>
|
|
243
|
+
.editor {
|
|
244
|
+
&-toolbar {}
|
|
245
|
+
|
|
246
|
+
&-content {
|
|
247
|
+
min-height: 7.5rem;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.group-header {
|
|
252
|
+
min-height: calc(var(--line-height)*1rem)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.tool {
|
|
256
|
+
&:not(:last-child) {
|
|
257
|
+
margin-right: 0.25rem;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
</style>
|