1、安装
pnpm i @editorjs/editorjs --save
2、初始化
在需要引用的文件中import
import EditorJS from '@editorjs/editorjs'
const editor=new EditorJS({
holder:'editor'
})
注意:holder的值对应的是块编辑器容器元素的id,可以自定义
3、插件配置
全局属性
| 属性名 | 类型 | 可用值 | 备注 |
|---|---|---|---|
| class | 组件 | 自定义安装 | 需要引入的插件 |
| inlineToolbar | boolean/string[] | 当为true时,展示所有的选项 数组可用选项如下 link:链接 marker:标识 bold:加粗 italic:斜体 inlineCode:行内代码 | 行内工具选项,可单独设置在各个组件内,也可全局设置 |
| toolbox | Object | icon:图标 title:标题 | 重写工具内部icon和title |
1)标题插件
安装:pnpm i @editorjs/header
属性说明:
| 属性名 | 类型 | 可用属性 | 备注 |
|---|---|---|---|
| config | Object | placeholder:占位符 levels:可用的标题等级,类型为number[] defaultLevel:默认标题等级 |
import EditorJS from '@editorjs/editorjs'
import { IconH1 } from '@codexteam/icons'
import Header from '@editorjs/header'
const editor=new EditorJS({
holder:'editor'
//全局设置:
inlineToolbar: ['link', 'marker', 'bold', 'italic', 'inlineCode'],
//在tools中配置需要的插件
tools:{
header:{
class:Header,
//inlineToolbar: ['link', 'marker']
config: {
placeholder: '请输入标题',
levels: [1, 2, 3,4,5,6],
defaultLevel: 1,
},
toolbox: {
title: '标题1',
icon: IconH1,
},
}
}
})
2)段落插件
安装:pnpm i @editorjs/paragraph
属性说明:
| 属性名 | 类型 | 可用属性 | 备注 |
|---|---|---|---|
| config | Object | placeholder:类型为string。占位符 levels:可用的标题等级,类型为preserveBlank:类型为boolean。保存的时候是否保存空段落 |
3)列表插件
安装:pnpm i @editorjs/list
属性说明
| 属性名 | 类型 | 可用属性 | 备注 |
|---|---|---|---|
| config | Object | defaultStyle:类型为string。默认列表样式,有序、无序或待办列表 maxLevel:类型为number。列表嵌套的最大级别,可以设置为1来禁用嵌套,默认为无限制。使用时按tab切换下一等级 counterTypes:类型为string[]。有序列表样式显示哪些计数器类型,可用值:numeric,upper-roman,alpha,默认为全部展示 |
4)warning插件
安装:pnpm i @editorjs/warning
属性说明
| 属性名 | 类型 | 可用属性 | 备注 |
|---|---|---|---|
| config | Object | titlePlaceholder:类型为string。 messagePlaceholder:类型为string。 |
5)alert插件
安装:pnpm i editorjs-alert
属性说明
| 属性名 | 类型 | 可用属性 | 备注 |
|---|---|---|---|
| config | Object | alertTypes:类型为string[],可用值:['primary', 'secondary', 'info', 'success', 'warning', 'danger', 'light', 'dark'] defaultType:类型为string。默认为info defaultAlign:类型为string。默认为left messagePlaceholder:类型为string。 |
6)table插件
安装:pnpm i @editorjs/table
属性说明
| 属性名 | 类型 | 可用属性 | 备注 |
|---|---|---|---|
| config | Object | rows:类型为number,默认值为2。初始化表格行数 cols:类型为number,默认值为2。初始化表格列数 maxRows:类型为number,默认值为5,最大行数 maxCols:类型为number,默认值为5,最大列数 withHeadings:类型为boolean,默认值为false。是否设置表格头 stretched:类型为boolean。是否拉伸以填充 |
7)code插件
安装:pnpm i @calumk/editorjs-codeflask
8)image插件
安装:pnpm i @editorjs/image
config属性:
| 属性名 | 类型 | 描述 |
|---|---|---|
| endpoints | {byFile: string, byUrl: string} | byFile:file类型的上传 byUrl:通过url上传 |
| field | string | 默认:image。POST请求中上传图片字段的名称 |
| types | string | 默认:image/*。能被接受的Mime类型 |
| additionalRequestData | object | 上传图片时携带的数据 |
| additionalRequestHeaders | object | 上传图片时携带的headers |
| captionPlaceholder | string | 默认:Caption。 |
| buttonContent | string | 上传图片按钮text |
| uploader | {{uploadByFile: function, uploadByUrl: function}} | 可选的自定义上传方式。详情见下文 |
| actions | array | 要显示在工具设置菜单中的自定义操作。详情见下文。 |
| features | object | 启用/禁用其他功能,如边界,背景和标题。详情见下文。 |
注意:如果没有使用自定义上传方式,endpoints参数是必要的。
actions的配置如下:
actions: [
{
name: 'new_button',
icon: '<svg>...</svg>',
title: 'New Button',
toggle: true,
action: (name) => {
alert(`${name} button clicked`);
}
}
]
features的配置如下:
features: {
border: false,
caption: 'optional',
stretch: false
}
uploader参数:
| 属性名 | 入参 | 返回值 | 描述 |
|---|---|---|---|
| uploadByFile | File | {Promise.<{success, file: {url}}>} | 上传文件到服务,返回上传完成后的图片数据 |
| uploadByUrl | string | {Promise.<{success, file: {url}}>} | 发送url到服务,通过URL加载图片,返回上传完成后的数据 |
9)分隔符插件
安装:pnpm i @coolbytes/editorjs-delimiter
config属性:
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| styleOptions | string[] | ['star', 'dash', 'line'] | 所支持的分隔符类型 |
| defaultStyle | string | 'star' | 首选分隔符样式 |
| lineWidthOptions | number[] | [8, 15, 25, 35, 50, 60, 100] | 分隔符类型为"line"时,线条width值 |
| defaultLineWidth | number | 25 | 分隔符类型为"line"时,默认线条width值 |
| lineThicknessOptions | number[] | [1, 2, 3, 4, 5, 6] | 分隔符类型为"line"时,线条粗细值 |
| defaultLineThickness | number | 2 | 分隔符类型为"line"时,默认线条粗细值 |
10)Quote插件
安装:pnpm i @editorjs/quote
config属性:
| 属性名 | 类型 | 描述 |
|---|---|---|
| quotePlaceholder | string | |
| captionPlaceholder | string | 标题的占位符 |
4、国际化
通过message属性进行配置,包含四个部分
ui:转换UI文本
toolNames:转换插件名称
tools:转换工具文本
blockTunes:转换Block Tunes
5、修改属性
1、通过传入的参数控制编辑器是否可以编辑
2、初始化数据进行渲染
<script setup lang="ts" name="BlockEditor">
import { watch, ref} from "vue";
import EditorJS from "@editorjs/editorjs";
import Header from "@editorjs/header";
import Paragraph from "@editorjs/paragraph";
import { IconH1 } from "@codexteam/icons";
//双向绑定的数据,可用于初始化渲染
const description = defineModel<{ blocks: any[] }>();
const props = withDefaults(defineProps<{ readOnly: boolean }>(), {
readOnly: true,
});
const editor = ref<EditorJS | null>(null);
// 初始化编辑器
const initEditor = () => {
if (editor.value) {
return;
}
editor.value = new EditorJS({
holder: "editor",
inlineToolbar: true,
data: description.value,
readOnly: props.readOnly,
tools: {
header: {
// @ts-ignore
class: Header,
inlineToolbar: ["link", "marker", "bold", "italic", "inlineCode"],
config: {
placeholder: "请输入标题",
levels: [1, 2, 3],
defaultLevel: 1,
},
toolbox: {
title: "标题1",
icon: IconH1,
},
},
paragraph: {
// @ts-ignore
class: Paragraph,
config: {
placeholder: "请输入内容",
preserveBlank: false, // 保存数据时是否保存空白
},
},
},
});
};
//监听传入的参数,修改是否可编辑
watch(
() => props.readOnly,
() => {
editor.value?.readOnly.toggle(props.readOnly);
},
{
immediate: true,
}
);
//通过监听数据,重新初始化编辑器
watch(
() => description.value,
() => {
initEditor();
},
{
immediate: true,
}
);
</script>
<template>
<div class="editor">
<div class="editble-area">
<div class="editor-container" id="editor"></div>
</div>
</div>
</template>
<style scoped lang="less">
</style>
6、完整代码示例
<script setup lang="ts" name="BlockEditor">
import { ref } from 'vue'
import EditorJS from '@editorjs/editorjs'
import Header from '@editorjs/header'
import List from '@editorjs/list'
import SimpleImage from '@editorjs/simple-image'
import ImageTool from '@editorjs/image'
import Warning from '@editorjs/warning'
// import Delimiter from '@editorjs/delimiter'
import Delimiter from '@coolbytes/editorjs-delimiter'
import LinkTool from '@editorjs/link'
import Embed from '@editorjs/embed'
import Table from '@editorjs/table'
import Checklist from '@editorjs/checklist'
import Quote from '@editorjs/quote'
import Marker from '@editorjs/marker'
import CodeTool from '@editorjs/code'
import InlineCode from '@editorjs/inline-code'
import Paragraph from '@editorjs/paragraph'
import { IconH1 } from '@codexteam/icons'
// import AceCodeEditorJS from 'ace-code-editorjs'
import EditorjsCodeflask from '@calumk/editorjs-codeflask'
import Alert from 'editorjs-alert'
import CodeBox from '@bomdi/codebox'
import type IBlockEditor from './BlockEditor'
const editor = new EditorJS({
holder: 'editor',
inlineToolbar: true,
tools: {
header: {
// @ts-ignore
class: Header,
inlineToolbar: ['link', 'marker', 'bold', 'italic', 'inlineCode'],
config: {
placeholder: '请输入标题',
levels: [1, 2, 3],
defaultLevel: 1,
},
toolbox: {
title: '标题1',
icon: IconH1,
},
},
paragraph: {
// @ts-ignore
class: Paragraph,
config: {
placeholder: '请输入内容',
preserveBlank: false, //保存数据时是否保存空白
},
},
list: {
// @ts-ignore
class: List,
inlineToolbar: true,
config: {
maxLevel: 3,
},
},
// image: SimpleImage,
checklist: {
class: Checklist,
inlineToolbar: true,
},
alert: {
class: Alert,
inlineToolbar: true,
shortcut: 'CMD+SHIFT+A',
config: {
alertTypes: [
'primary',
'secondary',
'info',
'success',
'warning',
'danger',
'light',
'dark',
],
defaultType: 'primary',
messagePlaceholder: '请输入',
},
},
quote: {
class: Quote,
inlineToolbar: true,
config: {
quotePlaceholder: 'Enter a quote',
captionPlaceholder: "Quote's author",
},
shortcut: 'CMD+SHIFT+O',
},
warning: {
class: Warning,
config: {
titlePlaceholder: 'dfsfsfsfdsf',
messagePlaceholder: 'kkkkkkkk',
},
},
//image可以上传视频,types中要配置
image: {
class: ImageTool,
config: {
uploader: {
/**
* Upload file to the server and return an uploaded image data
* @param {File} file - file selected from the device or pasted by drag-n-drop
* @return {Promise.<{success, file: {url}}>}
*/
uploadByFile(file: any) {
// your own uploading logic here
return MyAjax.post(file).then(() => {
return {
success: 1,
file: {
url: 'https://codex.so/upload/redactor_images/o_80beea670e49f04931ce9e3b2122ac70.jpg',
// any other image data you want to store, such as width, height, color, extension, etc
},
}
})
},
/**
* Send URL-string to the server. Backend should load image by this URL and return an uploaded image data
* @param {string} url - pasted image URL
* @return {Promise.<{success, file: {url}}>}
*/
uploadByUrl(file: any) {
// your ajax request for uploading
return MyAjax.post(file).then(() => {
return {
success: 1,
file: {
url: 'https://codex.so/upload/redactor_images/o_e48549d1855c7fc1807308dd14990126.jpg',
// any other image data you want to store, such as width, height, color, extension, etc
},
}
})
},
},
types: '.png',
// additionalRequestData:{} 随着图片上传要上传的数据
//additionalRequestHeaders:{}
// buttonContent: '',//上传图片按钮,只支持string
actions: [
{
name: 'new_button',
icon: '<svg>...</svg>',
title: 'New Button',
toggle: true,
action: (name: string) => {
alert(`${name} button clicked`)
},
},
],
},
},
marker: {
class: Marker,
shortcut: 'CMD+SHIFT+M',
},
code: {
class: EditorjsCodeflask,
shortcut: 'CMD+SHIFT+C',
},
// delimiter: Delimiter,
delimiter: {
class: Delimiter,
config: {
styleOptions: ['star', 'dash', 'line'],
defaultStyle: 'star',
lineWidthOptions: [8, 15, 25, 35, 50, 60, 100],
defaultLineWidth: 25,
lineThicknessOptions: [1, 2, 3, 4, 5, 6],
defaultLineThickness: 2,
},
},
inlineCode: {
class: InlineCode,
shortcut: 'CMD+SHIFT+C',
},
linkTool: LinkTool,
embed: Embed,
table: {
// @ts-ignore
class: Table,
inlineToolbar: true,
shortcut: 'CMD+ALT+T',
},
},
i18n: {
messages: {
ui: {
blockTunes: {
toggler: {
'Click to tune': '点击转换',
'or drag to move': '拖动',
},
},
inlineToolbar: {
converter: {
'Convert to': '转换',
},
},
toolbar: {
toolbox: {
Add: '工具栏添加',
},
},
popover: {
Filter: '过滤',
'Nothing found': '找不到',
'Convert to': '转换为',
},
},
toolNames: {
Text: '段落',
Bold: '加粗',
Italic: '斜体',
'Unordered List': '无序列表',
'Ordered List': '有序列表',
Checklist: '待办列表',
Warning: '警告',
Quote: '引用',
Code: '代码块',
CodeFlask: '代码块',
Delimiter: '分隔符',
'Raw HTML': 'HTML',
Table: '表格',
Link: '链接',
Marker: '标识',
InlineCode: '行内代码',
Image: '图片',
},
tools: {
paragraph: {
'Press Tab': '输入内容',
},
header: {
'Heading 1': '标题1',
'Heading 2': '标题2',
'Heading 3': '标题3',
'Heading 4': '标题4',
'Heading 5': '标题5',
'Heading 6': '标题6',
},
warning: {
// <-- 'Warning' tool will accept this dictionary section
Title: '警告',
Message: 'Сообщение',
},
link: {
'Add a link': '添加链接',
},
image: {
Caption: '标题',
'Select an Image': '选择图片',
'With border': '带边框',
'Stretch image': '拉伸图片',
'With background': '带背景',
},
code: {
'Enter a code': '输入代码',
Language: '语言',
},
linkTool: {
Link: '链接',
"Couldn't fetch the link data": '无法获取链接数据',
"Couldn't get this link data, try the other one": '无法获取链接数据,请尝试另一个',
'Wrong response format from the server': '服务器响应格式错误',
},
list: {
Ordered: '有序',
Unordered: '无序',
Checklist: '待办',
'Start with': '开始序号',
'Counter type': '序号类型',
Numeric: '数字',
'Lower Roman': '小写罗马数字',
'Upper Roman': '大写罗马数字',
'Lower Alpha': '小写字母',
'Upper Alpha': '大写字母',
},
table: {
'With headings': '带头部',
'Without headings': '不带头部',
Stretch: '拉伸',
Collapse: '还原',
'Add column to left': '在左侧添加列',
'Add column to right': '在右侧添加列',
'Delete column': '删除列',
'Add row above': '在上方添加行',
'Add row below': '在下方添加行',
'Delete row': '删除行',
},
},
blockTunes: {
delete: {
Delete: '删除',
'Click to delete': '点击删除',
},
moveUp: {
'Move up': '上移',
},
moveDown: {
'Move down': '下移',
},
},
},
},
autofocus: true,
})
const onSave = async () => {
console.log('editor>>>>>>>>>>', editor)
try {
const output = await editor.save()
console.log('保存的数据:', output)
} catch (error) {
console.error('保存失败:', error)
}
}
</script>
<template>
<div class="editor">
<div class="editble-area">
<div class="editor-container" id="editor"></div>
<div class="button" @click="onSave">保存</div>
</div>
</div>
</template>
<style scoped lang="less">
.editor {
width: 100vw;
height: 100vh;
.editble-area {
width: 100%;
height: 100%;
.button {
display: flex;
align-items: center;
justify-content: center;
width: 60px;
height: 40px;
border-radius: 12px;
background-color: #496dff;
color: #fff;
}
}
}
:deep(.cdx-button) {
width: 400px;
color: #496dff;
}
:deep(.nice-select) {
float: none;
}
:deep(.editorjs-codeFlask_LangDisplay) {
height: 40px !important;
line-height: 30px !important;
font-size: 14px !important;
color: #2c2c2c !important;
}
:deep(.ce-block__content) {
width: 80%;
max-width: 80%;
font-size: 14px;
}
:deep(.codeflask.codeflask--has-line-numbers:before) {
z-index: 0;
}
:deep(.ce-toolbar__actions) {
right: none;
left: 20%;
}
:deep(.ce-toolbar__content) {
position: absolute;
}
</style>
349

被折叠的 条评论
为什么被折叠?



