@nakedcreativity/cee-ms-node 2.0.0-beta.1 → 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 +202 -10
- package/lib/draft.d.ts +7 -4
- package/lib/draft.js +47 -25
- package/lib/draft.js.map +1 -1
- package/lib/index.d.ts +31 -4
- package/lib/index.js +131 -23
- package/lib/index.js.map +1 -1
- package/lib/interfaces.d.ts +295 -10
- package/lib/interfaces.js +1 -0
- package/lib/interfaces.js.map +1 -1
- package/lib/links.d.ts +67 -2
- package/lib/links.js +201 -7
- package/lib/links.js.map +1 -1
- package/lib/preview-token.d.ts +13 -4
- package/lib/preview-token.js +6 -2
- package/lib/preview-token.js.map +1 -1
- package/lib/social-meta.d.ts +15 -0
- package/lib/social-meta.js +74 -0
- package/lib/social-meta.js.map +1 -0
- package/lib/storage.d.ts +1 -0
- package/lib/storage.js +3 -1
- package/lib/storage.js.map +1 -1
- package/lib/structured-data.d.ts +35 -0
- package/lib/structured-data.js +591 -0
- package/lib/structured-data.js.map +1 -0
- package/lib/tags/blog.d.ts +3 -2
- package/lib/tags/blog.js +14 -5
- package/lib/tags/blog.js.map +1 -1
- package/lib/tags/index.d.ts +2 -0
- package/lib/tags/index.js +2 -0
- package/lib/tags/index.js.map +1 -1
- package/lib/tags/social-meta.d.ts +6 -0
- package/lib/tags/social-meta.js +19 -0
- package/lib/tags/social-meta.js.map +1 -0
- package/lib/tags/structured-data.d.ts +8 -0
- package/lib/tags/structured-data.js +36 -0
- package/lib/tags/structured-data.js.map +1 -0
- package/lib/views.d.ts +76 -0
- package/lib/views.js +426 -0
- package/lib/views.js.map +1 -0
- package/package.json +8 -1
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
|
`®ion={key}` one region, so the editor can refresh just what changed.
|
|
30
169
|
|
|
31
|
-
Set
|
|
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
|
|
34
|
-
|
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
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
|
|
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
|
-
|
|
14
|
-
|
|
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
|
|
18
|
-
*
|
|
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
|
@@ -37,29 +37,47 @@ exports.draftServiceFor = draftServiceFor;
|
|
|
37
37
|
const links_1 = require("./links");
|
|
38
38
|
const cheerio = __importStar(require("cheerio"));
|
|
39
39
|
const preview_token_1 = require("./preview-token");
|
|
40
|
-
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
|
+
}
|
|
41
57
|
/**
|
|
42
|
-
* Draft previews, or `null` when this site
|
|
43
|
-
*
|
|
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.
|
|
44
61
|
*/
|
|
45
62
|
function draftServiceFor(config = {}) {
|
|
46
63
|
const secret = config.secret || process.env.CEEMS_PREVIEW_SECRET;
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
? { src: process.env.CEEMS_BRIDGE_SRC, integrity: process.env.CEEMS_BRIDGE_INTEGRITY }
|
|
52
|
-
: undefined);
|
|
53
|
-
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)
|
|
54
68
|
return null;
|
|
55
|
-
const graphqlUrl = apiUrl.replace(/\/$/, '') + '/graphql';
|
|
56
69
|
return {
|
|
57
70
|
async fetch(pageId, token) {
|
|
58
|
-
|
|
71
|
+
const verified = (0, preview_token_1.verifyPreviewToken)(secret, token, pageId);
|
|
72
|
+
if (!verified.ok)
|
|
59
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 };
|
|
60
78
|
let body;
|
|
61
79
|
try {
|
|
62
|
-
const response = await fetch(
|
|
80
|
+
const response = await fetch(apiUrl + '/graphql', {
|
|
63
81
|
method: 'POST',
|
|
64
82
|
headers: { 'content-type': 'application/json' },
|
|
65
83
|
body: JSON.stringify({ query: DRAFT_QUERY, variables: { token } }),
|
|
@@ -80,29 +98,33 @@ function draftServiceFor(config = {}) {
|
|
|
80
98
|
return { ok: false, status: 404 };
|
|
81
99
|
return { ok: false, status: 502 };
|
|
82
100
|
}
|
|
83
|
-
|
|
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 };
|
|
84
110
|
},
|
|
85
|
-
setHeaders(res) {
|
|
111
|
+
setHeaders(res, editorOrigin) {
|
|
86
112
|
res.set({
|
|
87
113
|
'Cache-Control': 'no-store',
|
|
88
114
|
'X-Robots-Tag': 'noindex, nofollow',
|
|
89
|
-
'Content-Security-Policy': `frame-ancestors ${editorOrigin}`,
|
|
115
|
+
'Content-Security-Policy': `frame-ancestors ${editorOrigin || fallbackEditor || "'none'"}`,
|
|
90
116
|
// The token is in the URL: never send it on to the page's assets and links.
|
|
91
117
|
'Referrer-Policy': 'no-referrer',
|
|
92
118
|
'X-Content-Type-Options': 'nosniff',
|
|
93
119
|
});
|
|
94
120
|
},
|
|
95
|
-
injectBridge(html) {
|
|
121
|
+
injectBridge(html, editorOrigin) {
|
|
96
122
|
const $ = cheerio.load(html);
|
|
97
123
|
$('body').addClass('cee-ms-edit');
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (bridge.integrity)
|
|
103
|
-
script.attr('integrity', bridge.integrity).attr('crossorigin', 'anonymous');
|
|
104
|
-
$('body').append(script);
|
|
105
|
-
}
|
|
124
|
+
const script = $('<script defer></script>')
|
|
125
|
+
.attr('src', bridgeSrc || `${editorOrigin}/bridge.js`)
|
|
126
|
+
.attr('data-editor-origin', editorOrigin);
|
|
127
|
+
$('body').append(script);
|
|
106
128
|
return $.html();
|
|
107
129
|
},
|
|
108
130
|
};
|
package/lib/draft.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
|
@@ -5,7 +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 { LinkDrop, wrapLinks } from './links';
|
|
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';
|
|
9
15
|
/**
|
|
10
16
|
* A Cee.ms site renderer: an Express app that renders published pages, and verified editor drafts, with the
|
|
11
17
|
* site's Liquid templates.
|
|
@@ -21,20 +27,41 @@ export declare class Ceems {
|
|
|
21
27
|
site: CeemsSite | undefined;
|
|
22
28
|
storage: Storage;
|
|
23
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;
|
|
24
33
|
constructor(config: CeemsConfig);
|
|
34
|
+
private storageFor;
|
|
25
35
|
private registerTags;
|
|
26
36
|
/** Loads the published `site.json`, remembers it as `this.site`, and returns it. */
|
|
27
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;
|
|
28
47
|
/**
|
|
29
48
|
* Loads the published page for the request's path (or the `404` page), ready for `render`. With
|
|
30
49
|
* `loadPage` false, returns `page` as given (for custom routes that build their own page object).
|
|
31
50
|
*/
|
|
32
51
|
load(req: CeemsRequest, res: express.Response, loadPage?: boolean, page?: CeemsPage): Promise<CeemsPage>;
|
|
33
|
-
/** Reads
|
|
34
|
-
|
|
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>;
|
|
35
59
|
/** Synchronous local read, kept for custom tags. `filename` is a full path. */
|
|
36
60
|
loadLocalFile(filename: string): any;
|
|
37
|
-
/**
|
|
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
|
+
*/
|
|
38
65
|
loadS3File(filename: string): Promise<any>;
|
|
39
66
|
/** The site path for a request (`home` for `/`), and the blog page number if the URL ends in one. */
|
|
40
67
|
parsePath(req: CeemsRequest): {
|