feat: Initialize simple-lit-vite project with Lit and Vite setup
- Add README.md for project description and CLI usage - Create main application component with basic structure in app.tsx - Implement CodeMirror editor base functionality in editor.base.ts - Extend CodeMirror editor for JSON support in editor.json.ts - Add support for multiple languages in editor.ts - Create utility functions for editor manipulation in editor.utils.ts - Implement tab key formatting and indentation in tab.ts - Add Tailwind CSS integration in index.css - Develop JSON editor web component in json.ts - Create a template component for rendering in lib.ts - Set up main entry point in main.ts - Configure TypeScript settings in tsconfig.json - Define custom element typings in typings.d.ts - Configure Vite for library and application builds in vite.config.lib.ts and vite.config.ts
This commit is contained in:
92
src/codemirror/editor.base.ts
Normal file
92
src/codemirror/editor.base.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { basicSetup } from 'codemirror';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { StateEffect } from '@codemirror/state';
|
||||
import { ViewUpdate } from '@codemirror/view';
|
||||
import { formatKeymap } from './extensions/tab.ts';
|
||||
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;
|
||||
};
|
||||
/**
|
||||
* 创建单例
|
||||
* @param el
|
||||
* @returns
|
||||
*/
|
||||
const createEditorInstance = (el?: HTMLDivElement, opts?: EditorOptions) => {
|
||||
if (editor && el) {
|
||||
el.appendChild(editor.dom);
|
||||
return editor;
|
||||
} else if (editor) {
|
||||
return editor;
|
||||
}
|
||||
const extensions = opts?.extensions || [];
|
||||
extensions.push(formatKeymap);
|
||||
const hasBaseicSetup = opts?.hasBasicSetup ?? true;
|
||||
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 as CodeEditor;
|
||||
};
|
||||
|
||||
/**
|
||||
* 每次都创建新的实例
|
||||
* @param el
|
||||
* @returns
|
||||
*/
|
||||
export const createEditor = (el: HTMLDivElement, opts?: EditorOptions) => {
|
||||
const extensions = opts?.extensions || [];
|
||||
const hasBaseicSetup = opts?.hasBasicSetup ?? true;
|
||||
if (hasBaseicSetup) {
|
||||
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 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);
|
||||
},
|
||||
};
|
||||
};
|
||||
33
src/codemirror/editor.json.ts
Normal file
33
src/codemirror/editor.json.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { json } from '@codemirror/lang-json';
|
||||
import * as Base from './editor.base.ts';
|
||||
|
||||
/**
|
||||
* 创建单例
|
||||
* @param el
|
||||
* @returns
|
||||
*/
|
||||
export const createEditorInstance = (el?: HTMLDivElement, opts?: Base.EditorOptions) => {
|
||||
return Base.createEditorInstance(el, {
|
||||
...opts,
|
||||
extensions: [...(opts?.extensions || []), json()]
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 每次都创建新的实例
|
||||
* @param el
|
||||
* @returns
|
||||
*/
|
||||
export const createEditor = (el: HTMLDivElement, opts?: Base.EditorOptions) => {
|
||||
return Base.createEditor(el, {
|
||||
...opts,
|
||||
extensions: [...(opts?.extensions || []), json()]
|
||||
});
|
||||
};
|
||||
|
||||
export { Base };
|
||||
|
||||
export type CodeEditor = Base.CodeEditor;
|
||||
export type EditorOptions = Base.EditorOptions;
|
||||
|
||||
export const editor = Base.editor;
|
||||
120
src/codemirror/editor.ts
Normal file
120
src/codemirror/editor.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
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.ts';
|
||||
import { createOnChangeListener } from './editor.base.ts';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
export type CodeEditor = EditorView & {
|
||||
emitter?: EventEmitter;
|
||||
};
|
||||
let editor: CodeEditor = null;
|
||||
|
||||
type CreateOpts = {
|
||||
jsx?: boolean;
|
||||
typescript?: boolean;
|
||||
type?: 'javascript' | 'json' | 'html' | 'markdown' | 'css';
|
||||
hasBasicSetup?: boolean;
|
||||
extensions?: any[];
|
||||
hasKeymap?: boolean;
|
||||
onChange?: (content: string) => void;
|
||||
};
|
||||
/**
|
||||
* 创建单例
|
||||
* @param el
|
||||
* @returns
|
||||
*/
|
||||
const createEditorInstance = (el?: HTMLDivElement, opts?: CreateOpts) => {
|
||||
if (editor && el) {
|
||||
el.appendChild(editor.dom);
|
||||
return editor;
|
||||
} else if (editor) {
|
||||
return editor;
|
||||
}
|
||||
const { type = 'javascript' } = opts || {};
|
||||
const extensions = opts?.extensions || [];
|
||||
const hasBaseicSetup = opts?.hasBasicSetup ?? true;
|
||||
const hasKeymap = opts?.hasKeymap ?? true;
|
||||
if (hasBaseicSetup) {
|
||||
extensions.unshift(basicSetup);
|
||||
}
|
||||
if (hasKeymap) {
|
||||
extensions.push(formatKeymap);
|
||||
}
|
||||
switch (type) {
|
||||
case 'json':
|
||||
extensions.push(json());
|
||||
break;
|
||||
case 'javascript':
|
||||
extensions.push(javascript({ jsx: opts?.jsx, typescript: opts?.typescript }));
|
||||
break;
|
||||
case 'css':
|
||||
extensions.push(css());
|
||||
break;
|
||||
case 'html':
|
||||
extensions.push(html());
|
||||
break;
|
||||
case 'markdown':
|
||||
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%';
|
||||
editor.emitter = emitter;
|
||||
return editor as CodeEditor;
|
||||
};
|
||||
|
||||
/**
|
||||
* 每次都创建新的实例
|
||||
* @param el
|
||||
* @returns
|
||||
*/
|
||||
export const createEditor = (el: HTMLDivElement, opts?: CreateOpts) => {
|
||||
const { type = 'javascript' } = opts || {};
|
||||
const extensions = opts?.extensions || [];
|
||||
const hasBaseicSetup = opts?.hasBasicSetup ?? true;
|
||||
const hasKeymap = opts?.hasKeymap ?? true;
|
||||
if (hasBaseicSetup) {
|
||||
extensions.unshift(basicSetup);
|
||||
}
|
||||
if (hasKeymap) {
|
||||
extensions.push(formatKeymap);
|
||||
}
|
||||
switch (type) {
|
||||
case 'json':
|
||||
extensions.push(json());
|
||||
break;
|
||||
case 'javascript':
|
||||
extensions.push(javascript({ jsx: opts?.jsx, typescript: opts?.typescript }));
|
||||
break;
|
||||
case 'css':
|
||||
extensions.push(css());
|
||||
break;
|
||||
case 'html':
|
||||
extensions.push(html());
|
||||
break;
|
||||
case 'markdown':
|
||||
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%';
|
||||
editor.emitter = emitter;
|
||||
return editor as CodeEditor;
|
||||
};
|
||||
|
||||
export { editor, createEditorInstance };
|
||||
59
src/codemirror/editor.utils.ts
Normal file
59
src/codemirror/editor.utils.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { CodeEditor } from './editor.base.ts';
|
||||
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 () => {};
|
||||
}
|
||||
}
|
||||
56
src/codemirror/extensions/tab.ts
Normal file
56
src/codemirror/extensions/tab.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { EditorView, keymap } from '@codemirror/view';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { defaultKeymap, indentWithTab, insertTab } from '@codemirror/commands';
|
||||
import prettier from 'prettier';
|
||||
// import parserBabel from 'prettier/plugins/babel';
|
||||
import parserEstree from 'prettier/plugins/estree';
|
||||
// import parserHtml from 'prettier/plugins/html';
|
||||
import parserTypescript from 'prettier/plugins/typescript';
|
||||
|
||||
// 格式化函数
|
||||
// Function to format the code using Prettier
|
||||
type FormatCodeOptions = {
|
||||
type: 'typescript';
|
||||
plugins?: any[];
|
||||
};
|
||||
async function formatCode(view: EditorView, opts?: FormatCodeOptions) {
|
||||
const editor = view;
|
||||
const code = editor.state.doc.toString();
|
||||
const plugins = opts?.plugins || [];
|
||||
plugins.push(parserEstree);
|
||||
const parser = opts?.type || 'typescript';
|
||||
if (parser === 'typescript') {
|
||||
plugins.push(parserTypescript);
|
||||
}
|
||||
try {
|
||||
const formattedCode = await prettier.format(code, {
|
||||
parser: parser,
|
||||
plugins: plugins,
|
||||
});
|
||||
|
||||
editor.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: editor.state.doc.length,
|
||||
insert: formattedCode.trim(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error formatting code:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export const formatKeymap = keymap.of([
|
||||
{
|
||||
// bug, 必须小写
|
||||
key: 'alt-shift-f', // 快捷键绑定
|
||||
// mac: 'cmd-shift-f',
|
||||
run: (view) => {
|
||||
formatCode(view);
|
||||
return true; // 表示按键事件被处理
|
||||
},
|
||||
},
|
||||
// indentWithTab, // Tab键自动缩进
|
||||
{ key: 'Tab', run: insertTab }, // 在光标位置插入Tab字符
|
||||
...defaultKeymap, // 默认快捷键
|
||||
]);
|
||||
Reference in New Issue
Block a user