ghost 4.20.2 → 4.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +1 -1
- package/Gruntfile.js +1 -0
- package/content/themes/casper/assets/built/screen.css +1 -1
- package/content/themes/casper/assets/built/screen.css.map +1 -1
- package/content/themes/casper/assets/css/screen.css +263 -50
- package/content/themes/casper/default.hbs +12 -3
- package/content/themes/casper/index.hbs +25 -23
- package/content/themes/casper/package.json +91 -2
- package/content/themes/casper/partials/post-card.hbs +1 -1
- package/content/themes/casper/post.hbs +18 -14
- package/content/themes/casper/yarn.lock +245 -192
- package/core/boot.js +5 -0
- package/core/bridge.js +14 -0
- package/core/built/assets/{chunk.3.777d43e2ce954ba8b2f5.js → chunk.3.1148677ff3b78e5aeaee.js} +60 -60
- package/core/built/assets/{ghost-dark-20e2892d4f30d0d1183c9ac725ea37d0.css → ghost-dark-684ad238e1a858c7cb5be6988de7c6f5.css} +1 -1
- package/core/built/assets/{ghost.min-57e46fd3b1145ecf2cbd185a13611f3b.css → ghost.min-66e08535f8bb797a8c40e0a2b31f1e9e.css} +1 -1
- package/core/built/assets/{ghost.min-07b6a50c54b3e2e190332c28c7255d2f.js → ghost.min-efbfb823467b66f4acc66537d033aa55.js} +1770 -1906
- package/core/built/assets/{vendor.min-af502ac4142871500fc424f6a5a254ec.js → vendor.min-7c8fdd90f7ecd2e94328a07ea3b64608.js} +985 -956
- package/core/frontend/apps/amp/lib/helpers/amp_content.js +16 -4
- package/core/frontend/helpers/asset.js +9 -1
- package/core/frontend/helpers/ghost_head.js +13 -1
- package/core/frontend/meta/title.js +15 -5
- package/core/frontend/services/card-assets/index.js +16 -0
- package/core/frontend/services/card-assets/service.js +101 -0
- package/core/frontend/services/routing/router-manager.js +5 -3
- package/core/frontend/services/theme-engine/config/defaults.json +4 -1
- package/core/frontend/services/theme-engine/config/index.js +1 -1
- package/core/frontend/src/cards/css/bookmark.css +83 -0
- package/core/frontend/src/cards/css/gallery.css +36 -0
- package/core/frontend/src/cards/js/gallery.js +8 -0
- package/core/frontend/web/middleware/serve-public-file.js +10 -1
- package/core/frontend/web/site.js +10 -9
- package/core/server/adapters/storage/LocalImagesStorage.js +50 -0
- package/core/server/adapters/storage/LocalMediaStorage.js +23 -0
- package/core/server/adapters/storage/{LocalFileStorage.js → LocalStorageBase.js} +36 -48
- package/core/server/adapters/storage/index.js +1 -1
- package/core/server/adapters/storage/utils.js +2 -2
- package/core/server/api/canary/index.js +4 -0
- package/core/server/api/canary/media.js +22 -0
- package/core/server/api/canary/members.js +6 -103
- package/core/server/api/canary/membersStripeConnect.js +0 -10
- package/core/server/api/canary/redirects.js +1 -6
- package/core/server/api/canary/utils/serializers/input/pages.js +8 -0
- package/core/server/api/canary/utils/serializers/output/images.js +4 -0
- package/core/server/api/canary/utils/serializers/output/index.js +4 -0
- package/core/server/api/canary/utils/serializers/output/media.js +28 -0
- package/core/server/api/canary/utils/validators/input/index.js +4 -0
- package/core/server/api/canary/utils/validators/input/media.js +7 -0
- package/core/server/api/v2/redirects.js +1 -6
- package/core/server/api/v2/utils/serializers/output/images.js +4 -0
- package/core/server/api/v3/members.js +5 -1
- package/core/server/api/v3/redirects.js +1 -6
- package/core/server/api/v3/utils/serializers/output/images.js +4 -0
- package/core/server/data/migrations/utils.js +55 -16
- package/core/server/data/migrations/versions/4.22/01-add-is-launch-complete-setting.js +8 -0
- package/core/server/data/migrations/versions/4.22/02-update-launch-complete-setting-from-user-data.js +39 -0
- package/core/server/data/schema/default-settings.json +8 -0
- package/core/server/frontend/ghost.min.css +1 -1
- package/core/server/lib/image/blog-icon.js +2 -4
- package/core/server/lib/image/image-size.js +1 -1
- package/core/server/services/limits.js +3 -6
- package/core/server/services/mega/template.js +4 -0
- package/core/server/services/members/api.js +1 -1
- package/core/server/services/members/emails/signup.js +2 -2
- package/core/server/services/members/stripe-connect.js +14 -0
- package/core/server/services/offers/service.js +1 -31
- package/core/server/services/redirects/api.js +270 -0
- package/core/server/services/redirects/index.js +27 -12
- package/core/server/services/themes/ThemeStorage.js +5 -5
- package/core/server/web/admin/views/default-prod.html +4 -4
- package/core/server/web/admin/views/default.html +4 -4
- package/core/server/web/api/canary/admin/routes.js +13 -4
- package/core/server/web/api/middleware/upload.js +117 -10
- package/core/server/web/members/app.js +1 -1
- package/core/server/web/shared/middlewares/index.js +0 -4
- package/core/shared/config/defaults.json +3 -1
- package/core/shared/config/helpers.js +2 -0
- package/core/shared/config/overrides.json +8 -0
- package/core/shared/labs.js +5 -3
- package/package.json +35 -34
- package/yarn.lock +997 -1016
- package/core/built/assets/img/themes/Editorial-a25a4a34c04dedd858bd5e05ef388b1c.jpg +0 -0
- package/core/built/assets/img/themes/Massively-06edf00108429f7fb8e65f190fba34fe.jpg +0 -0
- package/core/server/services/redirects/settings.js +0 -234
- package/core/server/web/shared/middlewares/custom-redirects.js +0 -128
|
Binary file
|
|
Binary file
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const moment = require('moment-timezone');
|
|
4
|
-
const yaml = require('js-yaml');
|
|
5
|
-
const Promise = require('bluebird');
|
|
6
|
-
|
|
7
|
-
const tpl = require('@tryghost/tpl');
|
|
8
|
-
const errors = require('@tryghost/errors');
|
|
9
|
-
|
|
10
|
-
const validation = require('./validation');
|
|
11
|
-
const config = require('../../../shared/config');
|
|
12
|
-
|
|
13
|
-
const messages = {
|
|
14
|
-
jsonParse: 'Could not parse JSON: {context}.',
|
|
15
|
-
yamlParse: 'YAML input cannot be a plain string. Check the format of your YAML file.',
|
|
16
|
-
yamlParseHelp: 'https://ghost.org/docs/themes/routing/#redirects'
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Redirect configuration object
|
|
21
|
-
* @typedef {Object} RedirectConfig
|
|
22
|
-
* @property {String} from - Defines the relative incoming URL or pattern (regex)
|
|
23
|
-
* @property {String} to - Defines where the incoming traffic should be redirected to, which can be a static URL, or a dynamic value using regex (example: "to": "/$1/")
|
|
24
|
-
* @property {boolean} permanent - Can be defined with true for a permanent HTTP 301 redirect, or false for a temporary HTTP 302 redirect
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
const readRedirectsFile = (redirectsPath) => {
|
|
28
|
-
return fs.readFile(redirectsPath, 'utf-8')
|
|
29
|
-
.catch((err) => {
|
|
30
|
-
if (err.code === 'ENOENT') {
|
|
31
|
-
return Promise.resolve([]);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (errors.utils.isIgnitionError(err)) {
|
|
35
|
-
throw err;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
throw new errors.NotFoundError({
|
|
39
|
-
err: err
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
*
|
|
46
|
-
* @param {String} content serialized JSON or YAML configuration
|
|
47
|
-
* @param {String} ext one of `.json` or `.yaml` extensions
|
|
48
|
-
*
|
|
49
|
-
* @returns {RedirectConfig[]} of parsed redirect config objects
|
|
50
|
-
*/
|
|
51
|
-
const parseRedirectsFile = (content, ext) => {
|
|
52
|
-
if (ext === '.json') {
|
|
53
|
-
let redirects;
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
redirects = JSON.parse(content);
|
|
57
|
-
} catch (err) {
|
|
58
|
-
throw new errors.BadRequestError({
|
|
59
|
-
message: tpl(messages.jsonParse, {context: err.message})
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return redirects;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (ext === '.yaml') {
|
|
67
|
-
let redirects = [];
|
|
68
|
-
let configYaml = yaml.load(content);
|
|
69
|
-
|
|
70
|
-
// yaml.load passes almost every yaml code.
|
|
71
|
-
// Because of that, it's hard to detect if there's an error in the file.
|
|
72
|
-
// But one of the obvious errors is the plain string output.
|
|
73
|
-
// Here we check if the user made this mistake.
|
|
74
|
-
if (typeof configYaml === 'string') {
|
|
75
|
-
throw new errors.BadRequestError({
|
|
76
|
-
message: tpl(messages.yamlParse),
|
|
77
|
-
help: tpl(messages.yamlParseHelp)
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* 302: Temporary redirects
|
|
83
|
-
*/
|
|
84
|
-
for (const redirect in configYaml['302']) {
|
|
85
|
-
redirects.push({
|
|
86
|
-
from: redirect,
|
|
87
|
-
to: configYaml['302'][redirect],
|
|
88
|
-
permanent: false
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* 301: Permanent redirects
|
|
94
|
-
*/
|
|
95
|
-
for (const redirect in configYaml['301']) {
|
|
96
|
-
redirects.push({
|
|
97
|
-
from: redirect,
|
|
98
|
-
to: configYaml['301'][redirect],
|
|
99
|
-
permanent: true
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return redirects;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
throw new errors.IncorrectUsageError();
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const createRedirectsFilePath = (ext) => {
|
|
110
|
-
return path.join(config.getContentPath('data'), `redirects${ext}`);
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const getRedirectsFilePath = async () => {
|
|
114
|
-
const yamlPath = createRedirectsFilePath('.yaml');
|
|
115
|
-
const jsonPath = createRedirectsFilePath('.json');
|
|
116
|
-
|
|
117
|
-
const yamlExists = await fs.pathExists(yamlPath);
|
|
118
|
-
|
|
119
|
-
if (yamlExists) {
|
|
120
|
-
return yamlPath;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const jsonExist = await fs.pathExists(jsonPath);
|
|
124
|
-
|
|
125
|
-
if (jsonExist) {
|
|
126
|
-
return jsonPath;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return null;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const getCurrentRedirectsFilePathSync = () => {
|
|
133
|
-
const yamlPath = createRedirectsFilePath('.yaml');
|
|
134
|
-
const jsonPath = createRedirectsFilePath('.json');
|
|
135
|
-
|
|
136
|
-
if (fs.existsSync(yamlPath)) {
|
|
137
|
-
return yamlPath;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (fs.existsSync(jsonPath)) {
|
|
141
|
-
return jsonPath;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return null;
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const getBackupRedirectsFilePath = (filePath) => {
|
|
148
|
-
const {dir, name, ext} = path.parse(filePath);
|
|
149
|
-
|
|
150
|
-
return path.join(dir, `${name}-${moment().format('YYYY-MM-DD-HH-mm-ss')}${ext}`);
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
// '.json' is the default here because 'yaml' redirects file format is added later in v3.
|
|
154
|
-
// @TODO: change the default to '.yaml' in v4. It may be even removed if '.json' format is deprecated.
|
|
155
|
-
const setFromFilePath = (filePath, ext = '.json') => {
|
|
156
|
-
return getRedirectsFilePath()
|
|
157
|
-
.then((redirectsFilePath) => {
|
|
158
|
-
if (!redirectsFilePath) {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const backupRedirectsPath = getBackupRedirectsFilePath(redirectsFilePath);
|
|
163
|
-
|
|
164
|
-
return fs.pathExists(backupRedirectsPath)
|
|
165
|
-
.then((backupExists) => {
|
|
166
|
-
if (!backupExists) {
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return fs.unlink(backupRedirectsPath);
|
|
171
|
-
})
|
|
172
|
-
.then(() => {
|
|
173
|
-
return fs.move(redirectsFilePath, backupRedirectsPath);
|
|
174
|
-
});
|
|
175
|
-
})
|
|
176
|
-
.then(() => {
|
|
177
|
-
return readRedirectsFile(filePath)
|
|
178
|
-
.then((content) => {
|
|
179
|
-
return parseRedirectsFile(content, ext);
|
|
180
|
-
})
|
|
181
|
-
.then((content) => {
|
|
182
|
-
validation.validate(content);
|
|
183
|
-
|
|
184
|
-
if (ext === '.json') {
|
|
185
|
-
return fs.writeFile(createRedirectsFilePath('.json'), JSON.stringify(content), 'utf-8');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (ext === '.yaml') {
|
|
189
|
-
return fs.copy(filePath, createRedirectsFilePath('.yaml'));
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
// @TODO: When yaml has been changed as the default redirects file format,
|
|
196
|
-
// change this like `const defaultRedirectsContent = ''`.
|
|
197
|
-
// Default json content is []. But the default YAML content is an empty string.
|
|
198
|
-
const defaultJsonFileContent = [];
|
|
199
|
-
|
|
200
|
-
const get = () => {
|
|
201
|
-
return getRedirectsFilePath().then((filePath) => {
|
|
202
|
-
if (filePath === null) {
|
|
203
|
-
return defaultJsonFileContent;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return readRedirectsFile(filePath).then((content) => {
|
|
207
|
-
return path.extname(filePath) === '.json'
|
|
208
|
-
? parseRedirectsFile(content, '.json')
|
|
209
|
-
: content;
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Syncrounously loads current oncifg file and parses it's content
|
|
216
|
-
*
|
|
217
|
-
* @returns {{RedirectConfig[]}} of parsed redirect configurations
|
|
218
|
-
*/
|
|
219
|
-
const loadRedirectsFile = () => {
|
|
220
|
-
const filePath = getCurrentRedirectsFilePathSync();
|
|
221
|
-
|
|
222
|
-
if (filePath === null) {
|
|
223
|
-
return defaultJsonFileContent;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const content = fs.readFileSync(filePath);
|
|
227
|
-
|
|
228
|
-
return parseRedirectsFile(content, path.extname(filePath));
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
module.exports.get = get;
|
|
232
|
-
module.exports.setFromFilePath = setFromFilePath;
|
|
233
|
-
module.exports.getRedirectsFilePath = getRedirectsFilePath;
|
|
234
|
-
module.exports.loadRedirectsFile = loadRedirectsFile;
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
const express = require('../../../../shared/express');
|
|
2
|
-
const url = require('url');
|
|
3
|
-
const querystring = require('querystring');
|
|
4
|
-
const debug = require('@tryghost/debug')('web:shared:mw:custom-redirects');
|
|
5
|
-
const config = require('../../../../shared/config');
|
|
6
|
-
const urlUtils = require('../../../../shared/url-utils');
|
|
7
|
-
const errors = require('@tryghost/errors');
|
|
8
|
-
const logging = require('@tryghost/logging');
|
|
9
|
-
const redirectsService = require('../../../services/redirects');
|
|
10
|
-
|
|
11
|
-
const messages = {
|
|
12
|
-
customRedirectsRegistrationFailure: 'Could not register custom redirects.'
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const _private = {};
|
|
16
|
-
|
|
17
|
-
let customRedirectsRouter;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
*
|
|
21
|
-
* @param {Object} router instance of Express Router to decorate with redirects
|
|
22
|
-
* @param {Object} redirects valid redirects JSON
|
|
23
|
-
*
|
|
24
|
-
* @returns {Object} instance of express.Router express router handling redirects based on config
|
|
25
|
-
*/
|
|
26
|
-
_private.registerRoutes = (router, redirects) => {
|
|
27
|
-
debug('redirects loading');
|
|
28
|
-
|
|
29
|
-
redirects.forEach((redirect) => {
|
|
30
|
-
/**
|
|
31
|
-
* Detect case insensitive modifier when regex is enclosed by
|
|
32
|
-
* / ... /i
|
|
33
|
-
*/
|
|
34
|
-
let options = '';
|
|
35
|
-
if (redirect.from.match(/^\/.*\/i$/)) {
|
|
36
|
-
redirect.from = redirect.from.slice(1, -2);
|
|
37
|
-
options = 'i';
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* always delete trailing slashes, doesn't matter if regex or not
|
|
42
|
-
* Example:
|
|
43
|
-
* - you define /my-blog-post-1/ as from property
|
|
44
|
-
* - /my-blog-post-1 or /my-blog-post-1/ should work
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
if (redirect.from.match(/\/$/)) {
|
|
48
|
-
redirect.from = redirect.from.slice(0, -1);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (redirect.from[redirect.from.length - 1] !== '$') {
|
|
52
|
-
redirect.from += '/?$';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
debug('register', redirect.from);
|
|
56
|
-
router.get(new RegExp(redirect.from, options), function (req, res) {
|
|
57
|
-
const maxAge = redirect.permanent ? config.get('caching:customRedirects:maxAge') : 0;
|
|
58
|
-
const toURL = url.parse(redirect.to);
|
|
59
|
-
const toURLParams = querystring.parse(toURL.query);
|
|
60
|
-
const currentURL = url.parse(req.url);
|
|
61
|
-
const currentURLParams = querystring.parse(currentURL.query);
|
|
62
|
-
const params = Object.assign({}, currentURLParams, toURLParams);
|
|
63
|
-
const search = querystring.stringify(params);
|
|
64
|
-
|
|
65
|
-
toURL.pathname = currentURL.pathname.replace(new RegExp(redirect.from, options), toURL.pathname);
|
|
66
|
-
toURL.search = search !== '' ? `?${search}` : null;
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Only if the URL is internal should we prepend the Ghost subdirectory
|
|
70
|
-
* @see https://github.com/TryGhost/Ghost/issues/10776
|
|
71
|
-
*/
|
|
72
|
-
if (!toURL.hostname) {
|
|
73
|
-
toURL.pathname = urlUtils.urlJoin(urlUtils.getSubdir(), toURL.pathname);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
res.set({
|
|
77
|
-
'Cache-Control': `public, max-age=${maxAge}`
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
res.redirect(redirect.permanent ? 301 : 302, url.format(toURL));
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
debug('redirects loaded');
|
|
85
|
-
|
|
86
|
-
return router;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const loadRoutes = () => {
|
|
90
|
-
try {
|
|
91
|
-
customRedirectsRouter = express.Router('redirects');
|
|
92
|
-
|
|
93
|
-
const redirects = redirectsService.loadRedirectsFile();
|
|
94
|
-
redirectsService.validate(redirects);
|
|
95
|
-
|
|
96
|
-
_private.registerRoutes(customRedirectsRouter, redirects);
|
|
97
|
-
} catch (err) {
|
|
98
|
-
if (errors.utils.isIgnitionError(err)) {
|
|
99
|
-
logging.error(err);
|
|
100
|
-
} else {
|
|
101
|
-
logging.error(new errors.IncorrectUsageError({
|
|
102
|
-
message: messages.customRedirectsRegistrationFailure,
|
|
103
|
-
context: err.message,
|
|
104
|
-
help: 'https://ghost.org/docs/themes/routing/#redirects',
|
|
105
|
-
err
|
|
106
|
-
}));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* - you can extend Ghost with a custom redirects file
|
|
113
|
-
* - see https://github.com/TryGhost/Ghost/issues/7707 and https://ghost.org/docs/themes/routing/#redirects
|
|
114
|
-
* - file loads synchronously, because we need to register the routes before anything else
|
|
115
|
-
*/
|
|
116
|
-
exports.use = function use(siteApp) {
|
|
117
|
-
loadRoutes();
|
|
118
|
-
|
|
119
|
-
// Recommended approach by express, see https://github.com/expressjs/express/issues/2596#issuecomment-81353034.
|
|
120
|
-
// As soon as the express router get's re-instantiated, the old router instance is not used anymore.
|
|
121
|
-
siteApp.use(function customRedirect(req, res, next) {
|
|
122
|
-
customRedirectsRouter(req, res, next);
|
|
123
|
-
});
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
exports.reload = function reload() {
|
|
127
|
-
loadRoutes();
|
|
128
|
-
};
|