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
16 changes: 16 additions & 0 deletions packages/super-editor/src/assets/styles/extensions/pagination.css
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,19 @@
flex-direction: column;
background-color: white;
}

/**
Workaround to display pagination in footer
on the right if it is inside shape textbox.
*/
.pagination-section-footer .pm-shape-container:has(
[data-id="auto-page-number"],
[data-id="auto-total-pages"]
) {
margin-left: auto;
}

.pagination-section-header img[contenteditable="false"],
.pagination-section-footer img[contenteditable="false"] {
pointer-events: none;
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class SuperConverter {
{ name: 'w:spacing', type: 'lineHeight', mark: 'textStyle', property: 'lineHeight' },
{ name: 'link', type: 'link', mark: 'link', property: 'href' },
{ name: 'w:highlight', type: 'highlight', mark: 'highlight', property: 'color' },
// { name: 'w:shd', type: 'highlight', mark: 'highlight', property: 'color'}
{ name: 'w:shd', type: 'highlight', mark: 'highlight', property: 'color'}
];

static propertyTypes = Object.freeze({
Expand Down
61 changes: 61 additions & 0 deletions packages/super-editor/src/core/super-converter/exporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ export function exportSchemaToJson(params) {
commentRangeStart: () => translateCommentNode(params, 'Start'),
commentRangeEnd: () => translateCommentNode(params, 'End'),
commentReference: () => null,
shapeContainer: translateShapeContainer,
shapeTextbox: translateShapeTextbox,
};

if (!router[type]) {
Expand Down Expand Up @@ -1757,6 +1759,65 @@ export function translateHardBreak() {
};
}

function translateShapeContainer(params) {
const { node } = params;
const elements = translateChildNodes(params);

const shape = {
name: 'v:shape',
attributes: {
...node.attrs.attributes,
fillcolor: node.attrs.fillcolor,
},
elements: [
...elements,
...(
node.attrs.wrapAttributes
? [{
name: 'w10:wrap',
attributes: { ...node.attrs.wrapAttributes },
}]
: []
),
],
};

const pict = {
name: 'w:pict',
attributes: {
'w14:anchorId': Math.floor(Math.random() * 0xffffffff).toString(),
},
elements: [shape],
};

const par = {
name: 'w:p',
elements: [wrapTextInRun(pict)],
};

return par;
}

function translateShapeTextbox(params) {
const { node } = params;
const elements = translateChildNodes(params);

const textboxContent = {
name: 'w:txbxContent',
elements,
};

const textbox = {
name: 'v:textbox',
attributes: {
...node.attrs.attributes,
},
elements: [textboxContent],
};

return textbox;
}

export class DocxExporter {
constructor(converter) {
this.converter = converter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { bookmarkNodeHandlerEntity } from './bookmarkNodeImporter.js';
import { autoPageHandlerEntity, autoTotalPageCountEntity } from './autoPageNumberImporter.js';
import { tabNodeEntityHandler } from './tabImporter.js';
import { listHandlerEntity } from './listImporter.js';
import { pictNodeHandlerEntity } from './pictNodeImporter.js';
import { importCommentData } from './documentCommentsImporter.js';
import { getDefaultStyleDefinition } from './paragraphNodeImporter.js';
import { baseNumbering } from '../exporter/helpers/base-list.definitions.js';
Expand Down Expand Up @@ -121,6 +122,7 @@ export const createDocumentJson = (docx, converter, editor) => {
export const defaultNodeListHandler = () => {
const entities = [
runNodeHandlerEntity,
pictNodeHandlerEntity,
paragraphNodeHandlerEntity,
listHandlerEntity,
textNodeHandlerEntity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ function getLineHeightValue(attributes) {
}

function getHighLightValue(attributes) {
if (attributes['w:fill']) return `#${attributes['w:fill']}`;
const fill = attributes['w:fill'];
if (fill && fill !== 'auto') return `#${fill}`;
if (isValidHexColor(attributes?.['w:val'])) return `#${attributes['w:val']}`;
return getHexColorFromDocxSystem(attributes?.['w:val']) || null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,16 @@ export const handleParagraphNode = (params) => {
const nestedRPr = pPr?.elements?.find((el) => el.name === 'w:rPr');

if (nestedRPr) {
schemaNode.attrs.marksAttrs = parseMarks(nestedRPr, []);
let marks = parseMarks(nestedRPr, []);

if (!schemaNode.content?.length) {
let highlightIndex = marks?.findIndex((i) => i.type === 'highlight');
if (highlightIndex !== -1) {
marks.splice(highlightIndex, 1)
}
}

schemaNode.attrs.marksAttrs = marks;
}

if (styleTag) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { handleParagraphNode } from './paragraphNodeImporter.js';
import { defaultNodeListHandler } from './docxImporter.js';

export const handlePictNode = (params) => {
const { nodes } = params;

if (!nodes.length || nodes[0].name !== 'w:p') {
return { nodes: [], consumed: 0 };
}

const [pNode] = nodes;
const run = pNode.elements?.find((el) => el.name === 'w:r');
const pict = run?.elements?.find((el) => el.name === 'w:pict');

// if there is no pict, then process as a paragraph or list.
if (!pict) {
return { nodes: [], consumed: 0 };
}

const node = pict;
const shape = node.elements?.find((el) => el.name === 'v:shape');
const shapetype = node.elements?.find((el) => el.name === 'v:shapetype');
const group = node.elements?.find((el) => el.name === 'v:group');

// such a case probably shouldn't exist.
if (!shape && !group) {
return { nodes: [], consumed: 0 };
}

let result = null;

const isGroup = group && !shape;

if (isGroup) {
// there should be a group of shapes being processed here (skip for now).
result = null;
} else {
const textbox = shape.elements?.find((el) => el.name === 'v:textbox');

// process shapes with textbox.
if (textbox) {
result = handleShapTextboxImport({
pict,
pNode,
shape,
params,
});
}
}

return { nodes: result ? [result] : [], consumed: 1 };
};

export function handleShapTextboxImport({
pict,
pNode,
shape,
params,
}) {
const schemaAttrs = {};
const schemaTextboxAttrs = {};

const shapeAttrs = shape.attributes || {};

schemaAttrs.attributes = shapeAttrs;

if (shapeAttrs.fillcolor) {
schemaAttrs.fillcolor = shapeAttrs.fillcolor;
}

const parsedStyle = parseInlineStyles(shapeAttrs.style);
const shapeStyle = buildStyles(parsedStyle);

if (shapeStyle) {
schemaAttrs.style = shapeStyle;
}

const textbox = shape.elements?.find((el) => el.name === 'v:textbox');
const wrap = shape.elements?.find((el) => el.name === 'w10:wrap');

if (wrap?.attributes) {
schemaAttrs.wrapAttributes = wrap.attributes;
}

if (textbox?.attributes) {
schemaTextboxAttrs.attributes = textbox.attributes;
}

const textboxContent = textbox?.elements?.find((el) => el.name === 'w:txbxContent');
const textboxContentElems = textboxContent?.elements || [];

const content = textboxContentElems.map((elem) => handleParagraphNode({
nodes: [elem],
docx: params.docx,
nodeListHandler: defaultNodeListHandler(),
}));
const contentNodes = content.reduce((acc, current) => (
[...acc, ...current.nodes]
), []);

const shapeTextbox = {
type: 'shapeTextbox',
attrs: schemaTextboxAttrs,
content: contentNodes,
};

const shapeContainer = {
type: 'shapeContainer',
attrs: schemaAttrs,
content: [shapeTextbox],
};

return shapeContainer;
}

function parseInlineStyles(styleString) {
if (!styleString) return {};
return styleString
.split(';')
.filter((style) => !!style.trim())
.reduce((acc, style) => {
const [prop, value] = style.split(':').map((str) => str.trim());
if (prop && value) acc[prop] = value;
return acc;
}, {});
}

function buildStyles(styleObject) {
const allowed = [
'width',
'height',

// these styles should probably work relative to the page,
// since in the doc it is positioned absolutely.
// 'margin-left',
// 'margin-right',

// causes pagination issues.
// 'margin-top',
// 'margin-bottom',

// styleObject - also contains other word styles (mso-).
];

let style = '';
for (const [prop, value] of Object.entries(styleObject)) {
if (allowed.includes(prop)) {
style += `${prop}: ${value};`;
}
}

return style;
}

export const pictNodeHandlerEntity = {
handlerName: 'handlePictNode',
handler: handlePictNode,
};
3 changes: 2 additions & 1 deletion packages/super-editor/src/extensions/image/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ export const Image = Node.create({
},

style: {
default: {},
default: null,
rendered: true,
renderDOM: ({ style }) => {
if (!style) return {};
return { style };
},
},
Expand Down
4 changes: 4 additions & 0 deletions packages/super-editor/src/extensions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import { Image } from './image/index.js';
import { BookmarkStart } from './bookmarks/index.js';
import { Mention } from './mention/index.js';
import { PageNumber, TotalPageCount } from './page-number/index.js';
import { ShapeContainer } from './shape-container/index.js';
import { ShapeTextbox } from './shape-textbox/index.js';

// Marks extensions
import { TextStyle } from './text-style/text-style.js';
Expand Down Expand Up @@ -155,6 +157,8 @@ const getStarterExtensions = () => {
AiAnimationMark,
PageNumber,
TotalPageCount,
ShapeContainer,
ShapeTextbox,
];
};

Expand Down
10 changes: 9 additions & 1 deletion packages/super-editor/src/extensions/pagination/pagination.js
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,9 @@ function generateInternalPageBreaks(doc, view, editor, sectionData) {
const sectionContainer = decoration.type.toDOM;
const totalPageNumber = sectionContainer?.querySelector('span[data-id="auto-total-pages"]');
if (totalPageNumber) {
const fontSize = totalPageNumber.previousElementSibling?.style?.fontSize ||
totalPageNumber.nextElementSibling?.style?.fontSize;
if (fontSize) totalPageNumber.style.fontSize = fontSize;
totalPageNumber.innerText = currentPageNumber;
};
});
Expand Down Expand Up @@ -484,7 +487,12 @@ function createFooter(pageMargins, pageSize, sectionData, footerId, currentPageN
if (!sectionContainer) sectionContainer = document.createElement('div');

const autoPageNumber = sectionContainer?.querySelector('span[data-id="auto-page-number"]');
if (autoPageNumber) autoPageNumber.innerText = currentPageNumber;
if (autoPageNumber) {
const fontSize = autoPageNumber.previousElementSibling?.style?.fontSize ||
autoPageNumber.nextElementSibling?.style?.fontSize;
if (fontSize) autoPageNumber.style.fontSize = fontSize;
autoPageNumber.innerText = currentPageNumber;
}

sectionContainer.className = 'pagination-section-footer';
sectionContainer.style.height = footerHeight + 'px';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const Paragraph = Node.create({
},
extraAttrs: {
default: {},
parseHTML: (element) => {
parseDOM: (element) => {
const extra = {};
Array.from(element.attributes).forEach((attr) => {
extra[attr.name] = attr.value;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './shape-container.js';
Loading
Loading