@sandboxsonny/ace-kit 0.9.0 → 0.11.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/CHANGELOG.md +12 -0
- package/astro/AceSeo.astro +9 -4
- package/dist/astro.d.ts +25 -0
- package/dist/astro.js +1 -1
- package/dist/{chunk-EKZRLWNO.js → chunk-PXT3H7C6.js} +23 -2
- package/dist/cli.js +2 -2
- package/dist/{environment-YCQRJ56U.js → environment-LTPUSFAK.js} +1 -1
- package/dist/index.d.ts +43 -2
- package/dist/index.js +10 -2
- package/dist/{main-6O2IIVHO.js → main-IKZ3DQLC.js} +9 -0
- package/dist/react-router-server.d.ts +29 -0
- package/dist/react-router-server.js +1 -1
- package/docs/astro.md +1 -1
- package/docs/react-router.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sandboxsonny/ace-kit
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f0a08c3: Follow the redirects set up in the CMS. When a request is for an address the site has no page for, `getPage` (and the React Router page loader) now asks the CMS whether the site's owners redirected it, and answers with that redirect (301 or 302, to a path on the site or another address) instead of "not found". Your page already handles redirects, so there is nothing to change. A page shown in the CMS's preview frame is never redirected. Also adds `client.getRedirect(address)`.
|
|
8
|
+
|
|
9
|
+
## 0.10.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 4215f32: Show the site's favicon in `<AceSeo>`. The favicon is chosen in the site's settings in the CMS; `<AceSeo>` adds `<link rel="icon">` and `<link rel="apple-touch-icon">` for it when you don't pass a `favicon` yourself. If your layout already has its own icon link, remove it, or the browser may pick either. `getSite()` now also returns the favicon.
|
|
14
|
+
|
|
3
15
|
## 0.9.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/astro/AceSeo.astro
CHANGED
|
@@ -7,10 +7,15 @@ import { getSite } from "../dist/astro.js";
|
|
|
7
7
|
|
|
8
8
|
type Props = AceSeoOptions;
|
|
9
9
|
|
|
10
|
-
// With no picture of its own, a page shows the
|
|
11
|
-
// failing the page for, so a CMS that can't be reached just means
|
|
12
|
-
const
|
|
13
|
-
const
|
|
10
|
+
// With no picture or icon of its own, a page shows the ones chosen in the site's settings. The tags are never worth
|
|
11
|
+
// failing the page for, so a CMS that can't be reached just means none.
|
|
12
|
+
const needsSite = Astro.props.image === undefined || Astro.props.favicon === undefined;
|
|
13
|
+
const site = needsSite ? await getSite().catch(() => null) : null;
|
|
14
|
+
const properties = seoProperties({
|
|
15
|
+
...Astro.props,
|
|
16
|
+
image: Astro.props.image ?? site?.social_image?.src,
|
|
17
|
+
favicon: Astro.props.favicon ?? site?.favicon ?? undefined,
|
|
18
|
+
});
|
|
14
19
|
---
|
|
15
20
|
|
|
16
21
|
<SEO {...properties} />
|
package/dist/astro.d.ts
CHANGED
|
@@ -29,6 +29,18 @@ type AceImageData = {
|
|
|
29
29
|
content_type: string;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
/** The site's favicon as the CMS Ace public API returns it: the original, and square PNG copies. */
|
|
33
|
+
type AceFavicon = {
|
|
34
|
+
id: number;
|
|
35
|
+
/** The untouched original. */
|
|
36
|
+
url: string;
|
|
37
|
+
/** A small square PNG (48 pixels), for the browser tab and search results. */
|
|
38
|
+
icon: string;
|
|
39
|
+
/** A square PNG (180 pixels), for a phone's home screen. */
|
|
40
|
+
apple_touch_icon: string;
|
|
41
|
+
content_type: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
32
44
|
/** The site the request is for, as the CMS Ace public API returns it. */
|
|
33
45
|
type AceSite = {
|
|
34
46
|
id: number;
|
|
@@ -37,6 +49,18 @@ type AceSite = {
|
|
|
37
49
|
handle: string;
|
|
38
50
|
/** The picture the site's owners chose to show in search results and social posts; null if none. */
|
|
39
51
|
social_image: AceImageData | null;
|
|
52
|
+
/** The small picture the site's owners chose for browser tabs; null if none. */
|
|
53
|
+
favicon: AceFavicon | null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** A redirect the CMS has for an address, as its public API returns it. */
|
|
57
|
+
type AceRedirect = {
|
|
58
|
+
/** The old address the redirect is for. */
|
|
59
|
+
from: string;
|
|
60
|
+
/** Where it goes: a path on the site, or a full web address. */
|
|
61
|
+
to: string;
|
|
62
|
+
/** 301 is permanent; 302 is temporary. */
|
|
63
|
+
status: 301 | 302;
|
|
40
64
|
};
|
|
41
65
|
|
|
42
66
|
/** One link of a menu as the public API returns it, with its address already worked out. */
|
|
@@ -115,6 +139,7 @@ declare const ace: {
|
|
|
115
139
|
limit?: number;
|
|
116
140
|
}): Promise<AceEntry<AceValuesOf<Handle>>[]>;
|
|
117
141
|
getMenu(handle: string): Promise<AceMenu | null>;
|
|
142
|
+
getRedirect(address: string): Promise<AceRedirect | null>;
|
|
118
143
|
getSite(): Promise<AceSite | null>;
|
|
119
144
|
getContentTypes(): Promise<AceContentTypeSchema[]>;
|
|
120
145
|
};
|
package/dist/astro.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createAceClient, loadMenuLinks, loadPage } from './chunk-
|
|
1
|
+
import { createAceClient, loadMenuLinks, loadPage } from './chunk-PXT3H7C6.js';
|
|
2
2
|
import './chunk-M5WR5TCR.js';
|
|
3
3
|
import './chunk-YLO6IAXF.js';
|
|
4
4
|
import { ACE_SITE_DOMAIN, ACE_API_URL } from 'astro:env/server';
|
|
@@ -89,6 +89,15 @@ function createAceClient({
|
|
|
89
89
|
getMenu(handle) {
|
|
90
90
|
return get(`/public/menus/${encodeURIComponent(handle)}`);
|
|
91
91
|
},
|
|
92
|
+
/**
|
|
93
|
+
* Where an address the site has no page for should redirect to, if the site's owners set that up; null if not.
|
|
94
|
+
* `address` is the path with its query string, e.g. "/old-page?id=7".
|
|
95
|
+
*/
|
|
96
|
+
async getRedirect(address) {
|
|
97
|
+
const redirect = await get(`/public/redirects?from=${encodeURIComponent(address)}`);
|
|
98
|
+
const goesSomewhereSafe = redirect !== null && typeof redirect.to === "string" && (redirect.to.startsWith("/") && !redirect.to.startsWith("//") && !redirect.to.includes("\\") || /^https?:\/\/[^\s]+$/i.test(redirect.to)) && (redirect.status === 301 || redirect.status === 302);
|
|
99
|
+
return goesSomewhereSafe ? redirect : null;
|
|
100
|
+
},
|
|
92
101
|
/** The site itself: its name, and the social image its owners chose (null if none). */
|
|
93
102
|
getSite() {
|
|
94
103
|
return get("/public/site");
|
|
@@ -132,12 +141,12 @@ async function loadPage(client, { slug, url }) {
|
|
|
132
141
|
if (slug && address && isHomeAddress(address)) {
|
|
133
142
|
return { kind: "redirect", status: url.search ? 302 : 301, location: `/${url.search}`, headers };
|
|
134
143
|
}
|
|
135
|
-
if (!address) return
|
|
144
|
+
if (!address) return notFoundOrRedirect(client, url, previewToken, headers);
|
|
136
145
|
try {
|
|
137
146
|
const entry = await client.getEntry(address.contentTypeHandle, address.entryHandle, {
|
|
138
147
|
previewToken
|
|
139
148
|
});
|
|
140
|
-
return entry ? { kind: "entry", status: 200, entry, headers } :
|
|
149
|
+
return entry ? { kind: "entry", status: 200, entry, headers } : notFoundOrRedirect(client, url, previewToken, headers);
|
|
141
150
|
} catch (error) {
|
|
142
151
|
if (error instanceof AcePreviewExpiredError) return { kind: "preview-expired", status: 403, headers };
|
|
143
152
|
if (error instanceof AceUnavailableError) {
|
|
@@ -147,5 +156,17 @@ async function loadPage(client, { slug, url }) {
|
|
|
147
156
|
throw error;
|
|
148
157
|
}
|
|
149
158
|
}
|
|
159
|
+
async function notFoundOrRedirect(client, url, previewToken, headers) {
|
|
160
|
+
if (previewToken) return { kind: "not-found", status: 404, headers };
|
|
161
|
+
try {
|
|
162
|
+
const redirect = await client.getRedirect(`${url.pathname}${url.search}`);
|
|
163
|
+
if (redirect) return { kind: "redirect", status: redirect.status, location: redirect.to, headers };
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (!(error instanceof AceUnavailableError)) throw error;
|
|
166
|
+
console.error(error.message);
|
|
167
|
+
return { kind: "unavailable", status: 503, headers };
|
|
168
|
+
}
|
|
169
|
+
return { kind: "not-found", status: 404, headers };
|
|
170
|
+
}
|
|
150
171
|
|
|
151
172
|
export { AcePreviewExpiredError, AceUnavailableError, createAceClient, isCurrentPage, linksFromMenu, loadMenuLinks, loadPage };
|
package/dist/cli.js
CHANGED
|
@@ -9,8 +9,8 @@ if (major < 22 || major === 22 && minor < 12) {
|
|
|
9
9
|
process.exit(1);
|
|
10
10
|
}
|
|
11
11
|
try {
|
|
12
|
-
const { createRealEnvironment } = await import("./environment-
|
|
13
|
-
const { runCli } = await import("./main-
|
|
12
|
+
const { createRealEnvironment } = await import("./environment-LTPUSFAK.js");
|
|
13
|
+
const { runCli } = await import("./main-IKZ3DQLC.js");
|
|
14
14
|
const environment = await createRealEnvironment();
|
|
15
15
|
process.exit(await runCli(process.argv.slice(2), environment));
|
|
16
16
|
} catch (error) {
|
|
@@ -145,7 +145,7 @@ function createLastApiUrl(path) {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
// src/cli/version.ts
|
|
148
|
-
var aceKitVersion = true ? "0.
|
|
148
|
+
var aceKitVersion = true ? "0.11.0" : "development";
|
|
149
149
|
|
|
150
150
|
// src/cli/environment.ts
|
|
151
151
|
var runCommand = (command, commandArguments, input) => new Promise((resolve, reject) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -84,6 +84,16 @@ type AceNavigationLink = {
|
|
|
84
84
|
children?: AceNavigationLink[];
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
+
/** A redirect the CMS has for an address, as its public API returns it. */
|
|
88
|
+
type AceRedirect = {
|
|
89
|
+
/** The old address the redirect is for. */
|
|
90
|
+
from: string;
|
|
91
|
+
/** Where it goes: a path on the site, or a full web address. */
|
|
92
|
+
to: string;
|
|
93
|
+
/** 301 is permanent; 302 is temporary. */
|
|
94
|
+
status: 301 | 302;
|
|
95
|
+
};
|
|
96
|
+
|
|
87
97
|
/** An image as the CMS Ace public API returns it. Use `src` and `srcset` in an `<img>`. */
|
|
88
98
|
type AceImageData = {
|
|
89
99
|
id: number;
|
|
@@ -99,6 +109,18 @@ type AceImageData = {
|
|
|
99
109
|
content_type: string;
|
|
100
110
|
};
|
|
101
111
|
|
|
112
|
+
/** The site's favicon as the CMS Ace public API returns it: the original, and square PNG copies. */
|
|
113
|
+
type AceFavicon = {
|
|
114
|
+
id: number;
|
|
115
|
+
/** The untouched original. */
|
|
116
|
+
url: string;
|
|
117
|
+
/** A small square PNG (48 pixels), for the browser tab and search results. */
|
|
118
|
+
icon: string;
|
|
119
|
+
/** A square PNG (180 pixels), for a phone's home screen. */
|
|
120
|
+
apple_touch_icon: string;
|
|
121
|
+
content_type: string;
|
|
122
|
+
};
|
|
123
|
+
|
|
102
124
|
/** The site the request is for, as the CMS Ace public API returns it. */
|
|
103
125
|
type AceSite = {
|
|
104
126
|
id: number;
|
|
@@ -107,6 +129,8 @@ type AceSite = {
|
|
|
107
129
|
handle: string;
|
|
108
130
|
/** The picture the site's owners chose to show in search results and social posts; null if none. */
|
|
109
131
|
social_image: AceImageData | null;
|
|
132
|
+
/** The small picture the site's owners chose for browser tabs; null if none. */
|
|
133
|
+
favicon: AceFavicon | null;
|
|
110
134
|
};
|
|
111
135
|
|
|
112
136
|
/**
|
|
@@ -129,6 +153,11 @@ declare function createAceClient({ apiUrl, siteDomain, cacheSeconds, fetchImplem
|
|
|
129
153
|
}): Promise<AceEntry<AceValuesOf<Handle>>[]>;
|
|
130
154
|
/** One of the site's navigation menus by its handle, with every link worked out; null if the site has none. */
|
|
131
155
|
getMenu(handle: string): Promise<AceMenu | null>;
|
|
156
|
+
/**
|
|
157
|
+
* Where an address the site has no page for should redirect to, if the site's owners set that up; null if not.
|
|
158
|
+
* `address` is the path with its query string, e.g. "/old-page?id=7".
|
|
159
|
+
*/
|
|
160
|
+
getRedirect(address: string): Promise<AceRedirect | null>;
|
|
132
161
|
/** The site itself: its name, and the social image its owners chose (null if none). */
|
|
133
162
|
getSite(): Promise<AceSite | null>;
|
|
134
163
|
/** What the site's content types and fields are, which is what type generation reads. */
|
|
@@ -401,7 +430,8 @@ type AcePage<Values = Record<string, unknown>> =
|
|
|
401
430
|
* - `slug` is the part of the path after the site's root, e.g. "pages/about" ("" or undefined for the home page).
|
|
402
431
|
* - `url` is the full request address; it carries the preview token the CMS puts in its preview frame.
|
|
403
432
|
*
|
|
404
|
-
* Addresses are `/{content type}/{entry}` as on Shopify, and `/` is the home entry.
|
|
433
|
+
* Addresses are `/{content type}/{entry}` as on Shopify, and `/` is the home entry. An address with no page is
|
|
434
|
+
* redirected if the site's owners set up a redirect for it (in the CMS), and otherwise not found. A page shown with a preview
|
|
405
435
|
* token is never cached or indexed (the answer's `headers` say so).
|
|
406
436
|
*/
|
|
407
437
|
declare function loadPage<Values = Record<string, unknown>>(client: AceClient, { slug, url }: {
|
|
@@ -478,6 +508,11 @@ type AceSeoOptions = {
|
|
|
478
508
|
* social image chosen in the site's settings in the CMS (if any).
|
|
479
509
|
*/
|
|
480
510
|
image?: string;
|
|
511
|
+
/**
|
|
512
|
+
* The site's favicon. In the Astro `<AceSeo>`, leaving it out uses the favicon chosen in the site's settings in the
|
|
513
|
+
* CMS (if any); it then adds `<link rel="icon">` and `<link rel="apple-touch-icon">`.
|
|
514
|
+
*/
|
|
515
|
+
favicon?: Pick<AceFavicon, "icon" | "apple_touch_icon">;
|
|
481
516
|
/** Tells search engines to leave the page out, as for a draft shown in preview. */
|
|
482
517
|
noindex?: boolean;
|
|
483
518
|
};
|
|
@@ -486,6 +521,11 @@ type Meta = {
|
|
|
486
521
|
name?: string;
|
|
487
522
|
content: string;
|
|
488
523
|
};
|
|
524
|
+
type Link = {
|
|
525
|
+
rel: string;
|
|
526
|
+
href: string;
|
|
527
|
+
sizes?: string;
|
|
528
|
+
};
|
|
489
529
|
/** What `astro-seo`'s `<SEO>` takes (only the parts used here). */
|
|
490
530
|
type AceSeoProperties = {
|
|
491
531
|
title?: string;
|
|
@@ -500,6 +540,7 @@ type AceSeoProperties = {
|
|
|
500
540
|
};
|
|
501
541
|
extend: {
|
|
502
542
|
meta: Meta[];
|
|
543
|
+
link?: Link[];
|
|
503
544
|
};
|
|
504
545
|
};
|
|
505
546
|
/**
|
|
@@ -551,4 +592,4 @@ type AceSection = {
|
|
|
551
592
|
blocks: AceSectionBlock[];
|
|
552
593
|
};
|
|
553
594
|
|
|
554
|
-
export { type AceAddress, type AceClient, type AceClientOptions, type AceContentTypeHandle, type AceContentTypeSchema, type AceContentTypes, type AceDateFormat, type AceEntry, type AceFieldSchema, type AceImageAttributes, type AceImageData, type AceImageOptions, type AceLinkAttributes, type AceLinkData, type AceMenu, type AceMenuItem, type AceNavigationLink, type AcePage, AcePreviewExpiredError, type AceRichTextDocument, type AceRichTextMark, type AceRichTextNode, type AceSection, type AceSectionBlock, type AceSeoOptions, type AceSeoProperties, type AceSite, AceUnavailableError, type AceValuesOf, type BlockDefinition, type SectionDefinition, type SectionFieldDefinition, type SectionFieldType, type SectionOf, type SectionTypesPayload, type ValuesOfFields, addFramingPolicy, addressFromPath, checkSectionDefinition, createAceClient, defineSection, formatAceDate, framingPolicy, headingLevel, homeAddress, imageAttributes, isCurrentPage, isHomeAddress, isInternalPath, isRefreshRequest, linkAddress, linkAttributes, linksFromMenu, listStart, listenForPreviewRefresh, loadMenuLinks, loadPage, originOf, pageNotices, pathOfAddress, previewHeaders, previewParameter, previewTokenFrom, refreshMessageType, refreshPageContent, renderRichText, renderTextArea, richTextImageSizes, sectionTypesPayload, seoDescriptionHandle, seoProperties, seoTitleHandle, swapPageContent, visitablePath };
|
|
595
|
+
export { type AceAddress, type AceClient, type AceClientOptions, type AceContentTypeHandle, type AceContentTypeSchema, type AceContentTypes, type AceDateFormat, type AceEntry, type AceFavicon, type AceFieldSchema, type AceImageAttributes, type AceImageData, type AceImageOptions, type AceLinkAttributes, type AceLinkData, type AceMenu, type AceMenuItem, type AceNavigationLink, type AcePage, AcePreviewExpiredError, type AceRedirect, type AceRichTextDocument, type AceRichTextMark, type AceRichTextNode, type AceSection, type AceSectionBlock, type AceSeoOptions, type AceSeoProperties, type AceSite, AceUnavailableError, type AceValuesOf, type BlockDefinition, type SectionDefinition, type SectionFieldDefinition, type SectionFieldType, type SectionOf, type SectionTypesPayload, type ValuesOfFields, addFramingPolicy, addressFromPath, checkSectionDefinition, createAceClient, defineSection, formatAceDate, framingPolicy, headingLevel, homeAddress, imageAttributes, isCurrentPage, isHomeAddress, isInternalPath, isRefreshRequest, linkAddress, linkAttributes, linksFromMenu, listStart, listenForPreviewRefresh, loadMenuLinks, loadPage, originOf, pageNotices, pathOfAddress, previewHeaders, previewParameter, previewTokenFrom, refreshMessageType, refreshPageContent, renderRichText, renderTextArea, richTextImageSizes, sectionTypesPayload, seoDescriptionHandle, seoProperties, seoTitleHandle, swapPageContent, visitablePath };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { pageNotices } from './chunk-XPZY5R5A.js';
|
|
2
2
|
export { headingLevel, imageAttributes, isInternalPath, linkAddress, linkAttributes, listStart, renderRichText, renderTextArea, richTextImageSizes } from './chunk-FVR55GIU.js';
|
|
3
3
|
export { listenForPreviewRefresh, refreshPageContent } from './chunk-YN7LJWGI.js';
|
|
4
|
-
export { AcePreviewExpiredError, AceUnavailableError, createAceClient, isCurrentPage, linksFromMenu, loadMenuLinks, loadPage } from './chunk-
|
|
4
|
+
export { AcePreviewExpiredError, AceUnavailableError, createAceClient, isCurrentPage, linksFromMenu, loadMenuLinks, loadPage } from './chunk-PXT3H7C6.js';
|
|
5
5
|
export { addressFromPath, homeAddress, isHomeAddress, pathOfAddress, visitablePath } from './chunk-M5WR5TCR.js';
|
|
6
6
|
export { addFramingPolicy, framingPolicy, isRefreshRequest, originOf, previewHeaders, previewParameter, previewTokenFrom, refreshMessageType, swapPageContent } from './chunk-YLO6IAXF.js';
|
|
7
7
|
|
|
@@ -160,7 +160,15 @@ function seoProperties(options) {
|
|
|
160
160
|
...description && { description },
|
|
161
161
|
...options.image && { image: options.image }
|
|
162
162
|
},
|
|
163
|
-
extend: {
|
|
163
|
+
extend: {
|
|
164
|
+
meta,
|
|
165
|
+
...options.favicon && {
|
|
166
|
+
link: [
|
|
167
|
+
{ rel: "icon", href: options.favicon.icon, sizes: "48x48" },
|
|
168
|
+
{ rel: "apple-touch-icon", href: options.favicon.apple_touch_icon }
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
164
172
|
};
|
|
165
173
|
}
|
|
166
174
|
|
|
@@ -1367,6 +1367,15 @@ function createAceClient({
|
|
|
1367
1367
|
getMenu(handle) {
|
|
1368
1368
|
return get(`/public/menus/${encodeURIComponent(handle)}`);
|
|
1369
1369
|
},
|
|
1370
|
+
/**
|
|
1371
|
+
* Where an address the site has no page for should redirect to, if the site's owners set that up; null if not.
|
|
1372
|
+
* `address` is the path with its query string, e.g. "/old-page?id=7".
|
|
1373
|
+
*/
|
|
1374
|
+
async getRedirect(address) {
|
|
1375
|
+
const redirect = await get(`/public/redirects?from=${encodeURIComponent(address)}`);
|
|
1376
|
+
const goesSomewhereSafe = redirect !== null && typeof redirect.to === "string" && (redirect.to.startsWith("/") && !redirect.to.startsWith("//") && !redirect.to.includes("\\") || /^https?:\/\/[^\s]+$/i.test(redirect.to)) && (redirect.status === 301 || redirect.status === 302);
|
|
1377
|
+
return goesSomewhereSafe ? redirect : null;
|
|
1378
|
+
},
|
|
1370
1379
|
/** The site itself: its name, and the social image its owners chose (null if none). */
|
|
1371
1380
|
getSite() {
|
|
1372
1381
|
return get("/public/site");
|
|
@@ -50,6 +50,18 @@ type AceImageData = {
|
|
|
50
50
|
content_type: string;
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
/** The site's favicon as the CMS Ace public API returns it: the original, and square PNG copies. */
|
|
54
|
+
type AceFavicon = {
|
|
55
|
+
id: number;
|
|
56
|
+
/** The untouched original. */
|
|
57
|
+
url: string;
|
|
58
|
+
/** A small square PNG (48 pixels), for the browser tab and search results. */
|
|
59
|
+
icon: string;
|
|
60
|
+
/** A square PNG (180 pixels), for a phone's home screen. */
|
|
61
|
+
apple_touch_icon: string;
|
|
62
|
+
content_type: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
53
65
|
/** The site the request is for, as the CMS Ace public API returns it. */
|
|
54
66
|
type AceSite = {
|
|
55
67
|
id: number;
|
|
@@ -58,6 +70,8 @@ type AceSite = {
|
|
|
58
70
|
handle: string;
|
|
59
71
|
/** The picture the site's owners chose to show in search results and social posts; null if none. */
|
|
60
72
|
social_image: AceImageData | null;
|
|
73
|
+
/** The small picture the site's owners chose for browser tabs; null if none. */
|
|
74
|
+
favicon: AceFavicon | null;
|
|
61
75
|
};
|
|
62
76
|
|
|
63
77
|
/** One link of a menu as the public API returns it, with its address already worked out. */
|
|
@@ -112,6 +126,16 @@ type AceContentTypeSchema = {
|
|
|
112
126
|
fields: AceFieldSchema[];
|
|
113
127
|
};
|
|
114
128
|
|
|
129
|
+
/** A redirect the CMS has for an address, as its public API returns it. */
|
|
130
|
+
type AceRedirect = {
|
|
131
|
+
/** The old address the redirect is for. */
|
|
132
|
+
from: string;
|
|
133
|
+
/** Where it goes: a path on the site, or a full web address. */
|
|
134
|
+
to: string;
|
|
135
|
+
/** 301 is permanent; 302 is temporary. */
|
|
136
|
+
status: 301 | 302;
|
|
137
|
+
};
|
|
138
|
+
|
|
115
139
|
/**
|
|
116
140
|
* A client for the CMS Ace public API. It runs on the server only, so the visitor's browser never talks to the
|
|
117
141
|
* API. The API finds the site from X-Ace-Site-Domain, which this client sets to the site's domain. X-Forwarded-Host
|
|
@@ -132,6 +156,11 @@ declare function createAceClient({ apiUrl, siteDomain, cacheSeconds, fetchImplem
|
|
|
132
156
|
}): Promise<AceEntry<AceValuesOf<Handle>>[]>;
|
|
133
157
|
/** One of the site's navigation menus by its handle, with every link worked out; null if the site has none. */
|
|
134
158
|
getMenu(handle: string): Promise<AceMenu | null>;
|
|
159
|
+
/**
|
|
160
|
+
* Where an address the site has no page for should redirect to, if the site's owners set that up; null if not.
|
|
161
|
+
* `address` is the path with its query string, e.g. "/old-page?id=7".
|
|
162
|
+
*/
|
|
163
|
+
getRedirect(address: string): Promise<AceRedirect | null>;
|
|
135
164
|
/** The site itself: its name, and the social image its owners chose (null if none). */
|
|
136
165
|
getSite(): Promise<AceSite | null>;
|
|
137
166
|
/** What the site's content types and fields are, which is what type generation reads. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createAceClient, loadMenuLinks, loadPage } from './chunk-
|
|
1
|
+
import { createAceClient, loadMenuLinks, loadPage } from './chunk-PXT3H7C6.js';
|
|
2
2
|
import './chunk-M5WR5TCR.js';
|
|
3
3
|
import { hostedAdminOrigin, hostedApiUrl } from './chunk-N5DIMG3X.js';
|
|
4
4
|
import { previewTokenFrom, addFramingPolicy } from './chunk-YLO6IAXF.js';
|
package/docs/astro.md
CHANGED
|
@@ -46,7 +46,7 @@ if (page.kind === "redirect") return Astro.redirect(page.location, page.status);
|
|
|
46
46
|
{page.kind === "entry" ? <h1>{page.entry.title}</h1> : <AceNotice page={page} />}
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Addresses are `/{content type}/{entry}` as on Shopify, `/` is the `home` entry of `pages`, and `/pages/home` redirects to `/`. `page.kind` is `entry`, `redirect`, `not-found`, `unavailable` or `preview-expired`. `AceNotice` draws a heading and a message for the last three; draw your own if you prefer.
|
|
49
|
+
Addresses are `/{content type}/{entry}` as on Shopify, `/` is the `home` entry of `pages`, and `/pages/home` redirects to `/`. An address with no page is redirected too if the site's owners set up a redirect for it in the CMS (Redirects), so old addresses from a site you moved keep working. `page.kind` is `entry`, `redirect`, `not-found`, `unavailable` or `preview-expired`. `AceNotice` draws a heading and a message for the last three; draw your own if you prefer.
|
|
50
50
|
|
|
51
51
|
## Reading content
|
|
52
52
|
|
package/docs/react-router.md
CHANGED
|
@@ -69,7 +69,7 @@ export default function Page({ loaderData: page }: Route.ComponentProps) {
|
|
|
69
69
|
}
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
The loader reads the address (`/{content type}/{entry}`, and `/` is the `home` entry of `pages`), loads the entry and answers with the right status and headers: a missing page is a real 404, an unreachable CMS a 503, an expired preview a 403, `/pages/home` redirects to `/`, and a preview is never cached or indexed. `page.kind` is `entry`, `not-found`, `unavailable` or `preview-expired`. `createMeta` titles the page from the entry (followed by the site's name), adds its description, and asks search engines to ignore a preview. With [typed content](./typed-content.md), `createPageLoader<PageValues>()` gives `page.entry.values` its real shape.
|
|
72
|
+
The loader reads the address (`/{content type}/{entry}`, and `/` is the `home` entry of `pages`), loads the entry and answers with the right status and headers: a missing page is a real 404, an unreachable CMS a 503, an expired preview a 403, `/pages/home` redirects to `/`, an address with no page follows the redirect the site's owners set up in the CMS (if any), and a preview is never cached or indexed. `page.kind` is `entry`, `not-found`, `unavailable` or `preview-expired`. `createMeta` titles the page from the entry (followed by the site's name), adds its description, and asks search engines to ignore a preview. With [typed content](./typed-content.md), `createPageLoader<PageValues>()` gives `page.entry.values` its real shape.
|
|
73
73
|
|
|
74
74
|
**4. The root route** loads the header menu and keeps previews fresh:
|
|
75
75
|
|
package/package.json
CHANGED