@moloodev/editor-sdk 0.0.0-stage → 0.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.
Files changed (110) hide show
  1. package/package.json +62 -3
  2. package/src/channel.ts +287 -0
  3. package/src/chrome-manifest.ts +111 -0
  4. package/src/commands/clipboard/router.ts +99 -0
  5. package/src/commands/clipboard/types.ts +20 -0
  6. package/src/commands/history/stack.ts +53 -0
  7. package/src/commands/icons/catalog.ts +172 -0
  8. package/src/commands/index.ts +100 -0
  9. package/src/commands/keys/display.ts +16 -0
  10. package/src/commands/keys/normalize.ts +55 -0
  11. package/src/commands/keys/reserved.ts +62 -0
  12. package/src/commands/keys/validate.ts +77 -0
  13. package/src/commands/manifest/configuration.ts +59 -0
  14. package/src/commands/manifest/contribution-manifest-schema.ts +56 -0
  15. package/src/commands/manifest/editor-document.ts +501 -0
  16. package/src/commands/manifest/ids.ts +2 -0
  17. package/src/commands/manifest/merge.ts +215 -0
  18. package/src/commands/manifest/types.ts +113 -0
  19. package/src/commands/manifest/validate-manifest.ts +45 -0
  20. package/src/commands/react/context.tsx +249 -0
  21. package/src/commands/react/hotkey-defaults.ts +11 -0
  22. package/src/commands/react/scope-context.tsx +31 -0
  23. package/src/commands/react/scope-provider.tsx +123 -0
  24. package/src/commands/react/store.ts +182 -0
  25. package/src/commands/registry/create.ts +55 -0
  26. package/src/commands/registry/types.ts +12 -0
  27. package/src/commands/when/evaluate.ts +80 -0
  28. package/src/commands/when/index.ts +19 -0
  29. package/src/commands/when/parse.ts +276 -0
  30. package/src/commands/when/types.ts +24 -0
  31. package/src/commands/when/validate.ts +20 -0
  32. package/src/crdt/awareness.ts +29 -0
  33. package/src/crdt/bytes.ts +26 -0
  34. package/src/crdt/connect.ts +285 -0
  35. package/src/crdt/diff.ts +86 -0
  36. package/src/crdt/doc.ts +67 -0
  37. package/src/crdt/front-matter.ts +45 -0
  38. package/src/crdt/index.ts +76 -0
  39. package/src/crdt/persist.ts +40 -0
  40. package/src/crdt/protocol.ts +119 -0
  41. package/src/crdt/shared.ts +104 -0
  42. package/src/crdt/text-body.ts +125 -0
  43. package/src/crdt/undo.ts +59 -0
  44. package/src/crdt/vite-plugin.ts +42 -0
  45. package/src/crdt/yjs.ts +16 -0
  46. package/src/editor-common.ts +160 -0
  47. package/src/editor-id.ts +56 -0
  48. package/src/frame-attributes.ts +9 -0
  49. package/src/index.ts +229 -0
  50. package/src/json-schema.ts +13 -0
  51. package/src/layout/bind-toolbar.ts +60 -0
  52. package/src/layout/catalog.ts +82 -0
  53. package/src/layout/chrome/command-affordances.ts +47 -0
  54. package/src/layout/chrome/command-layout-state.ts +42 -0
  55. package/src/layout/chrome/editor-menu.tsx +358 -0
  56. package/src/layout/chrome/editor-shortcut-keys.tsx +27 -0
  57. package/src/layout/chrome/menu-styles.ts +3 -0
  58. package/src/layout/chrome/mid-menu-nodes.ts +27 -0
  59. package/src/layout/chrome/use-editor-command-affordances.ts +33 -0
  60. package/src/layout/chrome/visible-menu-leaves.ts +15 -0
  61. package/src/layout/content-error-notice.tsx +31 -0
  62. package/src/layout/define-editor-layout.ts +60 -0
  63. package/src/layout/define-editor-layout.types.ts +57 -0
  64. package/src/layout/doc-undo-bridge.tsx +22 -0
  65. package/src/layout/dock-zones.tsx +61 -0
  66. package/src/layout/drop.ts +39 -0
  67. package/src/layout/editor-layout.tsx +287 -0
  68. package/src/layout/editor-loading.tsx +23 -0
  69. package/src/layout/frame-layout-binding.tsx +74 -0
  70. package/src/layout/frame.ts +23 -0
  71. package/src/layout/hide.ts +103 -0
  72. package/src/layout/index.ts +14 -0
  73. package/src/layout/layout-binding.tsx +42 -0
  74. package/src/layout/layout-storage-key.ts +6 -0
  75. package/src/layout/layout-values.ts +48 -0
  76. package/src/layout/mutate.ts +486 -0
  77. package/src/layout/nesting-context.tsx +20 -0
  78. package/src/layout/panel-header-action.tsx +29 -0
  79. package/src/layout/panel-header.tsx +168 -0
  80. package/src/layout/panel-hide-menu.tsx +6 -0
  81. package/src/layout/panel-menu.tsx +85 -0
  82. package/src/layout/panel-section.tsx +114 -0
  83. package/src/layout/panel-size.ts +94 -0
  84. package/src/layout/panel-title.tsx +5 -0
  85. package/src/layout/registry.tsx +333 -0
  86. package/src/layout/restore-panel.ts +26 -0
  87. package/src/layout/slots-context.tsx +189 -0
  88. package/src/layout/tab-button.tsx +60 -0
  89. package/src/layout/toolbar-scroll.ts +22 -0
  90. package/src/layout/toolbar.tsx +168 -0
  91. package/src/layout/types.ts +41 -0
  92. package/src/layout/use-collapsed-sections.ts +50 -0
  93. package/src/layout/validate-layout-spec.ts +143 -0
  94. package/src/message-samples.ts +377 -0
  95. package/src/origin-headers.ts +57 -0
  96. package/src/print/index.ts +10 -0
  97. package/src/print/inspect.ts +16 -0
  98. package/src/print/schema.ts +122 -0
  99. package/src/protocol-entry.ts +19 -0
  100. package/src/protocol.ts +471 -0
  101. package/src/render-result.ts +25 -0
  102. package/src/view/editor-chrome.ts +28 -0
  103. package/src/view/hooks.ts +260 -0
  104. package/src/view/mount-editor-view.ts +36 -0
  105. package/src/view/port-binding.ts +127 -0
  106. package/src/view/port-commands.ts +36 -0
  107. package/src/view/port.ts +35 -0
  108. package/src/view/theme-class.ts +11 -0
  109. package/src/view.ts +313 -0
  110. package/README.md +0 -3
