learningequality / learningequality/studio
Auto-upload inline data: URI images on paste
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 191
- Forks
- 307
- Avg merge
- 5d 6h
- Merged PRs (30d)
- 10
Description
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.
Overview
When users paste HTML containing inline data: URI images, the current behavior (post-#5897) strips them entirely. This task instead converts each data: URI on paste into an uploaded checksum-backed image via imageProcessor.processFile, matching the shape of every other image in the editor.
Complexity: Medium
Target branch: hotfixes
Context
- All other images in the editor are checksum-backed via
imageProcessor.processFile(toolbar insert, drag-and-drop). This task adds paste as a third entry point. - Paste is sync (ProseMirror's
transformPastedHTML); uploads are async. Needs a placeholder-and-resolve pattern.
The Change
Currently transformPastedHTML strips every <img>. Replace with scheme-based routing:
data:URIs: keep the node, marked as pending.- All other schemes: continue to strip.
An async resolver walks the doc post-paste, decodes each pending data: URI to a File, runs imageProcessor.processFile, and on success swaps the node's attributes: src to the returned storageUrl, permanentSrc to ${checksum}.${file_format}, plus width/height from the returned metadata. On failure, remove the pending node silently.
To keep pending state out of saved markdown, getMarkdown() returns null whenever any image carries the pending marker. Both TipTapEditor.vue watchers short-circuit on null. The serializer's case 'image' branch also returns '' for pending nodes as defense in depth.
How to Get There
- Open an exercise's TipTap editor.
- Paste HTML containing an inline
<img src="data:image/png;base64,...">(e.g. from a tool that inlines images, or a hand-crafted snippet). - Current behavior (post-#5897): image silently removed.
- After this change: image appears in the editor and persists as a checksum-backed image after publish.
Out of Scope
- Remote URL fetch/upload.
- User-visible affordance when an image fails to upload.
- Refactoring the resolver into a generic worker — keep it editor-local.
Acceptance Criteria
General
-
transformPastedHTMLkeepsdata:URI imgs (with a pending marker); strips all other schemes. -
imageextension gainspendingIdandpendingSrcattrs. - A composable watches editor state, finds pending nodes by
pendingId(de-duped), decodespendingSrc→File, callsimageProcessor.processFile. - Success swaps attrs:
src← storageUrl,permanentSrc←${checksum}.${file_format},width/heightfrom metadata, pending attrs cleared. - Failure removes the pending node silently.
-
getMarkdown()returnsnullwhile any image carriespendingId. - Both
TipTapEditor.vuewatchers short-circuit onnullfromgetMarkdown(). - Serializer's
case 'image'returns''for pending nodes (defense in depth).
Testing
- Unit tests cover each behavior above.
- Pre-#5897 sanitizer behavior and idempotency still hold.
References
- #5895 (the original bug that motivated the strip-only fix in #5897).
- #5897 (the strip-only PR; this task replaces its
data:-URI strip branch with upload-and-keep). imageProcessor.processFileatfrontend/shared/views/TipTapEditor/TipTapEditor/services/imageService.js— the existing upload flow used by toolbar insert and drag-and-drop.
AI usage
Used Claude (Opus 4.7) to draft this issue from the spec written during #5897's brainstorming. I reviewed each section and adjusted scope and phrasing.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the existing upload flow in frontend/shared/views/TipTapEditor/TipTapEditor/services/imageService.js, then inspect the TipTap editor, image extension, getMarkdown(), serializer, and both TipTapEditor.vue watchers. Trace paste handling and the existing toolbar and drag-and-drop paths before implementing the pending-image flow. Done means data: images upload and resolve, failures remove them, pending images are excluded from markdown, and unit tests cover the listed behaviors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100