Skip to content

Commit 5f95114

Browse files
authored
fix: image rendering (#2796)
* fix: image rendering * chore: update test case
1 parent 5cb9691 commit 5f95114

5 files changed

Lines changed: 97 additions & 9 deletions

File tree

packages/layout-engine/pm-adapter/src/utilities.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,36 @@ describe('Media Utilities', () => {
752752
expect(result[0].src).toBe('data:image/png;base64,base64data');
753753
});
754754

755+
it('hydrates word/media src from media storage key', () => {
756+
const blocks: FlowBlock[] = [
757+
{
758+
kind: 'image',
759+
id: '1',
760+
src: 'word/media/image.png',
761+
runs: [],
762+
},
763+
];
764+
const mediaFiles = { 'media/image.png': 'base64data' };
765+
766+
const result = hydrateImageBlocks(blocks, mediaFiles);
767+
expect(result[0].src).toBe('data:image/png;base64,base64data');
768+
});
769+
770+
it('hydrates media src from word/media storage key', () => {
771+
const blocks: FlowBlock[] = [
772+
{
773+
kind: 'image',
774+
id: '1',
775+
src: 'media/image.png',
776+
runs: [],
777+
},
778+
];
779+
const mediaFiles = { 'word/media/image.png': 'base64data' };
780+
781+
const result = hydrateImageBlocks(blocks, mediaFiles);
782+
expect(result[0].src).toBe('data:image/png;base64,base64data');
783+
});
784+
755785
it('uses rId fallback when direct path does not match', () => {
756786
const blocks: FlowBlock[] = [
757787
{

packages/layout-engine/pm-adapter/src/utilities.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,9 +1001,18 @@ export function hydrateImageBlocks(blocks: FlowBlock[], mediaFiles?: Record<stri
10011001
return undefined;
10021002
}
10031003

1004+
const addPathCandidates = (value?: string) => {
1005+
if (!value) return [] as string[];
1006+
const normalized = normalizeMediaKey(value);
1007+
if (!normalized) return [value];
1008+
const withoutWordPrefix = normalized.startsWith('word/') ? normalized.slice(5) : normalized;
1009+
const withWordPrefix = normalized.startsWith('word/') ? normalized : `word/${normalized}`;
1010+
return [value, normalized, withoutWordPrefix, withWordPrefix];
1011+
};
1012+
10041013
const candidates = new Set<string>();
1005-
candidates.add(src);
1006-
if (attrSrc) candidates.add(attrSrc);
1014+
addPathCandidates(src).forEach((candidate) => candidates.add(candidate));
1015+
if (attrSrc) addPathCandidates(attrSrc).forEach((candidate) => candidates.add(candidate));
10071016
if (relId) {
10081017
const inferredExt = extension ?? inferExtensionFromPath(src) ?? 'jpeg';
10091018
candidates.add(`word/media/${relId}.${inferredExt}`);

packages/super-editor/src/editors/v1/core/super-converter/v3/handlers/mc/altermateContent/alternate-content-translator.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,11 @@ export function selectAlternateContentElements(node) {
116116
const requiresAttr = choice?.attributes?.Requires || choice?.attributes?.requires;
117117
if (!requiresAttr) return false;
118118

119-
return requiresAttr
120-
.split(/\s+/)
121-
.filter(Boolean)
122-
.some((namespace) => SUPPORTED_ALTERNATE_CONTENT_REQUIRES.has(namespace));
119+
const requiredNamespaces = requiresAttr.split(/\s+/).filter(Boolean);
120+
if (requiredNamespaces.length === 0) return false;
121+
122+
// ECMA-376 mc:Choice requires ALL listed namespaces to be understood.
123+
return requiredNamespaces.every((namespace) => SUPPORTED_ALTERNATE_CONTENT_REQUIRES.has(namespace));
123124
});
124125

125126
const branch = supportedChoice || fallback || choices[0] || null;

packages/super-editor/src/editors/v1/core/super-converter/v3/handlers/mc/altermateContent/alternate-content-translator.test.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,10 @@ describe('selectAlternateContentElements', () => {
178178
expect(SUPPORTED_ALTERNATE_CONTENT_REQUIRES.has('w16sdtfl')).toBe(true);
179179
});
180180

181-
it('selects supported choice when namespace matches set', () => {
181+
it('selects supported choice when all required namespaces are supported', () => {
182182
const choice = {
183183
name: 'mc:Choice',
184-
attributes: { Requires: 'foo wps bar' },
184+
attributes: { Requires: 'wps w14' },
185185
elements: [{ name: 'w:r' }],
186186
};
187187
const node = {
@@ -193,6 +193,22 @@ describe('selectAlternateContentElements', () => {
193193
expect(elements).toEqual(choice.elements);
194194
});
195195

196+
it('falls back when mc:Choice requires an unsupported namespace', () => {
197+
const fallback = { name: 'mc:Fallback', elements: [{ name: 'w:p' }] };
198+
const mixedChoice = {
199+
name: 'mc:Choice',
200+
attributes: { Requires: 'wps unknownNs' },
201+
elements: [{ name: 'w:r' }],
202+
};
203+
const node = {
204+
elements: [mixedChoice, fallback],
205+
};
206+
207+
const { branch, elements } = selectAlternateContentElements(node);
208+
expect(branch).toBe(fallback);
209+
expect(elements).toEqual(fallback.elements);
210+
});
211+
196212
it('returns fallback when no choice is supported', () => {
197213
const fallback = { name: 'mc:Fallback', elements: [{ name: 'w:p' }] };
198214
const node = {

packages/super-editor/src/editors/v1/tests/import/alternateChoiceImporter.test.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ describe('alternateChoiceHandler', () => {
194194
expect(text).toBe('cell choice');
195195
});
196196

197-
it('selects a supported choice when Requires contains multiple namespaces', () => {
197+
it('falls back when Requires lists any unsupported namespace (all must be supported)', () => {
198198
const altNode = {
199199
type: 'element',
200200
name: 'mc:AlternateContent',
@@ -203,6 +203,38 @@ describe('alternateChoiceHandler', () => {
203203
type: 'element',
204204
name: 'mc:Choice',
205205
attributes: { Requires: 'foo wps bar' },
206+
elements: [createTextRun('choice run')],
207+
},
208+
{
209+
type: 'element',
210+
name: 'mc:Fallback',
211+
elements: [createTextRun('fallback run')],
212+
},
213+
],
214+
};
215+
216+
const { handlerSpy, result } = callHandler([altNode]);
217+
218+
expect(result.consumed).toBe(1);
219+
expect(handlerSpy).toHaveBeenCalledTimes(1);
220+
const handledCall = handlerSpy.mock.calls[0][0];
221+
const handledNodes = handledCall?.nodes ?? [];
222+
expect(handledNodes).toHaveLength(1);
223+
const run = handledNodes[0];
224+
const textElement = run.elements?.find((el) => el.name === 'w:t');
225+
const textNode = textElement?.elements?.find((el) => el.type === 'text');
226+
expect(textNode?.text).toBe('fallback run');
227+
});
228+
229+
it('selects choice when every Requires namespace is supported', () => {
230+
const altNode = {
231+
type: 'element',
232+
name: 'mc:AlternateContent',
233+
elements: [
234+
{
235+
type: 'element',
236+
name: 'mc:Choice',
237+
attributes: { Requires: 'wps w14' },
206238
elements: [createTextRun('supported choice')],
207239
},
208240
{

0 commit comments

Comments
 (0)