-
- tiptap
-
tiptap
的mark
拓展bold
简单解读
-
- 自定义
mark
拓展:大小写变换
- 自定义
-
- 常见方法和参考资料
1. tiptap
项目中需要一个富文本编辑器,提出了个加粗
,斜体
,加粗
,链接
等功能,想到了tiptap
这个富文本编辑器。
tiptap 是 VueJS
里面比较强大的可以自定义扩展的富文本编辑器,源代码在这里
而tiptap
可以自定义三种拓展 nodes
, marks
, 和plugins
本章节只讲述marks
拓展,所以看看Mark
和它的基类Extension
:
/tiptap/dist/tiptap.common.js
class Mark extends Extension {
constructor(options = {}) {
super(options);
}
get type() {
return "mark";
}
get view() {
return null;
}
get schema() {
return null;
}
command() {
return () => {};
}
}
class Extension {
constructor(options = {}) {
this.options = { ...this.defaultOptions, ...options };
}
init() {
return null;
}
bindEditor(editor = null) {
this.editor = editor;
}
get name() {
return null;
}
get type() {
return "extension";
}
get defaultOptions() {
return {};
}
get plugins() {
return [];
}
inputRules() {
return [];
}
pasteRules() {
return [];
}
keys() {
return {};
}
}
2. tiptap
的mark
拓展bold
简单解读
加粗
和斜体
,tiptap
在这里给了一个加粗
的例子。
加粗
源码在/tiptap-extensions/src/marks/Bold.js
里面
import { Mark } from "tiptap";
import { toggleMark, markInputRule, markPasteRule } from "tiptap-commands";
export default class Bold extends Mark {
get name() {
return "bold";
}
get schema() {
return {
parseDOM: [
{
tag: "strong",
},
{
tag: "b",
getAttrs: (node) => node.style.fontWeight !== "normal" && null,
},
{
style: "font-weight",
getAttrs: (value) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null,
},
],
toDOM: () => ["strong", 0],
};
}
keys({ type }) {
return {
"Mod-b": toggleMark(type),
};
}
commands({ type }) {
return () => toggleMark(type);
}
inputRules({ type }) {
return [markInputRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)$/, type)];
}
pasteRules({ type }) {
return [markPasteRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)/g, type)];
}
}
翻看加粗
功能,我们发现选中文字->点击加粗
,tiptap 会给选中的文字添加strong
标签,
查看源码:
Bold extends Mark
: 基于Mark
的拓展
get name()
方法:返回定义扩展的名字bold
get schema()
方法:本质为对原始富文本bold
功能进行渲染,有两个子属性:
`parseDOM`: 初始化时解析 `content`中包含的定义的标签的内容,如这里,接卸了 `strong`,`b`,`font-weight`三个标签,也就是content里包含这三个标签的均为加粗组件需要渲染的内容。
`toDOM`: 初始化时解析上面三种元素后,设置`content`里面这三个标签带的值给`strong`。
commands
方法:外部调用后触发的操作, 常用下面几个方法:
`toggleMark`: 反向`mark`标记 反向上一步操作,如果上一步加粗了,这次就是去掉加粗。
`updateMark`: 更新`mark`标记
`removeMark`: 移除`mark`标记
3. 自定义 mark
拓展:大小写变换
mark基础定义中并没有这个组件:变换大小写,
类似word里面的功能:选中一段英文,点击功能按钮,默认变为大写,如果已经标记为大写,则变为小写,如果已经标记为小写,则变为大写。
import { Mark } from 'tiptap';
import { updateMark } from 'tiptap-commands';
export default class SmallCape extends Mark {
get name() {
return 'smallCape';
}
get schema() {
return {
attrs: { textTransform: { default: '' } },
// 渲染带有 `text-transform` 标签的 `content`
parseDOM: [
{
style: 'text-transform',
getAttrs: (value) => (value && value.length > 0 ? { textTransform: value } : ''),
},
],
// 将需要`text-transform`处理的元素添加到`span`标签并嵌入`text-transform`样式
toDOM: (mark) => [
'span',
{
style: `text-transform: ${mark.attrs.textTransform}`,
},
0,
],
};
}
commands({ type }) {
return (attrs) => {
// 获取选中文字的`mark`
let textTransform = attrs.textTransform.textTransform;
// updateMark:更新`mark`
if (!textTransform || textTransform === 'lowercase') {
return updateMark(type, { textTransform: 'uppercase' });
} else {
return updateMark(type, { textTransform: 'lowercase' });
}
};
}
}
4. 常见方法和参考资料
- 获取选中 content 的 Mark
自带的 isActive
, getMarkAttrs
方法可以拿到当前选中content
是否已经标记为xx,获取当前选中的content
的mark
参考链接: EditorMenuBar – getMarkAttrs
所以可以很简单的写出在commands
中获取mark
的方法
<editor-menu-bar :editor="editor" v-slot="{ commands, isActive, getMarkAttrs }">
<button
class="menubar__button"
:class="{ 'is-active': isActive.smallCape() }"
@click="commands.smallCape({ textTransform: getMarkAttrs('smallCape')})"
>
<icon name="smallcape" />
</button>
</editor-menu-bar>
commands({ type, schema, node }) {
return (attrs) => {
let textTransform = attrs.textTransform.textTransform;
// 默认切换为大写,反之再次点击大写转小写
if (!textTransform || textTransform === 'lowercase') {
return updateMark(type, { textTransform: 'uppercase' });
} else {
return updateMark(type, { textTransform: 'lowercase' });
}
};
}
view: editor.view
- 获取选中的文字
写大小写转回的mark
的时候,我开始的想法是获取到选中的文本内容
或者转换后的html
,然后截取第一个字母的大小写。
发现state
的doc
可以拿到content
内容(见下面代码),但是拿到的仅仅是页面未被渲染前的文本,这样有个缺陷,无法确认当前的文本时大写还是小写。
于是用了上面1的方法来获取选中的content
的mark。
参考链接1: How to get selected text? #369
参考链接2: Font Size, Font Family, Text Color #388
// vue页面
<editor-menu-bar :editor="editor" v-slot="{ commands, isActive, getMarkAttrs }">
<button
class="menubar__button"
:class="{ 'is-active': isActive.smallCape() }"
@click="commands.smallCape({ textTransform: getMarkAttrs('smallCape'),view: editor.view })"
// 2.
>
<icon name="smallcape" />
</button>
</editor-menu-bar>
// mark 插件
commands({ type }) {
// attrs 为前端调用commands 方法时传来的参数
// 2. 或者在 `vue` 页面将`editor.view`传进来,即可在`attrs`中获取到,在`editor.view`里同样有`state`信息。
return (attrs) => (state, dispatch) => {
const { selection } = state;
// const position = selection.$cursor ? selection.$cursor.pos : selection.$from.pos;
const { from, to } = selection;
// console.log(state, attrs);
const text = state.doc.textBetween(from, to, ' ');
// const html = state.doc.textContent; 获取content内容
// text 即为选中的`content`内容
};
}