Jul 28 - Update text-editor to allow image resize

This commit is contained in:
2026-07-28 14:55:12 -04:00
parent ea9458395d
commit 4bb345da39
7 changed files with 622 additions and 26 deletions
+41
View File
@@ -119,11 +119,52 @@ shows only `is_published` topics (sections with no published topics are hidden).
shows the editor). If Quill fails to load, the textarea stays usable and a save
never wipes the body. The submit handler keeps the body when it contains an
`img`/`table` even though `getText()` is empty for embed-only content.
- **Paste / drop upload:** `static/js/image-upload.js` (ours). Quill 2's built-in
`uploader` module base64s a pasted/dropped file into the body — which
`sanitize_html()` then strips on save (bleach blocks `data:` on img src), so
the image looked fine in the editor and vanished on save. We override
`modules.uploader` (`{mimetypes, handler}`) so paste AND drag-drop both POST to
`/admin/upload` and embed the returned `/static/uploads/...` path. The toolbar
image button now goes through the same `insertFiles()`, so all three routes
behave identically.
- Quill's uploader only sees pasted *files*. HTML pasted from Word/Docs carries
base64 `<img>` markup through the clipboard instead, so `watch(quill)` sweeps
`img[src^="data:image/"]` after a user edit and rehosts each one (delete +
re-insert the embed, preserving any `width`). Re-entrancy is guarded by a
`busy` flag — our own edits fire `text-change` too. A failed rehost tags the
node `data-upload-failed` so it isn't retried forever, and says plainly that
the image won't be saved (we don't silently delete admin content).
- Feedback is a non-blocking `.jqc-toast` (an `alert()` mid-paste interrupts
typing). Client-side checks mirror the server: MIME allowlist + 8 MB cap.
- Remote `http(s)` images pasted from a web page are NOT rehosted — bleach
allows those URLs, so they render, but they hotlink the original server.
- **Images:** the custom `#editor-toolbar` has a `ql-image` button. Images upload
via `POST /admin/upload` (`login_required`, CSRF via `X-CSRFToken` header):
extension allowlist + magic-byte sniff (`_sniff_image`, SVG excluded), 8 MB cap,
saved as a random `uuid4().hex.<ext>` under `static/uploads/`, returns `{url}`;
the handler `insertEmbed`s it (no base64 → DB stays small).
- **Image resize:** `static/js/image-resize.js` — ours, not vendored (Quill 2 has
no resize UI and the third-party modules target Quill 1). Click an image in the
editor → fixed-positioned frame with 4 corner handles, S/M/L/Full presets
(25/50/75/100 % of the containing block, so it also works inside a table cell)
and a Reset. Loaded after Quill in `topic_form.html` and initialised behind a
`window.JQCImageResize` guard, so a failed load just means no resize UI.
- **Size is the `width` ATTRIBUTE, never inline `style`** — `style` is not on
the sanitizer's img allowlist, `width` is, so the size survives the save.
`height` is removed on every change; the public CSS (`.prose img{max-width:
100%;height:auto}`) keeps the aspect ratio and still shrinks on a phone.
- **Commit via `quill.formatText(i, 1, {width: v}, 'user')` — pass a formats
OBJECT, not `(name, value)`.** Quill's argument overload reads a `null`
`value` as the `source` argument, so the `(name, value)` form silently
no-ops when clearing the width (this is exactly what broke Reset). The width
round-trips through `clipboard.convert` on load because Quill's Image blot
lists `width` in its `formats()`.
- A drag writes the attribute directly for live preview and commits once on
release → one undo step per drag, not dozens. The preset bar calls
`preventDefault()` on mousedown so it never steals the caret (otherwise
Ctrl+Z after a resize stops reaching Quill), and it flips above the image /
clamps to the viewport — the frame is `position:fixed`, so a bar left below
the fold could not be scrolled to.
- **Tables:** `quill-table-better` 1.2.3 vendored (`static/vendor/quill-table-better.js`
+ `.css`, UMD → reads global `Quill`, exposes `QuillTableBetter`; self-contained,
no CDN/CSP issues). Registered as `modules/table-better`; Quill 2's basic