| 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;
|
| }
|
|
|