Skip to content
Open
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
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ lerna-debug.log*
.vitepress/.cache
.vitepress/dist

packages/super-editor/src/tests/data/*/

packages/word-layout/tests/**/*.js
packages/word-layout/tests/**/*.d.ts
packages/word-layout/tests/**/*.map
Expand Down
3 changes: 3 additions & 0 deletions packages/layout-engine/pm-adapter/src/converter-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import type { ParagraphSpacing } from '@superdoc/contracts';
import type { NumberingProperties, StylesDocumentProperties, TableInfo } from '@superdoc/style-engine/ooxml';
import { SuperConverter } from '@superdoc/super-editor';

/**
* Paragraph properties from a table style that should be applied to
Expand Down Expand Up @@ -44,6 +45,8 @@ export type ConverterContext = {
* contrast with the cell background per WCAG guidelines.
*/
backgroundColor?: string;

numbering?: SuperConverter['numbering'];
Copy link
Contributor

@luccas-harbour luccas-harbour Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This property isn't used by the pm-adapter anymore, it was superseded by translatedNumbering. The translatedNumbering property contains all the numbering information but uses a v3 translator for it. I had to keep the old numbering in the SuperConverter class just not to break existing code on the ProseMirror side of things but the pm-adapter and the layout-engine in general don't depend on it anymore.

I can see that removing this property causes an error in this line but you can safely delete that line.

};

/**
Expand Down
59 changes: 39 additions & 20 deletions packages/super-editor/src/core/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { ProseMirrorRenderer } from './renderers/ProseMirrorRenderer.js';
import { BLANK_DOCX_DATA_URI } from './blank-docx.js';
import { getArrayBufferFromUrl } from '@core/super-converter/helpers.js';
import { Telemetry, COMMUNITY_LICENSE_KEY } from '@superdoc/common';
import { OpenXmlNode } from '@converter/v2/types';

declare const __APP_VERSION__: string;
declare const version: string | undefined;
Expand Down Expand Up @@ -151,6 +152,25 @@ export interface SaveOptions {
*/
export type ExportOptions = SaveOptions;

type ExportDocxProps = {
isFinalDoc?: boolean;
commentsType?: string;
exportJsonOnly?: boolean;
exportXmlOnly?: boolean;
comments?: Comment[];
getUpdatedDocs?: boolean;
fieldsHighlightColor?: string | null;
compression?: 'DEFLATE' | 'STORE';
};

type ExportDocxResult<TProps extends ExportDocxProps> = TProps extends { exportJsonOnly: true }
? OpenXmlNode
: TProps extends { exportXmlOnly: true }
? string
: TProps extends { getUpdatedDocs: true }
? Record<string, string>
: Blob | Buffer;

