From 737014b90f6506beefd8c255d4701157ed341126 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Tue, 3 Jun 2025 19:14:50 +0800 Subject: [PATCH] update codemirror --- packages/codemirror/package.json | 7 ++- packages/codemirror/rollup.config.mjs | 2 +- packages/codemirror/src/editor.base.ts | 46 ++++++++++++++++--- packages/codemirror/src/editor.ts | 24 +++++++--- packages/codemirror/src/editor.utils.ts | 59 +++++++++++++++++++++++++ pnpm-lock.yaml | 32 ++++++++------ 6 files changed, 144 insertions(+), 26 deletions(-) create mode 100644 packages/codemirror/src/editor.utils.ts diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 73cd636..2c90444 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -1,6 +1,6 @@ { "name": "@kevisual/codemirror", - "version": "0.0.8", + "version": "0.0.12", "description": "", "main": "dist/editor.js", "private": false, @@ -27,6 +27,7 @@ "@codemirror/state": "^6.5.2", "@codemirror/view": "^6.36.7", "codemirror": "^6.0.1", + "eventemitter3": "^5.0.1", "prettier": "^3.5.3" }, "devDependencies": { @@ -52,6 +53,10 @@ "./json": { "import": "./dist/editor.json.js", "types": "./dist/editor.json.d.ts" + }, + "./utils": { + "import": "./dist/editor.utils.js", + "types": "./dist/editor.utils.d.ts" } } } \ No newline at end of file diff --git a/packages/codemirror/rollup.config.mjs b/packages/codemirror/rollup.config.mjs index 0cca353..5e89f6c 100644 --- a/packages/codemirror/rollup.config.mjs +++ b/packages/codemirror/rollup.config.mjs @@ -1,7 +1,7 @@ import { nodeResolve } from '@rollup/plugin-node-resolve'; import typescript from '@rollup/plugin-typescript'; -const entrys = ['editor', 'editor.json', 'editor.base']; +const entrys = ['editor', 'editor.json', 'editor.base', 'editor.utils']; const configs = entrys.map((entry) => ({ input: `./src/${entry}.ts`, // 修改输入文件为 TypeScript 文件 output: { diff --git a/packages/codemirror/src/editor.base.ts b/packages/codemirror/src/editor.base.ts index f3ff43e..e773652 100644 --- a/packages/codemirror/src/editor.base.ts +++ b/packages/codemirror/src/editor.base.ts @@ -1,10 +1,18 @@ -import { EditorView, basicSetup } from 'codemirror'; +import { basicSetup } from 'codemirror'; +import { EditorView } from '@codemirror/view'; +import { StateEffect } from '@codemirror/state'; +import { ViewUpdate } from '@codemirror/view'; import { formatKeymap } from './extensions/tab'; -let editor: EditorView = null; +import EventEmitter from 'eventemitter3'; +export type CodeEditor = EditorView & { + emitter?: EventEmitter; +}; +let editor: CodeEditor = null; export type EditorOptions = { extensions?: any[]; hasBasicSetup?: boolean; + onChange?: (content: string) => void; }; /** * 创建单例 @@ -24,12 +32,15 @@ const createEditorInstance = (el?: HTMLDivElement, opts?: EditorOptions) => { if (hasBaseicSetup) { extensions.unshift(basicSetup); } + const emitter = new EventEmitter(); + createOnChangeListener(emitter, opts.onChange).appendTo(extensions); editor = new EditorView({ extensions: extensions, parent: el || document.body, }); + editor.emitter = emitter; editor.dom.style.height = '100%'; - return editor; + return editor as CodeEditor; }; /** @@ -44,13 +55,38 @@ export const createEditor = (el: HTMLDivElement, opts?: EditorOptions) => { extensions.unshift(basicSetup); } extensions.push(formatKeymap); + const emitter = new EventEmitter(); + createOnChangeListener(emitter, opts.onChange).appendTo(extensions); const editor = new EditorView({ extensions, parent: el || document.body, - }); + }) as CodeEditor; + + editor.emitter = emitter; editor.dom.style.height = '100%'; - return editor; + + return editor as CodeEditor; }; export const getEditor = () => editor; export { editor, createEditorInstance }; + +export const createOnChangeListener = (emitter: EventEmitter, callback?: (content: string) => void) => { + const listener = EditorView.updateListener.of((update: ViewUpdate) => { + if (update.docChanged) { + const editor = update.view; + if (callback) { + callback(editor.state.doc.toString()); + } + // 触发自定义事件 + emitter.emit('change', editor.state.doc.toString()); + } + }); + // 返回监听器配置,而不是直接应用它 + return { + extension: listener, + appendTo: (ext: any[]) => { + ext.push(listener); + }, + }; +}; diff --git a/packages/codemirror/src/editor.ts b/packages/codemirror/src/editor.ts index 4f0ffeb..ed41458 100644 --- a/packages/codemirror/src/editor.ts +++ b/packages/codemirror/src/editor.ts @@ -1,11 +1,18 @@ -import { EditorView, basicSetup } from 'codemirror'; +import { basicSetup } from 'codemirror'; +import { EditorView } from '@codemirror/view'; import { javascript } from '@codemirror/lang-javascript'; import { json } from '@codemirror/lang-json'; import { html } from '@codemirror/lang-html'; import { markdown } from '@codemirror/lang-markdown'; import { css } from '@codemirror/lang-css'; import { formatKeymap } from './extensions/tab'; -let editor: EditorView = null; +import { createOnChangeListener } from './editor.base'; +import EventEmitter from 'eventemitter3'; + +export type CodeEditor = EditorView & { + emitter?: EventEmitter; +}; +let editor: CodeEditor = null; type CreateOpts = { jsx?: boolean; @@ -14,6 +21,7 @@ type CreateOpts = { hasBasicSetup?: boolean; extensions?: any[]; hasKeymap?: boolean; + onChange?: (content: string) => void; }; /** * 创建单例 @@ -54,12 +62,15 @@ const createEditorInstance = (el?: HTMLDivElement, opts?: CreateOpts) => { extensions.push(markdown()); break; } + const emitter = new EventEmitter(); + createOnChangeListener(emitter, opts.onChange).appendTo(extensions); editor = new EditorView({ extensions: extensions, parent: el || document.body, }); editor.dom.style.height = '100%'; - return editor; + editor.emitter = emitter; + return editor as CodeEditor; }; /** @@ -95,12 +106,15 @@ export const createEditor = (el: HTMLDivElement, opts?: CreateOpts) => { extensions.push(markdown()); break; } + const emitter = new EventEmitter(); + createOnChangeListener(emitter, opts.onChange).appendTo(extensions); const editor = new EditorView({ extensions: extensions, parent: el || document.body, - }); + }) as CodeEditor; editor.dom.style.height = '100%'; - return editor; + editor.emitter = emitter; + return editor as CodeEditor; }; export { editor, createEditorInstance }; diff --git a/packages/codemirror/src/editor.utils.ts b/packages/codemirror/src/editor.utils.ts new file mode 100644 index 0000000..cf1f5ba --- /dev/null +++ b/packages/codemirror/src/editor.utils.ts @@ -0,0 +1,59 @@ +import { EditorView } from '@codemirror/view'; +import { CodeEditor } from './editor.base'; +type ChainOpts = { + editor?: CodeEditor; +}; +export class Chain { + editor: CodeEditor | EditorView; + constructor(opts?: ChainOpts) { + this.editor = opts?.editor; + } + getEditor() { + return this.editor; + } + getContent() { + return this.editor?.state.doc.toString() || ''; + } + setContent(content: string) { + if (this.editor) { + this.editor.dispatch({ + changes: { from: 0, to: this.editor.state.doc.length, insert: content }, + }); + } + return this; + } + setEditor(editor: EditorView) { + this.editor = editor; + return this; + } + clearEditor() { + if (this.editor) { + this.editor.dispatch({ + changes: { from: 0, to: this.editor.state.doc.length, insert: '' }, + }); + } + return this; + } + destroy() { + if (this.editor) { + this.editor.destroy(); + this.editor = null; + } + return this; + } + static create(opts?: ChainOpts) { + return new Chain(opts); + } + setOnChange(callback: (content: string) => void) { + if (this.editor) { + const editor = this.editor as CodeEditor; + if (editor.emitter) { + editor.emitter.on('change', callback); + return () => { + editor.emitter.off('change', callback); + }; + } + } + return () => {}; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a95514..cb0057f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,7 +53,7 @@ importers: version: 5.7.12(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.29.2)(rollup@4.40.2)(terser@5.36.0)(typescript@5.8.3)(yaml@2.5.1) tailwind-merge: specifier: ^3.2.0 - version: 3.2.0 + version: 3.3.0 tailwindcss: specifier: ^4.1.6 version: 4.1.6 @@ -95,7 +95,7 @@ importers: version: 11.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) tailwind-merge: specifier: ^3.2.0 - version: 3.2.0 + version: 3.3.0 devDependencies: '@astrojs/mdx': specifier: ^4.2.6 @@ -135,7 +135,7 @@ importers: version: 1.2.9 packages/codemirror: - devDependencies: + dependencies: '@codemirror/autocomplete': specifier: ^6.18.6 version: 6.18.6 @@ -163,18 +163,22 @@ importers: '@codemirror/view': specifier: ^6.36.7 version: 6.36.7 + codemirror: + specifier: ^6.0.1 + version: 6.0.1 + eventemitter3: + specifier: ^5.0.1 + version: 5.0.1 + prettier: + specifier: ^3.5.3 + version: 3.5.3 + devDependencies: '@rollup/plugin-node-resolve': specifier: ^16.0.1 version: 16.0.1(rollup@4.40.2) '@rollup/plugin-typescript': specifier: ^12.1.2 version: 12.1.2(rollup@4.40.2)(tslib@2.8.1)(typescript@5.8.3) - codemirror: - specifier: ^6.0.1 - version: 6.0.1 - prettier: - specifier: ^3.5.3 - version: 3.5.3 rollup: specifier: ^4.40.2 version: 4.40.2 @@ -271,8 +275,8 @@ importers: specifier: ^16.1.0 version: 16.1.0 tailwind-merge: - specifier: ^3.2.0 - version: 3.2.0 + specifier: ^3.3.0 + version: 3.3.0 tailwindcss: specifier: ^4.1.6 version: 4.1.6 @@ -4144,8 +4148,8 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - tailwind-merge@3.2.0: - resolution: {integrity: sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==} + tailwind-merge@3.3.0: + resolution: {integrity: sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==} tailwindcss-animate@1.0.7: resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} @@ -9141,7 +9145,7 @@ snapshots: csso: 5.0.5 picocolors: 1.1.1 - tailwind-merge@3.2.0: {} + tailwind-merge@3.3.0: {} tailwindcss-animate@1.0.7(tailwindcss@4.1.6): dependencies: