editor-ts 0.0.1 → 0.0.12
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/README.md +197 -318
- package/index.ts +70 -0
- package/package.json +36 -11
- package/src/core/ComponentManager.ts +697 -6
- package/src/core/ComponentPalette.ts +109 -0
- package/src/core/CustomComponentRegistry.ts +74 -0
- package/src/core/KeyboardShortcuts.ts +220 -0
- package/src/core/LayerManager.ts +378 -0
- package/src/core/Page.ts +24 -5
- package/src/core/StorageManager.ts +447 -0
- package/src/core/StyleManager.ts +38 -2
- package/src/core/VersionControl.ts +189 -0
- package/src/core/aiChat.ts +427 -0
- package/src/core/iframeCanvas.ts +672 -0
- package/src/core/init.ts +3081 -248
- package/src/server/bun_server.ts +155 -0
- package/src/server/cf_worker.ts +225 -0
- package/src/server/schema.ts +21 -0
- package/src/server/sync.ts +195 -0
- package/src/types/sqlocal.d.ts +6 -0
- package/src/types.ts +591 -18
- package/src/utils/toolbar.ts +15 -1
package/src/types.ts
CHANGED
|
@@ -2,29 +2,185 @@
|
|
|
2
2
|
* Core type definitions for the HTML content editing library
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import type { OpencodeClient, ServerOptions, createOpencode } from '@opencode-ai/sdk';
|
|
6
|
+
import type { Page } from './core/Page';
|
|
7
|
+
import type { StorageAdapter, StorageManager, SqlocalClient } from './core/StorageManager';
|
|
8
|
+
|
|
9
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
10
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonValue[] | Component;
|
|
11
|
+
export type JsonObject = { [key: string]: JsonValue };
|
|
12
|
+
|
|
5
13
|
export interface PageData {
|
|
6
14
|
title: string;
|
|
7
15
|
item_id: number;
|
|
8
16
|
body: PageBody;
|
|
9
17
|
}
|
|
10
18
|
|
|
19
|
+
export interface MultiPageData {
|
|
20
|
+
pages: PageData[];
|
|
21
|
+
activePageIndex?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type PagePayload = PageData | MultiPageData | string;
|
|
25
|
+
|
|
26
|
+
export type AiProviderType = 'disabled' | 'opencode';
|
|
27
|
+
export type AiProviderMode = 'client' | 'client+server';
|
|
28
|
+
|
|
29
|
+
export type OpencodeServer = Awaited<ReturnType<typeof createOpencode>>['server'];
|
|
30
|
+
|
|
31
|
+
export interface OpencodeAiProviderConfig {
|
|
32
|
+
provider: 'opencode';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Optional: stream assistant output via server-sent events.
|
|
36
|
+
*
|
|
37
|
+
* When enabled, `editor.ai.chat()` can stream partial output via `options.onStream`.
|
|
38
|
+
*/
|
|
39
|
+
stream?: {
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Optional HTTP Basic Auth for password-protected opencode servers.
|
|
45
|
+
* Username defaults to "opencode" on the server if not provided.
|
|
46
|
+
*/
|
|
47
|
+
auth?: {
|
|
48
|
+
username?: string;
|
|
49
|
+
password: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Use your own SDK client instance.
|
|
54
|
+
*
|
|
55
|
+
* This is useful when your app already created a client via:
|
|
56
|
+
* `createOpencodeClient({ baseUrl })`
|
|
57
|
+
* or
|
|
58
|
+
* `const { client } = await createOpencode()`
|
|
59
|
+
*/
|
|
60
|
+
client?: OpencodeClient;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Optional server instance for `getUrl()`.
|
|
64
|
+
* If you pass this, EditorTs will NOT manage its lifecycle.
|
|
65
|
+
*/
|
|
66
|
+
server?: OpencodeServer;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* - 'client': connect to an existing opencode server via baseUrl
|
|
70
|
+
* - 'client+server': start a server and create a client
|
|
71
|
+
*/
|
|
72
|
+
mode?: AiProviderMode;
|
|
73
|
+
|
|
74
|
+
// Client-only mode
|
|
75
|
+
baseUrl?: string;
|
|
76
|
+
|
|
77
|
+
// Client+server mode
|
|
78
|
+
hostname?: ServerOptions['hostname'];
|
|
79
|
+
port?: ServerOptions['port'];
|
|
80
|
+
signal?: ServerOptions['signal'];
|
|
81
|
+
timeout?: ServerOptions['timeout'];
|
|
82
|
+
|
|
83
|
+
// opencode config overrides
|
|
84
|
+
config?: ServerOptions['config'];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type AiProviderConfig =
|
|
88
|
+
| { provider?: 'disabled' }
|
|
89
|
+
| OpencodeAiProviderConfig;
|
|
90
|
+
|
|
91
|
+
export type EditorTsAiChatReplacement = {
|
|
92
|
+
path: string;
|
|
93
|
+
content: string;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type EditorTsAiChatSession = {
|
|
97
|
+
id: string;
|
|
98
|
+
title?: string;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export type EditorTsAiChatResult = {
|
|
102
|
+
replacements: EditorTsAiChatReplacement[];
|
|
103
|
+
rawText: string;
|
|
104
|
+
|
|
105
|
+
/** Session that produced this response (for reuse/persistence). */
|
|
106
|
+
sessionId: string;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export interface EditorTsAiModule {
|
|
110
|
+
provider: 'opencode';
|
|
111
|
+
mode: AiProviderMode;
|
|
112
|
+
|
|
113
|
+
/** Lazily resolves to an opencode client */
|
|
114
|
+
getClient(): Promise<OpencodeClient>;
|
|
115
|
+
|
|
116
|
+
/** Returns current server URL/baseUrl if known */
|
|
117
|
+
getUrl(): string | null;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Request full-file replacements from AI.
|
|
121
|
+
* If a session is selected, prompts reuse that session.
|
|
122
|
+
*
|
|
123
|
+
* If streaming is enabled, `onStream` receives incremental text deltas.
|
|
124
|
+
*/
|
|
125
|
+
chat(
|
|
126
|
+
prompt: string,
|
|
127
|
+
options?: {
|
|
128
|
+
sessionId?: string;
|
|
129
|
+
model?: {
|
|
130
|
+
providerID: string;
|
|
131
|
+
modelID: string;
|
|
132
|
+
};
|
|
133
|
+
stream?: boolean;
|
|
134
|
+
onStream?: (delta: string) => void;
|
|
135
|
+
}
|
|
136
|
+
): Promise<EditorTsAiChatResult>;
|
|
137
|
+
|
|
138
|
+
/** Apply replacements to the current page */
|
|
139
|
+
apply(replacements: EditorTsAiChatReplacement[]): Promise<void>;
|
|
140
|
+
|
|
141
|
+
/** AI session management (persisted via StorageManager) */
|
|
142
|
+
sessions: {
|
|
143
|
+
current(): string | null;
|
|
144
|
+
setCurrent(sessionId: string | null): Promise<void>;
|
|
145
|
+
list(): Promise<EditorTsAiChatSession[]>;
|
|
146
|
+
create(title?: string): Promise<EditorTsAiChatSession>;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** Optional model selector data. */
|
|
150
|
+
models: {
|
|
151
|
+
list(): Promise<Array<{ providerID: string; modelID: string }>>;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/** Closes embedded server if started */
|
|
155
|
+
close(): Promise<void>;
|
|
156
|
+
}
|
|
157
|
+
|
|
11
158
|
export interface PageBody {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
159
|
+
//NOTE: only need one of these
|
|
160
|
+
html?: string;
|
|
161
|
+
components?: string | Component[]; // JSON string of Component[]
|
|
162
|
+
assets?: Asset[];
|
|
163
|
+
//NOTE: only need one of these
|
|
164
|
+
css?: string;
|
|
165
|
+
styles?: Style[];
|
|
17
166
|
}
|
|
18
167
|
|
|
168
|
+
export type ComponentAttributes = JsonObject & {
|
|
169
|
+
id?: string;
|
|
170
|
+
class?: string;
|
|
171
|
+
src?: string;
|
|
172
|
+
};
|
|
173
|
+
|
|
19
174
|
export interface Component {
|
|
20
175
|
type: string;
|
|
21
|
-
attributes?:
|
|
176
|
+
attributes?: ComponentAttributes;
|
|
22
177
|
components?: Component[];
|
|
23
178
|
tagName?: string;
|
|
24
179
|
void?: boolean;
|
|
25
180
|
style?: string;
|
|
26
181
|
script?: string;
|
|
27
|
-
|
|
182
|
+
content?: string; // Text content for the component
|
|
183
|
+
[key: string]: JsonValue | undefined;
|
|
28
184
|
}
|
|
29
185
|
|
|
30
186
|
export interface ToolbarConfig {
|
|
@@ -74,7 +230,7 @@ export interface ParsedComponents {
|
|
|
74
230
|
export interface ComponentQuery {
|
|
75
231
|
id?: string;
|
|
76
232
|
type?: string;
|
|
77
|
-
attributes?: Record<string,
|
|
233
|
+
attributes?: Record<string, JsonValue>;
|
|
78
234
|
tagName?: string;
|
|
79
235
|
}
|
|
80
236
|
|
|
@@ -94,23 +250,94 @@ export interface ToolbarRule {
|
|
|
94
250
|
config: ToolbarConfig;
|
|
95
251
|
}
|
|
96
252
|
|
|
97
|
-
export type ComponentSelector =
|
|
253
|
+
export type ComponentSelector =
|
|
98
254
|
| { id: string }
|
|
99
255
|
| { type: string }
|
|
100
256
|
| { tagName: string }
|
|
101
|
-
| { attributes: Record<string,
|
|
257
|
+
| { attributes: Record<string, JsonValue> }
|
|
102
258
|
| { custom: (component: Component) => boolean };
|
|
103
259
|
|
|
260
|
+
export interface EditorTsSyncMessage {
|
|
261
|
+
type: 'page';
|
|
262
|
+
key?: string;
|
|
263
|
+
payload: PagePayload;
|
|
264
|
+
sentAt: string;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface EditorTsSyncAck {
|
|
268
|
+
type: 'ack';
|
|
269
|
+
messageId: string;
|
|
270
|
+
receivedAt: string;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export type EditorTsSyncEnvelope = EditorTsSyncMessage | EditorTsSyncAck;
|
|
274
|
+
|
|
275
|
+
export type CustomComponentDefinition = {
|
|
276
|
+
/** Unique type identifier for this component (e.g. 'hero', 'button'). */
|
|
277
|
+
type: string;
|
|
278
|
+
|
|
279
|
+
/** Display name for UI (optional). */
|
|
280
|
+
label?: string;
|
|
281
|
+
|
|
282
|
+
/** Optional SVG icon (raw <svg>...</svg> markup). */
|
|
283
|
+
iconSvg?: string;
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Create a default component JSON object.
|
|
287
|
+
*
|
|
288
|
+
* This should return clean JSON-only component data.
|
|
289
|
+
*/
|
|
290
|
+
factory: () => Component;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export type CustomComponentRegistry = Record<string, CustomComponentDefinition>;
|
|
294
|
+
|
|
295
|
+
export type UiRender<Props> = (props: Props) => string | void;
|
|
296
|
+
|
|
297
|
+
export type PagesRenderProps = {
|
|
298
|
+
container: HTMLElement;
|
|
299
|
+
pages: PageData[];
|
|
300
|
+
activePageIndex: number;
|
|
301
|
+
onSelect: (index: number) => void;
|
|
302
|
+
};
|
|
303
|
+
|
|
104
304
|
export interface InitConfig {
|
|
105
305
|
// Required: iframe element ID (user creates this in their HTML)
|
|
106
306
|
iframeId: string;
|
|
107
|
-
|
|
307
|
+
|
|
308
|
+
// Optional: vim mode defaults to false
|
|
309
|
+
vimMode?: boolean;
|
|
310
|
+
|
|
311
|
+
/** Optional: Version control / undo-redo history (runtime config, persisted separately). */
|
|
312
|
+
versionControl?: {
|
|
313
|
+
enabled?: boolean;
|
|
314
|
+
maxSnapshots?: number;
|
|
315
|
+
};
|
|
316
|
+
|
|
108
317
|
// Required: page data
|
|
109
|
-
data:
|
|
110
|
-
|
|
318
|
+
data: PagePayload;
|
|
319
|
+
|
|
320
|
+
/** Optional: load initial data from storage. */
|
|
321
|
+
initialStorageKey?: string;
|
|
322
|
+
|
|
323
|
+
/** Optional: auto-save configuration (runtime only). */
|
|
324
|
+
autoSave?: {
|
|
325
|
+
/** Enable auto-save (default: false). */
|
|
326
|
+
enabled?: boolean;
|
|
327
|
+
|
|
328
|
+
/** Save after this many edits (default: 1). */
|
|
329
|
+
everyEdits?: number;
|
|
330
|
+
|
|
331
|
+
/** Optional storage key; otherwise uses last saveTo/loadFrom key. */
|
|
332
|
+
key?: string;
|
|
333
|
+
};
|
|
334
|
+
|
|
111
335
|
// Optional: toolbar configuration (runtime only)
|
|
112
336
|
toolbars?: ToolbarInitConfig;
|
|
113
|
-
|
|
337
|
+
|
|
338
|
+
// Optional: custom component registry
|
|
339
|
+
customComponents?: CustomComponentRegistry;
|
|
340
|
+
|
|
114
341
|
// Optional: UI container IDs (user controls placement)
|
|
115
342
|
ui?: {
|
|
116
343
|
sidebar?: {
|
|
@@ -125,13 +352,309 @@ export interface InitConfig {
|
|
|
125
352
|
containerId?: string; // Where to render selected component info (optional)
|
|
126
353
|
enabled?: boolean;
|
|
127
354
|
};
|
|
355
|
+
layers?: {
|
|
356
|
+
containerId?: string; // Where to render layer panel (optional)
|
|
357
|
+
enabled?: boolean;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
/** Optional: multipage switcher UI */
|
|
361
|
+
pages?: {
|
|
362
|
+
containerId?: string; // Where to render page dropdown
|
|
363
|
+
enabled?: boolean;
|
|
364
|
+
/** Optional: custom render for page dropdown. */
|
|
365
|
+
render?: UiRender<PagesRenderProps>;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// Optional: component palette (click-to-place)
|
|
369
|
+
componentPalette?: {
|
|
370
|
+
containerId?: string;
|
|
371
|
+
enabled?: boolean;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/** Optional: AI chat UI bindings (expand/collapse + controls). */
|
|
375
|
+
aiChat?: {
|
|
376
|
+
/** Root element to receive dataset/class toggles. Defaults to containerId if omitted. */
|
|
377
|
+
rootId?: string;
|
|
378
|
+
|
|
379
|
+
/** Expand/collapse button id. */
|
|
380
|
+
expandButtonId?: string;
|
|
381
|
+
|
|
382
|
+
/** Optional: set initial expanded state. */
|
|
383
|
+
defaultExpanded?: boolean;
|
|
384
|
+
|
|
385
|
+
/** Optional: set a class on the root when expanded. */
|
|
386
|
+
expandedClassName?: string;
|
|
387
|
+
|
|
388
|
+
/** Optional: set a class on the root when collapsed. */
|
|
389
|
+
collapsedClassName?: string;
|
|
390
|
+
|
|
391
|
+
/** AI chat input textarea id. */
|
|
392
|
+
inputId?: string;
|
|
393
|
+
|
|
394
|
+
/** Send button id. */
|
|
395
|
+
sendButtonId?: string;
|
|
396
|
+
|
|
397
|
+
/** Optional manual apply button id (fallback when auto-apply fails/disabled). */
|
|
398
|
+
applyButtonId?: string;
|
|
399
|
+
|
|
400
|
+
/** Chat log container id. */
|
|
401
|
+
logId?: string;
|
|
402
|
+
|
|
403
|
+
/** Optional sessions dropdown id. */
|
|
404
|
+
sessionSelectId?: string;
|
|
405
|
+
|
|
406
|
+
/** Optional model selector dropdown id. */
|
|
407
|
+
modelSelectId?: string;
|
|
408
|
+
|
|
409
|
+
/** Optional create-session button id. */
|
|
410
|
+
sessionNewButtonId?: string;
|
|
411
|
+
|
|
412
|
+
/** Optional health-check button id. */
|
|
413
|
+
healthButtonId?: string;
|
|
414
|
+
|
|
415
|
+
/** Optional health-check status container id. */
|
|
416
|
+
healthStatusId?: string;
|
|
417
|
+
|
|
418
|
+
/** Optional: baseUrl input id (used only when aiProvider is opencode client mode). */
|
|
419
|
+
baseUrlInputId?: string;
|
|
420
|
+
|
|
421
|
+
/** Optional: enable auto-apply for chat results. Default: true. */
|
|
422
|
+
autoApply?: boolean;
|
|
423
|
+
|
|
424
|
+
/** Optional: override streaming for this chat UI. */
|
|
425
|
+
stream?: {
|
|
426
|
+
enabled?: boolean;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
/** Optional: external link to OpenCode web chat UI. */
|
|
430
|
+
link?: {
|
|
431
|
+
/** Anchor element id for the external-link button. */
|
|
432
|
+
anchorId?: string;
|
|
433
|
+
|
|
434
|
+
/** Optional: override the URL path appended to the base URL. */
|
|
435
|
+
path?: string;
|
|
436
|
+
|
|
437
|
+
enabled?: boolean;
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
enabled?: boolean;
|
|
441
|
+
};
|
|
442
|
+
/** Optional: auto-save progress UI. */
|
|
443
|
+
autoSave?: {
|
|
444
|
+
/** Progress bar element id. */
|
|
445
|
+
progressBarId?: string;
|
|
446
|
+
enabled?: boolean;
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
/** Optional: command palette UI (Ctrl/Cmd+K). */
|
|
450
|
+
commandPalette?: {
|
|
451
|
+
/** Root modal container id. */
|
|
452
|
+
containerId?: string;
|
|
453
|
+
/** Search input id. */
|
|
454
|
+
inputId?: string;
|
|
455
|
+
/** Results list container id. */
|
|
456
|
+
resultsId?: string;
|
|
457
|
+
/** Optional: close button id. */
|
|
458
|
+
closeButtonId?: string;
|
|
459
|
+
/** Optional: hint element id (for status text). */
|
|
460
|
+
hintId?: string;
|
|
461
|
+
/** Optional: custom items to show in the palette. */
|
|
462
|
+
items?: Array<{
|
|
463
|
+
title: string;
|
|
464
|
+
action: () => void | Promise<void>;
|
|
465
|
+
type?: 'component' | 'command';
|
|
466
|
+
}>;
|
|
467
|
+
/** Optional: command palette shortcuts (runtime only). */
|
|
468
|
+
shortcuts?: ShortcutDefinition[];
|
|
469
|
+
/** Enable palette UI (default true when IDs provided). */
|
|
470
|
+
enabled?: boolean;
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
editors?: {
|
|
474
|
+
files?: {
|
|
475
|
+
containerId?: string; // Where to render workspace file list
|
|
476
|
+
enabled?: boolean;
|
|
477
|
+
};
|
|
478
|
+
viewer?: {
|
|
479
|
+
containerId?: string; // Where to render read-only file preview
|
|
480
|
+
enabled?: boolean;
|
|
481
|
+
};
|
|
482
|
+
js?: {
|
|
483
|
+
containerId?: string; // Where to render component JS editor
|
|
484
|
+
enabled?: boolean;
|
|
485
|
+
};
|
|
486
|
+
css?: {
|
|
487
|
+
containerId?: string; // Where to render page CSS editor
|
|
488
|
+
enabled?: boolean;
|
|
489
|
+
};
|
|
490
|
+
json?: {
|
|
491
|
+
containerId?: string; // Where to render page JSON editor
|
|
492
|
+
enabled?: boolean;
|
|
493
|
+
};
|
|
494
|
+
jsx?: {
|
|
495
|
+
containerId?: string; // Where to render JSX/TSX view
|
|
496
|
+
enabled?: boolean;
|
|
497
|
+
};
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Optional: wire UI tabs to toggle between canvas (iframe) and code panels.
|
|
502
|
+
*
|
|
503
|
+
* This does not create any UI; it only attaches click handlers to your
|
|
504
|
+
* existing buttons and toggles visibility/dataset state.
|
|
505
|
+
*/
|
|
506
|
+
viewTabs?: {
|
|
507
|
+
editorButtonId?: string;
|
|
508
|
+
codeButtonId?: string;
|
|
509
|
+
defaultView?: 'editor' | 'code';
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Optional: tabs within the code view (JS/CSS/JSON/JSX).
|
|
514
|
+
*
|
|
515
|
+
* This does not create any UI; it only wires existing buttons and toggles
|
|
516
|
+
* visibility of the editor containers.
|
|
517
|
+
*/
|
|
518
|
+
codeTabs?: {
|
|
519
|
+
defaultTab?: 'files' | 'viewer' | 'js' | 'css' | 'json' | 'jsx';
|
|
520
|
+
filesButtonId?: string;
|
|
521
|
+
viewerButtonId?: string;
|
|
522
|
+
jsButtonId?: string;
|
|
523
|
+
cssButtonId?: string;
|
|
524
|
+
jsonButtonId?: string;
|
|
525
|
+
jsxButtonId?: string;
|
|
526
|
+
};
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
// Optional: built-in code editor provider
|
|
530
|
+
// - 'textarea' (default): lightweight, zero deps
|
|
531
|
+
// - 'modern-monaco': advanced editor (requires optional peer dependency)
|
|
532
|
+
codeEditor?: {
|
|
533
|
+
provider?: 'textarea' | 'modern-monaco';
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* When using modern-monaco, enable a workspace-backed virtual filesystem.
|
|
537
|
+
*
|
|
538
|
+
* This makes editor models behave like real files and is the basis for
|
|
539
|
+
* later handing a file tree to AI/codegen.
|
|
540
|
+
*/
|
|
541
|
+
workspace?: {
|
|
542
|
+
enabled?: boolean;
|
|
543
|
+
name?: string;
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
// Optional: AI provider integration
|
|
548
|
+
// - 'disabled' (default): no AI integration
|
|
549
|
+
// - 'opencode': integrates with @opencode-ai/sdk
|
|
550
|
+
aiProvider?: AiProviderConfig;
|
|
551
|
+
|
|
552
|
+
/** Optional: keyboard shortcut definitions. */
|
|
553
|
+
shortcuts?: ShortcutDefinition[];
|
|
554
|
+
|
|
555
|
+
/** Optional: shortcut behavior configuration. */
|
|
556
|
+
shortcutConfig?: {
|
|
557
|
+
/** Which modifier key "mod" should map to (default: 'ctrl'). */
|
|
558
|
+
modKey?: 'ctrl' | 'meta' | 'alt';
|
|
128
559
|
};
|
|
129
|
-
|
|
560
|
+
|
|
130
561
|
// Optional: event callbacks
|
|
131
562
|
onComponentSelect?: (component: Component) => void;
|
|
132
563
|
onComponentEdit?: (component: Component) => void;
|
|
133
564
|
onComponentDelete?: (component: Component) => void;
|
|
134
565
|
onComponentDuplicate?: (component: Component, duplicate: Component) => void;
|
|
566
|
+
|
|
567
|
+
// Text editing callbacks
|
|
568
|
+
onTextEditStart?: (component: Component) => void;
|
|
569
|
+
onTextUpdate?: (component: Component, newContent: string, originalContent: string) => void;
|
|
570
|
+
onTextEditEnd?: (component: Component, saved: boolean) => void;
|
|
571
|
+
|
|
572
|
+
// Image editing callbacks
|
|
573
|
+
onImageEditStart?: (component: Component, currentSrc: string) => void;
|
|
574
|
+
onImageUpdate?: (component: Component, newSrc: string, originalSrc: string, fileInfo: ImageFileInfo) => void;
|
|
575
|
+
onImageEditEnd?: (component: Component, saved: boolean) => void;
|
|
576
|
+
|
|
577
|
+
// Optional: storage configuration
|
|
578
|
+
storage?: StorageConfig;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export type ShortcutDefinition = {
|
|
582
|
+
key: string;
|
|
583
|
+
action: () => void | Promise<void>;
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
export interface ImageFileInfo {
|
|
587
|
+
fileName: string;
|
|
588
|
+
fileType: string;
|
|
589
|
+
fileSize: number;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Storage types (imported from StorageManager)
|
|
593
|
+
export interface LocalStorageConfig {
|
|
594
|
+
/**
|
|
595
|
+
* Local storage is the default when `storage` is omitted.
|
|
596
|
+
*
|
|
597
|
+
* This field is optional to allow concise configs like:
|
|
598
|
+
* { prefix: 'myapp_' }
|
|
599
|
+
*/
|
|
600
|
+
type?: 'local';
|
|
601
|
+
prefix?: string;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export interface RemoteStorageConfig {
|
|
605
|
+
type: 'remote';
|
|
606
|
+
baseUrl: string;
|
|
607
|
+
imageUploadMethod?: 'form' | 'json';
|
|
608
|
+
headers?: Record<string, string>;
|
|
609
|
+
endpoints?: {
|
|
610
|
+
savePage?: string;
|
|
611
|
+
loadPage?: string;
|
|
612
|
+
deletePage?: string;
|
|
613
|
+
uploadImage?: string;
|
|
614
|
+
deleteImage?: string;
|
|
615
|
+
listPages?: string;
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export interface SqlocalStorageConfig {
|
|
620
|
+
type: 'sqlocal';
|
|
621
|
+
/** SQLite database file name stored in OPFS (used when `client` is not provided). */
|
|
622
|
+
databaseName?: string;
|
|
623
|
+
/** Pre-initialized SQLocal client (avoids dynamic import). */
|
|
624
|
+
client?: SqlocalClient;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export type StorageConfig = LocalStorageConfig | RemoteStorageConfig | SqlocalStorageConfig;
|
|
628
|
+
|
|
629
|
+
export interface ServerPageMeta {
|
|
630
|
+
key: string;
|
|
631
|
+
updatedAt: number;
|
|
632
|
+
checksum?: string;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export interface ServerFile {
|
|
636
|
+
path: string;
|
|
637
|
+
content: string;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export interface ServerSyncAdapter {
|
|
641
|
+
listPages(): Promise<ServerPageMeta[]>;
|
|
642
|
+
listFiles(pageKey: string): Promise<ServerFile[]>;
|
|
643
|
+
saveFiles(pageKey: string, files: ServerFile[]): Promise<void>;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export type FrontendSyncStatus =
|
|
647
|
+
| { state: 'loading' }
|
|
648
|
+
| { state: 'saving' }
|
|
649
|
+
| { state: 'idle' }
|
|
650
|
+
| { state: 'error'; message: string };
|
|
651
|
+
|
|
652
|
+
export interface FrontendSyncOptions {
|
|
653
|
+
pageKey: string;
|
|
654
|
+
storage: StorageAdapter;
|
|
655
|
+
adapter: ServerSyncAdapter;
|
|
656
|
+
includeFiles?: (path: string) => boolean;
|
|
657
|
+
onStatus?: (status: FrontendSyncStatus) => void;
|
|
135
658
|
}
|
|
136
659
|
|
|
137
660
|
export interface ToolbarInitConfig {
|
|
@@ -141,12 +664,62 @@ export interface ToolbarInitConfig {
|
|
|
141
664
|
default?: ToolbarConfig;
|
|
142
665
|
}
|
|
143
666
|
|
|
667
|
+
export interface EditorTsEventMap {
|
|
668
|
+
componentSelect: [component: Component];
|
|
669
|
+
componentInsert: [component: Component, parentId: string | null];
|
|
670
|
+
componentReorder: [component: Component, newParentId: string | null, newIndex: number];
|
|
671
|
+
|
|
672
|
+
componentEdit: [component: Component];
|
|
673
|
+
componentEditJS: [component: Component];
|
|
674
|
+
componentDuplicate: [original: Component, duplicate: Component];
|
|
675
|
+
componentDelete: [component: Component];
|
|
676
|
+
|
|
677
|
+
pageEditCSS: [body: PageBody];
|
|
678
|
+
pageEditJSON: [body: PageBody];
|
|
679
|
+
|
|
680
|
+
pageSaved: [key: string];
|
|
681
|
+
pageLoaded: [key: string];
|
|
682
|
+
|
|
683
|
+
textEditStart: [component: Component];
|
|
684
|
+
textUpdate: [component: Component, newContent: string, originalContent: string];
|
|
685
|
+
textEditEnd: [component: Component, saved: boolean];
|
|
686
|
+
|
|
687
|
+
imageEditStart: [component: Component, currentSrc: string];
|
|
688
|
+
imageUpdate: [component: Component, newSrc: string, originalSrc: string, fileInfo: ImageFileInfo];
|
|
689
|
+
imageEditEnd: [component: Component, saved: boolean];
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
export type EditorTsEventName = keyof EditorTsEventMap;
|
|
693
|
+
|
|
144
694
|
export interface EditorTsEditor {
|
|
145
|
-
page:
|
|
146
|
-
|
|
147
|
-
|
|
695
|
+
page: Page;
|
|
696
|
+
storage: StorageManager;
|
|
697
|
+
ai?: EditorTsAiModule;
|
|
698
|
+
vimMode: boolean;
|
|
699
|
+
versionControl?: {
|
|
700
|
+
enabled: boolean;
|
|
701
|
+
canUndo(): boolean;
|
|
702
|
+
canRedo(): boolean;
|
|
703
|
+
undo(): Promise<boolean>;
|
|
704
|
+
redo(): Promise<boolean>;
|
|
705
|
+
commit(meta?: { source?: 'user' | 'ai' | 'system'; message?: string }): Promise<void>;
|
|
706
|
+
};
|
|
707
|
+
components: CustomComponentRegistry;
|
|
708
|
+
workspace?: {
|
|
709
|
+
name: string;
|
|
710
|
+
listFiles(): string[];
|
|
711
|
+
readFile(path: string): Promise<string | null>;
|
|
712
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
713
|
+
openFile(path: string): Promise<void>;
|
|
714
|
+
};
|
|
715
|
+
on<K extends EditorTsEventName>(event: K, callback: (...args: EditorTsEventMap[K]) => void): void;
|
|
716
|
+
off<K extends EditorTsEventName>(event: K, callback: (...args: EditorTsEventMap[K]) => void): void;
|
|
148
717
|
refresh(): void;
|
|
149
718
|
save(): string;
|
|
719
|
+
/** Save page to storage */
|
|
720
|
+
saveTo(key: string): Promise<void>;
|
|
721
|
+
/** Load page from storage */
|
|
722
|
+
loadFrom(key: string): Promise<boolean>;
|
|
150
723
|
destroy(): void;
|
|
151
724
|
elements: {
|
|
152
725
|
iframe: HTMLIFrameElement;
|
package/src/utils/toolbar.ts
CHANGED
|
@@ -20,7 +20,21 @@ export const defaultToolbarActions: ToolbarAction[] = [
|
|
|
20
20
|
label: 'Edit JS',
|
|
21
21
|
icon: '📜',
|
|
22
22
|
enabled: true,
|
|
23
|
-
description: 'Edit component JavaScript
|
|
23
|
+
description: 'Edit component JavaScript',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'editCSS',
|
|
27
|
+
label: 'Edit CSS',
|
|
28
|
+
icon: '🎨',
|
|
29
|
+
enabled: true,
|
|
30
|
+
description: 'Edit page CSS',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'editJSON',
|
|
34
|
+
label: 'Edit JSON',
|
|
35
|
+
icon: '🧱',
|
|
36
|
+
enabled: true,
|
|
37
|
+
description: 'View/edit full page JSON structure',
|
|
24
38
|
},
|
|
25
39
|
{
|
|
26
40
|
id: 'duplicate',
|