Skip to content

Commit fc86b55

Browse files
authored
fix(types): add Comment, FontConfig, exportDocx overloads, and missing PresentationEditor types (#2675)
* fix(types): add Comment, FontConfig, exportDocx overloads, and missing PresentationEditor types - Replace thin Comment type with full runtime shape (commentId, elements, etc.) - Add CommentElement type for comment body nodes - Add FontConfig type for toolbar font picker options - Add exportDocx overloads for narrowed return types per flag - Add loadXmlData overloads accepting ArrayBuffer, narrowing out undefined - Add replaceFile ArrayBuffer support - Make User.name/email/image optional to match runtime behavior - Fix Toolbar interface removing index signature for SuperToolbar compat - Add ToolbarConfig fonts/hideButtons/pagination to JSDoc typedef - Export LayoutState, ImageSelectedEvent, ImageDeselectedEvent, TelemetryEvent, RemoteCursorsRenderPayload, FlowMode, ExportDocxParams, and all proofing types - Add JSDoc re-exports in superdoc barrel for all new types - Update consumer typecheck tests with new type imports and usage * ci: add PR preview publish to release workflow Adds optional `pr_number` input to workflow_dispatch. When provided, checks out the PR branch, sets version to `<base>-pr.<number>.<timestamp>`, builds, publishes with dist-tag `pr-<number>`, and comments on the PR with install instructions. Normal semantic-release flow is skipped. Also fixes replaceFile ArrayBuffer handling for fileSource assignment. * fix(types): inject command type augmentations into dist for TS4111 compat Consumers with noPropertyAccessFromIndexSignature get TS4111 errors on commands like insertComment, removeComment, etc. because they fall through to the EditorCommands index signature. The augmentation files (comment-commands.ts, formatting-commands.ts, etc.) already exist and extend ExtensionCommandMap, but vite-plugin-dts strips the side-effect imports that load them. Fix: inject /// <reference> directives into the dist index.d.ts via the ensure-types.cjs postbuild script, so augmentations are always loaded when consumers import from the package. * fix(types): restore unintentionally removed code from diff application - Restore `this.options.fragment = null` in replaceFile() collaboration path — without it, #generatePmData() reuses the stale Yjs fragment instead of the new file content - Restore documentModeChange event emission in Editor.setDocumentMode() and DocumentModeChangePayload type — SuperEditor.vue subscribes to this for viewing-mode UI cleanup - Fix exportDocx overload ordering — specific overloads must come before the generic catch-all so TypeScript resolves them correctly * refactor(types): use explicit imports for command types instead of reference directives Replace /// <reference> directive injection (via ensure-types.cjs postbuild) with direct import of command interfaces into ChainedCommands.ts. EditorCommands is now composed as an explicit intersection of CoreCommandSignatures + all 9 extension command interfaces. Export CoreCommandSignatures from core-command-map.d.ts so core commands (insertContent, toggleMark, selectAll, etc.) are also explicitly typed. * test(types): add consumer type compatibility test with noPropertyAccessFromIndexSignature Comprehensive test covering the full public API surface: - Type shapes (Comment, FontConfig, ProofingConfig, layout types, events) - Editor/PresentationEditor constructor options with optional User fields - Command dot-access (core + extension commands) under strict index sig - exportDocx overloads with narrowed return types - loadXmlData/replaceFile with ArrayBuffer - PresentationEditor methods (layout, selection, scrolling, hit testing) - Selection handle API - Event handlers with typed payloads - SuperToolbar assignable to setToolbar - Proofing provider contract Also fixes Toolbar.setActiveEditor param type for strict function compat. * fix(types): expose type re-exports via superdoc/super-editor sub-export The super-editor facade (.d.ts) only re-exported from the v1 runtime barrel, so types like Comment, FontConfig, EditorCommands were only available from the root 'superdoc' import, not 'superdoc/super-editor'. Add re-export of the type-enriched index.ts barrel to the facade so both entry points expose the full type surface. * ci: add consumer typecheck step to CI pipeline Packs the built superdoc package, installs it in tests/consumer-typecheck, and runs tsc --noEmit with noPropertyAccessFromIndexSignature enabled. Catches type regressions that source-level typechecking misses (e.g. missing exports, broken augmentations, index signature fallbacks). * fix(types): review fixes — track-changes fallbacks, test consolidation, CI hardening - Add || '' fallbacks for user.name/email/image in track-changes mark creation to prevent undefined author metadata when User fields are optional - Consolidate overlapping consumer typecheck files: merge unique tests from index.ts into customer-scenario.ts, strip imports-main.ts to imports-only, delete redundant index.ts - Pin PR preview checkout to exact SHA instead of branch ref - Restore missing Editor import in EditorTypes.ts (lost during rebase)
1 parent 42b895e commit fc86b55

File tree

18 files changed

+968
-64
lines changed

18 files changed

+968
-64
lines changed

.github/workflows/ci-superdoc.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ jobs:
7272
- name: Typecheck
7373
run: pnpm run type-check
7474

75+
- name: Consumer typecheck
76+
run: |
77+
cd packages/superdoc && pnpm pack --pack-destination .
78+
cd ../../tests/consumer-typecheck
79+
npm install ../../packages/superdoc/superdoc-*.tgz --no-save
80+
npx tsc --noEmit
81+
7582
unit-tests:
7683
needs: build
7784
runs-on: ubuntu-latest

.github/workflows/release-superdoc.yml

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Auto-releases on push to:
22
# - main (@next channel)
33
# - stable (@latest channel)
4+
# Manual PR preview: dispatch with pr_number to publish @pr-<number>
45
name: 📦 Release superdoc
56

67
on:
@@ -18,10 +19,16 @@ on:
1819
- 'pnpm-workspace.yaml'
1920
- '!**/*.md'
2021
workflow_dispatch:
22+
inputs:
23+
pr_number:
24+
description: 'PR number to publish a preview package for (leave empty for normal release)'
25+
required: false
26+
type: number
2127

2228
permissions:
2329
contents: write
2430
packages: write
31+
pull-requests: write
2532

2633
concurrency:
2734
group: ${{ github.ref_name == 'stable' && 'release-stable-writeback' || format('{0}-{1}', github.workflow, github.ref) }}
@@ -38,13 +45,25 @@ jobs:
3845
app-id: ${{ secrets.APP_ID }}
3946
private-key: ${{ secrets.APP_PRIVATE_KEY }}
4047

48+
# PR preview: check out the PR branch
49+
- name: Get PR head ref
50+
if: inputs.pr_number
51+
id: pr
52+
env:
53+
GH_TOKEN: ${{ github.token }}
54+
run: |
55+
PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${{ inputs.pr_number }} --jq '{ref: .head.ref, sha: .head.sha}')
56+
echo "ref=$(echo $PR_DATA | jq -r .ref)" >> $GITHUB_OUTPUT
57+
echo "sha=$(echo $PR_DATA | jq -r .sha)" >> $GITHUB_OUTPUT
58+
4159
- uses: actions/checkout@v6
4260
with:
4361
fetch-depth: 0
62+
ref: ${{ steps.pr.outputs.sha || '' }}
4463
token: ${{ steps.generate_token.outputs.token }}
4564

