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.
Files changed (85) hide show
  1. package/.eslintrc.js +1 -1
  2. package/Gruntfile.js +1 -0
  3. package/content/themes/casper/assets/built/screen.css +1 -1
  4. package/content/themes/casper/assets/built/screen.css.map +1 -1
  5. package/content/themes/casper/assets/css/screen.css +263 -50
  6. package/content/themes/casper/default.hbs +12 -3
  7. package/content/themes/casper/index.hbs +25 -23
  8. package/content/themes/casper/package.json +91 -2
  9. package/content/themes/casper/partials/post-card.hbs +1 -1
  10. package/content/themes/casper/post.hbs +18 -14
  11. package/content/themes/casper/yarn.lock +245 -192
  12. package/core/boot.js +5 -0
  13. package/core/bridge.js +14 -0
  14. package/core/built/assets/{chunk.3.777d43e2ce954ba8b2f5.js → chunk.3.1148677ff3b78e5aeaee.js} +60 -60
  15. package/core/built/assets/{ghost-dark-20e2892d4f30d0d1183c9ac725ea37d0.css → ghost-dark-684ad238e1a858c7cb5be6988de7c6f5.css} +1 -1
  16. package/core/built/assets/{ghost.min-57e46fd3b1145ecf2cbd185a13611f3b.css → ghost.min-66e08535f8bb797a8c40e0a2b31f1e9e.css} +1 -1
  17. package/core/built/assets/{ghost.min-07b6a50c54b3e2e190332c28c7255d2f.js → ghost.min-efbfb823467b66f4acc66537d033aa55.js} +1770 -1906
  18. package/core/built/assets/{vendor.min-af502ac4142871500fc424f6a5a254ec.js → vendor.min-7c8fdd90f7ecd2e94328a07ea3b64608.js} +985 -956
  19. package/core/frontend/apps/amp/lib/helpers/amp_content.js +16 -4
  20. package/core/frontend/helpers/asset.js +9 -1
  21. package/core/frontend/helpers/ghost_head.js +13 -1
  22. package/core/frontend/meta/title.js +15 -5
  23. package/core/frontend/services/card-assets/index.js +16 -0
  24. package/core/frontend/services/card-assets/service.js +101 -0
  25. package/core/frontend/services/routing/router-manager.js +5 -3
  26. package/core/frontend/services/theme-engine/config/defaults.json +4 -1
  27. package/core/frontend/services/theme-engine/config/index.js +1 -1
  28. package/core/frontend/src/cards/css/bookmark.css +83 -0
  29. package/core/frontend/src/cards/css/gallery.css +36 -0
  30. package/core/frontend/src/cards/js/gallery.js +8 -0
  31. package/core/frontend/web/middleware/serve-public-file.js +10 -1
  32. package/core/frontend/web/site.js +10 -9
  33. package/core/server/adapters/storage/LocalImagesStorage.js +50 -0
  34. package/core/server/adapters/storage/LocalMediaStorage.js +23 -0
  35. package/core/server/adapters/storage/{LocalFileStorage.js → LocalStorageBase.js} +36 -48
  36. package/core/server/adapters/storage/index.js +1 -1
  37. package/core/server/adapters/storage/utils.js +2 -2
  38. package/core/server/api/canary/index.js +4 -0
  39. package/core/server/api/canary/media.js +22 -0
  40. package/core/server/api/canary/members.js +6 -103
  41. package/core/server/api/canary/membersStripeConnect.js +0 -10
  42. package/core/server/api/canary/redirects.js +1 -6
  43. package/core/server/api/canary/utils/serializers/input/pages.js +8 -0
  44. package/core/server/api/canary/utils/serializers/output/images.js +4 -0
  45. package/core/server/api/canary/utils/serializers/output/index.js +4 -0
  46. package/core/server/api/canary/utils/serializers/output/media.js +28 -0
  47. package/core/server/api/canary/utils/validators/input/index.js +4 -0
  48. package/core/server/api/canary/utils/validators/input/media.js +7 -0
  49. package/core/server/api/v2/redirects.js +1 -6
  50. package/core/server/api/v2/utils/serializers/output/images.js +4 -0
  51. package/core/server/api/v3/members.js +5 -1
  52. package/core/server/api/v3/redirects.js +1 -6
  53. package/core/server/api/v3/utils/serializers/output/images.js +4 -0
  54. package/core/server/data/migrations/utils.js +55 -16
  55. package/core/server/data/migrations/versions/4.22/01-add-is-launch-complete-setting.js +8 -0
  56. package/core/server/data/migrations/versions/4.22/02-update-launch-complete-setting-from-user-data.js +39 -0
  57. package/core/server/data/schema/default-settings.json +8 -0
  58. package/core/server/frontend/ghost.min.css +1 -1
  59. package/core/server/lib/image/blog-icon.js +2 -4
  60. package/core/server/lib/image/image-size.js +1 -1
  61. package/core/server/services/limits.js +3 -6
  62. package/core/server/services/mega/template.js +4 -0
  63. package/core/server/services/members/api.js +1 -1
  64. package/core/server/services/members/emails/signup.js +2 -2
  65. package/core/server/services/members/stripe-connect.js +14 -0
  66. package/core/server/services/offers/service.js +1 -31
  67. package/core/server/services/redirects/api.js +270 -0
  68. package/core/server/services/redirects/index.js +27 -12
  69. package/core/server/services/themes/ThemeStorage.js +5 -5
  70. package/core/server/web/admin/views/default-prod.html +4 -4
  71. package/core/server/web/admin/views/default.html +4 -4
  72. package/core/server/web/api/canary/admin/routes.js +13 -4
  73. package/core/server/web/api/middleware/upload.js +117 -10
  74. package/core/server/web/members/app.js +1 -1
  75. package/core/server/web/shared/middlewares/index.js +0 -4
  76. package/core/shared/config/defaults.json +3 -1
  77. package/core/shared/config/helpers.js +2 -0
  78. package/core/shared/config/overrides.json +8 -0
  79. package/core/shared/labs.js +5 -3
  80. package/package.json +35 -34
  81. package/yarn.lock +997 -1016
  82. package/core/built/assets/img/themes/Editorial-a25a4a34c04dedd858bd5e05ef388b1c.jpg +0 -0
  83. package/core/built/assets/img/themes/Massively-06edf00108429f7fb8e65f190fba34fe.jpg +0 -0
  84. package/core/server/services/redirects/settings.js +0 -234
  85. package/core/server/web/shared/middlewares/custom-redirects.js +0 -128
@@ -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
- };