feat: v0.2.0 — TypeScript full rewrite, 95%+ core coverage
BREAKING CHANGE: All source files converted from JavaScript to TypeScript. - 12 .ts source files with strict types, full EditorOptions/Plugin/Token interfaces - 7 .ts test files, 610 total tests (27 new), 7 suites all passing - tsc --noEmit: 0 errors - rollup-plugin-typescript build: 5 artifacts (UMD/ESM/CJS/Min/DTS) - @babel/preset-typescript for jest - New tsconfig.json, updated babel/jest/rollup configs - Coverage: parser 99.5%, utils 95.7%, themes 96.2%, core 88.8%, plugins 89.5% - Removed types/ folder (types now inline in .ts + auto-generated .d.ts) - Desktop-only, no backward compatibility
This commit is contained in:
+102
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* MetonaEditor Utils — utility functions
|
||||
* @module utils
|
||||
* @version 0.2.0
|
||||
*/
|
||||
|
||||
/** Generate a unique ID */
|
||||
export const generateId = (): string => {
|
||||
return 'me-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
|
||||
};
|
||||
|
||||
/** HTML-escape a string */
|
||||
export const escapeHTML = (s: unknown): string => {
|
||||
const str = String(s == null ? '' : s);
|
||||
if (typeof document === 'undefined') {
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
};
|
||||
|
||||
/** Detect dark mode preference */
|
||||
export const prefersDark = (): boolean => {
|
||||
if (typeof window === 'undefined' || !window.matchMedia) return false;
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
};
|
||||
|
||||
/** Debounce a function */
|
||||
export function debounce<T extends (...args: any[]) => void>(
|
||||
func: T,
|
||||
wait: number,
|
||||
immediate: boolean = false,
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeout: ReturnType<typeof setTimeout> | null;
|
||||
return function (this: any, ...args: Parameters<T>) {
|
||||
const context = this;
|
||||
const later = () => {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
};
|
||||
const callNow = immediate && !timeout;
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) func.apply(context, args);
|
||||
};
|
||||
}
|
||||
|
||||
/** Throttle a function */
|
||||
export function throttle<T extends (...args: any[]) => void>(
|
||||
func: T,
|
||||
limit: number,
|
||||
): (...args: Parameters<T>) => void {
|
||||
let inThrottle = false;
|
||||
return function (this: any, ...args: Parameters<T>) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => { inThrottle = false; }, limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Deep-merge two objects */
|
||||
export const deepMerge = <T extends Record<string, unknown>>(target: T, source: Partial<T>): T => {
|
||||
const output: Record<string, unknown> = { ...target };
|
||||
for (const key of Object.keys(source as Record<string, unknown>)) {
|
||||
const sv = (source as Record<string, unknown>)[key];
|
||||
const tv = (target as Record<string, unknown>)[key];
|
||||
if (sv instanceof Object && key in target && tv instanceof Object) {
|
||||
output[key] = deepMerge(tv as Record<string, unknown>, sv as Record<string, unknown>);
|
||||
} else {
|
||||
output[key] = source[key];
|
||||
}
|
||||
}
|
||||
return output as T;
|
||||
};
|
||||
|
||||
/** Check if running in browser */
|
||||
export const isBrowser = (): boolean => {
|
||||
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
||||
};
|
||||
|
||||
/** Format file size to human-readable string */
|
||||
export const formatFileSize = (bytes: number, decimals: number = 2): string => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
/** Sleep for ms milliseconds */
|
||||
export const sleep = (ms: number): Promise<void> => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
Reference in New Issue
Block a user