@weatherboard/cms 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -0
- package/dist/contracts/query.d.cts +3 -0
- package/dist/contracts/query.d.mts +3 -0
- package/dist/contracts/query.d.ts +3 -0
- package/dist/index.cjs +5 -1
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.cts +3 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +5 -1
- package/dist/index.mjs.map +4 -4
- package/dist/next.cjs +5 -1
- package/dist/next.cjs.map +4 -4
- package/dist/next.d.cts +3 -0
- package/dist/next.d.mts +3 -0
- package/dist/next.d.ts +3 -0
- package/dist/next.mjs +5 -1
- package/dist/next.mjs.map +4 -4
- package/dist/portable/menu-options.d.cts +8 -0
- package/dist/portable/menu-options.d.mts +8 -0
- package/dist/portable/menu-options.d.ts +8 -0
- package/dist/portable/menu.d.cts +69 -0
- package/dist/portable/menu.d.mts +69 -0
- package/dist/portable/menu.d.ts +69 -0
- package/dist/portable/sections.d.cts +50 -7
- package/dist/portable/sections.d.mts +50 -7
- package/dist/portable/sections.d.ts +50 -7
- package/dist/portable/site-content.d.cts +41 -10
- package/dist/portable/site-content.d.mts +41 -10
- package/dist/portable/site-content.d.ts +41 -10
- package/dist/sanity.cjs +1 -1
- package/dist/sanity.cjs.map +2 -2
- package/dist/sanity.mjs +1 -1
- package/dist/sanity.mjs.map +2 -2
- package/dist/server.cjs +5 -1
- package/dist/server.cjs.map +4 -4
- package/dist/server.d.cts +3 -0
- package/dist/server.d.mts +3 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.mjs +5 -1
- package/dist/server.mjs.map +4 -4
- package/dist/site-content.cjs +10 -3
- package/dist/site-content.cjs.map +4 -4
- package/dist/site-content.d.cts +2 -2
- package/dist/site-content.d.mts +2 -2
- package/dist/site-content.d.ts +2 -2
- package/dist/site-content.mjs +10 -3
- package/dist/site-content.mjs.map +4 -4
- package/package.json +2 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { MENU_LAYOUTS, MENU_DECORATIONS } from "./menu-options.js";
|
|
2
|
+
export { MENU_LAYOUTS, MENU_DECORATIONS, MENU_LAYOUT_DEFAULTS } from "./menu-options.js";
|
|
3
|
+
export type MenuLayout = (typeof MENU_LAYOUTS)[number];
|
|
4
|
+
export type MenuDecoration = (typeof MENU_DECORATIONS)[number];
|
|
5
|
+
/** CMS-owned saved shapes. `_key` remains the mapper's stable array identity. */
|
|
6
|
+
export type MenuMoney = {
|
|
7
|
+
readonly minor: number;
|
|
8
|
+
readonly currency: string;
|
|
9
|
+
};
|
|
10
|
+
export type MenuPrice = {
|
|
11
|
+
readonly _key: string;
|
|
12
|
+
readonly amount: MenuMoney;
|
|
13
|
+
readonly label?: string;
|
|
14
|
+
};
|
|
15
|
+
export type MenuExtra = {
|
|
16
|
+
readonly _key: string;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly amount: MenuMoney;
|
|
19
|
+
};
|
|
20
|
+
export type MenuItem = {
|
|
21
|
+
readonly _key: string;
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly description?: string;
|
|
24
|
+
readonly prices: readonly MenuPrice[];
|
|
25
|
+
readonly priceNote?: string;
|
|
26
|
+
readonly extras: readonly MenuExtra[];
|
|
27
|
+
readonly dietary: readonly string[];
|
|
28
|
+
readonly allergenNote?: string;
|
|
29
|
+
readonly availability: "available" | "sold-out" | "hidden";
|
|
30
|
+
};
|
|
31
|
+
export type MenuSection = {
|
|
32
|
+
readonly _key: string;
|
|
33
|
+
readonly title: string;
|
|
34
|
+
readonly service?: string;
|
|
35
|
+
readonly items: readonly MenuItem[];
|
|
36
|
+
};
|
|
37
|
+
export type MenuVariant = {
|
|
38
|
+
readonly _key: string;
|
|
39
|
+
readonly label: string;
|
|
40
|
+
readonly note?: string;
|
|
41
|
+
readonly sections: readonly MenuSection[];
|
|
42
|
+
};
|
|
43
|
+
export type MenuDocument = {
|
|
44
|
+
/** The schema discriminator is required by the consuming site's mapper. */
|
|
45
|
+
readonly _type: "menu";
|
|
46
|
+
readonly _id: string;
|
|
47
|
+
readonly _rev: string;
|
|
48
|
+
/** The real published update date, preserved verbatim for stale displays. */
|
|
49
|
+
readonly _updatedAt: string;
|
|
50
|
+
readonly title: string;
|
|
51
|
+
readonly locationKeys: readonly string[];
|
|
52
|
+
readonly variants: readonly MenuVariant[];
|
|
53
|
+
readonly note?: string;
|
|
54
|
+
/** Approval is opaque to this reader. The site validates it with its contract. */
|
|
55
|
+
readonly signOff?: unknown;
|
|
56
|
+
};
|
|
57
|
+
export type MenuSectionInput = {
|
|
58
|
+
readonly kind: "menu";
|
|
59
|
+
readonly layout: MenuLayout;
|
|
60
|
+
readonly decoration: MenuDecoration;
|
|
61
|
+
readonly menu: MenuDocument;
|
|
62
|
+
};
|
|
63
|
+
/** Uses the same scoped type/id tags as every other CMS query. */
|
|
64
|
+
export declare function menuTags(scope: string, id?: string): readonly string[];
|
|
65
|
+
/** Validate saved field shapes without inferring approval or changing copy/order. */
|
|
66
|
+
export declare function parseMenuDocument(document: unknown, where?: string): MenuDocument;
|
|
67
|
+
/** Page references use the same single snapshot before checking their identity. */
|
|
68
|
+
export declare function parseReferencedMenuDocument(document: unknown, where: string): MenuDocument | null;
|
|
69
|
+
export declare function validMenuFallback(value: unknown): value is MenuDocument | null;
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* A-1915 (A-1739 M1c). The reader's half of the shared section set that
|
|
3
3
|
* `@weatherboard/sanity-schema-base` declares in `src/sections.ts` (A-1889):
|
|
4
4
|
* `page.sections[]`, drawn from the four home section types plus
|
|
5
|
-
* `textMediaBlock`, `faqBlock` (items `faqItem`), `galleryBlock
|
|
6
|
-
* `contactBlock`.
|
|
5
|
+
* `textMediaBlock`, `faqBlock` (items `faqItem`), `galleryBlock`,
|
|
6
|
+
* `contactBlock`, `textItemsBlock`, `stepsBlock` and `linkListBlock`.
|
|
7
7
|
*
|
|
8
8
|
* ===========================================================================
|
|
9
9
|
* LAYOUTS ARE THE A-1876 VARIANT NAMES, EXACTLY
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
* whose every photo was dropped, a contact
|
|
34
34
|
* section switched off and empty) is skipped
|
|
35
35
|
*/
|
|
36
|
-
import type {
|
|
36
|
+
import type { MenuSectionInput } from "./menu.cjs";
|
|
37
|
+
import type { ActionInput, CompositionBlockInput, CopyInput, RichBody } from "./site-content.cjs";
|
|
37
38
|
/** `Split`: text with media. */
|
|
38
39
|
export declare const SPLIT_LAYOUTS: readonly ["media-end", "media-start", "media-below"];
|
|
39
40
|
/** `HeadingList`: a heading column and a list column. */
|
|
@@ -59,6 +60,10 @@ export declare const READER_SECTION_KINDS: {
|
|
|
59
60
|
readonly faqBlock: "faq";
|
|
60
61
|
readonly galleryBlock: "gallery";
|
|
61
62
|
readonly contactBlock: "contact";
|
|
63
|
+
readonly textItemsBlock: "text_items";
|
|
64
|
+
readonly stepsBlock: "steps";
|
|
65
|
+
readonly linkListBlock: "link_list";
|
|
66
|
+
readonly menuBlock: "menu";
|
|
62
67
|
};
|
|
63
68
|
/** A gallery is a `StaggerRow`, which holds two to four items. */
|
|
64
69
|
export declare const READER_GALLERY_PHOTO_MIN = 2;
|
|
@@ -69,6 +74,7 @@ export declare const SECTION_WARNINGS: {
|
|
|
69
74
|
readonly imageMissingAlt: "section.image.missing-alt";
|
|
70
75
|
readonly imageMissingAsset: "section.image.missing-asset";
|
|
71
76
|
readonly emptySkipped: "section.empty.skipped";
|
|
77
|
+
readonly menuUnavailable: "section.menu.unavailable";
|
|
72
78
|
};
|
|
73
79
|
export type SectionWarningCode = (typeof SECTION_WARNINGS)[keyof typeof SECTION_WARNINGS];
|
|
74
80
|
/** A named warning and the path in the stored document it is about. Never copy. */
|
|
@@ -77,7 +83,10 @@ export type SectionWarning = {
|
|
|
77
83
|
readonly where: string;
|
|
78
84
|
readonly type: string;
|
|
79
85
|
} | {
|
|
80
|
-
readonly code:
|
|
86
|
+
readonly code: typeof SECTION_WARNINGS.menuUnavailable;
|
|
87
|
+
readonly where: string;
|
|
88
|
+
} | {
|
|
89
|
+
readonly code: Exclude<SectionWarningCode, typeof SECTION_WARNINGS.unknownType | typeof SECTION_WARNINGS.menuUnavailable>;
|
|
81
90
|
readonly where: string;
|
|
82
91
|
};
|
|
83
92
|
/** A Sanity image as a site needs it. `asset` is the image asset's id; `alt` is never empty. */
|
|
@@ -105,12 +114,15 @@ export type FaqSectionInput = {
|
|
|
105
114
|
readonly kind: "faq";
|
|
106
115
|
readonly anchor: string | null;
|
|
107
116
|
readonly layout: HeadingListLayout;
|
|
108
|
-
readonly heading: CopyInput;
|
|
117
|
+
readonly heading: CopyInput | null;
|
|
109
118
|
readonly tail: CopyInput | null;
|
|
110
119
|
readonly intro: CopyInput | null;
|
|
111
120
|
readonly items: readonly {
|
|
112
121
|
readonly question: CopyInput;
|
|
113
|
-
readonly answer: CopyInput
|
|
122
|
+
readonly answer: CopyInput | {
|
|
123
|
+
readonly kind: "rich";
|
|
124
|
+
readonly body: RichBody;
|
|
125
|
+
};
|
|
114
126
|
}[];
|
|
115
127
|
};
|
|
116
128
|
export type GallerySectionInput = {
|
|
@@ -133,7 +145,38 @@ export type ContactSectionInput = {
|
|
|
133
145
|
readonly action: ActionInput | null;
|
|
134
146
|
readonly image: SectionImageInput | null;
|
|
135
147
|
};
|
|
136
|
-
export type
|
|
148
|
+
export type TextItemsSectionInput = {
|
|
149
|
+
readonly kind: "text_items";
|
|
150
|
+
readonly anchor: string | null;
|
|
151
|
+
readonly layout: StaggerRowLayout;
|
|
152
|
+
readonly items: readonly {
|
|
153
|
+
readonly title: CopyInput;
|
|
154
|
+
readonly body: CopyInput;
|
|
155
|
+
}[];
|
|
156
|
+
};
|
|
157
|
+
export type StepsSectionInput = {
|
|
158
|
+
readonly kind: "steps";
|
|
159
|
+
readonly anchor: string | null;
|
|
160
|
+
readonly layout: HeadingListLayout;
|
|
161
|
+
readonly heading: CopyInput;
|
|
162
|
+
readonly intro: CopyInput;
|
|
163
|
+
readonly items: readonly {
|
|
164
|
+
readonly title: CopyInput;
|
|
165
|
+
readonly body: CopyInput;
|
|
166
|
+
}[];
|
|
167
|
+
};
|
|
168
|
+
export type LinkListSectionInput = {
|
|
169
|
+
readonly kind: "link_list";
|
|
170
|
+
readonly anchor: string | null;
|
|
171
|
+
readonly layout: HeadingListLayout;
|
|
172
|
+
readonly heading: CopyInput;
|
|
173
|
+
readonly tail: CopyInput | null;
|
|
174
|
+
readonly links: readonly {
|
|
175
|
+
readonly title: string;
|
|
176
|
+
readonly href: string;
|
|
177
|
+
}[];
|
|
178
|
+
};
|
|
179
|
+
export type NewSectionInput = TextMediaSectionInput | FaqSectionInput | GallerySectionInput | ContactSectionInput | TextItemsSectionInput | StepsSectionInput | LinkListSectionInput | MenuSectionInput;
|
|
137
180
|
/** One entry of `PageContent.sections`: a home section kind or one of the new kinds. */
|
|
138
181
|
export type PageSectionInput = Exclude<CompositionBlockInput, {
|
|
139
182
|
readonly kind: "campaign";
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* A-1915 (A-1739 M1c). The reader's half of the shared section set that
|
|
3
3
|
* `@weatherboard/sanity-schema-base` declares in `src/sections.ts` (A-1889):
|
|
4
4
|
* `page.sections[]`, drawn from the four home section types plus
|
|
5
|
-
* `textMediaBlock`, `faqBlock` (items `faqItem`), `galleryBlock
|
|
6
|
-
* `contactBlock`.
|
|
5
|
+
* `textMediaBlock`, `faqBlock` (items `faqItem`), `galleryBlock`,
|
|
6
|
+
* `contactBlock`, `textItemsBlock`, `stepsBlock` and `linkListBlock`.
|
|
7
7
|
*
|
|
8
8
|
* ===========================================================================
|
|
9
9
|
* LAYOUTS ARE THE A-1876 VARIANT NAMES, EXACTLY
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
* whose every photo was dropped, a contact
|
|
34
34
|
* section switched off and empty) is skipped
|
|
35
35
|
*/
|
|
36
|
-
import type {
|
|
36
|
+
import type { MenuSectionInput } from "./menu.mjs";
|
|
37
|
+
import type { ActionInput, CompositionBlockInput, CopyInput, RichBody } from "./site-content.mjs";
|
|
37
38
|
/** `Split`: text with media. */
|
|
38
39
|
export declare const SPLIT_LAYOUTS: readonly ["media-end", "media-start", "media-below"];
|
|
39
40
|
/** `HeadingList`: a heading column and a list column. */
|
|
@@ -59,6 +60,10 @@ export declare const READER_SECTION_KINDS: {
|
|
|
59
60
|
readonly faqBlock: "faq";
|
|
60
61
|
readonly galleryBlock: "gallery";
|
|
61
62
|
readonly contactBlock: "contact";
|
|
63
|
+
readonly textItemsBlock: "text_items";
|
|
64
|
+
readonly stepsBlock: "steps";
|
|
65
|
+
readonly linkListBlock: "link_list";
|
|
66
|
+
readonly menuBlock: "menu";
|
|
62
67
|
};
|
|
63
68
|
/** A gallery is a `StaggerRow`, which holds two to four items. */
|
|
64
69
|
export declare const READER_GALLERY_PHOTO_MIN = 2;
|
|
@@ -69,6 +74,7 @@ export declare const SECTION_WARNINGS: {
|
|
|
69
74
|
readonly imageMissingAlt: "section.image.missing-alt";
|
|
70
75
|
readonly imageMissingAsset: "section.image.missing-asset";
|
|
71
76
|
readonly emptySkipped: "section.empty.skipped";
|
|
77
|
+
readonly menuUnavailable: "section.menu.unavailable";
|
|
72
78
|
};
|
|
73
79
|
export type SectionWarningCode = (typeof SECTION_WARNINGS)[keyof typeof SECTION_WARNINGS];
|
|
74
80
|
/** A named warning and the path in the stored document it is about. Never copy. */
|
|
@@ -77,7 +83,10 @@ export type SectionWarning = {
|
|
|
77
83
|
readonly where: string;
|
|
78
84
|
readonly type: string;
|
|
79
85
|
} | {
|
|
80
|
-
readonly code:
|
|
86
|
+
readonly code: typeof SECTION_WARNINGS.menuUnavailable;
|
|
87
|
+
readonly where: string;
|
|
88
|
+
} | {
|
|
89
|
+
readonly code: Exclude<SectionWarningCode, typeof SECTION_WARNINGS.unknownType | typeof SECTION_WARNINGS.menuUnavailable>;
|
|
81
90
|
readonly where: string;
|
|
82
91
|
};
|
|
83
92
|
/** A Sanity image as a site needs it. `asset` is the image asset's id; `alt` is never empty. */
|
|
@@ -105,12 +114,15 @@ export type FaqSectionInput = {
|
|
|
105
114
|
readonly kind: "faq";
|
|
106
115
|
readonly anchor: string | null;
|
|
107
116
|
readonly layout: HeadingListLayout;
|
|
108
|
-
readonly heading: CopyInput;
|
|
117
|
+
readonly heading: CopyInput | null;
|
|
109
118
|
readonly tail: CopyInput | null;
|
|
110
119
|
readonly intro: CopyInput | null;
|
|
111
120
|
readonly items: readonly {
|
|
112
121
|
readonly question: CopyInput;
|
|
113
|
-
readonly answer: CopyInput
|
|
122
|
+
readonly answer: CopyInput | {
|
|
123
|
+
readonly kind: "rich";
|
|
124
|
+
readonly body: RichBody;
|
|
125
|
+
};
|
|
114
126
|
}[];
|
|
115
127
|
};
|
|
116
128
|
export type GallerySectionInput = {
|
|
@@ -133,7 +145,38 @@ export type ContactSectionInput = {
|
|
|
133
145
|
readonly action: ActionInput | null;
|
|
134
146
|
readonly image: SectionImageInput | null;
|
|
135
147
|
};
|
|
136
|
-
export type
|
|
148
|
+
export type TextItemsSectionInput = {
|
|
149
|
+
readonly kind: "text_items";
|
|
150
|
+
readonly anchor: string | null;
|
|
151
|
+
readonly layout: StaggerRowLayout;
|
|
152
|
+
readonly items: readonly {
|
|
153
|
+
readonly title: CopyInput;
|
|
154
|
+
readonly body: CopyInput;
|
|
155
|
+
}[];
|
|
156
|
+
};
|
|
157
|
+
export type StepsSectionInput = {
|
|
158
|
+
readonly kind: "steps";
|
|
159
|
+
readonly anchor: string | null;
|
|
160
|
+
readonly layout: HeadingListLayout;
|
|
161
|
+
readonly heading: CopyInput;
|
|
162
|
+
readonly intro: CopyInput;
|
|
163
|
+
readonly items: readonly {
|
|
164
|
+
readonly title: CopyInput;
|
|
165
|
+
readonly body: CopyInput;
|
|
166
|
+
}[];
|
|
167
|
+
};
|
|
168
|
+
export type LinkListSectionInput = {
|
|
169
|
+
readonly kind: "link_list";
|
|
170
|
+
readonly anchor: string | null;
|
|
171
|
+
readonly layout: HeadingListLayout;
|
|
172
|
+
readonly heading: CopyInput;
|
|
173
|
+
readonly tail: CopyInput | null;
|
|
174
|
+
readonly links: readonly {
|
|
175
|
+
readonly title: string;
|
|
176
|
+
readonly href: string;
|
|
177
|
+
}[];
|
|
178
|
+
};
|
|
179
|
+
export type NewSectionInput = TextMediaSectionInput | FaqSectionInput | GallerySectionInput | ContactSectionInput | TextItemsSectionInput | StepsSectionInput | LinkListSectionInput | MenuSectionInput;
|
|
137
180
|
/** One entry of `PageContent.sections`: a home section kind or one of the new kinds. */
|
|
138
181
|
export type PageSectionInput = Exclude<CompositionBlockInput, {
|
|
139
182
|
readonly kind: "campaign";
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* A-1915 (A-1739 M1c). The reader's half of the shared section set that
|
|
3
3
|
* `@weatherboard/sanity-schema-base` declares in `src/sections.ts` (A-1889):
|
|
4
4
|
* `page.sections[]`, drawn from the four home section types plus
|
|
5
|
-
* `textMediaBlock`, `faqBlock` (items `faqItem`), `galleryBlock
|
|
6
|
-
* `contactBlock`.
|
|
5
|
+
* `textMediaBlock`, `faqBlock` (items `faqItem`), `galleryBlock`,
|
|
6
|
+
* `contactBlock`, `textItemsBlock`, `stepsBlock` and `linkListBlock`.
|
|
7
7
|
*
|
|
8
8
|
* ===========================================================================
|
|
9
9
|
* LAYOUTS ARE THE A-1876 VARIANT NAMES, EXACTLY
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
* whose every photo was dropped, a contact
|
|
34
34
|
* section switched off and empty) is skipped
|
|
35
35
|
*/
|
|
36
|
-
import type {
|
|
36
|
+
import type { MenuSectionInput } from "./menu.js";
|
|
37
|
+
import type { ActionInput, CompositionBlockInput, CopyInput, RichBody } from "./site-content.js";
|
|
37
38
|
/** `Split`: text with media. */
|
|
38
39
|
export declare const SPLIT_LAYOUTS: readonly ["media-end", "media-start", "media-below"];
|
|
39
40
|
/** `HeadingList`: a heading column and a list column. */
|
|
@@ -59,6 +60,10 @@ export declare const READER_SECTION_KINDS: {
|
|
|
59
60
|
readonly faqBlock: "faq";
|
|
60
61
|
readonly galleryBlock: "gallery";
|
|
61
62
|
readonly contactBlock: "contact";
|
|
63
|
+
readonly textItemsBlock: "text_items";
|
|
64
|
+
readonly stepsBlock: "steps";
|
|
65
|
+
readonly linkListBlock: "link_list";
|
|
66
|
+
readonly menuBlock: "menu";
|
|
62
67
|
};
|
|
63
68
|
/** A gallery is a `StaggerRow`, which holds two to four items. */
|
|
64
69
|
export declare const READER_GALLERY_PHOTO_MIN = 2;
|
|
@@ -69,6 +74,7 @@ export declare const SECTION_WARNINGS: {
|
|
|
69
74
|
readonly imageMissingAlt: "section.image.missing-alt";
|
|
70
75
|
readonly imageMissingAsset: "section.image.missing-asset";
|
|
71
76
|
readonly emptySkipped: "section.empty.skipped";
|
|
77
|
+
readonly menuUnavailable: "section.menu.unavailable";
|
|
72
78
|
};
|
|
73
79
|
export type SectionWarningCode = (typeof SECTION_WARNINGS)[keyof typeof SECTION_WARNINGS];
|
|
74
80
|
/** A named warning and the path in the stored document it is about. Never copy. */
|
|
@@ -77,7 +83,10 @@ export type SectionWarning = {
|
|
|
77
83
|
readonly where: string;
|
|
78
84
|
readonly type: string;
|
|
79
85
|
} | {
|
|
80
|
-
readonly code:
|
|
86
|
+
readonly code: typeof SECTION_WARNINGS.menuUnavailable;
|
|
87
|
+
readonly where: string;
|
|
88
|
+
} | {
|
|
89
|
+
readonly code: Exclude<SectionWarningCode, typeof SECTION_WARNINGS.unknownType | typeof SECTION_WARNINGS.menuUnavailable>;
|
|
81
90
|
readonly where: string;
|
|
82
91
|
};
|
|
83
92
|
/** A Sanity image as a site needs it. `asset` is the image asset's id; `alt` is never empty. */
|
|
@@ -105,12 +114,15 @@ export type FaqSectionInput = {
|
|
|
105
114
|
readonly kind: "faq";
|
|
106
115
|
readonly anchor: string | null;
|
|
107
116
|
readonly layout: HeadingListLayout;
|
|
108
|
-
readonly heading: CopyInput;
|
|
117
|
+
readonly heading: CopyInput | null;
|
|
109
118
|
readonly tail: CopyInput | null;
|
|
110
119
|
readonly intro: CopyInput | null;
|
|
111
120
|
readonly items: readonly {
|
|
112
121
|
readonly question: CopyInput;
|
|
113
|
-
readonly answer: CopyInput
|
|
122
|
+
readonly answer: CopyInput | {
|
|
123
|
+
readonly kind: "rich";
|
|
124
|
+
readonly body: RichBody;
|
|
125
|
+
};
|
|
114
126
|
}[];
|
|
115
127
|
};
|
|
116
128
|
export type GallerySectionInput = {
|
|
@@ -133,7 +145,38 @@ export type ContactSectionInput = {
|
|
|
133
145
|
readonly action: ActionInput | null;
|
|
134
146
|
readonly image: SectionImageInput | null;
|
|
135
147
|
};
|
|
136
|
-
export type
|
|
148
|
+
export type TextItemsSectionInput = {
|
|
149
|
+
readonly kind: "text_items";
|
|
150
|
+
readonly anchor: string | null;
|
|
151
|
+
readonly layout: StaggerRowLayout;
|
|
152
|
+
readonly items: readonly {
|
|
153
|
+
readonly title: CopyInput;
|
|
154
|
+
readonly body: CopyInput;
|
|
155
|
+
}[];
|
|
156
|
+
};
|
|
157
|
+
export type StepsSectionInput = {
|
|
158
|
+
readonly kind: "steps";
|
|
159
|
+
readonly anchor: string | null;
|
|
160
|
+
readonly layout: HeadingListLayout;
|
|
161
|
+
readonly heading: CopyInput;
|
|
162
|
+
readonly intro: CopyInput;
|
|
163
|
+
readonly items: readonly {
|
|
164
|
+
readonly title: CopyInput;
|
|
165
|
+
readonly body: CopyInput;
|
|
166
|
+
}[];
|
|
167
|
+
};
|
|
168
|
+
export type LinkListSectionInput = {
|
|
169
|
+
readonly kind: "link_list";
|
|
170
|
+
readonly anchor: string | null;
|
|
171
|
+
readonly layout: HeadingListLayout;
|
|
172
|
+
readonly heading: CopyInput;
|
|
173
|
+
readonly tail: CopyInput | null;
|
|
174
|
+
readonly links: readonly {
|
|
175
|
+
readonly title: string;
|
|
176
|
+
readonly href: string;
|
|
177
|
+
}[];
|
|
178
|
+
};
|
|
179
|
+
export type NewSectionInput = TextMediaSectionInput | FaqSectionInput | GallerySectionInput | ContactSectionInput | TextItemsSectionInput | StepsSectionInput | LinkListSectionInput | MenuSectionInput;
|
|
137
180
|
/** One entry of `PageContent.sections`: a home section kind or one of the new kinds. */
|
|
138
181
|
export type PageSectionInput = Exclude<CompositionBlockInput, {
|
|
139
182
|
readonly kind: "campaign";
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
* anything writing over the API skips them entirely. So every document is
|
|
21
21
|
* checked here again, and a malformed one is refused BY NAME
|
|
22
22
|
* (`MalformedContentError`, code `malformed-content`) listing every problem
|
|
23
|
-
* with its path, rather than rendered half-empty. A
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* with its path, rather than rendered half-empty. A missing home headline
|
|
24
|
+
* uses siteHero only when the home document explicitly selects it and that
|
|
25
|
+
* document supplies a valid heading. Otherwise the home page is refused.
|
|
26
26
|
*
|
|
27
27
|
* Absent is not malformed: `null` from `home()` means nobody has created the
|
|
28
28
|
* home page yet, and a site decides what to do about that.
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
* kinds, the A-1876 layout names and the warning codes.
|
|
61
61
|
*
|
|
62
62
|
* Two places the composition cannot carry a proposed marker: a `cta` block's
|
|
63
|
-
* plain strings, and the campaign's title and body (which travel as
|
|
63
|
+
* plain strings, and the campaign's title and body (which travel as rich body content).
|
|
64
64
|
* There the text is returned plain and the line is still listed in
|
|
65
65
|
* `proposedCopy`, which is the list a dashboard and a site both read for
|
|
66
66
|
* approval. Nothing is dropped silently.
|
|
@@ -71,6 +71,30 @@ export type CopyInput = string | {
|
|
|
71
71
|
readonly text: string;
|
|
72
72
|
readonly copySource: "captured" | "proposed";
|
|
73
73
|
};
|
|
74
|
+
export type RichInline = {
|
|
75
|
+
readonly children: readonly {
|
|
76
|
+
readonly text: string;
|
|
77
|
+
readonly marks: readonly string[];
|
|
78
|
+
}[];
|
|
79
|
+
readonly markDefs: readonly {
|
|
80
|
+
readonly key: string;
|
|
81
|
+
readonly href: string;
|
|
82
|
+
}[];
|
|
83
|
+
};
|
|
84
|
+
export type RichBodyPart = {
|
|
85
|
+
readonly kind: "block";
|
|
86
|
+
readonly style: "normal" | "h2" | "h3" | "blockquote";
|
|
87
|
+
readonly listItem: "bullet" | "number" | null;
|
|
88
|
+
readonly level: number;
|
|
89
|
+
readonly content: RichInline;
|
|
90
|
+
} | {
|
|
91
|
+
readonly kind: "heading";
|
|
92
|
+
readonly text: string;
|
|
93
|
+
} | {
|
|
94
|
+
readonly kind: "table";
|
|
95
|
+
readonly rows: readonly (readonly RichInline[])[];
|
|
96
|
+
};
|
|
97
|
+
export type RichBody = string | readonly RichBodyPart[];
|
|
74
98
|
export type ActionInput = {
|
|
75
99
|
readonly label: CopyInput;
|
|
76
100
|
readonly href: string;
|
|
@@ -110,12 +134,14 @@ export type CompositionBlockInput = {
|
|
|
110
134
|
} | {
|
|
111
135
|
readonly kind: "accent_band";
|
|
112
136
|
readonly heading: CopyInput;
|
|
137
|
+
readonly tail?: CopyInput | null;
|
|
113
138
|
readonly body: CopyInput;
|
|
114
139
|
readonly action: ActionInput | null;
|
|
115
140
|
readonly image: string | null;
|
|
116
141
|
} | {
|
|
117
142
|
readonly kind: "cta";
|
|
118
143
|
readonly heading: string;
|
|
144
|
+
readonly image?: string | null;
|
|
119
145
|
readonly body: string | null;
|
|
120
146
|
readonly link: {
|
|
121
147
|
readonly label: string;
|
|
@@ -136,16 +162,17 @@ export type ProposedCopy = {
|
|
|
136
162
|
export type CampaignState = "shown" | "off" | "not-started" | "ended" | "none";
|
|
137
163
|
export type HomeContent = {
|
|
138
164
|
readonly composition: CompositionInput;
|
|
139
|
-
/**
|
|
140
|
-
readonly body:
|
|
165
|
+
/** Campaign text as Markdown, or structured blocks and tables when it contains a table. */
|
|
166
|
+
readonly body: RichBody;
|
|
141
167
|
readonly campaign: CampaignState;
|
|
142
168
|
readonly proposedCopy: readonly ProposedCopy[];
|
|
143
169
|
};
|
|
144
170
|
export type PageContent = {
|
|
145
171
|
readonly slug: string;
|
|
146
172
|
readonly title: string;
|
|
173
|
+
readonly titleTail?: string;
|
|
147
174
|
readonly composition: CompositionInput;
|
|
148
|
-
readonly body:
|
|
175
|
+
readonly body: RichBody;
|
|
149
176
|
readonly migrated: boolean;
|
|
150
177
|
readonly proposedCopy: readonly ProposedCopy[];
|
|
151
178
|
/**
|
|
@@ -191,8 +218,10 @@ export declare const READER_SITE_SETTINGS_ID = "siteSettings";
|
|
|
191
218
|
* upload one yet (the dashboard slice decides how), so one arriving means
|
|
192
219
|
* something wrote the document by hand, and a page silently missing a picture
|
|
193
220
|
* is the failure this reader exists to prevent.
|
|
221
|
+
* Text-only input remains a Markdown string. Input with a table produces
|
|
222
|
+
* ordered structured blocks and cells that never enter a Markdown parser.
|
|
194
223
|
*/
|
|
195
|
-
export declare function portableTextToMarkdown(value: unknown):
|
|
224
|
+
export declare function portableTextToMarkdown(value: unknown): RichBody;
|
|
196
225
|
/** Whether a campaign is shown at `now`, and if not, why not. */
|
|
197
226
|
export declare function campaignState(campaign: unknown, now: Date): CampaignState;
|
|
198
227
|
/**
|
|
@@ -208,8 +237,10 @@ export declare function parseContentPage(document: unknown): PageContent;
|
|
|
208
237
|
export declare function parseSiteSettings(document: unknown): SiteSettingsContent;
|
|
209
238
|
/** By fixed id: a second home page at a random id is never read. */
|
|
210
239
|
export declare const HOME_PAGE_DOCUMENT_QUERY = "*[_id == $id][0]";
|
|
211
|
-
|
|
212
|
-
export declare const
|
|
240
|
+
/** One snapshot contains both possible headline sources. */
|
|
241
|
+
export declare const HOME_PAGE_WITH_SITE_HERO_QUERY = "*[_id == $id][0]{..., \"_siteHero\": *[_id == \"siteHero\" && _type == \"siteHero\"][0]{heading}}";
|
|
242
|
+
export declare const CONTENT_PAGES_QUERY = "*[_type == \"page\" && defined(slug.current)]|order(slug.current asc){..., \"sections\": coalesce(sections[]{..., _type == \"menuBlock\" => {menu->{_type, _id, _rev, _updatedAt, title, locationKeys, note, signOff,\n variants[]{_key, label, note, sections[]{_key, title, service, items[]{\n _key, name, description, priceNote, dietary, allergenNote, availability,\n prices[]{_key, label, amount{minor, currency}}, extras[]{_key, name, amount{minor, currency}}\n }}}}}, _type == \"linkListBlock\" => {links[]{..., \"page\": page->{title, \"slug\": slug.current}}}}, sections)}";
|
|
243
|
+
export declare const CONTENT_PAGE_BY_SLUG_QUERY = "*[_type == \"page\" && slug.current == $slug][0]{..., \"sections\": coalesce(sections[]{..., _type == \"menuBlock\" => {menu->{_type, _id, _rev, _updatedAt, title, locationKeys, note, signOff,\n variants[]{_key, label, note, sections[]{_key, title, service, items[]{\n _key, name, description, priceNote, dietary, allergenNote, availability,\n prices[]{_key, label, amount{minor, currency}}, extras[]{_key, name, amount{minor, currency}}\n }}}}}, _type == \"linkListBlock\" => {links[]{..., \"page\": page->{title, \"slug\": slug.current}}}}, sections)}";
|
|
213
244
|
export interface SiteContentReader {
|
|
214
245
|
/** `null` when no home page document exists yet. */
|
|
215
246
|
home: (options: {
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
* anything writing over the API skips them entirely. So every document is
|
|
21
21
|
* checked here again, and a malformed one is refused BY NAME
|
|
22
22
|
* (`MalformedContentError`, code `malformed-content`) listing every problem
|
|
23
|
-
* with its path, rather than rendered half-empty. A
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* with its path, rather than rendered half-empty. A missing home headline
|
|
24
|
+
* uses siteHero only when the home document explicitly selects it and that
|
|
25
|
+
* document supplies a valid heading. Otherwise the home page is refused.
|
|
26
26
|
*
|
|
27
27
|
* Absent is not malformed: `null` from `home()` means nobody has created the
|
|
28
28
|
* home page yet, and a site decides what to do about that.
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
* kinds, the A-1876 layout names and the warning codes.
|
|
61
61
|
*
|
|
62
62
|
* Two places the composition cannot carry a proposed marker: a `cta` block's
|
|
63
|
-
* plain strings, and the campaign's title and body (which travel as
|
|
63
|
+
* plain strings, and the campaign's title and body (which travel as rich body content).
|
|
64
64
|
* There the text is returned plain and the line is still listed in
|
|
65
65
|
* `proposedCopy`, which is the list a dashboard and a site both read for
|
|
66
66
|
* approval. Nothing is dropped silently.
|
|
@@ -71,6 +71,30 @@ export type CopyInput = string | {
|
|
|
71
71
|
readonly text: string;
|
|
72
72
|
readonly copySource: "captured" | "proposed";
|
|
73
73
|
};
|
|
74
|
+
export type RichInline = {
|
|
75
|
+
readonly children: readonly {
|
|
76
|
+
readonly text: string;
|
|
77
|
+
readonly marks: readonly string[];
|
|
78
|
+
}[];
|
|
79
|
+
readonly markDefs: readonly {
|
|
80
|
+
readonly key: string;
|
|
81
|
+
readonly href: string;
|
|
82
|
+
}[];
|
|
83
|
+
};
|
|
84
|
+
export type RichBodyPart = {
|
|
85
|
+
readonly kind: "block";
|
|
86
|
+
readonly style: "normal" | "h2" | "h3" | "blockquote";
|
|
87
|
+
readonly listItem: "bullet" | "number" | null;
|
|
88
|
+
readonly level: number;
|
|
89
|
+
readonly content: RichInline;
|
|
90
|
+
} | {
|
|
91
|
+
readonly kind: "heading";
|
|
92
|
+
readonly text: string;
|
|
93
|
+
} | {
|
|
94
|
+
readonly kind: "table";
|
|
95
|
+
readonly rows: readonly (readonly RichInline[])[];
|
|
96
|
+
};
|
|
97
|
+
export type RichBody = string | readonly RichBodyPart[];
|
|
74
98
|
export type ActionInput = {
|
|
75
99
|
readonly label: CopyInput;
|
|
76
100
|
readonly href: string;
|
|
@@ -110,12 +134,14 @@ export type CompositionBlockInput = {
|
|
|
110
134
|
} | {
|
|
111
135
|
readonly kind: "accent_band";
|
|
112
136
|
readonly heading: CopyInput;
|
|
137
|
+
readonly tail?: CopyInput | null;
|
|
113
138
|
readonly body: CopyInput;
|
|
114
139
|
readonly action: ActionInput | null;
|
|
115
140
|
readonly image: string | null;
|
|
116
141
|
} | {
|
|
117
142
|
readonly kind: "cta";
|
|
118
143
|
readonly heading: string;
|
|
144
|
+
readonly image?: string | null;
|
|
119
145
|
readonly body: string | null;
|
|
120
146
|
readonly link: {
|
|
121
147
|
readonly label: string;
|
|
@@ -136,16 +162,17 @@ export type ProposedCopy = {
|
|
|
136
162
|
export type CampaignState = "shown" | "off" | "not-started" | "ended" | "none";
|
|
137
163
|
export type HomeContent = {
|
|
138
164
|
readonly composition: CompositionInput;
|
|
139
|
-
/**
|
|
140
|
-
readonly body:
|
|
165
|
+
/** Campaign text as Markdown, or structured blocks and tables when it contains a table. */
|
|
166
|
+
readonly body: RichBody;
|
|
141
167
|
readonly campaign: CampaignState;
|
|
142
168
|
readonly proposedCopy: readonly ProposedCopy[];
|
|
143
169
|
};
|
|
144
170
|
export type PageContent = {
|
|
145
171
|
readonly slug: string;
|
|
146
172
|
readonly title: string;
|
|
173
|
+
readonly titleTail?: string;
|
|
147
174
|
readonly composition: CompositionInput;
|
|
148
|
-
readonly body:
|
|
175
|
+
readonly body: RichBody;
|
|
149
176
|
readonly migrated: boolean;
|
|
150
177
|
readonly proposedCopy: readonly ProposedCopy[];
|
|
151
178
|
/**
|
|
@@ -191,8 +218,10 @@ export declare const READER_SITE_SETTINGS_ID = "siteSettings";
|
|
|
191
218
|
* upload one yet (the dashboard slice decides how), so one arriving means
|
|
192
219
|
* something wrote the document by hand, and a page silently missing a picture
|
|
193
220
|
* is the failure this reader exists to prevent.
|
|
221
|
+
* Text-only input remains a Markdown string. Input with a table produces
|
|
222
|
+
* ordered structured blocks and cells that never enter a Markdown parser.
|
|
194
223
|
*/
|
|
195
|
-
export declare function portableTextToMarkdown(value: unknown):
|
|
224
|
+
export declare function portableTextToMarkdown(value: unknown): RichBody;
|
|
196
225
|
/** Whether a campaign is shown at `now`, and if not, why not. */
|
|
197
226
|
export declare function campaignState(campaign: unknown, now: Date): CampaignState;
|
|
198
227
|
/**
|
|
@@ -208,8 +237,10 @@ export declare function parseContentPage(document: unknown): PageContent;
|
|
|
208
237
|
export declare function parseSiteSettings(document: unknown): SiteSettingsContent;
|
|
209
238
|
/** By fixed id: a second home page at a random id is never read. */
|
|
210
239
|
export declare const HOME_PAGE_DOCUMENT_QUERY = "*[_id == $id][0]";
|
|
211
|
-
|
|
212
|
-
export declare const
|
|
240
|
+
/** One snapshot contains both possible headline sources. */
|
|
241
|
+
export declare const HOME_PAGE_WITH_SITE_HERO_QUERY = "*[_id == $id][0]{..., \"_siteHero\": *[_id == \"siteHero\" && _type == \"siteHero\"][0]{heading}}";
|
|
242
|
+
export declare const CONTENT_PAGES_QUERY = "*[_type == \"page\" && defined(slug.current)]|order(slug.current asc){..., \"sections\": coalesce(sections[]{..., _type == \"menuBlock\" => {menu->{_type, _id, _rev, _updatedAt, title, locationKeys, note, signOff,\n variants[]{_key, label, note, sections[]{_key, title, service, items[]{\n _key, name, description, priceNote, dietary, allergenNote, availability,\n prices[]{_key, label, amount{minor, currency}}, extras[]{_key, name, amount{minor, currency}}\n }}}}}, _type == \"linkListBlock\" => {links[]{..., \"page\": page->{title, \"slug\": slug.current}}}}, sections)}";
|
|
243
|
+
export declare const CONTENT_PAGE_BY_SLUG_QUERY = "*[_type == \"page\" && slug.current == $slug][0]{..., \"sections\": coalesce(sections[]{..., _type == \"menuBlock\" => {menu->{_type, _id, _rev, _updatedAt, title, locationKeys, note, signOff,\n variants[]{_key, label, note, sections[]{_key, title, service, items[]{\n _key, name, description, priceNote, dietary, allergenNote, availability,\n prices[]{_key, label, amount{minor, currency}}, extras[]{_key, name, amount{minor, currency}}\n }}}}}, _type == \"linkListBlock\" => {links[]{..., \"page\": page->{title, \"slug\": slug.current}}}}, sections)}";
|
|
213
244
|
export interface SiteContentReader {
|
|
214
245
|
/** `null` when no home page document exists yet. */
|
|
215
246
|
home: (options: {
|