@ebl-vue/editor-full 2.31.16 → 2.31.18
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/dist/index.mjs +247 -201
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/Editor/Editor.vue +2 -11
- package/src/components/Editor/EditorRender.vue +276 -0
- package/src/components/index.ts +5 -3
- package/src/plugins/code/index.css +10 -1
- package/src/plugins/code/index.ts +53 -23
package/package.json
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="ebl-editor" id="holder">
|
|
3
|
-
<!-- <div class="ce-block__content ebl-editor-title-wrap">
|
|
4
|
-
<input type="text" v-model="title" placeholder="请输入标题" class="ebl-editor-title-input"></input>
|
|
5
|
-
</div> -->
|
|
6
|
-
<!-- <div class="ce-block__content ebl-editor-time-wrap">
|
|
7
|
-
<span class="ebl-editor-time">最后修改时间:{{ lastUpdateTime }}</span>
|
|
8
|
-
</div> -->
|
|
9
|
-
<!-- <button @click="validate">保存</button> -->
|
|
10
|
-
|
|
11
|
-
</div>
|
|
2
|
+
<div class="ebl-editor" id="holder"></div>
|
|
12
3
|
</template>
|
|
13
4
|
<script lang="ts" setup>
|
|
14
5
|
|
|
@@ -83,7 +74,7 @@ const tunes = ['indent', 'blockAlignment'];
|
|
|
83
74
|
onMounted(() => {
|
|
84
75
|
|
|
85
76
|
editor = new EditorJS({
|
|
86
|
-
minHeight:
|
|
77
|
+
minHeight: 300,
|
|
87
78
|
holder: 'holder',
|
|
88
79
|
autofocus: true,
|
|
89
80
|
defaultBlock: 'paragraph',
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ebl-editor" id="holder"></div>
|
|
3
|
+
</template>
|
|
4
|
+
<script lang="ts" setup>
|
|
5
|
+
|
|
6
|
+
import EditorJS from '@ebl-vue/editorjs';
|
|
7
|
+
// import EditorJS from '../../../../editorjs/src/codex';
|
|
8
|
+
// window.VERSION="2.31.0"; //如果是用源码模式时,需要设置全局变量
|
|
9
|
+
|
|
10
|
+
import { onMounted, onUnmounted, watch, inject } from 'vue';
|
|
11
|
+
import { toRaw } from 'vue';
|
|
12
|
+
import { IEblEditorSettings } from '../../types';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 插件
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import DragDrop from '../../plugins/drag-drop';
|
|
19
|
+
import { H1, H2, H3, H4, H5, H6 } from '../../plugins/header';
|
|
20
|
+
import BlockAlignment from '../../plugins/block-alignment';
|
|
21
|
+
import Paragraph from '../../plugins/paragraph';
|
|
22
|
+
import Code from '../../plugins/code';
|
|
23
|
+
import type { OutputData } from '@ebl-vue/editorjs';
|
|
24
|
+
import Quote from "../../plugins/quote";
|
|
25
|
+
import Delimiter from '../../plugins/delimiter';
|
|
26
|
+
import List from '../../plugins/list';
|
|
27
|
+
import Undo from '../../plugins/undo';
|
|
28
|
+
import Alert from '../../plugins/alert';
|
|
29
|
+
import IndentTune from '../../plugins/indent';
|
|
30
|
+
import Marker from "../../plugins/marker";
|
|
31
|
+
import { ColorPickerWithoutSanitize } from "../../plugins/color-picker";
|
|
32
|
+
import Underline from "../../plugins/underline";
|
|
33
|
+
import InlineCode from "../../plugins/inline-code";
|
|
34
|
+
import Table from '../../plugins/table';
|
|
35
|
+
import ImageTool from "../../plugins/imageTool";
|
|
36
|
+
import { ImageToolTune } from "../../plugins/imageResizeCrop";
|
|
37
|
+
import Outline from '../../plugins/outline';
|
|
38
|
+
|
|
39
|
+
const EblEditorSettings: IEblEditorSettings | undefined = inject("EblEditorSettings");
|
|
40
|
+
defineOptions({
|
|
41
|
+
name: 'EblEditorRender',
|
|
42
|
+
inheritAttrs: false
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
let emits = defineEmits(["onReady", "onChange"]);
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
interface Props {
|
|
50
|
+
placeholder: string,
|
|
51
|
+
data: OutputData,
|
|
52
|
+
locale: any,
|
|
53
|
+
userStore: any,
|
|
54
|
+
showOutline?: boolean,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
58
|
+
|
|
59
|
+
placeholder: "Enter something",
|
|
60
|
+
data: ()=>({
|
|
61
|
+
blocks: []
|
|
62
|
+
}),
|
|
63
|
+
locale: {},
|
|
64
|
+
showOutline:false
|
|
65
|
+
});
|
|
66
|
+
let editorData=toRaw(props.data);
|
|
67
|
+
let editorIsReady = false;
|
|
68
|
+
|
|
69
|
+
let editor: EditorJS | null = null;
|
|
70
|
+
let undo: Undo | null = null;
|
|
71
|
+
let outline: Outline | null = null;
|
|
72
|
+
const tunes = ['indent', 'blockAlignment'];
|
|
73
|
+
onMounted(() => {
|
|
74
|
+
|
|
75
|
+
editor = new EditorJS({
|
|
76
|
+
minHeight: 50,
|
|
77
|
+
holder: 'holder',
|
|
78
|
+
autofocus: true,
|
|
79
|
+
defaultBlock: 'paragraph',
|
|
80
|
+
placeholder: props.placeholder,
|
|
81
|
+
tunes: tunes,
|
|
82
|
+
tools: {
|
|
83
|
+
inlineCode: InlineCode,
|
|
84
|
+
underline: Underline,
|
|
85
|
+
Color: {
|
|
86
|
+
class: ColorPickerWithoutSanitize
|
|
87
|
+
},
|
|
88
|
+
marker: {
|
|
89
|
+
class: Marker
|
|
90
|
+
},
|
|
91
|
+
indent: {
|
|
92
|
+
class: IndentTune,
|
|
93
|
+
},
|
|
94
|
+
alert: {
|
|
95
|
+
class: Alert,
|
|
96
|
+
inlineToolbar: true
|
|
97
|
+
},
|
|
98
|
+
List: {
|
|
99
|
+
class: List,
|
|
100
|
+
inlineToolbar: true,
|
|
101
|
+
config: {
|
|
102
|
+
defaultStyle: 'checklist',
|
|
103
|
+
|
|
104
|
+
maxLevel: 4,
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
h1: {
|
|
108
|
+
class: H1,
|
|
109
|
+
inlineToolbar: true
|
|
110
|
+
},
|
|
111
|
+
h2: {
|
|
112
|
+
class: H2,
|
|
113
|
+
inlineToolbar: true,
|
|
114
|
+
|
|
115
|
+
},
|
|
116
|
+
h3: {
|
|
117
|
+
class: H3,
|
|
118
|
+
inlineToolbar: true
|
|
119
|
+
},
|
|
120
|
+
h4: {
|
|
121
|
+
class: H4,
|
|
122
|
+
inlineToolbar: true
|
|
123
|
+
},
|
|
124
|
+
h5: {
|
|
125
|
+
class: H5,
|
|
126
|
+
inlineToolbar: true
|
|
127
|
+
},
|
|
128
|
+
h6: {
|
|
129
|
+
class: H6,
|
|
130
|
+
inlineToolbar: true
|
|
131
|
+
},
|
|
132
|
+
paragraph: {
|
|
133
|
+
class: Paragraph,
|
|
134
|
+
inlineToolbar: true
|
|
135
|
+
},
|
|
136
|
+
blockAlignment: {
|
|
137
|
+
class: BlockAlignment,
|
|
138
|
+
inlineToolbar: true
|
|
139
|
+
},
|
|
140
|
+
code: {
|
|
141
|
+
class: Code,
|
|
142
|
+
config: {
|
|
143
|
+
lang: "javascript",
|
|
144
|
+
theme: "github-dark-dimmed"//"catppuccin-latte"
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
quote: {
|
|
148
|
+
class: Quote,
|
|
149
|
+
inlineToolbar: true
|
|
150
|
+
},
|
|
151
|
+
delimiter: Delimiter,
|
|
152
|
+
table: {
|
|
153
|
+
class: Table,
|
|
154
|
+
inlineToolbar: true,
|
|
155
|
+
config: {
|
|
156
|
+
rows: 2,
|
|
157
|
+
cols: 3,
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
image: {
|
|
161
|
+
class: ImageTool,
|
|
162
|
+
inlineToolbar: true,
|
|
163
|
+
tunes: tunes.concat(['imageResize']),
|
|
164
|
+
config: {
|
|
165
|
+
types: ".png,.jpeg,.jpg,.gif,.webp",
|
|
166
|
+
userStore: props.userStore,
|
|
167
|
+
endpoints: {
|
|
168
|
+
byFile: EblEditorSettings?.fileUploadEndpoint,
|
|
169
|
+
//byUrl: EblEditorSettings?.urlUploadEndpoint
|
|
170
|
+
},
|
|
171
|
+
features: {
|
|
172
|
+
caption: false,
|
|
173
|
+
stretch: false,
|
|
174
|
+
border: false,
|
|
175
|
+
background: false,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
imageResize: {
|
|
182
|
+
class: ImageToolTune,
|
|
183
|
+
config: {
|
|
184
|
+
resize: true,
|
|
185
|
+
crop: false
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
data: editorData,
|
|
194
|
+
readOnly: true,
|
|
195
|
+
i18n: props.locale,
|
|
196
|
+
onChange: (api, event) => {
|
|
197
|
+
emits("onChange", api, event);
|
|
198
|
+
},
|
|
199
|
+
onReady: () => {
|
|
200
|
+
editorIsReady = true;
|
|
201
|
+
editorData = toRaw(props.data);
|
|
202
|
+
if (props.data && props.data.blocks && props.data.blocks.length > 0) {
|
|
203
|
+
editor!.render(editorData);
|
|
204
|
+
}
|
|
205
|
+
//文档大纲
|
|
206
|
+
if(props.showOutline){
|
|
207
|
+
outline = new Outline("holder",editor!);
|
|
208
|
+
outline.render(editorData);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
emits("onReady");
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
})
|
|
215
|
+
});
|
|
216
|
+
onUnmounted(() => {
|
|
217
|
+
if (editor !== null) {
|
|
218
|
+
editor.destroy();
|
|
219
|
+
editor = null;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
watch(() => props.data, (newVal, oldVal) => {
|
|
224
|
+
editorData = toRaw(newVal);
|
|
225
|
+
if (editor != null && editorIsReady) {
|
|
226
|
+
editor.render(editorData).then(() => {
|
|
227
|
+
if (outline != null && props.showOutline) {
|
|
228
|
+
outline.render(editorData);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
},{
|
|
236
|
+
deep:true
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 验证是否为空 */
|
|
242
|
+
function validate() {
|
|
243
|
+
return new Promise((resolve, reject) => {
|
|
244
|
+
if (editor !== null) {
|
|
245
|
+
editor.save().then((outputData: OutputData) => {
|
|
246
|
+
if (outputData.blocks.length > 0) {
|
|
247
|
+
return resolve(true);
|
|
248
|
+
|
|
249
|
+
}
|
|
250
|
+
return resolve(false);
|
|
251
|
+
});
|
|
252
|
+
} else {
|
|
253
|
+
return resolve(false);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function getData() {
|
|
258
|
+
return new Promise((resolve, reject) => {
|
|
259
|
+
if (editor !== null) {
|
|
260
|
+
editor.save().then((outputData: OutputData) => {
|
|
261
|
+
return resolve(outputData);
|
|
262
|
+
});
|
|
263
|
+
} else {
|
|
264
|
+
return resolve(null);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
defineExpose({
|
|
270
|
+
validate,
|
|
271
|
+
getData,
|
|
272
|
+
});
|
|
273
|
+
</script>
|
|
274
|
+
<style scoped>
|
|
275
|
+
|
|
276
|
+
</style>
|
package/src/components/index.ts
CHANGED
|
@@ -5,12 +5,13 @@ import type { SFCWithInstall } from 'types'
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
import Editor from "./Editor/Editor.vue"
|
|
8
|
-
|
|
8
|
+
import EditorRender from "./Editor/EditorRender.vue"
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
const items = [
|
|
13
|
-
Editor
|
|
13
|
+
Editor,
|
|
14
|
+
EditorRender
|
|
14
15
|
];
|
|
15
16
|
|
|
16
17
|
let PluginList: Plugin[] = [];
|
|
@@ -23,5 +24,6 @@ for (let key in items) {
|
|
|
23
24
|
|
|
24
25
|
export default PluginList;
|
|
25
26
|
export {
|
|
26
|
-
Editor as EblEditor
|
|
27
|
+
Editor as EblEditor,
|
|
28
|
+
EditorRender as EblEditorRender
|
|
27
29
|
};
|
|
@@ -51,8 +51,11 @@
|
|
|
51
51
|
font-size: 13px;
|
|
52
52
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
.ce-editorjs-x-shiki__span code{
|
|
55
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
|
56
|
+
}
|
|
55
57
|
.ce-editorjs-x-shiki__textarea {
|
|
58
|
+
all:unset;
|
|
56
59
|
white-space: pre;
|
|
57
60
|
overflow: hidden;
|
|
58
61
|
background-color: transparent;
|
|
@@ -72,6 +75,12 @@
|
|
|
72
75
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
.ce-editorjs-x-shiki__textarea::selection {
|
|
79
|
+
background-color: #083ca9; /* 选中文本的背景色 */
|
|
80
|
+
color: #ffffff; /* 选中文本的颜色 */
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
75
84
|
.ce-editorjs-x-shiki__lang {
|
|
76
85
|
/* position: absolute;
|
|
77
86
|
line-height: 14px;
|
|
@@ -365,7 +365,7 @@ export default class CodeTool implements BlockTool {
|
|
|
365
365
|
|
|
366
366
|
wrapperLang.innerHTML = this.data.lang
|
|
367
367
|
wrapperCopy.innerHTML = IconCopy;
|
|
368
|
-
wrapperCopyTip.innerText=this.api.i18n.t('Copied');
|
|
368
|
+
wrapperCopyTip.innerText = this.api.i18n.t('Copied');
|
|
369
369
|
|
|
370
370
|
|
|
371
371
|
|
|
@@ -398,7 +398,7 @@ export default class CodeTool implements BlockTool {
|
|
|
398
398
|
wrapperHolder.appendChild(span);
|
|
399
399
|
wrapperHolder.appendChild(textarea);
|
|
400
400
|
|
|
401
|
-
if(!this.readOnly) {
|
|
401
|
+
if (!this.readOnly) {
|
|
402
402
|
wrapperSelectorHolder.appendChild(selectorLanguage);
|
|
403
403
|
//wrapperSelectorHolder.appendChild(selectorTheme);
|
|
404
404
|
|
|
@@ -467,7 +467,7 @@ export default class CodeTool implements BlockTool {
|
|
|
467
467
|
}
|
|
468
468
|
});
|
|
469
469
|
wrapperCopy.addEventListener('click', (event: MouseEvent) => {
|
|
470
|
-
this.copyCode(this.data.code,event);
|
|
470
|
+
this.copyCode(this.data.code, event);
|
|
471
471
|
});
|
|
472
472
|
|
|
473
473
|
this.nodes.textarea = textarea;
|
|
@@ -486,7 +486,7 @@ export default class CodeTool implements BlockTool {
|
|
|
486
486
|
{
|
|
487
487
|
preprocess(code) {
|
|
488
488
|
// if (code.endsWith('\n'))
|
|
489
|
-
|
|
489
|
+
return `${code}\n`
|
|
490
490
|
},
|
|
491
491
|
pre(node) {
|
|
492
492
|
this.addClassToHast(node, 'ce-editorjs-x-shiki__span')
|
|
@@ -509,30 +509,60 @@ export default class CodeTool implements BlockTool {
|
|
|
509
509
|
|
|
510
510
|
}
|
|
511
511
|
|
|
512
|
-
private copyCode(code: string, event: MouseEvent) {
|
|
512
|
+
private copyCode(code: string, event: MouseEvent) {
|
|
513
513
|
if (this.data.code) {
|
|
514
|
-
navigator.clipboard.
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
tooltip
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
514
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
515
|
+
try {
|
|
516
|
+
navigator.clipboard.writeText(code).then(() => {
|
|
517
|
+
if (event.target) {
|
|
518
|
+
const parentEle = (event.target as HTMLElement).parentElement;
|
|
519
|
+
if (parentEle) {
|
|
520
|
+
const tooltip = parentEle.lastChild as HTMLElement;
|
|
521
|
+
if (tooltip) {
|
|
522
|
+
tooltip.classList.add('visible');
|
|
523
|
+
setTimeout(() => {
|
|
524
|
+
tooltip.classList.remove('visible');
|
|
525
|
+
|
|
526
|
+
}, 2000);
|
|
527
|
+
}
|
|
526
528
|
}
|
|
527
529
|
}
|
|
530
|
+
}).catch((err) => {
|
|
531
|
+
console.error(err);
|
|
532
|
+
alert("复制失败");
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
} catch (err) {
|
|
536
|
+
alert("复制失败");
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
// 传统方法:使用 textarea 和 execCommand
|
|
540
|
+
|
|
541
|
+
const textArea = document.createElement('textarea');
|
|
542
|
+
textArea.value = code;
|
|
543
|
+
|
|
544
|
+
// 避免滚动到底部
|
|
545
|
+
textArea.style.top = '0';
|
|
546
|
+
textArea.style.left = '0';
|
|
547
|
+
textArea.style.position = 'fixed';
|
|
548
|
+
textArea.style.opacity = '0';
|
|
549
|
+
textArea.style.pointerEvents = 'none';
|
|
550
|
+
textArea.style.zIndex = '-1000';
|
|
551
|
+
|
|
552
|
+
document.body.appendChild(textArea);
|
|
553
|
+
textArea.focus();
|
|
554
|
+
textArea.select();
|
|
555
|
+
try {
|
|
556
|
+
document.execCommand('copy');
|
|
557
|
+
document.body.removeChild(textArea);
|
|
558
|
+
} catch (err) {
|
|
559
|
+
alert("复制失败");
|
|
560
|
+
document.body.removeChild(textArea);
|
|
561
|
+
|
|
528
562
|
}
|
|
529
|
-
|
|
530
563
|
|
|
531
|
-
}
|
|
532
|
-
.catch((err) => {
|
|
533
|
-
console.error(err);
|
|
534
|
-
alert("Unable to copy");
|
|
535
|
-
});
|
|
564
|
+
}
|
|
536
565
|
}
|
|
566
|
+
|
|
537
567
|
}
|
|
538
568
|
}
|