ghost 4.19.1 → 4.20.3

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 (139) hide show
  1. package/.eslintrc.js +9 -8
  2. package/Gruntfile.js +1 -1
  3. package/PRIVACY.md +3 -0
  4. package/content/adapters/README.md +2 -2
  5. package/core/boot.js +4 -4
  6. package/core/bridge.js +9 -1
  7. package/core/built/assets/{chunk.3.0778d8e4d707d2a625f1.js → chunk.3.777d43e2ce954ba8b2f5.js} +1 -1
  8. package/core/built/assets/codemirror/{codemirror-21a09582262987037db73b152fb35f7c.js → codemirror-d25c379b87ec8b33d54ac7149bc0b6ae.js} +14 -14
  9. package/core/built/assets/ghost-dark-20e2892d4f30d0d1183c9ac725ea37d0.css +1 -0
  10. package/core/built/assets/{ghost.min-102753ec485602c8fe80d60a1750bf84.js → ghost.min-07b6a50c54b3e2e190332c28c7255d2f.js} +525 -340
  11. package/core/built/assets/ghost.min-57e46fd3b1145ecf2cbd185a13611f3b.css +1 -0
  12. package/core/built/assets/icons/arrow-left-small.svg +0 -4
  13. package/core/built/assets/img/footer-marketplace-bg-572b6c6486a7e26316954d599eaa9f30.png +0 -0
  14. package/core/built/assets/img/marketing/offers-1-f2e1b653c4d5bb90eea9d7a2862530f9.jpg +0 -0
  15. package/core/built/assets/img/marketing/offers-2-28a225d34cc39d133748431536961d00.jpg +0 -0
  16. package/core/built/assets/img/marketing/offers-3-2094c91ab21a16c37fbe6ec16c140160.jpg +0 -0
  17. package/core/built/assets/img/themes/Casper-c7e784d7188cc5d7f097d9b6c97b0263.jpg +0 -0
  18. package/core/built/assets/simplemde/{simplemde-232f69d126310434489071a1891e6d8b.js → simplemde-3ffc0ec9e9fecf29b9a499db678c9e65.js} +14 -14
  19. package/core/built/assets/{vendor.min-0916203b598271a795909e8e0b1c16c2.js → vendor.min-af502ac4142871500fc424f6a5a254ec.js} +1046 -1043
  20. package/core/frontend/apps/amp/lib/router.js +1 -1
  21. package/core/frontend/meta/author-url.js +1 -1
  22. package/core/frontend/meta/url.js +1 -1
  23. package/core/{server → frontend}/public/favicon.ico +0 -0
  24. package/core/{server → frontend}/public/ghost.css +0 -0
  25. package/core/{server → frontend}/public/ghost.min.css +0 -0
  26. package/core/{server → frontend}/public/robots.txt +0 -0
  27. package/core/{server → frontend}/public/sitemap.xsl +0 -0
  28. package/core/frontend/services/proxy.js +1 -1
  29. package/core/frontend/services/routing/CollectionRouter.js +3 -49
  30. package/core/frontend/services/routing/ParentRouter.js +1 -4
  31. package/core/frontend/services/routing/StaticPagesRouter.js +3 -5
  32. package/core/frontend/services/routing/StaticRoutesRouter.js +4 -6
  33. package/core/frontend/services/routing/TaxonomyRouter.js +4 -5
  34. package/core/frontend/services/routing/controllers/collection.js +2 -2
  35. package/core/frontend/services/routing/controllers/email-post.js +2 -2
  36. package/core/frontend/services/routing/controllers/entry.js +2 -2
  37. package/core/frontend/services/routing/controllers/preview.js +2 -2
  38. package/core/frontend/services/routing/index.js +6 -12
  39. package/core/frontend/services/routing/registry.js +13 -0
  40. package/core/frontend/services/routing/router-manager.js +185 -0
  41. package/core/frontend/services/rss/generate-feed.js +2 -2
  42. package/core/frontend/services/theme-engine/i18n/i18n.js +267 -28
  43. package/core/frontend/services/theme-engine/i18n/index.js +1 -1
  44. package/core/frontend/services/theme-engine/i18n/theme-i18n.js +73 -0
  45. package/core/frontend/web/index.js +1 -0
  46. package/core/{server/web/site → frontend/web}/middleware/handle-image-sizes.js +4 -4
  47. package/core/{server/web/site → frontend/web}/middleware/index.js +0 -0
  48. package/core/{server/web/site → frontend/web}/middleware/redirect-ghost-to-admin.js +3 -3
  49. package/core/{server/web/site → frontend/web}/middleware/serve-favicon.js +6 -6
  50. package/core/{server/web/site → frontend/web}/middleware/serve-public-file.js +2 -2
  51. package/core/{server/web/site → frontend/web}/middleware/static-theme.js +3 -3
  52. package/core/{server/web/site → frontend/web}/routes.js +5 -4
  53. package/core/{server/web/site/app.js → frontend/web/site.js} +12 -16
  54. package/core/server/adapters/storage/LocalFileStorage.js +35 -39
  55. package/core/server/adapters/storage/index.js +12 -2
  56. package/core/server/api/canary/images.js +1 -1
  57. package/core/server/api/canary/offers.js +19 -0
  58. package/core/server/api/canary/utils/serializers/output/settings.js +2 -3
  59. package/core/server/api/canary/utils/serializers/output/utils/url.js +1 -1
  60. package/core/server/api/v2/images.js +1 -1
  61. package/core/server/api/v2/utils/serializers/output/utils/url.js +1 -1
  62. package/core/server/api/v3/images.js +1 -1
  63. package/core/server/api/v3/utils/serializers/output/settings.js +2 -3
  64. package/core/server/api/v3/utils/serializers/output/utils/url.js +1 -1
  65. package/core/server/data/importer/handlers/image.js +1 -1
  66. package/core/server/data/importer/importers/image.js +1 -1
  67. package/core/server/data/migrations/init/1-create-tables.js +7 -8
  68. package/core/server/data/migrations/init/2-create-fixtures.js +8 -8
  69. package/core/server/data/migrations/versions/4.20/01-remove-offer-redemptions-table.js +19 -0
  70. package/core/server/data/migrations/versions/4.20/02-remove-offers-table.js +30 -0
  71. package/core/server/data/migrations/versions/4.20/03-add-offers-table.js +21 -0
  72. package/core/server/data/migrations/versions/4.20/04-add-offer-redemptions-table.js +9 -0
  73. package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +41 -0
  74. package/core/server/data/schema/fixtures/utils.js +150 -143
  75. package/core/server/data/schema/schema.js +4 -3
  76. package/core/server/frontend/ghost.min.css +1 -0
  77. package/core/server/lib/image/image-size.js +2 -2
  78. package/core/server/lib/mobiledoc.js +3 -2
  79. package/core/server/models/action.js +7 -4
  80. package/core/server/models/base/plugins/overrides.js +19 -6
  81. package/core/server/models/index.js +4 -46
  82. package/core/server/models/member.js +5 -0
  83. package/core/server/models/user.js +2 -1
  84. package/core/server/overrides.js +6 -2
  85. package/core/server/services/adapter-manager/config.js +1 -0
  86. package/core/server/services/adapter-manager/index.js +9 -5
  87. package/core/server/services/adapter-manager/options-resolver.js +18 -0
  88. package/core/server/services/bulk-email/mailgun.js +1 -1
  89. package/core/server/services/mega/post-email-serializer.js +2 -2
  90. package/core/server/services/members/api.js +1 -3
  91. package/core/server/services/members/emails/signin.js +1 -1
  92. package/core/server/services/members/emails/signup.js +1 -1
  93. package/core/server/services/members/emails/subscribe.js +1 -1
  94. package/core/server/services/members/service.js +2 -1
  95. package/core/server/services/offers/service.js +1 -1
  96. package/core/server/services/route-settings/route-settings.js +1 -1
  97. package/core/server/services/settings/index.js +3 -1
  98. package/core/server/services/settings/settings-bread-service.js +42 -20
  99. package/core/server/services/slack.js +1 -1
  100. package/core/server/services/themes/activate.js +2 -2
  101. package/core/server/services/themes/activation-bridge.js +6 -6
  102. package/core/server/services/themes/storage.js +1 -1
  103. package/core/{frontend → server}/services/url/Queue.js +0 -0
  104. package/core/{frontend → server}/services/url/Resource.js +0 -0
  105. package/core/{frontend → server}/services/url/Resources.js +2 -2
  106. package/core/{frontend → server}/services/url/UrlGenerator.js +14 -14
  107. package/core/{frontend → server}/services/url/UrlService.js +12 -15
  108. package/core/{frontend → server}/services/url/Urls.js +1 -1
  109. package/core/{frontend → server}/services/url/configs/canary.js +0 -0
  110. package/core/{frontend → server}/services/url/configs/v2.js +0 -0
  111. package/core/{frontend → server}/services/url/configs/v3.js +0 -0
  112. package/core/{frontend → server}/services/url/configs/v4.js +0 -0
  113. package/core/{frontend → server}/services/url/index.js +0 -0
  114. package/core/server/services/xmlrpc.js +1 -1
  115. package/core/server/update-check.js +3 -3
  116. package/core/server/web/admin/controller.js +11 -0
  117. package/core/server/web/admin/views/default-prod.html +4 -4
  118. package/core/server/web/admin/views/default.html +4 -4
  119. package/core/server/web/api/app.js +8 -9
  120. package/core/server/web/oauth/app.js +4 -2
  121. package/core/server/web/parent/backend.js +3 -3
  122. package/core/server/web/parent/frontend.js +2 -2
  123. package/core/server/web/shared/middlewares/custom-redirects.js +0 -8
  124. package/core/server/web/shared/middlewares/maintenance.js +1 -1
  125. package/core/server/web/well-known.js +10 -10
  126. package/core/shared/config/overrides.json +1 -1
  127. package/core/shared/express.js +10 -0
  128. package/core/shared/html-to-plaintext.js +2 -2
  129. package/core/shared/labs.js +14 -5
  130. package/package.json +45 -43
  131. package/yarn.lock +649 -284
  132. package/core/built/assets/ghost-dark-da8e8eba130fb52f97494e51850d1045.css +0 -1
  133. package/core/built/assets/ghost.min-0d8f19623e9f077351bce453034daf4d.css +0 -1
  134. package/core/frontend/services/routing/bootstrap.js +0 -134
  135. package/core/server/public/404-ghost.png +0 -0
  136. package/core/server/public/404-ghost@2x.png +0 -0
  137. package/core/server/web/site/index.js +0 -1
  138. package/core/shared/i18n/i18n.js +0 -312
  139. package/core/shared/i18n/index.js +0 -6
@@ -1,73 +1,312 @@
1
1
  const errors = require('@tryghost/errors');
2
- const i18n = require('../../../../shared/i18n');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const MessageFormat = require('intl-messageformat');
5
+ const jp = require('jsonpath');
6
+ const isString = require('lodash/isString');
7
+ const isObject = require('lodash/isObject');
8
+ const isEqual = require('lodash/isEqual');
9
+ const isNil = require('lodash/isNil');
10
+ const merge = require('lodash/merge');
11
+ const get = require('lodash/get');
3
12
 
4
- class ThemeI18n extends i18n.I18n {
13
+ class I18n {
5
14
  /**
6
15
  * @param {objec} [options]
7
- * @param {string} basePath - the base path for the translation directory (e.g. where themes live)
16
+ * @param {string} basePath - the base path to the translations directory
8
17
  * @param {string} [locale] - a locale string
18
+ * @param {{dot|fulltext}} [stringMode] - which mode our translation keys use
19
+ * @param {{object}} [logging] - logging method
9
20
  */
10
21
  constructor(options = {}) {
11
- super(options);
12
- // We don't care what gets passed in, themes use fulltext mode
13
- this._stringMode = 'fulltext';
22
+ this._basePath = options.basePath || __dirname;
23
+ this._locale = options.locale || this.defaultLocale();
24
+ this._stringMode = options.stringMode || 'dot';
25
+ this._logging = options.logging || console;
26
+
27
+ this._strings = null;
28
+ }
29
+
30
+ /**
31
+ * BasePath getter & setter used for testing
32
+ */
33
+ set basePath(basePath) {
34
+ this._basePath = basePath;
35
+ }
36
+
37
+ /**
38
+ * Need to call init after this
39
+ */
40
+ get basePath() {
41
+ return this._basePath;
42
+ }
43
+
44
+ /**
45
+ * English is our default locale
46
+ */
47
+ defaultLocale() {
48
+ return 'en';
49
+ }
50
+
51
+ supportedLocales() {
52
+ return [this.defaultLocale()];
53
+ }
54
+
55
+ /**
56
+ * Exporting the current locale (e.g. "en") to make it available for other files as well,
57
+ * such as core/frontend/helpers/date.js and core/frontend/helpers/lang.js
58
+ */
59
+ locale() {
60
+ return this._locale;
61
+ }
62
+
63
+ /**
64
+ * Helper method to find and compile the given data context with a proper string resource.
65
+ *
66
+ * @param {string} translationPath Path within the JSON language file to desired string (ie: "errors.init.jsNotBuilt")
67
+ * @param {object} [bindings]
68
+ * @returns {string}
69
+ */
70
+ t(translationPath, bindings) {
71
+ let string;
72
+ let msg;
73
+
74
+ string = this._findString(translationPath);
75
+
76
+ // If the path returns an array (as in the case with anything that has multiple paragraphs such as emails), then
77
+ // loop through them and return an array of translated/formatted strings. Otherwise, just return the normal
78
+ // translated/formatted string.
79
+ if (Array.isArray(string)) {
80
+ msg = [];
81
+ string.forEach(function (s) {
82
+ msg.push(this._formatMessage(s, bindings));
83
+ });
84
+ } else {
85
+ msg = this._formatMessage(string, bindings);
86
+ }
87
+
88
+ return msg;
89
+ }
90
+
91
+ /**
92
+ * Setup i18n support:
93
+ * - Load proper language file into memory
94
+ */
95
+ init() {
96
+ this._strings = this._loadStrings();
97
+
98
+ this._initializeIntl();
99
+ }
100
+
101
+ /**
102
+ * Attempt to load strings from a file
103
+ *
104
+ * @param {sting} [locale]
105
+ * @returns {object} strings
106
+ */
107
+ _loadStrings(locale) {
108
+ locale = locale || this.locale();
109
+
110
+ try {
111
+ return this._readTranslationsFile(locale);
112
+ } catch (err) {
113
+ if (err.code === 'ENOENT') {
114
+ this._handleMissingFileError(locale, err);
115
+
116
+ if (locale !== this.defaultLocale()) {
117
+ this._handleFallbackToDefault();
118
+ return this._loadStrings(this.defaultLocale());
119
+ }
120
+ } else if (err instanceof SyntaxError) {
121
+ this._handleInvalidFileError(locale, err);
122
+ } else {
123
+ throw err;
124
+ }
125
+
126
+ // At this point we've done all we can and strings must be an object
127
+ return {};
128
+ }
14
129
  }
15
130
 
16
131
  /**
17
- * Setup i18n support for themes:
18
- * - Load correct language file into memory
132
+ * Do the lookup within the JSON file using jsonpath
19
133
  *
20
- * @param {object} options
21
- * @param {String} options.activeTheme - name of the currently loaded theme
22
- * @param {String} options.locale - name of the currently loaded locale
134
+ * @param {String} msgPath
135
+ */
136
+ _getCandidateString(msgPath) {
137
+ // Our default string mode is "dot" for dot-notation, e.g. $.something.like.this used in the backend
138
+ // Both jsonpath's dot-notation and bracket-notation start with '$' E.g.: $.store.book.title or $['store']['book']['title']
139
+ // While bracket-notation allows any Unicode characters in keys (i.e. for themes / fulltext mode) E.g. $['Read more']
140
+ // dot-notation allows only word characters in keys for backend messages (that is \w or [A-Za-z0-9_] in RegExp)
141
+ let jsonPath = `$.${msgPath}`;
142
+ let fallback = null;
143
+
144
+ if (this._stringMode === 'fulltext') {
145
+ jsonPath = jp.stringify(['$', msgPath]);
146
+ // In fulltext mode we can use the passed string as a fallback
147
+ fallback = msgPath;
148
+ }
149
+
150
+ try {
151
+ return jp.value(this._strings, jsonPath) || fallback;
152
+ } catch (err) {
153
+ this._handleInvalidKeyError(msgPath, err);
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Parse JSON file for matching locale, returns string giving path.
23
159
  *
160
+ * @param {string} msgPath Path with in the JSON language file to desired string (ie: "errors.init.jsNotBuilt")
161
+ * @returns {string}
24
162
  */
25
- init({activeTheme, locale} = {}) {
26
- // This function is called during theme initialization, and when switching language or theme.
27
- this._locale = locale || this._locale;
28
- this._activetheme = activeTheme || this._activetheme;
163
+ _findString(msgPath, opts) {
164
+ const options = merge({log: true}, opts || {});
165
+ let candidateString;
166
+ let matchingString;
167
+
168
+ // no path? no string
169
+ if (msgPath.length === 0 || !isString(msgPath)) {
170
+ this._handleEmptyKeyError();
171
+ return '';
172
+ }
173
+
174
+ // If not in memory, load translations for core
175
+ if (isNil(this._strings)) {
176
+ this._handleUninitialisedError(msgPath);
177
+ }
178
+
179
+ candidateString = this._getCandidateString(msgPath);
180
+
181
+ matchingString = candidateString || {};
29
182
 
30
- super.init();
183
+ if (isObject(matchingString) || isEqual(matchingString, {})) {
184
+ if (options.log) {
185
+ this._handleMissingKeyError(msgPath);
186
+ }
187
+
188
+ matchingString = this._fallbackError();
189
+ }
190
+
191
+ return matchingString;
31
192
  }
32
193
 
33
194
  _translationFileDirs() {
34
- return [this.basePath, this._activetheme, 'locales'];
195
+ return [this.basePath];
196
+ }
197
+
198
+ // If we are passed a locale, use that, else use this.locale
199
+ _translationFileName(locale) {
200
+ return `${locale || this.locale()}.json`;
201
+ }
202
+
203
+ /**
204
+ * Read the translations file
205
+ * Error handling to be done by consumer
206
+ *
207
+ * @param {string} locale
208
+ */
209
+ _readTranslationsFile(locale) {
210
+ const filePath = path.join(...this._translationFileDirs(), this._translationFileName(locale));
211
+ const content = fs.readFileSync(filePath);
212
+ return JSON.parse(content);
213
+ }
214
+
215
+ /**
216
+ * Format the string using the correct locale and applying any bindings
217
+ * @param {String} string
218
+ * @param {Object} bindings
219
+ */
220
+ _formatMessage(string, bindings) {
221
+ let currentLocale = this.locale();
222
+ let msg = new MessageFormat(string, currentLocale);
223
+
224
+ try {
225
+ msg = msg.format(bindings);
226
+ } catch (err) {
227
+ this._handleFormatError(err);
228
+
229
+ // fallback
230
+ msg = new MessageFormat(this._fallbackError(), currentLocale);
231
+ msg = msg.format();
232
+ }
233
+
234
+ return msg;
235
+ }
236
+
237
+ /**
238
+ * [Private] Setup i18n support:
239
+ * - Polyfill node.js if it does not have Intl support or support for a particular locale
240
+ */
241
+ _initializeIntl() {
242
+ let hasBuiltInLocaleData;
243
+ let IntlPolyfill;
244
+
245
+ if (global.Intl) {
246
+ // Determine if the built-in `Intl` has the locale data we need.
247
+ hasBuiltInLocaleData = this.supportedLocales().every(function (locale) {
248
+ return Intl.NumberFormat.supportedLocalesOf(locale)[0] === locale &&
249
+ Intl.DateTimeFormat.supportedLocalesOf(locale)[0] === locale;
250
+ });
251
+ if (!hasBuiltInLocaleData) {
252
+ // `Intl` exists, but it doesn't have the data we need, so load the
253
+ // polyfill and replace the constructors with need with the polyfill's.
254
+ IntlPolyfill = require('intl');
255
+ Intl.NumberFormat = IntlPolyfill.NumberFormat;
256
+ Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
257
+ }
258
+ } else {
259
+ // No `Intl`, so use and load the polyfill.
260
+ global.Intl = require('intl');
261
+ }
35
262
  }
36
263
 
37
264
  _handleUninitialisedError(key) {
38
- throw new errors.IncorrectUsageError({message: `Theme translation was used before it was initialised with key ${key}`});
265
+ this._logging.warn(`i18n was used before it was initialised with key ${key}`);
266
+ this.init();
267
+ }
268
+
269
+ _handleFormatError(err) {
270
+ this._logging.error(err.message);
39
271
  }
40
272
 
41
273
  _handleFallbackToDefault() {
42
- this._logging.warn(`Theme translations falling back to locales/${this.defaultLocale()}.json.`);
274
+ this._logging.warn(`i18n is falling back to ${this.defaultLocale()}.json.`);
43
275
  }
44
276
 
45
277
  _handleMissingFileError(locale) {
46
- if (locale !== this.defaultLocale()) {
47
- this._logging.warn(`Theme translations file locales/${locale}.json not found.`);
48
- }
278
+ this._logging.warn(`i18n was unable to find ${locale}.json.`);
49
279
  }
50
280
  _handleInvalidFileError(locale, err) {
51
281
  this._logging.error(new errors.IncorrectUsageError({
52
282
  err,
53
- message: `Theme translations unable to parse locales/${locale}.json. Please check that it is valid JSON.`
283
+ message: `i18n was unable to parse ${locale}.json. Please check that it is valid JSON.`
54
284
  }));
55
285
  }
56
286
 
57
287
  _handleEmptyKeyError() {
58
- this._logging.warn('Theme translations {{t}} helper called without a translation key.');
288
+ this._logging.warn('i18n.t() was called without a key');
59
289
  }
60
290
 
61
- _handleMissingKeyError() {
62
- // This case cannot be reached in themes as we use the key as the fallback
291
+ _handleMissingKeyError(key) {
292
+ this._logging.error(new errors.IncorrectUsageError({
293
+ message: `i18n.t() was called with a key that could not be found: ${key}`
294
+ }));
63
295
  }
64
296
 
65
297
  _handleInvalidKeyError(key, err) {
66
298
  throw new errors.IncorrectUsageError({
67
299
  err,
68
- message: `Theme translations {{t}} helper called with an invalid translation key: ${key}`
300
+ message: `i18n.t() called with an invalid key: ${key}`
69
301
  });
70
302
  }
303
+
304
+ /**
305
+ * A really basic error for if everything goes wrong
306
+ */
307
+ _fallbackError() {
308
+ return get(this._strings, 'errors.errors.anErrorOccurred', 'An error occurred');
309
+ }
71
310
  }
72
311
 
73
- module.exports = ThemeI18n;
312
+ module.exports = I18n;
@@ -1,7 +1,7 @@
1
1
  const config = require('../../../../shared/config');
2
2
  const logging = require('@tryghost/logging');
3
3
 
4
- const ThemeI18n = require('./i18n');
4
+ const ThemeI18n = require('./theme-i18n');
5
5
 
6
6
  module.exports = new ThemeI18n({logging, basePath: config.getContentPath('themes')});
7
7
  module.exports.ThemeI18n = ThemeI18n;
@@ -0,0 +1,73 @@
1
+ const errors = require('@tryghost/errors');
2
+ const I18n = require('./i18n');
3
+
4
+ class ThemeI18n extends I18n {
5
+ /**
6
+ * @param {objec} [options]
7
+ * @param {string} basePath - the base path for the translation directory (e.g. where themes live)
8
+ * @param {string} [locale] - a locale string
9
+ */
10
+ constructor(options = {}) {
11
+ super(options);
12
+ // We don't care what gets passed in, themes use fulltext mode
13
+ this._stringMode = 'fulltext';
14
+ }
15
+
16
+ /**
17
+ * Setup i18n support for themes:
18
+ * - Load correct language file into memory
19
+ *
20
+ * @param {object} options
21
+ * @param {String} options.activeTheme - name of the currently loaded theme
22
+ * @param {String} options.locale - name of the currently loaded locale
23
+ *
24
+ */
25
+ init({activeTheme, locale} = {}) {
26
+ // This function is called during theme initialization, and when switching language or theme.
27
+ this._locale = locale || this._locale;
28
+ this._activetheme = activeTheme || this._activetheme;
29
+
30
+ super.init();
31
+ }
32
+
33
+ _translationFileDirs() {
34
+ return [this.basePath, this._activetheme, 'locales'];
35
+ }
36
+
37
+ _handleUninitialisedError(key) {
38
+ throw new errors.IncorrectUsageError({message: `Theme translation was used before it was initialised with key ${key}`});
39
+ }
40
+
41
+ _handleFallbackToDefault() {
42
+ this._logging.warn(`Theme translations falling back to locales/${this.defaultLocale()}.json.`);
43
+ }
44
+
45
+ _handleMissingFileError(locale) {
46
+ if (locale !== this.defaultLocale()) {
47
+ this._logging.warn(`Theme translations file locales/${locale}.json not found.`);
48
+ }
49
+ }
50
+ _handleInvalidFileError(locale, err) {
51
+ this._logging.error(new errors.IncorrectUsageError({
52
+ err,
53
+ message: `Theme translations unable to parse locales/${locale}.json. Please check that it is valid JSON.`
54
+ }));
55
+ }
56
+
57
+ _handleEmptyKeyError() {
58
+ this._logging.warn('Theme translations {{t}} helper called without a translation key.');
59
+ }
60
+
61
+ _handleMissingKeyError() {
62
+ // This case cannot be reached in themes as we use the key as the fallback
63
+ }
64
+
65
+ _handleInvalidKeyError(key, err) {
66
+ throw new errors.IncorrectUsageError({
67
+ err,
68
+ message: `Theme translations {{t}} helper called with an invalid translation key: ${key}`
69
+ });
70
+ }
71
+ }
72
+
73
+ module.exports = ThemeI18n;
@@ -0,0 +1 @@
1
+ module.exports = require('./site');
@@ -2,9 +2,9 @@ const _ = require('lodash');
2
2
  const path = require('path');
3
3
  const {GhostError} = require('@tryghost/errors');
4
4
  const imageTransform = require('@tryghost/image-transform');
5
- const storage = require('../../../adapters/storage');
6
- const activeTheme = require('../../../../frontend/services/theme-engine/active');
7
- const config = require('../../../../shared/config');
5
+ const storage = require('../../../server/adapters/storage');
6
+ const activeTheme = require('../../services/theme-engine/active');
7
+ const config = require('../../../shared/config');
8
8
 
9
9
  const SIZE_PATH_REGEX = /^\/size\/([^/]+)\//;
10
10
  const TRAILING_SLASH_REGEX = /\/+$/;
@@ -63,7 +63,7 @@ module.exports = function (req, res, next) {
63
63
  return redirectToOriginal();
64
64
  }
65
65
 
66
- const storageInstance = storage.getStorage();
66
+ const storageInstance = storage.getStorage('images');
67
67
  // CASE: unsupported storage adapter
68
68
  if (typeof storageInstance.saveRaw !== 'function') {
69
69
  return redirectToOriginal();
@@ -1,6 +1,6 @@
1
- const express = require('../../../../shared/express');
2
- const config = require('../../../../shared/config');
3
- const urlUtils = require('../../../../shared/url-utils');
1
+ const express = require('../../../shared/express');
2
+ const config = require('../../../shared/config');
3
+ const urlUtils = require('../../../shared/url-utils');
4
4
 
5
5
  const adminRedirect = (path) => {
6
6
  return function doRedirect(req, res) {
@@ -1,11 +1,11 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
  const crypto = require('crypto');
4
- const config = require('../../../../shared/config');
5
- const {blogIcon} = require('../../../lib/image');
6
- const storage = require('../../../adapters/storage');
7
- const urlUtils = require('../../../../shared/url-utils');
8
- const settingsCache = require('../../../../shared/settings-cache');
4
+ const config = require('../../../shared/config');
5
+ const {blogIcon} = require('../../../server/lib/image');
6
+ const storage = require('../../../server/adapters/storage');
7
+ const urlUtils = require('../../../shared/url-utils');
8
+ const settingsCache = require('../../../shared/settings-cache');
9
9
 
10
10
  let content;
11
11
 
@@ -49,7 +49,7 @@ function serveFavicon() {
49
49
  return res.redirect(302, urlUtils.urlFor({relativeUrl: `/favicon${originalExtension}`}));
50
50
  }
51
51
 
52
- storage.getStorage()
52
+ storage.getStorage('images')
53
53
  .read({path: filePath})
54
54
  .then((buf) => {
55
55
  iconType = blogIcon.getIconType();
@@ -2,8 +2,8 @@ const crypto = require('crypto');
2
2
  const fs = require('fs-extra');
3
3
  const path = require('path');
4
4
  const errors = require('@tryghost/errors');
5
- const config = require('../../../../shared/config');
6
- const urlUtils = require('../../../../shared/url-utils');
5
+ const config = require('../../../shared/config');
6
+ const urlUtils = require('../../../shared/url-utils');
7
7
  const tpl = require('@tryghost/tpl');
8
8
 
9
9
  const messages = {
@@ -1,8 +1,8 @@
1
1
  const path = require('path');
2
- const config = require('../../../../shared/config');
2
+ const config = require('../../../shared/config');
3
3
  const constants = require('@tryghost/constants');
4
- const themeEngine = require('../../../../frontend/services/theme-engine');
5
- const express = require('../../../../shared/express');
4
+ const themeEngine = require('../../services/theme-engine');
5
+ const express = require('../../../shared/express');
6
6
 
7
7
  function isBlackListedFileType(file) {
8
8
  const blackListedFileTypes = ['.hbs', '.md', '.json'];
@@ -1,12 +1,13 @@
1
1
  const debug = require('@tryghost/debug')('routing');
2
- const routing = require('../../../frontend/services/routing');
2
+
3
+ const routing = require('../services/routing');
3
4
  // NOTE: temporary import from the frontend, will become a backend service soon
4
- const urlService = require('../../../frontend/services/url');
5
- const routeSettings = require('../../services/route-settings');
5
+ const urlService = require('../../server/services/url');
6
+ const routeSettings = require('../../server/services/route-settings');
6
7
 
7
8
  module.exports = function siteRoutes(options = {}) {
8
9
  debug('site Routes', options);
9
10
  options.routerSettings = routeSettings.loadRouteSettingsSync();
10
11
  options.urlService = urlService;
11
- return routing.bootstrap.init(options);
12
+ return routing.routerManager.init(options);
12
13
  };
@@ -1,24 +1,24 @@
1
1
  const debug = require('@tryghost/debug')('frontend');
2
2
  const path = require('path');
3
- const express = require('../../../shared/express');
3
+ const express = require('../../shared/express');
4
4
  const cors = require('cors');
5
5
  const {URL} = require('url');
6
6
  const errors = require('@tryghost/errors');
7
7
 
8
8
  // App requires
9
- const config = require('../../../shared/config');
9
+ const config = require('../../shared/config');
10
10
  const constants = require('@tryghost/constants');
11
- const storage = require('../../adapters/storage');
12
- const urlService = require('../../../frontend/services/url');
13
- const urlUtils = require('../../../shared/url-utils');
14
- const sitemapHandler = require('../../../frontend/services/sitemap/handler');
15
- const appService = require('../../../frontend/services/apps');
16
- const themeEngine = require('../../../frontend/services/theme-engine');
11
+ const storage = require('../../server/adapters/storage');
12
+ const urlService = require('../../server/services/url');
13
+ const urlUtils = require('../../shared/url-utils');
14
+ const sitemapHandler = require('../services/sitemap/handler');
15
+ const appService = require('../services/apps');
16
+ const themeEngine = require('../services/theme-engine');
17
17
  const themeMiddleware = themeEngine.middleware;
18
- const membersService = require('../../services/members');
19
- const offersService = require('../../services/offers');
18
+ const membersService = require('../../server/services/members');
19
+ const offersService = require('../../server/services/offers');
20
20
  const siteRoutes = require('./routes');
21
- const shared = require('../shared');
21
+ const shared = require('../../server/web/shared');
22
22
  const mw = require('./middleware');
23
23
 
24
24
  const STATIC_IMAGE_URL_PREFIX = `/${urlUtils.STATIC_IMAGE_URL_PREFIX}`;
@@ -106,12 +106,8 @@ module.exports = function setupSiteApp(options = {}) {
106
106
  siteApp.use(mw.servePublicFile('public/ghost.css', 'text/css', constants.ONE_HOUR_S));
107
107
  siteApp.use(mw.servePublicFile('public/ghost.min.css', 'text/css', constants.ONE_YEAR_S));
108
108
 
109
- // Serve images for default templates
110
- siteApp.use(mw.servePublicFile('public/404-ghost@2x.png', 'image/png', constants.ONE_HOUR_S));
111
- siteApp.use(mw.servePublicFile('public/404-ghost.png', 'image/png', constants.ONE_HOUR_S));
112
-
113
109
  // Serve blog images using the storage adapter
114
- siteApp.use(STATIC_IMAGE_URL_PREFIX, mw.handleImageSizes, storage.getStorage().serve());
110
+ siteApp.use(STATIC_IMAGE_URL_PREFIX, mw.handleImageSizes, storage.getStorage('images').serve());
115
111
 
116
112
  // @TODO find this a better home
117
113
  // We do this here, at the top level, because helpers require so much stuff.