@nakedcreativity/cee-ms-node 2.0.0-beta.0 → 2.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@ import { CacheSource, Ceems } from '@nakedcreativity/cee-ms-node';
9
9
  const ceems = new Ceems({
10
10
  assets: './src',
11
11
  cacheSource: CacheSource.S3,
12
- cacheBucket: 'my-site',
12
+ cacheBucket: 'my-site-bucket',
13
13
  viewsPath: './views/',
14
14
  customHandler: true,
15
15
  tags: [],
@@ -22,31 +22,223 @@ ceems.app.get(/(.*)/, async (req, res) => {
22
22
  });
23
23
  ```
24
24
 
25
+ ## Site settings
26
+
27
+ Site-wide values edited on the Cee.ms Settings screen (contact details, social links, a banner…) are published in
28
+ `site.json` as `settings`, by field key. Every template, layouts, modules and partials alike, reads them as
29
+ `settings`:
30
+
31
+ ```liquid
32
+ <a href="tel:{{ settings.phone }}">{{ settings.phone }}</a>
33
+ <a href="{{ settings.cta }}">{{ settings.cta_label | default: 'Contact us' }}</a>
34
+ {% image settings.logo %}
35
+ ```
36
+
37
+ Link settings read as their address, like links in modules (`{{ settings.cta.kind }}` has the kind). Drafts show
38
+ the settings as edited; the published site shows them once they are published. A static global named `settings` in
39
+ `config.globals` takes precedence, so a site that already uses that name keeps working.
40
+
41
+ ## Reading the published site
42
+
43
+ Cee.ms publishes each environment to its own folder in the site's bucket: `<environment>/pages/site.json`,
44
+ `<environment>/pages/<id>.json` and `<environment>/data/<id>.json` (read through `loadS3File('data/<id>.json')`).
45
+ The bucket is private, in the site owner's AWS account. `CacheSource.S3` reads it (`cacheBucket`, `cacheRegion`)
46
+ with the host's own AWS credentials: a Lambda or container role, OIDC from the host, or keys, as the site chooses. It
47
+ needs `s3:GetObject` on `<bucket>/*/pages/*` and `<bucket>/*/data/*`; Cee.ms's Settings → Storage screen shows the
48
+ statement. Set `CEEMS_ENVIRONMENT` (or `config.environment`) to the environment this server renders, such as
49
+ `live`; it defaults to `development`.
50
+
51
+ `CacheSource.Local` reads a folder (`cacheFolder`) laid out like the bucket. Set `cachePrefix` to read from somewhere
52
+ other than `<environment>/`.
53
+
54
+ ## Structured data
55
+
56
+ `{% structureddata %}` outputs the page's [schema.org](https://schema.org) JSON-LD in one
57
+ `<script type="application/ld+json">`. Put it in the layout's `<head>`:
58
+
59
+ ```liquid
60
+ <head>
61
+ <title>{{ page.title | default: page.name }} | {{ site.name }}</title>
62
+ {% structureddata %}
63
+ </head>
64
+ ```
65
+
66
+ It outputs the types the page has switched on in its settings, or the site's defaults for its kind of page (home,
67
+ page, blog or article), plus a node for the page's **entity**: what the page is about. Each is built from data
68
+ Cee.ms already has:
69
+
70
+ | Type | From |
71
+ | ------------------------------------------------------------------------- | -------------------------------------------------------- |
72
+ | `website` (WebSite) | the site's name and URL |
73
+ | `organisation` (Organization, or a business type such as RealEstateAgent) | the organisation on the Settings screen |
74
+ | `webpage` (WebPage) | the page's SEO title, description and social image |
75
+ | `breadcrumb` (BreadcrumbList) | home and the page's published ancestors |
76
+ | `article` (BlogPosting) | an article's title, description, image, date and tags |
77
+ | `faq` (FAQPage) | the questions in the page's modules whose type maps them |
78
+ | `entity` (Product, Event, Person or Service) | the page's entity and the details entered for it |
79
+
80
+ By default the site and organisation are described in full on the home page, and other pages refer to them with a
81
+ short copy (name, URL, logo). A product or event page also gets the reviews in its modules whose type maps them,
82
+ with a rating worked out from them, so the markup always matches what the page shows. Reviews on other pages are
83
+ left out: Google ignores reviews a business publishes about itself.
84
+
85
+ A node missing what it needs (an event with no start date, a product with neither a price nor reviews) is left out
86
+ rather than output invalid, and the tag outputs nothing when no node has data. Name parts to output only those, to
87
+ place them separately: `{% structureddata 'article' 'faq' %}`, `{% structureddata 'entity' %}`.
88
+
89
+ The entity is also in templates and modules as `page.entity` (`HOME`, `PAGE`, `ARTICLE`, `PERSON`, `PRODUCT`,
90
+ `EVENT` or `SERVICE`), e.g. `{% if page.entity == 'PRODUCT' %}`. A blog's articles are `ARTICLE` unless they choose
91
+ otherwise, and any other page can be one: an article uses the site's article defaults and has authors.
92
+
93
+ ## Link previews
94
+
95
+ Facebook, LinkedIn, WhatsApp, Slack and X don't read JSON-LD: they build link previews from Open Graph `<meta>`
96
+ tags. `{% socialmeta %}` outputs them from the same page data, so put it beside `{% structureddata %}`:
97
+
98
+ ```liquid
99
+ <head>
100
+ {% structureddata %}
101
+ {% socialmeta %}
102
+ </head>
103
+ ```
104
+
105
+ | Tag | From |
106
+ | ---------------------------------------------------------- | ------------------------------------------------------------ |
107
+ | `og:title`, `og:description` | the page's SEO title (else its name) and description |
108
+ | `og:url`, `og:site_name` | the site's URL and name |
109
+ | `og:image` (with its width, height and alt) | the page's social image |
110
+ | `og:type` | `article` for articles, `profile` for people, else `website` |
111
+ | `article:published_time`, `modified_time`, `tag`, `author` | an article's dates, tags and authors' profile pages |
112
+ | `twitter:card` | a large image card with a social image, else a small one |
113
+ | `twitter:site` | the organisation's X profile, among its profile links |
114
+
115
+ X reads the title, description and image from Open Graph, so they aren't repeated. Tags without data are left
116
+ out.
117
+
118
+ The organisation is also in templates as `site.organisation`, e.g. `{{ site.organisation.telephone }}`. For custom
119
+ tags or routes, `structuredDataGraph(site, page)` returns the graph as an object.
120
+
121
+ People feed the structured data too. A page whose entity is `PERSON` and that has its person chosen becomes a
122
+ `ProfilePage`, describing them in full. An article's byline becomes its `author`, with the organisation still the
123
+ publisher, and its reviewer becomes `reviewedBy`. People that a module features through a module type's people
124
+ mapping each get a `Person` node. A person has the same `@id` on every page: their profile page's, or, without one,
125
+ one of the site's.
126
+
127
+ ## Links, documents and rows
128
+
129
+ Links, documents and rows are read from what is published as the page renders, so a renamed page or a replaced
130
+ document is right on every page once that one change is published:
131
+
132
+ - **Page links** take their address from `site.json` `pageIndex`. A link to a page that isn't published reads as
133
+ `''` and has `broken` set, so `{% if link %}` leaves it out. Rich text links to it lose their `href`.
134
+ - **Document values and links** take their address from `site.json` `documents`. A document value prints as its
135
+ address, and keeps `url`, `thumbnail` and `info`: `{{ download }}`, `{{ download.url }}`.
136
+ - **Row fields** read as the row they point at, with its columns: `{{ author.name }}`,
137
+ `{% image person.photo 'square' %}`. A row prints as its `name`. Rows come from the collection's
138
+ `data/<id>.json`, which is read once and kept until `site.json` changes. A deleted row reads as nothing and is
139
+ left out of lists.
140
+
141
+ A site last published before Cee.ms published `pageIndex` keeps the addresses stored with its links, as before.
142
+
143
+ A page set to redirect in Cee.ms (its settings' redirect, such as a section that goes to its first page) isn't
144
+ rendered: `render` sends the visitor on with a 302, so changing where it points takes effect at once. If the
145
+ page it points at isn't published, the page renders as usual. Draft previews always render it, so it can be
146
+ edited.
147
+
148
+ ## People and bylines
149
+
150
+ A site's People (name, job title, photo, bio, profile links, email, telephone, profile page) is published in
151
+ `site.json` `people`. An article's byline is `page.authors`, in order. Each author has a `kind` (`person`, `guest`
152
+ or `organisation`), a `name` and a `url`: a person's profile page, else their first profile link. A person
153
+ author also has all their People columns:
154
+
155
+ ```liquid
156
+ {% for author in page.authors %}
157
+ <a href="{{ author.url }}">{{ author.name }}</a>{% if author.jobTitle %}, {{ author.jobTitle }}{% endif %}
158
+ {% endfor %}
159
+ ```
160
+
161
+ On a profile page, `page.person` is the person. `{% blog news articles 10 author:page.person %}` lists the
162
+ articles they appear in the byline of. With none yet, the list is empty, not a 404.
163
+
25
164
  ## Draft previews
26
165
 
27
166
  The editor loads `GET /draft/{pageId}?token=…` in an iframe. The renderer verifies the token, fetches the draft from
28
167
  the Cee.ms API, and renders it with module markers and the editor bridge. `&module={id}` renders one module and
29
168
  `&region={key}` one region, so the editor can refresh just what changed.
30
169
 
31
- Set these in the site's environment (or `config.preview`). Without the first three, `/draft` returns 404.
170
+ Set the site's preview secret, `CEEMS_PREVIEW_SECRET` (or `config.preview.secret`). That is all a site needs:
171
+ each preview token, signed with that secret, names the Cee.ms API that serves its draft and the editor allowed to
172
+ frame it, and the editor bridge loads from that editor (`<editor>/bridge.js`). The bridge is released with the
173
+ editor, so sites don't change when it does. Without the secret, `/draft` returns 404.
174
+
175
+ Optional:
32
176
 
33
- | Variable | Purpose |
34
- | ------------------------ | ------------------------------------------------------------- |
35
- | `CEEMS_PREVIEW_SECRET` | The site's preview secret. Signs and verifies preview tokens. |
36
- | `CEEMS_API_URL` | The Cee.ms API, e.g. `https://api.cee.ms`. |
37
- | `CEEMS_EDITOR_ORIGIN` | The editor's origin; the only page allowed to frame drafts. |
38
- | `CEEMS_BRIDGE_SRC` | URL of the versioned editor bridge script. |
39
- | `CEEMS_BRIDGE_INTEGRITY` | Its Subresource Integrity hash. |
177
+ | Variable | Purpose |
178
+ | --------------------- | ------------------------------------------------------------------------------- |
179
+ | `CEEMS_API_URL` | The Cee.ms API, for tokens that don't name one (from Cee.ms before tokens did). |
180
+ | `CEEMS_EDITOR_ORIGIN` | The editor's origin, likewise. |
181
+ | `CEEMS_BRIDGE_SRC` | Loads the bridge from here instead, for testing a local build of it. |
40
182
 
41
183
  Draft responses send `Cache-Control: no-store`, `X-Robots-Tag: noindex`, `frame-ancestors <editor origin>` and
42
184
  `Referrer-Policy: no-referrer` (the token is in the URL).
43
185
 
186
+ ## Views and the editor
187
+
188
+ A view is named by its file name alone: a module of type `text` renders `text.liquid`, a page from a template
189
+ with the layout `default` renders `default.liquid`. Views can be grouped in folders, which also tell the editor
190
+ what each one is:
191
+
192
+ ```
193
+ views/
194
+ layouts/default.liquid templates: each {% region %} is somewhere modules go
195
+ modules/text.liquid module types: the values they print are their fields
196
+ partials/nav.liquid everything else (navigation, includes)
197
+ ```
198
+
199
+ Names are looked up in `views/`, then `layouts/`, `modules/` and `partials/`, so a site with every view at the
200
+ top of `views/` works as before.
201
+
202
+ When the preview secret is set, `GET /ceems/views?token=…` lists the views for the Cee.ms editor, which
203
+ suggests templates and module types from them: their regions, and for modules the values they read and how (an
204
+ image, a date, a link, a list of items). Run it again from the editor as the site grows. The token is one the
205
+ editor signs for this purpose, and only that editor may read the response, from the browser: that is how it
206
+ reaches a site running on `localhost` (the route answers Chrome's private network preflight). Without folders,
207
+ a view with a `{% region %}` is taken to be a layout, and the rest modules.
208
+
209
+ The guesses can be overridden in a comment at the top of a view:
210
+
211
+ ```liquid
212
+ {% comment %}
213
+ @name Team members
214
+ @field intro RICH_TEXT Introduction
215
+ @field people.photo IMAGE Photo
216
+ {% endcomment %}
217
+ ```
218
+
219
+ `@field <key> <kind> [label]` sets a field's kind (`TEXT`, `TEXTBOX`, `RICH_TEXT`, `SELECT`, `CHECKBOX`, `DATE`,
220
+ `IMAGE`, `DOCUMENT`, `LINK` or `CONTRIBUTOR`) and label; `people.photo` is the `photo` of each item looped over
221
+ in `people`. `scanViews(viewsPath)` returns the same list, for scripts.
222
+
223
+ ## Upgrading to 2.0.0-beta.8
224
+
225
+ - **Views in folders** (`layouts/`, `modules/`, `partials/`), and **`GET /ceems/views`** for the editor's view
226
+ sync (above). Sites with flat views are unchanged.
227
+
228
+ ## Upgrading to 2.0.0-beta.6
229
+
230
+ Upgrade the Cee.ms API first. Drafts now ask it for the data a page's rows come from, which earlier versions of
231
+ the API don't send. Before adding `row` fields to a site's module types, move the site to this version of the
232
+ package: older versions print a row reference as an object.
233
+
44
234
  ## Upgrading a site from 1.x
45
235
 
46
236
  2.0 ships with Cee.ms v2. Upgrade a site when it moves to v2: the 1.x `POST /draft` that the old editor uses is gone.
47
237
 
48
238
  - **Node 22 or later.** Update the Lambda runtime.
49
- - **Set the preview variables** above.
239
+ - **Set `CEEMS_PREVIEW_SECRET`** (above).
240
+ - **Published files are read from `ceems/`** in the bucket (above). Custom tags calling `loadS3File` keep passing
241
+ the same names (`data/<id>.json`).
50
242
  - **No code changes are needed for the common pattern** (`ceems.load(req, res, true)` then `ceems.render(page, …)`,
51
243
  `ceems.site`, `ceems.loadS3File`, `ceems.loadLocalFile`, `context.globals` in custom tags). `ceems.site` is now
52
244
  always the published site; it never holds draft data.
package/lib/draft.d.ts CHANGED
@@ -4,17 +4,20 @@ export type DraftResult = {
4
4
  ok: true;
5
5
  page: CeemsPage;
6
6
  site: CeemsSite;
7
+ editorOrigin: string;
7
8
  } | {
8
9
  ok: false;
9
10
  status: 401 | 404 | 502;
10
11
  };
11
12
  export interface DraftService {
12
13
  fetch(pageId: string, token: string): Promise<DraftResult>;
13
- setHeaders(res: express.Response): void;
14
- injectBridge(html: string): string;
14
+ /** Draft headers; `editorOrigin` is the editor allowed to frame the response, if the token named one. */
15
+ setHeaders(res: express.Response, editorOrigin?: string): void;
16
+ injectBridge(html: string, editorOrigin: string): string;
15
17
  }
16
18
  /**
17
- * Draft previews, or `null` when this site is not configured for them (they then 404). Needs the site's
18
- * preview secret, the API URL and the editor origin.
19
+ * Draft previews, or `null` when this site has no preview secret (they then 404). Each token names the API
20
+ * to fetch its draft from and the editor allowed to frame it, and the bridge loads from that editor, so the
21
+ * secret is all a site needs. `apiUrl` and `editorOrigin` are fallbacks for tokens that name neither.
19
22
  */
20
23
  export declare function draftServiceFor(config?: PreviewConfig): DraftService | null;
package/lib/draft.js CHANGED
@@ -34,31 +34,50 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.draftServiceFor = draftServiceFor;
37
+ const links_1 = require("./links");
37
38
  const cheerio = __importStar(require("cheerio"));
38
39
  const preview_token_1 = require("./preview-token");
39
- const DRAFT_QUERY = 'query Draft($token: String!) { draftRenderPayload(token: $token) { page site } }';
40
+ const DRAFT_QUERY = 'query Draft($token: String!) { draftRenderPayload(token: $token) { page site data } }';
41
+ /** The origin of an http(s) URL, or undefined. */
42
+ function originOf(url) {
43
+ if (!url)
44
+ return undefined;
45
+ try {
46
+ const parsed = new URL(url);
47
+ return parsed.protocol === 'https:' || parsed.protocol === 'http:' ? parsed.origin : undefined;
48
+ }
49
+ catch {
50
+ return undefined;
51
+ }
52
+ }
53
+ /** An http(s) URL without a trailing slash, or undefined. */
54
+ function baseOf(url) {
55
+ return originOf(url) && url.replace(/\/$/, '');
56
+ }
40
57
  /**
41
- * Draft previews, or `null` when this site is not configured for them (they then 404). Needs the site's
42
- * preview secret, the API URL and the editor origin.
58
+ * Draft previews, or `null` when this site has no preview secret (they then 404). Each token names the API
59
+ * to fetch its draft from and the editor allowed to frame it, and the bridge loads from that editor, so the
60
+ * secret is all a site needs. `apiUrl` and `editorOrigin` are fallbacks for tokens that name neither.
43
61
  */
44
62
  function draftServiceFor(config = {}) {
45
63
  const secret = config.secret || process.env.CEEMS_PREVIEW_SECRET;
46
- const apiUrl = config.apiUrl || process.env.CEEMS_API_URL;
47
- const editorOrigin = config.editorOrigin || process.env.CEEMS_EDITOR_ORIGIN;
48
- const bridge = config.bridge ||
49
- (process.env.CEEMS_BRIDGE_SRC
50
- ? { src: process.env.CEEMS_BRIDGE_SRC, integrity: process.env.CEEMS_BRIDGE_INTEGRITY }
51
- : undefined);
52
- if (!secret || !apiUrl || !editorOrigin)
64
+ const fallbackApi = baseOf(config.apiUrl || process.env.CEEMS_API_URL);
65
+ const fallbackEditor = originOf(config.editorOrigin || process.env.CEEMS_EDITOR_ORIGIN);
66
+ const bridgeSrc = config.bridgeSrc || process.env.CEEMS_BRIDGE_SRC;
67
+ if (!secret)
53
68
  return null;
54
- const graphqlUrl = apiUrl.replace(/\/$/, '') + '/graphql';
55
69
  return {
56
70
  async fetch(pageId, token) {
57
- if (!(0, preview_token_1.verifyPreviewToken)(secret, token, pageId).ok)
71
+ const verified = (0, preview_token_1.verifyPreviewToken)(secret, token, pageId);
72
+ if (!verified.ok)
58
73
  return { ok: false, status: 401 };
74
+ const apiUrl = baseOf(verified.payload.a) || fallbackApi;
75
+ const editorOrigin = originOf(verified.payload.o) || fallbackEditor;
76
+ if (!apiUrl || !editorOrigin)
77
+ return { ok: false, status: 404 };
59
78
  let body;
60
79
  try {
61
- const response = await fetch(graphqlUrl, {
80
+ const response = await fetch(apiUrl + '/graphql', {
62
81
  method: 'POST',
63
82
  headers: { 'content-type': 'application/json' },
64
83
  body: JSON.stringify({ query: DRAFT_QUERY, variables: { token } }),
@@ -79,29 +98,33 @@ function draftServiceFor(config = {}) {
79
98
  return { ok: false, status: 404 };
80
99
  return { ok: false, status: 502 };
81
100
  }
82
- return { ok: true, page: JSON.parse(payload.page), site: JSON.parse(payload.site) };
101
+ const site = JSON.parse(payload.site);
102
+ // links resolve against the draft site, and rows against the draft's data as it is now
103
+ const data = payload.data ? JSON.parse(payload.data) : {};
104
+ const context = (0, links_1.linkContextFor)(site, Object.values(data));
105
+ if (site.settings)
106
+ site.settings = (0, links_1.wrapLinks)(site.settings, context);
107
+ const page = (0, links_1.wrapLinks)(JSON.parse(payload.page), context);
108
+ page.authors = (0, links_1.bylineAuthors)(page, site, context);
109
+ return { ok: true, page, site, editorOrigin };
83
110
  },
84
- setHeaders(res) {
111
+ setHeaders(res, editorOrigin) {
85
112
  res.set({
86
113
  'Cache-Control': 'no-store',
87
114
  'X-Robots-Tag': 'noindex, nofollow',
88
- 'Content-Security-Policy': `frame-ancestors ${editorOrigin}`,
115
+ 'Content-Security-Policy': `frame-ancestors ${editorOrigin || fallbackEditor || "'none'"}`,
89
116
  // The token is in the URL: never send it on to the page's assets and links.
90
117
  'Referrer-Policy': 'no-referrer',
91
118
  'X-Content-Type-Options': 'nosniff',
92
119
  });
93
120
  },
94
- injectBridge(html) {
121
+ injectBridge(html, editorOrigin) {
95
122
  const $ = cheerio.load(html);
96
123
  $('body').addClass('cee-ms-edit');
97
- if (bridge) {
98
- const script = $('<script defer></script>')
99
- .attr('src', bridge.src)
100
- .attr('data-editor-origin', editorOrigin);
101
- if (bridge.integrity)
102
- script.attr('integrity', bridge.integrity).attr('crossorigin', 'anonymous');
103
- $('body').append(script);
104
- }
124
+ const script = $('<script defer></script>')
125
+ .attr('src', bridgeSrc || `${editorOrigin}/bridge.js`)
126
+ .attr('data-editor-origin', editorOrigin);
127
+ $('body').append(script);
105
128
  return $.html();
106
129
  },
107
130
  };
package/lib/draft.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"draft.js","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"draft.js","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts CHANGED
@@ -5,6 +5,13 @@ import { Storage } from './storage';
5
5
  export * from './interfaces';
6
6
  export { signPreviewToken, verifyPreviewToken } from './preview-token';
7
7
  export { renderState } from './state';
8
+ export { DocumentDrop, LinkDrop, RowDrop, linkContextFor, wrapLinks } from './links';
9
+ export type { LinkContext } from './links';
10
+ export { enabledTypes, entityOf, serialiseJsonLd, structuredDataGraph } from './structured-data';
11
+ export { socialMeta, renderSocialMeta, SocialMetaTag } from './social-meta';
12
+ export type { StructuredDataPart } from './structured-data';
13
+ export { VIEW_FOLDERS, VIEWS_TOKEN_SUBJECT, scanViews, viewRoots } from './views';
14
+ export type { ScanOptions, ViewHint, ViewInfo, ViewManifest, ViewVariable } from './views';
8
15
  /**
9
16
  * A Cee.ms site renderer: an Express app that renders published pages, and verified editor drafts, with the
10
17
  * site's Liquid templates.
@@ -20,20 +27,41 @@ export declare class Ceems {
20
27
  site: CeemsSite | undefined;
21
28
  storage: Storage;
22
29
  private drafts;
30
+ /** The `site.json` the data file cache was filled for: publishing rewrites it, which empties the cache. */
31
+ private siteSource;
32
+ private dataFiles;
23
33
  constructor(config: CeemsConfig);
34
+ private storageFor;
24
35
  private registerTags;
25
36
  /** Loads the published `site.json`, remembers it as `this.site`, and returns it. */
26
37
  loadSite(): Promise<CeemsSite>;
38
+ /** A published data file, read once until the site is next published. `null` if it isn't published. */
39
+ private dataFile;
40
+ /**
41
+ * What a page's references resolve against: the site's pages and documents, and the rows of the data
42
+ * collections it points at, and of those their rows point at.
43
+ */
44
+ private linkContext;
45
+ /** Wraps a page's references (see `wrapLinks`) and adds its byline as `page.authors`. */
46
+ private resolvePage;
27
47
  /**
28
48
  * Loads the published page for the request's path (or the `404` page), ready for `render`. With
29
49
  * `loadPage` false, returns `page` as given (for custom routes that build their own page object).
30
50
  */
31
51
  load(req: CeemsRequest, res: express.Response, loadPage?: boolean, page?: CeemsPage): Promise<CeemsPage>;
32
- /** Reads `pages/<id>.json`. */
33
- loadPageFile(id: string): Promise<CeemsPage>;
52
+ /** Reads a published file, under `cachePrefix`. */
53
+ private readPublished;
54
+ /**
55
+ * Reads `pages/<id>.json`. Stored links read as their address in templates (see `LinkDrop`), resolved
56
+ * against `site` (by default the last site loaded), and row references read as their rows (`RowDrop`).
57
+ */
58
+ loadPageFile(id: string, site?: CeemsSite | undefined): Promise<CeemsPage>;
34
59
  /** Synchronous local read, kept for custom tags. `filename` is a full path. */
35
60
  loadLocalFile(filename: string): any;
36
- /** Reads and parses a published JSON file, kept for custom tags. `filename` is relative to the bucket. */
61
+ /**
62
+ * Reads and parses a published JSON file, kept for custom tags. `filename` is as published, such as
63
+ * `data/<id>.json`; it is read under `cachePrefix`.
64
+ */
37
65
  loadS3File(filename: string): Promise<any>;
38
66
  /** The site path for a request (`home` for `/`), and the blog page number if the URL ends in one. */
39
67
  parsePath(req: CeemsRequest): {