learningequality / learningequality/studio

Auto-upload inline data: URI images on paste

Open
#5,898 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

DEV: frontend P2 - normal
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
  1. Open an exercise's TipTap editor.
  2. Paste HTML containing an inline <img src="data:image/png;base64,..."> (e.g. from a tool that inlines images, or a hand-crafted snippet).
  3. Current behavior (post-#5897): image silently removed.
  4. 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
  • transformPastedHTML keeps data: URI imgs (with a pending marker); strips all other schemes.
  • image extension gains pendingId and pendingSrc attrs.
  • A composable watches editor state, finds pending nodes by pendingId (de-duped), decodes pendingSrcFile, calls imageProcessor.processFile.
  • Success swaps attrs: src ← storageUrl, permanentSrc${checksum}.${file_format}, width/height from metadata, pending attrs cleared.
  • Failure removes the pending node silently.
  • getMarkdown() returns null while any image carries pendingId.
  • Both TipTapEditor.vue watchers short-circuit on null from getMarkdown().
  • 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.processFile at frontend/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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.