块编辑器editorJS使用

1、安装

pnpm i @editorjs/editorjs --save

2、初始化

在需要引用的文件中import

import EditorJS from '@editorjs/editorjs'
​
const editor=new EditorJS({
    holder:'editor'
})

注意:holder的值对应的是块编辑器容器元素的id,可以自定义

3、插件配置

全局属性

属性名类型可用值备注
class组件自定义安装需要引入的插件
inlineToolbarboolean/string[]当为true时,展示所有的选项 数组可用选项如下 link:链接 marker:标识 bold:加粗 italic:斜体 inlineCode:行内代码行内工具选项,可单独设置在各个组件内,也可全局设置
toolboxObjecticon:图标 title:标题重写工具内部icon和title

1)标题插件

安装:pnpm i @editorjs/header

属性说明:

属性名类型可用属性备注
configObjectplaceholder:占位符 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

属性说明:

属性名类型可用属性备注
configObjectplaceholder:类型为string。占位符 levels:可用的标题等级,类型为preserveBlank:类型为boolean。保存的时候是否保存空段落

3)列表插件

安装:pnpm i @editorjs/list

属性说明

属性名类型可用属性备注
configObjectdefaultStyle:类型为string。默认列表样式,有序、无序或待办列表 maxLevel:类型为number。列表嵌套的最大级别,可以设置为1来禁用嵌套,默认为无限制。使用时按tab切换下一等级 counterTypes:类型为string[]。有序列表样式显示哪些计数器类型,可用值:numeric,upper-roman,alpha,默认为全部展示

4)warning插件

安装:pnpm i @editorjs/warning

属性说明

属性名类型可用属性备注
configObjecttitlePlaceholder:类型为string。 messagePlaceholder:类型为string。

5)alert插件

安装:pnpm i editorjs-alert

属性说明

属性名类型可用属性备注
configObjectalertTypes:类型为string[],可用值:['primary', 'secondary', 'info', 'success', 'warning', 'danger', 'light', 'dark'] defaultType:类型为string。默认为info defaultAlign:类型为string。默认为left messagePlaceholder:类型为string。

6)table插件

安装:pnpm i @editorjs/table

属性说明

属性名类型可用属性备注
configObjectrows:类型为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上传
fieldstring默认:image。POST请求中上传图片字段的名称
typesstring默认:image/*。能被接受的Mime类型
additionalRequestDataobject上传图片时携带的数据
additionalRequestHeadersobject上传图片时携带的headers
captionPlaceholderstring默认:Caption。
buttonContentstring上传图片按钮text
uploader{{uploadByFile: function, uploadByUrl: function}}可选的自定义上传方式。详情见下文
actionsarray要显示在工具设置菜单中的自定义操作。详情见下文。
featuresobject启用/禁用其他功能,如边界,背景和标题。详情见下文。

注意:如果没有使用自定义上传方式,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参数:

属性名入参返回值描述
uploadByFileFile{Promise.<{success, file: {url}}>}上传文件到服务,返回上传完成后的图片数据
uploadByUrlstring{Promise.<{success, file: {url}}>}发送url到服务,通过URL加载图片,返回上传完成后的数据

9)分隔符插件

安装:pnpm i @coolbytes/editorjs-delimiter

config属性:

属性名类型默认值描述
styleOptionsstring[]['star', 'dash', 'line']所支持的分隔符类型
defaultStylestring'star'首选分隔符样式
lineWidthOptionsnumber[][8, 15, 25, 35, 50, 60, 100]分隔符类型为"line"时,线条width值
defaultLineWidthnumber25分隔符类型为"line"时,默认线条width值
lineThicknessOptionsnumber[][1, 2, 3, 4, 5, 6]分隔符类型为"line"时,线条粗细值
defaultLineThicknessnumber2分隔符类型为"line"时,默认线条粗细值

10)Quote插件

安装:pnpm i @editorjs/quote

config属性:

属性名类型描述
quotePlaceholderstring
captionPlaceholderstring标题的占位符

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>
​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值