@moloodev/editor-sdk 0.9.0 → 0.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moloodev/editor-sdk",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/view.ts",
@@ -29,8 +29,8 @@
29
29
  "y-protocols": "^1.0.7",
30
30
  "yjs": "^13.6.32",
31
31
  "zod": "^4.5.4",
32
- "@moloodev/platform": "0.9.0",
33
- "@moloodev/ui": "0.9.0"
32
+ "@moloodev/platform": "0.10.0",
33
+ "@moloodev/ui": "0.10.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "react": "^19.0.0",
@@ -48,7 +48,7 @@
48
48
  "react-dom": "^19.2.8",
49
49
  "tsx": "^4.23.13",
50
50
  "typescript": "^7.0.2",
51
- "@moloodev/typescript-config": "0.9.0"
51
+ "@moloodev/typescript-config": "0.10.0"
52
52
  },
53
53
  "publishConfig": {
54
54
  "access": "public"
package/src/index.ts CHANGED
@@ -207,7 +207,12 @@ export type EditorResponse<Type extends EditorRequestType = EditorRequestType> =
207
207
  | EditorSuccess<Type>
208
208
  | EditorFailure;
209
209
 
210
+ export type EditorSettingValue = boolean | number | string;
211
+ export type EditorSettingsValues = Record<string, EditorSettingValue>;
212
+
210
213
  export type EditorEvent =
214
+ | { v: 1; type: "settings.snapshot"; body: { values: EditorSettingsValues } }
215
+ | { v: 1; type: "settings.changed"; body: { values: EditorSettingsValues } }
211
216
  | { v: 1; type: "file.import"; body: { name: string; data: string } }
212
217
  | { v: 1; type: "session"; body: EditorSession }
213
218
  | { v: 1; type: "access"; body: EditorAccess }
@@ -313,6 +313,8 @@ export const session = {
313
313
  } satisfies EditorSession;
314
314
 
315
315
  export const events = {
316
+ "settings.snapshot": { v: 1, type: "settings.snapshot", body: { values: { "notes.indent": 2 } } },
317
+ "settings.changed": { v: 1, type: "settings.changed", body: { values: { "notes.indent": 4 } } },
316
318
  "file.import": { v: 1, type: "file.import", body: { name: "notes.md", data: "" } },
317
319
  session: {
318
320
  v: 1,
@@ -14,6 +14,8 @@ export type {
14
14
  EditorRevision,
15
15
  EditorSession,
16
16
  EditorSessionAsset,
17
+ EditorSettingValue,
18
+ EditorSettingsValues,
17
19
  EditorSuccess,
18
20
  Json,
19
21
  } from "./index";
package/src/protocol.ts CHANGED
@@ -125,6 +125,10 @@ const themeSchema = z.strictObject({
125
125
  theme: z.enum(["dark", "light"]),
126
126
  });
127
127
 
128
+ const settingsSchema = z.strictObject({
129
+ values: z.record(z.string(), z.union([z.boolean(), z.number(), z.string()])),
130
+ });
131
+
128
132
  const commandIdSchema = z.string().min(1);
129
133
 
130
134
  const commandInvokeBodySchema = z.strictObject({
@@ -319,6 +323,8 @@ const handshakeReplySchema = z.discriminatedUnion("ok", [
319
323
  export type HandshakeReply = z.infer<typeof handshakeReplySchema>;
320
324
 
321
325
  export const editorEventSchema = z.discriminatedUnion("type", [
326
+ z.strictObject({ v: z.literal(1), type: z.literal("settings.snapshot"), body: settingsSchema }),
327
+ z.strictObject({ v: z.literal(1), type: z.literal("settings.changed"), body: settingsSchema }),
322
328
  z.strictObject({
323
329
  v: z.literal(1),
324
330
  type: z.literal("file.import"),
package/src/view/hooks.ts CHANGED
@@ -9,6 +9,7 @@ import type {
9
9
  EditorRevision,
10
10
  EditorSession,
11
11
  EditorSessionAsset,
12
+ EditorSettingValue,
12
13
  } from "../index";
13
14
 
14
15
  export type { EditorAsset, EditorSessionAsset, EditorLayoutValues } from "../index";
@@ -30,6 +31,7 @@ export type EditorOpenBrowserArgs = {
30
31
  };
31
32
 
32
33
  export type EditorViewBinding = {
34
+ useEditorSetting(key: string): EditorSettingValue | undefined;
33
35
  useTheme(): { theme: "dark" | "light" };
34
36
  useKind(type: string): { id: string; label: string; color: string; icon: string };
35
37
  useKinds(): { id: string; label: string; color: string; icon: string }[];
@@ -136,6 +138,10 @@ export function useTheme() {
136
138
  return useBinding("useTheme").useTheme();
137
139
  }
138
140
 
141
+ export function useEditorSetting(key: string): EditorSettingValue | undefined {
142
+ return useBinding("useEditorSetting").useEditorSetting(key);
143
+ }
144
+
139
145
  export function useKind(type: string) {
140
146
  return useBinding("useKind").useKind(type);
141
147
  }
@@ -57,6 +57,9 @@ function createPortBinding(view: EditorView, acceptedTypes: readonly string[]):
57
57
  }
58
58
 
59
59
  return {
60
+ useEditorSetting(key) {
61
+ return useSyncExternalStore(view.onSettingsChange, () => view.getSetting(key), () => view.getSetting(key));
62
+ },
60
63
  useTheme() {
61
64
  const theme = useSnapshot().theme ?? "dark";
62
65
  return useMemo(() => ({ theme }), [theme]);
package/src/view.ts CHANGED
@@ -25,10 +25,14 @@ import type {
25
25
  EditorRequestType,
26
26
  EditorResponse,
27
27
  EditorSession,
28
+ EditorSettingValue,
29
+ EditorSettingsValues,
28
30
  } from "./index";
29
31
  import { EDITOR_IMPORT_BYTES_MAX, parseEditorId, type ChromeManifest } from "./protocol";
30
32
  import { editorPort } from "./view/port";
31
33
 
34
+ export type { EditorSettingValue, EditorSettingsValues } from "./index";
35
+
32
36
  export type EditorViewSnapshot = {
33
37
  session: EditorSession | null;
34
38
  access: EditorAccess | null;
@@ -59,6 +63,8 @@ export type EditorView = {
59
63
  subscribe(listener: () => void): () => void;
60
64
  onImportFile(listener: (file: File) => void): () => void;
61
65
  onCommand(listener: (commandId: string) => void): () => void;
66
+ getSetting(key: string): EditorSettingValue | undefined;
67
+ onSettingsChange(listener: (values: Readonly<EditorSettingsValues>) => void): () => void;
62
68
  sendCommandState(state: CommandStateBody): boolean;
63
69
  sendShortcut(body: ShortcutBody): boolean;
64
70
  close(): void;
@@ -124,6 +130,8 @@ export function connectEditorView(deps: EditorViewDeps): EditorView {
124
130
  const importFileListeners = new Set<(file: File) => void>();
125
131
  const pendingImportFiles: File[] = [];
126
132
  const commandListeners = new Set<(commandId: string) => void>();
133
+ let settings: Readonly<EditorSettingsValues> = {};
134
+ const settingsListeners = new Set<(values: Readonly<EditorSettingsValues>) => void>();
127
135
  let submitted: SubmittedChrome | null = null;
128
136
  let stopped = false;
129
137
  let channel: EditorChannel | undefined;
@@ -158,6 +166,12 @@ export function connectEditorView(deps: EditorViewDeps): EditorView {
158
166
  function onEvent(event: EditorEvent) {
159
167
  if (stopped) return;
160
168
  switch (event.type) {
169
+ case "settings.snapshot":
170
+ case "settings.changed": {
171
+ settings = Object.freeze(event.body.values);
172
+ for (const listener of [...settingsListeners]) listener(settings);
173
+ return;
174
+ }
161
175
  case "file.import": {
162
176
  const bytes = fromBase64(event.body.data);
163
177
  if (bytes.byteLength > EDITOR_IMPORT_BYTES_MAX) return;
@@ -281,6 +295,14 @@ export function connectEditorView(deps: EditorViewDeps): EditorView {
281
295
  commandListeners.delete(listener);
282
296
  };
283
297
  },
298
+ getSetting(key) {
299
+ return Object.hasOwn(settings, key) ? settings[key] : undefined;
300
+ },
301
+ onSettingsChange(listener) {
302
+ if (stopped) return () => {};
303
+ settingsListeners.add(listener);
304
+ return () => { settingsListeners.delete(listener); };
305
+ },
284
306
  sendCommandState(state) {
285
307
  if (!submitted?.commandIds.has(state.id)) return false;
286
308
  return channel?.emit({ v: 1, type: "command.state", body: state }) ?? false;
@@ -300,6 +322,7 @@ export function connectEditorView(deps: EditorViewDeps): EditorView {
300
322
  doc.off("update", onDocUpdate);
301
323
  listeners.clear();
302
324
  commandListeners.clear();
325
+ settingsListeners.clear();
303
326
  importFileListeners.clear();
304
327
  pendingImportFiles.length = 0;
305
328
  channel?.close();