smartrte-react 1.0.0-beta.9 โ 1.1.0
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 +46 -0
- package/README.md +239 -745
- package/dist/components/BlockquoteBorderPopover.d.ts +30 -0
- package/dist/components/BlockquoteBorderPopover.js +115 -0
- package/dist/components/CanonicalAuthorityEditor.js +289 -41
- package/dist/components/ColorPickerPopover.js +6 -7
- package/dist/components/MediaOverlay.d.ts +3 -1
- package/dist/components/MediaOverlay.js +2 -2
- package/dist/components/TableBorderPopover.js +6 -7
- package/dist/components/TableResizeHandles.js +22 -0
- package/dist/components/ToolbarPrimitives.d.ts +14 -1
- package/dist/components/ToolbarPrimitives.js +19 -17
- package/dist/components/fixedPositioning.d.ts +99 -16
- package/dist/components/fixedPositioning.js +114 -17
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +88 -0
- package/dist/toolbarTools.d.ts +6 -0
- package/dist/toolbarTools.js +3 -2
- package/package.json +13 -12
package/README.md
CHANGED
|
@@ -1,880 +1,374 @@
|
|
|
1
|
-
#
|
|
1
|
+
# smartrte-react
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/smartrte-react)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
A
|
|
6
|
+
A rich text editor for React, built on a document-model core rather than raw `contentEditable` state โ tables, LaTeX/KaTeX formulas, media, DOCX/PDF/Markdown import-export, per-tool toolbar visibility, and host-owned integration points for version history, comments, and suggestions/track-changes.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
It pairs with [`smartrte-core`](https://www.npmjs.com/package/smartrte-core), a framework-agnostic document engine โ you only need this package to use it from React.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- **๐ Advanced Table Support**: Create, edit, merge, split cells, and customize tables
|
|
12
|
-
- **๐ข Mathematical Formulas**: LaTeX/KaTeX integration for rendering mathematical expressions
|
|
13
|
-
- **๐ผ๏ธ Media Management**: Image upload, resize, drag-and-drop, and custom media manager integration
|
|
14
|
-
- **๐จ Styling Options**: Font sizes (8-96pt), text colors, background colors, and more
|
|
15
|
-
- **๐ Link Management**: Easy insertion and editing of hyperlinks
|
|
16
|
-
- **๐ฑ Responsive**: Works seamlessly across different screen sizes
|
|
17
|
-
- **โก Lightweight**: Minimal dependencies, optimized for performance
|
|
18
|
-
- **๐ฏ TypeScript Support**: Fully typed for better developer experience
|
|
19
|
-
- **๐ง Customizable**: Toggle features on/off, custom media managers, and more
|
|
10
|
+
## Contents
|
|
20
11
|
|
|
21
|
-
|
|
12
|
+
- [Install](#install)
|
|
13
|
+
- [Quick start](#quick-start)
|
|
14
|
+
- [Which component do I use?](#which-component-do-i-use-canonicalauthorityeditor-vs-classiceditor)
|
|
15
|
+
- [Props](#props)
|
|
16
|
+
- [Toolbar customization](#toolbar-customization)
|
|
17
|
+
- [Capability presets](#capability-presets-table-onoff)
|
|
18
|
+
- [Host-owned providers](#host-owned-providers-media-versions-comments-suggestions)
|
|
19
|
+
- [Imperative handle](#imperative-handle-ref)
|
|
20
|
+
- [Import & export formats](#import--export-formats)
|
|
21
|
+
- [Theming](#theming)
|
|
22
|
+
- [Standalone / non-React embed](#standalone--non-react-embed)
|
|
23
|
+
- [Security](#security)
|
|
24
|
+
- [Browser support](#browser-support)
|
|
25
|
+
- [Development](#development)
|
|
26
|
+
- [Contributing](#contributing)
|
|
27
|
+
- [License](#license)
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
## Install
|
|
24
30
|
|
|
25
31
|
```bash
|
|
26
32
|
npm install smartrte-react
|
|
33
|
+
# or: pnpm add smartrte-react / yarn add smartrte-react
|
|
27
34
|
```
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
`react` and `react-dom` (`>=18`) are peer dependencies โ install them if your project doesn't already have them. No separate CSS import is required; the editor injects its own stylesheet on mount.
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
yarn add smartrte-react
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Using pnpm
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
pnpm add smartrte-react
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## ๐ Quick Start
|
|
42
|
-
|
|
43
|
-
### ๐ฎ Live Demo
|
|
44
|
-
|
|
45
|
-
Try the editor instantly in your browser:
|
|
46
|
-
|
|
47
|
-
- **[Live Demo](https://playground-k9l44ah7t-ayush1852017s-projects.vercel.app/)** (Deployed Version)
|
|
48
|
-
- **[CodeSandbox Playground](https://codesandbox.io/s/github/ayush1852017/smart-rte/tree/master/packages/react/playground)** (Interactive)
|
|
49
|
-
|
|
50
|
-
### Basic Usage
|
|
38
|
+
## Quick start
|
|
51
39
|
|
|
52
40
|
```tsx
|
|
53
|
-
import
|
|
54
|
-
import {
|
|
41
|
+
import { useState } from "react";
|
|
42
|
+
import { CanonicalAuthorityEditor } from "smartrte-react";
|
|
55
43
|
|
|
56
44
|
function App() {
|
|
57
|
-
const [content, setContent] = useState(
|
|
45
|
+
const [content, setContent] = useState("<p>Start typingโฆ</p>");
|
|
58
46
|
|
|
59
47
|
return (
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
/>
|
|
66
|
-
</div>
|
|
48
|
+
<CanonicalAuthorityEditor
|
|
49
|
+
defaultValue={content}
|
|
50
|
+
onHtmlChange={setContent}
|
|
51
|
+
placeholder="Type hereโฆ"
|
|
52
|
+
/>
|
|
67
53
|
);
|
|
68
54
|
}
|
|
69
|
-
|
|
70
|
-
export default App;
|
|
71
55
|
```
|
|
72
56
|
|
|
73
|
-
|
|
57
|
+
`defaultValue` is uncontrolled โ it seeds the editor once on mount, not on every render (see [Props](#props) for why, and how to programmatically replace content later via the imperative handle).
|
|
74
58
|
|
|
75
|
-
|
|
59
|
+
## Which component do I use? `CanonicalAuthorityEditor` vs `ClassicEditor`
|
|
76
60
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
| Prop | Type | Default | Description |
|
|
80
|
-
|------|------|---------|-------------|
|
|
81
|
-
| `value` | `string` | `undefined` | HTML content of the editor |
|
|
82
|
-
| `onChange` | `(html: string) => void` | `undefined` | Callback fired when content changes |
|
|
83
|
-
| `placeholder` | `string` | `"Type hereโฆ"` | Placeholder text when editor is empty |
|
|
84
|
-
| `minHeight` | `number \| string` | `200` | Minimum height of the editor (in pixels) |
|
|
85
|
-
| `maxHeight` | `number \| string` | `500` | Maximum height of the editor (in pixels) |
|
|
86
|
-
| `readOnly` | `boolean` | `false` | Make the editor read-only |
|
|
87
|
-
| `table` | `boolean` | `true` | Enable/disable table functionality |
|
|
88
|
-
| `media` | `boolean` | `true` | Enable/disable media/image functionality |
|
|
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 |
|
|
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 |
|
|
99
|
-
| `tools` | `Partial<ToolbarTools>` | every tool `true` | Hide individual toolbar tools (Bold, Video, Version history, etc.) โ see [Hiding individual toolbar tools](#hiding-individual-toolbar-tools) below |
|
|
100
|
-
|
|
101
|
-
### Advanced Examples
|
|
102
|
-
|
|
103
|
-
#### Feature plugins
|
|
104
|
-
|
|
105
|
-
The editor uses the same plugin registry for core commands and React toolbar
|
|
106
|
-
state. For the architecture and custom plugin API, see
|
|
107
|
-
[`docs/PLUGIN_ARCHITECTURE.md`](../../docs/PLUGIN_ARCHITECTURE.md).
|
|
61
|
+
- **`CanonicalAuthorityEditor`** โ the actual editor. Everything in this guide (tools, providers, presets, the imperative handle) is its API. Use this for anything new.
|
|
62
|
+
- **`ClassicEditor`** โ a thin backwards-compatibility wrapper around `CanonicalAuthorityEditor`, kept for integrations written against the package's older `value`/`onChange: (html: string) => void` shape. It forwards everything it can (`tools`, `mediaProvider`, `versionProvider`, etc.) but silently ignores a handful of props from an even older, now-retired plugin system (`features`, `plugins`, `formats`, `fonts`, `theme`, `mediaManager` as an adapter object). If you're starting fresh, use `CanonicalAuthorityEditor` directly โ `ClassicEditor` exists so old call sites keep compiling, not as a recommended entry point.
|
|
108
63
|
|
|
109
64
|
```tsx
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
table: false,
|
|
115
|
-
media: false,
|
|
116
|
-
formula: false,
|
|
117
|
-
checklist: true,
|
|
118
|
-
}}
|
|
119
|
-
/>
|
|
65
|
+
// Legacy-compatible shape - only use this if migrating an existing integration
|
|
66
|
+
import { ClassicEditor } from "smartrte-react";
|
|
67
|
+
|
|
68
|
+
<ClassicEditor value={htmlString} onChange={(html) => setHtmlString(html)} />
|
|
120
69
|
```
|
|
121
70
|
|
|
122
|
-
|
|
71
|
+
## Props
|
|
72
|
+
|
|
73
|
+
The commonly-used `CanonicalAuthorityEditor` props:
|
|
123
74
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
`
|
|
130
|
-
|
|
131
|
-
|
|
75
|
+
| Prop | Type | Default | Description |
|
|
76
|
+
|---|---|---|---|
|
|
77
|
+
| `defaultValue` | `string \| PersistedEditorDocument` | `undefined` | Initial content (HTML string or a previously-saved document envelope). Uncontrolled after mount โ see [Imperative handle](#imperative-handle-ref) to replace content later. |
|
|
78
|
+
| `onChange` | `(change: SmartEditorChange) => void` | `undefined` | Fires per transaction with the structured change event. |
|
|
79
|
+
| `onHtmlChange` | `(html: string) => void` | `undefined` | Debounced (~250ms after the last edit) plain-HTML serialization โ the simplest way to persist content as a string. |
|
|
80
|
+
| `preset` | `"full" \| "simple"` | `"full"` | Construction-time capability preset โ `"simple"` excludes the table plugin entirely (schema-level, not just hidden in the toolbar). See [Capability presets](#capability-presets-table-onoff). |
|
|
81
|
+
| `tools` | `Partial<ToolbarTools>` | every tool `true` | Hide individual toolbar tools without touching document capability. See [Toolbar customization](#toolbar-customization). |
|
|
82
|
+
| `mediaProvider` | `MediaProvider` | `undefined` | Host-owned upload/search/remove boundary for images, video, and audio. Absent โ media tools don't render. |
|
|
83
|
+
| `mediaManager` | `boolean` | `true` when `mediaProvider` is set | Use the library/search/duplicate-detection picker for images (vs. the plain file-input default). |
|
|
84
|
+
| `mediaPicker` | `MediaPickerComponent` | built-in file picker | Replace the default file-picker UI for video/audio (and images, if `mediaManager` is `false`). |
|
|
85
|
+
| `versionProvider` | `VersionProvider` | `undefined` | Host-owned save/list/load/remove boundary for version history. Absent โ Version History tool doesn't render. |
|
|
86
|
+
| `commentProvider` | `CommentProvider` | `undefined` | Host-owned boundary for comment threads. Absent โ comment tools/markers don't render. |
|
|
87
|
+
| `suggestionProvider` | `SuggestionProvider` | `undefined` | Host-owned boundary for *structural* suggestions (track-changes). Absent โ suggestion tools/markers don't render. |
|
|
88
|
+
| `authorId` | `string` | `"anonymous"` | Attributed to new comment replies and suggestions. |
|
|
89
|
+
| `renderFormulaHtml` | `boolean` | `false` | Bake real KaTeX-rendered HTML into `onHtmlChange`'s formula markup instead of an empty placeholder โ turn this on if you render that HTML anywhere outside the editor (email, PDF export, a read-only view without KaTeX loaded). |
|
|
90
|
+
| `onClipboardDiagnostic` | `(report: ClipboardDiagnosticReport) => void` | `undefined` | Inspect what a paste was parsed as / why it was rejected โ useful while debugging a host's own copy sources. |
|
|
91
|
+
| `placeholder` | `string` | `undefined` | Placeholder text shown when the editor is empty. |
|
|
92
|
+
| `minHeight` / `maxHeight` | `number \| string` | `undefined` | Editing-surface height bounds. |
|
|
93
|
+
| `readOnly` | `boolean` | `false` | Disables editing; toolbar tools become inert. |
|
|
94
|
+
| `className` | `string` | `undefined` | Extra class(es) on the editor's root element โ this is also how you enable [dark mode](#theming). |
|
|
95
|
+
| `onRuntime` | `(runtime: CanonicalEditorRuntime) => void` | `undefined` | Escape hatch for tests/diagnostics; not part of the stable editing contract. |
|
|
96
|
+
|
|
97
|
+
`ClassicEditor` accepts the same props under `value`/`onChange: (html) => void` instead of `defaultValue`/`onHtmlChange`, plus a legacy `table?: boolean` (equivalent to `preset={table === false ? "simple" : "full"}`).
|
|
98
|
+
|
|
99
|
+
## Toolbar customization
|
|
100
|
+
|
|
101
|
+
`tools` hides individual toolbar entries โ Bold, Video, Version history, whatever you name โ without touching what the document itself can *store*. Every tool defaults to visible; only name the ones you want off:
|
|
132
102
|
|
|
133
103
|
```tsx
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
<ClassicEditor
|
|
137
|
-
tools={{
|
|
138
|
-
video: false,
|
|
139
|
-
audio: false,
|
|
140
|
-
versionHistory: false,
|
|
141
|
-
comments: false,
|
|
142
|
-
suggestions: false,
|
|
143
|
-
}}
|
|
104
|
+
<CanonicalAuthorityEditor
|
|
105
|
+
tools={{ video: false, audio: false, versionHistory: false, comments: false, suggestions: false }}
|
|
144
106
|
/>
|
|
145
107
|
```
|
|
146
108
|
|
|
147
|
-
|
|
148
|
-
things to be true to show up โ your `tools` flag says yes, *and* whatever
|
|
149
|
-
that tool actually depends on is present. So:
|
|
109
|
+
`tools` can only ever hide something, never conjure it into existence โ a tool still needs its underlying capability to actually be there:
|
|
150
110
|
|
|
151
|
-
- `
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
- `tools.versionHistory` also needs a `versionProvider`; `tools.comments`
|
|
155
|
-
needs a `commentProvider`; `tools.suggestions` needs a `suggestionProvider`.
|
|
156
|
-
- `tools.insertTable` also needs the table capability to actually be enabled
|
|
157
|
-
(i.e. you haven't set `table={false}`) โ you can't use `tools` to bring
|
|
158
|
-
back a capability you turned off elsewhere.
|
|
111
|
+
- `image`/`video`/`audio` also need `mediaProvider` configured.
|
|
112
|
+
- `versionHistory` also needs `versionProvider`; `comments` needs `commentProvider`; `suggestions` needs `suggestionProvider`.
|
|
113
|
+
- `insertTable` also needs the table capability enabled (i.e. you haven't set `preset="simple"`).
|
|
159
114
|
|
|
160
|
-
|
|
161
|
-
into existence that isn't otherwise configured. This means it's always safe
|
|
162
|
-
to leave `tools` unset โ every consumer who doesn't pass it sees the exact
|
|
163
|
-
toolbar they see today.
|
|
115
|
+
This means it's always safe to leave `tools` unset โ a consumer who never passes it sees every tool their other configuration already supports.
|
|
164
116
|
|
|
165
117
|
**Every toggleable key**, grouped the way they appear in the toolbar:
|
|
166
118
|
|
|
167
119
|
```ts
|
|
168
120
|
interface ToolbarTools {
|
|
169
121
|
// Text formatting
|
|
170
|
-
bold
|
|
171
|
-
superscript
|
|
122
|
+
bold: boolean; italic: boolean; underline: boolean; strikethrough: boolean; code: boolean;
|
|
123
|
+
superscript: boolean; subscript: boolean; textColor: boolean; backgroundColor: boolean;
|
|
124
|
+
fontSize: boolean; fontFamily: boolean;
|
|
172
125
|
|
|
173
126
|
// Paragraph
|
|
174
|
-
blockType
|
|
175
|
-
alignLeft
|
|
127
|
+
blockType: boolean; // the Paragraph/Heading 1-6/Code block dropdown
|
|
128
|
+
alignLeft: boolean; alignCenter: boolean; alignRight: boolean; alignJustify: boolean;
|
|
129
|
+
lineHeight: boolean; // the line-spacing dropdown (1/1.15/1.5/2/2.5 presets plus a custom value)
|
|
130
|
+
quote: boolean;
|
|
176
131
|
|
|
177
132
|
// Lists
|
|
178
|
-
bulletedList
|
|
179
|
-
listPreset
|
|
133
|
+
bulletedList: boolean; numberedList: boolean; checklist: boolean;
|
|
134
|
+
listPreset: boolean; // the named marker-preset picker (decimal/alpha/roman/outline/bullet glyphs)
|
|
180
135
|
|
|
181
136
|
// Insert
|
|
182
|
-
link
|
|
137
|
+
link: boolean; removeLink: boolean;
|
|
138
|
+
image: boolean; video: boolean; audio: boolean; // each requires mediaProvider
|
|
139
|
+
insertFormula: boolean; specialCharacters: boolean;
|
|
140
|
+
horizontalLine: boolean; // inserts a divider (<hr>)
|
|
141
|
+
pageBreak: boolean; // a print/export pagination marker, distinct from horizontalLine
|
|
142
|
+
insertTable: boolean; // requires the table capability (preset)
|
|
183
143
|
|
|
184
144
|
// Document
|
|
185
|
-
import
|
|
186
|
-
saveAsHtml
|
|
187
|
-
versionHistory
|
|
145
|
+
import: boolean;
|
|
146
|
+
saveAsHtml: boolean; saveAsMarkdown: boolean; saveAsWord: boolean; saveAsPdf: boolean; saveAsSmartRte: boolean;
|
|
147
|
+
versionHistory: boolean; // requires versionProvider
|
|
148
|
+
comments: boolean; // requires commentProvider
|
|
149
|
+
suggestions: boolean; // requires suggestionProvider
|
|
188
150
|
|
|
189
151
|
// History
|
|
190
|
-
undo
|
|
152
|
+
undo: boolean; redo: boolean;
|
|
191
153
|
}
|
|
192
154
|
```
|
|
193
155
|
|
|
194
|
-
|
|
195
|
-
apply to something you already have selected โ moving a block up/down,
|
|
196
|
-
indenting a list item, adding/removing a table row or column, resizing a
|
|
197
|
-
selected image โ aren't individually toggleable. They only make sense as
|
|
198
|
-
part of using the tool that created them (a table, a list, an image), so
|
|
199
|
-
they follow that tool's own visibility rather than needing a separate flag
|
|
200
|
-
each. If you turn off `insertTable`, its row/column tools go with it
|
|
201
|
-
automatically โ there's no separate flag to remember.
|
|
156
|
+
Purely contextual actions that only ever act on something already selected โ moving a block up/down, indenting a list item, adding/removing a table row, resizing a selected image โ aren't individually toggleable; they follow their owning tool's visibility (turn off `insertTable` and its row/column actions go with it, with no separate flag to remember).
|
|
202
157
|
|
|
203
|
-
|
|
204
|
-
`ClassicEditor`, `tools` works exactly the same way. And if you're using the
|
|
205
|
-
standalone embed (`window.SmartRTE.ClassicEditor.init(...)`), pass `tools`
|
|
206
|
-
in the same options object you pass `target`/`value`/etc.
|
|
158
|
+
## Capability presets (table on/off)
|
|
207
159
|
|
|
208
|
-
|
|
209
|
-
window.SmartRTE.ClassicEditor.init({
|
|
210
|
-
target: document.getElementById('editor'),
|
|
211
|
-
tools: { video: false, audio: false },
|
|
212
|
-
});
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
#### Complete Example with All Features
|
|
160
|
+
`preset` is a construction-time, host/integrator-level setting (there's no in-editor UI for a user to change their own preset) โ it decides which plugins the document's *schema* is built with, not just what the toolbar shows:
|
|
216
161
|
|
|
217
162
|
```tsx
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
// Custom media manager implementation
|
|
222
|
-
const customMediaManager: MediaManagerAdapter = {
|
|
223
|
-
async search(query) {
|
|
224
|
-
// Implement your media search logic
|
|
225
|
-
const response = await fetch(`/api/media/search?q=${query.text}`);
|
|
226
|
-
const data = await response.json();
|
|
227
|
-
return {
|
|
228
|
-
items: data.items.map(item => ({
|
|
229
|
-
id: item.id,
|
|
230
|
-
url: item.url,
|
|
231
|
-
thumbnailUrl: item.thumbnailUrl,
|
|
232
|
-
title: item.title,
|
|
233
|
-
})),
|
|
234
|
-
};
|
|
235
|
-
},
|
|
236
|
-
async upload(file) {
|
|
237
|
-
// Implement your file upload logic
|
|
238
|
-
const formData = new FormData();
|
|
239
|
-
formData.append('file', file);
|
|
240
|
-
const response = await fetch('/api/media/upload', {
|
|
241
|
-
method: 'POST',
|
|
242
|
-
body: formData,
|
|
243
|
-
});
|
|
244
|
-
const data = await response.json();
|
|
245
|
-
return {
|
|
246
|
-
id: data.id,
|
|
247
|
-
url: data.url,
|
|
248
|
-
thumbnailUrl: data.thumbnailUrl,
|
|
249
|
-
title: data.title,
|
|
250
|
-
};
|
|
251
|
-
},
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
function AdvancedEditor() {
|
|
255
|
-
const [content, setContent] = useState('');
|
|
256
|
-
|
|
257
|
-
return (
|
|
258
|
-
<ClassicEditor
|
|
259
|
-
value={content}
|
|
260
|
-
onChange={(html) => {
|
|
261
|
-
console.log('Content changed:', html);
|
|
262
|
-
setContent(html);
|
|
263
|
-
}}
|
|
264
|
-
placeholder="Start editing..."
|
|
265
|
-
minHeight={300}
|
|
266
|
-
maxHeight={800}
|
|
267
|
-
table={true}
|
|
268
|
-
media={true}
|
|
269
|
-
formula={true}
|
|
270
|
-
mediaManager={customMediaManager}
|
|
271
|
-
/>
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
export default AdvancedEditor;
|
|
163
|
+
<CanonicalAuthorityEditor preset="simple" /> // excludes the table plugin entirely
|
|
164
|
+
<CanonicalAuthorityEditor preset="full" /> // default - excludes nothing
|
|
276
165
|
```
|
|
277
166
|
|
|
278
|
-
|
|
167
|
+
`"simple"` exists for content that should never contain tables at all (e.g. a short-answer question editor) โ `preset="simple"` and `tools={{ insertTable: false }}` are not equivalent: the latter only hides the button, the former means the schema itself will reject a pasted or imported table.
|
|
279
168
|
|
|
280
|
-
|
|
281
|
-
import { ClassicEditor } from 'smartrte-react';
|
|
169
|
+
## Host-owned providers (media, versions, comments, suggestions)
|
|
282
170
|
|
|
283
|
-
|
|
284
|
-
return (
|
|
285
|
-
<ClassicEditor
|
|
286
|
-
value={content}
|
|
287
|
-
readOnly={true}
|
|
288
|
-
minHeight={200}
|
|
289
|
-
/>
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
```
|
|
171
|
+
Four features are opt-in via a provider interface the *host* implements โ the package never holds storage credentials, a socket, or a database connection itself. Absent provider โ that feature's toolbar entries simply don't render; nothing crashes or shows a broken control.
|
|
293
172
|
|
|
294
|
-
|
|
173
|
+
```ts
|
|
174
|
+
interface MediaProvider {
|
|
175
|
+
upload(file: File, opts?: { signal?: AbortSignal }): Promise<{ url: string; id: string }>;
|
|
176
|
+
search(query: string, filters?: MediaFilters, page?: number): Promise<MediaItem[]>;
|
|
177
|
+
remove(id: string): Promise<void>;
|
|
178
|
+
}
|
|
295
179
|
|
|
296
|
-
|
|
297
|
-
|
|
180
|
+
interface VersionProvider {
|
|
181
|
+
save(version: DocumentVersion): Promise<VersionListEntry>;
|
|
182
|
+
list(): Promise<readonly VersionListEntry[]>;
|
|
183
|
+
load(id: string): Promise<DocumentVersion>;
|
|
184
|
+
remove(id: string): Promise<void>;
|
|
185
|
+
}
|
|
298
186
|
|
|
299
|
-
|
|
300
|
-
|
|
187
|
+
interface CommentProvider {
|
|
188
|
+
list(): Promise<readonly CommentThread[]>;
|
|
189
|
+
save(thread: CommentThread): Promise<void>;
|
|
190
|
+
remove(threadId: string): Promise<void>;
|
|
191
|
+
}
|
|
301
192
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
table={false}
|
|
307
|
-
media={false}
|
|
308
|
-
formula={false}
|
|
309
|
-
placeholder="Simple text editor"
|
|
310
|
-
/>
|
|
311
|
-
);
|
|
193
|
+
interface SuggestionProvider {
|
|
194
|
+
list(): Promise<readonly StructuralSuggestion[]>;
|
|
195
|
+
save(suggestion: StructuralSuggestion): Promise<void>;
|
|
196
|
+
remove(suggestionId: string): Promise<void>;
|
|
312
197
|
}
|
|
313
198
|
```
|
|
314
199
|
|
|
315
|
-
#### Next.js Integration
|
|
316
|
-
|
|
317
|
-
For Next.js applications, you may need to use dynamic imports to avoid SSR issues:
|
|
318
|
-
|
|
319
200
|
```tsx
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
/>
|
|
338
|
-
</div>
|
|
339
|
-
);
|
|
340
|
-
}
|
|
201
|
+
<CanonicalAuthorityEditor
|
|
202
|
+
mediaProvider={{
|
|
203
|
+
async upload(file) {
|
|
204
|
+
const body = new FormData();
|
|
205
|
+
body.append("file", file);
|
|
206
|
+
const res = await fetch("/api/media", { method: "POST", body });
|
|
207
|
+
return res.json(); // { url, id }
|
|
208
|
+
},
|
|
209
|
+
async search(query) {
|
|
210
|
+
const res = await fetch(`/api/media?q=${encodeURIComponent(query)}`);
|
|
211
|
+
return res.json();
|
|
212
|
+
},
|
|
213
|
+
async remove(id) {
|
|
214
|
+
await fetch(`/api/media/${id}`, { method: "DELETE" });
|
|
215
|
+
},
|
|
216
|
+
}}
|
|
217
|
+
/>
|
|
341
218
|
```
|
|
342
219
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
### Text Formatting
|
|
346
|
-
|
|
347
|
-
The editor supports all standard text formatting options:
|
|
348
|
-
|
|
349
|
-
- **Bold**, *Italic*, <u>Underline</u>, ~~Strikethrough~~
|
|
350
|
-
- Font sizes from 8pt to 96pt
|
|
351
|
-
- Text color and background color
|
|
352
|
-
- Headings (H1-H6)
|
|
353
|
-
- Paragraph, blockquote, code block
|
|
354
|
-
- Ordered and unordered lists
|
|
355
|
-
- Text alignment (left, center, right, justify)
|
|
356
|
-
- Superscript and subscript
|
|
357
|
-
|
|
358
|
-
### Tables
|
|
359
|
-
|
|
360
|
-
Full-featured table support includes:
|
|
361
|
-
|
|
362
|
-
- Create tables with custom rows and columns
|
|
363
|
-
- Add/delete rows and columns
|
|
364
|
-
- Merge and split cells
|
|
365
|
-
- Toggle header rows/cells
|
|
366
|
-
- Cell background colors
|
|
367
|
-
- Cell borders toggle
|
|
368
|
-
- Right-click context menu for table operations
|
|
369
|
-
- Keyboard navigation (Tab, Shift+Tab, Arrow keys)
|
|
220
|
+
Your `upload` implementation must independently validate file type, size, and content server-side โ the editor applies only a best-effort client-side allow-list check as a UX nicety, not a security boundary.
|
|
370
221
|
|
|
371
|
-
|
|
372
|
-
- `Tab` - Move to next cell
|
|
373
|
-
- `Shift+Tab` - Move to previous cell
|
|
374
|
-
- `Arrow keys` - Navigate between cells
|
|
375
|
-
- Right-click on cell - Open context menu
|
|
222
|
+
There's a fifth contract, **`CollabTransport`** (real-time multi-writer editing), exported for hosts building against it โ it defines `sendTransaction`/`onRemoteTransaction`/`onPresenceUpdate`/`sendPresence`/`getRevisionHistory`, but isn't wired into the editor's runtime yet. Without one connected, the editor behaves exactly as it does today: single-writer, no rebase path ever triggers.
|
|
376
223
|
|
|
377
|
-
|
|
224
|
+
## Imperative handle (ref)
|
|
378
225
|
|
|
379
|
-
|
|
226
|
+
`CanonicalAuthorityEditor`/`ClassicEditor` forward a `SmartEditorHandle` ref for everything `defaultValue`/props alone can't do โ replacing content programmatically, reading the current document, and version snapshots:
|
|
380
227
|
|
|
381
228
|
```tsx
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
// Examples of supported LaTeX:
|
|
385
|
-
// - E=mc^2
|
|
386
|
-
// - \frac{a}{b}
|
|
387
|
-
// - \sqrt{x}
|
|
388
|
-
// - \sum_{i=1}^{n} x_i
|
|
389
|
-
```
|
|
229
|
+
import { useRef } from "react";
|
|
230
|
+
import { CanonicalAuthorityEditor, type SmartEditorHandle } from "smartrte-react";
|
|
390
231
|
|
|
391
|
-
|
|
232
|
+
function Editor() {
|
|
233
|
+
const ref = useRef<SmartEditorHandle>(null);
|
|
392
234
|
|
|
393
|
-
|
|
235
|
+
const loadDocument = (doc) => ref.current?.replaceValue(doc, { keepSelection: false });
|
|
236
|
+
const currentDoc = () => ref.current?.getValue();
|
|
394
237
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css">
|
|
398
|
-
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"></script>
|
|
238
|
+
return <CanonicalAuthorityEditor ref={ref} />;
|
|
239
|
+
}
|
|
399
240
|
```
|
|
400
241
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const myMediaManager: MediaManagerAdapter = {
|
|
417
|
-
async search(query: MediaSearchQuery) {
|
|
418
|
-
// Search your media library
|
|
419
|
-
return {
|
|
420
|
-
items: [/* array of MediaItem */],
|
|
421
|
-
hasMore: false,
|
|
422
|
-
nextPage: undefined,
|
|
423
|
-
};
|
|
424
|
-
},
|
|
425
|
-
|
|
426
|
-
async upload(file: File) {
|
|
427
|
-
// Upload file to your server
|
|
428
|
-
return {
|
|
429
|
-
id: 'unique-id',
|
|
430
|
-
url: 'https://example.com/image.jpg',
|
|
431
|
-
thumbnailUrl: 'https://example.com/thumb.jpg',
|
|
432
|
-
title: 'Image title',
|
|
433
|
-
};
|
|
434
|
-
},
|
|
435
|
-
};
|
|
242
|
+
```ts
|
|
243
|
+
interface SmartEditorHandle {
|
|
244
|
+
getValue(): PersistedEditorDocument;
|
|
245
|
+
replaceValue(doc: PersistedEditorDocument, opts?: { keepSelection?: boolean }): void;
|
|
246
|
+
isDirty(): boolean;
|
|
247
|
+
markSaved(revision: number): void;
|
|
248
|
+
getRevision(): number;
|
|
249
|
+
focus(): void;
|
|
250
|
+
executeOperations(operations: readonly SmartOperation[], opts?: ExecuteOperationsOptions): void;
|
|
251
|
+
createCheckpoint(): SmartEditorCheckpoint;
|
|
252
|
+
restoreCheckpoint(checkpoint: SmartEditorCheckpoint): void;
|
|
253
|
+
saveVersion(opts?: { label?: string; authorId?: string }): DocumentVersion;
|
|
254
|
+
restoreVersion(version: DocumentVersion, opts?: { keepSelection?: boolean }): void;
|
|
255
|
+
}
|
|
436
256
|
```
|
|
437
257
|
|
|
438
|
-
|
|
258
|
+
`saveVersion`/`restoreVersion` are the same operations the toolbar's Version History panel calls โ use them directly if you want your own save-version UI instead of (or alongside) the built-in one.
|
|
439
259
|
|
|
440
|
-
|
|
260
|
+
## Import & export formats
|
|
441
261
|
|
|
442
|
-
|
|
262
|
+
The toolbar's "Import" and "Save as ..." tools cover HTML, Markdown, DOCX (Word), PDF, and the package's own JSON document format out of the box โ no extra setup. DOCX import preserves real Word styling (fonts, colors, spacing) where possible; PDF export prints the same HTML the editor renders, so formulas, tables, and images all appear as they do live.
|
|
443
263
|
|
|
444
|
-
|
|
445
|
-
<ClassicEditor theme="dark" onChange={handleChange} />
|
|
446
|
-
```
|
|
264
|
+
For a custom import/export pipeline (e.g. converting on a server, or a "Save as..." flow outside the toolbar), the underlying codecs are re-exported from `smartrte-core/foundation`: `exportDocxDocument`, `importDocxDocumentWithMammoth`, `importStyledDocxDocument`, `buildPdfPrintDocument`, `importPdfDocument`, and the format-fidelity contract (`builtInFormatFidelity`) describing exactly what's lossless vs. lossy per format.
|
|
447
265
|
|
|
448
|
-
|
|
266
|
+
## Theming
|
|
449
267
|
|
|
450
|
-
|
|
268
|
+
The editor uses CSS custom properties for every color โ there's no `theme` prop; dark mode is a CSS class.
|
|
451
269
|
|
|
452
270
|
```tsx
|
|
453
|
-
<
|
|
271
|
+
<CanonicalAuthorityEditor className="srte-dark" />
|
|
454
272
|
```
|
|
455
273
|
|
|
456
274
|
```css
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
--srte-border: #3a3a5c;
|
|
461
|
-
/* Override only the variables you need */
|
|
275
|
+
/* Or follow system preference yourself and toggle the class conditionally */
|
|
276
|
+
@media (prefers-color-scheme: dark) {
|
|
277
|
+
.srte-editor:not(.srte-dark) { /* your own light/dark logic here */ }
|
|
462
278
|
}
|
|
463
279
|
```
|
|
464
280
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
```tsx
|
|
468
|
-
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
469
|
-
|
|
470
|
-
<ClassicEditor theme={prefersDark ? 'dark' : 'light'} />
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
Or purely via CSS (without the `theme` prop):
|
|
281
|
+
Override individual variables (scoped to your own class, composed alongside `srte-dark` or standalone) to build a custom palette:
|
|
474
282
|
|
|
475
283
|
```css
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
284
|
+
.my-theme {
|
|
285
|
+
--srte-background: #1a1a2e;
|
|
286
|
+
--srte-foreground: #eaeaea;
|
|
287
|
+
--srte-border: #3a3a5c;
|
|
288
|
+
--srte-accent: #7c3aed;
|
|
289
|
+
/* override only what you need - everything else falls back to the default */
|
|
483
290
|
}
|
|
484
291
|
```
|
|
485
292
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
|
489
|
-
|
|
490
|
-
| `--srte-
|
|
491
|
-
| `--srte-
|
|
492
|
-
| `--srte-
|
|
493
|
-
| `--srte-
|
|
494
|
-
| `--srte-
|
|
495
|
-
| `--srte-
|
|
496
|
-
| `--srte-
|
|
497
|
-
| `--srte-
|
|
498
|
-
| `--srte-input-border` | `#e5e7eb` | Button, input, select borders |
|
|
499
|
-
| `--srte-modal-backdrop` | `rgba(0,0,0,0.35)` | Modal overlay background |
|
|
500
|
-
| `--srte-modal-bg` | `#ffffff` | Modal/dialog background |
|
|
501
|
-
| `--srte-modal-text` | `#000000` | Modal text color |
|
|
502
|
-
| `--srte-menu-bg` | `#ffffff` | Context menu background |
|
|
503
|
-
| `--srte-menu-text` | `#111111` | Context menu text |
|
|
504
|
-
| `--srte-menu-shadow` | `0 8px 24px rgba(0,0,0,0.18)` | Menu box-shadow |
|
|
505
|
-
| `--srte-accent` | `#1e90ff` | Accent/selection color |
|
|
506
|
-
| `--srte-accent-bg` | `rgba(30,144,255,0.15)` | Accent background (translucent) |
|
|
507
|
-
| `--srte-danger` | `#dc2626` | Destructive action color |
|
|
508
|
-
| `--srte-primary` | `#2563eb` | Primary action button color |
|
|
509
|
-
| `--srte-surface-subtle` | `#f3f4f6` | Subtle surface (dropzone, preset buttons) |
|
|
510
|
-
| `--srte-on-primary` | `#ffffff` | Text on primary/danger buttons |
|
|
511
|
-
| `--srte-cancel-bg` | `#f3f4f6` | Cancel button background |
|
|
512
|
-
|
|
513
|
-
### Important Notes
|
|
514
|
-
|
|
515
|
-
- **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.
|
|
516
|
-
- **Color picker swatches are not themed** โ they display actual color values regardless of theme.
|
|
517
|
-
- **The theme only affects editor chrome** โ toolbar, dialogs, menus, and container. User content remains unchanged.
|
|
518
|
-
|
|
519
|
-
## ๐ ๏ธ Development
|
|
520
|
-
|
|
521
|
-
### Prerequisites
|
|
522
|
-
|
|
523
|
-
- Node.js 18+
|
|
524
|
-
- pnpm 9.10.0+
|
|
525
|
-
|
|
526
|
-
### Setting Up Development Environment
|
|
527
|
-
|
|
528
|
-
1. **Clone the repository**
|
|
529
|
-
|
|
530
|
-
```bash
|
|
531
|
-
git clone https://github.com/ayush1852017/smart-rte.git
|
|
532
|
-
cd smart-rte
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
2. **Install dependencies**
|
|
536
|
-
|
|
537
|
-
```bash
|
|
538
|
-
pnpm install
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
3. **Build the project**
|
|
542
|
-
|
|
543
|
-
```bash
|
|
544
|
-
# Build TypeScript packages
|
|
545
|
-
pnpm build
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
4. **Run the development playground**
|
|
549
|
-
|
|
550
|
-
```bash
|
|
551
|
-
cd packages/react/playground
|
|
552
|
-
pnpm install
|
|
553
|
-
pnpm dev
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
The playground will be available at `http://localhost:5173`
|
|
293
|
+
| Variable | Description |
|
|
294
|
+
|---|---|
|
|
295
|
+
| `--srte-background` / `--srte-canvas` | Toolbar/chrome background vs. editing-surface background |
|
|
296
|
+
| `--srte-foreground` / `--srte-muted-foreground` | Primary vs. secondary text |
|
|
297
|
+
| `--srte-border` | Standard border color |
|
|
298
|
+
| `--srte-ring` | Focus ring color |
|
|
299
|
+
| `--srte-accent` / `--srte-accent-bg` | Selection/active-state color and its translucent background |
|
|
300
|
+
| `--srte-primary` / `--srte-on-primary` | Primary action button background/text |
|
|
301
|
+
| `--srte-danger` | Destructive action color |
|
|
302
|
+
| `--srte-modal-bg` / `--srte-modal-backdrop` | Dialog background and overlay |
|
|
303
|
+
| `--srte-menu-bg` / `--srte-menu-shadow` | Dropdown/context-menu background and shadow |
|
|
304
|
+
| `--srte-code-bg` / `--srte-code-text` | Code block colors |
|
|
557
305
|
|
|
558
|
-
|
|
306
|
+
These fall back to sensible defaults, and also read from common shadcn/ui-style tokens (`--card`, `--background`, `--foreground`, `--muted`, `--border`, `--ring`) if your app already defines those โ so a Tailwind/shadcn app may need no overrides at all. Colors set via the color picker (text/background) are inline styles on content and are unaffected by theming โ only editor chrome (toolbar, dialogs, menus) is themed.
|
|
559
307
|
|
|
560
|
-
|
|
561
|
-
smart-rte/
|
|
562
|
-
โโโ packages/
|
|
563
|
-
โ โโโ react/ # Main React package (smartrte-react)
|
|
564
|
-
โ โโโ src/
|
|
565
|
-
โ โ โโโ components/
|
|
566
|
-
โ โ โ โโโ ClassicEditor.tsx # Main editor component
|
|
567
|
-
โ โ โ โโโ MediaManager.tsx # Media management component
|
|
568
|
-
โ โ โโโ index.ts
|
|
569
|
-
โ โโโ playground/ # Development playground
|
|
570
|
-
โ โโโ package.json
|
|
571
|
-
โโโ dart/ # Flutter/Dart packages
|
|
572
|
-
โ โโโ smartrte_flutter/ # Flutter WebView integration
|
|
573
|
-
โ โโโ example_app/ # Flutter example
|
|
574
|
-
โโโ package.json
|
|
575
|
-
```
|
|
308
|
+
## Standalone / non-React embed
|
|
576
309
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
```bash
|
|
580
|
-
# Build the React package
|
|
581
|
-
cd packages/react
|
|
582
|
-
pnpm build
|
|
583
|
-
|
|
584
|
-
# This creates:
|
|
585
|
-
# - dist/index.js - ES module
|
|
586
|
-
# - dist/index.d.ts - TypeScript definitions
|
|
587
|
-
# - dist/embed.js - Standalone embed bundle
|
|
588
|
-
```
|
|
310
|
+
For a host that isn't a React app (or embeds via WebView โ the [Flutter package](https://github.com/ayush1852017/smart-rte/tree/master/dart/smartrte_flutter) uses exactly this), a global-script build is available:
|
|
589
311
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
```bash
|
|
593
|
-
# Run vitest
|
|
594
|
-
pnpm test
|
|
595
|
-
|
|
596
|
-
# Run E2E tests with Playwright
|
|
597
|
-
pnpm e2e
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
### Running Storybook
|
|
312
|
+
```ts
|
|
313
|
+
import "smartrte-react/standalone/classic-editor-embed";
|
|
601
314
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
315
|
+
window.SmartRTE.ClassicEditor.init({
|
|
316
|
+
target: document.getElementById("editor"),
|
|
317
|
+
value: "<p>Hello</p>",
|
|
318
|
+
tools: { video: false, audio: false },
|
|
319
|
+
onChange: (html) => console.log(html),
|
|
320
|
+
});
|
|
605
321
|
```
|
|
606
322
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
## ๐ Publishing
|
|
323
|
+
Returns a controller: `{ setHtml, getHtml, focus, blur, destroy }`.
|
|
610
324
|
|
|
611
|
-
|
|
325
|
+
## Security
|
|
612
326
|
|
|
613
|
-
The
|
|
327
|
+
The editor outputs HTML and never persists anything itself โ storage, credentials, and the actual save are always the host's. Pasted HTML is sanitized on the way in (DOMPurify), but **always sanitize before rendering elsewhere**: if you take the editor's HTML output and `dangerouslySetInnerHTML` it in a different context (an email, a public page), treat it the same as any other user-generated HTML.
|
|
614
328
|
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
cd packages/react
|
|
329
|
+
```tsx
|
|
330
|
+
import DOMPurify from "dompurify";
|
|
618
331
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
332
|
+
function DisplayContent({ html }: { html: string }) {
|
|
333
|
+
return <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />;
|
|
334
|
+
}
|
|
622
335
|
```
|
|
623
336
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
### Version Management
|
|
627
|
-
|
|
628
|
-
We follow [Semantic Versioning](https://semver.org/):
|
|
629
|
-
|
|
630
|
-
- **MAJOR** version for incompatible API changes
|
|
631
|
-
- **MINOR** version for backwards-compatible functionality
|
|
632
|
-
- **PATCH** version for backwards-compatible bug fixes
|
|
633
|
-
|
|
634
|
-
## ๐ค Contributing
|
|
337
|
+
Found a security issue? Please email support@openstash.in rather than opening a public issue.
|
|
635
338
|
|
|
636
|
-
|
|
339
|
+
## Browser support
|
|
637
340
|
|
|
638
|
-
|
|
341
|
+
Chromium, Firefox, and WebKit (Safari) โ the full end-to-end suite runs against all three, headless and current, on every change.
|
|
639
342
|
|
|
640
|
-
|
|
641
|
-
2. If not, create a new issue with:
|
|
642
|
-
- Clear title and description
|
|
643
|
-
- Steps to reproduce
|
|
644
|
-
- Expected vs actual behavior
|
|
645
|
-
- Screenshots if applicable
|
|
646
|
-
- Your environment (browser, OS, React version)
|
|
343
|
+
## Development
|
|
647
344
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
1. Check [existing feature requests](https://github.com/ayush1852017/smart-rte/issues?q=is%3Aissue+label%3Aenhancement)
|
|
651
|
-
2. Create a new issue with:
|
|
652
|
-
- Clear description of the feature
|
|
653
|
-
- Use cases
|
|
654
|
-
- Proposed API (if applicable)
|
|
655
|
-
|
|
656
|
-
### Pull Requests
|
|
657
|
-
|
|
658
|
-
1. **Fork** the repository
|
|
659
|
-
2. **Create** a feature branch (`git checkout -b feature/amazing-feature`)
|
|
660
|
-
3. **Make** your changes
|
|
661
|
-
4. **Test** your changes thoroughly
|
|
662
|
-
5. **Commit** with clear messages (`git commit -m 'Add amazing feature'`)
|
|
663
|
-
6. **Push** to your fork (`git push origin feature/amazing-feature`)
|
|
664
|
-
7. **Open** a Pull Request
|
|
665
|
-
|
|
666
|
-
#### PR Guidelines
|
|
667
|
-
|
|
668
|
-
- Follow the existing code style
|
|
669
|
-
- Add tests for new features
|
|
670
|
-
- Update documentation
|
|
671
|
-
- Keep PRs focused on a single feature/fix
|
|
672
|
-
- Write clear commit messages
|
|
673
|
-
|
|
674
|
-
### Development Workflow
|
|
345
|
+
This package lives in a pnpm workspace monorepo alongside `smartrte-core`.
|
|
675
346
|
|
|
676
347
|
```bash
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
#
|
|
681
|
-
pnpm dev # Run playground
|
|
682
|
-
pnpm test # Run tests
|
|
683
|
-
|
|
684
|
-
# 3. Build to ensure no errors
|
|
685
|
-
pnpm build
|
|
686
|
-
|
|
687
|
-
# 4. Commit and push
|
|
688
|
-
git add .
|
|
689
|
-
git commit -m "feat: add my feature"
|
|
690
|
-
git push origin feature/my-feature
|
|
691
|
-
|
|
692
|
-
# 5. Create PR on GitHub
|
|
348
|
+
git clone https://github.com/ayush1852017/smart-rte.git
|
|
349
|
+
cd smart-rte
|
|
350
|
+
pnpm install
|
|
351
|
+
pnpm build # builds every package
|
|
693
352
|
```
|
|
694
353
|
|
|
695
|
-
## ๐ Troubleshooting
|
|
696
|
-
|
|
697
|
-
### Common Issues
|
|
698
|
-
|
|
699
|
-
#### Issue: Editor not showing/rendering
|
|
700
|
-
|
|
701
|
-
**Solution:** Make sure React and React-DOM are installed as peer dependencies:
|
|
702
|
-
|
|
703
354
|
```bash
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
**Solution:** Ensure KaTeX is loaded in your HTML:
|
|
710
|
-
|
|
711
|
-
```html
|
|
712
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css">
|
|
713
|
-
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"></script>
|
|
355
|
+
# Live playground (aliased to workspace source, not the built dist - edits hot-reload)
|
|
356
|
+
cd packages/react/playground
|
|
357
|
+
pnpm install
|
|
358
|
+
pnpm dev # http://localhost:5173
|
|
714
359
|
```
|
|
715
360
|
|
|
716
|
-
#### Issue: TypeScript errors
|
|
717
|
-
|
|
718
|
-
**Solution:** Make sure you have the latest type definitions:
|
|
719
|
-
|
|
720
361
|
```bash
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
**Solution:** Use dynamic imports to disable SSR:
|
|
727
|
-
|
|
728
|
-
```tsx
|
|
729
|
-
const ClassicEditor = dynamic(
|
|
730
|
-
() => import('smartrte-react').then(mod => mod.ClassicEditor),
|
|
731
|
-
{ ssr: false }
|
|
732
|
-
);
|
|
733
|
-
```
|
|
734
|
-
|
|
735
|
-
#### Issue: Images not uploading
|
|
736
|
-
|
|
737
|
-
**Solution:** Check that the `media` prop is set to `true` and implement a custom `mediaManager` if you need server-side uploads.
|
|
738
|
-
|
|
739
|
-
## ๐ Security
|
|
740
|
-
|
|
741
|
-
### Reporting Security Issues
|
|
742
|
-
|
|
743
|
-
If you discover a security vulnerability, please email [support@openstash.in] instead of using the issue tracker.
|
|
744
|
-
|
|
745
|
-
### Content Sanitization
|
|
746
|
-
|
|
747
|
-
**โ ๏ธ Important:** The editor outputs raw HTML. Always sanitize user-generated content before displaying it to prevent XSS attacks.
|
|
748
|
-
|
|
749
|
-
Recommended libraries:
|
|
750
|
-
- [DOMPurify](https://github.com/cure53/DOMPurify)
|
|
751
|
-
- [sanitize-html](https://github.com/apostrophecms/sanitize-html)
|
|
752
|
-
|
|
753
|
-
Example:
|
|
754
|
-
|
|
755
|
-
```tsx
|
|
756
|
-
import DOMPurify from 'dompurify';
|
|
757
|
-
|
|
758
|
-
function DisplayContent({ html }) {
|
|
759
|
-
const sanitized = DOMPurify.sanitize(html);
|
|
760
|
-
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
|
|
761
|
-
}
|
|
762
|
-
```
|
|
763
|
-
|
|
764
|
-
## ๐ License
|
|
765
|
-
|
|
766
|
-
This project is licensed under the MIT License - see the [LICENSE](../../dart/smartrte_flutter/LICENSE) file for details.
|
|
767
|
-
|
|
768
|
-
```
|
|
769
|
-
MIT License
|
|
770
|
-
|
|
771
|
-
Copyright (c) 2025 Smart RTE Contributors
|
|
772
|
-
|
|
773
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
774
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
775
|
-
in the Software without restriction, including without limitation the rights
|
|
776
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
777
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
778
|
-
furnished to do so, subject to the following conditions:
|
|
779
|
-
|
|
780
|
-
The above copyright notice and this permission notice shall be included in all
|
|
781
|
-
copies or substantial portions of the Software.
|
|
782
|
-
|
|
783
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
784
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
785
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
786
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
787
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
788
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
789
|
-
SOFTWARE.
|
|
362
|
+
# From packages/react
|
|
363
|
+
pnpm test # vitest unit suite
|
|
364
|
+
pnpm e2e # Playwright, all 3 browsers
|
|
365
|
+
pnpm storybook # component stories, http://localhost:6006
|
|
790
366
|
```
|
|
791
367
|
|
|
792
|
-
##
|
|
793
|
-
|
|
794
|
-
- **Smart RTE Team** - Initial work and maintenance
|
|
795
|
-
|
|
796
|
-
See the list of [contributors](https://github.com/ayush1852017/smart-rte/contributors) who participated in this project.
|
|
797
|
-
|
|
798
|
-
## ๐ Acknowledgments
|
|
799
|
-
|
|
800
|
-
- [KaTeX](https://katex.org/) - For mathematical formula rendering
|
|
801
|
-
- [React](https://reactjs.org/) - The UI library
|
|
802
|
-
- [Vite](https://vitejs.dev/) - Build tool
|
|
803
|
-
- All our amazing [contributors](https://github.com/ayush1852017/smart-rte/contributors)
|
|
804
|
-
|
|
805
|
-
## ๐ Support
|
|
806
|
-
|
|
807
|
-
- **Documentation:** You're reading it! ๐
|
|
808
|
-
- **Issues:** [GitHub Issues](https://github.com/ayush1852017/smart-rte/issues)
|
|
809
|
-
- **Discussions:** [GitHub Discussions](https://github.com/ayush1852017/smart-rte/discussions)
|
|
810
|
-
- **Twitter:** [@smartrte](https://twitter.com/smartrte) (if applicable)
|
|
811
|
-
|
|
812
|
-
## ๐บ๏ธ Roadmap
|
|
813
|
-
|
|
814
|
-
### Current Version (0.2.x)
|
|
815
|
-
|
|
816
|
-
- โ
Rich text editing
|
|
817
|
-
- โ
Table support
|
|
818
|
-
- โ
Formula support (LaTeX/KaTeX)
|
|
819
|
-
- โ
Media management
|
|
820
|
-
- โ
TypeScript support
|
|
821
|
-
- โ
Dark mode & theming (CSS custom properties)
|
|
822
|
-
|
|
823
|
-
### Upcoming Features
|
|
824
|
-
|
|
825
|
-
- ๐ Collaborative editing
|
|
826
|
-
- ๐ Undo/Redo improvements
|
|
827
|
-
- ๐ Code syntax highlighting
|
|
828
|
-
- ๐ Markdown import/export
|
|
829
|
-
- ๐ Custom toolbar configuration
|
|
830
|
-
- ๐ Mobile optimization
|
|
831
|
-
- ๐ Accessibility improvements (ARIA labels, keyboard shortcuts)
|
|
832
|
-
|
|
833
|
-
## ๐ Browser Support
|
|
834
|
-
|
|
835
|
-
| Browser | Version |
|
|
836
|
-
|---------|---------|
|
|
837
|
-
| Chrome | Last 2 versions |
|
|
838
|
-
| Firefox | Last 2 versions |
|
|
839
|
-
| Safari | Last 2 versions |
|
|
840
|
-
| Edge | Last 2 versions |
|
|
841
|
-
|
|
842
|
-
## ๐ Related Packages
|
|
843
|
-
|
|
844
|
-
- **smartrte-flutter** - Flutter/Dart WebView implementation
|
|
845
|
-
|
|
846
|
-
## ๐ก Tips & Best Practices
|
|
847
|
-
|
|
848
|
-
1. **Performance**: For large documents, consider implementing lazy loading or pagination
|
|
849
|
-
2. **State Management**: Use React state or a state management library (Redux, Zustand) for complex applications
|
|
850
|
-
3. **Validation**: Always validate and sanitize HTML content before storing or displaying
|
|
851
|
-
4. **Accessibility**: Test with screen readers and keyboard navigation
|
|
852
|
-
5. **Mobile**: Test on mobile devices as touch interactions may differ
|
|
853
|
-
6. **Auto-save**: Implement auto-save functionality to prevent data loss
|
|
854
|
-
|
|
855
|
-
## ๐ Learning Resources
|
|
856
|
-
|
|
857
|
-
### For Entry-Level Developers
|
|
858
|
-
|
|
859
|
-
1. **Getting Started with React**: [React Official Tutorial](https://react.dev/learn)
|
|
860
|
-
2. **Understanding Rich Text Editors**: [MDN ContentEditable](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable)
|
|
861
|
-
3. **TypeScript Basics**: [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html)
|
|
862
|
-
|
|
863
|
-
### For Mid-Level Developers
|
|
864
|
-
|
|
865
|
-
1. **Advanced React Patterns**: Hooks, Context, Performance Optimization
|
|
866
|
-
2. **Component Design**: Building reusable, maintainable components
|
|
867
|
-
3. **State Management**: When and how to use external state management
|
|
868
|
-
|
|
869
|
-
### For Senior Developers
|
|
870
|
-
|
|
871
|
-
1. **Architecture**: Designing scalable editor implementations
|
|
872
|
-
2. **Performance**: Optimization techniques for large documents
|
|
873
|
-
3. **Extensibility**: Building plugin systems and custom extensions
|
|
874
|
-
4. **Cross-platform**: Adapting the editor for different frameworks
|
|
368
|
+
## Contributing
|
|
875
369
|
|
|
876
|
-
|
|
370
|
+
Issues and PRs are welcome at [github.com/ayush1852017/smart-rte](https://github.com/ayush1852017/smart-rte/issues). For a bug report, include a minimal repro, expected vs. actual behavior, and your browser/OS. For a PR: keep it focused on one change, add test coverage (unit and/or a Playwright spec, matching whichever existing test file is closest to what you touched), and run `pnpm build && pnpm test` before pushing.
|
|
877
371
|
|
|
878
|
-
|
|
372
|
+
## License
|
|
879
373
|
|
|
880
|
-
|
|
374
|
+
MIT โ see [LICENSE](https://github.com/ayush1852017/smart-rte/blob/master/LICENSE).
|