Skip to content

Commit a9af47e

Browse files
authored
fix: images are missing for the document in edit mode (#831)
* fix: images are missing for the document in edit mode * test: update image import/export tests * fix: remove regex
1 parent 1ed6570 commit a9af47e

9 files changed

Lines changed: 94 additions & 13 deletions

File tree

packages/super-editor/src/core/DocxZipper.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,15 @@ class DocxZipper {
4848
name: zipEntry.name,
4949
content,
5050
});
51-
} else if (zipEntry.name.startsWith('word/media') && zipEntry.name !== 'word/media/') {
51+
} else if (
52+
(zipEntry.name.startsWith('word/media') && zipEntry.name !== 'word/media/') ||
53+
(zipEntry.name.startsWith('media') && zipEntry.name !== 'media/')
54+
) {
5255
// If we are in node, we need to convert the buffer to base64
5356
if (isNode) {
5457
const buffer = await zipEntry.async('nodebuffer');
5558
const fileBase64 = buffer.toString('base64');
56-
mediaObjects[zipEntry.name] = fileBase64;
59+
this.mediaFiles[zipEntry.name] = fileBase64;
5760
}
5861

5962
// If we are in the browser, we can use the base64 directly
@@ -215,9 +218,9 @@ class DocxZipper {
215218
zip.file(key, content);
216219
});
217220

