[Talk is cheap. Show me the code]
前言
实现一个带有插入标签的代码编辑器功能,如果只是简单的代码编辑器建议使用 monaco editor,本文参考掘金文章 # CodeMirror 6+ vue3 实现简单的计算公式,插入标签功能
直接上代码
直接复制即可展示效果
pnpm i @codemirror/state
pnpm i @codemirror/lang-javascript
pnpm i @codemirror/view
pnpm i codemirror
第一步
<template>
<div class="code" ref="codeMirror"></div>
<div @click="addText('名称')">测试数据</div>
<div>获取数据</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { EditorView, minimalSetup, basicSetup } from "codemirror";
import { EditorState } from "@codemirror/state";
import { javascript } from "@codemirror/lang-javascript";
import { MatchDecorator, WidgetType } from "@codemirror/view";
import { PlaceholderWidget } from "./index";
import {
Decoration,
DecorationSet,
ViewPlugin,
ViewUpdate,
} from "@codemirror/view";
const codeMirror = ref(null);
let doc = `console.log(2222)`;
const addText = () => {
let val = { name: "测试数据年就开了", id: "222" };
if (editor) {
console.log("codeMirror.value.state", editor);
editor.dispatch({
changes: {
from: editor.state.selection.main.head,
to: editor.state.selection.main.head,
insert: `[[${val.id}.${val.name}]]`,
},
// 光标位置
selection: {
anchor:
editor.state.selection.main.head +
val.name.length +
5 +
val.id.length,
},
});
}
};
let editor;
onMounted(() => {
const placeholderMatcher = new MatchDecorator({
// regexp: /\[\[(\w+)\]\]/g, // 原有逻辑
regexp: /\[\[(.+?)\]\]/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: (instance) => instance.placeholders,
provide: (plugin) =>
EditorView.atomicRanges.of((view) => {
return view.plugin(plugin)?.placeholders || Decoration.none;
}),
}
);
if (codeMirror.value) {
const baseTheme = EditorView.baseTheme({
".cm-mywidget": {
paddingLeft: "6px",
paddingRight: "6px",
paddingTop: "3px",
paddingBottom: "3px",
marginLeft: "3px",
marginRight: "3px",
backgroundColor: "#ffcdcc",
borderRadius: "4px",
},
});
editor = new EditorView({
state: EditorState.create({
doc: "Dear [[name]],\nYour [[item]] is on its way. Please see [[order]] for details.\n",
extensions: [placeholders, baseTheme, basicSetup, javascript()],
}),
parent: codeMirror.value,
});
// new EditorView({
// state: EditorState.create({
// doc: "Dear [[name]],\nYour [[item]] is on its way. Please see [[order]] for details.\n",
// extensions: [basicSetup, javascript(), [baseTheme, [], placeholders]],
// }),
// parent: codeMirror.value,
// });
// let editor = new EditorView({
// parent: codeMirror.value,
// doc: "Dear [[name]],\nYour [[item]] is on its way. Please see [[order]] for details.\n",
// extensions: [placeholders, baseTheme, basicSetup, javascript()],
// });
}
});
</script>
<style scoped>
.code {
width: 100%;
height: 300px;
}
.ͼ1 .cm-merge-b .cm-changedLine {
background: #ddfbe6;
}
.ͼ1 .cm-merge-b .cm-changedText {
background: #c6efd0;
}
.cm-merge-revert {
display: none;
}
.cm-merge-a .cm-changedText,
.cm-deletedChunk .cm-deletedText {
background: #eac3cc;
}
.cm-changeGutter {
width: 100%;
}
.cm-changeGutter {
position: absolute;
left: 0;
z-index: -1;
}
.cm-merge-b .cm-changedLineGutter {
background: #ddfbe6;
}
.cm-merge-a .cm-changedLineGutter,
.cm-deletedLineGutter {
background: #f9d7dc;
}
.cm-merge-a .cm-changedLine,
.cm-deletedChunk {
background: #fbe9eb;
}
.cm-merge-b .cm-changedLine {
background: #ecfdf0;
}
.cm-line {
padding: 2px 2px 0 6px;
padding-left: 40px;
position: relative;
font-size: 12px;
}
.cm-merge-a .cm-changedLine::before {
content: "-";
display: inline-block;
position: absolute;
left: 10px;
color: #9bb0a1;
}
.cm-merge-b .cm-changedLine::before {
content: "+";
display: inline-block;
position: absolute;
left: 10px;
color: #9bb0a1;
}
.cm-lineNumbers .cm-gutterElement {
/* padding: 2px 3px 0 5px; */
color: rgba(0, 0, 0, 0.3);
}
.cm-merge-revert {
display: none;
}
.cm-gutters {
padding-left: 30px;
}
.cm-content {
padding: 0;
}
</style>
第二步
// 此处为 import { PlaceholderWidget } from "./index"; 的index 文件
import {
WidgetType,
} from "@codemirror/view";
export class PlaceholderWidget extends WidgetType {
constructor(name) {
super();
this.name = name;
}
eq(other) {
return this.name == other.name;
}
toDOM() {
let elt = document.createElement("span");
elt.style.cssText = `
border: 1px solid blue;
border-radius: 4px;
padding: 0 3px;
background: lightblue;`;
elt.textContent = this.name;
return elt;
}
ignoreEvent() {
return false;
}
}
效果图:
重新赋值
editor.dispatch({
changes: {
from: 0,
to: editor.state.doc.length,
insert: "New Test Text",
},
});
如果有任何关于本文的意见,请在文章下方留言,我会在看到的第一时间回复。