Bug Fixes:
- fix(core): exec() now supports _customActions registered via addToolbarButton
- fix(core): _emit('focus'/'blur') no longer passes editor argument twice
- fix(core): getHTML() now triggers beforeRender/afterRender hooks
- fix(core): _wrapSelection defaults to 'text' instead of i18n placeholder
- fix(core): addToolbarButton onClick+action no longer double-fires
- fix(core): null-safety on focus/blur/enable/disable after destroy
- fix(parser): empty headings (# ) no longer produce empty <h1> tags
- fix(parser): slugify returns 'heading' fallback for empty/special inputs
- fix(i18n): setCurrentLocale no longer mutates input parameter
- fix(core): autofocus skipped when readOnly is enabled
Performance:
- perf(core): cache last rendered value, skip parsing when content unchanged
- perf(core): add refresh() API to force re-render after theme/locale changes
Hardening:
- feat(core): tabSize dynamically applied to textarea via style.tabSize
- feat(core): getStatus() now includes readOnly field
Tests:
- 7 new test cases: customActions, refresh, focus args, destroy safety,
getStatus readOnly, autofocus+readOnly, slugify edge cases
- Total: 425 tests passing (+7 from v0.1.2)
Types:
- EditorStatus includes readOnly field
- MarkdownEditor.refresh() added to type definitions
377 lines
11 KiB
TypeScript
377 lines
11 KiB
TypeScript
// Type definitions for MetonaEditor
|
|
// Project: https://git.metona.cn/MetonaTeam/MetonaEditor
|
|
// Author: thzxx
|
|
|
|
declare const VERSION: string;
|
|
|
|
/** 视图模式 */
|
|
export type EditMode = 'edit' | 'split' | 'preview';
|
|
|
|
/** 主题名 */
|
|
export type ThemeName = 'light' | 'dark' | 'auto' | 'warm' | string;
|
|
|
|
/** 渲染环境,传给自定义 render 函数 */
|
|
export interface RenderEnv {
|
|
highlight?: (code: string, lang: string) => string;
|
|
locale?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
/** 工具栏项:动作名或 '|'(分隔符) */
|
|
export type ToolbarItem = string | '|';
|
|
|
|
/** 编辑器配置 */
|
|
export interface MarkdownEditorOptions {
|
|
/** 初始内容 */
|
|
value?: string;
|
|
/** 占位符 */
|
|
placeholder?: string;
|
|
/** 视图模式,默认 'split' */
|
|
mode?: EditMode;
|
|
/** 高度:数字为 px,字符串原样使用,默认 400 */
|
|
height?: number | string;
|
|
/** 工具栏按钮序列,传 false 隐藏工具栏 */
|
|
toolbar?: ToolbarItem[] | false;
|
|
/** 是否显示字数统计状态栏,默认 true */
|
|
wordCount?: boolean;
|
|
/** 自动聚焦,默认 false */
|
|
autofocus?: boolean;
|
|
/** 拼写检查,默认 false */
|
|
spellcheck?: boolean;
|
|
/** 历史栈上限,默认 100 */
|
|
historyLimit?: number;
|
|
/** 分屏模式下同步滚动,默认 true */
|
|
syncScroll?: boolean;
|
|
/** Tab 插入的空格数,0 表示插入 \t,默认 2 */
|
|
tabSize?: number;
|
|
/** 只读模式,默认 false */
|
|
readOnly?: boolean;
|
|
/** 历史栈防抖延迟(ms),默认 400 */
|
|
historyDebounce?: number;
|
|
/** 主题,默认 'auto' */
|
|
theme?: ThemeName;
|
|
/** 国际化语言,默认 'zh-CN' */
|
|
locale?: string;
|
|
/** 自定义 Markdown 渲染函数,覆盖内置解析器 */
|
|
render?: (markdown: string, env: RenderEnv) => string;
|
|
/** 自定义代码高亮函数 */
|
|
highlight?: (code: string, lang: string) => string;
|
|
/** 自定义 HTML 净化函数(接收渲染后 HTML,返回安全 HTML) */
|
|
sanitize?: (html: string) => string;
|
|
/** 自定义容器 className */
|
|
className?: string;
|
|
/** 容器内联样式对象 */
|
|
style?: { [key: string]: string };
|
|
/** 实例级插件列表 */
|
|
plugins?: Array<string | PluginObject>;
|
|
/** 唯一 id */
|
|
id?: string;
|
|
|
|
// 生命周期回调
|
|
onCreate?: (editor: MarkdownEditor) => void;
|
|
onDestroy?: (editor: MarkdownEditor) => void;
|
|
onInput?: (value: string, editor: MarkdownEditor) => void;
|
|
onChange?: (value: string, editor: MarkdownEditor) => void;
|
|
onFocus?: (editor: MarkdownEditor) => void;
|
|
onBlur?: (editor: MarkdownEditor) => void;
|
|
onSave?: (value: string, editor: MarkdownEditor) => void;
|
|
onModeChange?: (mode: EditMode, editor: MarkdownEditor) => void;
|
|
onFullscreen?: (fullscreen: boolean, editor: MarkdownEditor) => void;
|
|
}
|
|
|
|
/** 插件对象 */
|
|
export interface PluginObject {
|
|
name: string;
|
|
description?: string;
|
|
[key: string]: any;
|
|
install?: (editor: MarkdownEditor) => void;
|
|
destroy?: (editor: MarkdownEditor) => void;
|
|
}
|
|
|
|
/** 工具栏按钮配置(addToolbarButton 用) */
|
|
export interface ToolbarButtonConfig {
|
|
action: string;
|
|
title?: string;
|
|
text?: string;
|
|
icon?: string;
|
|
onClick?: (editor: MarkdownEditor) => void;
|
|
handler?: (...args: any[]) => void;
|
|
}
|
|
|
|
/** 统计信息 */
|
|
export interface EditorStats {
|
|
characters: number;
|
|
words: number;
|
|
chineseChars: number;
|
|
englishWords: number;
|
|
lines: number;
|
|
readingTime: number;
|
|
}
|
|
|
|
/** 编辑器状态 */
|
|
export interface EditorStatus {
|
|
id: string;
|
|
mode: EditMode;
|
|
theme: ThemeName;
|
|
locale: string;
|
|
fullscreen: boolean;
|
|
readOnly: boolean;
|
|
disabled: boolean;
|
|
destroyed: boolean;
|
|
plugins: string[];
|
|
}
|
|
|
|
/** 事件名 */
|
|
export type EditorEventName =
|
|
| 'input' | 'change' | 'focus' | 'blur' | 'save'
|
|
| 'modeChange' | 'fullscreen' | 'autosave' | 'destroy'
|
|
| 'beforeRender' | 'afterRender';
|
|
|
|
/** 全局钩子名 */
|
|
export type HookName =
|
|
| 'beforeCreate' | 'afterCreate'
|
|
| 'beforeRender' | 'afterRender'
|
|
| 'beforeDestroy' | 'afterDestroy';
|
|
|
|
/** MarkdownEditor 编辑器类 */
|
|
export class MarkdownEditor {
|
|
constructor(container: string | HTMLElement, options?: MarkdownEditorOptions);
|
|
|
|
/** 实例 id */
|
|
readonly id: string;
|
|
/** 容器元素 */
|
|
readonly container: HTMLElement;
|
|
/** 配置(只读视图) */
|
|
readonly config: MarkdownEditorOptions;
|
|
/** 根元素 */
|
|
readonly el: HTMLElement;
|
|
readonly toolbarEl: HTMLElement;
|
|
readonly bodyEl: HTMLElement;
|
|
readonly editorPane: HTMLElement;
|
|
readonly previewPane: HTMLElement;
|
|
readonly dividerEl: HTMLElement;
|
|
readonly textarea: HTMLTextAreaElement;
|
|
readonly previewEl: HTMLElement;
|
|
readonly statusEl: HTMLElement | null;
|
|
|
|
// 静态钩子
|
|
static on(name: HookName, fn: (editor: MarkdownEditor) => void): () => void;
|
|
static off(name: HookName, fn: (editor: MarkdownEditor) => void): void;
|
|
static trigger(name: HookName, editor: MarkdownEditor): void;
|
|
|
|
// 内容
|
|
getValue(): string;
|
|
setValue(markdown: string, opts?: { silent?: boolean }): this;
|
|
getHTML(): string;
|
|
/** 强制刷新预览(内容未变时手动触发重渲染) */
|
|
refresh(): this;
|
|
insert(text: string, opts?: { replace?: boolean }): this;
|
|
wrap(before: string, after?: string): this;
|
|
|
|
// 选区/焦点
|
|
focus(): this;
|
|
blur(): this;
|
|
enable(): this;
|
|
disable(): this;
|
|
isDisabled(): boolean;
|
|
setReadOnly(readOnly: boolean): this;
|
|
isReadOnly(): boolean;
|
|
|
|
// 事件
|
|
on(name: EditorEventName, fn: (...args: any[]) => void): () => void;
|
|
off(name: EditorEventName, fn: (...args: any[]) => void): this;
|
|
|
|
// 命令
|
|
exec(action: string, ...args: any[]): this;
|
|
|
|
// 历史栈
|
|
undo(): this;
|
|
redo(): this;
|
|
canUndo(): boolean;
|
|
canRedo(): boolean;
|
|
|
|
// 模式与全屏
|
|
setMode(mode: EditMode): this;
|
|
getMode(): EditMode;
|
|
toggleFullscreen(): this;
|
|
isFullscreen(): boolean;
|
|
exitFullscreen(): this;
|
|
|
|
// 统计与状态
|
|
getStats(): EditorStats;
|
|
getStatus(): EditorStatus;
|
|
|
|
// 插件
|
|
use(plugin: string | PluginObject, options?: object): this;
|
|
getPlugins(): PluginObject[];
|
|
addToolbarButton(config: ToolbarButtonConfig): this;
|
|
|
|
// 生命周期
|
|
destroy(): void;
|
|
isDestroyed(): boolean;
|
|
}
|
|
|
|
/** MarkdownEditor 别名 */
|
|
export const Editor: typeof MarkdownEditor;
|
|
|
|
/** 工厂函数:创建编辑器实例 */
|
|
export function create(container: string | HTMLElement, options?: MarkdownEditorOptions): MarkdownEditor;
|
|
|
|
/** 注册全局默认插件(作用于所有后续创建的实例) */
|
|
export function use(plugin: string | PluginObject, options?: object): typeof api;
|
|
|
|
/** 注册全局事件钩子 */
|
|
export function on(name: HookName, fn: (editor: MarkdownEditor) => void): () => void;
|
|
/** 注销全局事件钩子 */
|
|
export function off(name: HookName, fn: (editor: MarkdownEditor) => void): typeof api;
|
|
|
|
/** 切换全局主题 */
|
|
export function setTheme(theme: ThemeName, options?: object): typeof api;
|
|
/** 切换全局语言 */
|
|
export function setLocale(locale: string): typeof api;
|
|
/** 销毁所有全局资源 */
|
|
export function destroy(): void;
|
|
/** 获取库运行状态 */
|
|
export function getStatus(): {
|
|
version: string;
|
|
theme: string;
|
|
locale: string;
|
|
globalPlugins: string[];
|
|
presetPlugins: string[];
|
|
};
|
|
|
|
// ============ 解析器 ============
|
|
/** Markdown 解析函数 */
|
|
export function parseMarkdown(markdown: string, env?: RenderEnv): string;
|
|
/** URL 安全过滤 */
|
|
export function safeUrl(url: string): string;
|
|
/** 生成标题锚点 id */
|
|
export function slugify(text: string): string;
|
|
|
|
// ============ 插件 ============
|
|
/** 预设插件表 */
|
|
export const presetPlugins: {
|
|
autoSave: PluginObject;
|
|
exportTool: PluginObject;
|
|
searchReplace: PluginObject;
|
|
[key: string]: PluginObject;
|
|
};
|
|
|
|
/** 插件工具 */
|
|
export const pluginUtils: {
|
|
createManager(): PluginManager;
|
|
register(name: string, plugin: PluginObject): PluginManager;
|
|
unregister(name: string): PluginManager;
|
|
get(name: string): PluginObject | null;
|
|
has(name: string): boolean;
|
|
getAll(): PluginObject[];
|
|
getNames(): string[];
|
|
enable(name: string): PluginManager;
|
|
disable(name: string): PluginManager;
|
|
isEnabled(name: string): boolean;
|
|
getPreset(name: string): PluginObject | null;
|
|
getAllPresets(): { [key: string]: PluginObject };
|
|
createPlugin(config: object): PluginObject;
|
|
validatePlugin(plugin: PluginObject): { valid: boolean; errors: string[] };
|
|
};
|
|
|
|
/** 插件管理器 */
|
|
export class PluginManager {
|
|
register(name: string, plugin: PluginObject): this;
|
|
unregister(name: string): this;
|
|
get(name: string): PluginObject | null;
|
|
has(name: string): boolean;
|
|
getAll(): PluginObject[];
|
|
getNames(): string[];
|
|
enable(name: string): this;
|
|
disable(name: string): this;
|
|
isEnabled(name: string): boolean;
|
|
destroy(): void;
|
|
}
|
|
|
|
// ============ 常量 ============
|
|
export const DEFAULTS: Readonly<MarkdownEditorOptions>;
|
|
export const ICONS: { [key: string]: string };
|
|
export const THEMES: { [key: string]: any };
|
|
export const EDIT_MODES: EditMode[];
|
|
export const DEFAULT_TOOLBAR: ToolbarItem[];
|
|
export const TOOLBAR_ACTIONS: string[];
|
|
|
|
// ============ 工具集 ============
|
|
export const themeUtils: {
|
|
initTheme(): void;
|
|
switchTheme(theme: ThemeName): void;
|
|
toggleTheme(): void;
|
|
getCurrentTheme(): string;
|
|
getResolvedTheme(): string;
|
|
applyTheme(theme: ThemeName): void;
|
|
registerTheme(name: string, config: object): void;
|
|
unregisterTheme(name: string): void;
|
|
getAllThemes(): object;
|
|
getThemeNames(): string[];
|
|
hasTheme(name: string): boolean;
|
|
watchSystemTheme(): void;
|
|
unwatchSystemTheme(): void;
|
|
addThemeListener(fn: (theme: string, resolved: string) => void): () => void;
|
|
removeThemeListener(fn: (theme: string, resolved: string) => void): void;
|
|
clearThemeListeners(): void;
|
|
[key: string]: any;
|
|
};
|
|
|
|
export const i18nUtils: {
|
|
initI18n(): void;
|
|
t(key: string, params?: object): string;
|
|
getCurrentLocale(): string;
|
|
setCurrentLocale(locale: string): void;
|
|
loadLocale(locale: string, messages: object): void;
|
|
addLocaleListener(fn: (locale: string) => void): () => void;
|
|
removeLocaleListener(fn: (locale: string) => void): void;
|
|
clearLocaleListeners(): void;
|
|
[key: string]: any;
|
|
};
|
|
|
|
export const animationUtils: {
|
|
cancelAll(): void;
|
|
[key: string]: any;
|
|
};
|
|
|
|
// ============ 默认导出 ============
|
|
export interface MeEditorAPI {
|
|
VERSION: string;
|
|
version: string;
|
|
MarkdownEditor: typeof MarkdownEditor;
|
|
Editor: typeof MarkdownEditor;
|
|
create: typeof create;
|
|
use: typeof use;
|
|
on: typeof on;
|
|
off: typeof off;
|
|
setTheme: typeof setTheme;
|
|
setLocale: typeof setLocale;
|
|
destroy: typeof destroy;
|
|
getStatus: typeof getStatus;
|
|
parseMarkdown: typeof parseMarkdown;
|
|
safeUrl: typeof safeUrl;
|
|
slugify: typeof slugify;
|
|
themes: typeof themeUtils;
|
|
i18n: typeof i18nUtils;
|
|
animations: typeof animationUtils;
|
|
plugins: typeof pluginUtils;
|
|
presetPlugins: typeof presetPlugins;
|
|
DEFAULTS: typeof DEFAULTS;
|
|
ICONS: typeof ICONS;
|
|
THEMES: typeof THEMES;
|
|
EDIT_MODES: typeof EDIT_MODES;
|
|
DEFAULT_TOOLBAR: typeof DEFAULT_TOOLBAR;
|
|
TOOLBAR_ACTIONS: typeof TOOLBAR_ACTIONS;
|
|
}
|
|
|
|
/** 默认导出 API 对象 */
|
|
declare const api: MeEditorAPI;
|
|
export default api;
|
|
|
|
export {
|
|
api,
|
|
api as meEditor,
|
|
api as MeEditor,
|
|
};
|