@obelism/content 0.5.0 → 0.7.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 +104 -0
- package/dist/bin/content.js +192 -11
- package/dist/bridge.d.mts +26 -2
- package/dist/bridge.d.mts.map +1 -1
- package/dist/bridge.d.ts +26 -2
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.global.js +26 -2
- package/dist/bridge.js +202 -2
- package/dist/bridge.mjs +201 -3
- package/dist/prompt.js +15 -36
- package/dist/prompt.mjs +13 -34
- package/dist/standardWebhooks-ew5mhpac.mjs +41 -0
- package/dist/standardWebhooks-kh5dc0ff.js +43 -0
- package/dist/webhooks.d.mts +72 -0
- package/dist/webhooks.d.mts.map +1 -0
- package/dist/webhooks.d.ts +72 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +110 -0
- package/dist/webhooks.mjs +106 -0
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -155,6 +155,73 @@ export const POST = createPromptHandler({
|
|
|
155
155
|
})
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
+
## Webhooks
|
|
159
|
+
|
|
160
|
+
A webhook tells your site that what the delivery API answers has changed
|
|
161
|
+
(Space settings → Webhooks). Its requests are signed (Standard Webhooks);
|
|
162
|
+
`createWebhookHandler` verifies them and hands you the typed event:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
// app/api/revalidate/route.ts
|
|
166
|
+
import { revalidatePath, revalidateTag } from 'next/cache'
|
|
167
|
+
import { createWebhookHandler } from '@obelism/content/webhooks'
|
|
168
|
+
|
|
169
|
+
export const POST = createWebhookHandler({
|
|
170
|
+
secret: process.env.CONTENT_WEBHOOK_SECRET!, // whsec_…, shown once
|
|
171
|
+
onEvent: (event, { id }) => {
|
|
172
|
+
if (event.type === 'entry.published' && event.data.entry.path) {
|
|
173
|
+
revalidatePath(`/${event.data.entry.path}`)
|
|
174
|
+
} else {
|
|
175
|
+
revalidateTag('content')
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
It answers 401 to anything not signed with the secret or with a
|
|
182
|
+
`webhook-timestamp` more than 5 minutes off (`tolerance`, in seconds), 204
|
|
183
|
+
once `onEvent` is done, and 500 when it throws, so the CMS tries again. `id`
|
|
184
|
+
is the delivery's `webhook-id`, the same on every retry.
|
|
185
|
+
|
|
186
|
+
Elsewhere, `verifyWebhook(request, secret, { tolerance? })` returns the event
|
|
187
|
+
or throws `WebhookVerificationError`. It takes a `Request`, or the raw body
|
|
188
|
+
and headers (`{ body, headers }`; Node's `req.headers` work as they are), the
|
|
189
|
+
body exactly as it was sent:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { verifyWebhook } from '@obelism/content/webhooks'
|
|
193
|
+
|
|
194
|
+
const event = await verifyWebhook(request, process.env.CONTENT_WEBHOOK_SECRET!)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`WebhookEvent` is a union on `type`: `entry.published`, `entry.unpublished`,
|
|
198
|
+
`entry.deleted` and `entry.moved` (`data.entry`: `{ id, contentType, name,
|
|
199
|
+
path }`, plus `previousPath` when moved), `asset.updated` (`data.asset`,
|
|
200
|
+
`data.entries`), `model.changed` (`data.contentTypes`: `{ apiId, action }`),
|
|
201
|
+
`locales.changed` (`data.defaultLocale`, `data.locales`) and `webhook.test`.
|
|
202
|
+
|
|
203
|
+
## Click rates
|
|
204
|
+
|
|
205
|
+
The visual editor can show how often each block is clicked, on the page
|
|
206
|
+
itself: a small label on every block with clicks ("24% clicked"), on or off
|
|
207
|
+
with the editor's **Show click rates on the page** switch. Clicking a label selects its
|
|
208
|
+
block. The rates come from Obelism numbers, so tag a block's clickable
|
|
209
|
+
element with its uid and load numbers' script on the site (see the CMS's
|
|
210
|
+
visual editor guide, "Click rates"):
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
<a href={block.link} data-numbers-id={block._uid}>
|
|
214
|
+
{block.label}
|
|
215
|
+
</a>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The bridge draws the labels itself; it needs nothing else from your site.
|
|
219
|
+
For your own UI, the rates and the switch are in the bridge's state:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
bridge.on('change', ({ stats, statsVisible }) => …) // stats[uid].rate, 0–1
|
|
223
|
+
```
|
|
224
|
+
|
|
158
225
|
## Helpers
|
|
159
226
|
|
|
160
227
|
- `richTextToHtml(doc, { renderEntry?, renderAsset? })`, `richTextToPlainText(doc)`
|
|
@@ -211,3 +278,40 @@ npx @obelism/content model apply # apply it, all or nothing
|
|
|
211
278
|
|
|
212
279
|
Options: `--file`, `--url` (or `$CONTENT_URL`), `--prune`. In GitHub Actions
|
|
213
280
|
the plan is also written to the job summary.
|
|
281
|
+
|
|
282
|
+
### Types
|
|
283
|
+
|
|
284
|
+
Write a TypeScript type per content type to `content/content-types.ts`:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
npx @obelism/content types # from the environment
|
|
288
|
+
npx @obelism/content types --file content/content-model.json # from the model file, no token
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Pages and data become `Entry<{ …fields }, 'apiId'>`, blocks
|
|
292
|
+
`Block<{ …fields }, 'apiId'>`, named after the apiId (`blogPost` →
|
|
293
|
+
`BlogPost`), with `ContentEntry` and `ContentBlock` as the unions of them.
|
|
294
|
+
Each field is typed by what the delivery API answers with its defaults (one
|
|
295
|
+
locale, rich text as a document), and `| null` when empty:
|
|
296
|
+
|
|
297
|
+
| Field | Type |
|
|
298
|
+
| ------------------------ | -------------------------------------------------------------------- |
|
|
299
|
+
| text, textarea, datetime | `string` |
|
|
300
|
+
| number / boolean | `number` / `boolean` |
|
|
301
|
+
| select | the options' values, `'a' \| 'b'` (a list when multiple) |
|
|
302
|
+
| asset | `Asset` (a list when multiple) |
|
|
303
|
+
| reference | the allowed entry types or their `EntryStub`s (a list when multiple) |
|
|
304
|
+
| blocks | a list of the allowed block types and `ImproveBlock` |
|
|
305
|
+
| richtext | `RichTextDocument` |
|
|
306
|
+
| json | `unknown` |
|
|
307
|
+
|
|
308
|
+
A localized field is the one locale's value. Pass the types to the client:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
import type { BlogPost } from './content/content-types'
|
|
312
|
+
|
|
313
|
+
const post = await client.getPage<BlogPost>('blog/hello')
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Run it again after the model changes, e.g. after `model pull`. Options:
|
|
317
|
+
`--dir` (default `content`), `--file`, `--url`.
|
package/dist/bin/content.js
CHANGED
|
@@ -256,9 +256,8 @@ class CliError extends Error {
|
|
|
256
256
|
super(message), this.exitCode = exitCode;
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
|
-
//
|
|
260
|
-
const
|
|
261
|
-
const call = async (method, path, body)=>{
|
|
259
|
+
// Calls the management API; anything but JSON back is a CliError.
|
|
260
|
+
const managementApi = (url, token, fetchImpl = fetch)=>async (method, path, body)=>{
|
|
262
261
|
const response = await fetchImpl(`${url}${config.MANAGEMENT_PATH}${path}`, {
|
|
263
262
|
method,
|
|
264
263
|
headers: {
|
|
@@ -284,6 +283,9 @@ const runModelCommand = async ({ command, file, url, token, prune, log = console
|
|
|
284
283
|
throw new CliError(`${response.status} from ${url}: ${text.slice(0, 200)}`);
|
|
285
284
|
}
|
|
286
285
|
};
|
|
286
|
+
// Exit code: 0 done, 1 problems in the plan.
|
|
287
|
+
const runModelCommand = async ({ command, file, url, token, prune, log = console.log, summaryFile, fetch: fetchImpl = fetch })=>{
|
|
288
|
+
const call = managementApi(url, token, fetchImpl);
|
|
287
289
|
if (command === 'pull') {
|
|
288
290
|
const { ok, json } = await call('GET', '/model');
|
|
289
291
|
if (!ok) throw new CliError(String(json.error ?? 'Pull failed'));
|
|
@@ -511,8 +513,173 @@ const runInitCommand = async ({ url, token, dir, install: shouldInstall, cwd = p
|
|
|
511
513
|
return 0;
|
|
512
514
|
};
|
|
513
515
|
|
|
516
|
+
// What `content types` writes to content/content-types.ts: a TypeScript type
|
|
517
|
+
// per content type, for what the delivery API answers with its defaults (one
|
|
518
|
+
// locale, rich text as a document). Pages and data are `Entry<…>`, blocks
|
|
519
|
+
// `Block<…>`, every field `| null` (delivery answers null for empty ones).
|
|
520
|
+
// Pure.
|
|
521
|
+
//
|
|
522
|
+
// const post = await client.getPage<BlogPost>('blog/hello')
|
|
523
|
+
// The SDK's types the file imports; a content type whose name would clash
|
|
524
|
+
// gets its kind as a suffix (`asset` → `AssetEntry`).
|
|
525
|
+
const SDK_TYPES = [
|
|
526
|
+
'Asset',
|
|
527
|
+
'Block',
|
|
528
|
+
'Entry',
|
|
529
|
+
'EntryStub',
|
|
530
|
+
'ImproveBlock',
|
|
531
|
+
'RichTextDocument'
|
|
532
|
+
];
|
|
533
|
+
// The unions of every entry and block type the file ends with.
|
|
534
|
+
const UNION_NAMES = [
|
|
535
|
+
'ContentEntry',
|
|
536
|
+
'ContentBlock'
|
|
537
|
+
];
|
|
538
|
+
const quote = (value)=>`'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
539
|
+
const union = (members)=>[
|
|
540
|
+
...new Set(members)
|
|
541
|
+
].join(' | ');
|
|
542
|
+
const list = (type, multiple)=>multiple ? type.includes(' | ') ? `(${type})[]` : `${type}[]` : type;
|
|
543
|
+
// `unknown` (a json field) holds null already.
|
|
544
|
+
const nullable = (type)=>type === 'unknown' ? type : `${type} | null`;
|
|
545
|
+
const comment = (text, indent)=>`${indent}// ${text.replace(/\s+/g, ' ').trim()}\n`;
|
|
546
|
+
const generateTypes = (model)=>{
|
|
547
|
+
const types = model.contentTypes;
|
|
548
|
+
const blockTypes = types.filter(({ kind })=>kind === 'block');
|
|
549
|
+
const entryTypes = types.filter(({ kind })=>kind !== 'block');
|
|
550
|
+
const used = new Set();
|
|
551
|
+
const use = (name)=>{
|
|
552
|
+
used.add(name);
|
|
553
|
+
return name;
|
|
554
|
+
};
|
|
555
|
+
// apiIds start with a lowercase letter and are unique, so capitalizing
|
|
556
|
+
// keeps them unique.
|
|
557
|
+
const typeName = new Map(types.map(({ apiId, kind })=>{
|
|
558
|
+
const name = `${apiId[0].toUpperCase()}${apiId.slice(1)}`;
|
|
559
|
+
const reserved = SDK_TYPES.includes(name) || UNION_NAMES.includes(name);
|
|
560
|
+
return [
|
|
561
|
+
apiId,
|
|
562
|
+
reserved ? `${name}${kind === 'block' ? 'Block' : 'Entry'}` : name
|
|
563
|
+
];
|
|
564
|
+
}));
|
|
565
|
+
const known = (allowed, candidates)=>allowed ? candidates.filter(({ apiId })=>allowed.includes(apiId)) : candidates;
|
|
566
|
+
const value = (field)=>{
|
|
567
|
+
switch(field.type){
|
|
568
|
+
case 'text':
|
|
569
|
+
case 'textarea':
|
|
570
|
+
case 'datetime':
|
|
571
|
+
return 'string';
|
|
572
|
+
case 'number':
|
|
573
|
+
return 'number';
|
|
574
|
+
case 'boolean':
|
|
575
|
+
return 'boolean';
|
|
576
|
+
case 'richtext':
|
|
577
|
+
return use('RichTextDocument');
|
|
578
|
+
case 'select':
|
|
579
|
+
return list(union(field.options.map((option)=>quote(option.value))), field.multiple);
|
|
580
|
+
case 'asset':
|
|
581
|
+
return list(use('Asset'), field.multiple);
|
|
582
|
+
case 'reference':
|
|
583
|
+
{
|
|
584
|
+
// The entry itself, or a stub past the requested depth.
|
|
585
|
+
const targets = known(field.contentTypes, entryTypes);
|
|
586
|
+
const type = targets.length === 0 ? `${use('Entry')} | ${use('EntryStub')}` : union([
|
|
587
|
+
...targets.map(({ apiId })=>typeName.get(apiId)),
|
|
588
|
+
`${use('EntryStub')}<${union(targets.map(({ apiId })=>quote(apiId)))}>`
|
|
589
|
+
]);
|
|
590
|
+
return list(type, field.multiple);
|
|
591
|
+
}
|
|
592
|
+
case 'blocks':
|
|
593
|
+
{
|
|
594
|
+
const allowed = known(field.allowedBlocks, blockTypes);
|
|
595
|
+
const blocks = allowed.length === 0 ? use('Block') : union(allowed.map(({ apiId })=>typeName.get(apiId)));
|
|
596
|
+
const improve = `${use('ImproveBlock')}${allowed.length === 0 ? '' : `<${blocks}>`}`;
|
|
597
|
+
return list(union([
|
|
598
|
+
blocks,
|
|
599
|
+
improve
|
|
600
|
+
]), true);
|
|
601
|
+
}
|
|
602
|
+
case 'json':
|
|
603
|
+
return 'unknown';
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
const declaration = (type)=>{
|
|
607
|
+
const wrapper = type.kind === 'block' ? use('Block') : use('Entry');
|
|
608
|
+
const fields = type.fields.map((field)=>`${field.description ? comment(field.description, '\t\t') : ''}\t\t${field.apiId}: ${nullable(value(field))}\n`).join('');
|
|
609
|
+
const body = fields ? `{\n${fields}\t}` : 'Record<never, never>';
|
|
610
|
+
return [
|
|
611
|
+
comment(`${type.name} (${type.kind})${type.description ? `: ${type.description}` : ''}`, ''),
|
|
612
|
+
`export type ${typeName.get(type.apiId)} = ${wrapper}<\n\t${body},\n\t${quote(type.apiId)}\n>\n`
|
|
613
|
+
].join('');
|
|
614
|
+
};
|
|
615
|
+
const declarations = types.map(declaration);
|
|
616
|
+
const unions = [
|
|
617
|
+
[
|
|
618
|
+
'ContentEntry',
|
|
619
|
+
entryTypes
|
|
620
|
+
],
|
|
621
|
+
[
|
|
622
|
+
'ContentBlock',
|
|
623
|
+
blockTypes
|
|
624
|
+
]
|
|
625
|
+
];
|
|
626
|
+
const unionLines = unions.filter(([, members])=>members.length > 0).map(([name, members])=>`export type ${name} = ${union(members.map(({ apiId })=>typeName.get(apiId)))}\n`);
|
|
627
|
+
const imports = SDK_TYPES.filter((name)=>used.has(name));
|
|
628
|
+
return [
|
|
629
|
+
'// Generated by `npx @obelism/content types` from the content model.\n',
|
|
630
|
+
"// Don't edit: run it again after the model changes.\n",
|
|
631
|
+
...imports.length > 0 ? [
|
|
632
|
+
`\nimport type { ${imports.join(', ')} } from '@obelism/content'\n`
|
|
633
|
+
] : [],
|
|
634
|
+
...declarations.map((text)=>`\n${text}`),
|
|
635
|
+
...unionLines.length > 0 ? [
|
|
636
|
+
'\n',
|
|
637
|
+
...unionLines
|
|
638
|
+
] : []
|
|
639
|
+
].join('');
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
const readModel = async (file)=>{
|
|
643
|
+
let text;
|
|
644
|
+
try {
|
|
645
|
+
text = await promises.readFile(file, 'utf8');
|
|
646
|
+
} catch {
|
|
647
|
+
throw new CliError(`Can't read ${file}. Run \`content model pull\` first?`);
|
|
648
|
+
}
|
|
649
|
+
try {
|
|
650
|
+
return JSON.parse(text);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
throw new CliError(`${file} is not valid JSON: ${error.message}`);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
const runTypesCommand = async ({ dir, file, url, token, cwd = process.cwd(), log = console.log, fetch: fetchImpl = fetch })=>{
|
|
656
|
+
let model;
|
|
657
|
+
if (file) {
|
|
658
|
+
model = await readModel(node_path.resolve(cwd, file));
|
|
659
|
+
} else {
|
|
660
|
+
if (!token) {
|
|
661
|
+
throw new CliError('CONTENT_MANAGEMENT_TOKEN is not set (or pass --file to read a model file).', 2);
|
|
662
|
+
}
|
|
663
|
+
const { ok, json } = await managementApi(url, token, fetchImpl)('GET', '/model');
|
|
664
|
+
if (!ok) throw new CliError(String(json.error ?? 'Pull failed'));
|
|
665
|
+
model = json;
|
|
666
|
+
}
|
|
667
|
+
if (!Array.isArray(model.contentTypes)) {
|
|
668
|
+
throw new CliError('The model has no contentTypes');
|
|
669
|
+
}
|
|
670
|
+
const out = node_path.join(dir, 'content-types.ts');
|
|
671
|
+
await promises.mkdir(node_path.resolve(cwd, dir), {
|
|
672
|
+
recursive: true
|
|
673
|
+
});
|
|
674
|
+
await promises.writeFile(node_path.resolve(cwd, out), generateTypes(model));
|
|
675
|
+
const count = model.contentTypes.length;
|
|
676
|
+
log(`Wrote ${count} ${count === 1 ? 'type' : 'types'} to ${out}.`);
|
|
677
|
+
return 0;
|
|
678
|
+
};
|
|
679
|
+
|
|
514
680
|
const USAGE = `Usage: content init [--dir content] [--url origin] [--skip-install]
|
|
515
681
|
content model <pull|plan|apply> [--file path] [--url origin] [--prune]
|
|
682
|
+
content types [--dir content] [--file path] [--url origin]
|
|
516
683
|
|
|
517
684
|
init start a site's repository on the token's environment: the
|
|
518
685
|
model file, an example per content type, the SDK installed
|
|
@@ -520,14 +687,19 @@ const USAGE = `Usage: content init [--dir content] [--url origin] [--skip-instal
|
|
|
520
687
|
model pull write the environment's model to the file
|
|
521
688
|
model plan show what applying the file would change, and what stands in the way
|
|
522
689
|
model apply apply the file in one transaction, or change nothing
|
|
690
|
+
types write content-types.ts: a TypeScript type per content type,
|
|
691
|
+
from the environment's model (or the model file with --file)
|
|
523
692
|
|
|
524
|
-
--dir where init writes content-model.json and examples
|
|
693
|
+
--dir where init writes content-model.json and examples/, and types
|
|
694
|
+
content-types.ts (default content)
|
|
525
695
|
--skip-install init leaves dependencies alone
|
|
526
|
-
--file the model file (default content/content-model.json)
|
|
696
|
+
--file the model file (default content/content-model.json); types
|
|
697
|
+
reads it instead of pulling the model
|
|
527
698
|
--url the CMS (default $CONTENT_URL, else ${config.DEFAULT_URL})
|
|
528
699
|
--prune plan and apply also delete content types the file leaves out
|
|
529
700
|
|
|
530
|
-
Needs CONTENT_MANAGEMENT_TOKEN: a management token from Space settings
|
|
701
|
+
Needs CONTENT_MANAGEMENT_TOKEN: a management token from Space settings
|
|
702
|
+
(not for types --file).`;
|
|
531
703
|
const main = async ()=>{
|
|
532
704
|
let parsed;
|
|
533
705
|
try {
|
|
@@ -535,8 +707,7 @@ const main = async ()=>{
|
|
|
535
707
|
allowPositionals: true,
|
|
536
708
|
options: {
|
|
537
709
|
file: {
|
|
538
|
-
type: 'string'
|
|
539
|
-
default: 'content/content-model.json'
|
|
710
|
+
type: 'string'
|
|
540
711
|
},
|
|
541
712
|
url: {
|
|
542
713
|
type: 'string'
|
|
@@ -570,13 +741,23 @@ const main = async ()=>{
|
|
|
570
741
|
return 0;
|
|
571
742
|
}
|
|
572
743
|
const isInit = group === 'init' && command === undefined;
|
|
744
|
+
const isTypes = group === 'types' && command === undefined;
|
|
573
745
|
const modelCommand = group === 'model' && (command === 'pull' || command === 'plan' || command === 'apply') ? command : null;
|
|
574
|
-
if (!isInit && !modelCommand) throw new CliError(USAGE, 2);
|
|
746
|
+
if (!isInit && !isTypes && !modelCommand) throw new CliError(USAGE, 2);
|
|
575
747
|
const token = process.env.CONTENT_MANAGEMENT_TOKEN;
|
|
748
|
+
const url = config.normalizeUrl(values.url ?? process.env.CONTENT_URL ?? config.DEFAULT_URL);
|
|
749
|
+
// With --file, types needs no token.
|
|
750
|
+
if (isTypes) {
|
|
751
|
+
return runTypesCommand({
|
|
752
|
+
dir: values.dir,
|
|
753
|
+
file: values.file,
|
|
754
|
+
url,
|
|
755
|
+
token
|
|
756
|
+
});
|
|
757
|
+
}
|
|
576
758
|
if (!token) {
|
|
577
759
|
throw new CliError(`CONTENT_MANAGEMENT_TOKEN is not set.\n\n${USAGE}`, 2);
|
|
578
760
|
}
|
|
579
|
-
const url = config.normalizeUrl(values.url ?? process.env.CONTENT_URL ?? config.DEFAULT_URL);
|
|
580
761
|
if (!modelCommand) {
|
|
581
762
|
return runInitCommand({
|
|
582
763
|
url,
|
|
@@ -587,7 +768,7 @@ const main = async ()=>{
|
|
|
587
768
|
}
|
|
588
769
|
return runModelCommand({
|
|
589
770
|
command: modelCommand,
|
|
590
|
-
file: values.file,
|
|
771
|
+
file: values.file ?? 'content/content-model.json',
|
|
591
772
|
url,
|
|
592
773
|
token,
|
|
593
774
|
prune: values.prune,
|
package/dist/bridge.d.mts
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { Entry } from './index.mjs';
|
|
2
2
|
|
|
3
|
+
type BlockStats = {
|
|
4
|
+
clicks: number;
|
|
5
|
+
clickers: number;
|
|
6
|
+
viewers: number;
|
|
7
|
+
rate: number;
|
|
8
|
+
};
|
|
9
|
+
type StatsLabels = {
|
|
10
|
+
clicked: string;
|
|
11
|
+
title: string;
|
|
12
|
+
};
|
|
13
|
+
declare const DEFAULT_STATS_LABELS: StatsLabels;
|
|
14
|
+
type StatsOverlay = {
|
|
15
|
+
update(stats: Readonly<Record<string, BlockStats>>, visible: boolean): void;
|
|
16
|
+
refresh(): void;
|
|
17
|
+
destroy(): void;
|
|
18
|
+
};
|
|
19
|
+
declare const mountStatsOverlay: (win: Window, { selector, onSelect, labels: labelOverrides, }: {
|
|
20
|
+
selector: (uid: string) => string;
|
|
21
|
+
onSelect: (uid: string) => void;
|
|
22
|
+
labels?: Partial<StatsLabels> | undefined;
|
|
23
|
+
}) => StatsOverlay;
|
|
24
|
+
|
|
3
25
|
type BridgeEvents = {
|
|
4
26
|
update: {
|
|
5
27
|
entry: Entry;
|
|
@@ -20,6 +42,8 @@ type BridgeState = {
|
|
|
20
42
|
saving: boolean;
|
|
21
43
|
publishing: boolean;
|
|
22
44
|
selecting: boolean;
|
|
45
|
+
stats: Readonly<Record<string, BlockStats>>;
|
|
46
|
+
statsVisible: boolean;
|
|
23
47
|
};
|
|
24
48
|
type PromptResult = {
|
|
25
49
|
summary: string;
|
|
@@ -93,6 +117,6 @@ type BridgeOptions = {
|
|
|
93
117
|
declare const peekContentBridge: () => ContentBridge | null;
|
|
94
118
|
declare const getContentBridge: ({ url, prompt, pagePath, }?: BridgeOptions) => ContentBridge;
|
|
95
119
|
|
|
96
|
-
export { DEFAULT_PROMPT_LABELS, INITIAL_STATE, getContentBridge, mountPromptBox, peekContentBridge };
|
|
97
|
-
export type { BridgeEvent, BridgeEvents, BridgeListener, BridgeOptions, BridgeState, ContentBridge, CreatePageResult, PromptBoxOptions, PromptLabels, PromptResult };
|
|
120
|
+
export { DEFAULT_PROMPT_LABELS, DEFAULT_STATS_LABELS, INITIAL_STATE, getContentBridge, mountPromptBox, mountStatsOverlay, peekContentBridge };
|
|
121
|
+
export type { BlockStats, BridgeEvent, BridgeEvents, BridgeListener, BridgeOptions, BridgeState, ContentBridge, CreatePageResult, PromptBoxOptions, PromptLabels, PromptResult, StatsLabels, StatsOverlay };
|
|
98
122
|
//# 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":";;
|
|
1
|
+
{"version":3,"file":"bridge.d.mts","sources":["../src/bridge/statsOverlay.ts","../src/bridge/createBridge.ts","../src/bridge/promptBox.ts","../src/bridge.ts"],"mappings":";;AAAA,KAAK,UAAU;AACf;AACA;AACA;AACA;AACA;AACA,KAAK,WAAW;AAChB;AACA;AACA;AACA,cAAc,oBAAoB,EAAE,WAAW;AAc/C,KAAK,YAAY;AACjB,kBAAkB,QAAQ,CAAC,MAAM,SAAS,UAAU;AACpD;AACA;AACA;AACA,cAAc,iBAAiB,QAAQ,MAAM;AAC7C;AACA;AACA,aAAa,OAAO,CAAC,WAAW;AAChC,MAAM,YAAY;;AC5BlB,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,WAAW,QAAQ,CAAC,MAAM,SAAS,UAAU;AAC7C;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;;ACxDxC,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;AACA,cAAc,cAAc,QAAQ,QAAQ,UAAU,aAAa,YAAY,gBAAgB;;ACzB/F,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
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { Entry } from './index.js';
|
|
2
2
|
|
|
3
|
+
type BlockStats = {
|
|
4
|
+
clicks: number;
|
|
5
|
+
clickers: number;
|
|
6
|
+
viewers: number;
|
|
7
|
+
rate: number;
|
|
8
|
+
};
|
|
9
|
+
type StatsLabels = {
|
|
10
|
+
clicked: string;
|
|
11
|
+
title: string;
|
|
12
|
+
};
|
|
13
|
+
declare const DEFAULT_STATS_LABELS: StatsLabels;
|
|
14
|
+
type StatsOverlay = {
|
|
15
|
+
update(stats: Readonly<Record<string, BlockStats>>, visible: boolean): void;
|
|
16
|
+
refresh(): void;
|
|
17
|
+
destroy(): void;
|
|
18
|
+
};
|
|
19
|
+
declare const mountStatsOverlay: (win: Window, { selector, onSelect, labels: labelOverrides, }: {
|
|
20
|
+
selector: (uid: string) => string;
|
|
21
|
+
onSelect: (uid: string) => void;
|
|
22
|
+
labels?: Partial<StatsLabels> | undefined;
|
|
23
|
+
}) => StatsOverlay;
|
|
24
|
+
|
|
3
25
|
type BridgeEvents = {
|
|
4
26
|
update: {
|
|
5
27
|
entry: Entry;
|
|
@@ -20,6 +42,8 @@ type BridgeState = {
|
|
|
20
42
|
saving: boolean;
|
|
21
43
|
publishing: boolean;
|
|
22
44
|
selecting: boolean;
|
|
45
|
+
stats: Readonly<Record<string, BlockStats>>;
|
|
46
|
+
statsVisible: boolean;
|
|
23
47
|
};
|
|
24
48
|
type PromptResult = {
|
|
25
49
|
summary: string;
|
|
@@ -93,6 +117,6 @@ type BridgeOptions = {
|
|
|
93
117
|
declare const peekContentBridge: () => ContentBridge | null;
|
|
94
118
|
declare const getContentBridge: ({ url, prompt, pagePath, }?: BridgeOptions) => ContentBridge;
|
|
95
119
|
|
|
96
|
-
export { DEFAULT_PROMPT_LABELS, INITIAL_STATE, getContentBridge, mountPromptBox, peekContentBridge };
|
|
97
|
-
export type { BridgeEvent, BridgeEvents, BridgeListener, BridgeOptions, BridgeState, ContentBridge, CreatePageResult, PromptBoxOptions, PromptLabels, PromptResult };
|
|
120
|
+
export { DEFAULT_PROMPT_LABELS, DEFAULT_STATS_LABELS, INITIAL_STATE, getContentBridge, mountPromptBox, mountStatsOverlay, peekContentBridge };
|
|
121
|
+
export type { BlockStats, BridgeEvent, BridgeEvents, BridgeListener, BridgeOptions, BridgeState, ContentBridge, CreatePageResult, PromptBoxOptions, PromptLabels, PromptResult, StatsLabels, StatsOverlay };
|
|
98
122
|
//# 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":";;
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sources":["../src/bridge/statsOverlay.ts","../src/bridge/createBridge.ts","../src/bridge/promptBox.ts","../src/bridge.ts"],"mappings":";;AAAA,KAAK,UAAU;AACf;AACA;AACA;AACA;AACA;AACA,KAAK,WAAW;AAChB;AACA;AACA;AACA,cAAc,oBAAoB,EAAE,WAAW;AAc/C,KAAK,YAAY;AACjB,kBAAkB,QAAQ,CAAC,MAAM,SAAS,UAAU;AACpD;AACA;AACA;AACA,cAAc,iBAAiB,QAAQ,MAAM;AAC7C;AACA;AACA,aAAa,OAAO,CAAC,WAAW;AAChC,MAAM,YAAY;;AC5BlB,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,WAAW,QAAQ,CAAC,MAAM,SAAS,UAAU;AAC7C;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;;ACxDxC,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;AACA,cAAc,cAAc,QAAQ,QAAQ,UAAU,aAAa,YAAY,gBAAgB;;ACzB/F,KAAK,aAAa;AAClB;AACA,uBAAuB,gBAAgB;AACvC;AACA;AACA,cAAc,iBAAiB,QAAQ,aAAa;AACpD,cAAc,gBAAgB,gCAAgC,aAAa,KAAK,aAAa","names":[]}
|
package/dist/bridge.global.js
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
|
-
!/*! Obelism Content bridge (@obelism/content), MIT. https://content.obelism.studio/docs/guides/visual-editor */function(){"use strict";var e;let t="data-cms-uid",r="data-cms-entry",n="cms_environment",o=
|
|
1
|
+
!/*! Obelism Content bridge (@obelism/content), MIT. https://content.obelism.studio/docs/guides/visual-editor */function(){"use strict";var e;let t="data-cms-uid",r="data-cms-entry",n="cms_environment",o={clicked:"{rate}% clicked",title:"{clickers} of {viewers} visitors of its page clicked it in the last 30 days ({clicks} clicks)"},a=(e,t)=>e.replace(/\{(\w+)\}/g,(e,r)=>r in t?String(t[r]):e),i=`
|
|
2
|
+
:host { all: initial; }
|
|
3
|
+
.layer {
|
|
4
|
+
position: fixed; inset: 0; pointer-events: none;
|
|
5
|
+
z-index: 2147483646;
|
|
6
|
+
font: 600 12px/1 'InterVariable', 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
7
|
+
-webkit-font-smoothing: antialiased;
|
|
8
|
+
}
|
|
9
|
+
.badge {
|
|
10
|
+
position: absolute; transform: translateX(-100%);
|
|
11
|
+
display: inline-flex; gap: 6px; align-items: center;
|
|
12
|
+
padding: 6px 10px; white-space: nowrap; cursor: pointer;
|
|
13
|
+
pointer-events: auto; border: 0;
|
|
14
|
+
color: rgb(238 236 233); background: rgb(12 12 12 / .88);
|
|
15
|
+
border-radius: 5em; box-shadow: 0 6px 16px -8px rgb(12 12 12 / .5);
|
|
16
|
+
font: inherit;
|
|
17
|
+
}
|
|
18
|
+
.badge::before {
|
|
19
|
+
content: ''; width: 8px; height: 8px; border-radius: 50%;
|
|
20
|
+
background: rgb(212 130 30);
|
|
21
|
+
}
|
|
22
|
+
.badge[data-place='beside'] { transform: translateY(-50%); }
|
|
23
|
+
.badge[data-place='above'] { transform: translate(-100%, -100%); }
|
|
24
|
+
.badge:focus-visible { outline: 2px solid rgb(212 130 30); outline-offset: 2px; }
|
|
25
|
+
`,s=(e,t)=>e.height>=64?{place:"inside",top:Math.max(e.top,0)+8,left:e.right-8}:e.right+8+140<=t?{place:"beside",top:e.top+e.height/2,left:e.right+8}:{place:"above",top:e.top-4,left:e.right},l="#d58a1f",d=(e="")=>`${e}[${t}]{cursor:pointer;transition:outline-color .15s}${e}[${t}]:hover{outline:2px dashed ${l};outline-offset:2px}${e}[${t}][data-cms-selected]{outline:2px solid ${l};outline-offset:2px}`,c={features:[],selectedUid:null,prompting:!1,canUndo:!1,unsaved:!1,saving:!1,publishing:!1,selecting:!1,stats:{},statsVisible:!1},p=()=>{let e={update:new Set,saved:new Set,published:new Set,change:new Set};return{listeners:e,on:(t,r)=>(e[t].add(r),()=>{e[t].delete(r)}),emit(t,r){for(let n of e[t])try{n(r)}catch(e){console.error("[@obelism/content bridge]",e)}}}},g=()=>Promise.reject(Error("Save and publish in the visual editor itself")),u={prompt:()=>Promise.reject(Error("Prompts only work inside the visual editor")),createPage:()=>Promise.reject(Error("New pages are made on the site itself")),undo:()=>{},save:g,publish:g,signOut:()=>{},select:()=>{},setSelecting:()=>{}},h=e=>"number"==typeof e&&Number.isFinite(e)&&e>=0,b=e=>`[${t}="${e.replace(/["\\]/g,"\\$&")}"]`,m=(e,t)=>{let r=p();e.on("saved",()=>r.emit("saved",{})),e.on("published",()=>r.emit("published",{}));let o=!1;return{isEditing:e.isEditing,mode:e.isEditing?"editor":null,entryId:e.entryId,environment:t.get(n),on:(t,n)=>("update"!==t||o||(o=!0,e.on("update",e=>{let t=e?.entry;t&&r.emit("update",{entry:t,selectedUid:null})})),r.on(t,n)),getState:()=>c,...u,destroy:()=>{}}},f={open:"Edit with a prompt",placeholder:"Describe a change, e.g. “make the heading shorter”",send:"Apply",undo:"Undo",close:"Close",scopeEntry:"Whole page",scopeBlock:"Selected block",clearBlock:"Edit the whole page instead",working:"Working on it…",save:"Save",publish:"Publish",saving:"Saving…",publishing:"Publishing…",saved:"Saved as a draft",published:"Published",unsaved:"Unsaved changes",signOut:"Exit editing",viewingDrafts:"Viewing drafts",selectHint:"Click a block to change only that block",newPage:"New page",newPageCancel:"Edit this page instead",newPagePlaceholder:"Describe the new page, e.g. “a pricing page for agencies”",path:"Path",pathPlaceholder:"/pricing",creating:"Writing the page…",created:"Opening the new page…"},v={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"/>',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"/>',block:'<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M7 8h10"/><path d="M7 12h6"/>',undo:'<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/>',close:'<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',send:'<path d="M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z"/><path d="M6 12h16"/>'},y=`
|
|
2
26
|
:host { all: initial; }
|
|
3
27
|
* { box-sizing: border-box; font: inherit; color: inherit; margin: 0; }
|
|
4
28
|
.root {
|
|
@@ -155,4 +179,4 @@ button.chip:disabled { opacity: .5; }
|
|
|
155
179
|
background: rgb(var(--theme-accent-rgb)); margin-right: 8px;
|
|
156
180
|
}
|
|
157
181
|
[hidden] { display: none !important; }
|
|
158
|
-
`,b=(e,t,r={},...n)=>{let o=e.createElement(t);for(let t of(Object.assign(o,r),n))o.appendChild("string"==typeof t?e.createTextNode(t):t);return o},m=(e,t)=>{let r=b(e,"span",{className:"icon"});return r.setAttribute("aria-hidden","true"),r.innerHTML=`<svg viewBox="0 0 24 24">${u[t]}</svg>`,r},f=(e,t,r={})=>{let n={...g,...r.labels},o=b(e,"div");o.setAttribute("data-obelism-content-prompt","");let a=o.attachShadow({mode:"open"}),i="site"===t.mode,s=b(e,"span",{textContent:n.open}),l=b(e,"button",{className:"pill",type:"button"},s,b(e,"span",{className:"mark",textContent:"⁜"})),d=b(e,"span",{className:"pill compact"},`${n.viewingDrafts} \xb7 `),c=b(e,"button",{className:"link",type:"button",textContent:n.signOut});d.appendChild(c);let p=b(e,"p",{className:"status"});p.setAttribute("role","status");let u=b(e,"textarea",{placeholder:n.placeholder,rows:1});u.setAttribute("aria-label",n.open);let f=m(e,"page"),v=m(e,"block"),y=b(e,"span"),x=b(e,"button",{type:"button",title:n.clearBlock});x.setAttribute("aria-label",n.clearBlock),x.appendChild(m(e,"close"));let w=b(e,"span",{className:"chip scope"},f,v,y,x),k=b(e,"button",{className:"action chip",type:"button"},m(e,"undo"),n.undo),E=b(e,"button",{className:"action chip",type:"button"},m(e,"newPage"),n.newPage),S=b(e,"button",{type:"button",title:n.newPageCancel});S.setAttribute("aria-label",n.newPageCancel),S.appendChild(m(e,"close"));let C=b(e,"span",{className:"chip scope"},m(e,"newPage"),b(e,"span",{textContent:n.newPage}),S),N=b(e,"input",{type:"text",placeholder:n.pathPlaceholder,autocomplete:"off",spellcheck:!1}),U=b(e,"label",{className:"path"},b(e,"span",{textContent:n.path}),N),P=b(e,"button",{className:"close icon-button",type:"button",title:n.close},m(e,"close"));P.setAttribute("aria-label",n.close);let L=b(e,"button",{className:"action primary icon-button",type:"submit"},b(e,"span",{className:"visually-hidden",textContent:n.send}),m(e,"send"));L.title=n.send;let $=b(e,"span",{className:"unsaved"});$.appendChild(b(e,"span",{className:"dot"})),$.appendChild(e.createTextNode(n.unsaved));let A=b(e,"button",{className:"link",type:"button",textContent:n.signOut}),O=b(e,"button",{className:"action chip",type:"button",textContent:n.save}),I=b(e,"button",{className:"action chip",type:"button",textContent:n.publish}),M=b(e,"div",{className:"footer"},$,b(e,"span",{className:"spacer"}),A,O,I),j=b(e,"form",{className:"panel"},p,U,u,b(e,"div",{className:"toolbar"},w,C,k,E,b(e,"span",{className:"spacer"}),P,b(e,"span",{className:"divider"}),L),M),R=b(e,"div",{className:"root"},b(e,"div",{className:"bar"},l,d),j);a.append(b(e,"style",{textContent:h}),R);let z=!1,D=!1,T=null,B=e=>{let r=e.features.includes("prompt"),o=e.saving||e.publishing,a=e.prompting||o;R.hidden=!r&&!i,l.hidden=z||!r,d.hidden=r||!i,j.hidden=!z||!r;let c=i&&e.features.includes("create"),g=i&&null===t.entryId,h=g&&c;c||(D=!1),h&&(D=!0),s.textContent=h?n.newPage:n.open,S.hidden=h,w.hidden=D,C.hidden=!D,E.hidden=D||!c,E.disabled=a,U.hidden=!D,N.disabled=a,u.placeholder=D?n.newPagePlaceholder:n.placeholder,y.textContent=e.selectedUid?n.scopeBlock:n.scopeEntry,x.hidden=!e.selectedUid,f.hidden=!!e.selectedUid,v.hidden=!e.selectedUid,k.hidden=D||!e.canUndo,k.disabled=a,L.disabled=a||""===u.value.trim()||D&&""===N.value.trim(),u.disabled=a,O.hidden=!e.features.includes("save")||g,O.disabled=a||!e.unsaved,O.textContent=e.saving?n.saving:n.save,I.hidden=!e.features.includes("publish")||g,I.disabled=a,I.textContent=e.publishing?n.publishing:n.publish,A.hidden=!i,M.hidden=!i,$.hidden=!e.unsaved;let b=e.prompting?D?n.creating:n.working:T?.text??(!i||D||e.selectedUid?"":n.selectHint);p.textContent=b,p.hidden=!b,p.className=`status${e.prompting?" working":""}${!a&&T?.error?" error":""}`},q=()=>B(t.getState()),H=()=>{u.style.height="auto",u.style.height=`${u.scrollHeight}px`},Y=async()=>{let e=u.value.trim();if(e&&!t.getState().prompting){T=null;try{if(D){let r=N.value.trim();if(!r)return N.focus();await t.createPage(r,e),T={text:n.created,error:!1}}else{let{summary:r}=await t.prompt(e);T={text:r,error:!1}}u.value="",H()}catch(e){T={text:e instanceof Error?e.message:String(e),error:!0}}q(),u.focus()}},_=e=>{z=e,q(),i&&t.setSelecting(z&&!D)},V=e=>{D=e,T=null,t.setSelecting(z&&!D),D&&t.select(null),q(),(D?N:u).focus()},W=async e=>{T=null;try{await t[e](),T={text:"save"===e?n.saved:n.published,error:!1}}catch(e){T={text:e instanceof Error?e.message:String(e),error:!0}}q()};l.addEventListener("click",()=>{_(!0),(D?N:u).focus()}),P.addEventListener("click",()=>_(!1)),O.addEventListener("click",()=>void W("save")),I.addEventListener("click",()=>void W("publish")),A.addEventListener("click",()=>t.signOut()),c.addEventListener("click",()=>t.signOut()),x.addEventListener("click",()=>t.select(null)),E.addEventListener("click",()=>V(!0)),S.addEventListener("click",()=>V(!1)),N.addEventListener("input",q),N.addEventListener("keydown",e=>{"Enter"!==e.key||e.isComposing?"Escape"===e.key&&_(!1):(e.preventDefault(),u.focus())}),k.addEventListener("click",()=>{T=null,t.undo()}),j.addEventListener("submit",e=>{e.preventDefault(),Y()}),u.addEventListener("input",()=>{H(),q()}),u.addEventListener("keydown",e=>{"Enter"!==e.key||e.shiftKey||e.isComposing?"Escape"===e.key&&_(!1):(e.preventDefault(),Y())});let F=t.on("change",B);return q(),e.body.appendChild(o),()=>{F(),o.remove()}},v="https://content.obelism.studio",y=(e,t)=>JSON.stringify(e)===JSON.stringify(t),x=Symbol.for("@obelism/content/bridge"),w=p({isEditing:!1,entryId:null,on:()=>{}},new URLSearchParams),k=({url:e,prompt:o=!0,pagePath:l}={})=>{let g;if("u"<typeof window)return w;let u=window;if(u[x])return u[x];if(u.ObelismBridge)g=p(u.ObelismBridge,new URLSearchParams(u.location.search));else{let p=(g=((e,r)=>{let o=new URLSearchParams(e.location.search),l=e.parent!==e&&o.has("cms_preview"),p=s(),g=[],u={...i,selecting:!0},h=e=>{u={...u,...e},p.emit("change",u)},b={isEditing:l,mode:l?"editor":null,entryId:o.get("cms_entry"),environment:o.get(n),on:p.on,getState:()=>u,...d,destroy:()=>{g.splice(0).forEach(e=>e())}};if(!l)return b;let m=e.document,f=(t,n={})=>{e.parent.postMessage({source:"obelism-content-bridge",type:t,...n},r)},v=m.createElement("style");v.textContent=a(),m.head.appendChild(v),g.push(()=>v.remove());let y=(e,t)=>{if(m.querySelectorAll("[data-cms-selected]").forEach(e=>{e.removeAttribute("data-cms-selected")}),u.selectedUid!==(e??null)&&h({selectedUid:e??null}),!e)return;let r=m.querySelector(c(e));r&&(r.setAttribute("data-cms-selected",""),t&&r.scrollIntoView({behavior:"smooth",block:"center"}))},x=e=>{let r=e.target,n=r?.closest?.(`[${t}]`);if(!n)return;e.preventDefault(),e.stopPropagation();let o=n.getAttribute(t);y(o,!1),f("select",{uid:o})};m.addEventListener("click",x,!0),g.push(()=>m.removeEventListener("click",x,!0));let w=()=>f("ready",{url:e.location.href}),k=new Map,E=0,S=e=>{let t=k.get(e);return t?(clearTimeout(t.timer),k.delete(e),0===k.size&&h({prompting:!1}),t):null};b.prompt=(e,t={})=>{let r=e.trim();if(!r)return Promise.reject(Error("Say what to change"));if(!u.features.includes("prompt"))return Promise.reject(Error("The editor does not offer prompts here"));let n=`prompt_${++E}`,o=void 0===t.uid?u.selectedUid:t.uid;return new Promise((e,t)=>{let a=setTimeout(()=>{S(n)?.reject(Error("The editor did not answer in time"))},12e4);k.set(n,{resolve:e,reject:t,timer:a}),h({prompting:!0}),f("prompt",{id:n,prompt:r,uid:o})})},b.undo=()=>f("undo"),b.select=e=>{y(e,!1),f("select",{uid:e})},g.push(()=>{for(let e of k.keys())S(e)?.reject(Error("The bridge was destroyed"))});let C=t=>{if(t.origin!==r||t.source!==e.parent)return;let n=t.data;if(n&&"obelism-content-editor"===n.source){if("connect"===n.type)w();else if("update"===n.type&&n.entry)p.emit("update",{entry:n.entry,selectedUid:n.selectedUid??null}),e.requestAnimationFrame(()=>y(n.selectedUid,!1));else if("select"===n.type)y(n.uid,!0);else if("saved"===n.type||"published"===n.type)p.emit(n.type,{}),0===p.listeners.update.size&&e.location.reload();else if("features"===n.type&&Array.isArray(n.features))h({features:n.features.filter(e=>"string"==typeof e)});else if("promptState"===n.type)h({canUndo:!0===n.canUndo});else if("promptResult"===n.type&&"string"==typeof n.id){let e=S(n.id);if(!e)return;!0===n.ok?e.resolve({summary:"string"==typeof n.summary?n.summary:""}):e.reject(Error("string"==typeof n.error?n.error:"The prompt failed"))}}};return e.addEventListener("message",C),g.push(()=>e.removeEventListener("message",C)),"loading"===m.readyState?(m.addEventListener("DOMContentLoaded",w,{once:!0}),g.push(()=>m.removeEventListener("DOMContentLoaded",w))):w(),b})(u,new URL(e||v).origin)).isEditing||u.parent!==u?null:(e=>{for(let t of e.cookie.split(";")){let[e,...r]=t.trim().split("=");if("obelism_content_editing"===e)try{let e=decodeURIComponent(r.join("="));return e.startsWith("/")&&!e.startsWith("//")?e:null}catch{break}}return null})(u.document);if(p&&(g=((e,n,{pagePath:o=e=>`/${e}`}={})=>{let l=e.document,d=s(),p=[],g=new Map,u=[],h=new Set,b=null,m=i,f=e=>{m={...m,...e},d.emit("change",m)},v=()=>f({unsaved:[...g.values()].some(({content:e,saved:t})=>!y(e,t)),canUndo:u.length>0}),x=async(t,r)=>{let o=await e.fetch(`${n}/api/${t}`,void 0===r?{credentials:"same-origin"}:{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json","x-obelism-content":"1"},body:JSON.stringify(r)}),a=await o.json().catch(()=>({}));if(401===o.status)throw f({features:[]}),Error("You were signed out. Sign in again to edit.");if(!o.ok)throw Error("string"==typeof a.error?a.error:`Obelism Content answered ${o.status}`);return a},w=()=>l.querySelector(`[${r}]`)?.getAttribute(r)??null,k=()=>{if(!b)return;let e=b.space.locales.map(({code:e})=>e),t=l.documentElement.lang;if(e.includes(t))return t;let r=t.split("-")[0]??"";return e.includes(r)?r:b.space.defaultLocale},E=async e=>{let t=g.get(e);if(t)return t;let r=await x(`entries/${encodeURIComponent(e)}`),n={content:r.content,saved:r.content,revision:r.draftRevision};return g.set(e,n),n},S=e=>{l.querySelectorAll("[data-cms-selected]").forEach(e=>{e.removeAttribute("data-cms-selected")}),m.selectedUid!==e&&f({selectedUid:e}),e&&l.querySelector(c(e))?.setAttribute("data-cms-selected","")},C=async t=>{if(0===d.listeners.update.size)return;let r=g.get(t)?.content,{entry:n}=await x(`entries/${encodeURIComponent(t)}/preview`,{content:r,locale:k()});d.emit("update",{entry:n,selectedUid:m.selectedUid}),e.requestAnimationFrame(()=>S(m.selectedUid))},N=t=>{d.emit(t,{}),0===d.listeners.update.size&&e.location.reload()},U=async()=>{for(let[e,t]of g){if(y(t.content,t.saved))continue;let{draftRevision:r}=await x(`entries/${encodeURIComponent(e)}/save`,{content:t.content,baseRevision:t.revision});t.saved=t.content,t.revision=r,h.add(e)}u.length=0,v()},P={isEditing:!0,mode:"site",get entryId(){return w()},environment:null,on:d.on,getState:()=>m,async prompt(e,t={}){let n,o=e.trim();if(!o)throw Error("Say what to change");if(!m.features.includes("prompt"))throw Error("You may not edit content here");let a=void 0===t.uid?m.selectedUid:t.uid,i=(n=a?l.querySelector(c(a)):null,n?.closest(`[${r}]`)?.getAttribute(r)??w());if(!i)throw Error("Nothing on this page is marked as an entry: add editable(entry)");f({prompting:!0});try{let e=await E(i),t=await x(`entries/${encodeURIComponent(i)}/prompt`,{prompt:o,content:e.content,locale:k(),uid:a});return u.push({entryId:i,content:e.content}),e.content=t.content,v(),await C(i),{summary:t.summary}}finally{f({prompting:!1})}},async createPage(t,r){let n=r.trim();if(!n)throw Error("Say what the page is for");if(!t.trim())throw Error("Say where the page goes, like /pricing");if(!m.features.includes("create"))throw Error("You may not add pages here");if(m.unsaved)throw Error("Save or undo your changes before adding a page");f({prompting:!0});try{let r=await x("entries",{path:t,prompt:n,locale:k(),from:w()}),a=o(r.path);return e.location.assign(a),{summary:r.summary,entryId:r.entryId,path:r.path,url:a}}finally{f({prompting:!1})}},undo(){let e=u.pop(),t=e&&g.get(e.entryId);e&&t&&(t.content=e.content,v(),C(e.entryId).catch(e=>console.error("[@obelism/content bridge]",e)))},async save(){if(!m.features.includes("save"))throw Error("You may not edit content here");f({saving:!0});try{await U()}finally{f({saving:!1})}N("saved")},async publish(){if(!m.features.includes("publish"))throw Error("You may not publish here");f({publishing:!0});try{await U();let e=w(),t=h.size>0?[...h]:e?[e]:[];if(0===t.length)throw Error("Nothing on this page is marked as an entry: add editable(entry)");for(let e of t)await x(`entries/${encodeURIComponent(e)}/publish`,{}),h.delete(e)}finally{f({publishing:!1})}N("published")},signOut(){if(m.unsaved&&!e.confirm("Sign out and lose the changes you have not saved?"))return;g.clear(),v();let t=e.location.pathname+e.location.search;e.location.href=`${n}/logout?return_to=${encodeURIComponent(t)}`},select(e){S(e)},setSelecting(e){e?l.documentElement.setAttribute("data-cms-selecting",""):l.documentElement.removeAttribute("data-cms-selecting"),f({selecting:e})},destroy(){p.splice(0).forEach(e=>e())}},L=l.createElement("style");L.textContent=a("html[data-cms-selecting] "),l.head.appendChild(L),p.push(()=>L.remove()),p.push(()=>l.documentElement.removeAttribute("data-cms-selecting"));let $=e=>{if(!m.selecting)return;let r=e.target,n=r?.closest?.(`[${t}]`);n&&(e.preventDefault(),e.stopPropagation(),S(n.getAttribute(t)))};l.addEventListener("click",$,!0),p.push(()=>l.removeEventListener("click",$,!0));let A=e=>{m.unsaved&&(e.preventDefault(),e.returnValue="")};return e.addEventListener("beforeunload",A),p.push(()=>e.removeEventListener("beforeunload",A)),x("session").then(e=>{b=e,f({features:e.features})}).catch(e=>console.error("[@obelism/content bridge]",e)),P})(u,p,{pagePath:l})),g.isEditing&&o){let e=f(u.document,g,!0===o?{}:o),t=g.destroy;g.destroy=()=>{e(),t()}}}return u[x]=g,g};(e=window).ObelismBridge=k((e=>{let t=e?.dataset.url,r=t||v;if(!t&&e?.src){let t=new URL(e.src);"/bridge.js"===t.pathname&&(r=t.origin)}return{url:r,prompt:e?.dataset.prompt!=="false"}})(document.currentScript)),e.ObelismContent={getContentBridge:k,mountPromptBox:f}}();
|
|
182
|
+
`,x=(e,t,r={},...n)=>{let o=e.createElement(t);for(let t of(Object.assign(o,r),n))o.appendChild("string"==typeof t?e.createTextNode(t):t);return o},w=(e,t)=>{let r=x(e,"span",{className:"icon"});return r.setAttribute("aria-hidden","true"),r.innerHTML=`<svg viewBox="0 0 24 24">${v[t]}</svg>`,r},k=(e,t,r={})=>{let n={...f,...r.labels},o=x(e,"div");o.setAttribute("data-obelism-content-prompt","");let a=o.attachShadow({mode:"open"}),i="site"===t.mode,s=x(e,"span",{textContent:n.open}),l=x(e,"button",{className:"pill",type:"button"},s,x(e,"span",{className:"mark",textContent:"⁜"})),d=x(e,"span",{className:"pill compact"},`${n.viewingDrafts} \xb7 `),c=x(e,"button",{className:"link",type:"button",textContent:n.signOut});d.appendChild(c);let p=x(e,"p",{className:"status"});p.setAttribute("role","status");let g=x(e,"textarea",{placeholder:n.placeholder,rows:1});g.setAttribute("aria-label",n.open);let u=w(e,"page"),h=w(e,"block"),b=x(e,"span"),m=x(e,"button",{type:"button",title:n.clearBlock});m.setAttribute("aria-label",n.clearBlock),m.appendChild(w(e,"close"));let v=x(e,"span",{className:"chip scope"},u,h,b,m),k=x(e,"button",{className:"action chip",type:"button"},w(e,"undo"),n.undo),E=x(e,"button",{className:"action chip",type:"button"},w(e,"newPage"),n.newPage),S=x(e,"button",{type:"button",title:n.newPageCancel});S.setAttribute("aria-label",n.newPageCancel),S.appendChild(w(e,"close"));let C=x(e,"span",{className:"chip scope"},w(e,"newPage"),x(e,"span",{textContent:n.newPage}),S),N=x(e,"input",{type:"text",placeholder:n.pathPlaceholder,autocomplete:"off",spellcheck:!1}),L=x(e,"label",{className:"path"},x(e,"span",{textContent:n.path}),N),U=x(e,"button",{className:"close icon-button",type:"button",title:n.close},w(e,"close"));U.setAttribute("aria-label",n.close);let P=x(e,"button",{className:"action primary icon-button",type:"submit"},x(e,"span",{className:"visually-hidden",textContent:n.send}),w(e,"send"));P.title=n.send;let $=x(e,"span",{className:"unsaved"});$.appendChild(x(e,"span",{className:"dot"})),$.appendChild(e.createTextNode(n.unsaved));let A=x(e,"button",{className:"link",type:"button",textContent:n.signOut}),O=x(e,"button",{className:"action chip",type:"button",textContent:n.save}),I=x(e,"button",{className:"action chip",type:"button",textContent:n.publish}),M=x(e,"div",{className:"footer"},$,x(e,"span",{className:"spacer"}),A,O,I),j=x(e,"form",{className:"panel"},p,L,g,x(e,"div",{className:"toolbar"},v,C,k,E,x(e,"span",{className:"spacer"}),U,x(e,"span",{className:"divider"}),P),M),z=x(e,"div",{className:"root"},x(e,"div",{className:"bar"},l,d),j);a.append(x(e,"style",{textContent:y}),z);let R=!1,B=!1,D=null,T=e=>{let r=e.features.includes("prompt"),o=e.saving||e.publishing,a=e.prompting||o;z.hidden=!r&&!i,l.hidden=R||!r,d.hidden=r||!i,j.hidden=!R||!r;let c=i&&e.features.includes("create"),f=i&&null===t.entryId,y=f&&c;c||(B=!1),y&&(B=!0),s.textContent=y?n.newPage:n.open,S.hidden=y,v.hidden=B,C.hidden=!B,E.hidden=B||!c,E.disabled=a,L.hidden=!B,N.disabled=a,g.placeholder=B?n.newPagePlaceholder:n.placeholder,b.textContent=e.selectedUid?n.scopeBlock:n.scopeEntry,m.hidden=!e.selectedUid,u.hidden=!!e.selectedUid,h.hidden=!e.selectedUid,k.hidden=B||!e.canUndo,k.disabled=a,P.disabled=a||""===g.value.trim()||B&&""===N.value.trim(),g.disabled=a,O.hidden=!e.features.includes("save")||f,O.disabled=a||!e.unsaved,O.textContent=e.saving?n.saving:n.save,I.hidden=!e.features.includes("publish")||f,I.disabled=a,I.textContent=e.publishing?n.publishing:n.publish,A.hidden=!i,M.hidden=!i,$.hidden=!e.unsaved;let x=e.prompting?B?n.creating:n.working:D?.text??(!i||B||e.selectedUid?"":n.selectHint);p.textContent=x,p.hidden=!x,p.className=`status${e.prompting?" working":""}${!a&&D?.error?" error":""}`},q=()=>T(t.getState()),V=()=>{g.style.height="auto",g.style.height=`${g.scrollHeight}px`},H=async()=>{let e=g.value.trim();if(e&&!t.getState().prompting){D=null;try{if(B){let r=N.value.trim();if(!r)return N.focus();await t.createPage(r,e),D={text:n.created,error:!1}}else{let{summary:r}=await t.prompt(e);D={text:r,error:!1}}g.value="",V()}catch(e){D={text:e instanceof Error?e.message:String(e),error:!0}}q(),g.focus()}},F=e=>{R=e,q(),i&&t.setSelecting(R&&!B)},Y=e=>{B=e,D=null,t.setSelecting(R&&!B),B&&t.select(null),q(),(B?N:g).focus()},_=async e=>{D=null;try{await t[e](),D={text:"save"===e?n.saved:n.published,error:!1}}catch(e){D={text:e instanceof Error?e.message:String(e),error:!0}}q()};l.addEventListener("click",()=>{F(!0),(B?N:g).focus()}),U.addEventListener("click",()=>F(!1)),O.addEventListener("click",()=>void _("save")),I.addEventListener("click",()=>void _("publish")),A.addEventListener("click",()=>t.signOut()),c.addEventListener("click",()=>t.signOut()),m.addEventListener("click",()=>t.select(null)),E.addEventListener("click",()=>Y(!0)),S.addEventListener("click",()=>Y(!1)),N.addEventListener("input",q),N.addEventListener("keydown",e=>{"Enter"!==e.key||e.isComposing?"Escape"===e.key&&F(!1):(e.preventDefault(),g.focus())}),k.addEventListener("click",()=>{D=null,t.undo()}),j.addEventListener("submit",e=>{e.preventDefault(),H()}),g.addEventListener("input",()=>{V(),q()}),g.addEventListener("keydown",e=>{"Enter"!==e.key||e.shiftKey||e.isComposing?"Escape"===e.key&&F(!1):(e.preventDefault(),H())});let W=t.on("change",T);return q(),e.body.appendChild(o),()=>{W(),o.remove()}},E="https://content.obelism.studio",S=(e,t)=>JSON.stringify(e)===JSON.stringify(t),C=Symbol.for("@obelism/content/bridge"),N=m({isEditing:!1,entryId:null,on:()=>{}},new URLSearchParams),L=({url:e,prompt:l=!0,pagePath:g}={})=>{let f;if("u"<typeof window)return N;let v=window;if(v[C])return v[C];if(v.ObelismBridge)f=m(v.ObelismBridge,new URLSearchParams(v.location.search));else{let m=(f=((e,r)=>{let l=new URLSearchParams(e.location.search),g=e.parent!==e&&l.has("cms_preview"),m=p(),f=[],v={...c,selecting:!0},y=e=>{v={...v,...e},m.emit("change",v)},x={isEditing:g,mode:g?"editor":null,entryId:l.get("cms_entry"),environment:l.get(n),on:m.on,getState:()=>v,...u,destroy:()=>{f.splice(0).forEach(e=>e())}};if(!g)return x;let w=e.document,k=(t,n={})=>{e.parent.postMessage({source:"obelism-content-bridge",type:t,...n},r)},E=w.createElement("style");E.textContent=d(),w.head.appendChild(E),f.push(()=>E.remove());let S=(e,t)=>{if(w.querySelectorAll("[data-cms-selected]").forEach(e=>{e.removeAttribute("data-cms-selected")}),v.selectedUid!==(e??null)&&y({selectedUid:e??null}),!e)return;let r=w.querySelector(b(e));r&&(r.setAttribute("data-cms-selected",""),t&&r.scrollIntoView({behavior:"smooth",block:"center"}))},C=e=>{let r=e.target,n=r?.closest?.(`[${t}]`);if(!n)return;e.preventDefault(),e.stopPropagation();let o=n.getAttribute(t);S(o,!1),k("select",{uid:o})};w.addEventListener("click",C,!0),f.push(()=>w.removeEventListener("click",C,!0));let N=()=>k("ready",{url:e.location.href}),L=new Map,U=0,P=e=>{let t=L.get(e);return t?(clearTimeout(t.timer),L.delete(e),0===L.size&&y({prompting:!1}),t):null};x.prompt=(e,t={})=>{let r=e.trim();if(!r)return Promise.reject(Error("Say what to change"));if(!v.features.includes("prompt"))return Promise.reject(Error("The editor does not offer prompts here"));let n=`prompt_${++U}`,o=void 0===t.uid?v.selectedUid:t.uid;return new Promise((e,t)=>{let a=setTimeout(()=>{P(n)?.reject(Error("The editor did not answer in time"))},12e4);L.set(n,{resolve:e,reject:t,timer:a}),y({prompting:!0}),k("prompt",{id:n,prompt:r,uid:o})})},x.undo=()=>k("undo"),x.select=e=>{S(e,!1),k("select",{uid:e})};let $=null;f.push(()=>$?.destroy()),f.push(()=>{for(let e of L.keys())P(e)?.reject(Error("The bridge was destroyed"))});let A=t=>{if(t.origin!==r||t.source!==e.parent)return;let n=t.data;if(n&&"obelism-content-editor"===n.source){if("connect"===n.type)N();else if("update"===n.type&&n.entry)m.emit("update",{entry:n.entry,selectedUid:n.selectedUid??null}),e.requestAnimationFrame(()=>S(n.selectedUid,!1)),$?.refresh();else if("select"===n.type)S(n.uid,!0);else if("saved"===n.type||"published"===n.type)m.emit(n.type,{}),0===m.listeners.update.size&&e.location.reload();else if("features"===n.type&&Array.isArray(n.features))y({features:n.features.filter(e=>"string"==typeof e)});else if("stats"===n.type)y({stats:(e=>{if(!e||"object"!=typeof e||Array.isArray(e))return{};let t={};for(let[r,n]of Object.entries(e)){let{clicks:e,clickers:o,viewers:a,rate:i}=n??{};h(e)&&h(o)&&h(a)&&h(i)&&(t[r]={clicks:e,clickers:o,viewers:a,rate:i})}return t})(n.stats),statsVisible:!1!==n.visible}),($||Object.keys(v.stats).length)&&($??($=((e,{selector:t,onSelect:r,labels:n})=>{let l=e.document,d={...o,...n},c=l.createElement("div");c.setAttribute("data-obelism-stats","");let p=c.attachShadow({mode:"open"}),g=l.createElement("style");g.textContent=i;let u=l.createElement("div");u.className="layer",p.append(g,u),l.body.appendChild(c);let h={},b=!1,m=null,f=()=>{if(m=null,u.replaceChildren(),u.hidden=!b,!b)return;let n=e.innerHeight;for(let[o,i]of Object.entries(h)){let c=l.querySelector(t(o));if(!c)continue;let p=c.getBoundingClientRect();if(!p.width&&!p.height||p.bottom<0||p.top>n)continue;let g=l.createElement("button");g.type="button",g.className="badge",g.dataset.uid=o,g.textContent=a(d.clicked,{rate:Math.round(100*i.rate)}),g.title=a(d.title,{clickers:i.clickers,viewers:i.viewers,clicks:i.clicks});let{place:h,top:b,left:m}=s(p,e.innerWidth);g.dataset.place=h,g.style.top=`${b}px`,g.style.left=`${m}px`,g.addEventListener("click",()=>r(o)),u.appendChild(g)}},v=()=>{null===m&&b&&(m=e.requestAnimationFrame(f))};e.addEventListener("scroll",v,{capture:!0,passive:!0}),e.addEventListener("resize",v);let y=e.MutationObserver,x="function"==typeof y?new y(v):null;return x?.observe(l.body,{childList:!0,subtree:!0}),{update(t,r){h=t,b=r,null!==m&&(e.cancelAnimationFrame?.(m),m=null),f()},refresh:v,destroy(){null!==m&&e.cancelAnimationFrame?.(m),e.removeEventListener("scroll",v,{capture:!0}),e.removeEventListener("resize",v),x?.disconnect(),c.remove()}}})(e,{selector:b,onSelect:e=>x.select(e)})),$.update(v.stats,v.statsVisible));else if("promptState"===n.type)y({canUndo:!0===n.canUndo});else if("promptResult"===n.type&&"string"==typeof n.id){let e=P(n.id);if(!e)return;!0===n.ok?e.resolve({summary:"string"==typeof n.summary?n.summary:""}):e.reject(Error("string"==typeof n.error?n.error:"The prompt failed"))}}};return e.addEventListener("message",A),f.push(()=>e.removeEventListener("message",A)),"loading"===w.readyState?(w.addEventListener("DOMContentLoaded",N,{once:!0}),f.push(()=>w.removeEventListener("DOMContentLoaded",N))):N(),x})(v,new URL(e||E).origin)).isEditing||v.parent!==v?null:(e=>{for(let t of e.cookie.split(";")){let[e,...r]=t.trim().split("=");if("obelism_content_editing"===e)try{let e=decodeURIComponent(r.join("="));return e.startsWith("/")&&!e.startsWith("//")?e:null}catch{break}}return null})(v.document);if(m&&(f=((e,n,{pagePath:o=e=>`/${e}`}={})=>{let a=e.document,i=p(),s=[],l=new Map,g=[],u=new Set,h=null,m=c,f=e=>{m={...m,...e},i.emit("change",m)},v=()=>f({unsaved:[...l.values()].some(({content:e,saved:t})=>!S(e,t)),canUndo:g.length>0}),y=async(t,r)=>{let o=await e.fetch(`${n}/api/${t}`,void 0===r?{credentials:"same-origin"}:{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json","x-obelism-content":"1"},body:JSON.stringify(r)}),a=await o.json().catch(()=>({}));if(401===o.status)throw f({features:[]}),Error("You were signed out. Sign in again to edit.");if(!o.ok)throw Error("string"==typeof a.error?a.error:`Obelism Content answered ${o.status}`);return a},x=()=>a.querySelector(`[${r}]`)?.getAttribute(r)??null,w=()=>{if(!h)return;let e=h.space.locales.map(({code:e})=>e),t=a.documentElement.lang;if(e.includes(t))return t;let r=t.split("-")[0]??"";return e.includes(r)?r:h.space.defaultLocale},k=async e=>{let t=l.get(e);if(t)return t;let r=await y(`entries/${encodeURIComponent(e)}`),n={content:r.content,saved:r.content,revision:r.draftRevision};return l.set(e,n),n},E=e=>{a.querySelectorAll("[data-cms-selected]").forEach(e=>{e.removeAttribute("data-cms-selected")}),m.selectedUid!==e&&f({selectedUid:e}),e&&a.querySelector(b(e))?.setAttribute("data-cms-selected","")},C=async t=>{if(0===i.listeners.update.size)return;let r=l.get(t)?.content,{entry:n}=await y(`entries/${encodeURIComponent(t)}/preview`,{content:r,locale:w()});i.emit("update",{entry:n,selectedUid:m.selectedUid}),e.requestAnimationFrame(()=>E(m.selectedUid))},N=t=>{i.emit(t,{}),0===i.listeners.update.size&&e.location.reload()},L=async()=>{for(let[e,t]of l){if(S(t.content,t.saved))continue;let{draftRevision:r}=await y(`entries/${encodeURIComponent(e)}/save`,{content:t.content,baseRevision:t.revision});t.saved=t.content,t.revision=r,u.add(e)}g.length=0,v()},U={isEditing:!0,mode:"site",get entryId(){return x()},environment:null,on:i.on,getState:()=>m,async prompt(e,t={}){let n,o=e.trim();if(!o)throw Error("Say what to change");if(!m.features.includes("prompt"))throw Error("You may not edit content here");let i=void 0===t.uid?m.selectedUid:t.uid,s=(n=i?a.querySelector(b(i)):null,n?.closest(`[${r}]`)?.getAttribute(r)??x());if(!s)throw Error("Nothing on this page is marked as an entry: add editable(entry)");f({prompting:!0});try{let e=await k(s),t=await y(`entries/${encodeURIComponent(s)}/prompt`,{prompt:o,content:e.content,locale:w(),uid:i});return g.push({entryId:s,content:e.content}),e.content=t.content,v(),await C(s),{summary:t.summary}}finally{f({prompting:!1})}},async createPage(t,r){let n=r.trim();if(!n)throw Error("Say what the page is for");if(!t.trim())throw Error("Say where the page goes, like /pricing");if(!m.features.includes("create"))throw Error("You may not add pages here");if(m.unsaved)throw Error("Save or undo your changes before adding a page");f({prompting:!0});try{let r=await y("entries",{path:t,prompt:n,locale:w(),from:x()}),a=o(r.path);return e.location.assign(a),{summary:r.summary,entryId:r.entryId,path:r.path,url:a}}finally{f({prompting:!1})}},undo(){let e=g.pop(),t=e&&l.get(e.entryId);e&&t&&(t.content=e.content,v(),C(e.entryId).catch(e=>console.error("[@obelism/content bridge]",e)))},async save(){if(!m.features.includes("save"))throw Error("You may not edit content here");f({saving:!0});try{await L()}finally{f({saving:!1})}N("saved")},async publish(){if(!m.features.includes("publish"))throw Error("You may not publish here");f({publishing:!0});try{await L();let e=x(),t=u.size>0?[...u]:e?[e]:[];if(0===t.length)throw Error("Nothing on this page is marked as an entry: add editable(entry)");for(let e of t)await y(`entries/${encodeURIComponent(e)}/publish`,{}),u.delete(e)}finally{f({publishing:!1})}N("published")},signOut(){if(m.unsaved&&!e.confirm("Sign out and lose the changes you have not saved?"))return;l.clear(),v();let t=e.location.pathname+e.location.search;e.location.href=`${n}/logout?return_to=${encodeURIComponent(t)}`},select(e){E(e)},setSelecting(e){e?a.documentElement.setAttribute("data-cms-selecting",""):a.documentElement.removeAttribute("data-cms-selecting"),f({selecting:e})},destroy(){s.splice(0).forEach(e=>e())}},P=a.createElement("style");P.textContent=d("html[data-cms-selecting] "),a.head.appendChild(P),s.push(()=>P.remove()),s.push(()=>a.documentElement.removeAttribute("data-cms-selecting"));let $=e=>{if(!m.selecting)return;let r=e.target,n=r?.closest?.(`[${t}]`);n&&(e.preventDefault(),e.stopPropagation(),E(n.getAttribute(t)))};a.addEventListener("click",$,!0),s.push(()=>a.removeEventListener("click",$,!0));let A=e=>{m.unsaved&&(e.preventDefault(),e.returnValue="")};return e.addEventListener("beforeunload",A),s.push(()=>e.removeEventListener("beforeunload",A)),y("session").then(e=>{h=e,f({features:e.features})}).catch(e=>console.error("[@obelism/content bridge]",e)),U})(v,m,{pagePath:g})),f.isEditing&&l){let e=k(v.document,f,!0===l?{}:l),t=f.destroy;f.destroy=()=>{e(),t()}}}return v[C]=f,f};(e=window).ObelismBridge=L((e=>{let t=e?.dataset.url,r=t||E;if(!t&&e?.src){let t=new URL(e.src);"/bridge.js"===t.pathname&&(r=t.origin)}return{url:r,prompt:e?.dataset.prompt!=="false"}})(document.currentScript)),e.ObelismContent={getContentBridge:L,mountPromptBox:k}}();
|