-
Notifications
You must be signed in to change notification settings - Fork 422
调整工具栏
cherry有五种工具栏位置,如下:
同时,cherry还默认提供了30+工具栏按钮(都定义在/src/toolbars/hooks/
目录下),并且还支持自定义工具栏按钮。
最简单的配置如下:
new Cherry({
id: 'markdown-container',
value: '## hello world',
fileUpload: myFileUpload,
toolbars: {
// 定义顶部工具栏
toolbar: ['bold','italic','strikethrough','|','color','header','ruby','|','list','panel','detail'],
// 定义侧边栏,默认为空
sidebar: [],
// 定义顶部右侧工具栏,默认为空
toolbarRight: [],
// 定义选中文字时弹出的“悬浮工具栏”,默认为 ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'quote', '|', 'size', 'color']
bubble: false,
// 定义光标出现在行首位置时出现的“提示工具栏”,默认为 ['h1', 'h2', 'h3', '|', 'checklist', 'quote', 'table', 'code']
float: false,
},
});
配置的效果如下图:
cherry自带的工具栏按钮有以下这些:
- 辅助类
- |: 分隔符,单纯的分割工具栏,无任何作用
- insert: 插入,单纯的占位,点击没有任何效果,用来配置二级菜单
- 字体样式类
- bold: 加粗
- italic: 斜体
- underline: 下划线
- strikethrough: 删除线
- sub: 下标
- sup: 上标
- ruby: 实现类似给文字加拼音的效果(当然如何把文字转成拼音需要业务方自行实现,也可参考在线demo用的文字转拼音组件)
- size: 文字尺寸,自带二级菜单,二级菜单里可选 小、中、大、特大
- color: 文字颜色,自带二级菜单,二级菜单里可选 文字颜色、文字背景色
- 段落属性类
- quote: 引用
- detail: 手风琴,即可以展开收起内容
- h1: 一级标题
- h2: 二级标题
- h3: 三级标题
- header: 标题菜单,自带二级菜单,二级菜单里可以选 1~5级标题
- ul: 无序列表
- ol: 有序列表
- checklist: 任务清单
- list: 列表菜单,自带二级菜单,二级菜单里可选 有序列表、无序列表、任务清单
- justify: 对齐方式,自带二级菜单,二级菜单里可以选 左对齐、居中、右对齐
- panel: 信息面板,自带二级菜单,二级菜单里可以选 tips、info、warning、danger、success
- 插入类
- image: 插入图片
- audio: 插入音频
- video: 插入视频
- pdf: 插入pdf
- word: 插入word文档
- file: 插入普通文件
- link: 插入链接
- hr: 插入水平分割线
- br: 插入新行
- code: 代码块
- formula: 插入数学公式
- toc: 插入目录
- table: 插入表格
- drawIo: 插入draw.io画图,点击后会出现draw.io画图面板
- graph: 插入画图,自带二级菜单,二级菜单里可选 流程图、时序图、状态图、类图、饼图、甘特图
- 功能类
- undo: 回撤操作
- redo: 恢复最近回撤的操作
- theme: 切换主题,自带二级菜单,主题可配置也可由业务方自行丰富
- codeTheme: 切换代码块的主题,自带二级菜单
- mobilePreview: 把预览区域变成h5模式
- togglePreview: 打开/关闭预览区(用于左右分栏模式,即左边是编辑区域,右边是预览区域)
- switchModel: 切换编辑/预览模式(用于单栏编辑模式,即点一下是编辑模式,再点一下是预览模式,类似github的交互体验)
- copy: 复制预览区域的html内容到剪贴板
- export: 导出,自带二级菜单,二级菜单里可选 导出PDF、导出长图、导出markdown、导出html
- fullScreen: 全屏/取消全屏
- settings: 设置,自带二级菜单,二级菜单里可选 常规换行/经典换行切换、关闭/打开预览、隐藏工具栏 (不推荐用了,完全可以自行实现)
我们可以配置将多个工具栏按钮变成某个工具栏的子按钮,从而达到节约空间、按钮分类等目的。
同时我们也可以把字段样式类按钮放进“悬浮工具栏”中,把段落类和插入类按钮放进“提示工具栏”中,把功能类按钮放进侧边栏或顶部右侧工具栏中
但需要注意,自带二级菜单的按钮无法被放进其他按钮里
代码如下:
new Cherry({
id: 'markdown-container',
value: '## hello world',
fileUpload: myFileUpload,
toolbars: {
// 定义顶部工具栏
toolbar: [
'undo', 'redo', '|',
// 把字体样式类按钮都放在加粗按钮下面
{bold:['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'ruby']},
'color', 'size', '|', 'header', 'list', 'panel', '|',
// 把插入类按钮都放在插入按钮下面
{insert: ['image', 'audio', 'video', 'link', 'hr', 'br', 'code', 'formula', 'toc', 'table', 'drawIo']},
'graph'
],
// 定义侧边栏,默认为空
sidebar: ['theme', 'mobilePreview', 'copy'],
// 定义顶部右侧工具栏,默认为空
toolbarRight: ['fullScreen', 'export'],
// 定义选中文字时弹出的“悬浮工具栏”,默认为 ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'quote', '|', 'size', 'color']
bubble: ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'ruby', '|', 'color','size',],
// 定义光标出现在行首位置时出现的“提示工具栏”,默认为 ['h1', 'h2', 'h3', '|', 'checklist', 'quote', 'table', 'code']
float: ['table', 'code', 'graph'],
},
});
配置的效果如下图:
自定义工具栏按钮三步搞定:
- 创建按钮对象
var customMenu = Cherry.createMenuHook('自定义', {
iconName: '', // 声明按钮的图标,空表示不显示图标直接显示文字
});
- 在cherry中声明该对象
toolbars: {
...
customMenu: {
myMenu: customMenu,
},
}
- 将其配置到cherry的工具栏中
toolbars: {
// 定义顶部工具栏
toolbar: ['bold', 'myMenu'],
...
}
接下来重点介绍第一步,如何创建按钮对象。
我们可通过cherry.createMenuHook
创建三类按钮对象
第一类普通按钮,可处理点击事件,点击时做相应的操作(比如往编辑区插入内容,或者修改预览区内容等),代码如下:
var customMenuA = Cherry.createMenuHook('加粗斜体', {
iconName: 'font', // 声明使用的图标,可选的图标可以在 https://github.com/Tencent/cherry-markdown/tree/main/src/sass/icons 里查看
onClick: function(selection) {
return `***${selection}***`;
}
});
第二类空壳按钮,点击没有任何效果,最主要作用是用来存放子按钮,代码如下:
var customMenuB = Cherry.createMenuHook('实验室', {
iconName: '',
});
第三类自带二级菜单的按钮,点击后出现二级菜单,点击二级菜单执行相关操作,代码如下:
var customMenuC = Cherry.createMenuHook('帮助中心', {
iconName: 'question',
subMenuConfig: [
{ noIcon: true, name: '快捷键', onclick: (event)=>{return cherry.insert('快捷键看这里:https://codemirror.net/5/demo/sublime.html');} },
{ noIcon: true, name: '联系我们', onclick: (event)=>{return cherry.insert('我们在这里:https://github.com/Tencent/cherry-markdown');} },
{ noIcon: true, name: '更新日志', onclick: (event)=>{return cherry.insert('我们在这里:https://github.com/Tencent/cherry-markdown/releases');} },
]
});
在了解三类按钮后,我们将重点介绍下createMenuHook都提供了哪些工具方法
- 获取选中区域的内容
/**
* 获取用户选中的文本内容,如果没有选中文本,则返回光标所在的位置的内容
* @param {string} selection 当前选中的文本内容
* @param {string} type 'line': 当没有选择文本时,获取光标所在行的内容; 'word': 当没有选择文本时,获取光标所在单词的内容
* @param {boolean} focus true;强行选中光标处的内容,否则只获取选中的内容
* @returns {string}
*/
getSelection(selection, type = 'word', focus = false)
- 让光标选择更多的区域
/**
* 基于当前已选择区域,获取更多的选择区
* @param {string} [appendBefore] 选择区前面追加的内容
* @param {string} [appendAfter] 选择区后面追加的内容
* @param {function} [cb] 扩大选区后的回调函数,如果cb返回false,则恢复原来的选取
*/
getMoreSelection(appendBefore, appendAfter, cb)
- 让光标选择更少的区域
/**
* 选中除了前后语法后的内容
* @param {String} lessBefore
* @param {String} lessAfter
*/
setLessSelection(lessBefore, lessAfter)
- 注册点击事件渲染后的回调函数
/**
* 注册点击事件渲染后的回调函数
* @param {function} cb
*/
registerAfterClickCb(cb)
- 其他属性
-
isSelections
true: 有多个选区/光标;false:只有一个 -
$cherry
实例化的cherry对象 -
editor
编辑区对象,也可通过this.$cherry.editor
获得 -
editor.editor
编辑区的codemirror对象,可调用codemirror的api -
updateMarkdown
true:本按钮点击后会变更编辑区内容 false:本按钮点击后不会更新编辑区内容(如复制、导出等按钮) -
noIcon
true: 没有图标,直接显示按钮名 false:尝试显示按钮图标
问题来了,这些方法有什么用?
不急,我们先回看下第一类普通按钮的例子:
var customMenuA = Cherry.createMenuHook('加粗斜体', {
iconName: 'font', // 声明使用的图标,可选的图标可以在 https://github.com/Tencent/cherry-markdown/tree/main/src/sass/icons 里查看
onClick: function(selection) {
return `***${selection}***`;
}
});
这个例子的实现有三个体验问题:
- 永远在追加
***
,用户每点击一次就会追加一层***
,这是非常不舒服的 - 点击按钮后,光标会选中包含
***
的内容,这导致用户需要再手动调整光标选区才能修改内容 - 如果用户的内容是"hello world",用户如果想加粗"world"这个单词就一定要先选中这个单词才能操作,多少有点麻烦
为解决以上体验问题,我们将上面的例子做了修改,修改后的完整例子如下:
/**
* 自定义一个自定义菜单按钮
* 点第一次时,把选中的文字变成同时加粗和斜体
* 保持光标选区不变,点第二次时,把加粗斜体的文字变成普通文本
*/
var customMenuA = Cherry.createMenuHook('加粗斜体', {
iconName: 'font',
onClick: function(selection) {
// 获取用户选中的文字,调用getSelection方法后,如果用户没有选中任何文字,会尝试获取光标所在位置的单词或句子
let $selection = this.getSelection(selection) || '同时加粗斜体';
// 如果是单选,并且选中内容的开始结束内没有加粗斜体语法,则尝试扩大选中范围
if (!this.isSelections && !/^\s*(\*\*\*)[\s\S]+(\1)/.test($selection)) {
this.getMoreSelection('***', '***', () => {
// 调用codemirror的api获取当前选中的文本
const newSelection = this.editor.editor.getSelection();
// 判断是否已经是加粗斜体语法了
const isBoldItalic = /^\s*(\*\*\*)[\s\S]+(\1)/.test(newSelection);
if (isBoldItalic) {
$selection = newSelection;
}
return isBoldItalic;
});
}
// 如果选中的文本中已经有加粗语法了,则去掉加粗语法
if (/^\s*(\*\*\*)[\s\S]+(\1)/.test($selection)) {
// return $selection.replace(/(^)(\s*)(\*\*\*)([^\n]+)(\3)(\s*)($)/gm, '$1$4$7'); // 这个正则主要是为了支持多行,可以忽略
return $selection.replace(/^\*\*\*/, '').replace(/\*\*\*$/, '');
}
/**
* 注册缩小选区的规则
* 注册后,插入“***TEXT***”,选中状态会变成“***【TEXT】***”
* 如果不注册,插入后选中效果为:“【***TEXT***】”
*/
this.registerAfterClickCb(() => {
this.setLessSelection('***', '***');
});
// 增加加粗斜体语法
// return $selection.replace(/(^)([^\n]+)($)/gm, '$1***$2***$3'); // 这个正则主要是为了支持多行,可以忽略
return `***${$selection}***`;
}
});
/**
* 自定义一个空壳自定义按钮
*/
var customMenuB = Cherry.createMenuHook('实验室', {
iconName: '',
});
/**
* 自定义一个带二级菜单的自定义按钮
*/
var customMenuC = Cherry.createMenuHook('帮助中心', {
iconName: 'question',
subMenuConfig: [
{ noIcon: true, name: '快捷键', onclick: (event)=>{return cherry.insert('快捷键看这里:https://codemirror.net/5/demo/sublime.html');} },
{ noIcon: true, name: '联系我们', onclick: (event)=>{return cherry.insert('我们在这里:https://github.com/Tencent/cherry-markdown');} },
{ noIcon: true, name: '更新日志', onclick: (event)=>{return cherry.insert('我们在这里:https://github.com/Tencent/cherry-markdown/releases');} },
]
});
new Cherry({
id: 'markdown-container',
value: '## hello world',
fileUpload: myFileUpload,
toolbars: {
// 定义顶部工具栏
toolbar: [
'undo', 'redo', '|',
// 把字体样式类按钮都放在加粗按钮下面
{bold:['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'ruby', 'bold&italic']},
'color', 'size', '|', 'header', 'list', 'panel', '|',
// 把插入类按钮都放在插入按钮下面
{insert: ['image', 'audio', 'video', 'link', 'hr', 'br', 'code', 'formula', 'toc', 'table', 'drawIo']},
'graph', '|',
// 把自定义按钮放进来
{lab: ['drawIo', 'ruby', 'bold&italic']}, 'help'
],
// 定义侧边栏,默认为空
sidebar: ['theme', 'mobilePreview', 'copy'],
// 定义顶部右侧工具栏,默认为空
toolbarRight: ['fullScreen', 'export'],
// 定义选中文字时弹出的“悬浮工具栏”,默认为 ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'quote', '|', 'size', 'color']
bubble: ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'ruby', '|', 'color','size',],
// 定义光标出现在行首位置时出现的“提示工具栏”,默认为 ['h1', 'h2', 'h3', '|', 'checklist', 'quote', 'table', 'code']
float: ['table', 'code', 'graph'],
// 声明自定义按钮
customMenu: {
'bold&italic': customMenuA,
lab: customMenuB,
help: customMenuC,
},
},
});
最终效果如下图: (忽略图片中的加拼音功能,该功能需要额外引入第三方的组件才能实现)
现在支持三种方式自定义icon图标,以及支持API的形式动态更新图标(一次性的)
此方式需要准备好字体图标文件,在 src/sass/ch-icon.scss
文件中引用,参照 .ch-icon-*:before { content: "*" }
(*
代表具体的icon内容) 的方式配置即可
配置项的类型定义在 types/menus.d.ts
中,具体来说形如👇
其中 name
就是原来的toolbar名称,不能自定义,除非使用自定义菜单
type
支持 svg
和 img
content
当 type
为 svg
时,填svg文件内容,反之填图片的 url或base64
剩下的就是样式配置,全凭个人喜好了
{
name: 'italic',
icon: 'bold',
}
这样就可以让下划线使用加粗的图标了,内部图标在 issue #589 里说明了
另外,每个按钮现在支持通过API的方式更新icon了,如下
cherry.toolbar.menus.hooks['header'].updateMenuIcon('bold')
updateMenuIcon
的参数与配置项一致,可参照 自定义 svg / img 图标 部分配置
/**
* 定义一个空壳,用于自行规划cherry已有工具栏的层级结构
*/
var customMenu = Cherry.createMenuHook('实验室', {
icon: {
type: 'svg',
content: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" y1="9" x2="9.01" y2="9" /><line x1="15" y1="9" x2="15.01" y2="9" /></svg>',
iconStyle: 'width: 15px; height: 15px; vertical-align: middle;',
},
});
...
...
new Cherry({toolbars: {
toolbar: [
customMenuName: ['ruby', 'audio', 'video'],
],
customMenu: {
customMenuName: customMenu,
},
}});
效果如下: