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.
- package/.eslintrc.js +9 -8
- package/Gruntfile.js +1 -1
- package/PRIVACY.md +3 -0
- package/content/adapters/README.md +2 -2
- package/core/boot.js +4 -4
- package/core/bridge.js +9 -1
- package/core/built/assets/{chunk.3.0778d8e4d707d2a625f1.js → chunk.3.777d43e2ce954ba8b2f5.js} +1 -1
- package/core/built/assets/codemirror/{codemirror-21a09582262987037db73b152fb35f7c.js → codemirror-d25c379b87ec8b33d54ac7149bc0b6ae.js} +14 -14
- package/core/built/assets/ghost-dark-20e2892d4f30d0d1183c9ac725ea37d0.css +1 -0
- package/core/built/assets/{ghost.min-102753ec485602c8fe80d60a1750bf84.js → ghost.min-07b6a50c54b3e2e190332c28c7255d2f.js} +525 -340
- package/core/built/assets/ghost.min-57e46fd3b1145ecf2cbd185a13611f3b.css +1 -0
- package/core/built/assets/icons/arrow-left-small.svg +0 -4
- package/core/built/assets/img/footer-marketplace-bg-572b6c6486a7e26316954d599eaa9f30.png +0 -0
- package/core/built/assets/img/marketing/offers-1-f2e1b653c4d5bb90eea9d7a2862530f9.jpg +0 -0
- package/core/built/assets/img/marketing/offers-2-28a225d34cc39d133748431536961d00.jpg +0 -0
- package/core/built/assets/img/marketing/offers-3-2094c91ab21a16c37fbe6ec16c140160.jpg +0 -0
- package/core/built/assets/img/themes/Casper-c7e784d7188cc5d7f097d9b6c97b0263.jpg +0 -0
- package/core/built/assets/simplemde/{simplemde-232f69d126310434489071a1891e6d8b.js → simplemde-3ffc0ec9e9fecf29b9a499db678c9e65.js} +14 -14
- package/core/built/assets/{vendor.min-0916203b598271a795909e8e0b1c16c2.js → vendor.min-af502ac4142871500fc424f6a5a254ec.js} +1046 -1043
- package/core/frontend/apps/amp/lib/router.js +1 -1
- package/core/frontend/meta/author-url.js +1 -1
- package/core/frontend/meta/url.js +1 -1
- package/core/{server → frontend}/public/favicon.ico +0 -0
- package/core/{server → frontend}/public/ghost.css +0 -0
- package/core/{server → frontend}/public/ghost.min.css +0 -0
- package/core/{server → frontend}/public/robots.txt +0 -0
- package/core/{server → frontend}/public/sitemap.xsl +0 -0
- package/core/frontend/services/proxy.js +1 -1
- package/core/frontend/services/routing/CollectionRouter.js +3 -49
- package/core/frontend/services/routing/ParentRouter.js +1 -4
- package/core/frontend/services/routing/StaticPagesRouter.js +3 -5
- package/core/frontend/services/routing/StaticRoutesRouter.js +4 -6
- package/core/frontend/services/routing/TaxonomyRouter.js +4 -5
- package/core/frontend/services/routing/controllers/collection.js +2 -2
- package/core/frontend/services/routing/controllers/email-post.js +2 -2
- package/core/frontend/services/routing/controllers/entry.js +2 -2
- package/core/frontend/services/routing/controllers/preview.js +2 -2
- package/core/frontend/services/routing/index.js +6 -12
- package/core/frontend/services/routing/registry.js +13 -0
- package/core/frontend/services/routing/router-manager.js +185 -0
- package/core/frontend/services/rss/generate-feed.js +2 -2
- package/core/frontend/services/theme-engine/i18n/i18n.js +267 -28
- package/core/frontend/services/theme-engine/i18n/index.js +1 -1
- package/core/frontend/services/theme-engine/i18n/theme-i18n.js +73 -0
- package/core/frontend/web/index.js +1 -0
- package/core/{server/web/site → frontend/web}/middleware/handle-image-sizes.js +4 -4
- package/core/{server/web/site → frontend/web}/middleware/index.js +0 -0
- package/core/{server/web/site → frontend/web}/middleware/redirect-ghost-to-admin.js +3 -3
- package/core/{server/web/site → frontend/web}/middleware/serve-favicon.js +6 -6
- package/core/{server/web/site → frontend/web}/middleware/serve-public-file.js +2 -2
- package/core/{server/web/site → frontend/web}/middleware/static-theme.js +3 -3
- package/core/{server/web/site → frontend/web}/routes.js +5 -4
- package/core/{server/web/site/app.js → frontend/web/site.js} +12 -16
- package/core/server/adapters/storage/LocalFileStorage.js +35 -39
- package/core/server/adapters/storage/index.js +12 -2
- package/core/server/api/canary/images.js +1 -1
- package/core/server/api/canary/offers.js +19 -0
- package/core/server/api/canary/utils/serializers/output/settings.js +2 -3
- package/core/server/api/canary/utils/serializers/output/utils/url.js +1 -1
- package/core/server/api/v2/images.js +1 -1
- package/core/server/api/v2/utils/serializers/output/utils/url.js +1 -1
- package/core/server/api/v3/images.js +1 -1
- package/core/server/api/v3/utils/serializers/output/settings.js +2 -3
- package/core/server/api/v3/utils/serializers/output/utils/url.js +1 -1
- package/core/server/data/importer/handlers/image.js +1 -1
- package/core/server/data/importer/importers/image.js +1 -1
- package/core/server/data/migrations/init/1-create-tables.js +7 -8
- package/core/server/data/migrations/init/2-create-fixtures.js +8 -8
- package/core/server/data/migrations/versions/4.20/01-remove-offer-redemptions-table.js +19 -0
- package/core/server/data/migrations/versions/4.20/02-remove-offers-table.js +30 -0
- package/core/server/data/migrations/versions/4.20/03-add-offers-table.js +21 -0
- package/core/server/data/migrations/versions/4.20/04-add-offer-redemptions-table.js +9 -0
- package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +41 -0
- package/core/server/data/schema/fixtures/utils.js +150 -143
- package/core/server/data/schema/schema.js +4 -3
- package/core/server/frontend/ghost.min.css +1 -0
- package/core/server/lib/image/image-size.js +2 -2
- package/core/server/lib/mobiledoc.js +3 -2
- package/core/server/models/action.js +7 -4
- package/core/server/models/base/plugins/overrides.js +19 -6
- package/core/server/models/index.js +4 -46
- package/core/server/models/member.js +5 -0
- package/core/server/models/user.js +2 -1
- package/core/server/overrides.js +6 -2
- package/core/server/services/adapter-manager/config.js +1 -0
- package/core/server/services/adapter-manager/index.js +9 -5
- package/core/server/services/adapter-manager/options-resolver.js +18 -0
- package/core/server/services/bulk-email/mailgun.js +1 -1
- package/core/server/services/mega/post-email-serializer.js +2 -2
- package/core/server/services/members/api.js +1 -3
- package/core/server/services/members/emails/signin.js +1 -1
- package/core/server/services/members/emails/signup.js +1 -1
- package/core/server/services/members/emails/subscribe.js +1 -1
- package/core/server/services/members/service.js +2 -1
- package/core/server/services/offers/service.js +1 -1
- package/core/server/services/route-settings/route-settings.js +1 -1
- package/core/server/services/settings/index.js +3 -1
- package/core/server/services/settings/settings-bread-service.js +42 -20
- package/core/server/services/slack.js +1 -1
- package/core/server/services/themes/activate.js +2 -2
- package/core/server/services/themes/activation-bridge.js +6 -6
- package/core/server/services/themes/storage.js +1 -1
- package/core/{frontend → server}/services/url/Queue.js +0 -0
- package/core/{frontend → server}/services/url/Resource.js +0 -0
- package/core/{frontend → server}/services/url/Resources.js +2 -2
- package/core/{frontend → server}/services/url/UrlGenerator.js +14 -14
- package/core/{frontend → server}/services/url/UrlService.js +12 -15
- package/core/{frontend → server}/services/url/Urls.js +1 -1
- package/core/{frontend → server}/services/url/configs/canary.js +0 -0
- package/core/{frontend → server}/services/url/configs/v2.js +0 -0
- package/core/{frontend → server}/services/url/configs/v3.js +0 -0
- package/core/{frontend → server}/services/url/configs/v4.js +0 -0
- package/core/{frontend → server}/services/url/index.js +0 -0
- package/core/server/services/xmlrpc.js +1 -1
- package/core/server/update-check.js +3 -3
- package/core/server/web/admin/controller.js +11 -0
- 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/app.js +8 -9
- package/core/server/web/oauth/app.js +4 -2
- package/core/server/web/parent/backend.js +3 -3
- package/core/server/web/parent/frontend.js +2 -2
- package/core/server/web/shared/middlewares/custom-redirects.js +0 -8
- package/core/server/web/shared/middlewares/maintenance.js +1 -1
- package/core/server/web/well-known.js +10 -10
- package/core/shared/config/overrides.json +1 -1
- package/core/shared/express.js +10 -0
- package/core/shared/html-to-plaintext.js +2 -2
- package/core/shared/labs.js +14 -5
- package/package.json +45 -43
- package/yarn.lock +649 -284
- package/core/built/assets/ghost-dark-da8e8eba130fb52f97494e51850d1045.css +0 -1
- package/core/built/assets/ghost.min-0d8f19623e9f077351bce453034daf4d.css +0 -1
- package/core/frontend/services/routing/bootstrap.js +0 -134
- package/core/server/public/404-ghost.png +0 -0
- package/core/server/public/404-ghost@2x.png +0 -0
- package/core/server/web/site/index.js +0 -1
- package/core/shared/i18n/i18n.js +0 -312
- package/core/shared/i18n/index.js +0 -6
|
@@ -1,73 +1,312 @@
|
|
|
1
1
|
const errors = require('@tryghost/errors');
|
|
2
|
-
const
|
|
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
|
|
13
|
+
class I18n {
|
|
5
14
|
/**
|
|
6
15
|
* @param {objec} [options]
|
|
7
|
-
* @param {string} basePath - the base path
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
this._stringMode = '
|
|
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
|
-
*
|
|
18
|
-
* - Load correct language file into memory
|
|
132
|
+
* Do the lookup within the JSON file using jsonpath
|
|
19
133
|
*
|
|
20
|
-
* @param {
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(`
|
|
274
|
+
this._logging.warn(`i18n is falling back to ${this.defaultLocale()}.json.`);
|
|
43
275
|
}
|
|
44
276
|
|
|
45
277
|
_handleMissingFileError(locale) {
|
|
46
|
-
|
|
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: `
|
|
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('
|
|
288
|
+
this._logging.warn('i18n.t() was called without a key');
|
|
59
289
|
}
|
|
60
290
|
|
|
61
|
-
_handleMissingKeyError() {
|
|
62
|
-
|
|
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: `
|
|
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 =
|
|
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('
|
|
7
|
-
const config = require('
|
|
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();
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const express = require('
|
|
2
|
-
const config = require('
|
|
3
|
-
const urlUtils = require('
|
|
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('
|
|
5
|
-
const {blogIcon} = require('../../../lib/image');
|
|
6
|
-
const storage = require('../../../adapters/storage');
|
|
7
|
-
const urlUtils = require('
|
|
8
|
-
const settingsCache = require('
|
|
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('
|
|
6
|
-
const urlUtils = require('
|
|
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('
|
|
2
|
+
const config = require('../../../shared/config');
|
|
3
3
|
const constants = require('@tryghost/constants');
|
|
4
|
-
const themeEngine = require('
|
|
5
|
-
const express = require('
|
|
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
|
-
|
|
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('
|
|
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.
|
|
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('
|
|
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('
|
|
9
|
+
const config = require('../../shared/config');
|
|
10
10
|
const constants = require('@tryghost/constants');
|
|
11
|
-
const storage = require('../../adapters/storage');
|
|
12
|
-
const urlService = require('
|
|
13
|
-
const urlUtils = require('
|
|
14
|
-
const sitemapHandler = require('
|
|
15
|
-
const appService = require('
|
|
16
|
-
const themeEngine = require('
|
|
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('
|
|
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.
|