@nakedcreativity/cee-ms-node 1.0.122 → 2.0.0-beta.1
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 +81 -0
- package/lib/draft.d.ts +20 -0
- package/lib/draft.js +110 -0
- package/lib/draft.js.map +1 -0
- package/lib/filters/active.js +1 -2
- package/lib/filters/active.js.map +1 -1
- package/lib/filters/index.js +5 -1
- package/lib/filters/index.js.map +1 -1
- package/lib/filters/slugify.js +3 -4
- package/lib/filters/slugify.js.map +1 -1
- package/lib/index.d.ts +52 -10
- package/lib/index.js +267 -324
- package/lib/index.js.map +1 -1
- package/lib/interfaces.d.ts +55 -16
- package/lib/interfaces.js +1 -1
- package/lib/interfaces.js.map +1 -1
- package/lib/links.d.ts +25 -0
- package/lib/links.js +61 -0
- package/lib/links.js.map +1 -0
- package/lib/preview-token.d.ts +21 -0
- package/lib/preview-token.js +37 -0
- package/lib/preview-token.js.map +1 -0
- package/lib/state.d.ts +17 -0
- package/lib/state.js +44 -0
- package/lib/state.js.map +1 -0
- package/lib/storage.d.ts +6 -0
- package/lib/storage.js +24 -0
- package/lib/storage.js.map +1 -0
- package/lib/tags/blog.d.ts +5 -2
- package/lib/tags/blog.js +25 -41
- package/lib/tags/blog.js.map +1 -1
- package/lib/tags/breadcrumb.d.ts +2 -1
- package/lib/tags/breadcrumb.js +13 -15
- package/lib/tags/breadcrumb.js.map +1 -1
- package/lib/tags/image.d.ts +2 -0
- package/lib/tags/image.js +25 -68
- package/lib/tags/image.js.map +1 -1
- package/lib/tags/index.js +5 -1
- package/lib/tags/index.js.map +1 -1
- package/lib/tags/module.d.ts +1 -0
- package/lib/tags/module.js +6 -6
- package/lib/tags/module.js.map +1 -1
- package/lib/tags/navigation.d.ts +5 -2
- package/lib/tags/navigation.js +14 -217
- package/lib/tags/navigation.js.map +1 -1
- package/lib/tags/page.d.ts +5 -2
- package/lib/tags/page.js +9 -21
- package/lib/tags/page.js.map +1 -1
- package/lib/tags/region.d.ts +1 -0
- package/lib/tags/region.js +6 -7
- package/lib/tags/region.js.map +1 -1
- package/package.json +25 -26
- package/lib/test-tags.d.ts +0 -3
- package/lib/test-tags.js +0 -125
- package/lib/test-tags.js.map +0 -1
- package/lib/test.d.ts +0 -1
- package/lib/test.js +0 -52
- package/lib/test.js.map +0 -1
package/lib/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -11,30 +15,67 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
11
15
|
}) : function(o, v) {
|
|
12
16
|
o["default"] = v;
|
|
13
17
|
});
|
|
14
|
-
var __importStar = (this && this.__importStar) || function (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
36
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20
37
|
};
|
|
21
38
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
39
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
40
|
};
|
|
24
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.Ceems = void 0;
|
|
42
|
+
exports.Ceems = exports.wrapLinks = exports.LinkDrop = exports.renderState = exports.verifyPreviewToken = exports.signPreviewToken = void 0;
|
|
26
43
|
const express_1 = __importDefault(require("express"));
|
|
27
44
|
const path_1 = __importDefault(require("path"));
|
|
28
45
|
const fs_1 = __importDefault(require("fs"));
|
|
29
|
-
const
|
|
30
|
-
const
|
|
46
|
+
const liquidjs_1 = require("liquidjs");
|
|
47
|
+
const links_1 = require("./links");
|
|
31
48
|
const cheerio = __importStar(require("cheerio"));
|
|
32
|
-
const body_parser_1 = __importDefault(require("body-parser"));
|
|
33
49
|
const tags_1 = require("./tags");
|
|
34
50
|
const filters_1 = require("./filters");
|
|
35
|
-
const aws_sdk_1 = require("aws-sdk");
|
|
36
51
|
const interfaces_1 = require("./interfaces");
|
|
52
|
+
const storage_1 = require("./storage");
|
|
53
|
+
const state_1 = require("./state");
|
|
54
|
+
const draft_1 = require("./draft");
|
|
55
|
+
__exportStar(require("./interfaces"), exports);
|
|
56
|
+
var preview_token_1 = require("./preview-token");
|
|
57
|
+
Object.defineProperty(exports, "signPreviewToken", { enumerable: true, get: function () { return preview_token_1.signPreviewToken; } });
|
|
58
|
+
Object.defineProperty(exports, "verifyPreviewToken", { enumerable: true, get: function () { return preview_token_1.verifyPreviewToken; } });
|
|
59
|
+
var state_2 = require("./state");
|
|
60
|
+
Object.defineProperty(exports, "renderState", { enumerable: true, get: function () { return state_2.renderState; } });
|
|
61
|
+
var links_2 = require("./links");
|
|
62
|
+
Object.defineProperty(exports, "LinkDrop", { enumerable: true, get: function () { return links_2.LinkDrop; } });
|
|
63
|
+
Object.defineProperty(exports, "wrapLinks", { enumerable: true, get: function () { return links_2.wrapLinks; } });
|
|
64
|
+
/**
|
|
65
|
+
* A Cee.ms site renderer: an Express app that renders published pages, and verified editor drafts, with the
|
|
66
|
+
* site's Liquid templates.
|
|
67
|
+
*
|
|
68
|
+
* One instance serves every request, so nothing about a request is stored on it. Each render carries its own
|
|
69
|
+
* `RenderState` (site, page, edit mode), passed to templates and tags as render-time globals.
|
|
70
|
+
*/
|
|
37
71
|
class Ceems {
|
|
72
|
+
app;
|
|
73
|
+
engine;
|
|
74
|
+
config;
|
|
75
|
+
/** The latest published `site.json`, refreshed on every public request. Never holds draft data. */
|
|
76
|
+
site;
|
|
77
|
+
storage;
|
|
78
|
+
drafts;
|
|
38
79
|
constructor(config) {
|
|
39
80
|
this.config = config;
|
|
40
81
|
// set defaults
|
|
@@ -44,367 +85,269 @@ class Ceems {
|
|
|
44
85
|
if (!this.config.viewsPath) {
|
|
45
86
|
this.config.viewsPath = './views/';
|
|
46
87
|
}
|
|
47
|
-
if (
|
|
88
|
+
if (this.config.liquidCache === undefined) {
|
|
48
89
|
this.config.liquidCache = true;
|
|
49
90
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
91
|
+
this.config.tags = this.config.tags || [];
|
|
92
|
+
this.config.filters = this.config.filters || [];
|
|
93
|
+
this.storage =
|
|
94
|
+
this.config.cacheSource === interfaces_1.CacheSource.S3
|
|
95
|
+
? (0, storage_1.s3Storage)(this.config.cacheBucket || '', this.config.cacheRegion || process.env.AWS_REGION)
|
|
96
|
+
: (0, storage_1.localStorage)(this.config.cacheFolder || './');
|
|
97
|
+
// One engine for the life of the process. Site data is passed per render, never through these options.
|
|
98
|
+
this.engine = new liquidjs_1.Liquid({
|
|
99
|
+
root: path_1.default.resolve(this.config.viewsPath),
|
|
100
|
+
extname: '.liquid',
|
|
101
|
+
cache: this.config.liquidCache,
|
|
102
|
+
jsTruthy: true,
|
|
103
|
+
globals: { ...(this.config.globals || {}) },
|
|
104
|
+
});
|
|
105
|
+
this.registerTags();
|
|
106
|
+
this.app = (0, express_1.default)();
|
|
57
107
|
this.app.set('case sensitive routing', true);
|
|
58
|
-
|
|
59
|
-
|
|
108
|
+
this.app.engine('liquid', this.engine.express());
|
|
109
|
+
this.app.set('views', this.config.viewsPath);
|
|
110
|
+
this.app.set('view engine', 'liquid');
|
|
60
111
|
this.app.use(express_1.default.static(this.config.assets));
|
|
61
|
-
this.app.use(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
this.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}));
|
|
68
|
-
// pagination and initial path processing
|
|
69
|
-
// this.app.use((req: CeemsRequest, res: express.Response, next) => {
|
|
70
|
-
// const regex = new RegExp('(.+)(/[0-9]{1,})$');
|
|
71
|
-
// this.path = req._parsedUrl.pathname.substring(1);
|
|
72
|
-
// if (regex.test(this.path)) {
|
|
73
|
-
// // looks like a blog url
|
|
74
|
-
// const matches = this.path.match(regex);
|
|
75
|
-
// this.path = matches[1];
|
|
76
|
-
// this.pagination = parseInt(matches[2].substring(1));
|
|
77
|
-
// }
|
|
78
|
-
// if (this.path === '') this.path = 'home';
|
|
79
|
-
// next();
|
|
80
|
-
// });
|
|
81
|
-
// this.app.use((req, res, next) => {
|
|
82
|
-
// if (this.config.response && this.config.response.headers) {
|
|
83
|
-
// _.each(this.config.response.headers, (header) => {
|
|
84
|
-
// res.header(header[0], header[1]);
|
|
85
|
-
// });
|
|
86
|
-
// }
|
|
87
|
-
// next();
|
|
88
|
-
// });
|
|
89
|
-
// load site map
|
|
112
|
+
this.app.use(express_1.default.json({ limit: this.config.parseLimit || '250kb' }));
|
|
113
|
+
this.app.use(express_1.default.urlencoded({ extended: false, limit: this.config.parseLimit || '250kb' }));
|
|
114
|
+
// Drafts are registered before the published-site middleware: they never read or replace `this.site`.
|
|
115
|
+
this.drafts = (0, draft_1.draftServiceFor)(this.config.preview);
|
|
116
|
+
this.app.get('/draft/:pageId', (req, res) => this.handleDraft(req, res));
|
|
117
|
+
// load the published site map for every page request
|
|
90
118
|
this.app.use((req, res, next) => {
|
|
91
119
|
this.loadSite().then((site) => {
|
|
120
|
+
// each request renders against the snapshot it started with
|
|
121
|
+
req.ceemsSite = site;
|
|
92
122
|
next();
|
|
93
|
-
});
|
|
123
|
+
}, (error) => next(error));
|
|
94
124
|
});
|
|
95
125
|
if (!this.config.customHandler) {
|
|
96
126
|
this.app.get(/(.*)/, async (req, res) => {
|
|
97
|
-
|
|
98
|
-
|
|
127
|
+
try {
|
|
128
|
+
const page = await this.load(req, res, true);
|
|
129
|
+
await this.render(page, req, res);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (!(0, state_1.isNotFound)(error))
|
|
133
|
+
console.error('[ceems] load failed', error);
|
|
134
|
+
if (!res.headersSent)
|
|
135
|
+
res.status(404).send('Not found');
|
|
136
|
+
}
|
|
99
137
|
});
|
|
100
138
|
}
|
|
101
|
-
// init routes
|
|
102
|
-
this.app.post(/draft\/(.*)/, async (req, res) => {
|
|
103
|
-
await this.postDraft(req, res);
|
|
104
|
-
});
|
|
105
139
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
if (this.config.cacheSource == interfaces_1.CacheSource.Local) {
|
|
123
|
-
sitePromise = fs_1.default.promises.readFile(this.config.cacheFolder + 'pages/site.json', 'utf-8');
|
|
124
|
-
}
|
|
140
|
+
registerTags() {
|
|
141
|
+
(0, tags_1.registerRegionTag)(this.engine);
|
|
142
|
+
(0, tags_1.registerModuleTag)(this.engine);
|
|
143
|
+
(0, tags_1.registerNavigationTag)(this.engine);
|
|
144
|
+
(0, tags_1.registerBlogTag)(this.engine);
|
|
145
|
+
(0, tags_1.registerPageTag)(this.engine, this);
|
|
146
|
+
(0, tags_1.registerImageTag)(this.engine);
|
|
147
|
+
(0, tags_1.registerImageUrlTag)(this.engine);
|
|
148
|
+
(0, tags_1.registerBreadcrumbTag)(this.engine);
|
|
149
|
+
(0, filters_1.registerActiveFilter)(this.engine);
|
|
150
|
+
(0, filters_1.registerSlugifyFilter)(this.engine);
|
|
151
|
+
// custom tags and filters
|
|
152
|
+
for (const tag of this.config.tags || []) {
|
|
153
|
+
this.engine.registerTag(tag.name, tag.register(this));
|
|
125
154
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
resolve(draft);
|
|
129
|
-
});
|
|
155
|
+
for (const filter of this.config.filters || []) {
|
|
156
|
+
this.engine.registerFilter(filter.name, filter.filter);
|
|
130
157
|
}
|
|
131
|
-
return sitePromise.then((site) => {
|
|
132
|
-
this.site = JSON.parse(site);
|
|
133
|
-
this.engine = new liquidjs_1.Liquid({
|
|
134
|
-
root: path_1.default.resolve(this.config.viewsPath),
|
|
135
|
-
extname: '.liquid',
|
|
136
|
-
cache: this.config.liquidCache,
|
|
137
|
-
jsTruthy: true,
|
|
138
|
-
globals: Object.assign({}, this.site)
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
158
|
}
|
|
159
|
+
/** Loads the published `site.json`, remembers it as `this.site`, and returns it. */
|
|
160
|
+
async loadSite() {
|
|
161
|
+
const site = JSON.parse(await this.storage.read('pages/site.json'));
|
|
162
|
+
this.site = site;
|
|
163
|
+
return site;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Loads the published page for the request's path (or the `404` page), ready for `render`. With
|
|
167
|
+
* `loadPage` false, returns `page` as given (for custom routes that build their own page object).
|
|
168
|
+
*/
|
|
142
169
|
async load(req, res, loadPage, page) {
|
|
143
|
-
|
|
144
|
-
// register liquid engine
|
|
145
|
-
this.app.engine('liquid', this.engine.express());
|
|
146
|
-
this.app.set('views', this.config.viewsPath);
|
|
147
|
-
this.app.set('view engine', 'liquid');
|
|
170
|
+
const site = req.ceemsSite || this.site || (await this.loadSite());
|
|
148
171
|
if (loadPage) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (
|
|
152
|
-
page = this.
|
|
153
|
-
|
|
154
|
-
page = this.loadLocalFile(this.config.cacheFolder + 'pages/' + page._id + '.json');
|
|
155
|
-
}
|
|
156
|
-
if (this.config.cacheSource == interfaces_1.CacheSource.S3) {
|
|
157
|
-
page = await this.loadS3File('pages/' + page._id + '.json');
|
|
158
|
-
}
|
|
159
|
-
page.fullpath = this.site.pages[path].url == 'home' ? '' : this.site.pages[path].url;
|
|
172
|
+
const { path, pagination } = this.parsePath(req);
|
|
173
|
+
const entry = site.pages[path];
|
|
174
|
+
if (entry) {
|
|
175
|
+
page = await this.loadPageFile(entry._id);
|
|
176
|
+
page.fullpath = entry.url == 'home' ? '' : entry.url;
|
|
160
177
|
page.path = page.fullpath.split('/');
|
|
161
|
-
if (
|
|
162
|
-
page.pagination =
|
|
178
|
+
if (pagination)
|
|
179
|
+
page.pagination = pagination;
|
|
163
180
|
}
|
|
164
|
-
// 404
|
|
165
181
|
else {
|
|
166
|
-
console.log('404 for ', path);
|
|
167
182
|
res.status(404);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
page = this.loadLocalFile(this.config.cacheFolder + 'pages/' + notFound._id + '.json');
|
|
172
|
-
}
|
|
173
|
-
if (this.config.cacheSource == interfaces_1.CacheSource.S3) {
|
|
174
|
-
page = await this.loadS3File('pages/' + notFound._id + '.json');
|
|
183
|
+
const notFound = site.pages['404'];
|
|
184
|
+
if (notFound) {
|
|
185
|
+
page = await this.loadPageFile(notFound._id);
|
|
175
186
|
}
|
|
176
187
|
}
|
|
177
188
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
tags_1.registerPageTag(this.engine, this, this.site);
|
|
183
|
-
tags_1.registerImageTag(this.engine);
|
|
184
|
-
tags_1.registerImageUrlTag(this.engine);
|
|
185
|
-
filters_1.registerActiveFilter(this.engine);
|
|
186
|
-
filters_1.registerSlugifyFilter(this.engine);
|
|
187
|
-
tags_1.registerBreadcrumbTag(this.engine, this.site, page);
|
|
188
|
-
// register each of our custom tags and filters
|
|
189
|
-
this.config.tags.forEach((tag) => {
|
|
190
|
-
this.engine.registerTag(tag.name, tag.register(this));
|
|
191
|
-
});
|
|
192
|
-
this.config.filters.forEach((tag) => {
|
|
193
|
-
this.engine.registerFilter(tag.name, tag.filter);
|
|
194
|
-
});
|
|
195
|
-
return page;
|
|
189
|
+
if (!page) {
|
|
190
|
+
throw new state_1.NotFoundError('No page');
|
|
191
|
+
}
|
|
192
|
+
return (0, state_1.attachState)(page, { site, page, edit: false });
|
|
196
193
|
}
|
|
194
|
+
/** Reads `pages/<id>.json`. Stored links read as their address in templates (see `LinkDrop`). */
|
|
195
|
+
async loadPageFile(id) {
|
|
196
|
+
return (0, links_1.wrapLinks)(JSON.parse(await this.storage.read('pages/' + id + '.json')));
|
|
197
|
+
}
|
|
198
|
+
/** Synchronous local read, kept for custom tags. `filename` is a full path. */
|
|
197
199
|
loadLocalFile(filename) {
|
|
198
|
-
return JSON.parse(fs_1.default.readFileSync(filename, 'utf8'));
|
|
200
|
+
return (0, links_1.wrapLinks)(JSON.parse(fs_1.default.readFileSync(filename, 'utf8')));
|
|
199
201
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
Bucket: this.config.cacheBucket,
|
|
204
|
-
Key: filename,
|
|
205
|
-
})
|
|
206
|
-
.promise()
|
|
207
|
-
.then((result) => {
|
|
208
|
-
return JSON.parse(result.Body.toString('utf-8'));
|
|
209
|
-
});
|
|
202
|
+
/** Reads and parses a published JSON file, kept for custom tags. `filename` is relative to the bucket. */
|
|
203
|
+
async loadS3File(filename) {
|
|
204
|
+
return (0, links_1.wrapLinks)(JSON.parse(await this.storage.read(filename)));
|
|
210
205
|
}
|
|
211
|
-
|
|
206
|
+
/** The site path for a request (`home` for `/`), and the blog page number if the URL ends in one. */
|
|
207
|
+
parsePath(req) {
|
|
212
208
|
if (req.overriddenPath) {
|
|
213
|
-
return req.overriddenPath;
|
|
209
|
+
return { path: req.overriddenPath, pagination: null };
|
|
214
210
|
}
|
|
215
211
|
const regex = new RegExp('(.+)(/[0-9]{1,})$');
|
|
216
|
-
let path = req._parsedUrl
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (
|
|
212
|
+
let path = (req._parsedUrl?.pathname || req.path).substring(1);
|
|
213
|
+
let pagination = null;
|
|
214
|
+
const matches = path.match(regex);
|
|
215
|
+
if (matches) {
|
|
220
216
|
// looks like a blog url
|
|
221
|
-
const matches = path.match(regex);
|
|
222
217
|
path = matches[1];
|
|
223
|
-
|
|
218
|
+
pagination = parseInt(matches[2].substring(1));
|
|
224
219
|
}
|
|
225
220
|
if (path === '')
|
|
226
221
|
path = 'home';
|
|
227
|
-
return path;
|
|
222
|
+
return { path, pagination };
|
|
223
|
+
}
|
|
224
|
+
/** @deprecated Use `parsePath`. */
|
|
225
|
+
getPath(req) {
|
|
226
|
+
return this.parsePath(req).path;
|
|
227
|
+
}
|
|
228
|
+
/** The Liquid globals for one render: the site data, static globals, and the render state. */
|
|
229
|
+
globalsFor(state) {
|
|
230
|
+
return { ...state.site, ...this.engine.options.globals, [state_1.RENDER_STATE]: state };
|
|
231
|
+
}
|
|
232
|
+
async renderModule(module, state) {
|
|
233
|
+
const scope = {
|
|
234
|
+
...module.content,
|
|
235
|
+
attributes: { _id: module._id, region: module.region, order: module.order },
|
|
236
|
+
page: state.page,
|
|
237
|
+
site: state.site.site,
|
|
238
|
+
pages: state.site.pages,
|
|
239
|
+
navigation: state.site.navigation,
|
|
240
|
+
};
|
|
241
|
+
let html = await this.engine.renderFile(module.type, scope, { globals: this.globalsFor(state) });
|
|
242
|
+
if (state.edit) {
|
|
243
|
+
const $ = cheerio.load(html, null, false);
|
|
244
|
+
$.root().children().attr('data-module-type', module.type);
|
|
245
|
+
if (module._id) {
|
|
246
|
+
$.root().children().attr('data-module-id', String(module._id));
|
|
247
|
+
}
|
|
248
|
+
html = $.html();
|
|
249
|
+
}
|
|
250
|
+
return html;
|
|
251
|
+
}
|
|
252
|
+
async renderRegion(key, modules, state) {
|
|
253
|
+
const html = (await Promise.all(modules.map((module) => this.renderModule(module, state)))).join('');
|
|
254
|
+
// In drafts, comments mark each region so the editor can replace one without reloading the page.
|
|
255
|
+
return state.edit ? `<!--ceems:region:${key}-->${html}<!--/ceems:region:${key}-->` : html;
|
|
228
256
|
}
|
|
229
|
-
|
|
257
|
+
/** Renders a full page with its layout template. */
|
|
258
|
+
async renderPage(state) {
|
|
259
|
+
const page = state.page;
|
|
230
260
|
if (this.config.whitelistedCookies) {
|
|
231
261
|
page.cookies = {};
|
|
232
|
-
underscore_1.default.each(this.config.whitelistedCookies, (cookieName) => {
|
|
233
|
-
// if (req.cookies && req.cookies[cookieName]) {
|
|
234
|
-
// page.cookies[cookieName] = req.cookies[cookieName];
|
|
235
|
-
// }
|
|
236
|
-
});
|
|
237
262
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
//console.log('inside module render', scope.page)
|
|
256
|
-
if (this.site) {
|
|
257
|
-
scope.site = this.site.site;
|
|
258
|
-
scope.pages = this.site.pages;
|
|
259
|
-
scope.navigation = this.site.navigation;
|
|
260
|
-
}
|
|
261
|
-
this.engine
|
|
262
|
-
.parseAndRender(moduleTemplate, scope)
|
|
263
|
-
.then((html) => {
|
|
264
|
-
if (req.query.edit != null) {
|
|
265
|
-
var $ = cheerio.load(html);
|
|
266
|
-
$.root().children().attr('data-module-type', module.type);
|
|
267
|
-
if (module._id) {
|
|
268
|
-
$.root().children().attr('data-module-id', module._id);
|
|
269
|
-
}
|
|
270
|
-
html = $.html();
|
|
271
|
-
}
|
|
272
|
-
//if (region.containers) {
|
|
273
|
-
//renderedModules[module._id] = html;
|
|
274
|
-
//} else {
|
|
275
|
-
renderedRegions[key][index] = html;
|
|
276
|
-
//}
|
|
277
|
-
return resolve(true);
|
|
278
|
-
})
|
|
279
|
-
.catch((error) => {
|
|
280
|
-
reject(error);
|
|
281
|
-
});
|
|
282
|
-
}));
|
|
283
|
-
}, this);
|
|
284
|
-
}, this);
|
|
285
|
-
// loop through each module not in a region and generate HTML
|
|
286
|
-
underscore_1.default.each(page.modules, (module, key) => {
|
|
287
|
-
let moduleTemplate = fs_1.default.readFileSync(this.config.viewsPath + module.type + '.liquid', 'utf8');
|
|
288
|
-
let moduleContent = Object.assign(Object.assign({}, module.content), { attributes: {
|
|
289
|
-
_id: module._id,
|
|
290
|
-
region: module.region,
|
|
291
|
-
order: module.order
|
|
292
|
-
} });
|
|
293
|
-
jobs.push(new Promise((resolve, reject) => {
|
|
294
|
-
// we need to give module access to entire page object, and therefore the 'sitemap' and other site information
|
|
295
|
-
var scope = Object.assign({}, moduleContent);
|
|
296
|
-
// DO WE NEED TO DO THIS?
|
|
297
|
-
scope.page = page;
|
|
298
|
-
scope.site = this.site.site;
|
|
299
|
-
scope.pages = this.site.pages;
|
|
300
|
-
this.engine
|
|
301
|
-
.parseAndRender(moduleTemplate, scope)
|
|
302
|
-
.then(function (html) {
|
|
303
|
-
//console.log(html)
|
|
304
|
-
if (req.query.edit != null) {
|
|
305
|
-
var $ = cheerio.load(html);
|
|
306
|
-
$.root().children().attr('data-module-type', module.type);
|
|
307
|
-
$.root().children().attr('data-module-id', module._id);
|
|
308
|
-
//console.log($.root().children())
|
|
309
|
-
html = $.html();
|
|
310
|
-
}
|
|
311
|
-
page.modules[key] = html;
|
|
312
|
-
return resolve(true);
|
|
313
|
-
})
|
|
314
|
-
.catch((error) => {
|
|
315
|
-
reject(error);
|
|
316
|
-
});
|
|
317
|
-
}));
|
|
318
|
-
}, this);
|
|
319
|
-
return Promise.all(jobs)
|
|
320
|
-
.then((html) => {
|
|
321
|
-
let finalPage = {
|
|
322
|
-
regions: {},
|
|
323
|
-
navigation: {},
|
|
324
|
-
page: page,
|
|
325
|
-
};
|
|
326
|
-
underscore_1.default.each(renderedRegions, (region, key) => {
|
|
327
|
-
finalPage.regions[key] = '';
|
|
328
|
-
underscore_1.default.each(region, function (html) {
|
|
329
|
-
// items in rendered columns to place
|
|
330
|
-
if (Object.keys(renderedModules).length > 0) {
|
|
331
|
-
underscore_1.default.each(renderedModules, (moduleHtml, key) => {
|
|
332
|
-
html = html.replace(new RegExp('{%\\s' + key + '\\s%}', 'g'), moduleHtml);
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
finalPage.regions[key] += html;
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
let scope = finalPage;
|
|
339
|
-
if (this.site && this.site.navigation)
|
|
340
|
-
scope.navigation = this.site.navigation;
|
|
341
|
-
//console.log('final page render')
|
|
342
|
-
//console.log(scope)
|
|
343
|
-
return new Promise((resolve, reject) => {
|
|
344
|
-
res.render(page.template, scope, (err, html) => {
|
|
345
|
-
if (err) {
|
|
346
|
-
reject(err);
|
|
347
|
-
}
|
|
348
|
-
resolve(html);
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
}).then((html) => {
|
|
352
|
-
if (req.query.edit != null) {
|
|
353
|
-
var $ = cheerio.load(html);
|
|
354
|
-
$('body').addClass('cee-ms-edit');
|
|
355
|
-
$('body').append('<script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>');
|
|
356
|
-
$('body').append('<script type="text/javascript" src="https://cdn.cee.ms/site.js"></script>');
|
|
357
|
-
html = $.html();
|
|
358
|
-
}
|
|
359
|
-
// res.header('X-FRAME-OPTIONS', 'allow-from http://localhost:3002');
|
|
360
|
-
if (send) {
|
|
361
|
-
res.send(html);
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
return html;
|
|
365
|
-
}
|
|
366
|
-
})
|
|
367
|
-
.catch((error) => {
|
|
368
|
-
res.status(500).send(error.message);
|
|
369
|
-
return;
|
|
370
|
-
});
|
|
263
|
+
const regions = {};
|
|
264
|
+
await Promise.all(Object.entries(page.regions || {}).map(async ([key, region]) => {
|
|
265
|
+
regions[key] = await this.renderRegion(key, region.modules || [], state);
|
|
266
|
+
}));
|
|
267
|
+
// modules not in a region, output with `{% module name %}`
|
|
268
|
+
const modules = {};
|
|
269
|
+
await Promise.all(Object.entries(page.modules || {}).map(async ([key, module]) => {
|
|
270
|
+
modules[key] = typeof module === 'string' ? module : await this.renderModule(module, state);
|
|
271
|
+
// legacy templates may read the rendered HTML from the page itself
|
|
272
|
+
page.modules[key] = modules[key];
|
|
273
|
+
}));
|
|
274
|
+
const scope = { regions, modules, navigation: state.site.navigation || {}, page };
|
|
275
|
+
let html = await this.engine.renderFile(page.template, scope, { globals: this.globalsFor(state) });
|
|
276
|
+
if (state.edit && this.drafts) {
|
|
277
|
+
html = this.drafts.injectBridge(html);
|
|
278
|
+
}
|
|
279
|
+
return html;
|
|
371
280
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
281
|
+
/**
|
|
282
|
+
* Renders a page from `load`. Sends the HTML, or returns it when `send` is false. On failure, sends a
|
|
283
|
+
* generic error (404 for a blog page past the last article) and resolves to undefined.
|
|
284
|
+
*/
|
|
285
|
+
async render(page, req, res, send = true) {
|
|
286
|
+
const state = (0, state_1.stateOf)(page) || { site: this.site || (await this.loadSite()), page, edit: false };
|
|
287
|
+
try {
|
|
288
|
+
const html = await this.renderPage(state);
|
|
289
|
+
if (!send)
|
|
290
|
+
return html;
|
|
291
|
+
res.send(html);
|
|
378
292
|
}
|
|
379
|
-
|
|
380
|
-
|
|
293
|
+
catch (error) {
|
|
294
|
+
const status = (0, state_1.isNotFound)(error) ? 404 : 500;
|
|
295
|
+
if (status === 500)
|
|
296
|
+
console.error('[ceems] render failed', error);
|
|
297
|
+
if (!res.headersSent)
|
|
298
|
+
res.status(status).send(status === 404 ? 'Not found' : 'Something went wrong');
|
|
381
299
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* `GET /draft/:pageId?token=…[&module=<id>|®ion=<key>]`: a verified editor draft, as a full page, one
|
|
303
|
+
* module, or one region. The token is checked here, then used to fetch the draft from the Cee.ms API.
|
|
304
|
+
*/
|
|
305
|
+
async handleDraft(req, res) {
|
|
306
|
+
if (!this.drafts) {
|
|
307
|
+
res.status(404).send('Not found');
|
|
308
|
+
return;
|
|
385
309
|
}
|
|
386
|
-
|
|
387
|
-
|
|
310
|
+
const draft = await this.drafts.fetch(req.params.pageId, String(req.query.token || ''));
|
|
311
|
+
if (!draft.ok) {
|
|
312
|
+
this.drafts.setHeaders(res);
|
|
313
|
+
res.status(draft.status).send(draft.status === 401 ? 'Unauthorized' : 'Not available');
|
|
314
|
+
return;
|
|
388
315
|
}
|
|
389
|
-
|
|
390
|
-
// find
|
|
391
|
-
Object.
|
|
392
|
-
|
|
393
|
-
|
|
316
|
+
const { page, site } = draft;
|
|
317
|
+
// find the page's full path from the site map
|
|
318
|
+
const entry = Object.values(site.pages || {}).find((p) => p._id == page._id);
|
|
319
|
+
page.fullpath = entry ? (entry.url == 'home' ? '' : entry.url) : page.handle;
|
|
320
|
+
page.path = page.fullpath.split('/');
|
|
321
|
+
const state = { site, page, edit: true };
|
|
322
|
+
try {
|
|
323
|
+
let html;
|
|
324
|
+
if (typeof req.query.module === 'string') {
|
|
325
|
+
const module = Object.values(page.regions || {})
|
|
326
|
+
.flatMap((region) => region.modules || [])
|
|
327
|
+
.find((m) => String(m._id) === req.query.module);
|
|
328
|
+
if (!module)
|
|
329
|
+
throw new state_1.NotFoundError('No such module');
|
|
330
|
+
html = await this.renderModule(module, state);
|
|
394
331
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
332
|
+
else if (typeof req.query.region === 'string') {
|
|
333
|
+
const region = page.regions?.[req.query.region];
|
|
334
|
+
if (!region)
|
|
335
|
+
throw new state_1.NotFoundError('No such region');
|
|
336
|
+
html = await this.renderRegion(req.query.region, region.modules || [], state);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
html = await this.renderPage(state);
|
|
340
|
+
}
|
|
341
|
+
this.drafts.setHeaders(res);
|
|
342
|
+
res.type('html').send(html);
|
|
398
343
|
}
|
|
399
|
-
|
|
400
|
-
|
|
344
|
+
catch (error) {
|
|
345
|
+
const status = (0, state_1.isNotFound)(error) ? 404 : 500;
|
|
346
|
+
if (status === 500)
|
|
347
|
+
console.error('[ceems] draft render failed', error);
|
|
348
|
+
this.drafts.setHeaders(res);
|
|
349
|
+
res.status(status).send(status === 404 ? 'Not found' : 'Something went wrong');
|
|
401
350
|
}
|
|
402
|
-
page.path = page.fullpath.split('/');
|
|
403
|
-
await this.load(req, res);
|
|
404
|
-
// re-register these tags with the page object
|
|
405
|
-
tags_1.registerNavigationTag(this.engine, this.site, page);
|
|
406
|
-
tags_1.registerBlogTag(this.engine, this.site, page);
|
|
407
|
-
this.render(page, req, res);
|
|
408
351
|
}
|
|
409
352
|
}
|
|
410
353
|
exports.Ceems = Ceems;
|