@obelism/content 0.2.0 → 0.4.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 CHANGED
@@ -36,12 +36,31 @@ with generics: `content.getPage<Entry<{ title: string }>>('home')`.
36
36
 
37
37
  ## Preview
38
38
 
39
- The visual editor loads your site with `?cms_preview=1&cms_entry=…&cms_environment=…`.
39
+ The visual editor loads your site with
40
+ `?cms_preview=1&cms_entry=…&cms_environment=…&cms_token=…`. `cms_token` is
41
+ made for the person editing and reads drafts for 8 hours; read the page with
42
+ it, server-side. Never show drafts because `cms_preview` is there: anyone can
43
+ add it to a URL.
40
44
 
41
45
  ```ts
42
- import { readPreviewParams } from '@obelism/content'
43
-
44
- const client = readPreviewParams(request.url) ? previewClient : deliveryClient
46
+ import {
47
+ ContentApiError,
48
+ ContentClient,
49
+ readPreviewParams,
50
+ } from '@obelism/content'
51
+
52
+ const preview = readPreviewParams(request.url)
53
+ const content = preview?.token
54
+ ? new ContentClient({ token: preview.token, init: { cache: 'no-store' } })
55
+ : deliveryClient
56
+
57
+ const page = await content.getPage(path).catch((error) => {
58
+ // A made-up or expired token: show what's published.
59
+ if (error instanceof ContentApiError && error.status === 401) {
60
+ return deliveryClient.getPage(path)
61
+ }
62
+ throw error
63
+ })
45
64
  ```
46
65
 
47
66
  ## Visual editor bridge
@@ -55,12 +74,35 @@ bridge.on('update', ({ entry }) => render(entry)) // unsaved edits, as typed
55
74
 
56
75
  Mark blocks so editors can click them: `<section {...editable(block)}>`
57
76
  (adds `data-cms-uid`). Without an `update` listener the page reloads after
58
- each save. It replaces the `<script src=".../bridge.js">` tag; when a page has
59
- both, it wraps the script's bridge instead of starting a second one. React
60
- sites use `@obelism/content-react`.
77
+ each save. React sites use `@obelism/content-react`.
78
+
79
+ Without a bundler, the same bridge (prompt box included) is one script tag:
80
+
81
+ ```html
82
+ <script src="https://content.obelism.studio/bridge.js"></script>
83
+ <script>
84
+ ObelismBridge.on('update', ({ entry }) => render(entry))
85
+ </script>
86
+ ```
87
+
88
+ The CMS serves this package's `dist/bridge.global.js` there; npm CDNs serve
89
+ it too (then add `data-url` if your CMS isn't content.obelism.studio).
90
+ `data-prompt="false"` leaves the prompt box out. The tag puts the bridge on
91
+ `window.ObelismBridge` and `getContentBridge` / `mountPromptBox` on
92
+ `window.ObelismContent`; a bundled copy on the same page shares its bridge.
61
93
 
62
94
  ## Prompt editing
63
95
 
96
+ The prompt box is plain DOM in a shadow root, no framework. Start the
97
+ bridge with `prompt: false` to leave it out, and mount it yourself later:
98
+
99
+ ```ts
100
+ import { getContentBridge, mountPromptBox } from '@obelism/content/bridge'
101
+
102
+ const bridge = getContentBridge({ prompt: false })
103
+ const unmount = mountPromptBox(document, bridge, { labels: { open: 'Ask' } })
104
+ ```
105
+
64
106
  Inside the visual editor, people who may edit get a prompt box at the bottom
65
107
  of the page: they type what to change and it shows on the page right away,
66
108
  unsaved, with undo. It acts on the selected block, or the whole entry. The
@@ -109,12 +151,50 @@ export const POST = createPromptHandler({
109
151
  - `richTextToHtml(doc, { renderEntry?, renderAsset? })`, `richTextToPlainText(doc)`
110
152
  - `imageUrl(asset, { w, h, fit, format, q })`, `imageSrcSet(asset, [480, 960])`
111
153
 
154
+ ## A/B tests and feature flags
155
+
156
+ Any blocks field can hold the built-in improve block (`ImproveBlock`,
157
+ `_type: '_improve'`): one list of blocks per option of an improve test or
158
+ flag. Ask improve's SDK for the visitor's option and render that variant:
159
+
160
+ ```ts
161
+ import { isImproveBlock, pickImproveVariant } from '@obelism/content'
162
+
163
+ // `improve` is improve's SDK (@obelism/improve-sdk), set up for the visitor.
164
+ const blocks = layout.flatMap((block) => {
165
+ if (!isImproveBlock(block)) return [block]
166
+ const option =
167
+ block.kind === 'flag'
168
+ ? improve.getFlagValue(block.slug)
169
+ : improve.getTestValue(block.slug)
170
+ return pickImproveVariant(block, option)
171
+ })
172
+ ```
173
+
174
+ `pickImproveVariant(block, option, fallback?)` falls back to `fallback`'s
175
+ variant, else the first one, when the option is `null` (the test isn't
176
+ active or the visitor isn't in it) or has no variant.
177
+
112
178
  ## CLI
113
179
 
114
- Keeps a content model file in Git in step with an environment.
180
+ Start a site's repository on a space, in its root:
181
+
182
+ ```bash
183
+ export CONTENT_MANAGEMENT_TOKEN=cms_m_… # a management token, Space settings
184
+ npx @obelism/content init
185
+ ```
186
+
187
+ It writes the model file (`content/content-model.json`), an example of what
188
+ the delivery API answers for every content type (`content/examples/`, with
189
+ the A/B test or flag block while your organization has improve on),
190
+ installs `@obelism/content` with your package manager, and adds the token variables to
191
+ `.env.example`. Options: `--dir` (default `content`), `--url`,
192
+ `--skip-install`. Agents get the same steps from the CMS's
193
+ `getProjectSetup` MCP tool.
194
+
195
+ Then keep the model file in Git in step with an environment:
115
196
 
116
197
  ```bash
117
- export CONTENT_MANAGEMENT_TOKEN=cms_m_…
118
198
  npx @obelism/content model pull # write content/content-model.json
119
199
  npx @obelism/content model plan # what applying would change
120
200
  npx @obelism/content model apply # apply it, all or nothing
@@ -1,9 +1,190 @@
1
1
  #!/usr/bin/env node
2
2
  var node_util = require('node:util');
3
3
  var config = require('../config-ci2hxmas.js');
4
+ var node_child_process = require('node:child_process');
4
5
  var promises = require('node:fs/promises');
5
6
  var node_path = require('node:path');
6
7
 
8
+ // Blocks inside blocks stop here, so a type that allows itself ends.
9
+ const MAX_DEPTH = 3;
10
+ const EXAMPLE_DATE = '2026-01-01';
11
+ const EXAMPLE_DATE_TIME = '2026-01-01T09:00:00.000Z';
12
+ // An image unless the field only takes other files; then its first exact
13
+ // type, or a PDF.
14
+ const exampleAsset = (mimeTypes)=>{
15
+ const image = !mimeTypes || mimeTypes.some((type)=>type.startsWith('image/'));
16
+ const exact = mimeTypes?.find((type)=>/^[a-z]+\/[^*]+$/.test(type));
17
+ const mimeType = image ? 'image/jpeg' : exact ?? 'application/pdf';
18
+ const filename = image ? 'example.jpg' : `example.${mimeType.split('/')[1]?.split('+')[0] ?? 'bin'}`;
19
+ return {
20
+ id: 'asset_example',
21
+ url: `https://content.obelism.studio/files/asset_example/${filename}`,
22
+ filename,
23
+ mimeType,
24
+ size: 120_000,
25
+ width: image ? 1600 : null,
26
+ height: image ? 900 : null,
27
+ title: null,
28
+ alt: image ? 'An example image' : null
29
+ };
30
+ };
31
+ const exampleRichText = (name)=>({
32
+ type: 'doc',
33
+ content: [
34
+ {
35
+ type: 'paragraph',
36
+ content: [
37
+ {
38
+ type: 'text',
39
+ text: `Example ${name}, with `
40
+ },
41
+ {
42
+ type: 'text',
43
+ text: 'bold',
44
+ marks: [
45
+ {
46
+ type: 'bold'
47
+ }
48
+ ]
49
+ },
50
+ {
51
+ type: 'text',
52
+ text: ' text.'
53
+ }
54
+ ]
55
+ }
56
+ ]
57
+ });
58
+ const truncate = (text, maxLength)=>maxLength === undefined ? text : text.slice(0, maxLength);
59
+ const buildExamples = (model, improve)=>{
60
+ const types = model.contentTypes;
61
+ const blockTypes = types.filter(({ kind })=>kind === 'block');
62
+ const entryTypes = types.filter(({ kind })=>kind !== 'block');
63
+ // Uids are unique within one example, like within one entry.
64
+ let next = 0;
65
+ const uid = ()=>`example${++next}`;
66
+ const allowedBlocks = (allowed)=>allowed ? blockTypes.filter(({ apiId })=>allowed.includes(apiId)) : blockTypes;
67
+ const improveBlock = (allowed, depth)=>{
68
+ const source = improve.sources[0] ?? {
69
+ kind: 'test',
70
+ slug: 'example-test',
71
+ options: [
72
+ {
73
+ slug: 'control',
74
+ name: 'Control'
75
+ },
76
+ {
77
+ slug: 'variation',
78
+ name: 'Variation'
79
+ }
80
+ ]
81
+ };
82
+ const first = allowedBlocks(allowed)[0];
83
+ return {
84
+ _uid: uid(),
85
+ _type: '_improve',
86
+ kind: source.kind,
87
+ slug: source.slug,
88
+ variants: Object.fromEntries(source.options.map((option)=>[
89
+ option.slug,
90
+ first && depth < MAX_DEPTH ? [
91
+ block(first, depth + 1)
92
+ ] : []
93
+ ]))
94
+ };
95
+ };
96
+ const value = (field, depth)=>{
97
+ const name = field.name.toLowerCase();
98
+ switch(field.type){
99
+ case 'text':
100
+ return truncate(`Example ${name}`, field.maxLength);
101
+ case 'textarea':
102
+ return truncate(`Example ${name}. A sentence or two of plain text.`, field.maxLength);
103
+ case 'richtext':
104
+ return exampleRichText(name);
105
+ case 'number':
106
+ return field.min ?? (field.integer ? 1 : 1.5);
107
+ case 'boolean':
108
+ return true;
109
+ case 'datetime':
110
+ return field.withTime ? EXAMPLE_DATE_TIME : EXAMPLE_DATE;
111
+ case 'select':
112
+ {
113
+ const first = field.options[0]?.value ?? null;
114
+ return field.multiple ? first ? [
115
+ first
116
+ ] : [] : first;
117
+ }
118
+ case 'asset':
119
+ {
120
+ const asset = exampleAsset(field.mimeTypes);
121
+ return field.multiple ? [
122
+ asset
123
+ ] : asset;
124
+ }
125
+ case 'reference':
126
+ {
127
+ const target = field.contentTypes?.[0] ?? entryTypes[0]?.apiId ?? 'page';
128
+ const stub = {
129
+ id: 'entry_example',
130
+ contentType: target
131
+ };
132
+ return field.multiple ? [
133
+ stub
134
+ ] : stub;
135
+ }
136
+ case 'blocks':
137
+ {
138
+ if (depth >= MAX_DEPTH) return [];
139
+ const allowed = allowedBlocks(field.allowedBlocks);
140
+ // At the top, one of every block the field allows (and the
141
+ // improve block); deeper, only the first.
142
+ const blocks = depth === 0 ? allowed.map((type)=>block(type, depth + 1)) : allowed.slice(0, 1).map((type)=>block(type, depth + 1));
143
+ return depth === 0 && improve.enabled ? [
144
+ ...blocks,
145
+ improveBlock(field.allowedBlocks, depth + 1)
146
+ ] : blocks;
147
+ }
148
+ case 'json':
149
+ return {};
150
+ }
151
+ };
152
+ const fields = (type, depth)=>Object.fromEntries(type.fields.map((field)=>[
153
+ field.apiId,
154
+ value(field, depth)
155
+ ]));
156
+ const block = (type, depth)=>({
157
+ _uid: uid(),
158
+ _type: type.apiId,
159
+ ...fields(type, depth)
160
+ });
161
+ const examples = {};
162
+ for (const type of types){
163
+ next = 0;
164
+ if (type.kind === 'block') {
165
+ examples[type.apiId] = block(type, 0);
166
+ continue;
167
+ }
168
+ const slug = type.kind === 'page' ? 'example' : null;
169
+ examples[type.apiId] = {
170
+ id: 'entry_example',
171
+ contentType: type.apiId,
172
+ name: `Example ${type.name.toLowerCase()}`,
173
+ slug,
174
+ path: slug,
175
+ publishedAt: EXAMPLE_DATE_TIME,
176
+ firstPublishedAt: EXAMPLE_DATE_TIME,
177
+ updatedAt: EXAMPLE_DATE_TIME,
178
+ fields: fields(type, 0)
179
+ };
180
+ }
181
+ if (improve.enabled) {
182
+ next = 0;
183
+ examples._improve = improveBlock(undefined, 0);
184
+ }
185
+ return examples;
186
+ };
187
+
7
188
  // Turns a model plan from the management API into text for a terminal, or
8
189
  // Markdown for a GitHub step summary or pull request comment.
9
190
  const ACTION_SIGN = {
@@ -146,15 +327,205 @@ const runModelCommand = async ({ command, file, url, token, prune, log = console
146
327
  return !ok || (plan.issues?.length ?? 0) > 0 ? 1 : 0;
147
328
  };
148
329
 
149
- const USAGE = `Usage: content model <pull|plan|apply> [--file path] [--url origin] [--prune]
330
+ const PACKAGE_MANAGERS = [
331
+ {
332
+ lockfile: 'pnpm-lock.yaml',
333
+ command: 'pnpm',
334
+ add: [
335
+ 'add'
336
+ ]
337
+ },
338
+ {
339
+ lockfile: 'yarn.lock',
340
+ command: 'yarn',
341
+ add: [
342
+ 'add'
343
+ ]
344
+ },
345
+ {
346
+ lockfile: 'bun.lock',
347
+ command: 'bun',
348
+ add: [
349
+ 'add'
350
+ ]
351
+ },
352
+ {
353
+ lockfile: 'bun.lockb',
354
+ command: 'bun',
355
+ add: [
356
+ 'add'
357
+ ]
358
+ },
359
+ {
360
+ lockfile: 'package-lock.json',
361
+ command: 'npm',
362
+ add: [
363
+ 'install'
364
+ ]
365
+ }
366
+ ];
367
+ const ENV_VARIABLES = [
368
+ [
369
+ 'CONTENT_DELIVERY_TOKEN',
370
+ 'reads published content (cms_d_…)'
371
+ ],
372
+ [
373
+ 'CONTENT_PREVIEW_TOKEN',
374
+ 'also reads drafts, server-side only (cms_p_…)'
375
+ ],
376
+ [
377
+ 'CONTENT_MANAGEMENT_TOKEN',
378
+ 'changes the content model: content model pull|plan|apply (cms_m_…)'
379
+ ]
380
+ ];
381
+ const exists = (path)=>promises.access(path).then(()=>true, ()=>false);
382
+ const runCommand = (command, args, cwd)=>new Promise((resolve)=>{
383
+ const child = node_child_process.spawn(command, args, {
384
+ cwd,
385
+ stdio: 'inherit',
386
+ shell: process.platform === 'win32'
387
+ });
388
+ child.on('error', ()=>resolve(127));
389
+ child.on('close', (code)=>resolve(code ?? 1));
390
+ });
391
+ const writeJson = async (path, value)=>promises.writeFile(path, `${JSON.stringify(value, null, '\t')}\n`);
392
+ // Whether the organization has improve on, and its tests and flags. A CMS
393
+ // without the endpoint has no improve block.
394
+ const readImprove = async (url, token, fetchImpl)=>{
395
+ const response = await fetchImpl(`${url}${config.MANAGEMENT_PATH}/improve`, {
396
+ headers: {
397
+ Authorization: `Bearer ${token}`
398
+ }
399
+ }).catch((error)=>{
400
+ throw new CliError(`Could not reach ${url}: ${error.message}`);
401
+ });
402
+ if (response.status === 404) return {
403
+ enabled: false,
404
+ sources: []
405
+ };
406
+ const json = await response.json().catch(()=>({}));
407
+ if (!response.ok) {
408
+ throw new CliError(String(json.error ?? `${response.status} from ${url}`));
409
+ }
410
+ return {
411
+ enabled: json.enabled === true,
412
+ sources: json.sources ?? []
413
+ };
414
+ };
415
+ const install = async (cwd, log, run)=>{
416
+ const manifestPath = node_path.join(cwd, 'package.json');
417
+ let manifest;
418
+ try {
419
+ manifest = JSON.parse(await promises.readFile(manifestPath, 'utf8'));
420
+ } catch {
421
+ log('No package.json here, so nothing was installed: add @obelism/content to your project yourself.');
422
+ return;
423
+ }
424
+ const dependencies = {
425
+ ...manifest.dependencies,
426
+ ...manifest.devDependencies
427
+ };
428
+ const wanted = [
429
+ '@obelism/content'
430
+ ].filter((name)=>!dependencies[name]);
431
+ if (wanted.length === 0) {
432
+ log('The SDK is already installed.');
433
+ return;
434
+ }
435
+ let manager = PACKAGE_MANAGERS.at(-1);
436
+ for (const candidate of PACKAGE_MANAGERS){
437
+ if (await exists(node_path.join(cwd, candidate.lockfile))) {
438
+ manager = candidate;
439
+ break;
440
+ }
441
+ }
442
+ const args = [
443
+ ...manager.add,
444
+ ...wanted
445
+ ];
446
+ log(`Installing: ${manager.command} ${args.join(' ')}`);
447
+ const code = await run(manager.command, args, cwd);
448
+ if (code !== 0) {
449
+ throw new CliError(`${manager.command} ${args.join(' ')} failed (exit ${code}). The files are written; install the SDK yourself.`);
450
+ }
451
+ };
452
+ // Appends the variables .env.example doesn't have yet. Never values.
453
+ const writeEnvExample = async (cwd, url)=>{
454
+ const path = node_path.join(cwd, '.env.example');
455
+ const current = await promises.readFile(path, 'utf8').catch(()=>'');
456
+ const has = (name)=>new RegExp(`^\\s*${name}\\s*=`, 'm').test(current);
457
+ const lines = [
458
+ ...ENV_VARIABLES.filter(([name])=>!has(name)).flatMap(([name, about])=>[
459
+ `# ${about}`,
460
+ `${name}=`
461
+ ]),
462
+ ...url !== config.DEFAULT_URL && !has('CONTENT_URL') ? [
463
+ `CONTENT_URL=${url}`
464
+ ] : []
465
+ ];
466
+ if (lines.length === 0) return false;
467
+ const header = '# Obelism Content (written by `content init`)';
468
+ const separator = current === '' || current.endsWith('\n') ? '' : '\n';
469
+ await promises.writeFile(path, `${current}${separator}${current ? '\n' : ''}${header}\n${lines.join('\n')}\n`);
470
+ return true;
471
+ };
472
+ const runInitCommand = async ({ url, token, dir, install: shouldInstall, cwd = process.cwd(), log = console.log, fetch: fetchImpl = fetch, run = runCommand })=>{
473
+ const base = node_path.join(cwd, dir);
474
+ const modelFile = node_path.join(base, 'content-model.json');
475
+ await runModelCommand({
476
+ command: 'pull',
477
+ // Relative, so the log names it the way the user would.
478
+ file: node_path.relative(process.cwd(), modelFile) || modelFile,
479
+ url,
480
+ token,
481
+ prune: false,
482
+ log,
483
+ fetch: fetchImpl
484
+ });
485
+ const model = JSON.parse(await promises.readFile(modelFile, 'utf8'));
486
+ const improve = await readImprove(url, token, fetchImpl);
487
+ const examples = buildExamples(model, improve);
488
+ const examplesDir = node_path.join(base, 'examples');
489
+ await promises.mkdir(examplesDir, {
490
+ recursive: true
491
+ });
492
+ for (const [apiId, example] of Object.entries(examples)){
493
+ await writeJson(node_path.join(examplesDir, `${apiId}.json`), example);
494
+ }
495
+ const count = Object.keys(examples).length;
496
+ log(`Wrote ${count} ${count === 1 ? 'example' : 'examples'} to ${node_path.join(dir, 'examples')}${improve.enabled ? ', with the A/B test or flag block (_improve.json): improve is on' : ''}.`);
497
+ if (await writeEnvExample(cwd, url)) {
498
+ log('Added the token variables to .env.example.');
499
+ }
500
+ if (shouldInstall) await install(cwd, log, run);
501
+ log([
502
+ '',
503
+ 'Next:',
504
+ '- Create a delivery token in Space settings and set CONTENT_DELIVERY_TOKEN (a preview token only if the site builds from drafts; the visual editor brings its own, as cms_token).',
505
+ '- Fetch content with ContentClient from @obelism/content; the examples show what each type looks like.',
506
+ ...improve.enabled ? [
507
+ "- Render _improve blocks: ask improve's SDK for the visitor's option (getTestValue / getFlagValue with the block's slug) and render pickImproveVariant(block, option)."
508
+ ] : [],
509
+ `- Keep the model in step with \`content model pull|plan|apply\` (${node_path.join(dir, 'content-model.json')}).`
510
+ ].join('\n'));
511
+ return 0;
512
+ };
150
513
 
