Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 107 additions & 13 deletions packages/super-editor/src/core/super-converter/exporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -1440,23 +1440,116 @@ function translateImageNode(params, imageSize) {
params.media[`${cleanUrl}_${hash}.${type}`] = src;
}

const inlineAttrs = attrs.originalPadding || {
let inlineAttrs = attrs.originalPadding || {
distT: 0,
distB: 0,
distL: 0,
distR: 0,
};

const anchorElements = [];
let wrapProp = [];

// Handle anchor image export
if (attrs.isAnchor) {
inlineAttrs = {
...inlineAttrs,
simplePos: attrs.originalAttributes?.simplePos,
relativeHeight: 1,
behindDoc: attrs.originalAttributes?.behindDoc,
locked: attrs.originalAttributes?.locked,
layoutInCell: attrs.originalAttributes?.layoutInCell,
allowOverlap: attrs.originalAttributes?.allowOverlap,
};
if (attrs.simplePos) {
anchorElements.push({
name: 'wp:simplePos',
attributes: {
x: 0,
y: 0,
}
});
}

if (attrs.anchorData) {
anchorElements.push({
name: 'wp:positionH',
attributes: {
relativeFrom: attrs.anchorData.hRelativeFrom,
},
...(attrs.marginOffset.left && {
elements: [{
name: 'wp:posOffset',
elements: [{
type: 'text',
text: pixelsToEmu(attrs.marginOffset.left).toString(),
}],
}]
}),
...(attrs.anchorData.alignH && {
elements: [{
name: 'wp:align',
elements: [{
type: 'text',
text: attrs.anchorData.alignH,
}],
}]
})
});
anchorElements.push({
name: 'wp:positionV',
attributes: {
relativeFrom: attrs.anchorData.vRelativeFrom,
},
...(attrs.marginOffset.top && {
elements: [{
name: 'wp:posOffset',
elements: [{
type: 'text',
text: pixelsToEmu(attrs.marginOffset.top).toString(),
}],
}]
}),
...(attrs.anchorData.alignV && {
elements: [{
name: 'wp:align',
elements: [{
type: 'text',
text: attrs.anchorData.alignV,
}],
}]
})
});
}

if (attrs.wrapText) {
wrapProp.push({
name: 'wp:wrapSquare',
attributes: {
wrapText: attrs.wrapText,
}
});
}

if (attrs.wrapTopAndBottom) {
wrapProp.push({
name: 'wp:wrapTopAndBottom',
});
}
}

const drawingXmlns = 'http://schemas.openxmlformats.org/drawingml/2006/main';
const pictureXmlns = 'http://schemas.openxmlformats.org/drawingml/2006/picture';
return wrapTextInRun(

const textNode = wrapTextInRun(
{
name: 'w:drawing',
elements: [
{
name: 'wp:inline',
name: attrs.isAnchor ? 'wp:anchor' : 'wp:inline',
attributes: inlineAttrs,
elements: [
...anchorElements,
{
name: 'wp:extent',
attributes: {
Expand All @@ -1473,12 +1566,13 @@ function translateImageNode(params, imageSize) {
b: 0,
},
},
...wrapProp,
{
name: 'wp:docPr',
attributes: {
id: 0,
name: '',
descr: '',
id: attrs.id || 0,
name: attrs.alt,
descr: attrs.title,
},
},
{
Expand Down Expand Up @@ -1511,9 +1605,8 @@ function translateImageNode(params, imageSize) {
{
name: 'pic:cNvPr',
attributes: {
id: 0,
name: '',
desc: '',
id: attrs.id || 0,
name: attrs.title,
},
},
{
Expand All @@ -1537,12 +1630,8 @@ function translateImageNode(params, imageSize) {
name: 'a:blip',
attributes: {
'r:embed': imageId,
cstate: 'none',
},
},
{
name: 'a:srcRect',
},
{
name: 'a:stretch',
elements: [{ name: 'a:fillRect' }],
Expand Down Expand Up @@ -1579,6 +1668,9 @@ function translateImageNode(params, imageSize) {
attributes: { prst: 'rect' },
elements: [{ name: 'a:avLst' }],
},
{
name: 'a:noFill'
}
],
},
],
Expand All @@ -1593,6 +1685,8 @@ function translateImageNode(params, imageSize) {
},
[],
);

return textNode;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ export const handleDrawingNode = (params) => {

// Some images are identified by wp:anchor
const isAnchor = elements.find((el) => el.name === 'wp:anchor');
if (isAnchor) result = handleImageImport(elements[0], currentFileName, params);
if (isAnchor) {

result = handleImageImport(elements[0], currentFileName, params);
result.attrs.isAnchor = isAnchor;
}

// Others, wp:inline
const inlineImage = elements.find((el) => el.name === 'wp:inline');
Expand Down Expand Up @@ -57,7 +61,7 @@ export function handleImageImport(node, currentFileName, params) {
const shapeURI = "http://schemas.microsoft.com/office/word/2010/wordprocessingShape";
if (!!uri && uri === shapeURI) {
return handleShapeDrawing(params, node, graphicData);
};
}

const picture = graphicData.elements.find((el) => el.name === 'pic:pic');
if (!picture || !picture.elements) return null;
Expand All @@ -68,11 +72,31 @@ export function handleImageImport(node, currentFileName, params) {
const positionHTag = node.elements.find((el) => el.name === 'wp:positionH');
const positionH = positionHTag?.elements.find((el) => el.name === 'wp:posOffset');
const positionHValue = emuToPixels(positionH?.elements[0]?.text);
const hRelativeFrom = positionHTag?.attributes.relativeFrom;
const alignH = positionHTag?.elements.find((el) => el.name === 'wp:align')?.elements[0]?.text;

const positionVTag = node.elements.find((el) => el.name === 'wp:positionV');
const positionV = positionVTag?.elements.find((el) => el.name === 'wp:posOffset');
const positionVValue = emuToPixels(positionV?.elements[0]?.text);

const vRelativeFrom = positionVTag?.attributes.relativeFrom;
const alignV = positionVTag?.elements.find((el) => el.name === 'wp:align')?.elements[0]?.text;

const simplePos = node.elements.find((el) => el.name === 'wp:simplePos');
const wrapSquare = node.elements.find((el) => el.name === 'wp:wrapSquare');
const wrapTopAndBottom = node.elements.find((el) => el.name === 'wp:wrapTopAndBottom');

const docPr = node.elements.find((el) => el.name === 'wp:docPr');

let anchorData = null;
if (hRelativeFrom || alignH || vRelativeFrom || alignV) {
anchorData = {
hRelativeFrom,
vRelativeFrom,
alignH,
alignV,
};
}

const marginOffset = {
left: positionHValue,
top: positionVValue,
Expand All @@ -96,17 +120,31 @@ export function handleImageImport(node, currentFileName, params) {
type: 'image',
attrs: {
src: path,
alt: 'Image',
alt: docPr?.attributes.name || 'Image',
id: docPr?.attributes.id || '',
title: docPr?.attributes.descr || 'Image',
inline: true,
padding,
marginOffset,
size,
anchorData,
...(simplePos && {
simplePos: {
x: simplePos.attributes.x,
y: simplePos.attributes.y,
}
}),
...(wrapSquare && {
wrapText: wrapSquare.attributes.wrapText
}),
wrapTopAndBottom: !!wrapTopAndBottom,
originalPadding: {
distT: attributes['distT'],
distB: attributes['distB'],
distL: attributes['distL'],
distR: attributes['distR'],
},
originalAttributes: node.attributes,
rId: relAttributes['Id'],
},
};
Expand Down
29 changes: 28 additions & 1 deletion packages/super-editor/src/extensions/image/image.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Node, Attribute } from '@core/index.js';
import { ImagePlaceholderPlugin } from './imageHelpers/imagePlaceholderPlugin.js';
import { ImagePositionPlugin } from './imageHelpers/imagePositionPlugin.js';

export const Image = Node.create({
name: 'image',
Expand Down Expand Up @@ -39,6 +40,8 @@ export const Image = Node.create({
alt: {
default: null,
},

id: { rendered: false },

title: {
default: null,
Expand All @@ -53,6 +56,17 @@ export const Image = Node.create({
default: null,
rendered: false,
},
originalAttributes: { rendered: false },
wrapTopAndBottom: { rendered: false },

anchorData: {
default: null,
rendered: false,
},

isAnchor: { rendered: false },
simplePos: { rendered: false },
wrapText: { rendered: false },

size: {
default: {},
Expand All @@ -64,6 +78,19 @@ export const Image = Node.create({
return { style };
},
},

padding: {
default: {},
renderDOM: ({ padding, marginOffset }) => {
let { left = 0, top = 0, bottom = 0, right = 0 } = padding ?? {};
let style = '';
if (left && !marginOffset?.left) style += `margin-left: ${left}px;`;
if (top && !marginOffset?.top) style += `margin-top: ${top}px;`;
if (bottom) style += `margin-bottom: ${bottom}px;`;
if (right) style += `margin-right: ${right}px;`;
return { style };
},
},

marginOffset: {
default: {},
Expand Down Expand Up @@ -113,6 +140,6 @@ export const Image = Node.create({
},

addPmPlugins() {
return [ImagePlaceholderPlugin()];
return [ImagePlaceholderPlugin(), ImagePositionPlugin({editor: this.editor })];
},
});
Loading
Loading