smartrte-react 0.3.5 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +106 -14
- package/dist/adapters/domChecklistCommandBridge.js +1 -1
- package/dist/adapters/domCommandBridge.d.ts +2 -2
- package/dist/adapters/domCommandBridge.js +2 -2
- package/dist/adapters/domInlineAtomCommandBridge.d.ts +10 -9
- package/dist/adapters/domInlineAtomCommandBridge.js +154 -50
- package/dist/adapters/domInlineImageCommandBridge.d.ts +7 -7
- package/dist/adapters/domInlineImageCommandBridge.js +6 -72
- package/dist/adapters/domMoveCommandBridge.js +1 -1
- package/dist/adapters/domSelectionBridge.d.ts +3 -3
- package/dist/adapters/domSmartDocument.d.ts +4 -4
- package/dist/adapters/domSmartDocument.js +2 -2
- package/dist/adapters/domTableCommandBridge.d.ts +42 -16
- package/dist/adapters/domTableCommandBridge.js +203 -173
- package/dist/adapters/internalFlags.d.ts +0 -8
- package/dist/adapters/internalFlags.js +0 -24
- package/dist/adapters/legacyListShadowComparator.d.ts +1 -1
- package/dist/adapters/legacyListShadowComparator.js +1 -1
- package/dist/adapters/pdfPrint.d.ts +8 -0
- package/dist/adapters/pdfPrint.js +22 -0
- package/dist/adapters/shadowMode.d.ts +3 -3
- package/dist/adapters/shadowMode.js +1 -1
- package/dist/canonicalEditorRuntime.d.ts +53 -1
- package/dist/canonicalEditorRuntime.js +61 -4
- package/dist/capabilityPresets.d.ts +27 -0
- package/dist/capabilityPresets.js +21 -0
- package/dist/collabTransport.d.ts +66 -0
- package/dist/collabTransport.js +1 -0
- package/dist/commentProvider.d.ts +24 -0
- package/dist/commentProvider.js +1 -0
- package/dist/components/CanonicalAuthorityEditor.d.ts +35 -1
- package/dist/components/CanonicalAuthorityEditor.js +1086 -112
- package/dist/components/ClassicEditorAuthority.d.ts +93 -22
- package/dist/components/ClassicEditorAuthority.js +11 -43
- package/dist/components/ColorPickerPopover.d.ts +39 -0
- package/dist/components/ColorPickerPopover.js +285 -0
- package/dist/components/CommentMarkers.d.ts +25 -0
- package/dist/components/CommentMarkers.js +105 -0
- package/dist/components/CommentThreadPanel.d.ts +26 -0
- package/dist/components/CommentThreadPanel.js +47 -0
- package/dist/components/ContextMenu.d.ts +23 -0
- package/dist/components/ContextMenu.js +141 -0
- package/dist/components/FormulaLibraryPopover.d.ts +16 -0
- package/dist/components/FormulaLibraryPopover.js +107 -0
- package/dist/components/LinkEditorPopover.d.ts +3 -1
- package/dist/components/LinkEditorPopover.js +22 -6
- package/dist/components/MediaManager.d.ts +4 -20
- package/dist/components/MediaManager.js +35 -6
- package/dist/components/MediaOverlay.d.ts +36 -0
- package/dist/components/MediaOverlay.js +163 -0
- package/dist/components/SpecialCharacterPopover.d.ts +18 -0
- package/dist/components/SpecialCharacterPopover.js +99 -0
- package/dist/components/StructuralSuggestionMarkers.d.ts +20 -0
- package/dist/components/StructuralSuggestionMarkers.js +56 -0
- package/dist/components/SuggestionPanel.d.ts +26 -0
- package/dist/components/SuggestionPanel.js +33 -0
- package/dist/components/TableBorderPopover.d.ts +59 -0
- package/dist/components/TableBorderPopover.js +147 -0
- package/dist/components/TableResizeHandles.d.ts +25 -0
- package/dist/components/TableResizeHandles.js +301 -0
- package/dist/components/TableSizePickerPopover.d.ts +16 -0
- package/dist/components/TableSizePickerPopover.js +126 -0
- package/dist/components/ToolbarPrimitives.d.ts +129 -0
- package/dist/components/ToolbarPrimitives.js +146 -0
- package/dist/components/VersionHistoryPanel.d.ts +9 -0
- package/dist/components/VersionHistoryPanel.js +157 -0
- package/dist/formulaLibrary.d.ts +31 -0
- package/dist/formulaLibrary.js +90 -0
- package/dist/index.d.ts +22 -24
- package/dist/index.js +14 -11
- package/dist/mediaManagerAdapter.d.ts +12 -0
- package/dist/mediaManagerAdapter.js +34 -0
- package/dist/mediaProvider.d.ts +21 -1
- package/dist/specialCharacters.d.ts +22 -0
- package/dist/specialCharacters.js +86 -0
- package/dist/standalone/classic-editor-embed.js +19 -12
- package/dist/suggestionProvider.d.ts +17 -0
- package/dist/suggestionProvider.js +1 -0
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +133 -1
- package/dist/versionProvider.d.ts +29 -0
- package/dist/versionProvider.js +1 -0
- package/package.json +28 -14
- package/dist/adapters/canonicalInlineCommandBridge.d.ts +0 -15
- package/dist/adapters/canonicalInlineCommandBridge.js +0 -269
- package/dist/adapters/canonicalListCommandBridge.d.ts +0 -26
- package/dist/adapters/canonicalListCommandBridge.js +0 -430
- package/dist/adapters/coreBold.d.ts +0 -5
- package/dist/adapters/coreBold.js +0 -3
- package/dist/adapters/documentFormats.d.ts +0 -26
- package/dist/adapters/documentFormats.js +0 -49
- package/dist/adapters/docxFormat.d.ts +0 -5
- package/dist/adapters/docxFormat.js +0 -272
- package/dist/adapters/domBlockCommandBridge.d.ts +0 -19
- package/dist/adapters/domBlockCommandBridge.js +0 -79
- package/dist/adapters/inlineMarkCoreExecution.d.ts +0 -9
- package/dist/adapters/inlineMarkCoreExecution.js +0 -25
- package/dist/adapters/pdfFormat.d.ts +0 -23
- package/dist/adapters/pdfFormat.js +0 -204
- package/dist/adapters/portableDocxAtoms.d.ts +0 -4
- package/dist/adapters/portableDocxAtoms.js +0 -58
- package/dist/adapters/styledDocxFormat.d.ts +0 -8
- package/dist/adapters/styledDocxFormat.js +0 -190
- package/dist/builtInFormatDefinitions.d.ts +0 -5
- package/dist/builtInFormatDefinitions.js +0 -82
- package/dist/canonicalAuthorityFlag.d.ts +0 -32
- package/dist/canonicalAuthorityFlag.js +0 -49
- package/dist/canonicalClipboardRuntime.d.ts +0 -15
- package/dist/canonicalClipboardRuntime.js +0 -76
- package/dist/components/ClassicEditor.d.ts +0 -76
- package/dist/components/ClassicEditor.js +0 -6729
- package/dist/editorController.d.ts +0 -78
- package/dist/editorController.js +0 -298
- package/dist/formatFidelity.d.ts +0 -14
- package/dist/formatFidelity.js +0 -107
- package/dist/formatRuntime.d.ts +0 -50
- package/dist/formatRuntime.js +0 -20
- package/dist/pluginRuntime.d.ts +0 -68
- package/dist/pluginRuntime.js +0 -78
- package/dist/standalone/editor.js +0 -1068
- package/dist/test-harness/atomShadowComparator.d.ts +0 -14
- package/dist/test-harness/atomShadowComparator.js +0 -63
- package/dist/test-harness/blockShadowComparator.d.ts +0 -30
- package/dist/test-harness/blockShadowComparator.js +0 -140
- package/dist/test-harness/clipboardShadowComparator.d.ts +0 -12
- package/dist/test-harness/clipboardShadowComparator.js +0 -46
- package/dist/test-harness/inlineShadowComparator.d.ts +0 -32
- package/dist/test-harness/inlineShadowComparator.js +0 -152
- package/dist/test-harness/legacyAtomEngine.d.ts +0 -10
- package/dist/test-harness/legacyAtomEngine.js +0 -15
- package/dist/test-harness/legacyBlockEngine.d.ts +0 -31
- package/dist/test-harness/legacyBlockEngine.js +0 -31
- package/dist/test-harness/legacyClipboardEngine.d.ts +0 -14
- package/dist/test-harness/legacyClipboardEngine.js +0 -67
- package/dist/test-harness/legacyInlineEngine.d.ts +0 -19
- package/dist/test-harness/legacyInlineEngine.js +0 -49
- package/dist/test-harness/legacyTableEngine.d.ts +0 -12
- package/dist/test-harness/legacyTableEngine.js +0 -25
- package/dist/test-harness/tableShadowComparator.d.ts +0 -29
- package/dist/test-harness/tableShadowComparator.js +0 -101
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.1
|
|
4
|
+
|
|
5
|
+
**BREAKING CHANGE.** `ClassicEditor` is now unconditionally the canonical, schema-driven editor (`CanonicalAuthorityEditor` underneath). The DOM-authoritative legacy editor and its rollback path have been fully removed; every published version through 0.3.4 defaulted to the DOM-authoritative implementation, so this changes real runtime behavior for every existing consumer, not just internals.
|
|
6
|
+
|
|
7
|
+
- Promote the canonical editing engine to sole production authority. Removed `LegacyClassicEditor` (the previous default), the runtime rollback flag, and all four of its DOM-authoritative command bridges.
|
|
8
|
+
- Add a documented, tested headless facade: `CanonicalEditorRuntime` / `SmartEditorHandle` (`createCanonicalEditorRuntime`) has zero React dependency and can drive a full editing session — mount, edit, checkpoint/restore, get/replace value — against a plain DOM element with no React runtime involved.
|
|
9
|
+
- Wire DOCX, PDF, and Markdown import/export to `smartrte-core`'s new format codecs, replacing the previous ad hoc, partially DOM-dependent adapters.
|
|
10
|
+
- Add live KaTeX rendering for formula content, including accessible MathML output.
|
|
11
|
+
- Depends on `smartrte-core@1.0.0-beta.1`.
|
|
12
|
+
|
|
3
13
|
## 0.3.5
|
|
4
14
|
|
|
5
15
|
- Remove the always-on drag handle that appeared next to every line, list item, and table.
|
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ pnpm add smartrte-react
|
|
|
41
41
|
## 🚀 Quick Start
|
|
42
42
|
|
|
43
43
|
### 🎮 Live Demo
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
Try the editor instantly in your browser:
|
|
46
46
|
|
|
47
47
|
- **[Live Demo](https://playground-k9l44ah7t-ayush1852017s-projects.vercel.app/)** (Deployed Version)
|
|
@@ -87,10 +87,37 @@ export default App;
|
|
|
87
87
|
| `table` | `boolean` | `true` | Enable/disable table functionality |
|
|
88
88
|
| `media` | `boolean` | `true` | Enable/disable media/image functionality |
|
|
89
89
|
| `formula` | `boolean` | `true` | Enable/disable formula/LaTeX functionality |
|
|
90
|
+
| `features` | `CoreFeatureConfig` | standard preset | Enable or disable individual core plugins |
|
|
91
|
+
| `plugins` | `ReactEditorPlugin[]` | standard preset | Use an exact custom plugin set; replaces the standard preset |
|
|
92
|
+
| `formats` | `EditorFormatConfig` | all built-ins | Enable or disable HTML, Markdown, DOCX, and PDF adapters |
|
|
93
|
+
| `formatDefinitions` | `EditorFormatDefinition[]` | built-ins | Add or replace import/export adapters |
|
|
90
94
|
| `mediaManager` | `MediaManagerAdapter` | `undefined` | Custom media manager for handling images |
|
|
95
|
+
| `fonts` | `{ name: string; value: string }[]` | Default web-safe fonts | Custom font list for the toolbar |
|
|
96
|
+
| `defaultFont` | `string` | `undefined` | Default font family for the editor content |
|
|
97
|
+
| `theme` | `"light" \| "dark"` | `"light"` | Built-in theme mode |
|
|
98
|
+
| `className` | `string` | `undefined` | Custom CSS class for theming via CSS variable overrides |
|
|
91
99
|
|
|
92
100
|
### Advanced Examples
|
|
93
101
|
|
|
102
|
+
#### Feature plugins
|
|
103
|
+
|
|
104
|
+
The editor uses the same plugin registry for core commands and React toolbar
|
|
105
|
+
state. For the architecture and custom plugin API, see
|
|
106
|
+
[`docs/PLUGIN_ARCHITECTURE.md`](../../docs/PLUGIN_ARCHITECTURE.md).
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { ClassicEditor } from 'smartrte-react';
|
|
110
|
+
|
|
111
|
+
<ClassicEditor
|
|
112
|
+
features={{
|
|
113
|
+
table: false,
|
|
114
|
+
media: false,
|
|
115
|
+
formula: false,
|
|
116
|
+
checklist: true,
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
119
|
+
```
|
|
120
|
+
|
|
94
121
|
#### Complete Example with All Features
|
|
95
122
|
|
|
96
123
|
```tsx
|
|
@@ -314,23 +341,87 @@ const myMediaManager: MediaManagerAdapter = {
|
|
|
314
341
|
};
|
|
315
342
|
```
|
|
316
343
|
|
|
317
|
-
## 🎨
|
|
344
|
+
## 🎨 Theming & Dark Mode
|
|
345
|
+
|
|
346
|
+
The editor uses CSS custom properties (CSS variables) for all colors, making it fully customizable. No hardcoded colors — everything can be themed.
|
|
347
|
+
|
|
348
|
+
### Quick Start: Dark Mode
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
<ClassicEditor theme="dark" onChange={handleChange} />
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Custom Themes via CSS
|
|
318
355
|
|
|
319
|
-
|
|
356
|
+
Apply a custom class and override any CSS variables:
|
|
320
357
|
|
|
321
358
|
```tsx
|
|
322
|
-
<
|
|
323
|
-
border: '1px solid #ddd',
|
|
324
|
-
borderRadius: '8px',
|
|
325
|
-
overflow: 'hidden',
|
|
326
|
-
}}>
|
|
327
|
-
<ClassicEditor
|
|
328
|
-
value={content}
|
|
329
|
-
onChange={setContent}
|
|
330
|
-
/>
|
|
331
|
-
</div>
|
|
359
|
+
<ClassicEditor className="my-theme" onChange={handleChange} />
|
|
332
360
|
```
|
|
333
361
|
|
|
362
|
+
```css
|
|
363
|
+
.my-theme {
|
|
364
|
+
--srte-bg: #1a1a2e;
|
|
365
|
+
--srte-text: #eaeaea;
|
|
366
|
+
--srte-border: #3a3a5c;
|
|
367
|
+
/* Override only the variables you need */
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Responding to System Preference
|
|
372
|
+
|
|
373
|
+
```tsx
|
|
374
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
375
|
+
|
|
376
|
+
<ClassicEditor theme={prefersDark ? 'dark' : 'light'} />
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Or purely via CSS (without the `theme` prop):
|
|
380
|
+
|
|
381
|
+
```css
|
|
382
|
+
@media (prefers-color-scheme: dark) {
|
|
383
|
+
.srte-editor {
|
|
384
|
+
--srte-bg: #1e1e1e;
|
|
385
|
+
--srte-text: #e0e0e0;
|
|
386
|
+
--srte-border: #3a3a3a;
|
|
387
|
+
/* ... */
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Available CSS Custom Properties
|
|
393
|
+
|
|
394
|
+
| Variable | Default (Light) | Description |
|
|
395
|
+
|---|---|---|
|
|
396
|
+
| `--srte-bg` | `#ffffff` | Editor container & content background |
|
|
397
|
+
| `--srte-text` | `#111111` | Primary UI text color |
|
|
398
|
+
| `--srte-text-muted` | `#4b5563` | Secondary/subtle text |
|
|
399
|
+
| `--srte-border` | `#dddddd` | Standard border color |
|
|
400
|
+
| `--srte-border-light` | `#eeeeee` | Light separator borders |
|
|
401
|
+
| `--srte-toolbar-bg` | `#ffffff` | Toolbar background |
|
|
402
|
+
| `--srte-input-bg` | `#ffffff` | Button, input, select backgrounds |
|
|
403
|
+
| `--srte-input-text` | `#111111` | Button, input, select text |
|
|
404
|
+
| `--srte-input-border` | `#e5e7eb` | Button, input, select borders |
|
|
405
|
+
| `--srte-modal-backdrop` | `rgba(0,0,0,0.35)` | Modal overlay background |
|
|
406
|
+
| `--srte-modal-bg` | `#ffffff` | Modal/dialog background |
|
|
407
|
+
| `--srte-modal-text` | `#000000` | Modal text color |
|
|
408
|
+
| `--srte-menu-bg` | `#ffffff` | Context menu background |
|
|
409
|
+
| `--srte-menu-text` | `#111111` | Context menu text |
|
|
410
|
+
| `--srte-menu-shadow` | `0 8px 24px rgba(0,0,0,0.18)` | Menu box-shadow |
|
|
411
|
+
| `--srte-accent` | `#1e90ff` | Accent/selection color |
|
|
412
|
+
| `--srte-accent-bg` | `rgba(30,144,255,0.15)` | Accent background (translucent) |
|
|
413
|
+
| `--srte-danger` | `#dc2626` | Destructive action color |
|
|
414
|
+
| `--srte-primary` | `#2563eb` | Primary action button color |
|
|
415
|
+
| `--srte-surface-subtle` | `#f3f4f6` | Subtle surface (dropzone, preset buttons) |
|
|
416
|
+
| `--srte-on-primary` | `#ffffff` | Text on primary/danger buttons |
|
|
417
|
+
| `--srte-cancel-bg` | `#f3f4f6` | Cancel button background |
|
|
418
|
+
|
|
419
|
+
### Important Notes
|
|
420
|
+
|
|
421
|
+
- **User content colors are preserved** — colors set via the color picker (text/background) are inline styles on content elements and are not affected by theming.
|
|
422
|
+
- **Color picker swatches are not themed** — they display actual color values regardless of theme.
|
|
423
|
+
- **The theme only affects editor chrome** — toolbar, dialogs, menus, and container. User content remains unchanged.
|
|
424
|
+
|
|
334
425
|
## 🛠️ Development
|
|
335
426
|
|
|
336
427
|
### Prerequisites
|
|
@@ -626,13 +717,14 @@ See the list of [contributors](https://github.com/ayush1852017/smart-rte/contrib
|
|
|
626
717
|
|
|
627
718
|
## 🗺️ Roadmap
|
|
628
719
|
|
|
629
|
-
### Current Version (0.
|
|
720
|
+
### Current Version (0.2.x)
|
|
630
721
|
|
|
631
722
|
- ✅ Rich text editing
|
|
632
723
|
- ✅ Table support
|
|
633
724
|
- ✅ Formula support (LaTeX/KaTeX)
|
|
634
725
|
- ✅ Media management
|
|
635
726
|
- ✅ TypeScript support
|
|
727
|
+
- ✅ Dark mode & theming (CSS custom properties)
|
|
636
728
|
|
|
637
729
|
### Upcoming Features
|
|
638
730
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checklistPlugin, createSmartEditor, listPlugin } from "smartrte-core";
|
|
1
|
+
import { checklistPlugin, createSmartEditor, listPlugin } from "smartrte-core/legacy";
|
|
2
2
|
import { smartDocumentFromHtml } from "./domSmartDocument.js";
|
|
3
3
|
/**
|
|
4
4
|
* Validates and executes a checklist mutation through core, then projects the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type SmartRtePlugin, type
|
|
1
|
+
import { type SmartRtePlugin, type LegacySmartSelection } from "smartrte-core/legacy";
|
|
2
2
|
export interface DomCommandResult {
|
|
3
3
|
html: string;
|
|
4
|
-
selection:
|
|
4
|
+
selection: LegacySmartSelection;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Executes one model command against the live editor boundary.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createSmartEditor, } from "smartrte-core";
|
|
1
|
+
import { createSmartEditor, } from "smartrte-core/legacy";
|
|
2
2
|
import { restoreSelectionToDom, selectionFromDom } from "./domSelectionBridge.js";
|
|
3
3
|
import { serializeSmartDocument, smartDocumentFromEditorRoot, } from "./domSmartDocument.js";
|
|
4
4
|
/**
|
|
@@ -9,7 +9,7 @@ import { serializeSmartDocument, smartDocumentFromEditorRoot, } from "./domSmart
|
|
|
9
9
|
* the React host until the complete editor runtime is migrated.
|
|
10
10
|
*/
|
|
11
11
|
export const executeDomCommand = (args) => {
|
|
12
|
-
// These nodes are not represented by
|
|
12
|
+
// These nodes are not represented by LegacySmartDocument yet. Falling back is
|
|
13
13
|
// mandatory so a supported command can never erase unsupported content.
|
|
14
14
|
if (args.root.querySelector("img,video,audio,iframe")) {
|
|
15
15
|
return null;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
export declare const inlineAtomPathFromDom: (root: HTMLElement, atom: HTMLElement) =>
|
|
3
|
-
/**
|
|
4
|
-
* Runs deletion through the owning feature plugin and removes only the
|
|
5
|
-
* validated live atom. Caret placement remains a DOM-host responsibility.
|
|
6
|
-
*/
|
|
7
|
-
export declare const executeDomInlineAtomDelete: (root: HTMLElement, atom: HTMLElement) => boolean;
|
|
1
|
+
import { type Attrs } from "smartrte-core/foundation";
|
|
2
|
+
export declare const inlineAtomPathFromDom: (root: HTMLElement, atom: HTMLElement) => number[] | null;
|
|
8
3
|
export interface DomFormulaInput {
|
|
9
4
|
value: string;
|
|
10
5
|
displayText?: string;
|
|
11
6
|
}
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
export interface DomCanonicalAtomInput {
|
|
8
|
+
type: "image" | "formula";
|
|
9
|
+
attrs: Attrs;
|
|
10
|
+
}
|
|
11
|
+
export declare const executeDomCanonicalAtomInsert: (root: HTMLElement, input: DomCanonicalAtomInput, domSelection?: Selection | null) => HTMLElement | null;
|
|
12
|
+
export declare const executeDomFormulaInsert: (root: HTMLElement, input: DomFormulaInput, selection?: Selection | null) => HTMLElement | null;
|
|
13
|
+
export declare const executeDomInlineAtomDelete: (root: HTMLElement, atom: HTMLElement) => boolean;
|
|
14
|
+
export declare const executeDomCanonicalAtomUpdate: (root: HTMLElement, atom: HTMLElement, attrs: Attrs) => boolean;
|
|
14
15
|
export declare const adjacentInlineAtom: (range: Range, direction: "backward" | "forward") => HTMLElement | null;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isEditorOnlyElement, pathForDomElement
|
|
3
|
-
import { smartDocumentFromEditorRoot } from "./domSmartDocument.js";
|
|
1
|
+
import { applyOperations, atomDeclarations, createNodeId, createScopeIndex, deleteAtom, foundationSchema, insertAtom, parseCanonicalListHtml, renderDocumentNaively, updateAtom, } from "smartrte-core/foundation";
|
|
2
|
+
import { isEditorOnlyElement, pathForDomElement } from "./domSelectionBridge.js";
|
|
4
3
|
const atomType = (element) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const declared = element.getAttribute("data-smart-type");
|
|
5
|
+
if (declared === "image" || element.tagName.toLowerCase() === "img")
|
|
6
|
+
return "image";
|
|
7
|
+
if (declared === "formula" || element.hasAttribute("data-formula") || element.hasAttribute("data-smart-formula"))
|
|
8
8
|
return "formula";
|
|
9
9
|
return null;
|
|
10
10
|
};
|
|
@@ -35,61 +35,165 @@ export const inlineAtomPathFromDom = (root, atom) => {
|
|
|
35
35
|
const index = inlineUnits(leaf).indexOf(atom);
|
|
36
36
|
return leafPath && index >= 0 ? [...leafPath, index] : null;
|
|
37
37
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
const ownerSelector = "p,h1,h2,h3,h4,h5,h6";
|
|
39
|
+
const prepareOwner = (owner) => {
|
|
40
|
+
var _a;
|
|
41
|
+
(_a = owner.dataset).smartId || (_a.smartId = createNodeId());
|
|
42
|
+
owner.querySelectorAll("img,[data-formula],[data-smart-formula]").forEach((atom) => {
|
|
43
|
+
var _a, _b, _c;
|
|
44
|
+
(_a = atom.dataset).smartId || (_a.smartId = createNodeId());
|
|
45
|
+
if (atomType(atom) === "formula") {
|
|
46
|
+
atom.dataset.smartType = "formula";
|
|
47
|
+
atom.dataset.smartFormula = atom.dataset.smartFormula || atom.dataset.formula || atom.textContent || "";
|
|
48
|
+
(_b = atom.dataset).smartNotation || (_b.smartNotation = "latex");
|
|
49
|
+
atom.dataset.smartAtomic = "true";
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
atom.dataset.smartType = "image";
|
|
53
|
+
(_c = atom.dataset).smartStatus || (_c.smartStatus = "ready");
|
|
54
|
+
atom.dataset.smartAtomic = "true";
|
|
55
|
+
}
|
|
52
56
|
});
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
};
|
|
58
|
+
const parseOwner = (owner) => {
|
|
59
|
+
prepareOwner(owner);
|
|
60
|
+
return parseCanonicalListHtml(owner.outerHTML);
|
|
61
|
+
};
|
|
62
|
+
const contentWidth = (node) => {
|
|
63
|
+
if (node.nodeType === node.TEXT_NODE)
|
|
64
|
+
return node.nodeValue?.length || 0;
|
|
65
|
+
if (node instanceof HTMLElement && atomType(node))
|
|
66
|
+
return 1;
|
|
67
|
+
if (node instanceof HTMLElement && isEditorOnlyElement(node))
|
|
68
|
+
return 0;
|
|
69
|
+
return [...node.childNodes].reduce((sum, child) => sum + contentWidth(child), 0);
|
|
70
|
+
};
|
|
71
|
+
const caretOffset = (owner, range) => {
|
|
72
|
+
const before = owner.ownerDocument.createRange();
|
|
73
|
+
before.selectNodeContents(owner);
|
|
74
|
+
try {
|
|
75
|
+
before.setEnd(range.startContainer, range.startOffset);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return contentWidth(before.cloneContents());
|
|
81
|
+
};
|
|
82
|
+
const renderOwner = (owner, document) => {
|
|
83
|
+
const projection = owner.ownerDocument.createElement("div");
|
|
84
|
+
renderDocumentNaively(projection, document);
|
|
85
|
+
const replacement = Array.from(projection.querySelectorAll("[data-smart-id]"))
|
|
86
|
+
.find((element) => element.dataset.smartId === owner.dataset.smartId);
|
|
87
|
+
if (!replacement)
|
|
55
88
|
return false;
|
|
56
|
-
|
|
89
|
+
owner.replaceChildren(...Array.from(replacement.childNodes));
|
|
57
90
|
return true;
|
|
58
91
|
};
|
|
59
|
-
|
|
60
|
-
export const executeDomFormulaInsert = (root, input, domSelection = root.ownerDocument.defaultView?.getSelection() || null) => {
|
|
92
|
+
export const executeDomCanonicalAtomInsert = (root, input, domSelection = root.ownerDocument.defaultView?.getSelection() || null) => {
|
|
61
93
|
if (!domSelection?.rangeCount || !domSelection.isCollapsed)
|
|
62
94
|
return null;
|
|
63
|
-
const
|
|
64
|
-
|
|
95
|
+
const range = domSelection.getRangeAt(0);
|
|
96
|
+
const element = range.startContainer instanceof Element ? range.startContainer : range.startContainer.parentElement;
|
|
97
|
+
const owner = element?.closest(ownerSelector);
|
|
98
|
+
if (!owner || !root.contains(owner))
|
|
65
99
|
return null;
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
state: { document, selection: modelSelection },
|
|
69
|
-
plugins: [formulaPlugin],
|
|
70
|
-
});
|
|
71
|
-
if (!editor.execute("formula.insert", input))
|
|
100
|
+
const offset = caretOffset(owner, range);
|
|
101
|
+
if (offset === null)
|
|
72
102
|
return null;
|
|
73
|
-
const
|
|
74
|
-
|
|
103
|
+
const document = parseOwner(owner);
|
|
104
|
+
const declaration = atomDeclarations.find((entry) => entry.type === input.type);
|
|
105
|
+
const nodeId = createNodeId();
|
|
106
|
+
const index = createScopeIndex();
|
|
107
|
+
const operations = insertAtom(document, { kind: "empty", range: { from: { path: [0], offset }, to: { path: [0], offset } }, isolatingAncestorId: null, clamped: false }, {
|
|
108
|
+
declaration, nodeId, attrs: input.attrs, ownerId: owner.dataset.smartId, offset,
|
|
109
|
+
}, { schema: foundationSchema, positions: index.positions(document, foundationSchema) });
|
|
110
|
+
if (!operations.length || !renderOwner(owner, applyOperations(document, operations)))
|
|
75
111
|
return null;
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
];
|
|
80
|
-
const atom = getNodeAtPath(editor.state.document, formulaPath);
|
|
81
|
-
if (atom?.type !== "formula")
|
|
112
|
+
const atom = Array.from(owner.querySelectorAll("[data-smart-id]"))
|
|
113
|
+
.find((element) => element.dataset.smartId === nodeId) || null;
|
|
114
|
+
if (!atom)
|
|
82
115
|
return null;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
116
|
+
if (input.type === "formula") {
|
|
117
|
+
atom.dataset.formula = String(input.attrs.source || "");
|
|
118
|
+
atom.textContent = String(input.attrs.displayText || input.attrs.source || "");
|
|
119
|
+
}
|
|
120
|
+
else
|
|
121
|
+
atom.dataset.srteInline = "true";
|
|
122
|
+
const next = root.ownerDocument.createRange();
|
|
123
|
+
next.setStartAfter(atom);
|
|
124
|
+
next.collapse(true);
|
|
90
125
|
domSelection.removeAllRanges();
|
|
91
|
-
domSelection.addRange(
|
|
92
|
-
return
|
|
126
|
+
domSelection.addRange(next);
|
|
127
|
+
return atom;
|
|
128
|
+
};
|
|
129
|
+
export const executeDomFormulaInsert = (root, input, selection) => executeDomCanonicalAtomInsert(root, { type: "formula", attrs: { source: input.value.trim(), notation: "latex", ...(input.displayText ? { displayText: input.displayText } : {}) } }, selection);
|
|
130
|
+
export const executeDomInlineAtomDelete = (root, atom) => {
|
|
131
|
+
const owner = atom.closest(ownerSelector);
|
|
132
|
+
if (!owner || !root.contains(owner) || !atomType(atom))
|
|
133
|
+
return false;
|
|
134
|
+
prepareOwner(owner);
|
|
135
|
+
const id = atom.dataset.smartId;
|
|
136
|
+
const document = parseOwner(owner);
|
|
137
|
+
const index = createScopeIndex();
|
|
138
|
+
const positions = index.positions(document, foundationSchema);
|
|
139
|
+
const range = positions.rangeOf(id);
|
|
140
|
+
if (!range)
|
|
141
|
+
return false;
|
|
142
|
+
const scope = { kind: "atomic-node", nodeId: id, inline: true, range, isolatingAncestorId: null, clamped: false };
|
|
143
|
+
const operations = deleteAtom(document, scope, {}, { schema: foundationSchema, positions });
|
|
144
|
+
return operations.length > 0 && renderOwner(owner, applyOperations(document, operations));
|
|
145
|
+
};
|
|
146
|
+
export const executeDomCanonicalAtomUpdate = (root, atom, attrs) => {
|
|
147
|
+
const owner = atom.closest(ownerSelector);
|
|
148
|
+
if (!owner || !root.contains(owner) || !atomType(atom))
|
|
149
|
+
return false;
|
|
150
|
+
prepareOwner(owner);
|
|
151
|
+
const id = atom.dataset.smartId;
|
|
152
|
+
const document = parseOwner(owner);
|
|
153
|
+
const index = createScopeIndex();
|
|
154
|
+
const positions = index.positions(document, foundationSchema);
|
|
155
|
+
const range = positions.rangeOf(id);
|
|
156
|
+
if (!range)
|
|
157
|
+
return false;
|
|
158
|
+
const scope = { kind: "atomic-node", nodeId: id, inline: true, range, isolatingAncestorId: null, clamped: false };
|
|
159
|
+
const operations = updateAtom(document, scope, { attrs }, { schema: foundationSchema, positions });
|
|
160
|
+
if (!operations.length)
|
|
161
|
+
return false;
|
|
162
|
+
const output = applyOperations(document, operations);
|
|
163
|
+
const nextPositions = createScopeIndex().positions(output, foundationSchema);
|
|
164
|
+
const resolved = nextPositions.positionOf(id);
|
|
165
|
+
const next = resolved?.parent.children?.find((child) => child.type !== "text" && "id" in child && child.id === id);
|
|
166
|
+
if (!next || next.type === "text")
|
|
167
|
+
return false;
|
|
168
|
+
if (atom instanceof HTMLImageElement) {
|
|
169
|
+
if (typeof next.attrs?.src === "string")
|
|
170
|
+
atom.src = next.attrs.src;
|
|
171
|
+
if (typeof next.attrs?.alt === "string")
|
|
172
|
+
atom.alt = next.attrs.alt;
|
|
173
|
+
if (typeof next.attrs?.title === "string")
|
|
174
|
+
atom.title = next.attrs.title;
|
|
175
|
+
if (Number(next.attrs?.width) > 0)
|
|
176
|
+
atom.width = Number(next.attrs?.width);
|
|
177
|
+
if (Number(next.attrs?.height) > 0)
|
|
178
|
+
atom.height = Number(next.attrs?.height);
|
|
179
|
+
const align = next.attrs?.align;
|
|
180
|
+
if (align === "center") {
|
|
181
|
+
atom.style.display = "block";
|
|
182
|
+
atom.style.margin = "0 auto";
|
|
183
|
+
atom.style.float = "none";
|
|
184
|
+
}
|
|
185
|
+
else if (align === "left" || align === "right") {
|
|
186
|
+
atom.style.display = "inline";
|
|
187
|
+
atom.style.float = align;
|
|
188
|
+
atom.style.margin = align === "left" ? "0 8px 8px 0" : "0 0 8px 8px";
|
|
189
|
+
}
|
|
190
|
+
atom.dataset.smartStatus = String(next.attrs?.status || "ready");
|
|
191
|
+
if (typeof next.attrs?.error === "string")
|
|
192
|
+
atom.dataset.smartError = next.attrs.error;
|
|
193
|
+
else
|
|
194
|
+
delete atom.dataset.smartError;
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
93
197
|
};
|
|
94
198
|
export const adjacentInlineAtom = (range, direction) => {
|
|
95
199
|
if (!range.collapsed || range.startContainer.nodeType !== Node.ELEMENT_NODE)
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export interface DomInlineImageInput {
|
|
2
2
|
src: string;
|
|
3
3
|
alt?: string;
|
|
4
|
+
decorative?: boolean;
|
|
4
5
|
title?: string;
|
|
5
6
|
width?: number;
|
|
6
7
|
height?: number;
|
|
8
|
+
align?: "center" | "left" | "right";
|
|
9
|
+
status?: "pending" | "ready" | "error";
|
|
10
|
+
uploadId?: string;
|
|
11
|
+
error?: string;
|
|
7
12
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* atom into the existing DOM range. Unrelated DOM and metadata stay untouched.
|
|
11
|
-
*/
|
|
12
|
-
export declare const executeDomInlineImageCommand: (root: HTMLElement, input: DomInlineImageInput, domSelection?: Selection | null) => HTMLImageElement | null;
|
|
13
|
-
/** Validates an inline image attribute update through core, then mutates it in place. */
|
|
14
|
-
export declare const executeDomInlineImageUpdate: (root: HTMLElement, image: HTMLImageElement, input: Omit<DomInlineImageInput, "src">) => boolean;
|
|
13
|
+
export declare const executeDomInlineImageCommand: (root: HTMLElement, input: DomInlineImageInput, selection?: Selection | null) => HTMLImageElement | null;
|
|
14
|
+
export declare const executeDomInlineImageUpdate: (root: HTMLElement, image: HTMLImageElement, input: Partial<DomInlineImageInput>) => boolean;
|
|
@@ -1,72 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* atom into the existing DOM range. Unrelated DOM and metadata stay untouched.
|
|
8
|
-
*/
|
|
9
|
-
export const executeDomInlineImageCommand = (root, input, domSelection = root.ownerDocument.defaultView?.getSelection() || null) => {
|
|
10
|
-
if (!domSelection?.rangeCount || !domSelection.isCollapsed)
|
|
11
|
-
return null;
|
|
12
|
-
const modelSelection = selectionFromDom(root, domSelection);
|
|
13
|
-
if (!modelSelection)
|
|
14
|
-
return null;
|
|
15
|
-
const { document } = smartDocumentFromEditorRoot(root);
|
|
16
|
-
const editor = createSmartEditor({
|
|
17
|
-
state: { document, selection: modelSelection },
|
|
18
|
-
plugins: [mediaPlugin],
|
|
19
|
-
});
|
|
20
|
-
if (!editor.execute("image.insert-inline", input))
|
|
21
|
-
return null;
|
|
22
|
-
const selectionAfter = editor.state.selection;
|
|
23
|
-
if (selectionAfter.type !== "text")
|
|
24
|
-
return null;
|
|
25
|
-
const imagePath = [
|
|
26
|
-
...selectionAfter.anchor.path.slice(0, -1),
|
|
27
|
-
selectionAfter.anchor.path[selectionAfter.anchor.path.length - 1] - 1,
|
|
28
|
-
];
|
|
29
|
-
const atom = getNodeAtPath(editor.state.document, imagePath);
|
|
30
|
-
if (atom?.type !== "inlineImage")
|
|
31
|
-
return null;
|
|
32
|
-
const range = domSelection.getRangeAt(0);
|
|
33
|
-
const image = root.ownerDocument.createElement("img");
|
|
34
|
-
image.src = atom.src;
|
|
35
|
-
image.alt = atom.alt || "";
|
|
36
|
-
if (atom.title)
|
|
37
|
-
image.title = atom.title;
|
|
38
|
-
if (atom.width)
|
|
39
|
-
image.width = atom.width;
|
|
40
|
-
if (atom.height)
|
|
41
|
-
image.height = atom.height;
|
|
42
|
-
image.dataset.srteInline = "true";
|
|
43
|
-
range.insertNode(image);
|
|
44
|
-
range.setStartAfter(image);
|
|
45
|
-
range.collapse(true);
|
|
46
|
-
domSelection.removeAllRanges();
|
|
47
|
-
domSelection.addRange(range);
|
|
48
|
-
return image;
|
|
49
|
-
};
|
|
50
|
-
/** Validates an inline image attribute update through core, then mutates it in place. */
|
|
51
|
-
export const executeDomInlineImageUpdate = (root, image, input) => {
|
|
52
|
-
const path = inlineAtomPathFromDom(root, image);
|
|
53
|
-
if (!path)
|
|
54
|
-
return false;
|
|
55
|
-
const selection = selectionFromDom(root, root.ownerDocument.defaultView?.getSelection() || null) || { type: "node", path };
|
|
56
|
-
const { document } = smartDocumentFromEditorRoot(root);
|
|
57
|
-
const editor = createSmartEditor({
|
|
58
|
-
state: { document, selection },
|
|
59
|
-
plugins: [mediaPlugin],
|
|
60
|
-
});
|
|
61
|
-
if (!editor.execute("image.update-inline", { path, ...input }))
|
|
62
|
-
return false;
|
|
63
|
-
if (input.alt !== undefined)
|
|
64
|
-
image.alt = input.alt;
|
|
65
|
-
if (input.title !== undefined)
|
|
66
|
-
image.title = input.title;
|
|
67
|
-
if (input.width !== undefined)
|
|
68
|
-
image.width = input.width;
|
|
69
|
-
if (input.height !== undefined)
|
|
70
|
-
image.height = input.height;
|
|
71
|
-
return true;
|
|
72
|
-
};
|
|
1
|
+
import { executeDomCanonicalAtomInsert, executeDomCanonicalAtomUpdate } from "./domInlineAtomCommandBridge.js";
|
|
2
|
+
export const executeDomInlineImageCommand = (root, input, selection = root.ownerDocument.defaultView?.getSelection() || null) => executeDomCanonicalAtomInsert(root, {
|
|
3
|
+
type: "image",
|
|
4
|
+
attrs: { src: input.src, alt: input.alt ?? "", decorative: input.decorative === true, status: input.status || "ready", ...(input.uploadId ? { uploadId: input.uploadId } : {}), ...(input.error ? { error: input.error } : {}), ...(input.title ? { title: input.title } : {}), ...(input.width ? { width: input.width } : {}), ...(input.height ? { height: input.height } : {}) },
|
|
5
|
+
}, selection);
|
|
6
|
+
export const executeDomInlineImageUpdate = (root, image, input) => executeDomCanonicalAtomUpdate(root, image, input);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createSmartEditor, movePlugin } from "smartrte-core";
|
|
1
|
+
import { createSmartEditor, movePlugin } from "smartrte-core/legacy";
|
|
2
2
|
import { smartDocumentFromHtml } from "./domSmartDocument.js";
|
|
3
3
|
/**
|
|
4
4
|
* Executes model move/indent commands while preserving the existing DOM nodes.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { Path,
|
|
1
|
+
import type { Path, LegacySmartSelection } from "smartrte-core/legacy";
|
|
2
2
|
export declare const isEditorOnlyElement: (node: Element) => boolean;
|
|
3
3
|
/** Returns the semantic model path for a DOM element at the adapter boundary. */
|
|
4
4
|
export declare const pathForDomElement: (element: Element, editor: HTMLElement) => Path | null;
|
|
5
5
|
/** Converts browser selection into a core selection without exposing editor UI nodes. */
|
|
6
|
-
export declare const selectionFromDom: (editor: HTMLElement, selection: Selection | null) =>
|
|
6
|
+
export declare const selectionFromDom: (editor: HTMLElement, selection: Selection | null) => LegacySmartSelection | null;
|
|
7
7
|
/** Restores a text selection from core paths after the editor DOM is rebuilt. */
|
|
8
|
-
export declare const restoreSelectionToDom: (editor: HTMLElement, smartSelection:
|
|
8
|
+
export declare const restoreSelectionToDom: (editor: HTMLElement, smartSelection: LegacySmartSelection) => boolean;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type LegacySmartDocument } from "smartrte-core/legacy";
|
|
2
2
|
/** Removes editor UI before handing the document to the core parser boundary. */
|
|
3
3
|
export declare const cleanEditorHtml: (root: HTMLElement) => string;
|
|
4
|
-
export declare const smartDocumentFromHtml: (html: string, ownerDocument: Document) =>
|
|
4
|
+
export declare const smartDocumentFromHtml: (html: string, ownerDocument: Document) => LegacySmartDocument;
|
|
5
5
|
export declare const smartDocumentFromEditorRoot: (root: HTMLElement) => {
|
|
6
|
-
document:
|
|
6
|
+
document: LegacySmartDocument;
|
|
7
7
|
html: string;
|
|
8
8
|
};
|
|
9
9
|
/** Serializes the shadow-only model. It is not used for persisted editor HTML. */
|
|
10
|
-
export declare const serializeSmartDocument: (document:
|
|
10
|
+
export declare const serializeSmartDocument: (document: LegacySmartDocument) => string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { normalizeCompatibilityHtml, sanitizeLinkAttrs, } from "smartrte-core";
|
|
2
|
-
import { isSmartListPreset } from "smartrte-core";
|
|
1
|
+
import { normalizeCompatibilityHtml, sanitizeLinkAttrs, } from "smartrte-core/legacy";
|
|
2
|
+
import { isSmartListPreset } from "smartrte-core/legacy";
|
|
3
3
|
import { isEditorOnlyElement } from "./domSelectionBridge.js";
|
|
4
4
|
const blockTags = new Set(["p", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "blockquote", "pre", "table", "img", "video", "audio"]);
|
|
5
5
|
const unwrap = (element) => {
|