151
- pull write the environment's model to the file
152
- plan show what applying the file would change, and what stands in the way
153
- apply apply the file in one transaction, or change nothing
514
+ const USAGE = `Usage: content init [--dir content] [--url origin] [--skip-install]
515
+ content model <pull|plan|apply> [--file path] [--url origin] [--prune]
154
516
 
155
- --file the model file (default content/content-model.json)
156
- --url the CMS (default $CONTENT_URL, else ${config.DEFAULT_URL})
157
- --prune plan and apply also delete content types the file leaves out
517
+ init start a site's repository on the token's environment: the
518
+ model file, an example per content type, the SDK installed
519
+ and the token variables in .env.example
520
+ model pull write the environment's model to the file
521
+ model plan show what applying the file would change, and what stands in the way
522
+ model apply apply the file in one transaction, or change nothing
523
+
524
+ --dir where init writes content-model.json and examples/ (default content)
525
+ --skip-install init leaves dependencies alone
526
+ --file the model file (default content/content-model.json)
527
+ --url the CMS (default $CONTENT_URL, else ${config.DEFAULT_URL})
528
+ --prune plan and apply also delete content types the file leaves out
158
529
 
159
530
  Needs CONTENT_MANAGEMENT_TOKEN: a management token from Space settings.`;
160
531
  const main = async ()=>{
@@ -174,6 +545,14 @@ const main = async ()=>{
174
545
  type: 'boolean',
175
546
  default: false
176
547
  },
548
+ dir: {
549
+ type: 'string',
550
+ default: 'content'
551
+ },
552
+ 'skip-install': {
553
+ type: 'boolean',
554
+ default: false
555
+ },
177
556
  help: {
178
557
  type: 'boolean',
179
558
  short: 'h',
@@ -190,17 +569,26 @@ const main = async ()=>{
190
569
  console.log(USAGE);
191
570
  return 0;
192
571
  }
193
- if (group !== 'model' || command !== 'pull' && command !== 'plan' && command !== 'apply') {
194
- throw new CliError(USAGE, 2);
195
- }
572
+ const isInit = group === 'init' && command === undefined;
573
+ const modelCommand = group === 'model' && (command === 'pull' || command === 'plan' || command === 'apply') ? command : null;
574
+ if (!isInit && !modelCommand) throw new CliError(USAGE, 2);
196
575
  const token = process.env.CONTENT_MANAGEMENT_TOKEN;
197
576
  if (!token) {
198
577
  throw new CliError(`CONTENT_MANAGEMENT_TOKEN is not set.\n\n${USAGE}`, 2);
199
578
  }
579
+ const url = config.normalizeUrl(values.url ?? process.env.CONTENT_URL ?? config.DEFAULT_URL);
580
+ if (!modelCommand) {
581
+ return runInitCommand({
582
+ url,
583
+ token,
584
+ dir: values.dir,
585
+ install: !values['skip-install']
586
+ });
587
+ }
200
588
  return runModelCommand({
201
- command,
589
+ command: modelCommand,
202
590
  file: values.file,
203
- url: config.normalizeUrl(values.url ?? process.env.CONTENT_URL ?? config.DEFAULT_URL),
591
+ url,
204
592
  token,
205
593
  prune: values.prune,
206
594
  summaryFile: process.env.GITHUB_STEP_SUMMARY || undefined
package/dist/bridge.d.mts CHANGED
@@ -83,6 +83,7 @@ declare const DEFAULT_PROMPT_LABELS: PromptLabels;
83
83
  type PromptBoxOptions = {
84
84
  labels?: Partial<PromptLabels> | undefined;
85
85
  };
86
+ declare const mountPromptBox: (doc: Document, bridge: ContentBridge, options?: PromptBoxOptions) => () => void;
86
87
 
87
88
  type BridgeOptions = {
88
89
  url?: string | undefined;
@@ -92,6 +93,6 @@ type BridgeOptions = {
92
93
  declare const peekContentBridge: () => ContentBridge | null;
93
94
  declare const getContentBridge: ({ url, prompt, pagePath, }?: BridgeOptions) => ContentBridge;
94
95
 
95
- export { DEFAULT_PROMPT_LABELS, INITIAL_STATE, getContentBridge, peekContentBridge };
96
+ export { DEFAULT_PROMPT_LABELS, INITIAL_STATE, getContentBridge, mountPromptBox, peekContentBridge };
96
97
  export type { BridgeEvent, BridgeEvents, BridgeListener, BridgeOptions, BridgeState, ContentBridge, CreatePageResult, PromptBoxOptions, PromptLabels, PromptResult };
97
98
  //# sourceMappingURL=bridge.d.mts.map
@@ -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,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":[]}
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;AACA,cAAc,cAAc,QAAQ,QAAQ,UAAU,aAAa,YAAY,gBAAgB;;AC3B/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
@@ -83,6 +83,7 @@ declare const DEFAULT_PROMPT_LABELS: PromptLabels;
83
83
  type PromptBoxOptions = {
84
84
  labels?: Partial<PromptLabels> | undefined;
85
85
  };
86
+ declare const mountPromptBox: (doc: Document, bridge: ContentBridge, options?: PromptBoxOptions) => () => void;
86
87
 
87
88
  type BridgeOptions = {
88
89
  url?: string | undefined;
@@ -92,6 +93,6 @@ type BridgeOptions = {
92
93
  declare const peekContentBridge: () => ContentBridge | null;
93
94
  declare const getContentBridge: ({ url, prompt, pagePath, }?: BridgeOptions) => ContentBridge;
94
95
 
95
- export { DEFAULT_PROMPT_LABELS, INITIAL_STATE, getContentBridge, peekContentBridge };
96
+ export { DEFAULT_PROMPT_LABELS, INITIAL_STATE, getContentBridge, mountPromptBox, peekContentBridge };
96
97
  export type { BridgeEvent, BridgeEvents, BridgeListener, BridgeOptions, BridgeState, ContentBridge, CreatePageResult, PromptBoxOptions, PromptLabels, PromptResult };
97
98
  //# sourceMappingURL=bridge.d.ts.map
@@ -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,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":[]}
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;AACA,cAAc,cAAc,QAAQ,QAAQ,UAAU,aAAa,YAAY,gBAAgB;;AC3B/F,KAAK,aAAa;AAClB;AACA,uBAAuB,gBAAgB;AACvC;AACA;AACA,cAAc,iBAAiB,QAAQ,aAAa;AACpD,cAAc,gBAAgB,gCAAgC,aAAa,KAAK,aAAa","names":[]}
@@ -0,0 +1,158 @@
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="#d58a1f",a=(e="")=>`${e}[${t}]{cursor:pointer;transition:outline-color .15s}${e}[${t}]:hover{outline:2px dashed ${o};outline-offset:2px}${e}[${t}][data-cms-selected]{outline:2px solid ${o};outline-offset:2px}`,i={features:[],selectedUid:null,prompting:!1,canUndo:!1,unsaved:!1,saving:!1,publishing:!1,selecting:!1},s=()=>{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)}}}},l=()=>Promise.reject(Error("Save and publish in the visual editor itself")),d={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:l,publish:l,signOut:()=>{},select:()=>{},setSelecting:()=>{}},c=e=>`[${t}="${e.replace(/["\\]/g,"\\$&")}"]`,p=(e,t)=>{let r=s();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:()=>i,...d,destroy:()=>{}}},g={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…"},u={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"/>'},h=`
2
+ :host { all: initial; }
3
+ * { box-sizing: border-box; font: inherit; color: inherit; margin: 0; }
4
+ .root {
5
+ --color-white-rgb: 248 248 248; --color-beige-rgb: 238 236 233; --color-beige-off-rgb: 228 225 217;
6
+ --color-grey-light-rgb: 132 132 132; --color-grey-rgb: 108 108 108; --color-grey-mid-rgb: 100 100 100; --color-grey-dark-rgb: 40 40 40;
7
+ --color-black-off-rgb: 20 20 20; --color-black-rgb: 12 12 12;
8
+ --color-orange-rgb: 212 130 30; --color-red-rgb: 240 93 93;
9
+ --theme-foreground-rgb: var(--color-black-rgb);
10
+ --theme-background-alt-rgb: var(--color-white-rgb);
11
+ --theme-background-actions-rgb: var(--color-beige-off-rgb);
12
+ --theme-border-rgb: var(--color-grey-light-rgb);
13
+ --theme-accent-rgb: var(--color-orange-rgb);
14
+ --theme-foreground-muted-rgb: var(--color-grey-mid-rgb);
15
+ --theme-error: #b3261e;
16
+ --border-radius-medium: 20px; --border-radius-pill: 5em;
17
+ --ease-roll: cubic-bezier(0.65, 0, 0.35, 1);
18
+ --shadow: 0 18px 40px -16px rgb(var(--color-black-rgb) / .3), 0 2px 6px rgb(var(--color-black-rgb) / .05);
19
+ --font-inter: 'InterVariable', 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
20
+ Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
21
+ position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%);
22
+ z-index: 2147483647; width: min(640px, calc(100vw - 32px)); color-scheme: light dark;
23
+ font: 400 14px/1.4 var(--font-inter); color: rgb(var(--theme-foreground-rgb));
24
+ -webkit-font-smoothing: antialiased;
25
+ }
26
+ @media (prefers-color-scheme: dark) {
27
+ .root {
28
+ --theme-foreground-rgb: var(--color-white-rgb);
29
+ --theme-background-alt-rgb: var(--color-black-off-rgb);
30
+ --theme-background-actions-rgb: var(--color-grey-dark-rgb);
31
+ --theme-border-rgb: var(--color-grey-rgb);
32
+ --theme-foreground-muted-rgb: var(--color-grey-light-rgb);
33
+ --theme-error: rgb(var(--color-red-rgb));
34
+ --shadow: 0 18px 40px -14px rgb(0 0 0 / .7), 0 2px 6px rgb(0 0 0 / .3);
35
+ }
36
+ }
37
+ .icon { display: inline-flex; flex: none; }
38
+ .icon svg {
39
+ width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.75;
40
+ stroke-linecap: round; stroke-linejoin: round;
41
+ }
42
+ .visually-hidden {
43
+ position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
44
+ }
45
+ button { cursor: pointer; background: none; border: 0; padding: 0; }
46
+ button:disabled { cursor: not-allowed; }
47
+ button:focus-visible { outline: 2px solid rgb(var(--theme-accent-rgb)); outline-offset: 2px; }
48
+
49
+ /* Closed: one floating pill that reads like an empty field, with the mark. */
50
+ .bar { display: flex; justify-content: center; }
51
+ .pill {
52
+ display: flex; align-items: center; gap: 12px; width: min(560px, 100%); height: 56px;
53
+ padding: 0 22px 0 26px; border-radius: var(--border-radius-pill);
54
+ background: rgb(var(--theme-background-alt-rgb));
55
+ border: 1px solid rgb(var(--theme-border-rgb) / .25); box-shadow: var(--shadow);
56
+ color: rgb(var(--theme-foreground-muted-rgb)); font-size: 16px; text-align: left;
57
+ transition: transform .25s var(--ease-roll), color .25s var(--ease-roll);
58
+ }
59
+ button.pill:hover { color: rgb(var(--theme-foreground-rgb)); transform: translateY(-2px); }
60
+ .pill > span:first-child { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
61
+ .mark { color: rgb(var(--theme-accent-rgb)); font-size: 22px; line-height: 1; }
62
+ .pill.compact { width: auto; height: 40px; padding: 0 18px; font-size: 14px; gap: 4px; }
63
+
64
+ /* Open: a floating card, the text on top and a row of pill buttons below. */
65
+ .panel {
66
+ display: grid; background: rgb(var(--theme-background-alt-rgb));
67
+ border: 1px solid rgb(var(--theme-border-rgb) / .25); border-radius: var(--border-radius-medium);
68
+ box-shadow: var(--shadow); overflow: hidden;
69
+ transition: border-color .25s var(--ease-roll), box-shadow .25s var(--ease-roll);
70
+ animation: rise .25s var(--ease-roll);
71
+ }
72
+ .panel:focus-within {
73
+ border-color: rgb(var(--theme-accent-rgb) / .6);
74
+ box-shadow: 0 0 0 3px rgb(var(--theme-accent-rgb) / .18), var(--shadow);
75
+ }
76
+ @keyframes rise { from { opacity: 0; transform: translateY(8px) scale(.98); } }
77
+ @keyframes spin { to { transform: rotate(1turn); } }
78
+ @media (prefers-reduced-motion: reduce) {
79
+ .panel { animation: none; }
80
+ .pill { transition: none; }
81
+ }
82
+ .status {
83
+ padding: 16px 22px 0; font-size: 14px; color: rgb(var(--theme-foreground-muted-rgb));
84
+ display: flex; align-items: center; gap: 8px;
85
+ }
86
+ .status.working::before {
87
+ content: ''; width: 12px; height: 12px; flex: none; border-radius: 50%;
88
+ border: 2px solid rgb(var(--theme-border-rgb) / .3); border-top-color: rgb(var(--theme-accent-rgb));
89
+ animation: spin .7s linear infinite;
90
+ }
91
+ .status.error { color: var(--theme-error); }
92
+ textarea {
93
+ display: block; width: 100%; resize: none; min-height: 76px; max-height: 200px;
94
+ padding: 16px 22px 6px; border: 0; outline: 0; background: transparent;
95
+ font-size: 16px; line-height: 1.6; color: rgb(var(--theme-foreground-rgb));
96
+ }
97
+ textarea::placeholder { color: rgb(var(--theme-foreground-muted-rgb)); }
98
+ /* New page: where it goes, above what it is for. */
99
+ .path {
100
+ display: flex; align-items: center; gap: 10px; padding: 16px 22px 0;
101
+ color: rgb(var(--theme-foreground-muted-rgb));
102
+ }
103
+ .path input {
104
+ flex: 1; min-width: 0; height: 34px; padding: 0 14px; outline: 0;
105
+ border: 1px solid rgb(var(--theme-border-rgb) / .25); border-radius: var(--border-radius-pill);
106
+ background: rgb(var(--theme-background-actions-rgb) / .45); color: rgb(var(--theme-foreground-rgb));
107
+ transition: border-color .25s var(--ease-roll);
108
+ }
109
+ .path input:focus-visible { border-color: rgb(var(--theme-accent-rgb)); }
110
+ .path input::placeholder { color: rgb(var(--theme-foreground-muted-rgb)); }
111
+ .path input:disabled { opacity: .5; }
112
+ textarea:disabled { opacity: .5; }
113
+ .toolbar { display: flex; align-items: center; gap: 6px; padding: 8px 12px 12px; }
114
+ .spacer { flex: 1; }
115
+ .chip {
116
+ display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: .4em 1.1em .4em .9em;
117
+ border: 1px solid rgb(var(--theme-border-rgb) / .2); border-radius: var(--border-radius-pill);
118
+ background: rgb(var(--theme-background-actions-rgb) / .6); white-space: nowrap;
119
+ transition: background-color .25s var(--ease-roll);
120
+ }
121
+ button.chip:hover:not(:disabled) { background: rgb(var(--theme-background-actions-rgb)); }
122
+ button.chip:disabled { opacity: .5; }
123
+ .scope { min-width: 0; background: transparent; }
124
+ .scope > span:not(.icon) { overflow: hidden; text-overflow: ellipsis; }
125
+ .scope button {
126
+ display: inline-flex; margin-right: -6px; padding: 3px; border-radius: 50%;
127
+ color: rgb(var(--theme-foreground-muted-rgb));
128
+ }
129
+ .scope button:hover { background: rgb(var(--theme-background-actions-rgb)); color: rgb(var(--theme-foreground-rgb)); }
130
+ .scope button svg { width: 14px; height: 14px; }
131
+ .icon-button {
132
+ display: inline-flex; align-items: center; justify-content: center;
133
+ width: 34px; height: 34px; border-radius: 50%; color: rgb(var(--theme-foreground-muted-rgb));
134
+ transition: background-color .25s var(--ease-roll), color .25s var(--ease-roll), opacity .25s var(--ease-roll);
135
+ }
136
+ .icon-button:hover:not(:disabled) { background: rgb(var(--theme-background-actions-rgb)); color: rgb(var(--theme-foreground-rgb)); }
137
+ /* The one call to action: white on orange. */
138
+ .icon-button.primary { background: rgb(var(--theme-accent-rgb)); color: #fff; }
139
+ .icon-button.primary:hover:not(:disabled) { background: rgb(var(--theme-accent-rgb)); color: #fff; opacity: .88; }
140
+ .icon-button.primary:disabled { opacity: .35; }
141
+ .divider { width: 1px; height: 20px; background: rgb(var(--theme-border-rgb) / .3); margin: 0 4px; }
142
+
143
+ /* On the site: storing the draft, and the way out. */
144
+ .footer {
145
+ display: flex; align-items: center; gap: 8px; padding: 10px 12px 10px 22px;
146
+ border-top: 1px solid rgb(var(--theme-border-rgb) / .2);
147
+ background: rgb(var(--theme-background-actions-rgb) / .45);
148
+ }
149
+ .footer .chip { height: 30px; padding: .4em 1.2em; background: rgb(var(--theme-background-alt-rgb)); }
150
+ .unsaved { color: rgb(var(--theme-foreground-muted-rgb)); display: inline-flex; align-items: center; }
151
+ .link { color: rgb(var(--theme-foreground-muted-rgb)); text-decoration: underline; text-underline-offset: 3px; padding: 0 6px; }
152
+ .link:hover { color: rgb(var(--theme-foreground-rgb)); }
153
+ .dot {
154
+ display: inline-block; width: 7px; height: 7px; border-radius: 50%;
155
+ background: rgb(var(--theme-accent-rgb)); margin-right: 8px;
156
+ }
157
+ [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}}();
package/dist/bridge.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
- var preview = require('./preview-by8d54dt.js');
3
+ var preview = require('./preview-mpwfmnla.js');
4
4
  var site = require('./site.js');
5
5
  var config = require('./config-ci2hxmas.js');
6
6
 
@@ -1192,4 +1192,5 @@ const getContentBridge = ({ url, prompt = true, pagePath } = {})=>{
1192
1192
  exports.DEFAULT_PROMPT_LABELS = DEFAULT_PROMPT_LABELS;
1193
1193
  exports.INITIAL_STATE = INITIAL_STATE;
1194
1194
  exports.getContentBridge = getContentBridge;
1195
+ exports.mountPromptBox = mountPromptBox;
1195
1196
  exports.peekContentBridge = peekContentBridge;
package/dist/bridge.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { P as PREVIEW_PARAMS, U as UID_ATTRIBUTE, E as ENTRY_ATTRIBUTE } from './preview-kvotryzw.mjs';
1
+ import { P as PREVIEW_PARAMS, U as UID_ATTRIBUTE, E as ENTRY_ATTRIBUTE } from './preview-h7rwp06v.mjs';
2
2
  import { SITE_EDITING_COOKIE, SITE_REQUEST_HEADER } from './site.mjs';
3
3
  import { n as normalizeUrl, D as DEFAULT_URL } from './config-c6qktlz6.mjs';
4
4
 
@@ -1187,4 +1187,4 @@ const getContentBridge = ({ url, prompt = true, pagePath } = {})=>{
1187
1187
  return bridge;
1188
1188
  };
1189
1189
 
1190
- export { DEFAULT_PROMPT_LABELS, INITIAL_STATE, getContentBridge, peekContentBridge };
1190
+ export { DEFAULT_PROMPT_LABELS, INITIAL_STATE, getContentBridge, mountPromptBox, peekContentBridge };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RichTextFormat, E as Entry, a as EntryList, M as ModelFile, A as Asset, b as EntryStub, c as RichTextNode } from './types-pa96z6po.js';
2
- export { B as Block, C as ContentTypeDefinition, d as ContentTypeKind, e as Environment, F as FieldDefinition, f as FieldType, g as RichTextDocument, h as RichTextMark } from './types-pa96z6po.js';
1
+ import { R as RichTextFormat, E as Entry, a as EntryList, M as ModelFile, A as Asset, B as Block, I as ImproveBlock, b as EntryStub, c as RichTextNode } from './types-nyvor49r.js';
2
+ export { C as ContentTypeDefinition, d as ContentTypeKind, e as Environment, F as FieldDefinition, f as FieldType, g as RichTextDocument, h as RichTextMark } from './types-nyvor49r.js';
3
3
 
4
4
  type ContentClientOptions = {
5
5
  token: string;
@@ -66,14 +66,20 @@ type ImageTransform = {
66
66
  declare const imageUrl: (asset: Pick<Asset, "url"> | string, transform?: ImageTransform) => string;
67
67
  declare const imageSrcSet: (asset: Pick<Asset, "url"> | string, widths: number[], transform?: Omit<ImageTransform, "w">) => string;
68
68
 
69
+ declare const IMPROVE_BLOCK = "_improve";
70
+ declare const isImproveBlock: (block: Block) => block is ImproveBlock;
71
+ declare const pickImproveVariant: <TBlock extends Block>(block: ImproveBlock<TBlock>, option: string | null | undefined, fallback?: string) => TBlock[];
72
+
69
73
  declare const PREVIEW_PARAMS: {
70
74
  readonly preview: "cms_preview";
71
75
  readonly entry: "cms_entry";
72
76
  readonly environment: "cms_environment";
77
+ readonly token: "cms_token";
73
78
  };
74
79
  type PreviewParams = {
75
80
  entryId: string | null;
76
81
  environment: string | null;
82
+ token: string | null;
77
83
  };
78
84
  type SearchParamsInput = URL | URLSearchParams | string | Record<string, string | string[] | undefined>;
79
85
  declare const readPreviewParams: (input: SearchParamsInput) => PreviewParams | null;
@@ -88,6 +94,6 @@ declare const richTextToHtml: (doc: RichTextNode | null | undefined, { renderEnt
88
94
  declare const headingLevel: (node: RichTextNode) => 1 | 2 | 3 | 4 | 5 | 6;
89
95
  declare const richTextToPlainText: (doc: RichTextNode | null | undefined) => string;
90
96
 
91
- export { Asset, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, ModelFile, PREVIEW_PARAMS, RichTextFormat, RichTextNode, SAFE_HREF, UID_ATTRIBUTE, editable, escapeHtml, headingLevel, imageSrcSet, imageUrl, readPreviewParams, richTextToHtml, richTextToPlainText };
97
+ export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, ModelFile, PREVIEW_PARAMS, RichTextFormat, RichTextNode, SAFE_HREF, UID_ATTRIBUTE, editable, escapeHtml, headingLevel, imageSrcSet, imageUrl, isImproveBlock, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText };
92
98
  export type { ContentClientOptions, EntriesQuery, ImageTransform, PreviewParams, RequestOptions, RichTextHtmlOptions };
93
99
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sources":["../src/client.ts","../src/config.ts","../src/editable.ts","../src/errors.ts","../src/images.ts","../src/preview.ts","../src/richText.ts"],"mappings":";;;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA;AACA,eAAe,cAAc;AAC7B;AACA,WAAW,WAAW;AACtB,mBAAmB,KAAK;AACxB;AACA,KAAK,cAAc;AACnB;AACA;AACA,eAAe,cAAc;AAC7B,WAAW,WAAW;AACtB;AACA,KAAK,YAAY,GAAG,cAAc;AAClC;AACA;AACA;AACA;AACA;AACA,cAAc,aAAa;AAC3B;AACA;AACA,2FAA2F,oBAAoB;AAC/G,6BAA6B,KAAK,GAAG,KAAK,0BAA0B,cAAc,KAAK,OAAO;AAC9F,8BAA8B,KAAK,GAAG,KAAK,wBAAwB,cAAc,KAAK,OAAO;AAC7F,gCAAgC,KAAK,GAAG,KAAK,yDAAyD,YAAY,KAAK,OAAO,CAAC,SAAS;AACxI,iCAAiC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,YAAY,cAAc;AACnG,gCAAgC,IAAI,CAAC,cAAc,cAAc,OAAO,CAAC,SAAS;AAClF;;AChCA,cAAc,WAAW;;ACAzB,cAAc,aAAa;AAC3B,cAAc,eAAe;AAC7B,KAAK,QAAQ;AACb;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA;AACA,yBAAyB,QAAQ;;ACXjC,cAAc,eAAe,SAAS,KAAK;AAC3C;AACA;AACA;AACA;AACA;;ACJA,KAAK,cAAc;AACnB;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,QAAQ,UAAU,IAAI,CAAC,KAAK,+BAA+B,cAAc;AACvF,cAAc,WAAW,UAAU,IAAI,CAAC,KAAK,iDAAiD,IAAI,CAAC,cAAc;;ACTjH,cAAc,cAAc;AAC5B;AACA;AACA;AACA;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA,KAAK,iBAAiB,GAAG,GAAG,GAAG,eAAe,YAAY,MAAM;AAChE,cAAc,iBAAiB,UAAU,iBAAiB,KAAK,aAAa;;ACT5E,cAAc,SAAS,EAAE,MAAM;AAC/B,cAAc,UAAU;AACxB,KAAK,mBAAmB;AACxB,0BAA0B,KAAK,GAAG,SAAS,QAAQ,YAAY;AAC/D,0BAA0B,KAAK,QAAQ,YAAY;AACnD;AACA,cAAc,cAAc,QAAQ,YAAY,oDAAoD,mBAAmB;AACvH,cAAc,YAAY,SAAS,YAAY;AAC/C,cAAc,mBAAmB,QAAQ,YAAY;;","names":[]}
1
+ {"version":3,"file":"index.d.mts","sources":["../src/client.ts","../src/config.ts","../src/editable.ts","../src/errors.ts","../src/images.ts","../src/improve.ts","../src/preview.ts","../src/richText.ts"],"mappings":";;;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA;AACA,eAAe,cAAc;AAC7B;AACA,WAAW,WAAW;AACtB,mBAAmB,KAAK;AACxB;AACA,KAAK,cAAc;AACnB;AACA;AACA,eAAe,cAAc;AAC7B,WAAW,WAAW;AACtB;AACA,KAAK,YAAY,GAAG,cAAc;AAClC;AACA;AACA;AACA;AACA;AACA,cAAc,aAAa;AAC3B;AACA;AACA,2FAA2F,oBAAoB;AAC/G,6BAA6B,KAAK,GAAG,KAAK,0BAA0B,cAAc,KAAK,OAAO;AAC9F,8BAA8B,KAAK,GAAG,KAAK,wBAAwB,cAAc,KAAK,OAAO;AAC7F,gCAAgC,KAAK,GAAG,KAAK,yDAAyD,YAAY,KAAK,OAAO,CAAC,SAAS;AACxI,iCAAiC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,YAAY,cAAc;AACnG,gCAAgC,IAAI,CAAC,cAAc,cAAc,OAAO,CAAC,SAAS;AAClF;;AChCA,cAAc,WAAW;;ACAzB,cAAc,aAAa;AAC3B,cAAc,eAAe;AAC7B,KAAK,QAAQ;AACb;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA;AACA,yBAAyB,QAAQ;;ACXjC,cAAc,eAAe,SAAS,KAAK;AAC3C;AACA;AACA;AACA;AACA;;ACJA,KAAK,cAAc;AACnB;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,QAAQ,UAAU,IAAI,CAAC,KAAK,+BAA+B,cAAc;AACvF,cAAc,WAAW,UAAU,IAAI,CAAC,KAAK,iDAAiD,IAAI,CAAC,cAAc;;ACRjH,cAAc,aAAa;AAC3B,cAAc,cAAc,UAAU,KAAK,cAAc,YAAY;AACrE,cAAc,kBAAkB,kBAAkB,KAAK,SAAS,YAAY;;ACH5E,cAAc,cAAc;AAC5B;AACA;AACA;AACA;AACA;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA;AACA,KAAK,iBAAiB,GAAG,GAAG,GAAG,eAAe,YAAY,MAAM;AAChE,cAAc,iBAAiB,UAAU,iBAAiB,KAAK,aAAa;;ACX5E,cAAc,SAAS,EAAE,MAAM;AAC/B,cAAc,UAAU;AACxB,KAAK,mBAAmB;AACxB,0BAA0B,KAAK,GAAG,SAAS,QAAQ,YAAY;AAC/D,0BAA0B,KAAK,QAAQ,YAAY;AACnD;AACA,cAAc,cAAc,QAAQ,YAAY,oDAAoD,mBAAmB;AACvH,cAAc,YAAY,SAAS,YAAY;AAC/C,cAAc,mBAAmB,QAAQ,YAAY;;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RichTextFormat, E as Entry, a as EntryList, M as ModelFile, A as Asset, b as EntryStub, c as RichTextNode } from './types-pa96z6po.js';
2
- export { B as Block, C as ContentTypeDefinition, d as ContentTypeKind, e as Environment, F as FieldDefinition, f as FieldType, g as RichTextDocument, h as RichTextMark } from './types-pa96z6po.js';
1
+ import { R as RichTextFormat, E as Entry, a as EntryList, M as ModelFile, A as Asset, B as Block, I as ImproveBlock, b as EntryStub, c as RichTextNode } from './types-nyvor49r.js';
2
+ export { C as ContentTypeDefinition, d as ContentTypeKind, e as Environment, F as FieldDefinition, f as FieldType, g as RichTextDocument, h as RichTextMark } from './types-nyvor49r.js';
3
3
 
4
4
  type ContentClientOptions = {
5
5
  token: string;
@@ -66,14 +66,20 @@ type ImageTransform = {
66
66
  declare const imageUrl: (asset: Pick<Asset, "url"> | string, transform?: ImageTransform) => string;
67
67
  declare const imageSrcSet: (asset: Pick<Asset, "url"> | string, widths: number[], transform?: Omit<ImageTransform, "w">) => string;
68
68
 
69
+ declare const IMPROVE_BLOCK = "_improve";
70
+ declare const isImproveBlock: (block: Block) => block is ImproveBlock;
71
+ declare const pickImproveVariant: <TBlock extends Block>(block: ImproveBlock<TBlock>, option: string | null | undefined, fallback?: string) => TBlock[];
72
+
69
73
  declare const PREVIEW_PARAMS: {
70
74
  readonly preview: "cms_preview";
71
75
  readonly entry: "cms_entry";
72
76
  readonly environment: "cms_environment";
77
+ readonly token: "cms_token";
73
78
  };
74
79
  type PreviewParams = {
75
80
  entryId: string | null;
76
81
  environment: string | null;
82
+ token: string | null;
77
83
  };
78
84
  type SearchParamsInput = URL | URLSearchParams | string | Record<string, string | string[] | undefined>;
79
85
  declare const readPreviewParams: (input: SearchParamsInput) => PreviewParams | null;
@@ -88,6 +94,6 @@ declare const richTextToHtml: (doc: RichTextNode | null | undefined, { renderEnt
88
94
  declare const headingLevel: (node: RichTextNode) => 1 | 2 | 3 | 4 | 5 | 6;
89
95
  declare const richTextToPlainText: (doc: RichTextNode | null | undefined) => string;
90
96
 
91
- export { Asset, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, ModelFile, PREVIEW_PARAMS, RichTextFormat, RichTextNode, SAFE_HREF, UID_ATTRIBUTE, editable, escapeHtml, headingLevel, imageSrcSet, imageUrl, readPreviewParams, richTextToHtml, richTextToPlainText };
97
+ export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, ModelFile, PREVIEW_PARAMS, RichTextFormat, RichTextNode, SAFE_HREF, UID_ATTRIBUTE, editable, escapeHtml, headingLevel, imageSrcSet, imageUrl, isImproveBlock, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText };
92
98
  export type { ContentClientOptions, EntriesQuery, ImageTransform, PreviewParams, RequestOptions, RichTextHtmlOptions };
93
99
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sources":["../src/client.ts","../src/config.ts","../src/editable.ts","../src/errors.ts","../src/images.ts","../src/preview.ts","../src/richText.ts"],"mappings":";;;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA;AACA,eAAe,cAAc;AAC7B;AACA,WAAW,WAAW;AACtB,mBAAmB,KAAK;AACxB;AACA,KAAK,cAAc;AACnB;AACA;AACA,eAAe,cAAc;AAC7B,WAAW,WAAW;AACtB;AACA,KAAK,YAAY,GAAG,cAAc;AAClC;AACA;AACA;AACA;AACA;AACA,cAAc,aAAa;AAC3B;AACA;AACA,2FAA2F,oBAAoB;AAC/G,6BAA6B,KAAK,GAAG,KAAK,0BAA0B,cAAc,KAAK,OAAO;AAC9F,8BAA8B,KAAK,GAAG,KAAK,wBAAwB,cAAc,KAAK,OAAO;AAC7F,gCAAgC,KAAK,GAAG,KAAK,yDAAyD,YAAY,KAAK,OAAO,CAAC,SAAS;AACxI,iCAAiC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,YAAY,cAAc;AACnG,gCAAgC,IAAI,CAAC,cAAc,cAAc,OAAO,CAAC,SAAS;AAClF;;AChCA,cAAc,WAAW;;ACAzB,cAAc,aAAa;AAC3B,cAAc,eAAe;AAC7B,KAAK,QAAQ;AACb;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA;AACA,yBAAyB,QAAQ;;ACXjC,cAAc,eAAe,SAAS,KAAK;AAC3C;AACA;AACA;AACA;AACA;;ACJA,KAAK,cAAc;AACnB;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,QAAQ,UAAU,IAAI,CAAC,KAAK,+BAA+B,cAAc;AACvF,cAAc,WAAW,UAAU,IAAI,CAAC,KAAK,iDAAiD,IAAI,CAAC,cAAc;;ACTjH,cAAc,cAAc;AAC5B;AACA;AACA;AACA;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA,KAAK,iBAAiB,GAAG,GAAG,GAAG,eAAe,YAAY,MAAM;AAChE,cAAc,iBAAiB,UAAU,iBAAiB,KAAK,aAAa;;ACT5E,cAAc,SAAS,EAAE,MAAM;AAC/B,cAAc,UAAU;AACxB,KAAK,mBAAmB;AACxB,0BAA0B,KAAK,GAAG,SAAS,QAAQ,YAAY;AAC/D,0BAA0B,KAAK,QAAQ,YAAY;AACnD;AACA,cAAc,cAAc,QAAQ,YAAY,oDAAoD,mBAAmB;AACvH,cAAc,YAAY,SAAS,YAAY;AAC/C,cAAc,mBAAmB,QAAQ,YAAY;;","names":[]}
1
+ {"version":3,"file":"index.d.ts","sources":["../src/client.ts","../src/config.ts","../src/editable.ts","../src/errors.ts","../src/images.ts","../src/improve.ts","../src/preview.ts","../src/richText.ts"],"mappings":";;;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA;AACA,eAAe,cAAc;AAC7B;AACA,WAAW,WAAW;AACtB,mBAAmB,KAAK;AACxB;AACA,KAAK,cAAc;AACnB;AACA;AACA,eAAe,cAAc;AAC7B,WAAW,WAAW;AACtB;AACA,KAAK,YAAY,GAAG,cAAc;AAClC;AACA;AACA;AACA;AACA;AACA,cAAc,aAAa;AAC3B;AACA;AACA,2FAA2F,oBAAoB;AAC/G,6BAA6B,KAAK,GAAG,KAAK,0BAA0B,cAAc,KAAK,OAAO;AAC9F,8BAA8B,KAAK,GAAG,KAAK,wBAAwB,cAAc,KAAK,OAAO;AAC7F,gCAAgC,KAAK,GAAG,KAAK,yDAAyD,YAAY,KAAK,OAAO,CAAC,SAAS;AACxI,iCAAiC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,YAAY,cAAc;AACnG,gCAAgC,IAAI,CAAC,cAAc,cAAc,OAAO,CAAC,SAAS;AAClF;;AChCA,cAAc,WAAW;;ACAzB,cAAc,aAAa;AAC3B,cAAc,eAAe;AAC7B,KAAK,QAAQ;AACb;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA;AACA,yBAAyB,QAAQ;;ACXjC,cAAc,eAAe,SAAS,KAAK;AAC3C;AACA;AACA;AACA;AACA;;ACJA,KAAK,cAAc;AACnB;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,QAAQ,UAAU,IAAI,CAAC,KAAK,+BAA+B,cAAc;AACvF,cAAc,WAAW,UAAU,IAAI,CAAC,KAAK,iDAAiD,IAAI,CAAC,cAAc;;ACRjH,cAAc,aAAa;AAC3B,cAAc,cAAc,UAAU,KAAK,cAAc,YAAY;AACrE,cAAc,kBAAkB,kBAAkB,KAAK,SAAS,YAAY;;ACH5E,cAAc,cAAc;AAC5B;AACA;AACA;AACA;AACA;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA;AACA,KAAK,iBAAiB,GAAG,GAAG,GAAG,eAAe,YAAY,MAAM;AAChE,cAAc,iBAAiB,UAAU,iBAAiB,KAAK,aAAa;;ACX5E,cAAc,SAAS,EAAE,MAAM;AAC/B,cAAc,UAAU;AACxB,KAAK,mBAAmB;AACxB,0BAA0B,KAAK,GAAG,SAAS,QAAQ,YAAY;AAC/D,0BAA0B,KAAK,QAAQ,YAAY;AACnD;AACA,cAAc,cAAc,QAAQ,YAAY,oDAAoD,mBAAmB;AACvH,cAAc,YAAY,SAAS,YAAY;AAC/C,cAAc,mBAAmB,QAAQ,YAAY;;","names":[]}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  var config = require('./config-ci2hxmas.js');
4
- var preview = require('./preview-by8d54dt.js');
4
+ var preview = require('./preview-mpwfmnla.js');
5
5
 
6
6
  // A response from the API that wasn't a success. `message` is the API's own
7
7
  // `error` text when it sent one.
@@ -171,6 +171,20 @@ const imageSrcSet = (asset, widths, transform = {})=>widths.map((w)=>`${imageUrl
171
171
  w
172
172
  })} ${w}w`).join(', ');
173
173
 
174
+ const IMPROVE_BLOCK = '_improve';
175
+ const isImproveBlock = (block)=>block._type === IMPROVE_BLOCK;
176
+ // The blocks to render for an improve block: the variant of `option`, what
177
+ // improve's SDK returned for the visitor (getTestValue / getFlagValue). It
178
+ // is null when the test or flag isn't active or the visitor isn't in it;
179
+ // then, or for an option the block has no variant for, the `fallback`
180
+ // option's variant, else the first one (usually the control).
181
+ const pickImproveVariant = (block, option, fallback)=>{
182
+ const variants = block.variants ?? {};
183
+ if (option && variants[option]) return variants[option];
184
+ if (fallback && variants[fallback]) return variants[fallback];
185
+ return Object.values(variants)[0] ?? [];
186
+ };
187
+
174
188
  // Links are only rendered for these; anything else (`javascript:` …) keeps its
175
189
  // text without the link. The same rule the CMS validates with.
176
190
  const SAFE_HREF = /^(https?:\/\/|mailto:|tel:|#|\/(?!\/))/i;
@@ -291,10 +305,13 @@ exports.editable = preview.editable;
291
305
  exports.readPreviewParams = preview.readPreviewParams;
292
306
  exports.ContentApiError = ContentApiError;
293
307
  exports.ContentClient = ContentClient;
308
+ exports.IMPROVE_BLOCK = IMPROVE_BLOCK;
294
309
  exports.SAFE_HREF = SAFE_HREF;
295
310
  exports.escapeHtml = escapeHtml;
296
311
  exports.headingLevel = headingLevel;
297
312
  exports.imageSrcSet = imageSrcSet;
298
313
  exports.imageUrl = imageUrl;
314
+ exports.isImproveBlock = isImproveBlock;
315
+ exports.pickImproveVariant = pickImproveVariant;
299
316
  exports.richTextToHtml = richTextToHtml;
300
317
  exports.richTextToPlainText = richTextToPlainText;
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as normalizeUrl, a as DEFAULT_TIMEOUT, b as DELIVERY_PATH, D as DEFAULT_URL } from './config-c6qktlz6.mjs';
2
- export { E as ENTRY_ATTRIBUTE, P as PREVIEW_PARAMS, U as UID_ATTRIBUTE, e as editable, r as readPreviewParams } from './preview-kvotryzw.mjs';
2
+ export { E as ENTRY_ATTRIBUTE, P as PREVIEW_PARAMS, U as UID_ATTRIBUTE, e as editable, r as readPreviewParams } from './preview-h7rwp06v.mjs';
3
3
 
4
4
  // A response from the API that wasn't a success. `message` is the API's own
5
5
  // `error` text when it sent one.
@@ -169,6 +169,20 @@ const imageSrcSet = (asset, widths, transform = {})=>widths.map((w)=>`${imageUrl
169
169
  w
170
170
  })} ${w}w`).join(', ');
171
171
 
172
+ const IMPROVE_BLOCK = '_improve';
173
+ const isImproveBlock = (block)=>block._type === IMPROVE_BLOCK;
174
+ // The blocks to render for an improve block: the variant of `option`, what
175
+ // improve's SDK returned for the visitor (getTestValue / getFlagValue). It
176
+ // is null when the test or flag isn't active or the visitor isn't in it;
177
+ // then, or for an option the block has no variant for, the `fallback`
178
+ // option's variant, else the first one (usually the control).
179
+ const pickImproveVariant = (block, option, fallback)=>{
180
+ const variants = block.variants ?? {};
181
+ if (option && variants[option]) return variants[option];
182
+ if (fallback && variants[fallback]) return variants[fallback];
183
+ return Object.values(variants)[0] ?? [];
184
+ };
185
+
172
186
  // Links are only rendered for these; anything else (`javascript:` …) keeps its
173
187
  // text without the link. The same rule the CMS validates with.
174
188
  const SAFE_HREF = /^(https?:\/\/|mailto:|tel:|#|\/(?!\/))/i;
@@ -281,4 +295,4 @@ const richTextToPlainText = (doc)=>{
281
295
  return parts.join('').replace(/\n+/g, '\n').trim();
282
296
  };
283
297
 
284
- export { ContentApiError, ContentClient, DEFAULT_URL, SAFE_HREF, escapeHtml, headingLevel, imageSrcSet, imageUrl, richTextToHtml, richTextToPlainText };
298
+ export { ContentApiError, ContentClient, DEFAULT_URL, IMPROVE_BLOCK, SAFE_HREF, escapeHtml, headingLevel, imageSrcSet, imageUrl, isImproveBlock, pickImproveVariant, richTextToHtml, richTextToPlainText };
@@ -24,12 +24,15 @@ const editable = (target)=>{
24
24
  };
25
25
 
26
26
  // The visual editor loads the site in an iframe at the environment's preview
27
- // URL plus the entry's path, with these parameters added. A request carrying
28
- // `cms_preview` should be answered with drafts: fetch with a preview token.
27
+ // URL plus the entry's path, with these parameters added. `cms_token` is a
28
+ // token the CMS made for the person editing (`cms_v_…`): it reads drafts
29
+ // through the delivery API for 8 hours. Read drafts with it, never because
30
+ // `cms_preview` is there: anyone can add that to a URL.
29
31
  const PREVIEW_PARAMS = {
30
32
  preview: 'cms_preview',
31
33
  entry: 'cms_entry',
32
- environment: 'cms_environment'
34
+ environment: 'cms_environment',
35
+ token: 'cms_token'
33
36
  };
34
37
  const toSearchParams = (input)=>{
35
38
  if (input instanceof URL) return input.searchParams;
@@ -51,13 +54,19 @@ const toSearchParams = (input)=>{
51
54
  // The editor's parameters when this is a preview request, else null.
52
55
  //
53
56
  // const preview = readPreviewParams(request.url)
54
- // const content = preview ? previewClient : deliveryClient
57
+ // const content = preview?.token
58
+ // ? new ContentClient({ token: preview.token, init: { cache: 'no-store' } })
59
+ // : deliveryClient
60
+ //
61
+ // A made-up or expired token makes the client throw a ContentApiError with
62
+ // status 401: answer with `deliveryClient` then.
55
63
  const readPreviewParams = (input)=>{
56
64
  const params = toSearchParams(input);
57
65
  if (!params.has(PREVIEW_PARAMS.preview)) return null;
58
66
  return {
59
67
  entryId: params.get(PREVIEW_PARAMS.entry),
60
- environment: params.get(PREVIEW_PARAMS.environment)
68
+ environment: params.get(PREVIEW_PARAMS.environment),
69
+ token: params.get(PREVIEW_PARAMS.token)
61
70
  };
62
71
  };
63
72
 
@@ -24,12 +24,15 @@ const editable = (target)=>{
24
24
  };
25
25
 
26
26
  // The visual editor loads the site in an iframe at the environment's preview
27
- // URL plus the entry's path, with these parameters added. A request carrying
28
- // `cms_preview` should be answered with drafts: fetch with a preview token.
27
+ // URL plus the entry's path, with these parameters added. `cms_token` is a
28
+ // token the CMS made for the person editing (`cms_v_…`): it reads drafts
29
+ // through the delivery API for 8 hours. Read drafts with it, never because
30
+ // `cms_preview` is there: anyone can add that to a URL.
29
31
  const PREVIEW_PARAMS = {
30
32
  preview: 'cms_preview',
31
33
  entry: 'cms_entry',
32
- environment: 'cms_environment'
34
+ environment: 'cms_environment',
35
+ token: 'cms_token'
33
36
  };
34
37
  const toSearchParams = (input)=>{
35
38
  if (input instanceof URL) return input.searchParams;
@@ -51,13 +54,19 @@ const toSearchParams = (input)=>{
51
54
  // The editor's parameters when this is a preview request, else null.
52
55
  //
53
56
  // const preview = readPreviewParams(request.url)
54
- // const content = preview ? previewClient : deliveryClient
57
+ // const content = preview?.token
58
+ // ? new ContentClient({ token: preview.token, init: { cache: 'no-store' } })
59
+ // : deliveryClient
60
+ //
61
+ // A made-up or expired token makes the client throw a ContentApiError with
62
+ // status 401: answer with `deliveryClient` then.
55
63
  const readPreviewParams = (input)=>{
56
64
  const params = toSearchParams(input);
57
65
  if (!params.has(PREVIEW_PARAMS.preview)) return null;
58
66
  return {
59
67
  entryId: params.get(PREVIEW_PARAMS.entry),
60
- environment: params.get(PREVIEW_PARAMS.environment)
68
+ environment: params.get(PREVIEW_PARAMS.environment),
69
+ token: params.get(PREVIEW_PARAMS.token)
61
70
  };
62
71
  };
63
72
 
package/dist/site.js CHANGED
@@ -2,9 +2,9 @@ Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  var config = require('./config-ci2hxmas.js');
4
4
 
5
- // Editing on your own site, outside the visual editor (the CMS's
6
- // docs/site-editing.md). One handler serves everything under `/cms` (the
7
- // `basePath`) on your site:
5
+ // Editing on your own site, outside the visual editor
6
+ // (https://content.obelism.studio/docs/guides/site-editing). One handler
7
+ // serves everything under `/cms` (the `basePath`) on your site:
8
8
  //
9
9
  // - `/cms/login?return_to=/pricing` — sends the browser to Obelism Content
10
10
  // to sign in, with a PKCE challenge, and…
package/dist/site.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { n as normalizeUrl, a as DEFAULT_TIMEOUT, D as DEFAULT_URL } from './config-c6qktlz6.mjs';
2
2
 
3
- // Editing on your own site, outside the visual editor (the CMS's
4
- // docs/site-editing.md). One handler serves everything under `/cms` (the
5
- // `basePath`) on your site:
3
+ // Editing on your own site, outside the visual editor
4
+ // (https://content.obelism.studio/docs/guides/site-editing). One handler
5
+ // serves everything under `/cms` (the `basePath`) on your site:
6
6
  //
7
7
  // - `/cms/login?return_to=/pricing` — sends the browser to Obelism Content
8
8
  // to sign in, with a PKCE challenge, and…
@@ -30,6 +30,11 @@ type Block<TFields extends Record<string, unknown> = Record<string, unknown>, TT
30
30
  _uid: string;
31
31
  _type: TType;
32
32
  } & TFields;
33
+ type ImproveBlock<TBlock extends Block = Block> = Block<{
34
+ kind: 'test' | 'flag';
35
+ slug: string;
36
+ variants: Record<string, TBlock[]>;
37
+ }, '_improve'>;
33
38
  type RichTextMark = {
34
39
  type: 'bold' | 'italic' | 'underline' | 'strike' | 'code' | 'link' | string;
35
40
  attrs?: Record<string, unknown>;
@@ -116,5 +121,5 @@ type ModelFile = {
116
121
  contentTypes: ContentTypeDefinition[];
117
122
  };
118
123
 
119
- export type { Asset as A, Block as B, ContentTypeDefinition as C, Entry as E, FieldDefinition as F, ModelFile as M, RichTextFormat as R, EntryList as a, EntryStub as b, RichTextNode as c, ContentTypeKind as d, Environment as e, FieldType as f, RichTextDocument as g, RichTextMark as h };
120
- //# sourceMappingURL=types-pa96z6po.d.ts.map
124
+ export type { Asset as A, Block as B, ContentTypeDefinition as C, Entry as E, FieldDefinition as F, ImproveBlock as I, ModelFile as M, RichTextFormat as R, EntryList as a, EntryStub as b, RichTextNode as c, ContentTypeKind as d, Environment as e, FieldType as f, RichTextDocument as g, RichTextMark as h };
125
+ //# sourceMappingURL=types-nyvor49r.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-nyvor49r.d.ts","sources":["../src/types.ts"],"mappings":"AAAA,KAAK,WAAW;AAChB,KAAK,cAAc;AACnB,KAAK,KAAK;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,MAAM;AAC1B,kBAAkB,MAAM;AACxB;AACA,KAAK,KAAK,iBAAiB,MAAM,oBAAoB,MAAM;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,SAAS;AACd;AACA;AACA;AACA,KAAK,KAAK,iBAAiB,MAAM,oBAAoB,MAAM;AAC3D;AACA;AACA;AACA,KAAK,YAAY,gBAAgB,KAAK,GAAG,KAAK,IAAI,KAAK;AACvD;AACA;AACA,cAAc,MAAM;AACpB;AACA,KAAK,YAAY;AACjB;AACA,YAAY,MAAM;AAClB;AACA,KAAK,YAAY;AACjB;AACA,YAAY,MAAM;AAClB,cAAc,YAAY;AAC1B;AACA,YAAY,YAAY;AACxB;AACA,KAAK,gBAAgB,GAAG,YAAY;AACpC;AACA;AACA,KAAK,SAAS,gBAAgB,KAAK,GAAG,KAAK;AAC3C;AACA;AACA;AACA;AACA;AACA,KAAK,eAAe;AACpB,KAAK,SAAS;AACd;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,eAAe,GAAG,SAAS;AAChC;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,SAAS,GAAG,eAAe;AAChC,KAAK,qBAAqB;AAC1B;AACA;AACA,UAAU,eAAe;AACzB;AACA,YAAY,eAAe;AAC3B;AACA,KAAK,SAAS;AACd;AACA;AACA,kBAAkB,qBAAqB;AACvC;;","names":[]}
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.2.0",
4
+ "version": "0.4.0",
5
5
  "keywords": [
6
6
  "cms",
7
7
  "headless-cms",
@@ -65,7 +65,7 @@
65
65
  }
66
66
  },
67
67
  "scripts": {
68
- "build": "bunchee",
68
+ "build": "bunchee && rollup --config rollup.bridge.config.mjs",
69
69
  "dev": "bunchee --watch --no-clean",
70
70
  "test": "vitest",
71
71
  "test:coverage": "vitest run --coverage",
@@ -77,12 +77,15 @@
77
77
  },
78
78
  "devDependencies": {
79
79
  "@obelism/content-typescript-config": "^0.0.0",
80
+ "@rollup/plugin-node-resolve": "^16.0.3",
80
81
  "@types/node": "^24.0.0",
81
82
  "@typescript/typescript6": "^6.0.2",
82
83
  "bunchee": "^7.0.2",
83
84
  "jsdom": "^30.1.2",
84
85
  "npm-run-all2": "^9.0.2",
85
86
  "oxlint": "^1.88.0",
87
+ "rollup": "^4.64.7",
88
+ "rollup-plugin-swc3": "^0.12.1",
86
89
  "typescript": "^7.0.2",
87
90
  "vitest": "^5.0.4"
88
91
  },
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-pa96z6po.d.ts","sources":["../src/types.ts"],"mappings":"AAAA,KAAK,WAAW;AAChB,KAAK,cAAc;AACnB,KAAK,KAAK;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,MAAM;AAC1B,kBAAkB,MAAM;AACxB;AACA,KAAK,KAAK,iBAAiB,MAAM,oBAAoB,MAAM;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,SAAS;AACd;AACA;AACA;AACA,KAAK,KAAK,iBAAiB,MAAM,oBAAoB,MAAM;AAC3D;AACA;AACA;AACA,KAAK,YAAY;AACjB;AACA,YAAY,MAAM;AAClB;AACA,KAAK,YAAY;AACjB;AACA,YAAY,MAAM;AAClB,cAAc,YAAY;AAC1B;AACA,YAAY,YAAY;AACxB;AACA,KAAK,gBAAgB,GAAG,YAAY;AACpC;AACA;AACA,KAAK,SAAS,gBAAgB,KAAK,GAAG,KAAK;AAC3C;AACA;AACA;AACA;AACA;AACA,KAAK,eAAe;AACpB,KAAK,SAAS;AACd;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,eAAe,GAAG,SAAS;AAChC;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,SAAS,GAAG,eAAe;AAChC,KAAK,qBAAqB;AAC1B;AACA;AACA,UAAU,eAAe;AACzB;AACA,YAAY,eAAe;AAC3B;AACA,KAAK,SAAS;AACd;AACA;AACA,kBAAkB,qBAAqB;AACvC;;","names":[]}