Skip to content

Commit e8e527c

Browse files
committed
handle shape textbox
1 parent eb8992c commit e8e527c

14 files changed

Lines changed: 356 additions & 7 deletions

File tree

packages/super-editor/src/assets/styles/extensions/pagination.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,14 @@
4040
flex-direction: column;
4141
background-color: white;
4242
}
43+
44+
/**
45+
Workaround to display pagination in footer
46+
on the right if it is inside shape textbox.
47+
*/
48+
.pagination-section-footer .pm-shape-container:has(
49+
[data-id="auto-page-number"],
50+
[data-id="auto-total-pages"]
51+
) {
52+
margin-left: auto;
53+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class SuperConverter {
5454
{ name: 'w:spacing', type: 'lineHeight', mark: 'textStyle', property: 'lineHeight' },
5555
{ name: 'link', type: 'link', mark: 'link', property: 'href' },
5656
{ name: 'w:highlight', type: 'highlight', mark: 'highlight', property: 'color' },
57-
// { name: 'w:shd', type: 'highlight', mark: 'highlight', property: 'color'}
57+
{ name: 'w:shd', type: 'highlight', mark: 'highlight', property: 'color'}
5858
];
5959

6060
static propertyTypes = Object.freeze({

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ export function exportSchemaToJson(params) {
8888
commentRangeStart: () => translateCommentNode(params, 'Start'),
8989
commentRangeEnd: () => translateCommentNode(params, 'End'),
9090
commentReference: () => null,
91+
shapeContainer: translateShapeContainer,
92+
shapeTextbox: translateShapeTextbox,
9193
};
9294

9395
if (!router[type]) {
@@ -1757,6 +1759,65 @@ export function translateHardBreak() {
17571759
};
17581760
}
17591761

1762+
function translateShapeContainer(params) {
1763+
const { node } = params;
1764+
const elements = translateChildNodes(params);
1765+
1766+
const shape = {
1767+
name: 'v:shape',
1768+
attributes: {
1769+
...node.attrs.attributes,
1770+
fillcolor: node.attrs.fillcolor,
1771+
},
1772+
elements: [
1773+
...elements,
1774+
...(
1775+
node.attrs.wrapAttributes
1776+
? [{
1777+
name: 'w10:wrap',
1778+
attributes: { ...node.attrs.wrapAttributes },
1779+
}]
1780+
: []
1781+
),
1782+
],
1783+
};
1784+
1785+
const pict = {
1786+
name: 'w:pict',
1787+
attributes: {
1788+
'w14:anchorId': Math.floor(Math.random() * 0xffffffff).toString(),
1789+
},
1790+
elements: [shape],
1791+
};
1792+
1793+
const par = {
1794+
name: 'w:p',
1795+
elements: [wrapTextInRun(pict)],
1796+
};
1797+
1798+
return par;
1799+
}
1800+
1801+
function translateShapeTextbox(params) {
1802+
const { node } = params;
1803+
const elements = translateChildNodes(params);
1804+
1805+
const textboxContent = {
1806+
name: 'w:txbxContent',
1807+
elements,
1808+
};
1809+
1810+
const textbox = {
1811+
name: 'v:textbox',
1812+
attributes: {
1813+
...node.attrs.attributes,
1814+
},
1815+
elements: [textboxContent],
1816+
};
1817+
1818+
return textbox;
1819+
}
1820+
17601821
export class DocxExporter {
17611822
constructor(converter) {
17621823
this.converter = converter;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { bookmarkNodeHandlerEntity } from './bookmarkNodeImporter.js';
1616
import { autoPageHandlerEntity, autoTotalPageCountEntity } from './autoPageNumberImporter.js';
1717
import { tabNodeEntityHandler } from './tabImporter.js';
1818
import { listHandlerEntity } from './listImporter.js';
19+
import { pictNodeHandlerEntity } from './pictNodeImporter.js';
1920
import { importCommentData } from './documentCommentsImporter.js';
2021
import { getDefaultStyleDefinition } from './paragraphNodeImporter.js';
2122
import { baseNumbering } from '../exporter/helpers/base-list.definitions.js';
@@ -121,6 +122,7 @@ export const createDocumentJson = (docx, converter, editor) => {
121122
export const defaultNodeListHandler = () => {
122123
const entities = [
123124
runNodeHandlerEntity,
125+
pictNodeHandlerEntity,
124126
paragraphNodeHandlerEntity,
125127
listHandlerEntity,
126128
textNodeHandlerEntity,

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,8 @@ function getLineHeightValue(attributes) {
186186
}
187187

188188
function getHighLightValue(attributes) {
189-
if (attributes['w:fill']) return `#${attributes['w:fill']}`;
189+
const fill = attributes['w:fill'];
190+
if (fill && fill !== 'auto') return `#${fill}`;
190191
if (isValidHexColor(attributes?.['w:val'])) return `#${attributes['w:val']}`;
191192
return getHexColorFromDocxSystem(attributes?.['w:val']) || null;
192193
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { emuToPixels } from '../../helpers.js';
2+
import { handleParagraphNode } from './paragraphNodeImporter.js';
3+
import { defaultNodeListHandler } from './docxImporter.js';
4+
5+
export const handlePictNode = (params) => {
6+
const { nodes } = params;
7+
8+
if (!nodes.length || nodes[0].name !== 'w:p') {
9+
return { nodes: [], consumed: 0 };
10+
}
11+
12+
const [pNode] = nodes;
13+
const run = pNode.elements?.find((el) => el.name === 'w:r');
14+
const pict = run?.elements?.find((el) => el.name === 'w:pict');
15+
16+
// if there is no pict, then process as a paragraph or list.
17+
if (!pict) {
18+
return { nodes: [], consumed: 0 };
19+
}
20+
21+
const node = pict;
22+
const shape = node.elements?.find((el) => el.name === 'v:shape');
23+
const shapetype = node.elements?.find((el) => el.name === 'v:shapetype');
24+
const group = node.elements?.find((el) => el.name === 'v:group');
25+
26+
// such a case probably shouldn't exist.
27+
if (!shape && !group) {
28+
return { nodes: [], consumed: 1 };
29+
}
30+
31+
let result = null;
32+
33+
const isGroup = group && !shape;
34+
35+
if (isGroup) {
36+
// there should be a group of shapes being processed here (skip for now).
37+
result = null;
38+
} else {
39+
const textbox = shape.elements?.find((el) => el.name === 'v:textbox');
40+
41+
// process shapes with textbox.
42+
if (textbox) {
43+
result = handleShapTextboxImport({
44+
node,
45+
pNode,
46+
shape,
47+
params,
48+
});
49+
}
50+
}
51+
52+
return { nodes: result ? [result] : [], consumed: 1 };
53+
};
54+
55+
export function handleShapTextboxImport({
56+
node,
57+
pNode,
58+
shape,
59+
params,
60+
}) {
61+
const schemaAttrs = {};
62+
const schemaTextboxAttrs = {};
63+
64+
const shapeAttrs = shape.attributes || {};
65+
66+
schemaAttrs.attributes = shapeAttrs;
67+
68+
if (shapeAttrs.fillcolor) {
69+
schemaAttrs.fillcolor = shapeAttrs.fillcolor;
70+
}
71+
72+
const parsedStyle = parseInlineStyles(shapeAttrs.style);
73+
const shapeStyle = buildStyles(parsedStyle);
74+
75+
if (shapeStyle) {
76+
schemaAttrs.style = shapeStyle;
77+
}
78+
79+
const textbox = shape.elements?.find((el) => el.name === 'v:textbox');
80+
const wrap = shape.elements?.find((el) => el.name === 'w10:wrap');
81+
82+
if (wrap?.attributes) {
83+
schemaAttrs.wrapAttributes = wrap.attributes;
84+
}
85+
86+
if (textbox?.attributes) {
87+
schemaTextboxAttrs.attributes = textbox.attributes;
88+
}
89+
90+
const textboxContent = textbox?.elements?.find((el) => el.name === 'w:txbxContent');
91+
const textboxContentElems = textboxContent?.elements || [];
92+
93+
const content = textboxContentElems.map((elem) => handleParagraphNode({
94+
nodes: [elem],
95+
docx: params.docx,
96+
nodeListHandler: defaultNodeListHandler(),
97+
}));
98+
const contentNodes = content.reduce((acc, current) => (
99+
[...acc, ...current.nodes]
100+
), []);
101+
102+
const shapeTextbox = {
103+
type: 'shapeTextbox',
104+
attrs: schemaTextboxAttrs,
105+
content: contentNodes,
106+
};
107+
108+
const shapeContainer = {
109+
type: 'shapeContainer',
110+
attrs: schemaAttrs,
111+
content: [shapeTextbox],
112+
};
113+
114+
return shapeContainer;
115+
}
116+
117+
function parseInlineStyles(styleString) {
118+
if (!styleString) return {};
119+
return styleString
120+
.split(';')
121+
.filter((style) => !!style.trim())
122+
.reduce((acc, style) => {
123+
const [prop, value] = style.split(':').map((str) => str.trim());
124+
if (prop && value) acc[prop] = value;
125+
return acc;
126+
}, {});
127+
}
128+
129+
function buildStyles(styleObject) {
130+
const allowed = [
131+
'width',
132+
'height',
133+
// 'margin-left',
134+
// 'margin-right',
135+
// 'margin-top', // causes pagination issues
136+
// 'margin-bottom',
137+
];
138+
139+
let style = '';
140+
for (const [prop, value] of Object.entries(styleObject)) {
141+
if (allowed.includes(prop)) {
142+
style += `${prop}: ${value};`;
143+
}
144+
}
145+
146+
return style;
147+
}
148+
149+
export const pictNodeHandlerEntity = {
150+
handlerName: 'handlePictNode',
151+
handler: handlePictNode,
152+
};

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import { Image } from './image/index.js';
3333
import { BookmarkStart } from './bookmarks/index.js';
3434
import { Mention } from './mention/index.js';
3535
import { PageNumber, TotalPageCount } from './page-number/index.js';
36+
import { ShapeContainer } from './shape-container/index.js';
37+
import { ShapeTextbox } from './shape-textbox/index.js';
3638

3739
// Marks extensions
3840
import { TextStyle } from './text-style/text-style.js';
@@ -150,6 +152,8 @@ const getStarterExtensions = () => {
150152
LinkedStyles,
151153
PageNumber,
152154
TotalPageCount,
155+
ShapeContainer,
156+
ShapeTextbox,
153157
];
154158
};
155159

packages/super-editor/src/extensions/linked-styles/linked-styles.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,9 @@ export const getMarksStyle = (attrs) => {
209209
case 'underline':
210210
styles += `text-decoration: underline; `;
211211
break;
212-
case 'highlight':
213-
styles += `background-color: ${attr.attrs.color}; `;
214-
break;
212+
// case 'highlight':
213+
// styles += `background-color: ${attr.attrs.color}; `;
214+
// break;
215215
case 'textStyle':
216216
const { fontFamily, fontSize } = attr.attrs;
217217
styles += `${fontFamily ? `font-family: ${fontFamily};` : ''} ${fontSize ? `font-size: ${fontSize};` : ''}`;

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,9 @@ function generateInternalPageBreaks(doc, view, editor, sectionData) {
406406
const sectionContainer = decoration.type.toDOM;
407407
const totalPageNumber = sectionContainer?.querySelector('span[data-id="auto-total-pages"]');
408408
if (totalPageNumber) {
409+
const fontSize = totalPageNumber.previousElementSibling?.style?.fontSize ||
410+
totalPageNumber.nextElementSibling?.style?.fontSize;
411+
if (fontSize) totalPageNumber.style.fontSize = fontSize;
409412
totalPageNumber.innerText = currentPageNumber;
410413
};
411414
});
@@ -484,7 +487,12 @@ function createFooter(pageMargins, pageSize, sectionData, footerId, currentPageN
484487
if (!sectionContainer) sectionContainer = document.createElement('div');
485488

486489
const autoPageNumber = sectionContainer?.querySelector('span[data-id="auto-page-number"]');
487-
if (autoPageNumber) autoPageNumber.innerText = currentPageNumber;
490+
if (autoPageNumber) {
491+
const fontSize = autoPageNumber.previousElementSibling?.style?.fontSize ||
492+
autoPageNumber.nextElementSibling?.style?.fontSize;
493+
if (fontSize) autoPageNumber.style.fontSize = fontSize;
494+
autoPageNumber.innerText = currentPageNumber;
495+
}
488496

489497
sectionContainer.className = 'pagination-section-footer';
490498
sectionContainer.style.height = footerHeight + 'px';

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export const Paragraph = Node.create({
3232
},
3333
extraAttrs: {
3434
default: {},
35-
parseHTML: (element) => {
35+
parseDOM: (element) => {
3636
const extra = {};
3737
Array.from(element.attributes).forEach((attr) => {
3838
extra[attr.name] = attr.value;

0 commit comments

Comments
 (0)