mirror of
https://github.com/trambarhq/relaks-wordpress-example.git
synced 2025-09-03 05:02:34 +02:00
Refactored routing code.
Implemented periodic freshness check (issue #8). Implemented proper cache purge (issue #10). Implemented compression in Node side. Implemented JSON retrieval through Node.
This commit is contained in:
19
README.md
19
README.md
@@ -2,14 +2,11 @@
|
||||
2. Go to http://localhost:8000/wp-admin/
|
||||
3. Enter site info
|
||||
4. Log in
|
||||
5. Go to Plugins page
|
||||
6. Search for, install, and activate "Demo Data Creator" plugin
|
||||
7. Search for, install, and activate "Proxy Cache Purge" plugin
|
||||
8. Go to Tools > Demo Data Creator
|
||||
9. Create demo users
|
||||
10. Create demo categories
|
||||
11. Create demo pages
|
||||
12. Create demo posts
|
||||
13. Create demo comments
|
||||
14. Go to Settings > Permalinks
|
||||
15. Select a scheme other than "Plain" (to enable cleaner JSON URLs)
|
||||
5. Go to Settings > Permalinks
|
||||
6. Select a scheme other than "Plain" (to enable clean JSON URLs)
|
||||
7. Go to Plugins page
|
||||
8. Search for, install, and activate "Proxy Cache Purge" plugin
|
||||
9. Search for, install, and activate "FakerPress" plugin
|
||||
|
||||
|
||||
docker exec server_wordpress_1 php -r "echo gethostbyname('node');"
|
||||
|
47
package-lock.json
generated
47
package-lock.json
generated
@@ -1413,10 +1413,9 @@
|
||||
}
|
||||
},
|
||||
"bluebird": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
|
||||
"integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==",
|
||||
"dev": true
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz",
|
||||
"integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw=="
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "4.11.8",
|
||||
@@ -1893,19 +1892,24 @@
|
||||
"dev": true
|
||||
},
|
||||
"compressible": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.14.tgz",
|
||||
"integrity": "sha1-MmxfUH+7BV9UEWeCuWmoG2einac=",
|
||||
"dev": true,
|
||||
"version": "2.0.15",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz",
|
||||
"integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==",
|
||||
"requires": {
|
||||
"mime-db": ">= 1.34.0 < 2"
|
||||
"mime-db": ">= 1.36.0 < 2"
|
||||
},
|
||||
"dependencies": {
|
||||
"mime-db": {
|
||||
"version": "1.37.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
|
||||
"integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"compression": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz",
|
||||
"integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"accepts": "~1.3.5",
|
||||
"bytes": "3.0.0",
|
||||
@@ -6638,15 +6642,15 @@
|
||||
}
|
||||
},
|
||||
"relaks": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/relaks/-/relaks-1.1.8.tgz",
|
||||
"integrity": "sha512-bu9mI7qEvKdPMJ+9wykM1TryRORvX+0CC0LSEIo9zglSgRbMDVdFHQP41mOafA7JWSkYWN8BjvFoVILIcYwGGw==",
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/relaks/-/relaks-1.1.9.tgz",
|
||||
"integrity": "sha512-FY3pBgbTS25+/mv4Q8plNOBivUlZCN5KScBgxUprmUPk+clcOnptSi3x8GjRBoWdV2/bx89XzzCP4fApdJ1kNA==",
|
||||
"dev": true
|
||||
},
|
||||
"relaks-event-emitter": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/relaks-event-emitter/-/relaks-event-emitter-0.0.1.tgz",
|
||||
"integrity": "sha512-h2O9+308vjxkR9YTt3pJYwwVr0JpHi4A6mK7KHBXqhigWbG1gXAZ0O6bHV6E8HjWY3A16zMTqtotGq+T8xtSMw==",
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/relaks-event-emitter/-/relaks-event-emitter-0.0.6.tgz",
|
||||
"integrity": "sha512-scNhLQeH7v1q+CRJFRWVYDUI4kES+P4suQzoW9bbayrwhpbSm5oZtofins8MDRLh+Hp55QEUmoIkfb1NGlljgw==",
|
||||
"dev": true
|
||||
},
|
||||
"relaks-harvest": {
|
||||
@@ -6656,12 +6660,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"relaks-route-manager": {
|
||||
"version": "0.0.18",
|
||||
"resolved": "https://registry.npmjs.org/relaks-route-manager/-/relaks-route-manager-0.0.18.tgz",
|
||||
"integrity": "sha512-Srxe5KaKtIbyrtckTwoNY8/j2A4DTRGB+DUN7vVaguiPxqygY2tDLc0dYcUAcoDxoq/iHKNFu6/wGZM3t2duqw==",
|
||||
"version": "0.0.20",
|
||||
"resolved": "https://registry.npmjs.org/relaks-route-manager/-/relaks-route-manager-0.0.20.tgz",
|
||||
"integrity": "sha512-OQ51fvpI18S/kgBhpo77xjcBYWwE62TTEHLTMVfAb38m+HkXtWM6o/FNZrKP0N+7vJg5muTsh5DfTuN73XdQVA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"relaks-event-emitter": "0.0.1"
|
||||
"relaks-event-emitter": "0.0.6"
|
||||
}
|
||||
},
|
||||
"relateurl": {
|
||||
@@ -6822,8 +6826,7 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safe-regex": {
|
||||
"version": "1.1.0",
|
||||
|
@@ -40,9 +40,9 @@
|
||||
"react": "^16.6.3",
|
||||
"react-html-parser": "^2.0.2",
|
||||
"regenerator-runtime": "^0.12.0",
|
||||
"relaks": "^1.1.8",
|
||||
"relaks": "^1.1.9",
|
||||
"relaks-harvest": "^0.0.3",
|
||||
"relaks-route-manager": "0.0.18",
|
||||
"relaks-route-manager": "0.0.20",
|
||||
"sass-loader": "^6.0.5",
|
||||
"uglifyjs-webpack-plugin": "^0.4.6",
|
||||
"webpack": "^3.1.0",
|
||||
@@ -51,6 +51,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.6.3",
|
||||
"bluebird": "^3.5.3",
|
||||
"compression": "^1.7.3",
|
||||
"cross-fetch": "^2.2.2",
|
||||
"dnscache": "^1.0.1",
|
||||
"express": "^4.16.3",
|
||||
|
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -6,6 +6,6 @@
|
||||
<title>Relaks WordPress Example</title>
|
||||
<link href="/styles.css" rel="stylesheet"></head>
|
||||
<body>
|
||||
<div id="app-container"><!--APP--></div>
|
||||
<script type="text/javascript" src="/app.js"></script></body>
|
||||
<div id="react-container"><!--REACT--></div>
|
||||
<script type="text/javascript" src="/front-end.js"></script></body>
|
||||
</html>
|
||||
|
@@ -24,10 +24,13 @@ A:link, A:visited {
|
||||
background-color: #66023c;
|
||||
overflow: hidden;
|
||||
color: #cccccc; }
|
||||
.side-nav A:link, .side-nav A:visited {
|
||||
color: #cccccc; }
|
||||
.side-nav A:link:hover, .side-nav A:visited:hover {
|
||||
color: #eeccdd; }
|
||||
.side-nav A {
|
||||
opacity: 0.5; }
|
||||
.side-nav A:link, .side-nav A:visited {
|
||||
opacity: 1;
|
||||
color: #cccccc; }
|
||||
.side-nav A:link:hover, .side-nav A:visited:hover {
|
||||
color: #eeccdd; }
|
||||
.side-nav .archive LI {
|
||||
margin-top: 0.1em;
|
||||
margin-bottom: 0.1em; }
|
||||
@@ -85,6 +88,7 @@ A:link, A:visited {
|
||||
.top-nav .page-bar A:link:hover, .top-nav .page-bar A:visited:hover {
|
||||
color: #eecccc; }
|
||||
.top-nav .page-bar .button {
|
||||
flex: 0 0 auto;
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
border-right: 1px solid rgba(204, 204, 204, 0.25); }
|
||||
@@ -138,9 +142,13 @@ A:link, A:visited {
|
||||
max-width: 60em; }
|
||||
.page-container .page .meta, .page-container .post .meta {
|
||||
float: right;
|
||||
text-align: right; }
|
||||
text-align: right;
|
||||
margin-left: 1em;
|
||||
margin-top: 0.4em; }
|
||||
.page-container .page .meta .author, .page-container .post .meta .author {
|
||||
margin-top: 0.25em; }
|
||||
.page-container .page .post-list-view .excerpt, .page-container .post .post-list-view .excerpt {
|
||||
margin-top: -0.7em; }
|
||||
.page-container .page .comments, .page-container .post .comments {
|
||||
font-size: 0.9em;
|
||||
padding-left: 1.5em; }
|
||||
@@ -154,6 +162,8 @@ A:link, A:visited {
|
||||
vertical-align: middle; }
|
||||
.page-container .page .comments .replies, .page-container .post .comments .replies {
|
||||
padding-left: 1.5em; }
|
||||
.page-container .page IMG, .page-container .post IMG {
|
||||
max-width: 100%; }
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.page-container {
|
||||
|
@@ -16,6 +16,8 @@ services:
|
||||
depends_on:
|
||||
- db
|
||||
image: wordpress:latest
|
||||
volumes:
|
||||
- wp_content:/var/www/html/wp-content
|
||||
environment:
|
||||
WORDPRESS_DB_HOST: db:3306
|
||||
WORDPRESS_DB_USER: wordpress
|
||||
@@ -45,4 +47,5 @@ services:
|
||||
restart: always
|
||||
volumes:
|
||||
db_data:
|
||||
wp_content:
|
||||
web_cache:
|
||||
|
203
server/index.js
203
server/index.js
@@ -1,22 +1,22 @@
|
||||
const FS = require('fs');
|
||||
const Bluebird = require('bluebird');
|
||||
const FS = Bluebird.promisifyAll(require('fs'));
|
||||
const OS = require('os');
|
||||
const Express = require('express');
|
||||
const CrossFetch = require('cross-fetch');
|
||||
const DNSCache = require('dnscache');
|
||||
const Crypto = require('crypto');
|
||||
const Compression = require('compression');
|
||||
const SpiderDetector = require('spider-detector')
|
||||
const DNSCache = require('dnscache');
|
||||
const CrossFetch = require('cross-fetch');
|
||||
const ReactDOMServer = require('react-dom/server');
|
||||
const ClientApp = require('./client/app');
|
||||
const FrontEnd = require('./client/front-end');
|
||||
const NginxCache = require('./nginx-cache');
|
||||
|
||||
// enable DNS caching
|
||||
let dnsCache = DNSCache({ enable: true, ttl: 300, cachesize: 100 });
|
||||
|
||||
const basePath = `/`;
|
||||
const perPage = 10;
|
||||
const serverPort = 80;
|
||||
const wordpressHost = process.env.WORDPRESS_HOST;
|
||||
const nginxHost = process.env.NGINX_HOST;
|
||||
const nginxCache = process.env.NGINX_CACHE;
|
||||
|
||||
let wordpressIP;
|
||||
dnsCache.lookup(wordpressHost, (err, result) => {
|
||||
@@ -27,14 +27,40 @@ dnsCache.lookup(wordpressHost, (err, result) => {
|
||||
|
||||
let app = Express();
|
||||
app.set('json spaces', 2);
|
||||
app.use(Compression())
|
||||
app.use(SpiderDetector.middleware());
|
||||
app.use(`/`, Express.static(`${__dirname}/www`));
|
||||
app.get('/.mtime', handleTimestampRequest);
|
||||
app.get('/json/*', handleJSONRequest);
|
||||
app.get(`/*`, handlePageRequest);
|
||||
app.purge(`/*`, handlePurgeRequest);
|
||||
app.use(handleError);
|
||||
app.listen(serverPort);
|
||||
|
||||
let pageDependencies = {};
|
||||
|
||||
async function handleJSONRequest(req, res, next) {
|
||||
try {
|
||||
let path = `/wp-json/${req.url.substr(6)}`;
|
||||
let url = `http://${wordpressHost}${path}`;
|
||||
let sres = await CrossFetch(url);
|
||||
let text = await sres.text();
|
||||
res.send(text);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
function handleTimestampRequest(req, res, next) {
|
||||
try {
|
||||
let now = new Date;
|
||||
let ts = now.toISOString();
|
||||
res.type('text').send(ts);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePageRequest(req, res, next) {
|
||||
try {
|
||||
let host = `http://${nginxHost}`;
|
||||
@@ -52,10 +78,10 @@ async function handlePageRequest(req, res, next) {
|
||||
return CrossFetch(url, options);
|
||||
};
|
||||
let options = { host, path, target, fetch };
|
||||
let rootNode = await ClientApp.render(options);
|
||||
let rootNode = await FrontEnd.render(options);
|
||||
let appHTML = ReactDOMServer.renderToString(rootNode);
|
||||
let indexHTMLPath = `${__dirname}/client/index.html`;
|
||||
let html = await replaceHTMLComment(indexHTMLPath, 'APP', appHTML);
|
||||
let html = await replaceHTMLComment(indexHTMLPath, 'REACT', appHTML);
|
||||
|
||||
if (target === 'hydrate') {
|
||||
// add <noscript> tag to redirect to SEO version
|
||||
@@ -66,20 +92,8 @@ async function handlePageRequest(req, res, next) {
|
||||
}
|
||||
res.type('html').send(html);
|
||||
|
||||
recordDependencies(path, sourceURLs);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
function handleTimestampRequest(req, res, next) {
|
||||
try {
|
||||
let now = new Date;
|
||||
let ts = now.toISOString();
|
||||
res.type('text').send(ts);
|
||||
|
||||
let path = req.url;
|
||||
recordDependencies(path, '*');
|
||||
// save the URLs that the page depends on
|
||||
pageDependencies[path] = sourceURLs.map(addTrailingSlash);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
@@ -92,125 +106,86 @@ function handleError(err, req, res, next) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
async function handlePurgeRequest(req, res) {
|
||||
function handlePurgeRequest(req, res) {
|
||||
let remoteIP = req.connection.remoteAddress;
|
||||
if (remoteIP === wordpressIP) {
|
||||
let url = req.url;
|
||||
let method = req.headers['x-purge-method'];
|
||||
await purgeCachedFile(url, method);
|
||||
|
||||
let pattern = (method === 'regex') ? new RegExp(url) : url;
|
||||
let isJSON;
|
||||
if (pattern instanceof RegExp) {
|
||||
isJSON = pattern.test('/wp-json');
|
||||
} else {
|
||||
isJSON = pattern.startsWith('/wp-json');
|
||||
}
|
||||
if (isJSON) {
|
||||
await purgeDependentPages(pattern);
|
||||
}
|
||||
purgeCachedFile(url, method);
|
||||
}
|
||||
res.end();
|
||||
}
|
||||
|
||||
let pageDependencies = {};
|
||||
|
||||
function recordDependencies(url, sourceURLs) {
|
||||
if (sourceURLs instanceof Array) {
|
||||
sourceURLs = sourceURLs.map(removeTrailingSlash);
|
||||
}
|
||||
pageDependencies[url] = sourceURLs;
|
||||
}
|
||||
|
||||
async function purgeDependentPages(host, pattern) {
|
||||
for (let [ url, sourceURLs ] of Object.entries(pageDependencies)) {
|
||||
let match = false;
|
||||
if (sourceURLs === '*') {
|
||||
match = true;
|
||||
} else if (pattern instanceof RegExp) {
|
||||
match = sourceURLs.some((sourceURL) => {
|
||||
return pattern.test(sourceURL);
|
||||
});
|
||||
} else {
|
||||
let url = removeTrailingSlash(pattern);
|
||||
if (sourceURLs.indexOf(url)) {
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
delete pageDependencies[pageURL];
|
||||
await purgeCachedFile(pageURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function purgeCachedFile(url, method) {
|
||||
console.log(`Purging: ${url}`);
|
||||
if (method === 'regex') {
|
||||
// delete everything
|
||||
let files = await new Promise((resolve, reject) => {
|
||||
FS.readdir(nginxCache, (err, files) => {
|
||||
if (!err) {
|
||||
resolve(files);
|
||||
} else {
|
||||
resolve([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
let isMD5 = /^[0-9a-f]{32}$/;
|
||||
for (let file of files) {
|
||||
if (isMD5.test(file)) {
|
||||
await unlinkFile(`${nginxCache}/${file}`);
|
||||
let pattern, isJSON;
|
||||
if (method === 'default' && url.startsWith('/wp-json/')) {
|
||||
let path = url.substr(9);
|
||||
let m = /^(\w+\/\w+\/(\w+)\/)(\d+)\/$/.exec(path);
|
||||
if (m) {
|
||||
let folderPath = m[1];
|
||||
let folderType = m[2];
|
||||
pattern = new RegExp(`^/json/${folderPath}.*`);
|
||||
}
|
||||
} else if (method === 'regex' && url === '.*') {
|
||||
pattern = /.*/;
|
||||
}
|
||||
if (!pattern) {
|
||||
return;
|
||||
}
|
||||
let purged = await NginxCache.purge(pattern);
|
||||
for (let [ pageURL, sourceURLs ] of Object.entries(pageDependencies)) {
|
||||
let affected = false;
|
||||
for (let jsonURL of purged) {
|
||||
jsonURL = addTrailingSlash(jsonURL);
|
||||
if (sourceURLs.indexOf(jsonURL)) {
|
||||
affected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let hash = Crypto.createHash('md5').update(url);
|
||||
let md5 = hash.digest("hex");
|
||||
await unlinkFile(`${nginxCache}/${md5}`);
|
||||
if (affected) {
|
||||
delete pageDependencies[pageURL];
|
||||
await NginxCache.purge(pageURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function unlinkFile(path) {
|
||||
console.log(`Unlinking ${path}`);
|
||||
await new Promise((resolve, reject) => {
|
||||
FS.unlink(path, (err) => {
|
||||
if (!err) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
await NginxCache.purge('/.mtime');
|
||||
}
|
||||
|
||||
async function replaceHTMLComment(path, comment, newElement) {
|
||||
let text = await new Promise((resolve, reject) => {
|
||||
FS.readFile(path, 'utf-8', (err, text) => {
|
||||
if (!err) {
|
||||
resolve(text);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
let text = await FS.readFileAsync(path, 'utf-8');
|
||||
return text.replace(`<!--${comment}-->`, newElement).replace(`<!--${comment}-->`, newElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove trailing slash from URL
|
||||
* Add trailing slash to URL
|
||||
*
|
||||
* @param {String} url
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
function removeTrailingSlash(url) {
|
||||
var lc = url.charAt(url.length - 1);
|
||||
if (lc === '/') {
|
||||
url = url.substr(0, url.length - 1);
|
||||
function addTrailingSlash(url) {
|
||||
let qi = url.indexOf('?');
|
||||
if (qi === -1) {
|
||||
qi = url.length;
|
||||
}
|
||||
let lc = url.charAt(qi - 1);
|
||||
if (lc !== '/') {
|
||||
url = url.substr(0, qi) + '/' + url.substr(qi);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
[ './index', './nginx-cache', './client/front-end' ].forEach((path) => {
|
||||
let fullPath = require.resolve(path);
|
||||
FS.watchFile(fullPath, (curr, prev) => {
|
||||
if (curr.mtime !== prev.mtime) {
|
||||
console.log('Restarting');
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
NginxCache.purge(/.*/);
|
||||
|
||||
process.on('unhandledRejection', (err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
102
server/nginx-cache.js
Normal file
102
server/nginx-cache.js
Normal file
@@ -0,0 +1,102 @@
|
||||
const Bluebird = require('bluebird');
|
||||
const FS = Bluebird.promisifyAll(require('fs'));
|
||||
const Crypto = require('crypto');
|
||||
|
||||
const nginxCache = process.env.NGINX_CACHE;
|
||||
|
||||
async function purge(pattern) {
|
||||
console.log(`Purging: ${pattern}`);
|
||||
let purged = [];
|
||||
if (typeof(pattern) === 'string') {
|
||||
let url = pattern;
|
||||
let hash = Crypto.createHash('md5').update(url);
|
||||
let md5 = hash.digest('hex');
|
||||
let success = await removeCacheEntry({ url, md5 });
|
||||
if (success) {
|
||||
purged.push(url);
|
||||
}
|
||||
} else if (pattern instanceof RegExp) {
|
||||
let cacheEntries = await loadCacheEntries();
|
||||
for (let cacheEntry of cacheEntries) {
|
||||
if (pattern.test(cacheEntry.url)) {
|
||||
let success = await removeCacheEntry(cacheEntry);
|
||||
if (success) {
|
||||
purged.push(cacheEntry.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return purged;
|
||||
}
|
||||
|
||||
const isMD5 = /^[0-9a-f]{32}$/;
|
||||
|
||||
let cacheEntriesPromise = null;
|
||||
|
||||
async function loadCacheEntries() {
|
||||
if (!cacheEntriesPromise) {
|
||||
cacheEntriesPromise = loadCacheEntriesUncached();
|
||||
}
|
||||
let entries = await cacheEntriesPromise;
|
||||
cacheEntriesPromise = null;
|
||||
return entries;
|
||||
}
|
||||
|
||||
async function loadCacheEntriesUncached() {
|
||||
let files = await FS.readdirAsync(nginxCache);
|
||||
let entries = [];
|
||||
for (let file of files) {
|
||||
if (isMD5.test(file)) {
|
||||
let entry = await loadCacheEntry(file);
|
||||
if (entry) {
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
let cacheEntryCache = {};
|
||||
|
||||
async function loadCacheEntry(md5) {
|
||||
try {
|
||||
let path = `${nginxCache}/${md5}`;
|
||||
let { mtime } = await FS.statAsync(path);
|
||||
let entry = cacheEntryCache[md5];
|
||||
if (!entry || entry.mtime !== mtime) {
|
||||
let url = await loadCacheEntryKey(path);
|
||||
entry = cacheEntryCache[md5] = { url, mtime, md5 };
|
||||
}
|
||||
return entry;
|
||||
} catch (err) {
|
||||
delete cacheEntryCache[md5];
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCacheEntryKey(path) {
|
||||
let fd = await FS.openAsync(path, 'r');
|
||||
let buf = Buffer.alloc(1024);
|
||||
let bytesRead = await FS.readAsync(fd, buf, 0, 1024, 0);
|
||||
let si = buf.indexOf('KEY:');
|
||||
let ei = buf.indexOf('\n', si);
|
||||
if (si !== -1 && ei !== -1) {
|
||||
let s = buf.toString('utf-8', si + 4, ei).trim();;
|
||||
return s;
|
||||
} else {
|
||||
throw new Error('Unable to find key');
|
||||
}
|
||||
}
|
||||
|
||||
async function removeCacheEntry(entry) {
|
||||
try {
|
||||
delete cacheEntryCache[entry.md5];
|
||||
await FS.unlinkAsync(`${nginxCache}/${entry.md5}`);
|
||||
console.log(`Purged: ${entry.url}`);
|
||||
return true;
|
||||
} catch (err){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
exports.purge = purge;
|
@@ -1,14 +1,3 @@
|
||||
gzip on;
|
||||
gzip_disable "msie6";
|
||||
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
|
||||
gzip_min_length 256;
|
||||
|
||||
proxy_cache_path /var/cache/nginx/data keys_zone=data:10m max_size=1g;
|
||||
proxy_temp_path /var/cache/nginx/tmp;
|
||||
|
||||
@@ -16,12 +5,6 @@ server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
location ~ ^/wp-json {
|
||||
proxy_pass http://wordpress;
|
||||
proxy_set_header Host $http_host;
|
||||
include /etc/nginx/conf.d/inc/caching.conf;
|
||||
}
|
||||
|
||||
location ~ ^/wp-* {
|
||||
proxy_pass http://wordpress;
|
||||
proxy_set_header Host $http_host;
|
||||
@@ -36,7 +19,19 @@ server {
|
||||
location / {
|
||||
proxy_pass http://node;
|
||||
proxy_set_header Host $http_host;
|
||||
include /etc/nginx/conf.d/inc/caching.conf;
|
||||
proxy_buffering on;
|
||||
proxy_cache data;
|
||||
proxy_cache_key $uri$is_args$args;
|
||||
proxy_cache_min_uses 1;
|
||||
proxy_cache_valid 200 301 302 120m;
|
||||
proxy_cache_valid 404 1m;
|
||||
proxy_hide_header Cache-Control;
|
||||
proxy_ignore_headers Cache-Control Expires Set-Cookie;
|
||||
|
||||
add_header Cache-Control "public,max-age=0";
|
||||
add_header X-Cache-Date $upstream_http_date;
|
||||
add_header X-Cache-Status $upstream_cache_status;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,16 +0,0 @@
|
||||
proxy_buffering on;
|
||||
proxy_cache data;
|
||||
proxy_cache_key $uri$is_args$args;
|
||||
proxy_cache_min_uses 1;
|
||||
proxy_cache_valid 200 301 302 120m;
|
||||
proxy_cache_valid 404 1m;
|
||||
proxy_hide_header Cache-Control;
|
||||
proxy_hide_header Expires;
|
||||
proxy_hide_header Set-Cookie;
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
proxy_ignore_headers Cache-Control Expires Set-Cookie;
|
||||
|
||||
add_header Cache-Control "public,must-revalidate";
|
||||
add_header X-Cache-Date $upstream_http_date;
|
||||
add_header X-Cache-Status $upstream_cache_status;
|
||||
add_header Access-Control-Allow-Origin *;
|
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -24,10 +24,13 @@ A:link, A:visited {
|
||||
background-color: #66023c;
|
||||
overflow: hidden;
|
||||
color: #cccccc; }
|
||||
.side-nav A:link, .side-nav A:visited {
|
||||
color: #cccccc; }
|
||||
.side-nav A:link:hover, .side-nav A:visited:hover {
|
||||
color: #eeccdd; }
|
||||
.side-nav A {
|
||||
opacity: 0.5; }
|
||||
.side-nav A:link, .side-nav A:visited {
|
||||
opacity: 1;
|
||||
color: #cccccc; }
|
||||
.side-nav A:link:hover, .side-nav A:visited:hover {
|
||||
color: #eeccdd; }
|
||||
.side-nav .archive LI {
|
||||
margin-top: 0.1em;
|
||||
margin-bottom: 0.1em; }
|
||||
@@ -85,6 +88,7 @@ A:link, A:visited {
|
||||
.top-nav .page-bar A:link:hover, .top-nav .page-bar A:visited:hover {
|
||||
color: #eecccc; }
|
||||
.top-nav .page-bar .button {
|
||||
flex: 0 0 auto;
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
border-right: 1px solid rgba(204, 204, 204, 0.25); }
|
||||
@@ -138,9 +142,13 @@ A:link, A:visited {
|
||||
max-width: 60em; }
|
||||
.page-container .page .meta, .page-container .post .meta {
|
||||
float: right;
|
||||
text-align: right; }
|
||||
text-align: right;
|
||||
margin-left: 1em;
|
||||
margin-top: 0.4em; }
|
||||
.page-container .page .meta .author, .page-container .post .meta .author {
|
||||
margin-top: 0.25em; }
|
||||
.page-container .page .post-list-view .excerpt, .page-container .post .post-list-view .excerpt {
|
||||
margin-top: -0.7em; }
|
||||
.page-container .page .comments, .page-container .post .comments {
|
||||
font-size: 0.9em;
|
||||
padding-left: 1.5em; }
|
||||
@@ -154,6 +162,8 @@ A:link, A:visited {
|
||||
vertical-align: middle; }
|
||||
.page-container .page .comments .replies, .page-container .post .comments .replies {
|
||||
padding-left: 1.5em; }
|
||||
.page-container .page IMG, .page-container .post IMG {
|
||||
max-width: 100%; }
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.page-container {
|
||||
|
@@ -15,7 +15,7 @@ class FrontEnd extends PureComponent {
|
||||
super(props);
|
||||
let { routeManager, dataSource } = this.props;
|
||||
this.state = {
|
||||
route: new Route(routeManager),
|
||||
route: new Route(routeManager, dataSource),
|
||||
wp: new Wordpress(dataSource, props.ssr),
|
||||
sideNavCollapsed: true,
|
||||
topNavCollapsed: false,
|
||||
@@ -97,7 +97,8 @@ class FrontEnd extends PureComponent {
|
||||
* @param {RelaksRouteManagerEvent} evt
|
||||
*/
|
||||
handleRouteChange = (evt) => {
|
||||
this.setState({ route: new Route(evt.target) });
|
||||
let { dataSource } = this.props;
|
||||
this.setState({ route: new Route(evt.target, dataSource) });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -6,6 +6,6 @@
|
||||
<title>Relaks WordPress Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app-container"><!--APP--></div>
|
||||
<div id="react-container"><!--REACT--></div>
|
||||
</body>
|
||||
</html>
|
||||
|
48
src/main.js
48
src/main.js
@@ -1,11 +1,12 @@
|
||||
import { delay } from 'bluebird';
|
||||
import { createElement } from 'react';
|
||||
import { hydrate, render } from 'react-dom';
|
||||
import { FrontEnd } from 'front-end';
|
||||
import { routes, setPageType } from 'routing';
|
||||
import { Route, routes } from 'routing';
|
||||
import WordpressDataSource from 'wordpress-data-source';
|
||||
import RouteManager from 'relaks-route-manager';
|
||||
import { harvest } from 'relaks-harvest';
|
||||
import Relaks from 'relaks';
|
||||
import Relaks, { plant } from 'relaks';
|
||||
|
||||
const pageBasePath = '';
|
||||
|
||||
@@ -18,7 +19,7 @@ if (typeof(window) === 'object') {
|
||||
host = 'http://192.168.0.56:8000';
|
||||
}
|
||||
let dataSource = new WordpressDataSource({
|
||||
baseURL: `${host}/wp-json`,
|
||||
baseURL: `${host}/json`,
|
||||
});
|
||||
dataSource.activate();
|
||||
|
||||
@@ -29,32 +30,50 @@ if (typeof(window) === 'object') {
|
||||
preloadingDelay: 2000,
|
||||
});
|
||||
routeManager.addEventListener('beforechange', (evt) => {
|
||||
evt.postponeDefault(setPageType(dataSource, evt.params));
|
||||
let route = new Route(routeManager, dataSource);
|
||||
evt.postponeDefault(route.setPageType(evt.params));
|
||||
});
|
||||
routeManager.activate();
|
||||
await routeManager.start();
|
||||
|
||||
let appContainer = document.getElementById('app-container');
|
||||
if (!appContainer) {
|
||||
throw new Error('Unable to find app element in DOM');
|
||||
}
|
||||
let container = document.getElementById('react-container');
|
||||
// expect SSR unless we're running in dev-server
|
||||
if (!(process.env.NODE_ENV !== 'production' && process.env.WEBPACK_DEV_SERVER)) {
|
||||
let ssrElement = createElement(FrontEnd, { dataSource, routeManager, ssr: 'hydrate' });
|
||||
let seeds = await harvest(ssrElement, { seeds: true });
|
||||
Relaks.set('seeds', seeds);
|
||||
hydrate(ssrElement, appContainer);
|
||||
plant(seeds);
|
||||
hydrate(ssrElement, container);
|
||||
}
|
||||
|
||||
let appElement = createElement(FrontEnd, { dataSource, routeManager });
|
||||
render(appElement, appContainer);
|
||||
let csrElement = createElement(FrontEnd, { dataSource, routeManager });
|
||||
render(csrElement, container);
|
||||
|
||||
// check for changes periodically
|
||||
let mtimeURL = `${host}/.mtime`;
|
||||
let mtimeLast;
|
||||
for (;;) {
|
||||
try {
|
||||
let res = await fetch(mtimeURL);
|
||||
let mtime = await res.text();
|
||||
if (mtime !== mtimeLast) {
|
||||
if (mtimeLast) {
|
||||
console.log('changed');
|
||||
dataSource.invalidate();
|
||||
}
|
||||
mtimeLast = mtime;
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
await delay(10 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', initialize);
|
||||
} else {
|
||||
async function serverSideRender(options) {
|
||||
let dataSource = new WordpressDataSource({
|
||||
baseURL: `${options.host}/wp-json`,
|
||||
baseURL: `${options.host}/json`,
|
||||
fetchFunc: options.fetch,
|
||||
});
|
||||
dataSource.activate();
|
||||
@@ -64,7 +83,8 @@ if (typeof(window) === 'object') {
|
||||
basePath: pageBasePath,
|
||||
});
|
||||
routeManager.addEventListener('beforechange', (evt) => {
|
||||
evt.postponeDefault(setPageType(dataSource, evt.params));
|
||||
let route = new Route(routeManager, dataSource);
|
||||
evt.postponeDefault(route.setPageType(evt.params));
|
||||
});
|
||||
routeManager.activate();
|
||||
await routeManager.start(options.path);
|
||||
|
@@ -31,7 +31,7 @@ class PagePageSync extends PureComponent {
|
||||
static displayName = 'PagePageSync';
|
||||
|
||||
render() {
|
||||
let { route, page, parentPages, childPages } = this.props;
|
||||
let { route, page, parentPages, childPages, transform } = this.props;
|
||||
let trail = [];
|
||||
let parents = [];
|
||||
if (parentPages) {
|
||||
@@ -48,7 +48,7 @@ class PagePageSync extends PureComponent {
|
||||
return (
|
||||
<div className="page">
|
||||
<Breadcrumb trail={trail} />
|
||||
<PageView page={page} />
|
||||
<PageView page={page} transform={route.transformLink} />
|
||||
<PageList route={route} pages={childPages} parentPages={parents} />
|
||||
</div>
|
||||
);
|
||||
|
@@ -64,7 +64,7 @@ class PostPageSync extends PureComponent {
|
||||
return (
|
||||
<div className="page">
|
||||
<Breadcrumb trail={trail} />
|
||||
<PostView category={category} post={post} author={author} />
|
||||
<PostView category={category} post={post} author={author} transform={route.transformLink} />
|
||||
<CommentSection comments={comments} />
|
||||
</div>
|
||||
);
|
||||
|
174
src/routing.js
174
src/routing.js
@@ -1,12 +1,15 @@
|
||||
let _ = require('lodash');
|
||||
|
||||
class Route {
|
||||
constructor(routeManager) {
|
||||
constructor(routeManager, dataSource) {
|
||||
this.routeManager = routeManager;
|
||||
this.name = routeManager.name;
|
||||
this.params = routeManager.params;
|
||||
this.history = routeManager.history;
|
||||
this.url = routeManager.url;
|
||||
this.dataSource = dataSource;
|
||||
this.pageLinkRegExp = null;
|
||||
this.imageLinkRegExp = null;
|
||||
}
|
||||
|
||||
change(url, options) {
|
||||
@@ -21,6 +24,106 @@ class Route {
|
||||
return this.routeManager.find('page', params);
|
||||
}
|
||||
}
|
||||
|
||||
async setPageType(params) {
|
||||
let slugs = params.slugs;
|
||||
if (slugs.length > 0) {
|
||||
let slugType1 = await this.getSlugType(slugs[0]);
|
||||
if (slugType1 === 'page') {
|
||||
params.pageType = 'page';
|
||||
params.pageSlug = _.last(slugs);
|
||||
params.parentPageSlugs = _.slice(slugs, 0, -1);
|
||||
} else if (slugType1 === 'category') {
|
||||
if (slugs.length === 1) {
|
||||
params.pageType = 'category';
|
||||
params.categorySlug = slugs[0];
|
||||
} else if (slugs.length === 2) {
|
||||
params.pageType = 'post';
|
||||
params.categorySlug = slugs[0];
|
||||
params.postSlug = slugs[1];
|
||||
}
|
||||
} else if (slugType1 === 'archive') {
|
||||
if (slugs.length === 1) {
|
||||
params.pageType = 'archive';
|
||||
params.monthSlug = slugs[0];
|
||||
} else if (slugs.length === 2) {
|
||||
let slugType2 = await this.getSlugType(slugs[1]);
|
||||
if (slugType2 === 'category') {
|
||||
params.pageType = 'archive';
|
||||
params.monthSlug = slugs[0];
|
||||
params.categorySlug = slugs[1];
|
||||
} else {
|
||||
params.pageType = 'post';
|
||||
params.monthSlug = slugs[0];
|
||||
params.postSlug = slugs[1];
|
||||
}
|
||||
} else if (slugs.length === 3) {
|
||||
params.pageType = 'post';
|
||||
params.monthSlug = slugs[0];
|
||||
params.categorySlug = slugs[1];
|
||||
params.postSlug = slugs[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!params.pageType) {
|
||||
if (params.search !== undefined) {
|
||||
params.pageType = 'search';
|
||||
} else {
|
||||
params.pageType = 'welcome';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getSlugType(slug) {
|
||||
let options = { minimum: '100%' };
|
||||
let pages = await this.dataSource.fetchList('/wp/v2/pages/?parent=0', options);
|
||||
if (_.some(pages, { slug })) {
|
||||
return 'page';
|
||||
}
|
||||
let categories = await this.dataSource.fetchList('/wp/v2/categories/', options);
|
||||
if (_.some(categories, { slug })) {
|
||||
return 'category';
|
||||
}
|
||||
if (/^\d{4}\-\d{2}$/.test(slug)) {
|
||||
return 'archive';
|
||||
}
|
||||
}
|
||||
|
||||
async preloadPage(params) {
|
||||
try {
|
||||
if (params.postSlug) {
|
||||
this.dataSource.fetchOne('/wp/v2/posts/', params.postSlug);
|
||||
} else if (params.pageSlug) {
|
||||
this.dataSource.fetchOne('/wp/v2/pages/', params.pageSlug);
|
||||
}
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
|
||||
transformLink = (node) => {
|
||||
if (node.type === 'tag' && node.name === 'a') {
|
||||
if (this.pageLinkRegExp) {
|
||||
let m = this.pageLinkRegExp.exec(node.attribs.href);
|
||||
if (m) {
|
||||
let categorySlug = m[1];
|
||||
let postSlug = m[3];
|
||||
node.attribs.href = `/${categorySlug}/${postSlug}/`;
|
||||
delete node.attribs.target;
|
||||
this.preloadPage({ categorySlug, postSlug });
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.imageLinkRegExp) {
|
||||
let m = this.imageLinkRegExp.exec(node.attribs.href);
|
||||
if (m) {
|
||||
if (!node.attribs.target) {
|
||||
node.attribs.target = '_blank';
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let routes = {
|
||||
@@ -50,68 +153,7 @@ let routes = {
|
||||
},
|
||||
};
|
||||
|
||||
async function setPageType(dataSource, params) {
|
||||
let slugs = params.slugs;
|
||||
if (slugs.length > 0) {
|
||||
let slugType1 = await getSlugType(dataSource, slugs[0]);
|
||||
if (slugType1 === 'page') {
|
||||
params.pageType = 'page';
|
||||
params.pageSlug = _.last(slugs);
|
||||
params.parentPageSlugs = _.slice(slugs, 0, -1);
|
||||
} else if (slugType1 === 'category') {
|
||||
if (slugs.length === 1) {
|
||||
params.pageType = 'category';
|
||||
params.categorySlug = slugs[0];
|
||||
} else if (slugs.length === 2) {
|
||||
params.pageType = 'post';
|
||||
params.categorySlug = slugs[0];
|
||||
params.postSlug = slugs[1];
|
||||
}
|
||||
} else if (slugType1 === 'archive') {
|
||||
if (slugs.length === 1) {
|
||||
params.pageType = 'archive';
|
||||
params.monthSlug = slugs[0];
|
||||
} else if (slugs.length === 2) {
|
||||
let slugType2 = await getSlugType(dataSource, slugs[1]);
|
||||
if (slugType2 === 'category') {
|
||||
params.pageType = 'archive';
|
||||
params.monthSlug = slugs[0];
|
||||
params.categorySlug = slugs[1];
|
||||
} else {
|
||||
params.pageType = 'post';
|
||||
params.monthSlug = slugs[0];
|
||||
params.postSlug = slugs[1];
|
||||
}
|
||||
} else if (slugs.length === 3) {
|
||||
params.pageType = 'post';
|
||||
params.monthSlug = slugs[0];
|
||||
params.categorySlug = slugs[1];
|
||||
params.postSlug = slugs[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!params.pageType) {
|
||||
if (params.search !== undefined) {
|
||||
params.pageType = 'search';
|
||||
} else {
|
||||
params.pageType = 'welcome';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getSlugType(dataSource, slug) {
|
||||
let options = { minimum: '100%' };
|
||||
let pages = await dataSource.fetchList('/wp/v2/pages/?parent=0', options);
|
||||
if (_.some(pages, { slug })) {
|
||||
return 'page';
|
||||
}
|
||||
let categories = await dataSource.fetchList('/wp/v2/categories/', options);
|
||||
if (_.some(categories, { slug })) {
|
||||
return 'category';
|
||||
}
|
||||
if (/^\d{4}\-\d{2}$/.test(slug)) {
|
||||
return 'archive';
|
||||
}
|
||||
}
|
||||
|
||||
export { Route, routes, setPageType };
|
||||
export {
|
||||
Route,
|
||||
routes,
|
||||
};
|
||||
|
@@ -249,6 +249,10 @@ A {
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
IMG {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,11 +3,8 @@ import ReactHtmlParser from 'react-html-parser';
|
||||
|
||||
class HTML extends PureComponent {
|
||||
render() {
|
||||
let { text } = this.props;
|
||||
let options = {};
|
||||
if (transformFunc) {
|
||||
options.transform = transformFunc;
|
||||
}
|
||||
let { text, transform } = this.props;
|
||||
let options = { transform };
|
||||
return ReactHtmlParser(text, options);
|
||||
}
|
||||
}
|
||||
@@ -16,17 +13,11 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
const PropTypes = require('prop-types');
|
||||
HTML.propTypes = {
|
||||
text: PropTypes.string,
|
||||
transform: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
let transformFunc = null;
|
||||
|
||||
function setTransformFunction(f) {
|
||||
transformFunc = f;
|
||||
}
|
||||
|
||||
export {
|
||||
HTML as default,
|
||||
HTML,
|
||||
setTransformFunction,
|
||||
};
|
||||
|
@@ -8,7 +8,7 @@ class PageView extends PureComponent {
|
||||
static displayName = 'PageView';
|
||||
|
||||
render() {
|
||||
let { page } = this.props;
|
||||
let { page, transform } = this.props;
|
||||
let title = _.get(page, 'title.rendered', '');
|
||||
let content = _.get(page, 'content.rendered', '');
|
||||
let date = _.get(page, 'modified_gmt');
|
||||
@@ -21,7 +21,9 @@ class PageView extends PureComponent {
|
||||
<div className="date">{date}</div>
|
||||
</div>
|
||||
<h1><HTML text={title} /></h1>
|
||||
<div className="content"><HTML text={content} /></div>
|
||||
<div className="content">
|
||||
<HTML text={content} transform={transform}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -32,6 +34,7 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
|
||||
PageView.propTypes = {
|
||||
category: PropTypes.object,
|
||||
transform: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -8,7 +8,7 @@ class PostView extends PureComponent {
|
||||
static displayName = 'PostView';
|
||||
|
||||
render() {
|
||||
let { category, post, author } = this.props;
|
||||
let { category, post, author, transform } = this.props;
|
||||
let title = _.get(post, 'title.rendered', '');
|
||||
let content = _.get(post, 'content.rendered', '');
|
||||
let date = _.get(post, 'date_gmt');
|
||||
@@ -23,7 +23,9 @@ class PostView extends PureComponent {
|
||||
<div className="author">{name}</div>
|
||||
</div>
|
||||
<h1><HTML text={title} /></h1>
|
||||
<div className="content"><HTML text={content} /></div>
|
||||
<div className="content">
|
||||
<HTML text={content} transform={transform} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -36,6 +38,7 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
category: PropTypes.object,
|
||||
post: PropTypes.object,
|
||||
author: PropTypes.object,
|
||||
transform: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -1662,11 +1662,11 @@ prototype.delete = function(url) {
|
||||
*/
|
||||
prototype.request = function(url, options, token, waitForAuthentication) {
|
||||
var _this = this;
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (token) {
|
||||
var keyword = this.options.authorizationKeyword;
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (!options.headers) {
|
||||
options.headers = {};
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ var clientConfig = {
|
||||
output: {
|
||||
path: Path.resolve('./server/www'),
|
||||
publicPath: '/',
|
||||
filename: 'app.js',
|
||||
filename: 'front-end.js',
|
||||
},
|
||||
resolve: {
|
||||
extensions: [ '.js', '.jsx' ],
|
||||
@@ -81,7 +81,7 @@ var serverConfig = {
|
||||
output: {
|
||||
path: Path.resolve('./server/client'),
|
||||
publicPath: '/',
|
||||
filename: 'app.js',
|
||||
filename: 'front-end.js',
|
||||
libraryTarget: 'commonjs2',
|
||||
},
|
||||
resolve: clientConfig.resolve,
|
||||
|
Reference in New Issue
Block a user