fix(super-editor): persist data-URI images set as SDT preset content (SD-3116)#3516
Merged
caio-pizzol merged 106 commits intoMay 27, 2026
Conversation
Register data URI image sources as DOCX media parts during export so the relationship target resolves correctly. Map the svg+xml MIME subtype to a .svg extension when deriving the media filename.
…processing Detect SVG data URIs with known finite sizes and register them in place during browser-path image handling, skipping the canvas-based resize pipeline that strips vector content. Normalize svg+xml extensions to .svg when generating media filenames so the relationship target matches the stored media key.
…tadata Only emit the empty-inline-SDT placeholder when the resolved metadata describes a structuredContent node, so other inline SDT variants aren't collapsed into a placeholder text run when their content is empty.
Parse data URIs by inspecting the meta header rather than assuming base64 encoding. URL-encoded payloads (e.g. SVG with charset=utf-8) are now decoded as text and written through as-is, while base64 payloads continue through atob/binary conversion. Adds coverage for the non-base64 SVG path in handleBase64 and the browser registration plugin.
Replace the base64-only data URL regex with an allowlist-based validator that accepts URL-encoded SVG payloads while still restricting raster image MIME types to base64. Applies to both inline image runs and field annotation images, and adds tests for the SVG, raster, and non-image cases.
When backspace or delete targets an sdtContentLocked structured-content SDT, collapse the selection to the wrapper boundary instead of letting the keystroke fall through. Prevents the locked inline content from being mutated while keeping the caret in a usable position for the next edit.
Replace the invisible 8px spacer with a full "Click or tap here to enter text" placeholder for both inline and block structured-content controls. The placeholder is layout-only (no document text), styled via CSS pseudo-element, and selected-node highlight inherits the system Highlight color. Wire the new chrome into pointer mapping, caret geometry, and the input manager so clicks on the placeholder land inside the SDT instead of snapping to the wrapper boundary. ArrowLeft from the trailing boundary now re-enters an empty inline SDT, and Backspace/Delete inside an unlocked inline SDT no longer escapes into surrounding text.
Route empty block-SDT paragraphs through paragraphToFlowBlocks so the placeholder run picks up the paragraph's resolved font, size, and color instead of falling back to the document defaults. The painter now applies those run styles to the placeholder span, keeping "Click or tap here to enter text" visually consistent with the surrounding paragraph chrome.
Use `max-content` with `max-width: 130px` instead of stretching block SDT labels to the chrome width, so short labels no longer span the entire block. Inner span now flexes with `min-width: 0` to keep ellipsis behavior.
Stop maxing the measured placeholder width with the fallback — for empty SDTs we now trust the measured value and only fall back when measurement returns zero. Also treat empty-SDT placeholder runs as visible content so chrome geometry (--sd-sdt-chrome-left/width) is emitted for them.
…int modes Remove CSS rules that blanked the ::before content for empty SDT placeholders in viewing and print modes so the placeholder prompt stays visible. Update tests to assert the rules are absent.
…ments fix(sdt): empty SDT placeholder text + cursor/keyboard interactions (SD-3237)
fix(super-editor): repair caret, keyboard, and drag interactions around block SDTs (SD-3237)
…ling-toolbar-when-selection-includes-a-locked-sdt feat(super-editor): disable toolbar mutations inside content-locked SDTs (SD-3274)
4518925
into
luccas/sd-3258-bug-image-resizing-inside-structured-content
5 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Images set as
presetContentinside a structured-content (SDT) field – most commonly SVG signatures and other preset graphics – were being dropped on DOCX export. The root cause was a chain of assumptions about data-URI images that only held for inline-pasted, base64 PNG/JPEG content:base64,payloads, so URL-encoded SVGs (the format produced by most signature widgets) were rejected at paint time;src.split('word/')[1], which returnsundefinedfor adata:URI, so the relationship target was never written and the<a:blip>lost its image reference;This PR consolidates the data-URI policy into
shared/url-validation, threads it through the painter, importer, registration plugin, and DOCX exporter, and adds a roundtrip test covering preset-content insertion → paint → export → re-import.Highlights
Shared data-URI policy (
shared/url-validation)getDataUriMetadata,tryDecodeDataUriText,isValidImageDataUrl, plusIMAGE_DATA_URL_MIME_TYPESandMAX_IMAGE_DATA_URL_LENGTH.image/svg+xml, and only when the percent-encoded text decodes successfully. 10 MB cap is enforced uniformly.Painter (
layout-engine/painters/dom)isValidImageDataUrl. Removed the localVALID_IMAGE_DATA_URLregex /MAX_DATA_URL_LENGTHconstant.Image registration plugin (
super-editor/extensions/image)getDataUriDecodedByteLengthenforces the upload byte cap before in-place registration (handles both base64 and percent-encoded payloads).DOCX exporter (
v3/handlers/wp/helpers/decode-image-node-helpers.js)createMediaTargetForDataUriallocates a stableword/media/image-<hash>.<ext>package path for each data-URI source, caches the mapping per export (params.dataUriMediaTargets), and resolves rId collisions by appending a random suffix when two distinct sources hash to the same path.resolveImageRelationshipId+getImageRelationshipLookup).pm-adapter
resolveNodeSdtMetadatais now generic in its override type; callers likefieldAnnotationNodeToRunget the precise metadata type without a cast.structuredContentmetadata only — other inline SDT variants no longer collapse into a placeholder when empty.Importer/helpers cleanup
helpers.js#dataUriToArrayBufferaccepts URL-encoded SVG payloads.image-dimensions.jsreads SVG intrinsic dimensions from<svg width/height>attributes (base64 and percent-encoded).simpleStringHash/stableHexHashhelpers moved tocore/utilities/hash.js;documentCommentsImporterandhandleBase64now reuse them.mediaHelpers.jscentralizes MIME → extension mapping (getImageExtensionFromMimeType) and re-exports shared metadata helpers with the extension annotation.Tests
sd-3116-structured-content-image-roundtrip.test.js— 453 LOC suite covering preset-content insertion, paint, save, and re-import for both base64 and percent-encoded SVG signatures plus PNG cases.structured-content-commands.test.js— verifiesinsertStructuredContentBlockregisters the preset image in media and rewritessrcto aword/media/...path.imageRegistrationPlugin.browser.test.js— in-place SVG registration, parent-store mirroring, and upload cap.handleBase64.test.js,image-dimensions.test.js,mediaHelpers.test.js,helpers.test.js,decode-image-node-helpers.test.js— coverage for non-base64 SVG paths, malformed payloads, MIME normalization, and exporter target collisions.painters/dom/src/index.test.ts— non-base64 SVG rendering for bothimageruns and field annotations.Risk / compatibility
word/media/image-*.svgparts.editor.options.parentEditor— unchanged for standalone editors.