@obelism/content 0.1.1 → 0.2.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/README.md +11 -0
- package/dist/bridge.d.mts +17 -2
- package/dist/bridge.d.mts.map +1 -1
- package/dist/bridge.d.ts +17 -2
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +155 -21
- package/dist/bridge.mjs +155 -21
- package/dist/site.js +4 -0
- package/dist/site.mjs +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,6 +77,17 @@ const { summary } = await bridge.prompt('Make the heading shorter')
|
|
|
77
77
|
bridge.undo()
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
On a site signed in to edit (`@obelism/content/site`), the box also has **New
|
|
81
|
+
page**: a path (`/pricing`, or under an existing page) and what the page is
|
|
82
|
+
for. The CMS writes the page like the one the editor is on and stores it as a
|
|
83
|
+
draft, and the bridge opens it at `/` + its path. When your routes put CMS
|
|
84
|
+
pages elsewhere, say where:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
getContentBridge({ pagePath: (path) => `/en/${path}` })
|
|
88
|
+
await bridge.createPage('/pricing', 'Our plans, for agencies') // or your own UI
|
|
89
|
+
```
|
|
90
|
+
|
|
80
91
|
The CMS runs prompts on Workers AI. To use your own model, set a prompt
|
|
81
92
|
endpoint under Space settings → Prompt editing and answer it with
|
|
82
93
|
`createPromptHandler` (it verifies the signed request; you call the model):
|
package/dist/bridge.d.mts
CHANGED
|
@@ -24,6 +24,12 @@ type BridgeState = {
|
|
|
24
24
|
type PromptResult = {
|
|
25
25
|
summary: string;
|
|
26
26
|
};
|
|
27
|
+
type CreatePageResult = {
|
|
28
|
+
summary: string;
|
|
29
|
+
entryId: string;
|
|
30
|
+
path: string;
|
|
31
|
+
url: string;
|
|
32
|
+
};
|
|
27
33
|
type ContentBridge = {
|
|
28
34
|
readonly isEditing: boolean;
|
|
29
35
|
readonly mode: 'editor' | 'site' | null;
|
|
@@ -34,6 +40,7 @@ type ContentBridge = {
|
|
|
34
40
|
prompt(prompt: string, options?: {
|
|
35
41
|
uid?: string | null;
|
|
36
42
|
}): Promise<PromptResult>;
|
|
43
|
+
createPage(path: string, prompt: string): Promise<CreatePageResult>;
|
|
37
44
|
undo(): void;
|
|
38
45
|
save(): Promise<void>;
|
|
39
46
|
publish(): Promise<void>;
|
|
@@ -64,6 +71,13 @@ type PromptLabels = {
|
|
|
64
71
|
signOut: string;
|
|
65
72
|
viewingDrafts: string;
|
|
66
73
|
selectHint: string;
|
|
74
|
+
newPage: string;
|
|
75
|
+
newPageCancel: string;
|
|
76
|
+
newPagePlaceholder: string;
|
|
77
|
+
path: string;
|
|
78
|
+
pathPlaceholder: string;
|
|
79
|
+
creating: string;
|
|
80
|
+
created: string;
|
|
67
81
|
};
|
|
68
82
|
declare const DEFAULT_PROMPT_LABELS: PromptLabels;
|
|
69
83
|
type PromptBoxOptions = {
|
|
@@ -73,10 +87,11 @@ type PromptBoxOptions = {
|
|
|
73
87
|
type BridgeOptions = {
|
|
74
88
|
url?: string | undefined;
|
|
75
89
|
prompt?: boolean | PromptBoxOptions | undefined;
|
|
90
|
+
pagePath?: ((path: string) => string) | undefined;
|
|
76
91
|
};
|
|
77
92
|
declare const peekContentBridge: () => ContentBridge | null;
|
|
78
|
-
declare const getContentBridge: ({ url, prompt, }?: BridgeOptions) => ContentBridge;
|
|
93
|
+
declare const getContentBridge: ({ url, prompt, pagePath, }?: BridgeOptions) => ContentBridge;
|
|
79
94
|
|
|
80
95
|
export { DEFAULT_PROMPT_LABELS, INITIAL_STATE, getContentBridge, peekContentBridge };
|
|
81
|
-
export type { BridgeEvent, BridgeEvents, BridgeListener, BridgeOptions, BridgeState, ContentBridge, PromptBoxOptions, PromptLabels, PromptResult };
|
|
96
|
+
export type { BridgeEvent, BridgeEvents, BridgeListener, BridgeOptions, BridgeState, ContentBridge, CreatePageResult, PromptBoxOptions, PromptLabels, PromptResult };
|
|
82
97
|
//# sourceMappingURL=bridge.d.mts.map
|
package/dist/bridge.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.mts","sources":["../src/bridge/createBridge.ts","../src/bridge/promptBox.ts","../src/bridge.ts"],"mappings":";;AAIA,KAAK,YAAY;AACjB;AACA,eAAe,KAAK;AACpB;AACA;AACA,WAAW,MAAM;AACjB,eAAe,MAAM;AACrB,YAAY,WAAW;AACvB;AACA,KAAK,WAAW,SAAS,YAAY;AACrC,KAAK,cAAc,WAAW,WAAW,cAAc,YAAY;AACnE,KAAK,WAAW;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,YAAY;AACjB;AACA;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA;AACA,iBAAiB,WAAW,sBAAsB,cAAc;AAChE,gBAAgB,WAAW;AAC3B;AACA;AACA,QAAQ,OAAO,CAAC,YAAY;AAC5B;AACA,YAAY,OAAO;AACnB,eAAe,OAAO;AACtB;AACA;AACA;AACA;AACA;AAEA,cAAc,aAAa,EAAE,WAAW;;
|
|
1
|
+
{"version":3,"file":"bridge.d.mts","sources":["../src/bridge/createBridge.ts","../src/bridge/promptBox.ts","../src/bridge.ts"],"mappings":";;AAIA,KAAK,YAAY;AACjB;AACA,eAAe,KAAK;AACpB;AACA;AACA,WAAW,MAAM;AACjB,eAAe,MAAM;AACrB,YAAY,WAAW;AACvB;AACA,KAAK,WAAW,SAAS,YAAY;AACrC,KAAK,cAAc,WAAW,WAAW,cAAc,YAAY;AACnE,KAAK,WAAW;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,YAAY;AACjB;AACA;AACA,KAAK,gBAAgB;AACrB;AACA;AACA;AACA;AACA;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA;AACA,iBAAiB,WAAW,sBAAsB,cAAc;AAChE,gBAAgB,WAAW;AAC3B;AACA;AACA,QAAQ,OAAO,CAAC,YAAY;AAC5B,8CAA8C,OAAO,CAAC,gBAAgB;AACtE;AACA,YAAY,OAAO;AACnB,eAAe,OAAO;AACtB;AACA;AACA;AACA;AACA;AAEA,cAAc,aAAa,EAAE,WAAW;;ACrDxC,KAAK,YAAY;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,qBAAqB,EAAE,YAAY;AACjD,KAAK,gBAAgB;AACrB,aAAa,OAAO,CAAC,YAAY;AACjC;;AC1BA,KAAK,aAAa;AAClB;AACA,uBAAuB,gBAAgB;AACvC;AACA;AACA,cAAc,iBAAiB,QAAQ,aAAa;AACpD,cAAc,gBAAgB,gCAAgC,aAAa,KAAK,aAAa","names":[]}
|
package/dist/bridge.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ type BridgeState = {
|
|
|
24
24
|
type PromptResult = {
|
|
25
25
|
summary: string;
|
|
26
26
|
};
|
|
27
|
+
type CreatePageResult = {
|
|
28
|
+
summary: string;
|
|
29
|
+
entryId: string;
|
|
30
|
+
path: string;
|
|
31
|
+
url: string;
|
|
32
|
+
};
|
|
27
33
|
type ContentBridge = {
|
|
28
34
|
readonly isEditing: boolean;
|
|
29
35
|
readonly mode: 'editor' | 'site' | null;
|
|
@@ -34,6 +40,7 @@ type ContentBridge = {
|
|
|
34
40
|
prompt(prompt: string, options?: {
|
|
35
41
|
uid?: string | null;
|
|
36
42
|
}): Promise<PromptResult>;
|
|
43
|
+
createPage(path: string, prompt: string): Promise<CreatePageResult>;
|
|
37
44
|
undo(): void;
|
|
38
45
|
save(): Promise<void>;
|
|
39
46
|
publish(): Promise<void>;
|
|
@@ -64,6 +71,13 @@ type PromptLabels = {
|
|
|
64
71
|
signOut: string;
|
|
65
72
|
viewingDrafts: string;
|
|
66
73
|
selectHint: string;
|
|
74
|
+
newPage: string;
|
|
75
|
+
newPageCancel: string;
|
|
76
|
+
newPagePlaceholder: string;
|
|
77
|
+
path: string;
|
|
78
|
+
pathPlaceholder: string;
|
|
79
|
+
creating: string;
|
|
80
|
+
created: string;
|
|
67
81
|
};
|
|
68
82
|
declare const DEFAULT_PROMPT_LABELS: PromptLabels;
|
|
69
83
|
type PromptBoxOptions = {
|
|
@@ -73,10 +87,11 @@ type PromptBoxOptions = {
|
|
|
73
87
|
type BridgeOptions = {
|
|
74
88
|
url?: string | undefined;
|
|
75
89
|
prompt?: boolean | PromptBoxOptions | undefined;
|
|
90
|
+
pagePath?: ((path: string) => string) | undefined;
|
|
76
91
|
};
|
|
77
92
|
declare const peekContentBridge: () => ContentBridge | null;
|
|
78
|
-
declare const getContentBridge: ({ url, prompt, }?: BridgeOptions) => ContentBridge;
|
|
93
|
+
declare const getContentBridge: ({ url, prompt, pagePath, }?: BridgeOptions) => ContentBridge;
|
|
79
94
|
|
|
80
95
|
export { DEFAULT_PROMPT_LABELS, INITIAL_STATE, getContentBridge, peekContentBridge };
|
|
81
|
-
export type { BridgeEvent, BridgeEvents, BridgeListener, BridgeOptions, BridgeState, ContentBridge, PromptBoxOptions, PromptLabels, PromptResult };
|
|
96
|
+
export type { BridgeEvent, BridgeEvents, BridgeListener, BridgeOptions, BridgeState, ContentBridge, CreatePageResult, PromptBoxOptions, PromptLabels, PromptResult };
|
|
82
97
|
//# sourceMappingURL=bridge.d.ts.map
|
package/dist/bridge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.ts","sources":["../src/bridge/createBridge.ts","../src/bridge/promptBox.ts","../src/bridge.ts"],"mappings":";;AAIA,KAAK,YAAY;AACjB;AACA,eAAe,KAAK;AACpB;AACA;AACA,WAAW,MAAM;AACjB,eAAe,MAAM;AACrB,YAAY,WAAW;AACvB;AACA,KAAK,WAAW,SAAS,YAAY;AACrC,KAAK,cAAc,WAAW,WAAW,cAAc,YAAY;AACnE,KAAK,WAAW;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,YAAY;AACjB;AACA;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA;AACA,iBAAiB,WAAW,sBAAsB,cAAc;AAChE,gBAAgB,WAAW;AAC3B;AACA;AACA,QAAQ,OAAO,CAAC,YAAY;AAC5B;AACA,YAAY,OAAO;AACnB,eAAe,OAAO;AACtB;AACA;AACA;AACA;AACA;AAEA,cAAc,aAAa,EAAE,WAAW;;
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sources":["../src/bridge/createBridge.ts","../src/bridge/promptBox.ts","../src/bridge.ts"],"mappings":";;AAIA,KAAK,YAAY;AACjB;AACA,eAAe,KAAK;AACpB;AACA;AACA,WAAW,MAAM;AACjB,eAAe,MAAM;AACrB,YAAY,WAAW;AACvB;AACA,KAAK,WAAW,SAAS,YAAY;AACrC,KAAK,cAAc,WAAW,WAAW,cAAc,YAAY;AACnE,KAAK,WAAW;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,YAAY;AACjB;AACA;AACA,KAAK,gBAAgB;AACrB;AACA;AACA;AACA;AACA;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA;AACA,iBAAiB,WAAW,sBAAsB,cAAc;AAChE,gBAAgB,WAAW;AAC3B;AACA;AACA,QAAQ,OAAO,CAAC,YAAY;AAC5B,8CAA8C,OAAO,CAAC,gBAAgB;AACtE;AACA,YAAY,OAAO;AACnB,eAAe,OAAO;AACtB;AACA;AACA;AACA;AACA;AAEA,cAAc,aAAa,EAAE,WAAW;;ACrDxC,KAAK,YAAY;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,qBAAqB,EAAE,YAAY;AACjD,KAAK,gBAAgB;AACrB,aAAa,OAAO,CAAC,YAAY;AACjC;;AC1BA,KAAK,aAAa;AAClB;AACA,uBAAuB,gBAAgB;AACvC;AACA;AACA,cAAc,iBAAiB,QAAQ,aAAa;AACpD,cAAc,gBAAgB,gCAAgC,aAAa,KAAK,aAAa","names":[]}
|
package/dist/bridge.js
CHANGED
|
@@ -64,6 +64,7 @@ const onlyOnSite = ()=>Promise.reject(new Error('Save and publish in the visual
|
|
|
64
64
|
// What every bridge without a feature does for it.
|
|
65
65
|
const INERT_METHODS = {
|
|
66
66
|
prompt: inertPrompt,
|
|
67
|
+
createPage: ()=>Promise.reject(new Error('New pages are made on the site itself')),
|
|
67
68
|
undo: ()=>{},
|
|
68
69
|
save: onlyOnSite,
|
|
69
70
|
publish: onlyOnSite,
|
|
@@ -306,11 +307,19 @@ const DEFAULT_PROMPT_LABELS = {
|
|
|
306
307
|
unsaved: 'Unsaved changes',
|
|
307
308
|
signOut: 'Exit editing',
|
|
308
309
|
viewingDrafts: 'Viewing drafts',
|
|
309
|
-
selectHint: 'Click a block to change only that block'
|
|
310
|
+
selectHint: 'Click a block to change only that block',
|
|
311
|
+
newPage: 'New page',
|
|
312
|
+
newPageCancel: 'Edit this page instead',
|
|
313
|
+
newPagePlaceholder: 'Describe the new page, e.g. “a pricing page for agencies”',
|
|
314
|
+
path: 'Path',
|
|
315
|
+
pathPlaceholder: '/pricing',
|
|
316
|
+
creating: 'Writing the page…',
|
|
317
|
+
created: 'Opening the new page…'
|
|
310
318
|
};
|
|
311
319
|
// Icons (Lucide), drawn in the text colour.
|
|
312
320
|
const ICONS = {
|
|
313
321
|
page: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>',
|
|
322
|
+
newPage: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M9 15h6"/><path d="M12 18v-6"/>',
|
|
314
323
|
block: '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M7 8h10"/><path d="M7 12h6"/>',
|
|
315
324
|
undo: '<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/>',
|
|
316
325
|
close: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
|
|
@@ -415,6 +424,20 @@ textarea {
|
|
|
415
424
|
font-size: 16px; line-height: 1.6; color: rgb(var(--theme-foreground-rgb));
|
|
416
425
|
}
|
|
417
426
|
textarea::placeholder { color: rgb(var(--theme-foreground-muted-rgb)); }
|
|
427
|
+
/* New page: where it goes, above what it is for. */
|
|
428
|
+
.path {
|
|
429
|
+
display: flex; align-items: center; gap: 10px; padding: 16px 22px 0;
|
|
430
|
+
color: rgb(var(--theme-foreground-muted-rgb));
|
|
431
|
+
}
|
|
432
|
+
.path input {
|
|
433
|
+
flex: 1; min-width: 0; height: 34px; padding: 0 14px; outline: 0;
|
|
434
|
+
border: 1px solid rgb(var(--theme-border-rgb) / .25); border-radius: var(--border-radius-pill);
|
|
435
|
+
background: rgb(var(--theme-background-actions-rgb) / .45); color: rgb(var(--theme-foreground-rgb));
|
|
436
|
+
transition: border-color .25s var(--ease-roll);
|
|
437
|
+
}
|
|
438
|
+
.path input:focus-visible { border-color: rgb(var(--theme-accent-rgb)); }
|
|
439
|
+
.path input::placeholder { color: rgb(var(--theme-foreground-muted-rgb)); }
|
|
440
|
+
.path input:disabled { opacity: .5; }
|
|
418
441
|
textarea:disabled { opacity: .5; }
|
|
419
442
|
.toolbar { display: flex; align-items: center; gap: 6px; padding: 8px 12px 12px; }
|
|
420
443
|
.spacer { flex: 1; }
|
|
@@ -492,12 +515,13 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
492
515
|
mode: 'open'
|
|
493
516
|
});
|
|
494
517
|
const site = bridge.mode === 'site';
|
|
518
|
+
const pillText = element(doc, 'span', {
|
|
519
|
+
textContent: labels.open
|
|
520
|
+
});
|
|
495
521
|
const pill = element(doc, 'button', {
|
|
496
522
|
className: 'pill',
|
|
497
523
|
type: 'button'
|
|
498
|
-
},
|
|
499
|
-
textContent: labels.open
|
|
500
|
-
}), // The Obelism mark.
|
|
524
|
+
}, pillText, // The Obelism mark.
|
|
501
525
|
element(doc, 'span', {
|
|
502
526
|
className: 'mark',
|
|
503
527
|
textContent: '⁜'
|
|
@@ -540,6 +564,34 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
540
564
|
className: 'action chip',
|
|
541
565
|
type: 'button'
|
|
542
566
|
}, icon(doc, 'undo'), labels.undo);
|
|
567
|
+
// New page (on the site, for people who may add pages): the scope becomes
|
|
568
|
+
// the new page, with a way back, and a path field shows above the prompt.
|
|
569
|
+
const newPage = element(doc, 'button', {
|
|
570
|
+
className: 'action chip',
|
|
571
|
+
type: 'button'
|
|
572
|
+
}, icon(doc, 'newPage'), labels.newPage);
|
|
573
|
+
const cancelNew = element(doc, 'button', {
|
|
574
|
+
type: 'button',
|
|
575
|
+
title: labels.newPageCancel
|
|
576
|
+
});
|
|
577
|
+
cancelNew.setAttribute('aria-label', labels.newPageCancel);
|
|
578
|
+
cancelNew.appendChild(icon(doc, 'close'));
|
|
579
|
+
const newScope = element(doc, 'span', {
|
|
580
|
+
className: 'chip scope'
|
|
581
|
+
}, icon(doc, 'newPage'), element(doc, 'span', {
|
|
582
|
+
textContent: labels.newPage
|
|
583
|
+
}), cancelNew);
|
|
584
|
+
const pathInput = element(doc, 'input', {
|
|
585
|
+
type: 'text',
|
|
586
|
+
placeholder: labels.pathPlaceholder,
|
|
587
|
+
autocomplete: 'off',
|
|
588
|
+
spellcheck: false
|
|
589
|
+
});
|
|
590
|
+
const pathRow = element(doc, 'label', {
|
|
591
|
+
className: 'path'
|
|
592
|
+
}, element(doc, 'span', {
|
|
593
|
+
textContent: labels.path
|
|
594
|
+
}), pathInput);
|
|
543
595
|
const close = element(doc, 'button', {
|
|
544
596
|
className: 'close icon-button',
|
|
545
597
|
type: 'button',
|
|
@@ -583,9 +635,9 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
583
635
|
}), signOut, save, publish);
|
|
584
636
|
const panel = element(doc, 'form', {
|
|
585
637
|
className: 'panel'
|
|
586
|
-
}, status, input, element(doc, 'div', {
|
|
638
|
+
}, status, pathRow, input, element(doc, 'div', {
|
|
587
639
|
className: 'toolbar'
|
|
588
|
-
}, scope, undo, element(doc, 'span', {
|
|
640
|
+
}, scope, newScope, undo, newPage, element(doc, 'span', {
|
|
589
641
|
className: 'spacer'
|
|
590
642
|
}), close, element(doc, 'span', {
|
|
591
643
|
className: 'divider'
|
|
@@ -599,6 +651,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
599
651
|
textContent: CSS
|
|
600
652
|
}), root);
|
|
601
653
|
let open = false;
|
|
654
|
+
let creating = false;
|
|
602
655
|
let message = null;
|
|
603
656
|
const render = (state)=>{
|
|
604
657
|
const available = state.features.includes('prompt');
|
|
@@ -608,24 +661,40 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
608
661
|
pill.hidden = open || !available;
|
|
609
662
|
viewing.hidden = available || !site;
|
|
610
663
|
panel.hidden = !open || !available;
|
|
664
|
+
const canCreate = site && state.features.includes('create');
|
|
665
|
+
// A page of the site that isn't an entry (no `editable(entry)`, like a
|
|
666
|
+
// hand-written /about) has nothing to edit: only New page.
|
|
667
|
+
const pageless = site && bridge.entryId === null;
|
|
668
|
+
const onlyNew = pageless && canCreate;
|
|
669
|
+
if (!canCreate) creating = false;
|
|
670
|
+
if (onlyNew) creating = true;
|
|
671
|
+
pillText.textContent = onlyNew ? labels.newPage : labels.open;
|
|
672
|
+
cancelNew.hidden = onlyNew;
|
|
673
|
+
scope.hidden = creating;
|
|
674
|
+
newScope.hidden = !creating;
|
|
675
|
+
newPage.hidden = creating || !canCreate;
|
|
676
|
+
newPage.disabled = busy;
|
|
677
|
+
pathRow.hidden = !creating;
|
|
678
|
+
pathInput.disabled = busy;
|
|
679
|
+
input.placeholder = creating ? labels.newPagePlaceholder : labels.placeholder;
|
|
611
680
|
scopeText.textContent = state.selectedUid ? labels.scopeBlock : labels.scopeEntry;
|
|
612
681
|
clearBlock.hidden = !state.selectedUid;
|
|
613
682
|
pageIcon.hidden = !!state.selectedUid;
|
|
614
683
|
blockIcon.hidden = !state.selectedUid;
|
|
615
|
-
undo.hidden = !state.canUndo;
|
|
684
|
+
undo.hidden = creating || !state.canUndo;
|
|
616
685
|
undo.disabled = busy;
|
|
617
|
-
send.disabled = busy || input.value.trim() === '';
|
|
686
|
+
send.disabled = busy || input.value.trim() === '' || creating && pathInput.value.trim() === '';
|
|
618
687
|
input.disabled = busy;
|
|
619
|
-
save.hidden = !state.features.includes('save');
|
|
688
|
+
save.hidden = !state.features.includes('save') || pageless;
|
|
620
689
|
save.disabled = busy || !state.unsaved;
|
|
621
690
|
save.textContent = state.saving ? labels.saving : labels.save;
|
|
622
|
-
publish.hidden = !state.features.includes('publish');
|
|
691
|
+
publish.hidden = !state.features.includes('publish') || pageless;
|
|
623
692
|
publish.disabled = busy;
|
|
624
693
|
publish.textContent = state.publishing ? labels.publishing : labels.publish;
|
|
625
694
|
signOut.hidden = !site;
|
|
626
695
|
footer.hidden = !site;
|
|
627
696
|
unsaved.hidden = !state.unsaved;
|
|
628
|
-
const text = state.prompting ? labels.working : message?.text ?? (site && !state.selectedUid ? labels.selectHint : '');
|
|
697
|
+
const text = state.prompting ? creating ? labels.creating : labels.working : message?.text ?? (site && !creating && !state.selectedUid ? labels.selectHint : '');
|
|
629
698
|
status.textContent = text;
|
|
630
699
|
status.hidden = !text;
|
|
631
700
|
status.className = `status${state.prompting ? ' working' : ''}${!busy && message?.error ? ' error' : ''}`;
|
|
@@ -640,11 +709,22 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
640
709
|
if (!prompt || bridge.getState().prompting) return;
|
|
641
710
|
message = null;
|
|
642
711
|
try {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
712
|
+
if (creating) {
|
|
713
|
+
const path = pathInput.value.trim();
|
|
714
|
+
if (!path) return pathInput.focus();
|
|
715
|
+
// The bridge opens the new page; this shows until it does.
|
|
716
|
+
await bridge.createPage(path, prompt);
|
|
717
|
+
message = {
|
|
718
|
+
text: labels.created,
|
|
719
|
+
error: false
|
|
720
|
+
};
|
|
721
|
+
} else {
|
|
722
|
+
const { summary } = await bridge.prompt(prompt);
|
|
723
|
+
message = {
|
|
724
|
+
text: summary,
|
|
725
|
+
error: false
|
|
726
|
+
};
|
|
727
|
+
}
|
|
648
728
|
input.value = '';
|
|
649
729
|
grow();
|
|
650
730
|
} catch (error) {
|
|
@@ -658,8 +738,16 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
658
738
|
};
|
|
659
739
|
const setOpen = (next)=>{
|
|
660
740
|
open = next;
|
|
661
|
-
if (site) bridge.setSelecting(open);
|
|
662
741
|
refresh();
|
|
742
|
+
if (site) bridge.setSelecting(open && !creating);
|
|
743
|
+
};
|
|
744
|
+
const setCreating = (next)=>{
|
|
745
|
+
creating = next;
|
|
746
|
+
message = null;
|
|
747
|
+
bridge.setSelecting(open && !creating);
|
|
748
|
+
if (creating) bridge.select(null);
|
|
749
|
+
refresh();
|
|
750
|
+
(creating ? pathInput : input).focus();
|
|
663
751
|
};
|
|
664
752
|
const store = async (action)=>{
|
|
665
753
|
message = null;
|
|
@@ -679,7 +767,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
679
767
|
};
|
|
680
768
|
pill.addEventListener('click', ()=>{
|
|
681
769
|
setOpen(true);
|
|
682
|
-
input.focus();
|
|
770
|
+
(creating ? pathInput : input).focus();
|
|
683
771
|
});
|
|
684
772
|
close.addEventListener('click', ()=>setOpen(false));
|
|
685
773
|
save.addEventListener('click', ()=>void store('save'));
|
|
@@ -687,6 +775,17 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
687
775
|
signOut.addEventListener('click', ()=>bridge.signOut());
|
|
688
776
|
pillSignOut.addEventListener('click', ()=>bridge.signOut());
|
|
689
777
|
clearBlock.addEventListener('click', ()=>bridge.select(null));
|
|
778
|
+
newPage.addEventListener('click', ()=>setCreating(true));
|
|
779
|
+
cancelNew.addEventListener('click', ()=>setCreating(false));
|
|
780
|
+
pathInput.addEventListener('input', refresh);
|
|
781
|
+
pathInput.addEventListener('keydown', (event)=>{
|
|
782
|
+
if (event.key === 'Enter' && !event.isComposing) {
|
|
783
|
+
event.preventDefault();
|
|
784
|
+
input.focus();
|
|
785
|
+
} else if (event.key === 'Escape') {
|
|
786
|
+
setOpen(false);
|
|
787
|
+
}
|
|
788
|
+
});
|
|
690
789
|
undo.addEventListener('click', ()=>{
|
|
691
790
|
message = null;
|
|
692
791
|
bridge.undo();
|
|
@@ -740,7 +839,7 @@ const readSiteEditing = (doc)=>{
|
|
|
740
839
|
return null;
|
|
741
840
|
};
|
|
742
841
|
const same = (a, b)=>JSON.stringify(a) === JSON.stringify(b);
|
|
743
|
-
const createSiteBridge = (win, basePath)=>{
|
|
842
|
+
const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}` } = {})=>{
|
|
744
843
|
const doc = win.document;
|
|
745
844
|
const emitter = createEmitter();
|
|
746
845
|
const cleanup = [];
|
|
@@ -903,6 +1002,39 @@ const createSiteBridge = (win, basePath)=>{
|
|
|
903
1002
|
});
|
|
904
1003
|
}
|
|
905
1004
|
},
|
|
1005
|
+
async createPage (path, prompt) {
|
|
1006
|
+
const text = prompt.trim();
|
|
1007
|
+
if (!text) throw new Error('Say what the page is for');
|
|
1008
|
+
if (!path.trim()) throw new Error('Say where the page goes, like /pricing');
|
|
1009
|
+
if (!has('create')) throw new Error('You may not add pages here');
|
|
1010
|
+
// Opening the new page would lose them.
|
|
1011
|
+
if (state.unsaved) {
|
|
1012
|
+
throw new Error('Save or undo your changes before adding a page');
|
|
1013
|
+
}
|
|
1014
|
+
setState({
|
|
1015
|
+
prompting: true
|
|
1016
|
+
});
|
|
1017
|
+
try {
|
|
1018
|
+
const created = await api('entries', {
|
|
1019
|
+
path,
|
|
1020
|
+
prompt: text,
|
|
1021
|
+
locale: locale(),
|
|
1022
|
+
from: pageEntryId()
|
|
1023
|
+
});
|
|
1024
|
+
const url = pagePath(created.path);
|
|
1025
|
+
win.location.assign(url);
|
|
1026
|
+
return {
|
|
1027
|
+
summary: created.summary,
|
|
1028
|
+
entryId: created.entryId,
|
|
1029
|
+
path: created.path,
|
|
1030
|
+
url
|
|
1031
|
+
};
|
|
1032
|
+
} finally{
|
|
1033
|
+
setState({
|
|
1034
|
+
prompting: false
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
},
|
|
906
1038
|
undo () {
|
|
907
1039
|
const last = history.pop();
|
|
908
1040
|
const entry = last && working.get(last.entryId);
|
|
@@ -1029,7 +1161,7 @@ const peekContentBridge = ()=>typeof window === 'undefined' ? null : window[KEY]
|
|
|
1029
1161
|
// if (bridge.isEditing) bridge.on('update', ({ entry }) => render(entry))
|
|
1030
1162
|
//
|
|
1031
1163
|
// The first call's options win: there is one bridge per page.
|
|
1032
|
-
const getContentBridge = ({ url, prompt = true } = {})=>{
|
|
1164
|
+
const getContentBridge = ({ url, prompt = true, pagePath } = {})=>{
|
|
1033
1165
|
if (typeof window === 'undefined') return SERVER_BRIDGE;
|
|
1034
1166
|
const win = window;
|
|
1035
1167
|
if (win[KEY]) return win[KEY];
|
|
@@ -1041,7 +1173,9 @@ const getContentBridge = ({ url, prompt = true } = {})=>{
|
|
|
1041
1173
|
// Not in the visual editor, but signed in to edit through the site's
|
|
1042
1174
|
// own `/cms/login` (@obelism/content/site).
|
|
1043
1175
|
const sitePath = bridge.isEditing || win.parent !== win ? null : readSiteEditing(win.document);
|
|
1044
|
-
if (sitePath) bridge = createSiteBridge(win, sitePath
|
|
1176
|
+
if (sitePath) bridge = createSiteBridge(win, sitePath, {
|
|
1177
|
+
pagePath
|
|
1178
|
+
});
|
|
1045
1179
|
if (bridge.isEditing && prompt) {
|
|
1046
1180
|
const unmount = mountPromptBox(win.document, bridge, prompt === true ? {} : prompt);
|
|
1047
1181
|
const destroy = bridge.destroy;
|
package/dist/bridge.mjs
CHANGED
|
@@ -62,6 +62,7 @@ const onlyOnSite = ()=>Promise.reject(new Error('Save and publish in the visual
|
|
|
62
62
|
// What every bridge without a feature does for it.
|
|
63
63
|
const INERT_METHODS = {
|
|
64
64
|
prompt: inertPrompt,
|
|
65
|
+
createPage: ()=>Promise.reject(new Error('New pages are made on the site itself')),
|
|
65
66
|
undo: ()=>{},
|
|
66
67
|
save: onlyOnSite,
|
|
67
68
|
publish: onlyOnSite,
|
|
@@ -304,11 +305,19 @@ const DEFAULT_PROMPT_LABELS = {
|
|
|
304
305
|
unsaved: 'Unsaved changes',
|
|
305
306
|
signOut: 'Exit editing',
|
|
306
307
|
viewingDrafts: 'Viewing drafts',
|
|
307
|
-
selectHint: 'Click a block to change only that block'
|
|
308
|
+
selectHint: 'Click a block to change only that block',
|
|
309
|
+
newPage: 'New page',
|
|
310
|
+
newPageCancel: 'Edit this page instead',
|
|
311
|
+
newPagePlaceholder: 'Describe the new page, e.g. “a pricing page for agencies”',
|
|
312
|
+
path: 'Path',
|
|
313
|
+
pathPlaceholder: '/pricing',
|
|
314
|
+
creating: 'Writing the page…',
|
|
315
|
+
created: 'Opening the new page…'
|
|
308
316
|
};
|
|
309
317
|
// Icons (Lucide), drawn in the text colour.
|
|
310
318
|
const ICONS = {
|
|
311
319
|
page: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>',
|
|
320
|
+
newPage: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M9 15h6"/><path d="M12 18v-6"/>',
|
|
312
321
|
block: '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M7 8h10"/><path d="M7 12h6"/>',
|
|
313
322
|
undo: '<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/>',
|
|
314
323
|
close: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
|
|
@@ -413,6 +422,20 @@ textarea {
|
|
|
413
422
|
font-size: 16px; line-height: 1.6; color: rgb(var(--theme-foreground-rgb));
|
|
414
423
|
}
|
|
415
424
|
textarea::placeholder { color: rgb(var(--theme-foreground-muted-rgb)); }
|
|
425
|
+
/* New page: where it goes, above what it is for. */
|
|
426
|
+
.path {
|
|
427
|
+
display: flex; align-items: center; gap: 10px; padding: 16px 22px 0;
|
|
428
|
+
color: rgb(var(--theme-foreground-muted-rgb));
|
|
429
|
+
}
|
|
430
|
+
.path input {
|
|
431
|
+
flex: 1; min-width: 0; height: 34px; padding: 0 14px; outline: 0;
|
|
432
|
+
border: 1px solid rgb(var(--theme-border-rgb) / .25); border-radius: var(--border-radius-pill);
|
|
433
|
+
background: rgb(var(--theme-background-actions-rgb) / .45); color: rgb(var(--theme-foreground-rgb));
|
|
434
|
+
transition: border-color .25s var(--ease-roll);
|
|
435
|
+
}
|
|
436
|
+
.path input:focus-visible { border-color: rgb(var(--theme-accent-rgb)); }
|
|
437
|
+
.path input::placeholder { color: rgb(var(--theme-foreground-muted-rgb)); }
|
|
438
|
+
.path input:disabled { opacity: .5; }
|
|
416
439
|
textarea:disabled { opacity: .5; }
|
|
417
440
|
.toolbar { display: flex; align-items: center; gap: 6px; padding: 8px 12px 12px; }
|
|
418
441
|
.spacer { flex: 1; }
|
|
@@ -490,12 +513,13 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
490
513
|
mode: 'open'
|
|
491
514
|
});
|
|
492
515
|
const site = bridge.mode === 'site';
|
|
516
|
+
const pillText = element(doc, 'span', {
|
|
517
|
+
textContent: labels.open
|
|
518
|
+
});
|
|
493
519
|
const pill = element(doc, 'button', {
|
|
494
520
|
className: 'pill',
|
|
495
521
|
type: 'button'
|
|
496
|
-
},
|
|
497
|
-
textContent: labels.open
|
|
498
|
-
}), // The Obelism mark.
|
|
522
|
+
}, pillText, // The Obelism mark.
|
|
499
523
|
element(doc, 'span', {
|
|
500
524
|
className: 'mark',
|
|
501
525
|
textContent: '⁜'
|
|
@@ -538,6 +562,34 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
538
562
|
className: 'action chip',
|
|
539
563
|
type: 'button'
|
|
540
564
|
}, icon(doc, 'undo'), labels.undo);
|
|
565
|
+
// New page (on the site, for people who may add pages): the scope becomes
|
|
566
|
+
// the new page, with a way back, and a path field shows above the prompt.
|
|
567
|
+
const newPage = element(doc, 'button', {
|
|
568
|
+
className: 'action chip',
|
|
569
|
+
type: 'button'
|
|
570
|
+
}, icon(doc, 'newPage'), labels.newPage);
|
|
571
|
+
const cancelNew = element(doc, 'button', {
|
|
572
|
+
type: 'button',
|
|
573
|
+
title: labels.newPageCancel
|
|
574
|
+
});
|
|
575
|
+
cancelNew.setAttribute('aria-label', labels.newPageCancel);
|
|
576
|
+
cancelNew.appendChild(icon(doc, 'close'));
|
|
577
|
+
const newScope = element(doc, 'span', {
|
|
578
|
+
className: 'chip scope'
|
|
579
|
+
}, icon(doc, 'newPage'), element(doc, 'span', {
|
|
580
|
+
textContent: labels.newPage
|
|
581
|
+
}), cancelNew);
|
|
582
|
+
const pathInput = element(doc, 'input', {
|
|
583
|
+
type: 'text',
|
|
584
|
+
placeholder: labels.pathPlaceholder,
|
|
585
|
+
autocomplete: 'off',
|
|
586
|
+
spellcheck: false
|
|
587
|
+
});
|
|
588
|
+
const pathRow = element(doc, 'label', {
|
|
589
|
+
className: 'path'
|
|
590
|
+
}, element(doc, 'span', {
|
|
591
|
+
textContent: labels.path
|
|
592
|
+
}), pathInput);
|
|
541
593
|
const close = element(doc, 'button', {
|
|
542
594
|
className: 'close icon-button',
|
|
543
595
|
type: 'button',
|
|
@@ -581,9 +633,9 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
581
633
|
}), signOut, save, publish);
|
|
582
634
|
const panel = element(doc, 'form', {
|
|
583
635
|
className: 'panel'
|
|
584
|
-
}, status, input, element(doc, 'div', {
|
|
636
|
+
}, status, pathRow, input, element(doc, 'div', {
|
|
585
637
|
className: 'toolbar'
|
|
586
|
-
}, scope, undo, element(doc, 'span', {
|
|
638
|
+
}, scope, newScope, undo, newPage, element(doc, 'span', {
|
|
587
639
|
className: 'spacer'
|
|
588
640
|
}), close, element(doc, 'span', {
|
|
589
641
|
className: 'divider'
|
|
@@ -597,6 +649,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
597
649
|
textContent: CSS
|
|
598
650
|
}), root);
|
|
599
651
|
let open = false;
|
|
652
|
+
let creating = false;
|
|
600
653
|
let message = null;
|
|
601
654
|
const render = (state)=>{
|
|
602
655
|
const available = state.features.includes('prompt');
|
|
@@ -606,24 +659,40 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
606
659
|
pill.hidden = open || !available;
|
|
607
660
|
viewing.hidden = available || !site;
|
|
608
661
|
panel.hidden = !open || !available;
|
|
662
|
+
const canCreate = site && state.features.includes('create');
|
|
663
|
+
// A page of the site that isn't an entry (no `editable(entry)`, like a
|
|
664
|
+
// hand-written /about) has nothing to edit: only New page.
|
|
665
|
+
const pageless = site && bridge.entryId === null;
|
|
666
|
+
const onlyNew = pageless && canCreate;
|
|
667
|
+
if (!canCreate) creating = false;
|
|
668
|
+
if (onlyNew) creating = true;
|
|
669
|
+
pillText.textContent = onlyNew ? labels.newPage : labels.open;
|
|
670
|
+
cancelNew.hidden = onlyNew;
|
|
671
|
+
scope.hidden = creating;
|
|
672
|
+
newScope.hidden = !creating;
|
|
673
|
+
newPage.hidden = creating || !canCreate;
|
|
674
|
+
newPage.disabled = busy;
|
|
675
|
+
pathRow.hidden = !creating;
|
|
676
|
+
pathInput.disabled = busy;
|
|
677
|
+
input.placeholder = creating ? labels.newPagePlaceholder : labels.placeholder;
|
|
609
678
|
scopeText.textContent = state.selectedUid ? labels.scopeBlock : labels.scopeEntry;
|
|
610
679
|
clearBlock.hidden = !state.selectedUid;
|
|
611
680
|
pageIcon.hidden = !!state.selectedUid;
|
|
612
681
|
blockIcon.hidden = !state.selectedUid;
|
|
613
|
-
undo.hidden = !state.canUndo;
|
|
682
|
+
undo.hidden = creating || !state.canUndo;
|
|
614
683
|
undo.disabled = busy;
|
|
615
|
-
send.disabled = busy || input.value.trim() === '';
|
|
684
|
+
send.disabled = busy || input.value.trim() === '' || creating && pathInput.value.trim() === '';
|
|
616
685
|
input.disabled = busy;
|
|
617
|
-
save.hidden = !state.features.includes('save');
|
|
686
|
+
save.hidden = !state.features.includes('save') || pageless;
|
|
618
687
|
save.disabled = busy || !state.unsaved;
|
|
619
688
|
save.textContent = state.saving ? labels.saving : labels.save;
|
|
620
|
-
publish.hidden = !state.features.includes('publish');
|
|
689
|
+
publish.hidden = !state.features.includes('publish') || pageless;
|
|
621
690
|
publish.disabled = busy;
|
|
622
691
|
publish.textContent = state.publishing ? labels.publishing : labels.publish;
|
|
623
692
|
signOut.hidden = !site;
|
|
624
693
|
footer.hidden = !site;
|
|
625
694
|
unsaved.hidden = !state.unsaved;
|
|
626
|
-
const text = state.prompting ? labels.working : message?.text ?? (site && !state.selectedUid ? labels.selectHint : '');
|
|
695
|
+
const text = state.prompting ? creating ? labels.creating : labels.working : message?.text ?? (site && !creating && !state.selectedUid ? labels.selectHint : '');
|
|
627
696
|
status.textContent = text;
|
|
628
697
|
status.hidden = !text;
|
|
629
698
|
status.className = `status${state.prompting ? ' working' : ''}${!busy && message?.error ? ' error' : ''}`;
|
|
@@ -638,11 +707,22 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
638
707
|
if (!prompt || bridge.getState().prompting) return;
|
|
639
708
|
message = null;
|
|
640
709
|
try {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
710
|
+
if (creating) {
|
|
711
|
+
const path = pathInput.value.trim();
|
|
712
|
+
if (!path) return pathInput.focus();
|
|
713
|
+
// The bridge opens the new page; this shows until it does.
|
|
714
|
+
await bridge.createPage(path, prompt);
|
|
715
|
+
message = {
|
|
716
|
+
text: labels.created,
|
|
717
|
+
error: false
|
|
718
|
+
};
|
|
719
|
+
} else {
|
|
720
|
+
const { summary } = await bridge.prompt(prompt);
|
|
721
|
+
message = {
|
|
722
|
+
text: summary,
|
|
723
|
+
error: false
|
|
724
|
+
};
|
|
725
|
+
}
|
|
646
726
|
input.value = '';
|
|
647
727
|
grow();
|
|
648
728
|
} catch (error) {
|
|
@@ -656,8 +736,16 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
656
736
|
};
|
|
657
737
|
const setOpen = (next)=>{
|
|
658
738
|
open = next;
|
|
659
|
-
if (site) bridge.setSelecting(open);
|
|
660
739
|
refresh();
|
|
740
|
+
if (site) bridge.setSelecting(open && !creating);
|
|
741
|
+
};
|
|
742
|
+
const setCreating = (next)=>{
|
|
743
|
+
creating = next;
|
|
744
|
+
message = null;
|
|
745
|
+
bridge.setSelecting(open && !creating);
|
|
746
|
+
if (creating) bridge.select(null);
|
|
747
|
+
refresh();
|
|
748
|
+
(creating ? pathInput : input).focus();
|
|
661
749
|
};
|
|
662
750
|
const store = async (action)=>{
|
|
663
751
|
message = null;
|
|
@@ -677,7 +765,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
677
765
|
};
|
|
678
766
|
pill.addEventListener('click', ()=>{
|
|
679
767
|
setOpen(true);
|
|
680
|
-
input.focus();
|
|
768
|
+
(creating ? pathInput : input).focus();
|
|
681
769
|
});
|
|
682
770
|
close.addEventListener('click', ()=>setOpen(false));
|
|
683
771
|
save.addEventListener('click', ()=>void store('save'));
|
|
@@ -685,6 +773,17 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
685
773
|
signOut.addEventListener('click', ()=>bridge.signOut());
|
|
686
774
|
pillSignOut.addEventListener('click', ()=>bridge.signOut());
|
|
687
775
|
clearBlock.addEventListener('click', ()=>bridge.select(null));
|
|
776
|
+
newPage.addEventListener('click', ()=>setCreating(true));
|
|
777
|
+
cancelNew.addEventListener('click', ()=>setCreating(false));
|
|
778
|
+
pathInput.addEventListener('input', refresh);
|
|
779
|
+
pathInput.addEventListener('keydown', (event)=>{
|
|
780
|
+
if (event.key === 'Enter' && !event.isComposing) {
|
|
781
|
+
event.preventDefault();
|
|
782
|
+
input.focus();
|
|
783
|
+
} else if (event.key === 'Escape') {
|
|
784
|
+
setOpen(false);
|
|
785
|
+
}
|
|
786
|
+
});
|
|
688
787
|
undo.addEventListener('click', ()=>{
|
|
689
788
|
message = null;
|
|
690
789
|
bridge.undo();
|
|
@@ -738,7 +837,7 @@ const readSiteEditing = (doc)=>{
|
|
|
738
837
|
return null;
|
|
739
838
|
};
|
|
740
839
|
const same = (a, b)=>JSON.stringify(a) === JSON.stringify(b);
|
|
741
|
-
const createSiteBridge = (win, basePath)=>{
|
|
840
|
+
const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}` } = {})=>{
|
|
742
841
|
const doc = win.document;
|
|
743
842
|
const emitter = createEmitter();
|
|
744
843
|
const cleanup = [];
|
|
@@ -901,6 +1000,39 @@ const createSiteBridge = (win, basePath)=>{
|
|
|
901
1000
|
});
|
|
902
1001
|
}
|
|
903
1002
|
},
|
|
1003
|
+
async createPage (path, prompt) {
|
|
1004
|
+
const text = prompt.trim();
|
|
1005
|
+
if (!text) throw new Error('Say what the page is for');
|
|
1006
|
+
if (!path.trim()) throw new Error('Say where the page goes, like /pricing');
|
|
1007
|
+
if (!has('create')) throw new Error('You may not add pages here');
|
|
1008
|
+
// Opening the new page would lose them.
|
|
1009
|
+
if (state.unsaved) {
|
|
1010
|
+
throw new Error('Save or undo your changes before adding a page');
|
|
1011
|
+
}
|
|
1012
|
+
setState({
|
|
1013
|
+
prompting: true
|
|
1014
|
+
});
|
|
1015
|
+
try {
|
|
1016
|
+
const created = await api('entries', {
|
|
1017
|
+
path,
|
|
1018
|
+
prompt: text,
|
|
1019
|
+
locale: locale(),
|
|
1020
|
+
from: pageEntryId()
|
|
1021
|
+
});
|
|
1022
|
+
const url = pagePath(created.path);
|
|
1023
|
+
win.location.assign(url);
|
|
1024
|
+
return {
|
|
1025
|
+
summary: created.summary,
|
|
1026
|
+
entryId: created.entryId,
|
|
1027
|
+
path: created.path,
|
|
1028
|
+
url
|
|
1029
|
+
};
|
|
1030
|
+
} finally{
|
|
1031
|
+
setState({
|
|
1032
|
+
prompting: false
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
904
1036
|
undo () {
|
|
905
1037
|
const last = history.pop();
|
|
906
1038
|
const entry = last && working.get(last.entryId);
|
|
@@ -1027,7 +1159,7 @@ const peekContentBridge = ()=>typeof window === 'undefined' ? null : window[KEY]
|
|
|
1027
1159
|
// if (bridge.isEditing) bridge.on('update', ({ entry }) => render(entry))
|
|
1028
1160
|
//
|
|
1029
1161
|
// The first call's options win: there is one bridge per page.
|
|
1030
|
-
const getContentBridge = ({ url, prompt = true } = {})=>{
|
|
1162
|
+
const getContentBridge = ({ url, prompt = true, pagePath } = {})=>{
|
|
1031
1163
|
if (typeof window === 'undefined') return SERVER_BRIDGE;
|
|
1032
1164
|
const win = window;
|
|
1033
1165
|
if (win[KEY]) return win[KEY];
|
|
@@ -1039,7 +1171,9 @@ const getContentBridge = ({ url, prompt = true } = {})=>{
|
|
|
1039
1171
|
// Not in the visual editor, but signed in to edit through the site's
|
|
1040
1172
|
// own `/cms/login` (@obelism/content/site).
|
|
1041
1173
|
const sitePath = bridge.isEditing || win.parent !== win ? null : readSiteEditing(win.document);
|
|
1042
|
-
if (sitePath) bridge = createSiteBridge(win, sitePath
|
|
1174
|
+
if (sitePath) bridge = createSiteBridge(win, sitePath, {
|
|
1175
|
+
pagePath
|
|
1176
|
+
});
|
|
1043
1177
|
if (bridge.isEditing && prompt) {
|
|
1044
1178
|
const unmount = mountPromptBox(win.document, bridge, prompt === true ? {} : prompt);
|
|
1045
1179
|
const destroy = bridge.destroy;
|
package/dist/site.js
CHANGED
package/dist/site.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obelism/content",
|
|
3
3
|
"description": "Obelism Content SDK: the delivery API client, the visual editor bridge with prompt editing, editing on your own site, and the content model CLI",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cms",
|
|
7
7
|
"headless-cms",
|