Skip to content

Commit 00a3095

Browse files
authored
Merge pull request #491 from Harbour-Enterprises/har-9435_anchor-images
HAR-9435 Anchor images
2 parents 2deda3d + 6dced4a commit 00a3095

9 files changed

Lines changed: 366 additions & 31 deletions

File tree

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

Lines changed: 107 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,23 +1445,116 @@ function translateImageNode(params, imageSize) {
14451445
params.media[`${cleanUrl}_${hash}.${type}`] = src;
14461446
}
14471447

1448-
const inlineAttrs = attrs.originalPadding || {
1448+
let inlineAttrs = attrs.originalPadding || {
14491449
distT: 0,
14501450
distB: 0,
14511451
distL: 0,
14521452
distR: 0,
14531453
};
14541454

1455+
const anchorElements = [];
1456+
let wrapProp = [];
1457+
1458+
// Handle anchor image export
1459+
if (attrs.isAnchor) {
1460+
inlineAttrs = {
1461+
...inlineAttrs,
1462+
simplePos: attrs.originalAttributes?.simplePos,
1463+
relativeHeight: 1,
1464+
behindDoc: attrs.originalAttributes?.behindDoc,
1465+
locked: attrs.originalAttributes?.locked,
1466+
layoutInCell: attrs.originalAttributes?.layoutInCell,
1467+
allowOverlap: attrs.originalAttributes?.allowOverlap,
1468+
};
1469+
if (attrs.simplePos) {
1470+
anchorElements.push({
1471+
name: 'wp:simplePos',
1472+
attributes: {
1473+
x: 0,
1474+
y: 0,
1475+
}
1476+
});
1477+
}
1478+
1479+
if (attrs.anchorData) {
1480+
anchorElements.push({
1481+
name: 'wp:positionH',
1482+
attributes: {
1483+
relativeFrom: attrs.anchorData.hRelativeFrom,
1484+
},
1485+
...(attrs.marginOffset.left && {
1486+
elements: [{
1487+
name: 'wp:posOffset',
1488+
elements: [{
1489+
type: 'text',
1490+
text: pixelsToEmu(attrs.marginOffset.left).toString(),
1491+
}],
1492+
}]
1493+
}),
1494+
...(attrs.anchorData.alignH && {
1495+
elements: [{
1496+
name: 'wp:align',
1497+
elements: [{
1498+
type: 'text',
1499+
text: attrs.anchorData.alignH,
1500+
}],
1501+
}]
1502+
})
1503+
});
1504+
anchorElements.push({
1505+
name: 'wp:positionV',
1506+
attributes: {
1507+
relativeFrom: attrs.anchorData.vRelativeFrom,
1508+
},
1509+
...(attrs.marginOffset.top && {
1510+
elements: [{
1511+
name: 'wp:posOffset',
1512+
elements: [{
1513+
type: 'text',
1514+
text: pixelsToEmu(attrs.marginOffset.top).toString(),
1515+
}],
1516+
}]
1517+
}),
1518+
...(attrs.anchorData.alignV && {
1519+
elements: [{
1520+
name: 'wp:align',
1521+
elements: [{
1522+
type: 'text',
1523+
text: attrs.anchorData.alignV,
1524+
}],
1525+
}]
1526+
})
1527+
});
1528+
}
1529+
1530+
if (attrs.wrapText) {
1531+
wrapProp.push({
1532+
name: 'wp:wrapSquare',
1533+
attributes: {
1534+
wrapText: attrs.wrapText,
1535+
}
1536+
});
1537+
}
1538+
1539+
if (attrs.wrapTopAndBottom) {
1540+
wrapProp.push({
1541+
name: 'wp:wrapTopAndBottom',
1542+
});
1543+
}
1544+
}
1545+
14551546
const drawingXmlns = 'http://schemas.openxmlformats.org/drawingml/2006/main';
14561547
const pictureXmlns = 'http://schemas.openxmlformats.org/drawingml/2006/picture';
1457-
return wrapTextInRun(
1548+
1549+
const textNode = wrapTextInRun(
14581550
{
14591551
name: 'w:drawing',
14601552
elements: [
14611553
{
1462-
name: 'wp:inline',
1554+
name: attrs.isAnchor ? 'wp:anchor' : 'wp:inline',
14631555
attributes: inlineAttrs,
14641556
elements: [
1557+
...anchorElements,
14651558
{
14661559
name: 'wp:extent',
14671560
attributes: {
@@ -1478,12 +1571,13 @@ function translateImageNode(params, imageSize) {
14781571
b: 0,
14791572
},
14801573
},
1574+
...wrapProp,
14811575
{
14821576
name: 'wp:docPr',
14831577
attributes: {
1484-
id: 0,
1485-
name: '',
1486-
descr: '',
1578+
id: attrs.id || 0,
1579+
name: attrs.alt,
1580+
descr: attrs.title,
14871581
},
14881582
},
14891583
{
@@ -1516,9 +1610,8 @@ function translateImageNode(params, imageSize) {
15161610
{
15171611
name: 'pic:cNvPr',
15181612
attributes: {
1519-
id: 0,
1520-
name: '',
1521-
desc: '',
1613+
id: attrs.id || 0,
1614+
name: attrs.title,
15221615
},
15231616
},
15241617
{
@@ -1542,12 +1635,8 @@ function translateImageNode(params, imageSize) {
15421635
name: 'a:blip',
15431636
attributes: {
15441637
'r:embed': imageId,
1545-
cstate: 'none',
15461638
},
15471639
},
1548-
{
1549-
name: 'a:srcRect',
1550-
},
15511640
{
15521641
name: 'a:stretch',
15531642
elements: [{ name: 'a:fillRect' }],
@@ -1584,6 +1673,9 @@ function translateImageNode(params, imageSize) {
15841673
attributes: { prst: 'rect' },
15851674
elements: [{ name: 'a:avLst' }],
15861675
},
1676+
{
1677+
name: 'a:noFill'
1678+
}
15871679
],
15881680
},
15891681
],
@@ -1598,6 +1690,8 @@ function translateImageNode(params, imageSize) {
15981690
},
15991691
[],
16001692
);
1693+
1694+
return textNode;
16011695
}
16021696

16031697
/**

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

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ export const handleDrawingNode = (params) => {
2626

2727
// Some images are identified by wp:anchor
2828
const isAnchor = elements.find((el) => el.name === 'wp:anchor');
29-
if (isAnchor) result = handleImageImport(elements[0], currentFileName, params);
29+
if (isAnchor) {
30+
31+
result = handleImageImport(elements[0], currentFileName, params);
32+
result.attrs.isAnchor = isAnchor;
33+
}
3034

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

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

7278
const positionVTag = node.elements.find((el) => el.name === 'wp:positionV');
7379
const positionV = positionVTag?.elements.find((el) => el.name === 'wp:posOffset');
7480
const positionVValue = emuToPixels(positionV?.elements[0]?.text);
75-
81+
const vRelativeFrom = positionVTag?.attributes.relativeFrom;
82+
const alignV = positionVTag?.elements.find((el) => el.name === 'wp:align')?.elements[0]?.text;
83+
84+
const simplePos = node.elements.find((el) => el.name === 'wp:simplePos');
85+
const wrapSquare = node.elements.find((el) => el.name === 'wp:wrapSquare');
86+
const wrapTopAndBottom = node.elements.find((el) => el.name === 'wp:wrapTopAndBottom');
87+
88+
const docPr = node.elements.find((el) => el.name === 'wp:docPr');
89+
90+
let anchorData = null;
91+
if (hRelativeFrom || alignH || vRelativeFrom || alignV) {
92+
anchorData = {
93+
hRelativeFrom,
94+
vRelativeFrom,
95+
alignH,
96+
alignV,
97+
};
98+
}
99+
76100
const marginOffset = {
77101
left: positionHValue,
78102
top: positionVValue,
@@ -96,17 +120,31 @@ export function handleImageImport(node, currentFileName, params) {
96120
type: 'image',
97121
attrs: {
98122
src: path,
99-
alt: 'Image',
123+
alt: docPr?.attributes.name || 'Image',
124+
id: docPr?.attributes.id || '',
125+
title: docPr?.attributes.descr || 'Image',
100126
inline: true,
101127
padding,
102128
marginOffset,
103129
size,
130+
anchorData,
131+
...(simplePos && {
132+
simplePos: {
133+
x: simplePos.attributes.x,
134+
y: simplePos.attributes.y,
135+
}
136+
}),
137+
...(wrapSquare && {
138+
wrapText: wrapSquare.attributes.wrapText
139+
}),
140+
wrapTopAndBottom: !!wrapTopAndBottom,
104141
originalPadding: {
105142
distT: attributes['distT'],
106143
distB: attributes['distB'],
107144
distL: attributes['distL'],
108145
distR: attributes['distR'],
109146
},
147+
originalAttributes: node.attributes,
110148
rId: relAttributes['Id'],
111149
},
112150
};

packages/super-editor/src/extensions/image/image.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Node, Attribute } from '@core/index.js';
22
import { ImagePlaceholderPlugin } from './imageHelpers/imagePlaceholderPlugin.js';
3+
import { ImagePositionPlugin } from './imageHelpers/imagePositionPlugin.js';
34

45
export const Image = Node.create({
56
name: 'image',
@@ -39,6 +40,8 @@ export const Image = Node.create({
3940
alt: {
4041
default: null,
4142
},
43+
44+
id: { rendered: false },
4245

4346
title: {
4447
default: null,
@@ -53,6 +56,17 @@ export const Image = Node.create({
5356
default: null,
5457
rendered: false,
5558
},
59+
originalAttributes: { rendered: false },
60+
wrapTopAndBottom: { rendered: false },
61+
62+
anchorData: {
63+
default: null,
64+
rendered: false,
65+
},
66+
67+
isAnchor: { rendered: false },
68+
simplePos: { rendered: false },
69+
wrapText: { rendered: false },
5670

5771
size: {
5872
default: {},
@@ -64,6 +78,19 @@ export const Image = Node.create({
6478
return { style };
6579
},
6680
},
81+
82+
padding: {
83+
default: {},
84+
renderDOM: ({ padding, marginOffset }) => {
85+
let { left = 0, top = 0, bottom = 0, right = 0 } = padding ?? {};
86+
let style = '';
87+
if (left && !marginOffset?.left) style += `margin-left: ${left}px;`;
88+
if (top && !marginOffset?.top) style += `margin-top: ${top}px;`;
89+
if (bottom) style += `margin-bottom: ${bottom}px;`;
90+
if (right) style += `margin-right: ${right}px;`;
91+
return { style };
92+
},
93+
},
6794

6895
marginOffset: {
6996
default: {},
@@ -113,6 +140,6 @@ export const Image = Node.create({
113140
},
114141

115142
addPmPlugins() {
116-
return [ImagePlaceholderPlugin()];
143+
return [ImagePlaceholderPlugin(), ImagePositionPlugin({editor: this.editor })];
117144
},
118145
});

0 commit comments

Comments
 (0)