/**
* Main editor class that manages document state, extensions, and user interactions
*/
Expand Down Expand Up @@ -2510,7 +2530,7 @@ export class Editor extends EventEmitter<EditorEventMap> {
/**
* Export the editor document to DOCX.
*/
async exportDocx({
async exportDocx<TProps extends ExportDocxProps>({
isFinalDoc = false,
commentsType = 'external',
exportJsonOnly = false,
Expand All @@ -2519,16 +2539,7 @@ export class Editor extends EventEmitter<EditorEventMap> {
getUpdatedDocs = false,
fieldsHighlightColor = null,
compression,
}: {
isFinalDoc?: boolean;
commentsType?: string;
exportJsonOnly?: boolean;
exportXmlOnly?: boolean;
comments?: Comment[];
getUpdatedDocs?: boolean;
fieldsHighlightColor?: string | null;
compression?: 'DEFLATE' | 'STORE';
} = {}): Promise<Blob | ArrayBuffer | Buffer | Record<string, string> | ProseMirrorJSON | string | undefined> {
}: ExportDocxProps = {}): Promise<ExportDocxResult<TProps> | undefined> {
try {
// Use provided comments, or fall back to imported comments from converter
const effectiveComments = comments ?? this.converter.comments ?? [];
Expand Down Expand Up @@ -2561,16 +2572,22 @@ export class Editor extends EventEmitter<EditorEventMap> {

this.#validateDocumentExport();

if (exportXmlOnly || exportJsonOnly) return documentXml;
if (exportXmlOnly || exportJsonOnly) {
// Ideally this cast wouldn't be required; this would likely be solved by
// https://github.com/microsoft/TypeScript/pull/61359
return documentXml as ExportDocxResult<TProps>;
}

const customXml = this.converter.schemaToXml(this.converter.convertedXml['docProps/custom.xml'].elements[0]);
const styles = this.converter.schemaToXml(this.converter.convertedXml['word/styles.xml'].elements[0]);
const customXml = this.converter.schemaToXml(this.converter.convertedXml['docProps/custom.xml']?.elements?.[0]);
const styles = this.converter.schemaToXml(this.converter.convertedXml['word/styles.xml']?.elements?.[0]);
const hasCustomSettings = !!this.converter.convertedXml['word/settings.xml']?.elements?.length;
const customSettings = hasCustomSettings
? this.converter.schemaToXml(this.converter.convertedXml['word/settings.xml']?.elements?.[0])
: null;

const rels = this.converter.schemaToXml(this.converter.convertedXml['word/_rels/document.xml.rels'].elements[0]);
const rels = this.converter.schemaToXml(
this.converter.convertedXml['word/_rels/document.xml.rels']?.elements?.[0],
);
const footnotesData = this.converter.convertedXml['word/footnotes.xml'];
const footnotesXml = footnotesData?.elements?.[0] ? this.converter.schemaToXml(footnotesData.elements[0]) : null;
const footnotesRelsData = this.converter.convertedXml['word/_rels/footnotes.xml.rels'];
Expand All @@ -2590,7 +2607,7 @@ export class Editor extends EventEmitter<EditorEventMap> {
});

const numberingData = this.converter.convertedXml['word/numbering.xml'];
const numbering = this.converter.schemaToXml(numberingData.elements[0]);
const numbering = this.converter.schemaToXml(numberingData?.elements?.[0]);

// Export core.xml (contains dcterms:created timestamp)
const coreXmlData = this.converter.convertedXml['docProps/core.xml'];
Expand Down Expand Up @@ -2620,7 +2637,7 @@ export class Editor extends EventEmitter<EditorEventMap> {
}

if (preparedComments.length) {
const commentsXml = this.converter.schemaToXml(this.converter.convertedXml['word/comments.xml'].elements[0]);
const commentsXml = this.converter.schemaToXml(this.converter.convertedXml['word/comments.xml']?.elements?.[0]);
updatedDocs['word/comments.xml'] = String(commentsXml);

const commentsExtended = this.converter.convertedXml['word/commentsExtended.xml'];
Expand Down Expand Up @@ -2653,7 +2670,9 @@ export class Editor extends EventEmitter<EditorEventMap> {
true,
updatedDocs,
);
return updatedDocs;
// Ideally this cast wouldn't be required; this would likely be solved by
// https://github.com/microsoft/TypeScript/pull/61359
return updatedDocs as ExportDocxResult<TProps>;
}

const result = await zipper.updateZip({
Expand Down Expand Up @@ -3137,9 +3156,9 @@ export class Editor extends EventEmitter<EditorEventMap> {
}

if (type === 'json') {
return this.converter.convertedXml[name].elements[0] || null;
return this.converter.convertedXml[name]?.elements?.[0] || null;
}
return this.converter.schemaToXml(this.converter.convertedXml[name].elements[0]);
return this.converter.schemaToXml(this.converter.convertedXml[name]?.elements?.[0]);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,19 @@ import type { Editor } from '@core/Editor.js';
import { EventEmitter } from '@core/EventEmitter.js';
import { createHeaderFooterEditor, onHeaderFooterDataUpdate } from '@extensions/pagination/pagination-helpers.js';
import type { ConverterContext } from '@superdoc/pm-adapter/converter-context.js';
import { SuperConverter } from '@converter/SuperConverter';

const HEADER_FOOTER_VARIANTS = ['default', 'first', 'even', 'odd'] as const;
const DEFAULT_HEADER_FOOTER_HEIGHT = 100;
const EDITOR_READY_TIMEOUT_MS = 5000;
const MAX_CACHED_EDITORS_LIMIT = 100;

type MinimalConverterContext = {
docx?: Record<string, unknown>;
numbering?: {
definitions?: Record<string, unknown>;
abstracts?: Record<string, unknown>;
};
linkedStyles?: Array<{
id: string;
definition?: {
styles?: Record<string, unknown>;
attrs?: Record<string, unknown>;
};
}>;
};

/**
* Extended Editor interface that includes the converter property.
* Used for type-safe access to header/footer data stored in the converter.
*/
interface EditorWithConverter extends Editor {
converter: HeaderFooterCollections;
converter: Required<Editor>['converter']; // TODO: Should `converter` property of Editor be marked as optional (`?`) rather than definite-assignment (`!`)?
}

export type HeaderFooterKind = 'header' | 'footer';
Expand Down Expand Up @@ -782,7 +768,7 @@ export class HeaderFooterEditorManager extends EventEmitter {
if (!this.#hasConverter(this.#editor)) {
return;
}
const converter = this.#editor.converter as Record<string, unknown>;
const converter = this.#editor.converter;
if (!converter) return;

const targetKey = descriptor.kind === 'header' ? 'headerEditors' : 'footerEditors';
Expand Down Expand Up @@ -817,7 +803,7 @@ export class HeaderFooterEditorManager extends EventEmitter {
if (!this.#hasConverter(this.#editor)) {
return;
}
const converter = this.#editor.converter as Record<string, unknown>;
const converter = this.#editor.converter;
if (!converter) return;

const targetKey = descriptor.kind === 'header' ? 'headerEditors' : 'footerEditors';
Expand Down Expand Up @@ -1176,15 +1162,15 @@ export class HeaderFooterLayoutAdapter {
if (!('converter' in rootEditor)) {
return undefined;
}
const converter = (rootEditor as EditorWithConverter).converter as Record<string, unknown> | undefined;
const converter = rootEditor.converter as SuperConverter | undefined;
if (!converter) return undefined;

const context: ConverterContext = {
docx: converter.convertedXml,
numbering: converter.numbering,
translatedLinkedStyles: converter.translatedLinkedStyles,
translatedNumbering: converter.translatedNumbering,
} as ConverterContext;
};

return context;
}
Expand Down
Loading
Loading