4665
- name: Refresh stable branch head
47-
if: github.ref_name == 'stable'
66+
if: github.ref_name == 'stable' && !inputs.pr_number
4867
run: |
4968
git fetch origin "${{ github.ref_name }}" --tags
5069
git checkout -B "${{ github.ref_name }}" "origin/${{ github.ref_name }}"
@@ -81,10 +100,48 @@ jobs:
81100
- name: Install dependencies
82101
run: pnpm install
83102

103+
# PR preview: set version before build so it's baked into the package
104+
- name: Set preview version
105+
if: inputs.pr_number
106+
id: version
107+
run: |
108+
BASE_VERSION=$(node -p "require('./packages/superdoc/package.json').version")
109+
PREVIEW_VERSION="${BASE_VERSION}-pr.${{ inputs.pr_number }}.$(date +%s)"
110+
echo "version=$PREVIEW_VERSION" >> $GITHUB_OUTPUT
111+
cd packages/superdoc
112+
npm version "$PREVIEW_VERSION" --no-git-tag-version
113+
84114
- name: Build packages
85115
run: pnpm run build
86116

117+
# PR preview: publish with pr-<number> dist-tag
118+
- name: Publish PR preview
119+
if: inputs.pr_number
120+
env:
121+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
122+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
123+
run: |
124+
node scripts/publish-superdoc.cjs \
125+
--dist-tag pr-${{ inputs.pr_number }} \
126+
--skip-build
127+
128+
- name: Comment on PR
129+
if: inputs.pr_number
130+
env:
131+
GH_TOKEN: ${{ github.token }}
132+
run: |
133+
gh pr comment ${{ inputs.pr_number }} --body "$(cat <<'EOF'
134+
📦 **Preview published:** `superdoc@${{ steps.version.outputs.version }}`
135+
136+
```bash
137+
npm install superdoc@pr-${{ inputs.pr_number }}
138+
```
139+
EOF
140+
)"
141+
142+
# Normal release: semantic-release (only when NOT a PR preview)
87143
- name: Release
144+
if: ${{ !inputs.pr_number }}
88145
env:
89146
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
90147
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

