Skip to content

Commit b0f09b4

Browse files
committed
add and export new headers/footers
1 parent 6e61405 commit b0f09b4

5 files changed

Lines changed: 99 additions & 42 deletions

File tree

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,23 +121,34 @@ class DocxZipper {
121121
if (docx.files['word/comments.xml']) {
122122
const commentsDef = `<Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml" />`;
123123
if (!hasComments) typesString += commentsDef;
124-
};
124+
}
125125

126126
if (docx.files['word/commentsExtended.xml']) {
127127
const commentsExtendedDef = `<Override PartName="/word/commentsExtended.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml" />`;
128128
if (!hasCommentsExtended) typesString += commentsExtendedDef;
129-
};
129+
}
130130

131131
if (docx.files['word/commentsIds.xml']) {
132132
const commentsIdsDef = `<Override PartName="/word/commentsIds.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml" />`;
133133
if (!hasCommentsIds) typesString += commentsIdsDef;
134-
};
134+
}
135135

136136
if (docx.files['word/commentsExtensible.xml']) {
137137
const commentsExtendedDef = `<Override PartName="/word/commentsExtensible.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtensible+xml" />`;
138138
if (!hasCommentsExtensible) typesString += commentsExtendedDef;
139-
};
140-
139+
}
140+
141+
142+
Object.keys(docx.files).forEach((name) => {
143+
if (!name.includes('header') && !name.includes('footer')) return;
144+
const hasExtensible = types.elements?.some((el) => el.name === 'Override' && el.attributes.PartName === `/${name}`);
145+
const type = name.includes('header') ? 'header' : 'footer';
146+
const extendedDef = `<Override PartName="/${name}" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.${type}+xml"/>`;
147+
if (!hasExtensible) {
148+
typesString += extendedDef;
149+
}
150+
})
151+
141152
const beginningString = '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">';
142153
const updatedContentTypesXml = contentTypesXml.replace(beginningString, `${beginningString}${typesString}`);
143154

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

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import { DocxExporter, exportSchemaToJson } from './exporter';
55
import { createDocumentJson, addDefaultStylesIfMissing } from './v2/importer/docxImporter.js';
66
import { deobfuscateFont, getArrayBufferFromUrl } from './helpers.js';
77
import { baseNumbering } from './v2/exporter/helpers/base-list.definitions.js';
8-
import { DEFAULT_CUSTOM_XML, SETTINGS_CUSTOM_XML } from './exporter-docx-defs.js';
8+
import { DEFAULT_CUSTOM_XML, DEFAULT_DOCX_DEFS, SETTINGS_CUSTOM_XML } from './exporter-docx-defs.js';
99
import {
1010
getCommentDefinition,
1111
prepareCommentParaIds,
1212
prepareCommentsXmlFilesForExport,
1313
} from './v2/exporter/commentsExporter.js';
14-
import { HYPERLINK_RELATIONSHIP_TYPE } from './constants.js';
14+
import { FOOTER_RELATIONSHIP_TYPE, HEADER_RELATIONSHIP_TYPE, HYPERLINK_RELATIONSHIP_TYPE } from './constants.js';
1515

1616
class SuperConverter {
1717
static allowedElements = Object.freeze({
@@ -420,11 +420,11 @@ class SuperConverter {
420420
}
421421

422422
this.convertedXml = { ...this.convertedXml, ...updatedXml };
423+
424+
const headFootRels = this.#exportProcessHeadersFooters({ isFinalDoc });
423425

424426
// Update the rels table
425-
this.#exportProcessNewRelationships([...params.relationships, ...commentsRels]);
426-
427-
this.#exportProcessHeadersFooters({ isFinalDoc });
427+
this.#exportProcessNewRelationships([...params.relationships, ...commentsRels, ...headFootRels]);
428428

429429
// Store the SuperDoc version
430430
storeSuperdocVersion(this.convertedXml);
@@ -502,9 +502,10 @@ class SuperConverter {
502502
#exportProcessHeadersFooters({ isFinalDoc = false }) {
503503
const relsData = this.convertedXml['word/_rels/document.xml.rels'];
504504
const relationships = relsData.elements.find((x) => x.name === 'Relationships');
505+
const newDocRels = [];
505506

506-
Object.entries(this.headers).forEach(([id, header]) => {
507-
const fileName = relationships.elements.find((el) => el.attributes.Id === id)?.attributes.Target;
507+
Object.entries(this.headers).forEach(([id, header], index) => {
508+
const fileName = relationships.elements.find((el) => el.attributes.Id === id)?.attributes.Target || `header${index + 1}.xml`;
508509
const headerEditor = this.headerEditors.find((item) => item.id === id);
509510

510511
if (!headerEditor) return;
@@ -521,7 +522,29 @@ class SuperConverter {
521522

522523
const bodyContent = result.elements[0].elements;
523524
const file = this.convertedXml[`word/${fileName}`];
524-
file.elements[0].elements = bodyContent;
525+
526+
if (!file) {
527+
this.convertedXml[`word/${fileName}`] = {
528+
declaration: this.initialJSON?.declaration,
529+
elements: [{
530+
attributes: DEFAULT_DOCX_DEFS,
531+
name: 'w:hdr',
532+
type: 'element',
533+
elements: []
534+
}]
535+
};
536+
newDocRels.push({
537+
type: 'element',
538+
name: 'Relationship',
539+
attributes: {
540+
Id: id,
541+
Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/header',
542+
Target: fileName,
543+
},
544+
});
545+
}
546+
547+
this.convertedXml[`word/${fileName}`].elements[0].elements = bodyContent;
525548

526549
if (params.relationships.length) {
527550
const relationships = this.convertedXml[`word/_rels/${fileName}.rels`]?.elements?.find((x) => x.name === 'Relationships')?.elements || [];
@@ -541,8 +564,8 @@ class SuperConverter {
541564
}
542565
});
543566

544-
Object.entries(this.footers).forEach(([id, footer]) => {
545-
const fileName = relationships.elements.find((el) => el.attributes.Id === id)?.attributes.Target;
567+
Object.entries(this.footers).forEach(([id, footer], index) => {
568+
const fileName = relationships.elements.find((el) => el.attributes.Id === id)?.attributes.Target || `footer${index + 1}.xml`;
546569
const footerEditor = this.footerEditors.find((item) => item.id === id);
547570

548571
if (!footerEditor) return;
@@ -559,7 +582,29 @@ class SuperConverter {
559582

560583
const bodyContent = result.elements[0].elements;
561584
const file = this.convertedXml[`word/${fileName}`];
562-
file.elements[0].elements = bodyContent;
585+
586+
if (!file) {
587+
this.convertedXml[`word/${fileName}`] = {
588+
declaration: this.initialJSON?.declaration,
589+
elements: [{
590+
attributes: DEFAULT_DOCX_DEFS,
591+
name: 'w:ftr',
592+
type: 'element',
593+
elements: []
594+
}]
595+
};
596+
newDocRels.push({
597+
type: 'element',
598+
name: 'Relationship',
599+
attributes: {
600+
Id: id,
601+
Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer',
602+
Target: fileName,
603+
},
604+
});
605+
}
606+
607+
this.convertedXml[`word/${fileName}`].elements[0].elements = bodyContent;
563608

564609
if (params.relationships.length) {
565610
const relationships = this.convertedXml[`word/_rels/${fileName}.rels`]?.elements?.find((x) => x.name === 'Relationships')?.elements || [];
@@ -578,6 +623,8 @@ class SuperConverter {
578623
};
579624
}
580625
});
626+
627+
return newDocRels;
581628
}
582629

583630
#exportProcessNewRelationships(rels = []) {
@@ -593,8 +640,9 @@ class SuperConverter {
593640
const existingTarget = relationships.elements.find((el) => el.attributes.Target === rel.attributes.Target);
594641
const isNewMedia = rel.attributes.Target?.startsWith('media/') && existingId.length > 6;
595642
const isNewHyperlink = rel.attributes.Type === HYPERLINK_RELATIONSHIP_TYPE && existingId.length > 6;
596-
597-
if (existingTarget && !isNewMedia && !isNewHyperlink) {
643+
const isNewHeadFoot = rel.attributes.Type === (HEADER_RELATIONSHIP_TYPE || rel.attributes.Type === FOOTER_RELATIONSHIP_TYPE) && existingId.length > 6;
644+
645+
if (existingTarget && !isNewMedia && !isNewHyperlink && !isNewHeadFoot) {
598646
return;
599647
}
600648

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export const HYPERLINK_RELATIONSHIP_TYPE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink';
2+
export const HEADER_RELATIONSHIP_TYPE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/header';
3+
export const FOOTER_RELATIONSHIP_TYPE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer';

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,16 @@ function translateBodyNode(params) {
117117
const hasHeader = sectPr.elements.some((n) => n.name === 'w:headerReference');
118118
const hasDefaultHeader = params.converter.headerIds?.default;
119119
if (!hasHeader && hasDefaultHeader && !params.editor.options.isHeaderOrFooter) {
120-
const defaultHeader = generateDefaultHeader();
120+
const defaultHeader = generateDefaultHeaderFooter('header', params.converter.headerIds?.default);
121121
sectPr.elements.push(defaultHeader);
122122
}
123+
124+
const hasFooter = sectPr.elements.some((n) => n.name === 'w:footerReference');
125+
const hasDefaultFooter = params.converter.footerIds?.default;
126+
if (!hasFooter && hasDefaultFooter && !params.editor.options.isHeaderOrFooter) {
127+
const defaultFooter = generateDefaultHeaderFooter('footer', params.converter.footerIds?.default);
128+
sectPr.elements.push(defaultFooter);
129+
}
123130

124131
const newMargins = params.converter.pageStyles.pageMargins;
125132
const sectPrMargins = sectPr.elements.find((n) => n.name === 'w:pgMar');
@@ -146,13 +153,13 @@ function translateBodyNode(params) {
146153
};
147154
}
148155

149-
const generateDefaultHeader = () => {
156+
const generateDefaultHeaderFooter = (type, id) => {
150157
return {
151158
"type": "element",
152-
"name": "w:headerReference",
159+
"name": `w:${type}Reference`,
153160
"attributes": {
154161
"w:type": "default",
155-
"r:id": "rId6"
162+
"r:id": id
156163
}
157164
};
158165
}

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

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { CollaborationPluginKey } from '@extensions/collaboration/collaboration.
77
import { ImagePlaceholderPluginKey } from '@extensions/image/imageHelpers/imagePlaceholderPlugin.js';
88
import { LinkedStylesPluginKey } from '@extensions/linked-styles/linked-styles.js';
99
import { findParentNodeClosestToPos } from '@core/helpers/findParentNodeClosestToPos.js';
10+
import { generateDocxRandomId } from '../../core/helpers/index.js';
1011

1112
const isDebugging = false;
1213

@@ -494,7 +495,7 @@ function createHeader(pageMargins, pageSize, sectionData, headerId, editor) {
494495
const editorContainer = document.createElement('div');
495496

496497
if (!headerId && !editor.converter.headerIds['default']) {
497-
headerId = 'rId6';
498+
headerId = 'rId' + generateDocxRandomId();
498499
editor.converter.headerIds['default'] = headerId;
499500
}
500501

@@ -503,7 +504,7 @@ function createHeader(pageMargins, pageSize, sectionData, headerId, editor) {
503504
type: 'doc',
504505
content: [{ type: 'paragraph', content: [] }]
505506
}
506-
};
507+
}
507508

508509
const data = editor.converter.headers[headerId];
509510
const editorSection = createHeaderFooterEditor({
@@ -515,16 +516,12 @@ function createHeader(pageMargins, pageSize, sectionData, headerId, editor) {
515516
type: 'header',
516517
availableHeight,
517518
});
519+
editor.converter.headerEditors.push({
520+
id: headerId,
521+
editor: editorSection,
522+
});
518523
editorSection.setEditable(false, false);
519524

520-
const hasEditorInConverter = editor.converter.headerEditors.find((headerEditor) => headerEditor.id === headerId);
521-
if (!hasEditorInConverter) {
522-
editor.converter.headerEditors.push({
523-
id: headerId,
524-
editor: editorSection,
525-
});
526-
};
527-
528525
broadcastEditorEvents(editor, editorSection);
529526
editorContainer.className = 'pagination-section-header';
530527
editorContainer.style.paddingTop = headerMargin + 'px';
@@ -573,7 +570,7 @@ function createFooter(pageMargins, pageSize, sectionData, footerId, editor, curr
573570
const editorContainer = document.createElement('div');
574571

575572
if (!footerId && !editor.converter.footerIds['default']) {
576-
footerId = 'rId7';
573+
footerId = 'rId' + generateDocxRandomId();
577574
editor.converter.footerIds['default'] = footerId;
578575
}
579576

@@ -582,7 +579,7 @@ function createFooter(pageMargins, pageSize, sectionData, footerId, editor, curr
582579
type: 'doc',
583580
content: [{ type: 'paragraph', content: [] }]
584581
}
585-
};
582+
}
586583

587584
const data = editor.converter.footers[footerId];
588585
const editorSection = createHeaderFooterEditor({
@@ -600,14 +597,6 @@ function createFooter(pageMargins, pageSize, sectionData, footerId, editor, curr
600597
});
601598
editorSection.setEditable(false, false);
602599

603-
const hasEditorInConverter = editor.converter.footerEditors.find((footerEditor) => footerEditor.id === footerId);
604-
if (!hasEditorInConverter) {
605-
editor.converter.footerEditors.push({
606-
id: footerId,
607-
editor: editorSection,
608-
});
609-
};
610-
611600
broadcastEditorEvents(editor, editorSection);
612601

613602
editorContainer.className = 'pagination-section-footer';

0 commit comments

Comments
 (0)