Skip to content

Auto-upload inline data: URI images on paste #5898

@rtibbles

Description

@rtibbles

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-Strip <img> tags from pasted HTML in TipTap editor #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

References

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions