From c37c1e67aff7225bbeb7e57936250c9a61ffb5da Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 26 May 2026 10:09:36 +0000 Subject: [PATCH] Resizable image support --- src/frontend/package.json | 1 + .../src/components/editors/NotesEditor.tsx | 64 +++++-------------- src/frontend/yarn.lock | 5 ++ 3 files changed, 23 insertions(+), 47 deletions(-) diff --git a/src/frontend/package.json b/src/frontend/package.json index e13c65ac2f..7dca970377 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -112,6 +112,7 @@ "react-window": "1.8.11", "recharts": "^3.1.2", "styled-components": "^6.1.14", + "tiptap-extension-resizable-image": "^2.1.0", "undici": "^6.24.0", "zustand": "^5.0.8" }, diff --git a/src/frontend/src/components/editors/NotesEditor.tsx b/src/frontend/src/components/editors/NotesEditor.tsx index 36bc96e089..a39fb8ddb2 100644 --- a/src/frontend/src/components/editors/NotesEditor.tsx +++ b/src/frontend/src/components/editors/NotesEditor.tsx @@ -4,12 +4,13 @@ import '@mantine/tiptap/styles.css'; import { useHotkeys } from '@mantine/hooks'; import { notifications } from '@mantine/notifications'; import { useQuery } from '@tanstack/react-query'; -import Image from '@tiptap/extension-image'; import { useEditor } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import DOMPurify from 'dompurify'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; +import { ResizableImage } from 'tiptap-extension-resizable-image'; +import 'tiptap-extension-resizable-image/styles.css'; import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import type { ModelType } from '@lib/enums/ModelType'; @@ -143,51 +144,16 @@ export default function NotesEditor({ StarterKit.configure({ link: { openOnClick: false } }), - Image + ResizableImage.configure({ + // Paste and drop are handled by the extension's built-in plugin + onUpload: async (file: File) => { + const src = await uploadFileRef.current(file); + return { src, 'data-keep-ratio': true }; + } + }) ], content: '', - onUpdate: () => setIsDirty(true), - editorProps: { - handleDrop: (view, event) => { - const files = event.dataTransfer?.files; - if (!files?.length) return false; - const imageFiles = Array.from(files).filter((f) => - f.type.startsWith('image/') - ); - if (!imageFiles.length) return false; - - event.preventDefault(); - const coords = view.posAtCoords({ - left: event.clientX, - top: event.clientY - }); - imageFiles.forEach((file) => { - uploadFileRef.current(file).then((url) => { - if (!url || !coords) return; - const node = view.state.schema.nodes.image?.create({ src: url }); - if (node) view.dispatch(view.state.tr.insert(coords.pos, node)); - }); - }); - return true; - }, - handlePaste: (view, event) => { - const files = event.clipboardData?.files; - if (!files?.length) return false; - const imageFiles = Array.from(files).filter((f) => - f.type.startsWith('image/') - ); - if (!imageFiles.length) return false; - - imageFiles.forEach((file) => { - uploadFileRef.current(file).then((url) => { - if (!url) return; - const node = view.state.schema.nodes.image?.create({ src: url }); - if (node) view.dispatch(view.state.tr.replaceSelectionWith(node)); - }); - }); - return true; - } - } + onUpdate: () => setIsDirty(true) }); // Fetch the available notes for the given model type and ID @@ -360,8 +326,12 @@ export default function NotesEditor({ async (file: File | null) => { if (!file || !editor) return; try { - const url = await uploadFile(file); - editor.chain().focus().setImage({ src: url }).run(); + const src = await uploadFile(file); + editor + .chain() + .focus() + .setResizableImage({ src, 'data-keep-ratio': true }) + .run(); } catch { notifications.show({ title: t`Error`, @@ -514,7 +484,7 @@ export default function NotesEditor({ color='green' leftSection={} onClick={createNote.open} - disabled={!canEdit || isDirty} + // disabled={!canEdit || isDirty} > {t`Add Note`} diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock index 50ade990e7..f091761fe7 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -5482,6 +5482,11 @@ tinyglobby@^0.2.13: fdir "^6.5.0" picomatch "^4.0.4" +tiptap-extension-resizable-image@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiptap-extension-resizable-image/-/tiptap-extension-resizable-image-2.1.0.tgz#26573bcddfeeb06067be6f7c5ace2573b14661b4" + integrity sha512-uvdY/mTHMZ3EF3Z+b61y1htOUg8oF4TiepNFF1pYRg/KITYoYSteF5/L2/qtlZF9SMrzzwnrtRFQFzn/qBj7qQ== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"