-
🌐 async-utils.jsWeb, 3.69 kB⬇
-
🌐 auth-storage.jsWeb, 735 B⬇
-
🌐 chat-helpers.jsWeb, 8.85 kB⬇
-
🌐 chat-renderer.jsWeb, 70.00 kB⬇
-
🌐 code-overlay.jsWeb, 21.77 kB⬇
-
🌐 confirm-dialog.jsWeb, 3.17 kB⬇
-
🌐 conversation-format-utils.jsWeb, 21.51 kB⬇
-
🌐 diagram-overlay.jsWeb, 7.93 kB⬇
-
🌐 dialog.jsWeb, 2.50 kB⬇
-
🌐 dom-utils.jsWeb, 8.33 kB⬇
-
🌐 drag-reorder.jsWeb, 4.82 kB⬇
-
🌐 elements.jsWeb, 11.06 kB⬇
-
🌐 export-utils.jsWeb, 1.53 kB⬇
-
🌐 format-utils.jsWeb, 29.60 kB⬇
-
🌐 history-navigation.jsWeb, 1.95 kB⬇
-
🌐 hljs-yaml.jsWeb, 5.07 kB⬇
-
🌐 inline-format.jsWeb, 7.35 kB⬇
-
🌐 instant-answer.jsWeb, 19.82 kB⬇
-
🌐 interactive-tools.jsWeb, 38.77 kB⬇
-
🌐 mermaid-fixes.jsWeb, 4.60 kB⬇
-
🌐 mermaid-render.jsWeb, 5.10 kB⬇
-
🌐 mermaid.jsWeb, 195 B⬇
-
🌐 migrations.jsWeb, 12.11 kB⬇
-
🌐 multi-choice-dialog.jsWeb, 1.33 kB⬇
-
🌐 notification-stack.jsWeb, 2.50 kB⬇
-
🌐 options-manager.jsWeb, 45.56 kB⬇
-
🌐 overlay-utils.jsWeb, 4.14 kB⬇
-
🌐 pdf-export.jsWeb, 23.59 kB⬇
-
🌐 pinch-zoom.jsWeb, 3.21 kB⬇
-
🌐 plugin-dialog.jsWeb, 2.17 kB⬇
-
🌐 read-aloud.jsWeb, 33.84 kB⬇
-
🌐 response-pipeline.jsWeb, 55.19 kB⬇
-
🌐 scratchpad-editor.jsWeb, 8.64 kB⬇
-
🌐 scratchpad-export.jsWeb, 3.56 kB⬇
-
🌐 scrollbars.jsWeb, 3.12 kB⬇
-
🌐 selection-manager.jsWeb, 3.76 kB⬇
-
🌐 settings-utils.jsWeb, 31.08 kB⬇
-
🌐 share.jsWeb, 10.75 kB⬇
-
🌐 side-drawer.jsWeb, 9.37 kB⬇
-
🌐 speech-text.jsWeb, 6.60 kB⬇
-
🌐 swipe-navigation.jsWeb, 6.16 kB⬇
-
🌐 table-overlay.jsWeb, 8.10 kB⬇
-
🌐 theme-utils.jsWeb, 8.34 kB⬇
-
🌐 thinking-panel-base.jsWeb, 14.88 kB⬇
-
🌐 ui-sets.jsWeb, 1.00 kB⬇
-
🌐 version-overlay.jsWeb, 4.63 kB⬇
/**
* Asynchronous, clipboard, and timer utilities for TinAI.
*/
import { extractFormattedContent } from './format-utils.js';
/**
* Creates a debounced function that delays invoking func until after wait milliseconds.
* Provides a .cancel() method to abort any scheduled execution.
* @param {Function} func - Function to debounce.
* @param {number} [wait=250] - Milliseconds to delay.
* @returns {Function & { cancel: Function }} Debounced wrapper function with cancel method.
*/
export function debounce(func, wait = 250) {
let timeout = null;
const executedFunction = function(...args) {
const later = () => {
timeout = null;
func.apply(this, args);
};
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
timeout = setTimeout(later, wait);
};
executedFunction.cancel = function() {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
};
return executedFunction;
}
/**
* Copies plain text or rich HTML/images to the clipboard with modern clipboard API and fallback.
* Supports strings, HTMLElements, or objects with text and html fields.
* @param {string|HTMLElement|{ text: string, html?: string }} payload - Content or element to copy.
* @param {HTMLElement|null} [feedbackButton=null] - Optional button to show visual feedback on.
* @returns {Promise<boolean>} Whether copy succeeded.
*/
export async function copyToClipboard(payload, feedbackButton = null) {
const applyFeedback = () => {
if (!feedbackButton) return;
const originalText = feedbackButton.textContent;
feedbackButton.textContent = 'Copied!';
feedbackButton.disabled = true;
setTimeout(() => {
feedbackButton.textContent = originalText;
feedbackButton.disabled = false;
}, 1500);
};
let plainText = '';
let htmlText = '';
if (payload instanceof HTMLElement || (payload && typeof payload === 'object' && payload.nodeType === 1)) {
const extracted = await extractFormattedContent(payload);
plainText = extracted.text;
htmlText = extracted.html;
} else if (payload && typeof payload === 'object' && (payload.text !== undefined || payload.html !== undefined)) {
plainText = payload.text || '';
htmlText = payload.html || '';
} else {
plainText = typeof payload === 'string' ? payload : String(payload || '');
}
if (navigator.clipboard && typeof navigator.clipboard.write === 'function' && typeof ClipboardItem !== 'undefined') {
try {
const clipboardItems = {};
if (plainText) {
clipboardItems['text/plain'] = new Blob([plainText], { type: 'text/plain' });
}
if (htmlText) {
clipboardItems['text/html'] = new Blob([htmlText], { type: 'text/html' });
}
if (Object.keys(clipboardItems).length > 0) {
await navigator.clipboard.write([new ClipboardItem(clipboardItems)]);
applyFeedback();
return true;
}
} catch (err) {
console.warn('navigator.clipboard.write failed, falling back to writeText:', err);
}
}
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
try {
await navigator.clipboard.writeText(plainText);
applyFeedback();
return true;
} catch (err) {
console.warn('navigator.clipboard.writeText failed, falling back to legacy execCommand:', err);
}
}
try {
const textarea = document.createElement('textarea');
textarea.value = plainText;
textarea.setAttribute('readonly', '');
textarea.style.position = 'fixed';
textarea.style.left = '-9999px';
textarea.style.top = '0';
document.body.appendChild(textarea);
textarea.select();
const successful = document['execCommand']('copy');
document.body.removeChild(textarea);
if (successful) {
applyFeedback();
}
return !!successful;
} catch (err) {
console.error('All clipboard copy methods failed:', err);
return false;
}
}
async-utils.js
×
Type: Web, text/x-java
3.69 Kilobytes
Last Modified 2026-11-04 09:48:28
⬇ Download File
Type: Web, text/x-java
3.69 Kilobytes
Last Modified 2026-11-04 09:48:28
⬇ Download File