packages/super-editor/src/editors/v1/components/toolbar/super-toolbar.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ import { NodeSelection } from 'prosemirror-state';
3838
* @property {string} [role='editor'] - Role of the toolbar ('editor' or 'viewer')
3939
* @property {Object} [icons] - Custom icons for toolbar items
4040
* @property {Object} [texts] - Custom texts for toolbar items
41+
* @property {import('../../core/types/EditorConfig.js').FontConfig[]} [fonts] - Font options for the font picker dropdown
42+
* @property {boolean} [hideButtons=true] - Whether to hide buttons when the editor is not focused
43+
* @property {boolean} [pagination=false] - Whether to show pagination controls
4144
* @property {string} [mode='docx'] - Editor mode
4245
* @property {string[]} [excludeItems=[]] - Items to exclude from the toolbar
4346
* @property {Object} [groups=null] - Custom groups configuration

packages/super-editor/src/editors/v1/core/Editor.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export interface SaveOptions {
213213
commentsType?: string;
214214

215215
/** Comments to include in export */
216-
comments?: Array<{ id: string; [key: string]: unknown }>;
216+
comments?: Comment[];
217217

218218
/** Highlight color for fields */
219219
fieldsHighlightColor?: string | null;
@@ -2235,7 +2235,22 @@ export class Editor extends EventEmitter<EditorEventMap> {
22352235
* - [3] fonts - Object containing font files from the DOCX
22362236
*/
22372237
static async loadXmlData(
2238-
fileSource: File | Blob | Buffer,
2238+
fileSource: File | Blob | Buffer | ArrayBuffer,
2239+
isNode?: boolean,
2240+
options?: { password?: string },
2241+
): Promise<
2242+
[DocxFileEntry[], Record<string, unknown>, Record<string, unknown>, Record<string, unknown>, Uint8Array | null]
2243+
>;
2244+
static async loadXmlData(
2245+
fileSource: File | Blob | Buffer | ArrayBuffer | null | undefined,
2246+
isNode?: boolean,
2247+
options?: { password?: string },
2248+
): Promise<
2249+
| [DocxFileEntry[], Record<string, unknown>, Record<string, unknown>, Record<string, unknown>, Uint8Array | null]
2250+
| undefined
2251+
>;
2252+
static async loadXmlData(
2253+
fileSource: File | Blob | Buffer | ArrayBuffer | null | undefined,
22392254
isNode: boolean = false,
22402255
options?: { password?: string },
22412256
): Promise<
@@ -3707,11 +3722,11 @@ export class Editor extends EventEmitter<EditorEventMap> {
37073722
/**
37083723
* Replace the current file
37093724
*/
3710-
async replaceFile(newFile: File | Blob | Buffer, options?: { password?: string }): Promise<void> {
3725+
async replaceFile(newFile: File | Blob | Buffer | ArrayBuffer, options?: { password?: string }): Promise<void> {
37113726
this.setOptions({ annotations: true });
37123727
const [docx, media, mediaFiles, fonts, decryptedData] = (await Editor.loadXmlData(newFile, false, options))!;
37133728
this.setOptions({
3714-
fileSource: decryptedData ?? newFile,
3729+
fileSource: decryptedData ?? (newFile instanceof ArrayBuffer ? new Blob([newFile]) : newFile),
37153730
content: docx,
37163731
media,
37173732
mediaFiles,

packages/super-editor/src/editors/v1/core/commands/core-command-map.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ type CoreCommandNames =
5555
| 'insertTableAt'
5656
| 'getSelectionMarks';
5757

58-
type CoreCommandSignatures = {
58+
export type CoreCommandSignatures = {
5959
[K in CoreCommandNames]: ExtractCommandSignature<(typeof CoreCommandExports)[K]>;
6060
};
6161

packages/super-editor/src/editors/v1/core/types/ChainedCommands.ts

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@ import type { Transaction, EditorState } from 'prosemirror-state';
22
import type { EditorView } from 'prosemirror-view';
33
import type { Editor } from '../Editor.js';
44

5+
// Command interfaces — imported directly for reliable cross-package typing.
6+
// Module augmentation doesn't survive the npm package boundary, so we
7+
// compose EditorCommands as an explicit intersection of all command interfaces.
8+
import type { CoreCommandSignatures } from '../commands/core-command-map.js';
9+
import type { CommentCommands } from '../../extensions/types/comment-commands.js';
10+
import type { FormattingCommandAugmentations } from '../../extensions/types/formatting-commands.js';
11+
import type { HistoryLinkTableCommandAugmentations } from '../../extensions/types/history-link-table-commands.js';
12+
import type { SpecializedCommandAugmentations } from '../../extensions/types/specialized-commands.js';
13+
import type { ParagraphCommands } from '../../extensions/types/paragraph-commands.js';
14+
import type { BlockNodeCommands } from '../../extensions/types/block-node-commands.js';
15+
import type { ImageCommands } from '../../extensions/types/image-commands.js';
16+
import type { MiscellaneousCommands } from '../../extensions/types/miscellaneous-commands.js';
17+
import type { TrackChangesCommands } from '../../extensions/types/track-changes-commands.js';
18+
519
/**
620
* Map of built-in command names to their parameter signatures.
7-
* Extensions can augment this interface to add more precise types.
8-
* Currently empty because built-in focus/blur are exposed directly on Editor,
9-
* not via `editor.commands`. Populate here when core commands are added.
21+
* Populated via core-command-map.d.ts module augmentation.
1022
*/
1123
export interface CoreCommandMap {}
1224

1325
/**
1426
* Map of extension command names to their parameter signatures.
15-
* Each extension should augment this interface when adding typed commands.
16-
*/
17-
/**
18-
* Map of extension command names to their parameter signatures.
19-
* Extensions should augment this interface via module augmentation, e.g.:
20-
*
21-
* ```ts
22-
* declare module '@core/types/ChainedCommands.js' {
23-
* interface ExtensionCommandMap {
24-
* setFontSize: (fontSize: string | number) => boolean;
25-
* }
26-
* }
27-
* ```
27+
* Kept for backward compat with any external module augmentations.
2828
*/
2929
export interface ExtensionCommandMap {}
3030

@@ -98,9 +98,25 @@ export type CoreCommands = Pick<KnownCommandRecord, keyof CoreCommandMap>;
9898
export type ExtensionCommands = Pick<KnownCommandRecord, keyof ExtensionCommandMap>;
9999

100100
/**
101-
* All available editor commands
101+
* All available editor commands.
102+
*
103+
* Composed from explicit imports of each command interface for reliable
104+
* cross-package typing (module augmentation doesn't survive the npm boundary).
105+
* The Record<string, AnyCommand> fallback allows dynamic/plugin commands.
102106
*/
103-
export type EditorCommands = CoreCommands & ExtensionCommands & Record<string, AnyCommand>;
107+
export type EditorCommands = CoreCommands &
108+
ExtensionCommands &
109+
CoreCommandSignatures &
110+
CommentCommands &
111+
FormattingCommandAugmentations &
112+
HistoryLinkTableCommandAugmentations &
113+
SpecializedCommandAugmentations &
114+
ParagraphCommands &
115+
BlockNodeCommands &
116+
ImageCommands &
117+
MiscellaneousCommands &
118+
TrackChangesCommands &
119+
Record<string, AnyCommand>;
104120

105121
/**
106122
* Command props made available to every command handler.

packages/super-editor/src/editors/v1/core/types/EditorConfig.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,39 @@ export type LinkPopoverResolution =
8484
*/
8585
export type LinkPopoverResolver = (ctx: LinkPopoverContext) => LinkPopoverResolution | null | undefined;
8686

87+
/**
88+
* Configuration for a font option in the toolbar font picker.
89+
*
90+
* Each entry represents a selectable font that appears in the toolbar dropdown.
91+
* The `props.style.fontFamily` value is applied to text when the font is selected.
92+
*/
93+
export interface FontConfig {
94+
/** Unique key identifying this font */
95+
key: string;
96+
/** Display label shown in the font picker dropdown */
97+
label: string;
98+
/** Font weight (e.g. 400 for normal, 700 for bold) */
99+
fontWeight?: number;
100+
/** CSS properties applied when this font is selected */
101+
props?: {
102+
style?: {
103+
fontFamily?: string;
104+
};
105+
};
106+
}
107+
87108
/**
88109
* User information for collaboration
89110
*/
90111
export interface User {
91112
/** The user's name */
92-
name: string;
113+
name?: string;
93114

94115
/** The user's email */
95-
email: string;
116+
email?: string;
96117

97118
/** The user's photo URL */
98-
image: string | null;
119+
image?: string | null;
99120

100121
/**
101122
* Explicit permission-range principal identifiers for this user.

packages/super-editor/src/editors/v1/core/types/EditorEvents.ts

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,63 @@ export interface FontsResolvedPayload {
1616
}
1717

1818
/**
19-
* Comment data structure
19+
* A structured element within a comment body.
20+
*
21+
* Comment bodies are stored as an array of elements, each representing
22+
* a paragraph or nested content node. This mirrors ProseMirror-style
23+
* node structure used throughout SuperDoc.
24+
*/
25+
export interface CommentElement {
26+
/** Node type (e.g. 'paragraph', 'text') */
27+
type: string;
28+
/** Text content for leaf nodes */
29+
text?: string;
30+
/** Nested child elements */
31+
content?: CommentElement[];
32+
/** Additional element properties */
33+
[key: string]: unknown;
34+
}
35+
36+
/**
37+
* A comment object as emitted through editor events and accepted by `exportDocx()`.
38+
*
39+
* This is the canonical comment shape used across SuperDoc:
40+
* - Emitted via `commentsLoaded` and `commentsUpdate` events
41+
* - Accepted by `exportDocx({ comments })` for saving
42+
* - Produced by DOCX import in SuperConverter
2043
*/
2144
export interface Comment {
22-
id: string;
45+
/** Unique identifier for this comment */
46+
commentId: string;
47+
/** Timestamp when the comment was created (ms since epoch) */
48+
createdTime: number | null;
49+
/** Display name of the comment author */
50+
creatorName: string | null;
51+
/** Email address of the comment author */
52+
creatorEmail: string | null;
53+
/** Avatar URL of the comment author */
54+
creatorImage?: string | null;
55+
/** Structured body content of the comment */
56+
elements: CommentElement[];
57+
/** Whether the comment thread is resolved/done */
58+
isDone: boolean;
59+
/** Parent comment ID for threaded replies */
60+
parentCommentId?: string | null;
61+
/** Original ID from the imported DOCX (used for round-trip fidelity) */
62+
importedId?: string | null;
63+
/** ProseMirror JSON representation of the comment body */
64+
commentJSON?: unknown;
65+
/** Plain text content of the comment */
66+
commentText?: string | null;
67+
/** Whether this is an internal/private comment */
68+
isInternal?: boolean;
69+
/** Whether this comment is associated with a tracked change */
70+
trackedChange?: boolean;
71+
/** The document/file ID this comment belongs to */
72+
fileId?: string | null;
73+
/** The document ID this comment belongs to */
74+
documentId?: string | null;
75+
/** Additional fields from the host application */
2376
[key: string]: unknown;
2477
}
2578

@@ -51,6 +104,9 @@ export interface PaginationPayload {
51104
[key: string]: unknown;
52105
}
53106

107+
/**
108+
* Payload for document mode change events
109+
*/
54110
export interface DocumentModeChangePayload {
55111
editor: Editor;
56112
documentMode: 'editing' | 'viewing' | 'suggesting';

packages/super-editor/src/editors/v1/extensions/track-changes/trackChangesHelpers/addMarkStep.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ export const addMarkStep = ({ state, step, newTr, doc, user, date }) => {
105105
const newFormatMark = state.schema.marks[TrackFormatMarkName].create({
106106
id: wid,
107107
sourceId: formatChangeMark?.attrs?.sourceId || '',
108-
author: user.name,
109-
authorEmail: user.email,
110-
authorImage: user.image,
108+
author: user.name || '',
109+
authorEmail: user.email || '',
110+
authorImage: user.image || '',
111111
date,
112112
before,
113113
after,

packages/super-editor/src/editors/v1/extensions/track-changes/trackChangesHelpers/markDeletion.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ export const markDeletion = ({ tr, from, to, user, date, id: providedId }) => {
4444

4545
const deletionMark = tr.doc.type.schema.marks[TrackDeleteMarkName].create({
4646
id,
47-
author: user.name,
48-
authorEmail: user.email,
49-
authorImage: user.image,
47+
author: user.name || '',
48+
authorEmail: user.email || '',
49+
authorImage: user.image || '',
5050
date,
5151
});
5252

0 commit comments

Comments
 (0)