@shop-on/theme-contract 0.4.0 → 0.6.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/dist/ai-files.d.ts +18 -0
- package/dist/ai-files.js +47 -0
- package/dist/block-allowlist.d.ts +27 -0
- package/dist/block-allowlist.js +21 -0
- package/dist/closest-scope.d.ts +51 -0
- package/dist/closest-scope.js +66 -0
- package/dist/document.d.ts +13 -0
- package/dist/document.js +19 -5
- package/dist/editor-tools/apply.d.ts +85 -0
- package/dist/editor-tools/apply.js +544 -0
- package/dist/editor-tools/args.d.ts +153 -0
- package/dist/editor-tools/args.js +145 -0
- package/dist/editor-tools/coerce.d.ts +43 -0
- package/dist/editor-tools/coerce.js +200 -0
- package/dist/editor-tools/digest.d.ts +75 -0
- package/dist/editor-tools/digest.js +99 -0
- package/dist/editor-tools/index.d.ts +21 -0
- package/dist/editor-tools/index.js +24 -0
- package/dist/editor-tools/recipes.d.ts +122 -0
- package/dist/editor-tools/recipes.js +192 -0
- package/dist/fonts.d.ts +9 -0
- package/dist/fonts.js +11 -0
- package/dist/google-fonts.generated.d.ts +7 -0
- package/dist/google-fonts.generated.js +753 -0
- package/dist/index.d.ts +15 -6
- package/dist/index.js +15 -6
- package/dist/link-url.d.ts +9 -0
- package/dist/link-url.js +33 -0
- package/dist/metafield-binding.d.ts +89 -9
- package/dist/metafield-binding.js +254 -43
- package/dist/ops.d.ts +85 -0
- package/dist/ops.js +442 -0
- package/dist/richtext.d.ts +23 -0
- package/dist/richtext.js +115 -1
- package/dist/schemas.d.ts +329 -0
- package/dist/schemas.js +62 -8
- package/dist/section-scope.d.ts +17 -0
- package/dist/section-scope.js +22 -0
- package/dist/store-path.d.ts +9 -0
- package/dist/store-path.js +13 -1
- package/dist/template-limits.d.ts +34 -0
- package/dist/template-limits.js +71 -0
- package/dist/video-url.d.ts +10 -0
- package/dist/video-url.js +36 -0
- package/package.json +8 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AiEntry, LoadedRecipe } from "./editor-tools/recipes.js";
|
|
2
|
+
//#region src/ai-files.d.ts
|
|
3
|
+
/** Every entry `aiManifest` judges: the top level, and `recipes/` when it is
|
|
4
|
+
* a directory. Nothing deeper is listed: a subdir is refused as itself. */
|
|
5
|
+
export declare function listAiDir(dir: string): Promise<AiEntry[]>;
|
|
6
|
+
export interface ThemeAiFiles {
|
|
7
|
+
guide: string;
|
|
8
|
+
recipes: LoadedRecipe[];
|
|
9
|
+
/** Every path left out, and why: refused by the rule, or a recipe that didn't parse. */
|
|
10
|
+
skipped: {
|
|
11
|
+
path: string;
|
|
12
|
+
reason: string;
|
|
13
|
+
}[];
|
|
14
|
+
/** The bytes read, guide and recipes. */
|
|
15
|
+
bytes: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function readThemeAiDir(dir: string): Promise<ThemeAiFiles>;
|
|
18
|
+
//#endregion
|
package/dist/ai-files.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { aiManifest, loadRecipes } from "./editor-tools/recipes.js";
|
|
2
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
//#region src/ai-files.ts
|
|
5
|
+
async function entriesOf(dir, prefix) {
|
|
6
|
+
const dirents = await readdir(dir, { withFileTypes: true }).catch((err) => {
|
|
7
|
+
if (err.code === "ENOENT") return [];
|
|
8
|
+
throw err;
|
|
9
|
+
});
|
|
10
|
+
const entries = [];
|
|
11
|
+
for (const d of dirents) {
|
|
12
|
+
const rel = prefix + d.name;
|
|
13
|
+
const kind = d.isFile() ? "file" : d.isDirectory() ? "dir" : "other";
|
|
14
|
+
const bytes = kind === "file" ? (await stat(path.join(dir, d.name))).size : 0;
|
|
15
|
+
entries.push({
|
|
16
|
+
path: rel,
|
|
17
|
+
kind,
|
|
18
|
+
bytes
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return entries;
|
|
22
|
+
}
|
|
23
|
+
/** Every entry `aiManifest` judges: the top level, and `recipes/` when it is
|
|
24
|
+
* a directory. Nothing deeper is listed: a subdir is refused as itself. */
|
|
25
|
+
async function listAiDir(dir) {
|
|
26
|
+
const top = await entriesOf(dir, "");
|
|
27
|
+
const recipes = top.some((e) => e.path === "recipes" && e.kind === "dir") ? await entriesOf(path.join(dir, "recipes"), "recipes/") : [];
|
|
28
|
+
return [...top, ...recipes];
|
|
29
|
+
}
|
|
30
|
+
async function readThemeAiDir(dir) {
|
|
31
|
+
const manifest = aiManifest(await listAiDir(dir));
|
|
32
|
+
const read = (rel) => readFile(path.join(dir, rel), "utf-8");
|
|
33
|
+
const [guide, bodies] = await Promise.all([manifest.guide ? read(manifest.guide.path) : "", Promise.all(manifest.recipes.map((e) => read(e.path)))]);
|
|
34
|
+
const files = Object.fromEntries(manifest.recipes.map((e, i) => [e.path.slice(8), bodies[i]]));
|
|
35
|
+
const loaded = loadRecipes(files);
|
|
36
|
+
return {
|
|
37
|
+
guide,
|
|
38
|
+
recipes: loaded.recipes,
|
|
39
|
+
skipped: [...manifest.refused, ...loaded.skipped.map(({ name, reason }) => ({
|
|
40
|
+
path: `recipes/${name}`,
|
|
41
|
+
reason: `recipes/${reason}`
|
|
42
|
+
}))],
|
|
43
|
+
bytes: Buffer.byteLength(guide) + bodies.reduce((n, b) => n + Buffer.byteLength(b), 0)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { listAiDir, readThemeAiDir };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/block-allowlist.d.ts
|
|
2
|
+
type ContainerSchema = {
|
|
3
|
+
blocks?: {
|
|
4
|
+
type: string;
|
|
5
|
+
static?: boolean;
|
|
6
|
+
}[];
|
|
7
|
+
} | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* The block types that may be inserted into a container: its schema's
|
|
10
|
+
* non-static `blocks`, without the `@app` wildcard. A container that lists
|
|
11
|
+
* `@app` at `depth` 0 (a section) or 1 (a block directly in a section) also
|
|
12
|
+
* takes every installed app block (the editor-loader serves those as
|
|
13
|
+
* `source:"app"` block schemas); deeper, `@app` means nothing, as publish lets
|
|
14
|
+
* no app block stand there.
|
|
15
|
+
*
|
|
16
|
+
* Every insert path (picker, paste, AI, the editor tools) goes through this,
|
|
17
|
+
* so none can offer a type publish would drop or the storefront would fail to
|
|
18
|
+
* render.
|
|
19
|
+
*/
|
|
20
|
+
export declare function allowedChildBlockTypes(containerSchema: ContainerSchema, blockSchemas: readonly {
|
|
21
|
+
type?: string;
|
|
22
|
+
source?: "app";
|
|
23
|
+
}[], { depth }: {
|
|
24
|
+
depth: number;
|
|
25
|
+
}): string[];
|
|
26
|
+
//#endregion
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { acceptsAppBlocks } from "./document.js";
|
|
2
|
+
//#region src/block-allowlist.ts
|
|
3
|
+
/**
|
|
4
|
+
* The block types that may be inserted into a container: its schema's
|
|
5
|
+
* non-static `blocks`, without the `@app` wildcard. A container that lists
|
|
6
|
+
* `@app` at `depth` 0 (a section) or 1 (a block directly in a section) also
|
|
7
|
+
* takes every installed app block (the editor-loader serves those as
|
|
8
|
+
* `source:"app"` block schemas); deeper, `@app` means nothing, as publish lets
|
|
9
|
+
* no app block stand there.
|
|
10
|
+
*
|
|
11
|
+
* Every insert path (picker, paste, AI, the editor tools) goes through this,
|
|
12
|
+
* so none can offer a type publish would drop or the storefront would fail to
|
|
13
|
+
* render.
|
|
14
|
+
*/
|
|
15
|
+
function allowedChildBlockTypes(containerSchema, blockSchemas, { depth }) {
|
|
16
|
+
const types = (containerSchema?.blocks ?? []).filter((b) => !b.static && b.type !== "@app").map((b) => b.type);
|
|
17
|
+
if (depth > 1 || !containerSchema || !acceptsAppBlocks(containerSchema)) return types;
|
|
18
|
+
return [...types, ...blockSchemas.flatMap((b) => b.source === "app" && b.type ? [b.type] : [])];
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { allowedChildBlockTypes };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ClosestResource, Provides } from "./schemas.js";
|
|
2
|
+
//#region src/closest-scope.d.ts
|
|
3
|
+
/** A node's providers: single ones inferred from its pickers, list ones
|
|
4
|
+
* declared under `provides`. `each` absent ⇒ single. */
|
|
5
|
+
export type Providers = Partial<Record<ClosestResource, {
|
|
6
|
+
from: string;
|
|
7
|
+
each?: readonly string[];
|
|
8
|
+
}>>;
|
|
9
|
+
/** One link of a root-first node chain: the node's providers and the id it
|
|
10
|
+
* hangs under in its parent (a section's instance id, a block's id). The
|
|
11
|
+
* root link stands for the route — its providers name the route resources
|
|
12
|
+
* the page has — and its key is never read. */
|
|
13
|
+
export interface ScopeLink {
|
|
14
|
+
providers: Providers;
|
|
15
|
+
key: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ClosestScope {
|
|
18
|
+
/** Index into the chain of the providing link; 0 is the route. */
|
|
19
|
+
index: number;
|
|
20
|
+
decl: {
|
|
21
|
+
from: string;
|
|
22
|
+
each?: readonly string[];
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare const PICKER_RESOURCES: Readonly<Record<string, ClosestResource>>;
|
|
26
|
+
/** The resources a schema provides: the FIRST picker of each resource type in
|
|
27
|
+
* schema order (Shopify's rule — `closest.product` is the nearest `product`
|
|
28
|
+
* setting), overridden per resource by an explicit `provides` entry. A second
|
|
29
|
+
* picker of the same type never provides; `provides` is how a schema says
|
|
30
|
+
* which one does. */
|
|
31
|
+
export declare function providersOf(schema: {
|
|
32
|
+
settings?: ReadonlyArray<{
|
|
33
|
+
type?: string;
|
|
34
|
+
id?: string;
|
|
35
|
+
}>;
|
|
36
|
+
provides?: Provides;
|
|
37
|
+
} | undefined): Providers;
|
|
38
|
+
/** The nearest link that provides `resource` to the LAST link of `chain`,
|
|
39
|
+
* or null when none does (the binding keeps its literal; a token blanks).
|
|
40
|
+
*
|
|
41
|
+
* - A single provider (a picker) scopes itself and every descendant.
|
|
42
|
+
* - An `each` provider scopes only the subtrees rooted at the block ids it
|
|
43
|
+
* names — never itself, never its other children.
|
|
44
|
+
* - The nearest provider wins per resource, even when its picker is unset:
|
|
45
|
+
* an empty `featured-product` shows literals rather than borrowing the
|
|
46
|
+
* route product from further up.
|
|
47
|
+
* - A mount carries ONE item id (the nearest `each`'s), so an `each` scope
|
|
48
|
+
* for one resource that sits outside a nearer `each` for another cannot
|
|
49
|
+
* be resolved per item: null, not the outer list's items. */
|
|
50
|
+
export declare function closestScopeAt(chain: readonly ScopeLink[], resource: ClosestResource): ClosestScope | null;
|
|
51
|
+
//#endregion
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//#region src/closest-scope.ts
|
|
2
|
+
const PICKER_RESOURCES = {
|
|
3
|
+
product: "product",
|
|
4
|
+
collection: "collection",
|
|
5
|
+
page: "page",
|
|
6
|
+
blog: "blog",
|
|
7
|
+
article: "article"
|
|
8
|
+
};
|
|
9
|
+
/** The resources a schema provides: the FIRST picker of each resource type in
|
|
10
|
+
* schema order (Shopify's rule — `closest.product` is the nearest `product`
|
|
11
|
+
* setting), overridden per resource by an explicit `provides` entry. A second
|
|
12
|
+
* picker of the same type never provides; `provides` is how a schema says
|
|
13
|
+
* which one does. */
|
|
14
|
+
function providersOf(schema) {
|
|
15
|
+
const out = {};
|
|
16
|
+
for (const setting of schema?.settings ?? []) {
|
|
17
|
+
const resource = setting.type ? PICKER_RESOURCES[setting.type] : void 0;
|
|
18
|
+
if (resource && setting.id && !out[resource]) out[resource] = { from: setting.id };
|
|
19
|
+
}
|
|
20
|
+
for (const [resource, decl] of Object.entries(schema?.provides ?? {})) out[resource] = decl;
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
/** True when `link` (at chain index `i`) scopes the next link through an
|
|
24
|
+
* `each` declaration for some resource — i.e. the next link is one of the
|
|
25
|
+
* per-item subtrees the list renders. */
|
|
26
|
+
function eachChildAt(chain, i) {
|
|
27
|
+
const next = chain[i + 1];
|
|
28
|
+
if (!next) return false;
|
|
29
|
+
for (const decl of Object.values(chain[i].providers)) if (decl?.each?.includes(next.key)) return true;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
/** The nearest link that provides `resource` to the LAST link of `chain`,
|
|
33
|
+
* or null when none does (the binding keeps its literal; a token blanks).
|
|
34
|
+
*
|
|
35
|
+
* - A single provider (a picker) scopes itself and every descendant.
|
|
36
|
+
* - An `each` provider scopes only the subtrees rooted at the block ids it
|
|
37
|
+
* names — never itself, never its other children.
|
|
38
|
+
* - The nearest provider wins per resource, even when its picker is unset:
|
|
39
|
+
* an empty `featured-product` shows literals rather than borrowing the
|
|
40
|
+
* route product from further up.
|
|
41
|
+
* - A mount carries ONE item id (the nearest `each`'s), so an `each` scope
|
|
42
|
+
* for one resource that sits outside a nearer `each` for another cannot
|
|
43
|
+
* be resolved per item: null, not the outer list's items. */
|
|
44
|
+
function closestScopeAt(chain, resource) {
|
|
45
|
+
const last = chain.length - 1;
|
|
46
|
+
let nearestEach = -1;
|
|
47
|
+
for (let i = last - 1; i >= 0; i--) if (eachChildAt(chain, i)) {
|
|
48
|
+
nearestEach = i;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
for (let i = last; i >= 0; i--) {
|
|
52
|
+
const decl = chain[i].providers[resource];
|
|
53
|
+
if (!decl) continue;
|
|
54
|
+
if (!decl.each) return {
|
|
55
|
+
index: i,
|
|
56
|
+
decl
|
|
57
|
+
};
|
|
58
|
+
if (i < last && decl.each.includes(chain[i + 1].key)) return i === nearestEach ? {
|
|
59
|
+
index: i,
|
|
60
|
+
decl
|
|
61
|
+
} : null;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { PICKER_RESOURCES, closestScopeAt, providersOf };
|
package/dist/document.d.ts
CHANGED
|
@@ -21,6 +21,19 @@ export interface CollectedTypes {
|
|
|
21
21
|
layouts: Set<string>;
|
|
22
22
|
}
|
|
23
23
|
export declare function defaultSettings(schemaDefs?: SchemaSetting[]): Record<string, unknown>;
|
|
24
|
+
/** The `blocks` entry a section or block schema lists to accept app blocks
|
|
25
|
+
* (Shopify's `{ "type": "@app" }`). Honoured on a section, and on a block that
|
|
26
|
+
* is a direct child of a section, so an app block is never deeper than
|
|
27
|
+
* depth 2. */
|
|
28
|
+
export declare const APP_BLOCK_WILDCARD = "@app";
|
|
29
|
+
/** The most block ancestors an app block's container may have: 0 for a
|
|
30
|
+
* section, 1 for a block directly in a section. */
|
|
31
|
+
export declare const MAX_APP_BLOCK_CONTAINER_DEPTH = 1;
|
|
32
|
+
export declare function acceptsAppBlocks(schema: {
|
|
33
|
+
blocks?: {
|
|
34
|
+
type: string;
|
|
35
|
+
}[];
|
|
36
|
+
}): boolean;
|
|
24
37
|
export declare function staticChildId(childType: string, parentType: string): string;
|
|
25
38
|
export declare function staticBlockId(type: string): string;
|
|
26
39
|
export declare function buildStaticChildren(args: {
|
package/dist/document.js
CHANGED
|
@@ -4,6 +4,17 @@ function defaultSettings(schemaDefs) {
|
|
|
4
4
|
for (const st of schemaDefs ?? []) if (st.id && st.default != null) out[st.id] = st.default;
|
|
5
5
|
return out;
|
|
6
6
|
}
|
|
7
|
+
/** The `blocks` entry a section or block schema lists to accept app blocks
|
|
8
|
+
* (Shopify's `{ "type": "@app" }`). Honoured on a section, and on a block that
|
|
9
|
+
* is a direct child of a section, so an app block is never deeper than
|
|
10
|
+
* depth 2. */
|
|
11
|
+
const APP_BLOCK_WILDCARD = "@app";
|
|
12
|
+
/** The most block ancestors an app block's container may have: 0 for a
|
|
13
|
+
* section, 1 for a block directly in a section. */
|
|
14
|
+
const MAX_APP_BLOCK_CONTAINER_DEPTH = 1;
|
|
15
|
+
function acceptsAppBlocks(schema) {
|
|
16
|
+
return schema.blocks?.some((b) => b.type === "@app") ?? false;
|
|
17
|
+
}
|
|
7
18
|
function staticChildId(childType, parentType) {
|
|
8
19
|
const cleanChild = childType.replace(/^_/, "");
|
|
9
20
|
const cleanParent = parentType.replace(/^_/, "");
|
|
@@ -17,7 +28,7 @@ function buildStaticChildren(args) {
|
|
|
17
28
|
const blocks = {};
|
|
18
29
|
const block_order = [];
|
|
19
30
|
for (const childDef of args.childDefs) {
|
|
20
|
-
if (!childDef.static) continue;
|
|
31
|
+
if (!childDef.static || childDef.type === "@app") continue;
|
|
21
32
|
const childSchema = lookupSchema(args.getBlockSchema, childDef.type);
|
|
22
33
|
const id = staticChildId(childDef.type, args.parentType);
|
|
23
34
|
const block = {
|
|
@@ -92,6 +103,7 @@ function buildPresetBlock(blockDef, getBlockSchema, idFor, parentType) {
|
|
|
92
103
|
block_order.push(...statics.block_order);
|
|
93
104
|
}
|
|
94
105
|
for (const childDef of blockDef.blocks ?? []) {
|
|
106
|
+
if (childDef.type === "@app") continue;
|
|
95
107
|
const child = buildPresetBlock(childDef, getBlockSchema, idFor, blockDef.type);
|
|
96
108
|
blocks[child.id] = child.block;
|
|
97
109
|
if (!child.isStatic) block_order.push(child.id);
|
|
@@ -140,6 +152,7 @@ function buildSectionInstance(args) {
|
|
|
140
152
|
const block_order = [];
|
|
141
153
|
const blockDefs = preset?.blocks ?? sectionSchema?.blocks ?? [];
|
|
142
154
|
for (const blockDef of blockDefs) {
|
|
155
|
+
if (blockDef.type === "@app") continue;
|
|
143
156
|
const built = buildPresetBlock(blockDef, getBlockSchema, idFor);
|
|
144
157
|
blocks[built.id] = built.block;
|
|
145
158
|
if (!built.isStatic) block_order.push(built.id);
|
|
@@ -157,11 +170,11 @@ function buildSectionInstance(args) {
|
|
|
157
170
|
function selectRenderableSections(sections, order) {
|
|
158
171
|
const out = [];
|
|
159
172
|
for (const instanceId of order) {
|
|
160
|
-
const section = sections[instanceId];
|
|
173
|
+
const section = Object.hasOwn(sections, instanceId) ? sections[instanceId] : void 0;
|
|
161
174
|
if (!section || section.disabled) continue;
|
|
162
175
|
const blocks = [];
|
|
163
176
|
for (const blockId of section.block_order ?? []) {
|
|
164
|
-
const raw = section.blocks
|
|
177
|
+
const raw = section.blocks && Object.hasOwn(section.blocks, blockId) ? section.blocks[blockId] : void 0;
|
|
165
178
|
if (!raw || raw.disabled) continue;
|
|
166
179
|
blocks.push({
|
|
167
180
|
...raw,
|
|
@@ -197,7 +210,7 @@ function selectRenderableChildren(block) {
|
|
|
197
210
|
}
|
|
198
211
|
for (const id of block.block_order ?? []) {
|
|
199
212
|
if (seen.has(id)) continue;
|
|
200
|
-
const child = block.blocks
|
|
213
|
+
const child = block.blocks && Object.hasOwn(block.blocks, id) ? block.blocks[id] : void 0;
|
|
201
214
|
if (!child || child.disabled) continue;
|
|
202
215
|
out.push({
|
|
203
216
|
...child,
|
|
@@ -212,6 +225,7 @@ function collectTypes(template) {
|
|
|
212
225
|
const layouts = /* @__PURE__ */ new Set();
|
|
213
226
|
layouts.add(template.layout ?? "main");
|
|
214
227
|
const walkBlock = (block) => {
|
|
228
|
+
if (block.source === "app") return;
|
|
215
229
|
blocks.add(block.type);
|
|
216
230
|
if (block.blocks) for (const child of Object.values(block.blocks)) walkBlock(child);
|
|
217
231
|
};
|
|
@@ -294,4 +308,4 @@ function deepClone(value) {
|
|
|
294
308
|
return JSON.parse(JSON.stringify(value));
|
|
295
309
|
}
|
|
296
310
|
//#endregion
|
|
297
|
-
export { buildBlockInstance, buildSectionInstance, buildStaticChildren, collectTypes, defaultSettings, seedMissingStatics, selectRenderableChildren, selectRenderableSections, staticBlockId, staticChildId, stripStaticFromBlockOrder };
|
|
311
|
+
export { APP_BLOCK_WILDCARD, MAX_APP_BLOCK_CONTAINER_DEPTH, acceptsAppBlocks, buildBlockInstance, buildSectionInstance, buildStaticChildren, collectTypes, defaultSettings, seedMissingStatics, selectRenderableChildren, selectRenderableSections, staticBlockId, staticChildId, stripStaticFromBlockOrder };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { BlockDefinition, SchemaSetting, TemplateSection } from "../schemas.js";
|
|
2
|
+
import { Op, ThemeDocument } from "../ops.js";
|
|
3
|
+
import { IdPolicy } from "./coerce.js";
|
|
4
|
+
import { EditorScope } from "./digest.js";
|
|
5
|
+
import { LoadedRecipe, Recipe } from "./recipes.js";
|
|
6
|
+
//#region src/editor-tools/apply.d.ts
|
|
7
|
+
type Placement = {
|
|
8
|
+
enabled_on?: {
|
|
9
|
+
groups?: string[];
|
|
10
|
+
templates?: string[];
|
|
11
|
+
};
|
|
12
|
+
disabled_on?: {
|
|
13
|
+
groups?: string[];
|
|
14
|
+
templates?: string[];
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
type AppFields = {
|
|
18
|
+
source?: "app";
|
|
19
|
+
appId?: string;
|
|
20
|
+
tag?: string;
|
|
21
|
+
scriptUrl?: string;
|
|
22
|
+
};
|
|
23
|
+
type Preset = {
|
|
24
|
+
settings?: Record<string, unknown>;
|
|
25
|
+
blocks?: BlockDefinition[];
|
|
26
|
+
};
|
|
27
|
+
export type EditorBlockSchema = AppFields & {
|
|
28
|
+
type: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
settings?: SchemaSetting[];
|
|
31
|
+
blocks?: BlockDefinition[];
|
|
32
|
+
max_blocks?: number;
|
|
33
|
+
presets?: Preset[];
|
|
34
|
+
};
|
|
35
|
+
export type EditorSectionSchema = EditorBlockSchema & Placement;
|
|
36
|
+
export interface EditorSchemas {
|
|
37
|
+
/** `config/settings-schema.json`, t: resolved. */
|
|
38
|
+
settingsSchema: readonly {
|
|
39
|
+
settings?: SchemaSetting[];
|
|
40
|
+
}[];
|
|
41
|
+
/** Theme and app sections, as the editor-loader lists them. */
|
|
42
|
+
sections: EditorSectionSchema[];
|
|
43
|
+
blocks: EditorBlockSchema[];
|
|
44
|
+
}
|
|
45
|
+
export interface EngineContext {
|
|
46
|
+
doc: ThemeDocument;
|
|
47
|
+
schemas: EditorSchemas;
|
|
48
|
+
recipes: LoadedRecipe[];
|
|
49
|
+
/** `config/ai/guide.md`; empty when the theme ships none. */
|
|
50
|
+
guide?: string;
|
|
51
|
+
sanitizeRichText: (html: string) => string;
|
|
52
|
+
images: IdPolicy;
|
|
53
|
+
fonts?: IdPolicy;
|
|
54
|
+
/** Mints a new instance id; `${type}_${8 hex}` when absent, as the editor does. */
|
|
55
|
+
idFor?: (type: string) => string;
|
|
56
|
+
}
|
|
57
|
+
export type EditorToolResult = {
|
|
58
|
+
ok: true;
|
|
59
|
+
ops: Op[];
|
|
60
|
+
result: Record<string, unknown>;
|
|
61
|
+
} | {
|
|
62
|
+
ok: false;
|
|
63
|
+
reason: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* A recipe's sections, built and coerced as `add_section` and
|
|
67
|
+
* `update_*_setting` would build and coerce them, keyed by their new ids in
|
|
68
|
+
* recipe order. Refuses the whole recipe at the first bad section, block,
|
|
69
|
+
* setting or param.
|
|
70
|
+
*/
|
|
71
|
+
export declare function expandRecipe(recipe: Recipe, params: Record<string, string | number>, ctx: EngineContext, scope?: EditorScope): {
|
|
72
|
+
ok: true;
|
|
73
|
+
sections: [string, TemplateSection][];
|
|
74
|
+
} | {
|
|
75
|
+
ok: false;
|
|
76
|
+
reason: string;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Runs one editor tool against a document: the ops that make the edit (what
|
|
80
|
+
* the admin would have sent for it, via `diffDocument`) and the result the
|
|
81
|
+
* model sees, or a refusal. Pure: no I/O, and `ctx.doc` is never mutated.
|
|
82
|
+
*/
|
|
83
|
+
export declare function applyEditorTool(name: string, args: unknown, ctx: EngineContext): EditorToolResult;
|
|
84
|
+
//#endregion
|
|
85
|
+
export {};
|