欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

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官网直通车