218-
Object.keys(media).forEach((name) => {
219-
const binaryData = Buffer.from(media[name], 'base64');
220-
zip.file(`word/media/${name}`, binaryData);
221+
Object.keys(media).forEach((path) => {
222+
const binaryData = Buffer.from(media[path], 'base64');
223+
zip.file(path, binaryData);
221224
});
222225

223226
// Export font files
@@ -252,8 +255,8 @@ class DocxZipper {
252255
unzippedOriginalDocx.file(key, updatedDocs[key]);
253256
});
254257

255-
Object.keys(media).forEach((name) => {
256-
unzippedOriginalDocx.file(`word/media/${name}`, media[name]);
258+
Object.keys(media).forEach((path) => {
259+
unzippedOriginalDocx.file(path, media[path]);
257260
});
258261

259262
await this.updateContentTypes(unzippedOriginalDocx, media);

packages/super-editor/src/core/super-converter/SuperConverter.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -712,8 +712,7 @@ class SuperConverter {
712712
const processedData = {};
713713
for (const filePath in media) {
714714
if (typeof media[filePath] !== 'string') continue;
715-
const name = filePath.split('/').pop();
716-
processedData[name] = await getArrayBufferFromUrl(media[filePath], editor.options.isHeadless);
715+
processedData[filePath] = await getArrayBufferFromUrl(media[filePath], editor.options.isHeadless);
717716
}
718717

719718
this.convertedXml.media = {

packages/super-editor/src/core/super-converter/exporter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1556,7 +1556,7 @@ function translateImageNode(params, imageSize) {
15561556

15571557
const src = attrs.src || attrs.imageSrc;
15581558
const { originalWidth, originalHeight } = getPngDimensions(src);
1559-
const imageName = params.node.type === 'image' ? src?.split('word/media/')[1] : attrs.fieldId?.replace('-', '_');
1559+
const imageName = params.node.type === 'image' ? src.split('/').pop() : attrs.fieldId?.replace('-', '_');
15601560

15611561
let size = attrs.size
15621562
? {

packages/super-editor/src/core/super-converter/v2/importer/imageImporter.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,12 @@ export function handleImageImport(node, currentFileName, params) {
116116
if (!rel) return null;
117117

118118
const { attributes: relAttributes } = rel;
119+
const targetPath = relAttributes['Target'];
119120

120-
const path = `word/${relAttributes['Target']}`;
121+
let path = `word/${targetPath}`;
122+
123+
// Some images may appear out of the word folder
124+
if (targetPath.startsWith('/word') || targetPath.startsWith('/media')) path = targetPath.substring(1);
121125

122126
return {
123127
type: 'image',

packages/super-editor/src/dev/components/DeveloperPlayground.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import '@/style.css';
33
import '@harbour-enterprises/common/styles/common-styles.css';
44
55
import { ref, shallowRef, computed, onMounted } from 'vue';
6+
import { NMessageProvider } from 'naive-ui';
67
import { SuperEditor } from '@/index.js';
78
import { getFileObject } from '@harbour-enterprises/common/helpers/get-file-object';
89
import { DOCX } from '@harbour-enterprises/common';
@@ -158,7 +159,9 @@ onMounted(async () => {
158159
</div>
159160
160161
<div class="dev-app__content" v-if="currentFile">
161-
<SuperEditor :file-source="currentFile" :options="editorOptions" />
162+
<n-message-provider>
163+
<SuperEditor :file-source="currentFile" :options="editorOptions" />
164+
</n-message-provider>
162165
</div>
163166
</div>
164167
</div>
125 KB
Binary file not shown.

packages/super-editor/src/tests/export/export-helpers/export-helpers.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,37 @@ export const getExportedResult = async (name, comments = []) => {
9696
return result;
9797
};
9898

99+
export const getExportMediaFiles = async (name, comments = []) => {
100+
const buffer = await getTestDataAsBuffer(name);
101+
const [docx, media, mediaFiles, fonts] = await Editor.loadXmlData(buffer, true);
102+
103+
const editor = new Editor({
104+
isHeadless: true,
105+
extensions: getStarterExtensions(),
106+
documentId: 'test-doc',
107+
content: docx,
108+
mode: 'docx',
109+
media,
110+
mediaFiles,
111+
fonts,
112+
annotations: true,
113+
});
114+
115+
const json = editor.getUpdatedJson();
116+
await editor.converter.exportToDocx(
117+
json,
118+
editor.schema,
119+
editor.storage.image.media,
120+
true,
121+
'external',
122+
comments,
123+
editor,
124+
false,
125+
null,
126+
);
127+
return editor.converter.addedMedia;
128+
};
129+
99130
export const getExportedResultForAnnotations = async (isFinalDoc) => {
100131
const buffer = await getTestDataAsBuffer('annotations_import.docx');
101132
const [docx, media, mediaFiles, fonts] = await Editor.loadXmlData(buffer, true);

packages/super-editor/src/tests/export/imageNodeExporter.test.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getExportedResult } from './export-helpers/index';
1+
import { getExportedResult, getExportMediaFiles } from './export-helpers/index';
22

33
describe('ImageNodeExporter', async () => {
44
window.URL.createObjectURL = vi.fn().mockImplementation((file) => {
@@ -68,3 +68,17 @@ describe('ImageNodeExporter anchor image', async () => {
6868
expect(anchorNode.elements[5].attributes.wrapText).toBe('bothSides');
6969
});
7070
});
71+
72+
describe('ImageNodeExporter images with absolute path', async () => {
73+
window.URL.createObjectURL = vi.fn().mockImplementation((file) => {
74+
return file.name;
75+
});
76+
77+
const fileName = 'image-out-of-folder.docx';
78+
const result = await getExportMediaFiles(fileName);
79+
80+
it('exports image with absolute path correctly', async () => {
81+
expect(result).toHaveProperty('word/media/image1.jpeg');
82+
expect(result).toHaveProperty('media/image.png');
83+
});
84+
});

packages/super-editor/src/tests/import/imageImporter.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,31 @@ describe('ImageNodeImporter', () => {
5454
expect(anchorData).toHaveProperty('alignH', 'left');
5555
expect(anchorData).toHaveProperty('alignV', 'top');
5656
});
57+
58+
it('imports image with absolute path in Target correctly', async () => {
59+
const dataName = 'image-out-of-folder.docx';
60+
const docx = await getTestDataByFileName(dataName);
61+
const documentXml = docx['word/document.xml'];
62+
63+
const doc = documentXml.elements[0];
64+
const body = doc.elements[0];
65+
const content = body.elements;
66+
console.log(content[6].elements[2]);
67+
68+
const { nodes } = handleParagraphNode({ nodes: [content[0]], docx, nodeListHandler: defaultNodeListHandler() });
69+
70+
let paragraphNode = nodes[0];
71+
let drawingNode = paragraphNode.content[0];
72+
const { attrs } = drawingNode;
73+
expect(attrs.src).toBe('media/image.png');
74+
75+
const { nodes: nodes1 } = handleParagraphNode({
76+
nodes: [content[5]],
77+
docx,
78+
nodeListHandler: defaultNodeListHandler(),
79+
});
80+
paragraphNode = nodes1[0];
81+
drawingNode = paragraphNode.content[1];
82+
expect(drawingNode.attrs.src).toBe('word/media/image1.jpeg');
83+
});
5784
});

0 commit comments

Comments
 (0)