| import { toast } from 'svelte-sonner'; |
| import { AttachmentType } from '$lib/enums'; |
| import type { |
| DatabaseMessageExtra, |
| DatabaseMessageExtraTextFile, |
| DatabaseMessageExtraLegacyContext, |
| ClipboardTextAttachment, |
| ParsedClipboardContent |
| } from '$lib/types'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export async function copyToClipboard( |
| text: string, |
| successMessage = 'Copied to clipboard', |
| errorMessage = 'Failed to copy to clipboard' |
| ): Promise<boolean> { |
| try { |
| |
| if (navigator.clipboard && navigator.clipboard.writeText) { |
| await navigator.clipboard.writeText(text); |
| toast.success(successMessage); |
| return true; |
| } |
|
|
| |
| const textArea = document.createElement('textarea'); |
| textArea.value = text; |
| textArea.style.position = 'fixed'; |
| textArea.style.left = '-999999px'; |
| textArea.style.top = '-999999px'; |
| document.body.appendChild(textArea); |
| textArea.focus(); |
| textArea.select(); |
|
|
| const successful = document.execCommand('copy'); |
| document.body.removeChild(textArea); |
|
|
| if (successful) { |
| toast.success(successMessage); |
| return true; |
| } else { |
| throw new Error('execCommand failed'); |
| } |
| } catch (error) { |
| console.error('Failed to copy to clipboard:', error); |
| toast.error(errorMessage); |
| return false; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export async function copyCodeToClipboard( |
| rawCode: string, |
| successMessage = 'Code copied to clipboard', |
| errorMessage = 'Failed to copy code' |
| ): Promise<boolean> { |
| return copyToClipboard(rawCode, successMessage, errorMessage); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function formatMessageForClipboard( |
| content: string, |
| extras?: DatabaseMessageExtra[], |
| asPlainText: boolean = false |
| ): string { |
| |
| const textAttachments = |
| extras?.filter( |
| (extra): extra is DatabaseMessageExtraTextFile | DatabaseMessageExtraLegacyContext => |
| extra.type === AttachmentType.TEXT || extra.type === AttachmentType.LEGACY_CONTEXT |
| ) ?? []; |
|
|
| if (textAttachments.length === 0) { |
| return content; |
| } |
|
|
| if (asPlainText) { |
| const parts = [content]; |
| for (const att of textAttachments) { |
| parts.push(att.content); |
| } |
| return parts.join('\n\n'); |
| } |
|
|
| const clipboardAttachments: ClipboardTextAttachment[] = textAttachments.map((att) => ({ |
| type: AttachmentType.TEXT, |
| name: att.name, |
| content: att.content |
| })); |
|
|
| return `${JSON.stringify(content)}\n${JSON.stringify(clipboardAttachments, null, 2)}`; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function parseClipboardContent(clipboardText: string): ParsedClipboardContent { |
| const defaultResult: ParsedClipboardContent = { |
| message: clipboardText, |
| textAttachments: [] |
| }; |
|
|
| if (!clipboardText.startsWith('"')) { |
| return defaultResult; |
| } |
|
|
| try { |
| let stringEndIndex = -1; |
| let escaped = false; |
|
|
| for (let i = 1; i < clipboardText.length; i++) { |
| const char = clipboardText[i]; |
|
|
| if (escaped) { |
| escaped = false; |
| continue; |
| } |
|
|
| if (char === '\\') { |
| escaped = true; |
| continue; |
| } |
|
|
| if (char === '"') { |
| stringEndIndex = i; |
| break; |
| } |
| } |
|
|
| if (stringEndIndex === -1) { |
| return defaultResult; |
| } |
|
|
| const jsonStringPart = clipboardText.substring(0, stringEndIndex + 1); |
| const remainingPart = clipboardText.substring(stringEndIndex + 1).trim(); |
|
|
| const message = JSON.parse(jsonStringPart) as string; |
|
|
| if (!remainingPart || !remainingPart.startsWith('[')) { |
| return { |
| message, |
| textAttachments: [] |
| }; |
| } |
|
|
| const attachments = JSON.parse(remainingPart) as unknown[]; |
|
|
| const validAttachments: ClipboardTextAttachment[] = []; |
|
|
| for (const att of attachments) { |
| if (isValidTextAttachment(att)) { |
| validAttachments.push({ |
| type: AttachmentType.TEXT, |
| name: att.name, |
| content: att.content |
| }); |
| } |
| } |
|
|
| return { |
| message, |
| textAttachments: validAttachments |
| }; |
| } catch { |
| return defaultResult; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| function isValidTextAttachment( |
| obj: unknown |
| ): obj is { type: string; name: string; content: string } { |
| if (typeof obj !== 'object' || obj === null) { |
| return false; |
| } |
|
|
| const record = obj as Record<string, unknown>; |
|
|
| return ( |
| (record.type === AttachmentType.TEXT || record.type === 'TEXT') && |
| typeof record.name === 'string' && |
| typeof record.content === 'string' |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| export function hasClipboardAttachments(clipboardText: string): boolean { |
| if (!clipboardText.startsWith('"')) { |
| return false; |
| } |
|
|
| const parsed = parseClipboardContent(clipboardText); |
| return parsed.textAttachments.length > 0; |
| } |
|
|