From cea4e86c343cd7889798845252b47ffe32f27441 Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Sun, 25 May 2025 01:39:24 +0200 Subject: [PATCH] add image support --- package-lock.json | 1 + package.json | 1 + src/lib/components/ChatInput.svelte | 65 ++++++++- src/lib/components/ChatMessage.svelte | 16 ++- src/lib/components/ChatMessageThread.svelte | 2 +- src/lib/components/Image.svelte | 38 +++++ src/lib/components/ReplyMessage.svelte | 2 +- src/lib/components/TimelineView.svelte | 24 +++- .../rich-text-editor/RichTextEditor.svelte | 135 ++++++++---------- src/lib/schema.ts | 6 +- .../channel/[channelId]/+page.svelte | 27 +++- .../thread/[threadId]/+page.svelte | 19 ++- 12 files changed, 234 insertions(+), 102 deletions(-) create mode 100644 src/lib/components/Image.svelte diff --git a/package-lock.json b/package-lock.json index 933dea1..6200766 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@tiptap/extension-underline": "^2.12.0", "@tiptap/starter-kit": "^2.12.0", "@use-gesture/vanilla": "^10.3.1", + "bits-ui": "^1.7.0", "jazz-browser-media-images": "^0.14.8", "jazz-inspector-element": "^0.14.0", "jazz-richtext-tiptap": "^0.1.15", diff --git a/package.json b/package.json index 51dabca..56d291f 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@tiptap/extension-underline": "^2.12.0", "@tiptap/starter-kit": "^2.12.0", "@use-gesture/vanilla": "^10.3.1", + "bits-ui": "^1.7.0", "jazz-browser-media-images": "^0.14.8", "jazz-inspector-element": "^0.14.0", "jazz-richtext-tiptap": "^0.1.15", diff --git a/src/lib/components/ChatInput.svelte b/src/lib/components/ChatInput.svelte index 58326d9..537bfed 100644 --- a/src/lib/components/ChatInput.svelte +++ b/src/lib/components/ChatInput.svelte @@ -1,10 +1,13 @@
{#if me?.root?.joinedSpaces?.some((space) => space?.id === route.spaceId)} {#if replyTo} @@ -69,11 +88,47 @@
{/if} - + + {#if images && images.length > 0} +
+ {#each images as image} +
+ + +
+ {/each} +
+ {/if} + {#if processingImage} +
+ Processing image... +
+ {/if} + + {:else} diff --git a/src/lib/components/ChatMessage.svelte b/src/lib/components/ChatMessage.svelte index 63baf57..42d15c7 100644 --- a/src/lib/components/ChatMessage.svelte +++ b/src/lib/components/ChatMessage.svelte @@ -1,5 +1,5 @@ + + diff --git a/src/lib/components/ReplyMessage.svelte b/src/lib/components/ReplyMessage.svelte index 885ec49..d488b8c 100644 --- a/src/lib/components/ReplyMessage.svelte +++ b/src/lib/components/ReplyMessage.svelte @@ -22,7 +22,7 @@ {#if message.current}
diff --git a/src/lib/components/TimelineView.svelte b/src/lib/components/TimelineView.svelte index 4c0a710..c8c13cb 100644 --- a/src/lib/components/TimelineView.svelte +++ b/src/lib/components/TimelineView.svelte @@ -6,6 +6,7 @@ import { page } from '$app/state'; import { setContext } from 'svelte'; import { view } from '../../routes/[spaceId]/view.svelte'; + import { ScrollArea } from '@fuxui/base'; let viewport: HTMLDivElement = $state(null!); let virtualizer: Virtualizer | undefined = $state(); @@ -54,12 +55,21 @@ }); -
+
+ {#if timeline && timeline.filter((m) => !m.softDeleted).length > 0} - k} overscan={10}> + k} + overscan={10} + scrollRef={viewport} + startMargin={96} + > {#snippet children(message: Loaded, index: number)} {@const previousMessage = index > 0 ? timeline[index - 1] : undefined} {#if message && !message.softDeleted} @@ -75,9 +85,13 @@ {/if} {/snippet} + +
+ {:else if timeline === undefined} +
Loading chat...
{:else}
No messages yet. Be the first to send a message!
{/if} -
+ diff --git a/src/lib/components/rich-text-editor/RichTextEditor.svelte b/src/lib/components/rich-text-editor/RichTextEditor.svelte index c97b406..c5a88bc 100644 --- a/src/lib/components/rich-text-editor/RichTextEditor.svelte +++ b/src/lib/components/rich-text-editor/RichTextEditor.svelte @@ -7,9 +7,14 @@ import Underline from '@tiptap/extension-underline'; import Typography from '@tiptap/extension-typography'; import { RichTextLink } from './RichTextLink'; - import { cn } from '@fuxui/base'; + import { Button, cn } from '@fuxui/base'; import { ImageUploadNode } from './image-upload/ImageUploadNode'; import { initKeyboardShortcutHandler } from './onEnter'; + import { ImageList } from '$lib/schema'; + import type { Loaded } from 'jazz-tools'; + import { createImage } from 'jazz-browser-media-images'; + import { publicGroup } from '$lib/utils'; + import { Portal } from 'bits-ui'; let { content = $bindable({}), @@ -19,7 +24,8 @@ class: className, onupdate, htmlContent = $bindable(''), - onEnter, + processImageFile, + onEnter }: { content?: Content; placeholder?: string; @@ -30,6 +36,7 @@ ontransaction?: () => void; htmlContent?: string; onEnter: () => void; + processImageFile: (file: File) => void; } = $props(); let hasFocus = true; @@ -57,12 +64,6 @@ return ''; } }), - // Image.configure({ - // HTMLAttributes: { - // class: 'max-w-full object-contain relative rounded-2xl' - // }, - // allowBase64: true - // }), Underline.configure({}), RichTextLink.configure({ openOnClick: false, @@ -70,19 +71,7 @@ defaultProtocol: 'https' }), Typography.configure(), - // ImageUploadNode.configure({ - // upload: async (file, onProgress, abortSignal) => { - // console.log('uploading image', file); - // // wait 2 seconds - // for(let i = 0; i < 10; i++) { - // await new Promise((resolve) => setTimeout(resolve, 200)); - // onProgress?.({ progress: i / 10 }); - // } - - // return 'https://picsum.photos/200/300'; - // } - // }), - initKeyboardShortcutHandler({ onEnter: onEnter }), + initKeyboardShortcutHandler({ onEnter: onEnter }) ]; editor = new Editor({ @@ -106,51 +95,10 @@ }, content: `` }); - }); - // Flag to track whether a file is being dragged over the drop area let isDragOver = $state(false); - // Store local image files for later upload - let localImages: Map = $state(new Map()); - - // Track which image URLs in the editor are local previews - let localImageUrls: Set = $state(new Set()); - - // Process image file to create a local preview - async function processImageFile(file: File) { - if (!editor) { - console.warn('Tiptap editor not initialized'); - return; - } - - try { - const localUrl = URL.createObjectURL(file); - - localImages.set(localUrl, file); - localImageUrls.add(localUrl); - - //editor.commands.setImageUploadNode(); - editor.chain().focus().setImageUploadNode( - { - preview: localUrl - } - ).run(); - - // wait 2 seconds - // await new Promise((resolve) => setTimeout(resolve, 500)); - - // content = editor.getJSON(); - - // console.log('replacing image url in content'); - // replaceImageUrlInContent(content, localUrl, 'https://picsum.photos/200/300'); - // editor.commands.setContent(content); - } catch (error) { - console.error('Error creating image preview:', error); - } - } - const handlePaste = (event: ClipboardEvent) => { const items = event.clipboardData?.items; if (!items) return; @@ -169,11 +117,13 @@ event.preventDefault(); event.stopPropagation(); isDragOver = true; + console.log('drag over'); } function handleDragLeave(event: DragEvent) { event.preventDefault(); event.stopPropagation(); isDragOver = false; + console.log('drag leave'); } function handleDrop(event: DragEvent) { event.preventDefault(); @@ -184,27 +134,60 @@ if (file?.type.startsWith('image/')) { processImageFile(file); } + console.log('drop'); } onDestroy(() => { - for (const localUrl of localImageUrls) { - URL.revokeObjectURL(localUrl); - } - editor?.destroy(); }); -
- - + + +{#if isDragOver} + +
+ Drop image to add it to your message +
+
+{/if} + +
+ +
+
+ +