feat(plugins): plugin system v2
- depends: declarative plugin dependencies with topological sort (Kahn algorithm)
- async plugins: install() returning Promise auto-await
- editor.unuse(name): uninstall individual plugins
- pluginUtils.validateConfig(schema, config): configuration validation
- plugin priority field controls install order within same dependency level
feat(i18n): instance-level locale isolation, pluralization, dynamic loading
- createInstanceI18n(): per-editor independent locale contexts
- editor.setLocale()/getLocale()/t() instance methods
- Plural rules: t('items', { count: 5 }) auto-selects one/other/few/many
- i18nUtils.loadRemote(url, locale): fetch translation packs from remote
- editor.on('localeChange') event
feat(core): toolbar & shortcut customization, context menu, toast
- editor.registerShortcut(combo, handler): custom keyboard shortcuts
- editor.configureToolbar(tools): dynamic toolbar rebuild
- editor.removeToolbarButton(action): remove single button
- editor.registerContextMenu(items): right-click context menu
- editor.toast(msg, {type, duration, animation}): toast notifications
style: toast notifications, context menu, dropdown CSS
- .me-toast with success/error/warning/info types, fade/slide animations
- .me-context-menu with items, separators, shortcut hints
test: 22 new tests covering plugin deps, unuse, shortcuts, toast, i18n plural (521 total)
chore: bump version 0.1.5 → 0.1.6 across all files, site, types
199 lines
6.2 KiB
JavaScript
199 lines
6.2 KiB
JavaScript
/**
|
||
* MetonaEditor Constants - 常量定义
|
||
* @module constants
|
||
* @version 0.1.6
|
||
* @description 默认配置、主题、动画、工具栏等常量
|
||
*/
|
||
|
||
import { ICONS } from './icons.js';
|
||
import { LOCALES } from './locales.js';
|
||
|
||
/**
|
||
* 默认工具栏按钮序列
|
||
* '|' 表示分组分隔符
|
||
*/
|
||
export const DEFAULT_TOOLBAR = [
|
||
'bold', 'italic', 'strikethrough', 'code', '|',
|
||
'h1', 'h2', 'h3', '|',
|
||
'quote', 'ul', 'ol', '|',
|
||
'link', 'image', 'table', 'hr', '|',
|
||
'undo', 'redo', '|',
|
||
'edit', 'split', 'preview', 'fullscreen',
|
||
];
|
||
|
||
/**
|
||
* 默认配置
|
||
*/
|
||
export const DEFAULTS = Object.freeze({
|
||
// 内容
|
||
value: '',
|
||
placeholder: '',
|
||
// 视图模式:edit / split / preview
|
||
mode: 'split',
|
||
// 高度:数字为 px,字符串原样使用
|
||
height: 400,
|
||
// 工具栏:数组或 false(隐藏)
|
||
toolbar: DEFAULT_TOOLBAR,
|
||
// 字数统计状态栏
|
||
wordCount: true,
|
||
// 自动聚焦
|
||
autofocus: false,
|
||
// 拼写检查
|
||
spellcheck: false,
|
||
// 历史栈上限
|
||
historyLimit: 100,
|
||
// 历史栈防抖延迟(ms)
|
||
historyDebounce: 400,
|
||
// 同步滚动(分屏模式)
|
||
syncScroll: true,
|
||
// 制表符插入的空格数(0 表示插入 \t)
|
||
tabSize: 2,
|
||
// 只读模式
|
||
readOnly: false,
|
||
// 主题:light / dark / auto / warm / 自定义
|
||
theme: 'auto',
|
||
// 国际化
|
||
locale: 'zh-CN',
|
||
// 自定义 Markdown 渲染函数 (md, env) => html,覆盖内置解析器
|
||
render: null,
|
||
// 自定义代码高亮函数 (code, lang) => html
|
||
highlight: null,
|
||
// 自定义渲染前的 HTML 净化函数(接收渲染后的 HTML,返回安全 HTML)
|
||
sanitize: null,
|
||
// 外观
|
||
className: '',
|
||
style: {},
|
||
// 插件
|
||
plugins: [],
|
||
// 生命周期回调
|
||
onChange: null,
|
||
onInput: null,
|
||
onFocus: null,
|
||
onBlur: null,
|
||
onSave: null,
|
||
onModeChange: null,
|
||
onFullscreen: null,
|
||
onCreate: null,
|
||
onDestroy: null,
|
||
});
|
||
|
||
// Icons - 从 icons.js 导入并重新导出(保持单一来源)
|
||
export { ICONS };
|
||
|
||
/**
|
||
* 类型颜色配置(保留用于状态提示与按钮强调)
|
||
*/
|
||
export const TYPE_COLORS = {
|
||
success: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
|
||
error: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
|
||
warning: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
|
||
info: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
|
||
loading: { fg: '#6366f1', bg: 'rgba(99, 102, 241, 0.1)' },
|
||
default: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
|
||
};
|
||
|
||
/**
|
||
* 主题配置
|
||
*/
|
||
export const THEMES = {
|
||
light: {
|
||
bg: 'rgba(255, 255, 255, 0.96)',
|
||
text: '#1f2937',
|
||
border: 'rgba(0, 0, 0, 0.08)',
|
||
shadow: '0 10px 36px -10px rgba(0, 0, 0, 0.18), 0 4px 14px -4px rgba(0, 0, 0, 0.08)',
|
||
hoverShadow: '0 14px 48px -10px rgba(0, 0, 0, 0.22), 0 6px 18px -4px rgba(0, 0, 0, 0.10)',
|
||
// 编辑器专属
|
||
toolbarBg: 'rgba(248, 249, 250, 0.92)',
|
||
textareaBg: '#ffffff',
|
||
previewBg: '#ffffff',
|
||
codeBg: 'rgba(243, 244, 246, 1)',
|
||
codeText: '#1f2937',
|
||
accent: '#3b82f6',
|
||
muted: '#6b7280',
|
||
},
|
||
dark: {
|
||
bg: 'rgba(28, 32, 40, 0.94)',
|
||
text: '#e6e8eb',
|
||
border: 'rgba(255, 255, 255, 0.1)',
|
||
shadow: '0 10px 36px -10px rgba(0, 0, 0, 0.6), 0 4px 14px -4px rgba(0, 0, 0, 0.4)',
|
||
hoverShadow: '0 14px 48px -8px rgba(0, 0, 0, 0.6), 0 6px 18px -4px rgba(0, 0, 0, 0.4)',
|
||
toolbarBg: 'rgba(22, 26, 33, 0.92)',
|
||
textareaBg: '#1c2028',
|
||
previewBg: '#1c2028',
|
||
codeBg: 'rgba(15, 18, 24, 1)',
|
||
codeText: '#e6e8eb',
|
||
accent: '#60a5fa',
|
||
muted: '#9ca3af',
|
||
},
|
||
auto: 'auto',
|
||
warm: {
|
||
bg: 'rgba(255, 251, 235, 0.96)',
|
||
text: '#78350f',
|
||
border: 'rgba(245, 158, 11, 0.2)',
|
||
shadow: '0 10px 36px -10px rgba(245, 158, 11, 0.18), 0 4px 14px -4px rgba(245, 158, 11, 0.08)',
|
||
hoverShadow: '0 14px 48px -10px rgba(245, 158, 11, 0.22), 0 6px 18px -4px rgba(245, 158, 11, 0.10)',
|
||
toolbarBg: 'rgba(254, 243, 199, 0.92)',
|
||
textareaBg: '#fffbeb',
|
||
previewBg: '#fffbeb',
|
||
codeBg: 'rgba(254, 215, 170, 0.6)',
|
||
codeText: '#78350f',
|
||
accent: '#d97706',
|
||
muted: '#a16207',
|
||
},
|
||
};
|
||
|
||
/**
|
||
* 位置配置(保留,预览面板提示等场景可用)
|
||
*/
|
||
export const POSITIONS = [
|
||
'top-left', 'top-center', 'top-right',
|
||
'bottom-left', 'bottom-center', 'bottom-right',
|
||
];
|
||
|
||
/**
|
||
* 动画配置(保留,DOM 过渡与未来扩展使用)
|
||
*/
|
||
export const ANIMATIONS = {
|
||
slide: { enter: { transform: 'translateX(80px)', opacity: 0 }, leave: { transform: 'translateX(120%)', opacity: 0 }, duration: 400, easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)' },
|
||
fade: { enter: { opacity: 0, filter: 'blur(3px)' }, leave: { opacity: 0, filter: 'blur(3px)' }, duration: 500, easing: 'ease' },
|
||
scale: { enter: { transform: 'scale(0.55)', opacity: 0 }, leave: { transform: 'scale(0.55)', opacity: 0 }, duration: 450, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' },
|
||
bounce: { enter: { transform: 'translateY(-80px)', opacity: 0 }, leave: { transform: 'translateY(20px)', opacity: 0 }, duration: 650, easing: 'ease' },
|
||
flip: { enter: { transform: 'perspective(500px) rotateX(-90deg)', opacity: 0 }, leave: { transform: 'perspective(500px) rotateX(90deg)', opacity: 0 }, duration: 500, easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)' },
|
||
rotate: { enter: { transform: 'rotate(-25deg) scale(0.6)', opacity: 0 }, leave: { transform: 'rotate(25deg) scale(0.6)', opacity: 0 }, duration: 500, easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)' },
|
||
zoom: { enter: { transform: 'scale(0.1)', opacity: 0 }, leave: { transform: 'scale(0.1)', opacity: 0 }, duration: 500, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' },
|
||
};
|
||
|
||
/**
|
||
* 主题类型
|
||
*/
|
||
export const THEME_TYPES = ['light', 'dark', 'auto'];
|
||
|
||
/**
|
||
* 动画类型
|
||
*/
|
||
export const ANIMATION_TYPES = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', 'zoom'];
|
||
|
||
/**
|
||
* 编辑器视图模式
|
||
*/
|
||
export const EDIT_MODES = ['edit', 'split', 'preview'];
|
||
|
||
/**
|
||
* 内置工具栏动作清单
|
||
*/
|
||
export const TOOLBAR_ACTIONS = [
|
||
'bold', 'italic', 'strikethrough', 'underline', 'code',
|
||
'h1', 'h2', 'h3', 'quote', 'ul', 'ol', 'indent', 'outdent', 'hr',
|
||
'link', 'image', 'table',
|
||
'undo', 'redo',
|
||
'edit', 'split', 'preview', 'fullscreen',
|
||
];
|
||
|
||
// Locales - 从 locales.js 导入并重新导出
|
||
export { LOCALES };
|
||
|
||
/**
|
||
* 进度条方向(保留兼容)
|
||
*/
|
||
export const PROGRESS_DIRECTIONS = ['horizontal', 'vertical'];
|