@actualwave/react-native-codeditor 1.2.1 → 1.2.2
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 +17 -0
- package/README.md +2 -0
- package/SKILL.md +2 -0
- package/lib/module/CodeEditor.js +7 -0
- package/lib/typescript/src/EditorAPI.d.ts +17 -0
- package/lib/typescript/src/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/assets/editor.html +84 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.2
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- A `content` change that arrives while the WebView is still booting is now applied on initialization instead of being dropped.
|
|
7
|
+
- `resetValue()` and `historyClear()` now really empty the undo history (they rebuild the editor
|
|
8
|
+
state, keeping the live extensions). Before, they only dispatched an unrecorded transaction, so
|
|
9
|
+
earlier undo steps survived and were applied on top of the new document.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `saveState()` / `restoreState(snapshot)`: JSON snapshot of document, selection, undo/redo history
|
|
13
|
+
and scroll offset, so a document can leave and re-enter an editor without losing its history.
|
|
14
|
+
`restoreState` keeps the live extensions (language, theme, shortcuts, completions) and fires the
|
|
15
|
+
content / history-size callbacks. Exports the `EditorStateSnapshot` type.
|
|
16
|
+
If a snapshot's undo history does not match its document (undo/redo would throw "Applying change
|
|
17
|
+
set to a document with the wrong length"), `restoreState` logs a warning and restores the text,
|
|
18
|
+
selection and scroll only, with an empty history.
|
|
19
|
+
|
|
3
20
|
## 1.2.1
|
|
4
21
|
|
|
5
22
|
### Changed
|
package/README.md
CHANGED
|
@@ -384,6 +384,8 @@ api.editor.historyClear(): Promise<void>
|
|
|
384
384
|
// Clears the undo/redo history without changing the document.
|
|
385
385
|
|
|
386
386
|
api.editor.historySize(): Promise<HistorySize>
|
|
387
|
+
api.editor.saveState(): Promise<EditorStateSnapshot> // doc + selection + undo/redo history + scroll (plain JSON)
|
|
388
|
+
api.editor.restoreState(snapshot): Promise<void> // keeps live language/theme/shortcuts; set language/theme first
|
|
387
389
|
// Returns the current { undo, redo } depth.
|
|
388
390
|
// Note: history size is also reported automatically via onHistorySizeUpdate.
|
|
389
391
|
```
|
package/SKILL.md
CHANGED
|
@@ -266,6 +266,8 @@ api.editor.historyUndo(): Promise<boolean>
|
|
|
266
266
|
api.editor.historyRedo(): Promise<boolean>
|
|
267
267
|
api.editor.historyClear(): Promise<void>
|
|
268
268
|
api.editor.historySize(): Promise<HistorySize> // { undo, redo }
|
|
269
|
+
api.editor.saveState(): Promise<EditorStateSnapshot> // doc + selection + undo/redo history + scroll (plain JSON)
|
|
270
|
+
api.editor.restoreState(snapshot): Promise<void> // keeps live language/theme/shortcuts; set language/theme first
|
|
269
271
|
```
|
|
270
272
|
|
|
271
273
|
### Editing commands
|
package/lib/module/CodeEditor.js
CHANGED
|
@@ -108,6 +108,13 @@ const CodeEditor = ({
|
|
|
108
108
|
onInitialized: api => {
|
|
109
109
|
initializedRef.current = true;
|
|
110
110
|
setInitialized(true);
|
|
111
|
+
// `content` may have changed while the WebView was still booting (the content effect
|
|
112
|
+
// skips pushing then), so the editor would keep the stale initial document.
|
|
113
|
+
const latest = propsRef.current.content;
|
|
114
|
+
if (latest !== currentContentRef.current) {
|
|
115
|
+
currentContentRef.current = latest;
|
|
116
|
+
void api.editor.setValue(latest);
|
|
117
|
+
}
|
|
111
118
|
callbacksRef.current.onInitialized(api);
|
|
112
119
|
},
|
|
113
120
|
onContentUpdate: value => {
|
|
@@ -44,6 +44,12 @@ export interface EmbedsConfig {
|
|
|
44
44
|
taggedTemplates?: string[];
|
|
45
45
|
supportExtensions?: Array<string | [string, Record<string, unknown>]>;
|
|
46
46
|
}
|
|
47
|
+
/** Opaque, JSON-serializable snapshot produced by `saveState()`. */
|
|
48
|
+
export interface EditorStateSnapshot {
|
|
49
|
+
state: unknown;
|
|
50
|
+
scrollTop: number;
|
|
51
|
+
scrollLeft: number;
|
|
52
|
+
}
|
|
47
53
|
export interface EditorAPI {
|
|
48
54
|
getValue(): Promise<string>;
|
|
49
55
|
setValue(value: string): Promise<void>;
|
|
@@ -72,6 +78,17 @@ export interface EditorAPI {
|
|
|
72
78
|
historyRedo(): Promise<boolean>;
|
|
73
79
|
historyClear(): Promise<void>;
|
|
74
80
|
historySize(): Promise<HistorySize>;
|
|
81
|
+
/**
|
|
82
|
+
* Snapshot of document, selection, undo/redo history and scroll offset. Plain JSON, so it
|
|
83
|
+
* can be kept in memory or persisted. Restore with `restoreState()`.
|
|
84
|
+
*/
|
|
85
|
+
saveState(): Promise<EditorStateSnapshot>;
|
|
86
|
+
/**
|
|
87
|
+
* Replaces the editor state with a `saveState()` snapshot, keeping the live extensions
|
|
88
|
+
* (language, theme, shortcuts, completions). Set language/theme first. The content and
|
|
89
|
+
* history-size callbacks fire afterwards.
|
|
90
|
+
*/
|
|
91
|
+
restoreState(snapshot: EditorStateSnapshot): Promise<void>;
|
|
75
92
|
scrollToCursor(margin?: number): Promise<void>;
|
|
76
93
|
startCompletion(): Promise<void>;
|
|
77
94
|
setCompletions(items: CompletionItem[]): Promise<void>;
|
|
@@ -3,5 +3,5 @@ export { default as CodeEditor } from './CodeEditor';
|
|
|
3
3
|
export type { CodeEditorProps } from './CodeEditor';
|
|
4
4
|
export { default as WebViewAPI } from './WebViewAPI';
|
|
5
5
|
export type { WebViewAPIHandlers, WebViewRef, InitialConfig } from './WebViewAPI';
|
|
6
|
-
export type { EditorAPI, ExtensionSpec, ViewportSettings, CursorPosition, HistorySize, CompletionItem, EmbedsConfig, } from './EditorAPI';
|
|
6
|
+
export type { EditorAPI, ExtensionSpec, ViewportSettings, CursorPosition, HistorySize, EditorStateSnapshot, CompletionItem, EmbedsConfig, } from './EditorAPI';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actualwave/react-native-codeditor",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "CodeMirror 6 code editor for React Native, embedded in a WebView with a full bidirectional RPC bridge. Offline, no CDN required.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/assets/editor.html
CHANGED
|
@@ -228,6 +228,40 @@
|
|
|
228
228
|
// setExtensions, loadExtension, destroy — then add methods not in EditorController.
|
|
229
229
|
const { getValue, setValue, setLanguage, setExtensions, loadExtension, destroy } = editor;
|
|
230
230
|
|
|
231
|
+
// Rebuilds the editor state around `doc` with an empty undo history (an unrecorded transaction
|
|
232
|
+
// only hides a change from the history — earlier entries stay and get mapped over it, so Undo
|
|
233
|
+
// would still walk back into the previous document). The live extension set is kept: the new
|
|
234
|
+
// state is built from the current config and the compartments' contents are re-applied.
|
|
235
|
+
const _replaceStateFresh = (doc, selection) => {
|
|
236
|
+
const view = editor.view;
|
|
237
|
+
const old = view.state;
|
|
238
|
+
view.setState(EditorState.create({ doc, selection, extensions: old.config.base }));
|
|
239
|
+
const effects = [];
|
|
240
|
+
old.config.compartments.forEach((ext, comp) => effects.push(comp.reconfigure(ext)));
|
|
241
|
+
if (effects.length) view.dispatch({ effects });
|
|
242
|
+
const state = view.state;
|
|
243
|
+
_rnPost('__contentChange__', {
|
|
244
|
+
value: state.doc.toString(),
|
|
245
|
+
undo: undoDepth(state),
|
|
246
|
+
redo: redoDepth(state),
|
|
247
|
+
});
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Dry-runs every undo and redo step on a throw-away copy of `state`. A history that does not
|
|
251
|
+
// match its document makes CodeMirror throw "Applying change set to a document with the wrong
|
|
252
|
+
// length" on the user's first Undo; catching that here keeps it from ever reaching the user.
|
|
253
|
+
const _historyConsistent = (state) => {
|
|
254
|
+
try {
|
|
255
|
+
let s = state;
|
|
256
|
+
const step = (tr) => { s = tr.state; };
|
|
257
|
+
for (let i = 0; i < 1000 && undo({ state: s, dispatch: step }); i++);
|
|
258
|
+
for (let i = 0; i < 1000 && redo({ state: s, dispatch: step }); i++);
|
|
259
|
+
return s.doc.eq(state.doc);
|
|
260
|
+
} catch (e) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
231
265
|
Object.assign(editorTarget, {
|
|
232
266
|
getValue,
|
|
233
267
|
setValue: (value) => {
|
|
@@ -245,6 +279,51 @@
|
|
|
245
279
|
loadExtension,
|
|
246
280
|
destroy,
|
|
247
281
|
|
|
282
|
+
// Serializable snapshot of the whole editor state: document, selection, undo/redo
|
|
283
|
+
// history and scroll offset. Pair with restoreState() to move a document in and out
|
|
284
|
+
// of this editor without losing its history.
|
|
285
|
+
saveState: async () => {
|
|
286
|
+
const { historyField } = await requireAsyncModule('@codemirror/commands');
|
|
287
|
+
return {
|
|
288
|
+
state: editor.view.state.toJSON({ history: historyField }),
|
|
289
|
+
scrollTop: editor.view.scrollDOM.scrollTop,
|
|
290
|
+
scrollLeft: editor.view.scrollDOM.scrollLeft,
|
|
291
|
+
};
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
// Replaces the editor's state with a snapshot from saveState(). The live extension set
|
|
295
|
+
// (language, theme, shortcuts, completions...) is kept: the new state is built from the
|
|
296
|
+
// current config and the compartments' current contents are re-applied. Set the desired
|
|
297
|
+
// language/theme BEFORE calling this. Notifies the host like a regular content change.
|
|
298
|
+
restoreState: async (snapshot) => {
|
|
299
|
+
const { historyField } = await requireAsyncModule('@codemirror/commands');
|
|
300
|
+
const old = editor.view.state;
|
|
301
|
+
const next = EditorState.fromJSON(
|
|
302
|
+
snapshot.state,
|
|
303
|
+
{ extensions: old.config.base },
|
|
304
|
+
{ history: historyField }
|
|
305
|
+
);
|
|
306
|
+
if (!_historyConsistent(next)) {
|
|
307
|
+
console.warn('restoreState: snapshot history does not match its document, restoring text only');
|
|
308
|
+
_replaceStateFresh(next.doc, next.selection);
|
|
309
|
+
editor.view.scrollDOM.scrollTop = snapshot.scrollTop ?? 0;
|
|
310
|
+
editor.view.scrollDOM.scrollLeft = snapshot.scrollLeft ?? 0;
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
editor.view.setState(next);
|
|
314
|
+
const effects = [];
|
|
315
|
+
old.config.compartments.forEach((ext, comp) => effects.push(comp.reconfigure(ext)));
|
|
316
|
+
if (effects.length) editor.view.dispatch({ effects });
|
|
317
|
+
editor.view.scrollDOM.scrollTop = snapshot.scrollTop ?? 0;
|
|
318
|
+
editor.view.scrollDOM.scrollLeft = snapshot.scrollLeft ?? 0;
|
|
319
|
+
const state = editor.view.state;
|
|
320
|
+
_rnPost('__contentChange__', {
|
|
321
|
+
value: state.doc.toString(),
|
|
322
|
+
undo: undoDepth(state),
|
|
323
|
+
redo: redoDepth(state),
|
|
324
|
+
});
|
|
325
|
+
},
|
|
326
|
+
|
|
248
327
|
// Maps a language name to a package that doesn't follow the
|
|
249
328
|
// @codemirror/lang-{name} convention. Takes effect on the next setLanguage().
|
|
250
329
|
registerLanguage: (name, packageName) => registerLanguage(name, packageName),
|
|
@@ -258,13 +337,8 @@
|
|
|
258
337
|
registerSupportExtension: (packageName, supportConfig) =>
|
|
259
338
|
registerSupportExtension(packageName, supportConfig),
|
|
260
339
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
view.dispatch({
|
|
264
|
-
changes: { from: 0, to: view.state.doc.length, insert: value ?? '' },
|
|
265
|
-
annotations: Transaction.addToHistory.of(false),
|
|
266
|
-
});
|
|
267
|
-
},
|
|
340
|
+
// Replaces the document AND empties the undo history.
|
|
341
|
+
resetValue: (value) => _replaceStateFresh(value ?? ''),
|
|
268
342
|
|
|
269
343
|
setTheme: (themeName) => _applyTheme(themeName),
|
|
270
344
|
|
|
@@ -358,11 +432,10 @@
|
|
|
358
432
|
|
|
359
433
|
historyRedo: () => redo(editor.view),
|
|
360
434
|
|
|
435
|
+
// Empties the undo/redo history, keeping the document and selection.
|
|
361
436
|
historyClear: () => {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
// callers don't crash, though the undo stack is not actually emptied.
|
|
365
|
-
editor.view.dispatch({ annotations: Transaction.addToHistory.of(false) });
|
|
437
|
+
const { doc, selection } = editor.view.state;
|
|
438
|
+
_replaceStateFresh(doc, selection);
|
|
366
439
|
},
|
|
367
440
|
|
|
368
441
|
startCompletion: () => startCompletion(editor.view),
|