@@ -0,0 +1,501 @@
1
+ import {
2
+ isQualifiedName,
3
+ isStorageMode,
4
+ type AssetTypeIcon,
5
+ type StorageMode,
6
+ } from "@moloodev/platform";
7
+ import { parseNetwork } from "@moloodev/platform/network";
8
+ import type { ContributionManifest } from "./types";
9
+ import { validateManifest } from "./validate-manifest";
10
+
11
+ export const TABLER_ICON_URI_PREFIX = "tabler:";
12
+ const TABLER_ICON_NAME = /^[a-z0-9]+(-[a-z0-9]+)*$/;
13
+ const HEX_COLOR = /^#[0-9a-fA-F]{6}$/;
14
+
15
+ export function parseTablerIconUri(value: string): string | null {
16
+ if (!value.startsWith(TABLER_ICON_URI_PREFIX)) return null;
17
+ const name = value.slice(TABLER_ICON_URI_PREFIX.length);
18
+ return TABLER_ICON_NAME.test(name) ? name : null;
19
+ }
20
+
21
+ export const EDITOR_CAPABILITIES = ["import", "download"] as const;
22
+
23
+ export type EditorCapability = (typeof EDITOR_CAPABILITIES)[number];
24
+
25
+ export type EditorContributes = Pick<
26
+ ContributionManifest,
27
+ "commands" | "menus" | "keybindings" | "configuration"
28
+ >;
29
+
30
+ export type EditorFileMatcher = {
31
+ /** File extensions with leading dot, supporting compound extensions (e.g. [".layout.json"], [".md", ".markdown"]). */
32
+ extensions?: string[];
33
+ /** MIME types or wildcard families (e.g. ["text/markdown"], ["application/json"]). */
34
+ mimeTypes?: string[];
35
+ /** Exact JSON $schema URI match (e.g. "https://moloo.dev/schemas/layout/v1.json"). */
36
+ schema?: string;
37
+ /** Storage mode required by this editor ("json" | "text" | "blob"). */
38
+ storageMode?: StorageMode;
39
+ /** Display label for this file type in the browser (e.g. "레이아웃", "마크다운"). */
40
+ label?: string;
41
+ /** Tabler icon URI (`tabler:{name}`) for files matching this rule. */
42
+ icon?: AssetTypeIcon;
43
+ /** Badge tint color (`#rrggbb`) for files matching this rule. */
44
+ color?: string;
45
+ };
46
+
47
+ /** Product editor document. Contributions live under `contributes`. */
48
+ export type EditorManifest = {
49
+ id: string;
50
+ title: string;
51
+ description?: string;
52
+ path: string;
53
+ acceptedTypes: string[];
54
+ fileTypes?: EditorFileMatcher[];
55
+ templates?: EditorTemplate[];
56
+ categories?: string[];
57
+ /** Host-resolved Tabler icon URI (`tabler:{name}`). */
58
+ icon?: string;
59
+ /** Optional host I/O the editor implements (`editor.import` / `editor.download`). */
60
+ capabilities?: EditorCapability[];
61
+ importAccept?: string;
62
+ layoutVersion?: number;
63
+ /** HTTPS origins this document may contact. Omitted means none. */
64
+ network?: string[];
65
+ contributes?: EditorContributes;
66
+ };
67
+
68
+ const CONTRIBUTION_KEYS = [
69
+ "commands",
70
+ "menus",
71
+ "keybindings",
72
+ "configuration",
73
+ ] as const;
74
+
75
+ function isRecord(value: unknown): value is Record<string, unknown> {
76
+ return value != null && typeof value === "object" && !Array.isArray(value);
77
+ }
78
+
79
+ function requireString(value: unknown, field: string): string {
80
+ if (typeof value !== "string" || value.length === 0) {
81
+ throw new Error(`Invalid editor manifest: ${field} is required`);
82
+ }
83
+ return value;
84
+ }
85
+
86
+ function requireEditorIdentifier(id: string): string {
87
+ if (!isQualifiedName(id)) {
88
+ throw new Error(
89
+ "Invalid editor id: must be publisher.name (VS Code extension identifier)",
90
+ );
91
+ }
92
+ return id;
93
+ }
94
+
95
+ export function parseEditorCapabilities(input: unknown): EditorCapability[] {
96
+ if (!Array.isArray(input)) {
97
+ throw new Error("Invalid editor manifest: capabilities must be an array");
98
+ }
99
+ return input.map((value, index) => {
100
+ const capability = EDITOR_CAPABILITIES.find((item) => item === value);
101
+ if (capability === undefined) {
102
+ throw new Error(
103
+ `Invalid editor manifest: capabilities.${index} is not a known capability`,
104
+ );
105
+ }
106
+ return capability;
107
+ });
108
+ }
109
+
110
+ function parseEditorFileMatchers(input: unknown): EditorFileMatcher[] {
111
+ if (!Array.isArray(input)) {
112
+ throw new Error("Invalid editor manifest: fileTypes must be an array");
113
+ }
114
+ return input.map((entry, index) => {
115
+ if (!isRecord(entry)) {
116
+ throw new Error(
117
+ `Invalid editor manifest: fileTypes.${index} must be an object`,
118
+ );
119
+ }
120
+ let extensions: string[] | undefined;
121
+ if (entry.extensions != null) {
122
+ if (!Array.isArray(entry.extensions) || entry.extensions.length === 0) {
123
+ throw new Error(
124
+ `Invalid editor manifest: fileTypes.${index}.extensions must be a non-empty array`,
125
+ );
126
+ }
127
+ extensions = entry.extensions.map((ext, extIdx) => {
128
+ const value = requireString(
129
+ ext,
130
+ `fileTypes.${index}.extensions.${extIdx}`,
131
+ );
132
+ if (!value.startsWith(".") || value.length < 2) {
133
+ throw new Error(
134
+ `Invalid editor manifest: fileTypes.${index}.extensions.${extIdx} must start with .`,
135
+ );
136
+ }
137
+ return value.toLowerCase();
138
+ });
139
+ }
140
+
141
+ let mimeTypes: string[] | undefined;
142
+ if (entry.mimeTypes != null) {
143
+ if (!Array.isArray(entry.mimeTypes) || entry.mimeTypes.length === 0) {
144
+ throw new Error(
145
+ `Invalid editor manifest: fileTypes.${index}.mimeTypes must be a non-empty array`,
146
+ );
147
+ }
148
+ mimeTypes = entry.mimeTypes.map((mime, mimeIdx) => {
149
+ const value = requireString(
150
+ mime,
151
+ `fileTypes.${index}.mimeTypes.${mimeIdx}`,
152
+ );
153
+ if (!value.includes("/")) {
154
+ throw new Error(
155
+ `Invalid editor manifest: fileTypes.${index}.mimeTypes.${mimeIdx} must be a MIME type`,
156
+ );
157
+ }
158
+ return value.toLowerCase();
159
+ });
160
+ }
161
+
162
+ let schema: string | undefined;
163
+ if (entry.schema != null) {
164
+ schema = requireString(entry.schema, `fileTypes.${index}.schema`);
165
+ try {
166
+ const parsed = new URL(schema);
167
+ if (parsed.protocol !== "https:") {
168
+ throw new Error("not https");
169
+ }
170
+ } catch {
171
+ throw new Error(
172
+ `Invalid editor manifest: fileTypes.${index}.schema must be an https URI`,
173
+ );
174
+ }
175
+ }
176
+
177
+ if (!extensions && !mimeTypes && !schema) {
178
+ throw new Error(
179
+ `Invalid editor manifest: fileTypes.${index} must declare at least one of extensions, mimeTypes, or schema`,
180
+ );
181
+ }
182
+
183
+ let storageMode: StorageMode | undefined;
184
+ if (entry.storageMode != null) {
185
+ if (!isStorageMode(entry.storageMode)) {
186
+ throw new Error(
187
+ `Invalid editor manifest: fileTypes.${index}.storageMode must be json, text, or blob`,
188
+ );
189
+ }
190
+ storageMode = entry.storageMode;
191
+ }
192
+
193
+ let label: string | undefined;
194
+ if (entry.label != null) {
195
+ label = requireString(entry.label, `fileTypes.${index}.label`);
196
+ }
197
+
198
+ let icon: AssetTypeIcon | undefined;
199
+ if (entry.icon != null) {
200
+ const rawIcon = requireString(entry.icon, `fileTypes.${index}.icon`);
201
+ if (parseTablerIconUri(rawIcon) === null) {
202
+ throw new Error(
203
+ `Invalid editor manifest: fileTypes.${index}.icon must be tabler:{name}`,
204
+ );
205
+ }
206
+ icon = rawIcon as AssetTypeIcon;
207
+ }
208
+
209
+ let color: string | undefined;
210
+ if (entry.color != null) {
211
+ const rawColor = requireString(entry.color, `fileTypes.${index}.color`);
212
+ if (!HEX_COLOR.test(rawColor)) {
213
+ throw new Error(
214
+ `Invalid editor manifest: fileTypes.${index}.color must be #rrggbb`,
215
+ );
216
+ }
217
+ color = rawColor.toLowerCase();
218
+ }
219
+
220
+ return {
221
+ ...(extensions ? { extensions } : {}),
222
+ ...(mimeTypes ? { mimeTypes } : {}),
223
+ ...(schema ? { schema } : {}),
224
+ ...(storageMode ? { storageMode } : {}),
225
+ ...(label ? { label } : {}),
226
+ ...(icon ? { icon } : {}),
227
+ ...(color ? { color } : {}),
228
+ };
229
+ });
230
+ }
231
+
232
+
233
+ function parseEditorTemplates(input: unknown): EditorTemplate[] {
234
+ if (!Array.isArray(input)) {
235
+ throw new Error("Invalid editor manifest: templates must be an array");
236
+ }
237
+ const ids = new Set<string>();
238
+ return input.map((entry, index) => {
239
+ if (!isRecord(entry)) {
240
+ throw new Error(`Invalid editor manifest: templates.${index} must be an object`);
241
+ }
242
+ const id = requireString(entry.id, `templates.${index}.id`);
243
+ if (ids.has(id)) {
244
+ throw new Error(`Invalid editor manifest: duplicate template ${id}`);
245
+ }
246
+ ids.add(id);
247
+ const label = requireString(entry.label, `templates.${index}.label`);
248
+ const description = requireString(entry.description, `templates.${index}.description`);
249
+ const type = requireString(entry.type, `templates.${index}.type`);
250
+ const defaultName = requireString(entry.defaultName, `templates.${index}.defaultName`);
251
+ let icon: string | undefined;
252
+ if (entry.icon != null) {
253
+ icon = requireString(entry.icon, `templates.${index}.icon`);
254
+ if (parseTablerIconUri(icon) === null) {
255
+ throw new Error(`Invalid editor manifest: templates.${index}.icon must be tabler:{name}`);
256
+ }
257
+ }
258
+ let previewClassName: string | undefined;
259
+ if (entry.previewClassName != null) {
260
+ previewClassName = requireString(entry.previewClassName, `templates.${index}.previewClassName`);
261
+ }
262
+ let storageMode: StorageMode | undefined;
263
+ if (entry.storageMode != null) {
264
+ if (!isStorageMode(entry.storageMode)) {
265
+ throw new Error(`Invalid editor manifest: templates.${index}.storageMode must be json, text, or blob`);
266
+ }
267
+ storageMode = entry.storageMode;
268
+ }
269
+ let contentType: string | undefined;
270
+ if (entry.contentType != null) {
271
+ contentType = requireString(entry.contentType, `templates.${index}.contentType`);
272
+ }
273
+ let schema: string | undefined;
274
+ if (entry.schema != null) {
275
+ schema = requireString(entry.schema, `templates.${index}.schema`);
276
+ }
277
+ let payload: Record<string, unknown> | undefined;
278
+ if (entry.payload != null) {
279
+ if (!isRecord(entry.payload)) {
280
+ throw new Error(`Invalid editor manifest: templates.${index}.payload must be an object`);
281
+ }
282
+ payload = entry.payload;
283
+ }
284
+ return {
285
+ id,
286
+ label,
287
+ description,
288
+ ...(icon ? { icon } : {}),
289
+ ...(previewClassName ? { previewClassName } : {}),
290
+ type,
291
+ defaultName,
292
+ ...(storageMode ? { storageMode } : {}),
293
+ ...(contentType ? { contentType } : {}),
294
+ ...(schema ? { schema } : {}),
295
+ ...(payload ? { payload } : {}),
296
+ };
297
+ });
298
+ }
299
+
300
+ /** Shape validation for editor documents. A missing `network` becomes []. Throws on failure. */
301
+ export function validateEditorManifest(
302
+ input: unknown,
303
+ ): EditorManifest & { network: string[] } {
304
+ if (!isRecord(input)) {
305
+ throw new Error("Invalid editor manifest: expected an object");
306
+ }
307
+ if ("activation" in input) {
308
+ throw new Error(
309
+ "Invalid editor manifest: activation belongs on contribution packs, not editor documents",
310
+ );
311
+ }
312
+ if ("editor" in input) {
313
+ throw new Error(
314
+ "Invalid editor manifest: chrome fields belong at the top level, not under editor",
315
+ );
316
+ }
317
+ for (const key of CONTRIBUTION_KEYS) {
318
+ if (key in input) {
319
+ throw new Error(
320
+ `Invalid editor manifest: ${key} must live under contributes`,
321
+ );
322
+ }
323
+ }
324
+
325
+ const id = requireEditorIdentifier(requireString(input.id, "id"));
326
+ const title = requireString(input.title, "title");
327
+ const path = requireString(input.path, "path");
328
+ if (!path.startsWith("/")) {
329
+ throw new Error("Invalid editor manifest: path must start with /");
330
+ }
331
+ let fileTypes: EditorFileMatcher[] | undefined;
332
+ if (input.fileTypes != null) {
333
+ fileTypes = parseEditorFileMatchers(input.fileTypes);
334
+ }
335
+ let templates: EditorTemplate[] | undefined;
336
+ if (input.templates != null) {
337
+ templates = parseEditorTemplates(input.templates);
338
+ }
339
+
340
+ if (input.acceptedTypes == null && fileTypes === undefined) {
341
+ throw new Error("Invalid editor manifest: acceptedTypes is required");
342
+ }
343
+ if (input.acceptedTypes != null && !Array.isArray(input.acceptedTypes)) {
344
+ throw new Error("Invalid editor manifest: acceptedTypes is required");
345
+ }
346
+ const acceptedTypes = Array.isArray(input.acceptedTypes)
347
+ ? input.acceptedTypes.map((type, index) => {
348
+ if (typeof type !== "string" || type.length === 0) {
349
+ throw new Error(
350
+ `Invalid editor manifest: acceptedTypes.${index} must be a non-empty string`,
351
+ );
352
+ }
353
+ if (!isQualifiedName(type)) {
354
+ throw new Error(
355
+ `Invalid editor manifest: acceptedTypes.${index} must be publisher.name`,
356
+ );
357
+ }
358
+ return type;
359
+ })
360
+ : [];
361
+
362
+ let description: string | undefined;
363
+ if (input.description != null) {
364
+ description = requireString(input.description, "description");
365
+ }
366
+ let icon: string | undefined;
367
+ if (input.icon != null) {
368
+ icon = requireString(input.icon, "icon");
369
+ if (parseTablerIconUri(icon) === null) {
370
+ throw new Error("Invalid editor manifest: icon must be tabler:{name}");
371
+ }
372
+ }
373
+ if ("order" in input) {
374
+ throw new Error(
375
+ "Invalid editor manifest: order belongs on workspace favorites, not editor documents",
376
+ );
377
+ }
378
+
379
+ let categories: string[] | undefined;
380
+ if (input.categories != null) {
381
+ if (!Array.isArray(input.categories))
382
+ throw new Error("Invalid editor manifest: categories must be an array");
383
+ categories = input.categories.map((value, index) =>
384
+ requireString(value, `categories.${index}`),
385
+ );
386
+ }
387
+
388
+ let capabilities: EditorCapability[] | undefined;
389
+ if (input.capabilities != null) {
390
+ capabilities = parseEditorCapabilities(input.capabilities);
391
+ }
392
+
393
+ let importAccept: string | undefined;
394
+ if (input.importAccept != null) {
395
+ importAccept = requireString(input.importAccept, "importAccept");
396
+ }
397
+
398
+ let layoutVersion: number | undefined;
399
+ if (input.layoutVersion != null) {
400
+ if (
401
+ typeof input.layoutVersion !== "number" ||
402
+ !Number.isInteger(input.layoutVersion) ||
403
+ input.layoutVersion < 1
404
+ ) {
405
+ throw new Error(
406
+ "Invalid editor manifest: layoutVersion must be a positive integer",
407
+ );
408
+ }
409
+ layoutVersion = input.layoutVersion;
410
+ }
411
+
412
+ const network = parseNetwork("network" in input ? input.network : undefined);
413
+
414
+ let contributes: EditorContributes | undefined;
415
+ if (input.contributes != null) {
416
+ if (!isRecord(input.contributes)) {
417
+ throw new Error("Invalid editor manifest: contributes must be an object");
418
+ }
419
+ const contribution = validateManifest({
420
+ id,
421
+ activation: "exact",
422
+ ...input.contributes,
423
+ });
424
+ contributes = {
425
+ commands: contribution.commands,
426
+ menus: contribution.menus,
427
+ keybindings: contribution.keybindings,
428
+ configuration: contribution.configuration,
429
+ };
430
+ }
431
+
432
+ return {
433
+ id,
434
+ title,
435
+ ...(description ? { description } : {}),
436
+ path,
437
+ acceptedTypes,
438
+ ...(fileTypes ? { fileTypes } : {}),
439
+ ...(templates ? { templates } : {}),
440
+ ...(icon ? { icon } : {}),
441
+ ...(categories ? { categories } : {}),
442
+ ...(capabilities ? { capabilities } : {}),
443
+ ...(importAccept ? { importAccept } : {}),
444
+ ...(layoutVersion !== undefined ? { layoutVersion } : {}),
445
+ network,
446
+ ...(contributes ? { contributes } : {}),
447
+ };
448
+ }
449
+
450
+ export type EditorManifestInput = Omit<
451
+ EditorManifest,
452
+ "acceptedTypes" | "categories" | "capabilities" | "contributes"
453
+ > & {
454
+ acceptedTypes?: string[];
455
+ fileTypes?: EditorFileMatcher[];
456
+ templates?: EditorTemplate[];
457
+ categories?: string[];
458
+ capabilities?: EditorCapability[];
459
+ contributes?: {
460
+ commands?: NonNullable<EditorContributes["commands"]>;
461
+ menus?: NonNullable<EditorContributes["menus"]>;
462
+ keybindings?: NonNullable<EditorContributes["keybindings"]>;
463
+ configuration?: EditorContributes["configuration"];
464
+ };
465
+ };
466
+
467
+ export function defineEditorManifest(input: EditorManifestInput) {
468
+ const manifest = {
469
+ ...input,
470
+ acceptedTypes: input.acceptedTypes ?? [],
471
+ ...(input.fileTypes ? { fileTypes: input.fileTypes } : {}),
472
+ ...(input.templates ? { templates: input.templates } : {}),
473
+ categories: input.categories ?? [],
474
+ capabilities: input.capabilities ?? [],
475
+ contributes: {
476
+ commands: input.contributes?.commands ?? [],
477
+ menus: input.contributes?.menus ?? [],
478
+ keybindings: input.contributes?.keybindings ?? [],
479
+ ...(input.contributes?.configuration
480
+ ? { configuration: input.contributes.configuration }
481
+ : {}),
482
+ },
483
+ };
484
+ validateEditorManifest(manifest);
485
+ return manifest;
486
+ }
487
+
488
+ export type EditorTemplate = {
489
+ id: string;
490
+ label: string;
491
+ description: string;
492
+ /** Tabler icon URI (`tabler:{name}`). */
493
+ icon?: string;
494
+ previewClassName?: string;
495
+ type: string;
496
+ defaultName: string;
497
+ storageMode?: StorageMode;
498
+ contentType?: string;
499
+ schema?: string;
500
+ payload?: Record<string, unknown>;
501
+ };
@@ -0,0 +1,2 @@
1
+ /** Shell chrome contribution id (not a product service id). */
2
+ export const SHELL_SERVICE_MANIFEST_ID = "service";