From c9fe086ff6dcee7e992c666b26b8db45390ecc24 Mon Sep 17 00:00:00 2001 From: Swathi Date: Tue, 21 Apr 2026 09:52:29 +0530 Subject: [PATCH] fix(web): real upload button + visible preview-toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two usability issues on the rich-notes editor: 1. No way to upload an image - The toolbar used EasyMDE's 'image' item, which only inserts a literal '![alt](url)' placeholder line. The 'upload-image' item is the one that fires a file-picker and calls imageUploadFunction. - Swapped 'image' → 'upload-image' (only when an upload URL is wired; falls back to 'image' otherwise). - Also set imageAccept + imageMaxSize explicitly so paste / drag-drop match the server-side allowlist (png/jpg/webp/gif, 10 MB). 2. Preview mode felt like a one-way door - EasyMDE disables the rest of the toolbar during preview and the user couldn't see a clear way back. - Defined a custom 'preview' toolbar item with a descriptive title ('Toggle preview (click again to go back)') and the no-disable flag so the button stays fully interactive while other buttons dim. - CSS: disabled-for-preview buttons now have opacity 0.35 (visibly greyed, not vanished) so the user still sees the toolbar. - Active-state styling bumped: filled accent background instead of just an accent-coloured icon, so toggles read at a glance. - Added 'fullscreen' to the toolbar for distraction-free writing. Paste + drag-drop upload continue to work via EasyMDE's own handlers (they fire imageUploadFunction whenever uploadImage is true). Tests unchanged; 46/46 still green. --- khata/web/static/editor.js | 43 +++++++++++++++++++++++++------------- khata/web/static/style.css | 13 +++++++++--- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/khata/web/static/editor.js b/khata/web/static/editor.js index 4ffb858..862e2b0 100644 --- a/khata/web/static/editor.js +++ b/khata/web/static/editor.js @@ -2,12 +2,15 @@ (function () { "use strict"; - function uploadImage(uploadUrl, file, onSuccess, onError) { + function upload(uploadUrl, file, onSuccess, onError) { const fd = new FormData(); fd.append("file", file); fetch(uploadUrl, { method: "POST", body: fd }) .then(async (r) => { - if (!r.ok) throw new Error(`Upload failed: ${r.status}`); + if (!r.ok) { + const txt = await r.text().catch(() => ""); + throw new Error(`upload ${r.status}: ${txt.slice(0, 120)}`); + } return r.json(); }) .then((j) => onSuccess((j && j.url) || (j && j.data && j.data.filePath))) @@ -19,30 +22,42 @@ textarea.dataset.khataMounted = "1"; const uploadUrl = textarea.dataset.upload; + const hasUpload = !!uploadUrl; const easy = new EasyMDE({ element: textarea, autoDownloadFontAwesome: false, spellChecker: false, status: ["lines", "words"], - minHeight: "160px", - uploadImage: !!uploadUrl, - imageUploadFunction: uploadUrl - ? (file, onSuccess, onError) => uploadImage(uploadUrl, file, onSuccess, onError) - : undefined, + minHeight: "200px", + // Enables paste + drag-drop + the 'upload-image' toolbar button. + uploadImage: hasUpload, + imageAccept: "image/png, image/jpeg, image/webp, image/gif", + imageMaxSize: 10 * 1024 * 1024, imagePathAbsolute: true, + imageUploadFunction: hasUpload + ? (file, onSuccess, onError) => upload(uploadUrl, file, onSuccess, onError) + : undefined, + // Toolbar items must use 'upload-image' (not 'image') to trigger the + // file-picker + imageUploadFunction flow. 'image' only inserts a stub + // `![alt](url)` line, which was the source of "can't upload" confusion. toolbar: [ "bold", "italic", "heading", "|", "quote", "unordered-list", "ordered-list", "|", - "link", "image", "code", "|", - "preview", "side-by-side", "|", + "link", hasUpload ? "upload-image" : "image", "code", "|", + { + // Custom preview-toggle button with an explicit title so users can + // see how to come *back* from preview mode. + name: "preview", + action: EasyMDE.togglePreview, + className: "fa fa-eye no-disable", + title: "Toggle preview (click again to go back)", + noDisable: true, + }, + "side-by-side", "fullscreen", "|", "guide", ], - previewRender: (text) => { - // Use marked if available — otherwise EasyMDE's default. marked ships - // inside EasyMDE. - return easy.markdown(text); - }, + previewRender: (text) => easy.markdown(text), }); // Keep the native textarea's value in sync so HTMX form submission works. diff --git a/khata/web/static/style.css b/khata/web/static/style.css index 089913b..d192d7d 100644 --- a/khata/web/static/style.css +++ b/khata/web/static/style.css @@ -436,9 +436,16 @@ h2 { } .note-block .editor-toolbar button.active, .note-block .editor-toolbar button.fa.active { - background: var(--surface); - color: var(--accent) !important; - border-color: var(--border); + background: var(--accent); + color: var(--accent-ink) !important; + border-color: var(--accent); +} +/* Preview/fullscreen dim other buttons. Keep them readable so the user can + still see the preview button to click it again and return. */ +.note-block .editor-toolbar.disabled-for-preview button:not(.no-disable), +.note-block .editor-toolbar button[disabled] { + opacity: 0.35; + cursor: not-allowed; } .note-block .editor-toolbar i.separator { border-color: var(--border); }