Skip to content

Commit f001315

Browse files
authored
feat: add proofing infrastructure to editor (spell check) (#2548)
* feat: add proofing infrastructure to editor * fix: stabilize presentation-editor proofing while typing * feat: add presentation-editor proofing platform and spell-check examples * chore: docs updates * chore: update docs to link to examples * chore: add examples of spell check libraries you can use * fix(proofing): resolve scheduling, mark-preservation, and cleanup regressions
1 parent ebcf398 commit f001315

80 files changed

Lines changed: 4985 additions & 14 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/core/superdoc/configuration.mdx

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,81 @@ See [Surfaces](/core/superdoc/surfaces) for the full API and examples.
290290
<Warning>**Deprecated** — Use `modules.contextMenu` instead. See the [Context Menu module](/modules/context-menu) for configuration options.</Warning>
291291
</ParamField>
292292

293+
## Proofing
294+
295+
Provider-based spell check and proofing for the layout-engine editor surface.
296+
297+
```javascript
298+
new SuperDoc({
299+
selector: '#editor',
300+
document: 'contract.docx',
301+
proofing: {
302+
enabled: true,
303+
provider,
304+
defaultLanguage: 'en-US',
305+
maxSuggestions: 3,
306+
}
307+
});
308+
```
309+
310+
<Note>
311+
Bring your own provider. SuperDoc handles the editor UI, while your app controls the proofing engine or API. See [Proofing](/guides/general/proofing) and [Custom proofing provider](/guides/general/custom-proofing-provider).
312+
</Note>
313+
314+
<Info>
315+
Proofing is available when the layout engine is active. In `print` layout this works out of the box. In `web` layout, keep the layout engine enabled with `layoutEngineOptions.flowMode: 'semantic'`.
316+
</Info>
317+
318+
<Note>
319+
In the current UI, only spelling issues are rendered. Grammar and style issues can still be returned by your provider, but they are not shown yet.
320+
</Note>
321+
322+
<ParamField path="proofing" type="Object">
323+
Proofing configuration
324+
325+
<Expandable title="properties" defaultOpen>
326+
<ParamField path="proofing.enabled" type="boolean" default="false">
327+
Enable or disable proofing
328+
</ParamField>
329+
<ParamField path="proofing.provider" type="Object">
330+
Provider instance with an `id` and `check()` function. When the provider returns `replacements`, SuperDoc shows them as direct context-menu actions.
331+
</ParamField>
332+
<ParamField path="proofing.defaultLanguage" type="string | null">
333+
Fallback language for segments without a resolved language
334+
</ParamField>
335+
<ParamField path="proofing.debounceMs" type="number" default="500">
336+
Debounce delay after edits before rechecking
337+
</ParamField>
338+
<ParamField path="proofing.maxSuggestions" type="number">
339+
Maximum replacement suggestions shown per issue
340+
</ParamField>
341+
<ParamField path="proofing.visibleFirst" type="boolean" default="true">
342+
Prioritize checking visible pages first
343+
</ParamField>
344+
<ParamField path="proofing.allowIgnoreWord" type="boolean" default="true">
345+
Show `Ignore` in the context menu
346+
</ParamField>
347+
<ParamField path="proofing.ignoredWords" type="string[]">
348+
Words to suppress from displayed proofing results. This is a SuperDoc-side suppression list, not a provider dictionary mutation.
349+
</ParamField>
350+
<ParamField path="proofing.timeoutMs" type="number" default="10000">
351+
Provider timeout per request in milliseconds
352+
</ParamField>
353+
<ParamField path="proofing.maxConcurrentRequests" type="number" default="2">
354+
Maximum number of provider requests in flight at the same time
355+
</ParamField>
356+
<ParamField path="proofing.maxSegmentsPerBatch" type="number" default="20">
357+
Maximum number of segments included in a single provider request
358+
</ParamField>
359+
<ParamField path="proofing.onProofingError" type="function">
360+
Called when the provider times out, returns invalid ranges, or fails
361+
</ParamField>
362+
<ParamField path="proofing.onStatusChange" type="function">
363+
Called with `idle`, `checking`, `disabled`, or `degraded`
364+
</ParamField>
365+
</Expandable>
366+
</ParamField>
367+
293368
## Appearance
294369

295370
<ParamField path="title" type="string" default="'SuperDoc'">
@@ -310,7 +385,7 @@ See [Surfaces](/core/superdoc/surfaces) for the full API and examples.
310385
- `'web'` - Content reflows to fit container width
311386
</ParamField>
312387
</Expandable>
313-
<Note>Use `'web'` for mobile devices and WCAG AA reflow compliance (Success Criterion 1.4.10). When set to `'web'`, the layout engine is automatically disabled.</Note>
388+
<Note>Use `'web'` for mobile devices and WCAG AA reflow compliance (Success Criterion 1.4.10). If you also need layout-engine-powered features such as proofing in web layout, set `layoutEngineOptions.flowMode: 'semantic'`.</Note>
314389

315390
```javascript
316391
viewOptions: { layout: 'web' }

apps/docs/docs.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@
242242
"guides/general/storage",
243243
"guides/general/stable-navigation",
244244
"guides/general/custom-themes",
245+
{
246+
"group": "Proofing",
247+
"pages": ["guides/general/proofing", "guides/general/custom-proofing-provider"]
248+
},
245249
{
246250
"group": "Collaboration",
247251
"pages": [

apps/docs/guides/general/accessibility.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ This enables:
4141
- Document structure (headings, lists, tables) remains intact
4242
- Ideal for mobile devices and responsive web applications
4343

44-
<Note>When using web layout mode, the layout engine is automatically disabled to allow content reflow.</Note>
44+
<Note>If you also need layout-engine-powered features such as proofing in web layout, set `layoutEngineOptions.flowMode: 'semantic'`.</Note>
4545

4646
## Keyboard navigation
4747

@@ -150,4 +150,4 @@ Accessibility features tested with:
150150
- NVDA (Windows)
151151
- JAWS (Windows)
152152
- VoiceOver (macOS)
153-
- Chrome, Firefox, Safari, Edge (latest versions)
153+
- Chrome, Firefox, Safari, Edge (latest versions)
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
---
2+
title: Custom proofing provider
3+
sidebarTitle: Custom provider
4+
keywords: "custom proofing provider, spellcheck api, proofing provider, spelling api"
5+
---
6+
7+
SuperDoc uses a provider-agnostic proofing contract. Your provider receives text segments and returns structured issues. SuperDoc owns the editor UI and document-range mapping.
8+
9+
## Provider shape
10+
11+
```javascript
12+
const provider = {
13+
id: 'custom-proofing',
14+
async check({ documentId, defaultLanguage, maxSuggestions, segments, signal }) {
15+
return { issues: [] };
16+
},
17+
dispose() {},
18+
};
19+
```
20+
21+
If you want TypeScript help, the proofing types are available from `superdoc/super-editor`.
22+
23+
## Request model
24+
25+
Each `check()` call receives:
26+
27+
- `documentId` when available
28+
- `defaultLanguage` as the configured fallback
29+
- `maxSuggestions` when set
30+
- `segments`, where each segment includes `id`, `text`, `language`, and `metadata`
31+
- `signal`, which you should pass through to your network call or worker task
32+
33+
### Segment shape
34+
35+
```javascript
36+
{
37+
id: 'segment-123',
38+
text: 'teh contract shall begin on Monday',
39+
language: 'en-US',
40+
metadata: {
41+
surface: 'body',
42+
blockId: 'paragraph-8',
43+
pageIndex: 0
44+
}
45+
}
46+
```
47+
48+
## Offset rules
49+
50+
Offsets must be zero-based UTF-16 code-unit offsets into the exact `segment.text` string that SuperDoc sends.
51+
52+
- `start` is inclusive
53+
- `end` is exclusive
54+
55+
For example, if `segment.text === 'teh contract'`, the misspelling `teh` should be returned as:
56+
57+
```javascript
58+
{
59+
segmentId: 'segment-123',
60+
start: 0,
61+
end: 3,
62+
kind: 'spelling',
63+
replacements: ['the']
64+
}
65+
```
66+
67+
## Returning issues
68+
69+
Each issue must include:
70+
71+
- `segmentId`
72+
- `start`
73+
- `end`
74+
- `kind`
75+
76+
Optional fields:
77+
78+
- `message`
79+
- `replacements` - shown as direct context-menu replacement actions when present
80+
- `ruleId`
81+
- `providerMeta`
82+
83+
<Note>
84+
Only `kind: 'spelling'` is rendered in the v1 UI. Providers can return grammar or style issues too, but they are not displayed yet.
85+
</Note>
86+
87+
## Example: call your own API
88+
89+
```javascript
90+
const provider = {
91+
id: 'company-proofing-api',
92+
async check({ documentId, defaultLanguage, maxSuggestions, segments, signal }) {
93+
const response = await fetch('/api/proofing/check', {
94+
method: 'POST',
95+
headers: { 'Content-Type': 'application/json' },
96+
body: JSON.stringify({
97+
documentId,
98+
defaultLanguage,
99+
maxSuggestions,
100+
segments,
101+
}),
102+
signal,
103+
});
104+
105+
if (!response.ok) {
106+
throw new Error(`Proofing request failed: ${response.status}`);
107+
}
108+
109+
const data = await response.json();
110+
111+
return {
112+
issues: data.issues.map((issue) => ({
113+
segmentId: issue.segmentId,
114+
start: issue.start,
115+
end: issue.end,
116+
kind: issue.kind || 'spelling',
117+
message: issue.message,
118+
replacements: issue.replacements || [],
119+
ruleId: issue.ruleId,
120+
})),
121+
};
122+
},
123+
};
124+
```
125+
126+
## Best practices
127+
128+
- Honor `signal` so cancelled checks stop immediately
129+
- Use `maxSuggestions` when your provider can limit results server-side
130+
- Return `kind: 'spelling'` for issues you want rendered in the current UI
131+
- Keep the provider pure: no DOM access, no editor assumptions
132+
- Batch segments when your backend supports it
133+
134+
<Note>
135+
`Ignore` is a SuperDoc-side suppression in v1, not a provider dictionary mutation. If you want ignored words to persist across reloads, store your own list and pass it back through `proofing.ignoredWords` the next time you create the editor.
136+
</Note>
137+
138+
## Troubleshooting
139+
140+
If proofing markers do not appear:
141+
142+
- Confirm `proofing.enabled` is `true`
143+
- Confirm the provider returns `kind: 'spelling'`
144+
- Confirm `start` and `end` offsets match the exact `segment.text` string
145+
- Confirm the layout engine is active
146+
- Use `proofing.onProofingError` to surface validation or provider failures in your app
147+
148+
## Working examples
149+
150+
If you want a concrete starting point, see the repo examples:
151+
152+
- [Typo.js example](https://github.com/superdoc-dev/superdoc/tree/main/examples/features/spell-check/typo-js) - Minimal local spell-check provider
153+
- [LanguageTool self-hosted example](https://github.com/superdoc-dev/superdoc/tree/main/examples/features/spell-check/language-tool-self-hosted) - Minimal self-hosted HTTP provider
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
---
2+
title: Spell Check & Proofing
3+
sidebarTitle: Proofing
4+
keywords: "spell check, spellcheck, proofing, spelling suggestions, custom proofing provider"
5+
---
6+
7+
SuperDoc supports proofing on the layout-engine editor surface. Because SuperDoc renders DOCX content through its own document model and layout engine, browser-native spellcheck is not used. Instead, you provide a proofing provider and SuperDoc handles the UI: inline spelling markers, direct right-click replacement suggestions, and local ignore flows.
8+
9+
## Quick start
10+
11+
```javascript
12+
const provider = {
13+
id: 'my-proofing',
14+
async check({ segments, maxSuggestions, signal }) {
15+
const response = await fetch('/api/proofing/check', {
16+
method: 'POST',
17+
headers: { 'Content-Type': 'application/json' },
18+
body: JSON.stringify({ segments, maxSuggestions }),
19+
signal,
20+
});
21+
22+
if (!response.ok) {
23+
throw new Error(`Proofing request failed: ${response.status}`);
24+
}
25+
26+
return response.json();
27+
},
28+
};
29+
30+
new SuperDoc({
31+
selector: '#editor',
32+
document: 'contract.docx',
33+
proofing: {
34+
enabled: true,
35+
provider,
36+
defaultLanguage: 'en-US',
37+
maxSuggestions: 3,
38+
allowIgnoreWord: true,
39+
},
40+
});
41+
```
42+
43+
## What SuperDoc handles
44+
45+
- Extracting proofable text from the document model
46+
- Checking visible content first, then continuing in the background
47+
- Rendering spelling markers on the layout-engine surface
48+
- Showing provider replacement suggestions as direct context-menu actions
49+
- Supporting `Ignore` for local suppression
50+
51+
## What you provide
52+
53+
- A provider object with an `id` and `check()` function
54+
- Any backend API, on-device dictionary engine, or proxy service you want to use
55+
- Optional persistence for ignored words if you want them to survive reloads
56+
57+
<Note>
58+
`Ignore` is a SuperDoc-side suppression in v1. It only persists if your app stores ignored words and passes them back through `proofing.ignoredWords`.
59+
</Note>
60+
61+
<Note>
62+
SuperDoc does not bundle a proofing engine or send document content anywhere by default. Your provider decides where proofing runs.
63+
</Note>
64+
65+
## Where proofing works
66+
67+
Proofing is available when the layout engine is active.
68+
69+
- In standard print layout, that works out of the box
70+
- In web layout, use `layoutEngineOptions.flowMode: 'semantic'` so the layout engine stays active on a continuous surface
71+
72+
```javascript
73+
new SuperDoc({
74+
selector: '#editor',
75+
document: 'contract.docx',
76+
viewOptions: { layout: 'web' },
77+
layoutEngineOptions: { flowMode: 'semantic' },
78+
proofing: {
79+
enabled: true,
80+
provider,
81+
},
82+
});
83+
```
84+
85+
## Current scope
86+
87+
- Spelling issues are rendered in the UI in v1
88+
- Grammar and style issue kinds are accepted by the provider contract but are not rendered yet
89+
- Proofing state is runtime UI state and is not written into the DOCX
90+
- Proofing is not part of the Document API in v1
91+
92+
## Provider options
93+
94+
SuperDoc does not include a spell-check engine. You bring your own provider.
95+
96+
- [Typo.js](https://github.com/cfinke/Typo.js) - Simple local browser-based spell check. Good for lightweight demos, privacy-sensitive flows, and no-backend setups. See the [Typo.js example](https://github.com/superdoc-dev/superdoc/tree/main/examples/features/spell-check/typo-js).
97+
- [LanguageTool](https://languagetool.org/) - API or self-hosted proofing with broader language support and a path to grammar checking later. See the [LanguageTool self-hosted example](https://github.com/superdoc-dev/superdoc/tree/main/examples/features/spell-check/language-tool-self-hosted).
98+
- [hunspell-asm](https://www.npmjs.com/package/hunspell-asm) - A stronger local/offline WebAssembly Hunspell option if you want a more serious on-device spell-check provider than pure JavaScript dictionaries.
99+
100+
## Examples
101+
102+
See the working repo examples for two complete provider integrations:
103+
104+
- [Typo.js example](https://github.com/superdoc-dev/superdoc/tree/main/examples/features/spell-check/typo-js) - Local, browser-only spell check with no backend
105+
- [LanguageTool self-hosted example](https://github.com/superdoc-dev/superdoc/tree/main/examples/features/spell-check/language-tool-self-hosted) - Self-hosted HTTP spell check with Docker
106+
107+
## Next steps
108+
109+
- See [Configuration](/core/superdoc/configuration#proofing) for all options
110+
- See [Custom proofing provider](/guides/general/custom-proofing-provider) to connect your own service

0 commit comments

Comments
 (0)