/** * Copy text to clipboard * @param text - text to copy * @returns - whether copy is successful */ async function copyTextToClipboard(text: string): Promise { // Try Clipboard API first if (navigator.clipboard?.writeText) { try { await navigator.clipboard.writeText(text); return true; } catch (err) { console.warn('Clipboard API failed:', err); } } // Fallback 1: execCommand (PC) try { const textarea = document.createElement('textarea'); textarea.value = text; // Prevent scrolling textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.appendChild(textarea); // Special handling for iOS if (navigator.userAgent.match(/ipad|iphone/i)) { textarea.contentEditable = 'true'; textarea.readOnly = false; const range = document.createRange(); range.selectNodeContents(textarea); const selection = window.getSelection(); if (selection) { selection.removeAllRanges(); selection.addRange(range); textarea.setSelectionRange(0, 999999); } } else { textarea.select(); } const result = document.execCommand('copy'); document.body.removeChild(textarea); return result; } catch (err) { console.warn('execCommand failed:', err); } // Fallback 2: Prompt user to copy manually throw new Error('Failed to copy text to clipboard, please copy manually'); } /** * exitFullscreen * @example * exitFullscreen(); */ function exitFullScreen() { if (!document?.fullscreenElement && !(document as any)?.webkitFullscreenElement && !(document as any)?.mozFullScreenElement) { return; } const exitFullScreenDocument = document as Document & { mozCancelFullScreen(): Promise; msExitFullscreen(): Promise; webkitExitFullscreen(): Promise; }; if (exitFullScreenDocument?.exitFullscreen) { exitFullScreenDocument?.exitFullscreen(); } else if (exitFullScreenDocument?.msExitFullscreen) { exitFullScreenDocument?.msExitFullscreen(); } else if (exitFullScreenDocument?.mozCancelFullScreen) { exitFullScreenDocument?.mozCancelFullScreen(); } else if (exitFullScreenDocument?.webkitExitFullscreen) { exitFullScreenDocument?.webkitExitFullscreen(); } } /** * Deep merge multiple objects into a single object * @param args - Objects to be merged * @returns Merged object * @example * const obj1 = { a: 1, b: { c: 2 } }; * const obj2 = { b: { d: 3 }, e: 4 }; * const result = objectMerge(obj1, obj2); * // result: { a: 1, b: { c: 2, d: 3 }, e: 4 } */ function objectMerge(...args: any[]) { return args.reduce((acc, cur) => { Object.keys(cur).forEach((key) => { if (acc[key] && typeof acc[key] === 'object') { acc[key] = objectMerge(acc[key], cur[key]); } else { acc[key] = cur[key]; } }); return acc; }, {}); } const _encoder = new TextEncoder(); /** * Truncate a string so its UTF-8 byte length does not exceed maxBytes. * Avoids splitting multi-byte characters mid-sequence. */ function truncateByBytes(str: string, maxBytes: number): string { if (_encoder.encode(str).length <= maxBytes) { return str; } let result = ''; let bytes = 0; for (const char of str) { const charBytes = _encoder.encode(char).length; if (bytes + charBytes > maxBytes) { break; } result += char; bytes += charBytes; } return result; } export { copyTextToClipboard, exitFullScreen, objectMerge, truncateByBytes, };