CodeMirror 6+ vue3 实现简单公式、插入标签功能
最编程
2024-05-04 08:19:16
...
我正在参加「掘金·启航计划」!
最近公司让新作一个简单的计算功能,支持标签插入,也支持手动输入,以进行计算。效果如下:
经过一番调研在手写,CodeMirror,Monaco Editor中选择了CodeMirror。因为monaco-editor不支持在编辑器中插入html元素,实现不了这种效果。而CodeMirror支持!!
ps:主要是看到了官网例子上有类似的效果,拉到页面最底部可以看到效果!!!链接戳我
大概思路是:
1.把一个标签看成一个最小单位:原子(atomic ),这样就可以实现整个添加,整个删除,而且中间不被插入其他元素,确保光标移动跳过该集合。
2.用正则匹配,替换该集合,设置装饰器,即标签,样式
3.把我们处理好的函数放入扩展函数里
MatchDecorator是一个辅助类,可用于快速设置视图插件,用于装饰视口中给定正则表达式的所有匹配项。
核心代码如下:
const placeholderMatcher = new MatchDecorator({ regexp: /\[\[(\w+)\]\]/g, decoration: (match) => Decoration.replace({ widget: new PlaceholderWidget(match[1]) }) });
扩展插件
const placeholders = ViewPlugin.fromClass( class { placeholders:DecorationSet; constructor(view: EditorView) { this.placeholders = placeholderMatcher.createDeco(view); } update(update: ViewUpdate) { this.placeholders = placeholderMatcher.updateDeco( update, this.placeholders ); } }, { decorations: (v) => v.placeholders, provide: (plugin) => EditorView.atomicRanges.of((view) => { return view.plugin(plugin)?.placeholders || Decoration.none; }) } );
标签块设置的背景样式
// 背景样式 const baseTheme = EditorView.baseTheme({ ".cm-mywidget": { paddingLeft: "6px", paddingRight: "6px", paddingTop: "3px", paddingBottom: "3px", marginLeft: "3px", marginRight: "3px", backgroundColor: "#ffcdcc", borderRadius: "4px" } });
放入我们新建的EditorView视图
new EditorView({ state: EditorState.create({ doc: doc.value, extensions: [ basicSetup, javascript(),
// 此处 [baseTheme, [], placeholders], ], }),
// 挂载的html元素 parent: coderef.value, }, })
之前使用官网的例子有两个问题:
1.仅英文支持块状标签
2.获取codeMirror.value.state.doc的内容是个字符串,没有id唯一值,后续无法处理计算逻辑
经过思考和尝试,把正则改了后解决了问题1
const placeholderMatcher = new MatchDecorator({ regexp: /\[\[(.+?)\]\]/g, ... });
改写PlaceholderWidget内部doc的构造方法,手动把id加上
class PlaceholderWidget extends WidgetType {
id: string; text: string;
constructor(text: string) {
super();
// 被替换的数据处理
if (text) {
const [id, ...texts] = text.split('.');
if (id && texts.length) {
this.text = texts.join('.');
this.id = id;
console.log(this.text,"id:",this.id)
}
}
}
eq(other: PlaceholderWidget) {
return this.text == other.text;
}
// 此处是我们的渲染方法
toDOM() {
let elt = document.createElement('span');
if (!this.text) return elt;
elt.className = "cm-mywidget";
elt.textContent = this.text;
return elt;
}
ignoreEvent() {
return true;
}
}
在插入标签的时候,把id也带上
注意:一定要手动控制光标的位置,不然会出像我这样的问题
插入标签,更新视图的逻辑如下
codeMirror.value.dispatch({ changes: { from: codeMirror.value.state.selection.main.head, to: codeMirror.value.state.selection.main.head, insert: `[[${val.id}.${val.name}]]` },
// 光标位置 selection: { anchor: codeMirror.value.state.selection.main.head + val.name.length+5+val.id.length, }, })
到这里整个功能难点就实现的差不多了,想要完整代码的可以找我!
codeMirror官网直通车