@obelism/content 0.2.0 → 0.3.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 +66 -5
- package/dist/bin/content.js +400 -12
- package/dist/bridge.d.mts +2 -1
- package/dist/bridge.d.mts.map +1 -1
- package/dist/bridge.d.ts +2 -1
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.global.js +158 -0
- package/dist/bridge.js +1 -0
- package/dist/bridge.mjs +1 -1
- package/dist/index.d.mts +7 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -0
- package/dist/index.mjs +15 -1
- package/dist/{types-pa96z6po.d.ts → types-nyvor49r.d.ts} +7 -2
- package/dist/types-nyvor49r.d.ts.map +1 -0
- package/package.json +5 -2
- package/dist/types-pa96z6po.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -55,12 +55,35 @@ bridge.on('update', ({ entry }) => render(entry)) // unsaved edits, as typed
|
|
|
55
55
|
|
|
56
56
|
Mark blocks so editors can click them: `<section {...editable(block)}>`
|
|
57
57
|
(adds `data-cms-uid`). Without an `update` listener the page reloads after
|
|
58
|
-
each save.
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
each save. React sites use `@obelism/content-react`.
|
|
59
|
+
|
|
60
|
+
Without a bundler, the same bridge (prompt box included) is one script tag:
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<script src="https://content.obelism.studio/bridge.js"></script>
|
|
64
|
+
<script>
|
|
65
|
+
ObelismBridge.on('update', ({ entry }) => render(entry))
|
|
66
|
+
</script>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The CMS serves this package's `dist/bridge.global.js` there; npm CDNs serve
|
|
70
|
+
it too (then add `data-url` if your CMS isn't content.obelism.studio).
|
|
71
|
+
`data-prompt="false"` leaves the prompt box out. The tag puts the bridge on
|
|
72
|
+
`window.ObelismBridge` and `getContentBridge` / `mountPromptBox` on
|
|
73
|
+
`window.ObelismContent`; a bundled copy on the same page shares its bridge.
|
|
61
74
|
|
|
62
75
|
## Prompt editing
|
|
63
76
|
|
|
77
|
+
The prompt box is plain DOM in a shadow root, no framework. Start the
|
|
78
|
+
bridge with `prompt: false` to leave it out, and mount it yourself later:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { getContentBridge, mountPromptBox } from '@obelism/content/bridge'
|
|
82
|
+
|
|
83
|
+
const bridge = getContentBridge({ prompt: false })
|
|
84
|
+
const unmount = mountPromptBox(document, bridge, { labels: { open: 'Ask' } })
|
|
85
|
+
```
|
|
86
|
+
|
|
64
87
|
Inside the visual editor, people who may edit get a prompt box at the bottom
|
|
65
88
|
of the page: they type what to change and it shows on the page right away,
|
|
66
89
|
unsaved, with undo. It acts on the selected block, or the whole entry. The
|
|
@@ -109,12 +132,50 @@ export const POST = createPromptHandler({
|
|
|
109
132
|
- `richTextToHtml(doc, { renderEntry?, renderAsset? })`, `richTextToPlainText(doc)`
|
|
110
133
|
- `imageUrl(asset, { w, h, fit, format, q })`, `imageSrcSet(asset, [480, 960])`
|
|
111
134
|
|
|
135
|
+
## A/B tests and feature flags
|
|
136
|
+
|
|
137
|
+
Any blocks field can hold the built-in improve block (`ImproveBlock`,
|
|
138
|
+
`_type: '_improve'`): one list of blocks per option of an improve test or
|
|
139
|
+
flag. Ask improve's SDK for the visitor's option and render that variant:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { isImproveBlock, pickImproveVariant } from '@obelism/content'
|
|
143
|
+
|
|
144
|
+
// `improve` is improve's SDK (@obelism/improve-sdk), set up for the visitor.
|
|
145
|
+
const blocks = layout.flatMap((block) => {
|
|
146
|
+
if (!isImproveBlock(block)) return [block]
|
|
147
|
+
const option =
|
|
148
|
+
block.kind === 'flag'
|
|
149
|
+
? improve.getFlagValue(block.slug)
|
|
150
|
+
: improve.getTestValue(block.slug)
|
|
151
|
+
return pickImproveVariant(block, option)
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`pickImproveVariant(block, option, fallback?)` falls back to `fallback`'s
|
|
156
|
+
variant, else the first one, when the option is `null` (the test isn't
|
|
157
|
+
active or the visitor isn't in it) or has no variant.
|
|
158
|
+
|
|
112
159
|
## CLI
|
|
113
160
|
|
|
114
|
-
|
|
161
|
+
Start a site's repository on a space, in its root:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
export CONTENT_MANAGEMENT_TOKEN=cms_m_… # a management token, Space settings
|
|
165
|
+
npx @obelism/content init
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
It writes the model file (`content/content-model.json`), an example of what
|
|
169
|
+
the delivery API answers for every content type (`content/examples/`, with
|
|
170
|
+
the A/B test or flag block while your organization has improve on),
|
|
171
|
+
installs `@obelism/content` with your package manager, and adds the token variables to
|
|
172
|
+
`.env.example`. Options: `--dir` (default `content`), `--url`,
|
|
173
|
+
`--skip-install`. Agents get the same steps from the CMS's
|
|
174
|
+
`getProjectSetup` MCP tool.
|
|
175
|
+
|
|
176
|
+
Then keep the model file in Git in step with an environment:
|
|
115
177
|
|
|
116
178
|
```bash
|
|
117
|
-
export CONTENT_MANAGEMENT_TOKEN=cms_m_…
|
|
118
179
|
npx @obelism/content model pull # write content/content-model.json
|
|
119
180
|
npx @obelism/content model plan # what applying would change
|
|
120
181
|
npx @obelism/content model apply # apply it, all or nothing
|
package/dist/bin/content.js
CHANGED
|
@@ -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
|
|
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 (and a preview token for drafts) in Space settings, and set CONTENT_DELIVERY_TOKEN and CONTENT_PREVIEW_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
|
-
|
|
152
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
194
|
-
|
|
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
|
|
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
|
package/dist/bridge.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.mts","sources":["../src/bridge/createBridge.ts","../src/bridge/promptBox.ts","../src/bridge.ts"],"mappings":";;AAIA,KAAK,YAAY;AACjB;AACA,eAAe,KAAK;AACpB;AACA;AACA,WAAW,MAAM;AACjB,eAAe,MAAM;AACrB,YAAY,WAAW;AACvB;AACA,KAAK,WAAW,SAAS,YAAY;AACrC,KAAK,cAAc,WAAW,WAAW,cAAc,YAAY;AACnE,KAAK,WAAW;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,YAAY;AACjB;AACA;AACA,KAAK,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;;
|
|
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
|
package/dist/bridge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.ts","sources":["../src/bridge/createBridge.ts","../src/bridge/promptBox.ts","../src/bridge.ts"],"mappings":";;AAIA,KAAK,YAAY;AACjB;AACA,eAAe,KAAK;AACpB;AACA;AACA,WAAW,MAAM;AACjB,eAAe,MAAM;AACrB,YAAY,WAAW;AACvB;AACA,KAAK,WAAW,SAAS,YAAY;AACrC,KAAK,cAAc,WAAW,WAAW,cAAc,YAAY;AACnE,KAAK,WAAW;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,YAAY;AACjB;AACA;AACA,KAAK,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;;
|
|
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/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
|
@@ -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
|
@@ -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-
|
|
2
|
-
export {
|
|
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,6 +66,10 @@ 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";
|
|
@@ -88,6 +92,6 @@ declare const richTextToHtml: (doc: RichTextNode | null | undefined, { renderEnt
|
|
|
88
92
|
declare const headingLevel: (node: RichTextNode) => 1 | 2 | 3 | 4 | 5 | 6;
|
|
89
93
|
declare const richTextToPlainText: (doc: RichTextNode | null | undefined) => string;
|
|
90
94
|
|
|
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 };
|
|
95
|
+
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
96
|
export type { ContentClientOptions, EntriesQuery, ImageTransform, PreviewParams, RequestOptions, RichTextHtmlOptions };
|
|
93
97
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -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;;
|
|
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,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":[]}
|
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-
|
|
2
|
-
export {
|
|
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,6 +66,10 @@ 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";
|
|
@@ -88,6 +92,6 @@ declare const richTextToHtml: (doc: RichTextNode | null | undefined, { renderEnt
|
|
|
88
92
|
declare const headingLevel: (node: RichTextNode) => 1 | 2 | 3 | 4 | 5 | 6;
|
|
89
93
|
declare const richTextToPlainText: (doc: RichTextNode | null | undefined) => string;
|
|
90
94
|
|
|
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 };
|
|
95
|
+
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
96
|
export type { ContentClientOptions, EntriesQuery, ImageTransform, PreviewParams, RequestOptions, RichTextHtmlOptions };
|
|
93
97
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;;
|
|
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,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":[]}
|
package/dist/index.js
CHANGED
|
@@ -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
|
@@ -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 };
|
|
@@ -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-
|
|
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.
|
|
4
|
+
"version": "0.3.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":[]}
|