ghost 4.15.0 → 4.17.1
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 +7 -1
- 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 +1 -1
- package/content/themes/casper/default.hbs +2 -2
- package/content/themes/casper/package.json +1 -1
- package/content/themes/casper/page.hbs +28 -26
- package/content/themes/casper/partials/post-card.hbs +2 -2
- package/content/themes/casper/post.hbs +67 -65
- package/content/themes/casper/tag.hbs +2 -2
- package/core/boot.js +7 -7
- package/core/bridge.js +4 -3
- package/core/built/assets/{chunk.3.4b1d9e20e57164ac9c29.js → chunk.3.b80d3e1e6b8556aaff3c.js} +72 -71
- package/core/built/assets/ghost-dark-f7bf2dd8d8c702716f75bfa4ccd92df2.css +1 -0
- package/core/built/assets/{ghost.min-e35cfee26d942c364166f57f3dcc9e75.js → ghost.min-52a5420ffcea6bf17761b5c59cf020e2.js} +979 -908
- package/core/built/assets/ghost.min-741246f42f000c073999a5363434ea2c.css +1 -0
- package/core/built/assets/icons/discount-bubble.svg +1 -0
- package/core/built/assets/{vendor.min-ca33abc718f21a51327841d58f8875d0.js → vendor.min-1bfc9d56d27508db88ef417deb55f16f.js} +454 -434
- package/core/frontend/apps/amp/lib/helpers/amp_analytics.js +2 -2
- package/core/frontend/apps/amp/lib/helpers/amp_components.js +2 -1
- package/core/frontend/apps/amp/lib/helpers/amp_content.js +5 -1
- package/core/frontend/apps/amp/lib/helpers/amp_style.js +1 -1
- package/core/frontend/apps/amp/lib/router.js +8 -4
- package/core/frontend/apps/private-blogging/index.js +13 -5
- package/core/frontend/apps/private-blogging/lib/helpers/input_password.js +1 -1
- package/core/frontend/apps/private-blogging/lib/middleware.js +8 -3
- package/core/frontend/helpers/asset.js +10 -2
- package/core/frontend/helpers/author.js +5 -3
- package/core/frontend/helpers/authors.js +4 -3
- package/core/frontend/helpers/body_class.js +1 -1
- package/core/frontend/helpers/cancel_link.js +9 -2
- package/core/frontend/helpers/concat.js +1 -1
- package/core/frontend/helpers/content.js +1 -1
- package/core/frontend/helpers/date.js +1 -1
- package/core/frontend/helpers/encode.js +1 -1
- package/core/frontend/helpers/excerpt.js +2 -1
- package/core/frontend/helpers/facebook_url.js +2 -1
- package/core/frontend/helpers/foreach.js +11 -2
- package/core/frontend/helpers/get.js +14 -3
- package/core/frontend/helpers/ghost_foot.js +2 -1
- package/core/frontend/helpers/ghost_head.js +10 -1
- package/core/frontend/helpers/has.js +8 -3
- package/core/frontend/helpers/img_url.js +9 -3
- package/core/frontend/helpers/is.js +7 -2
- package/core/frontend/helpers/lang.js +1 -1
- package/core/frontend/helpers/link.js +11 -2
- package/core/frontend/helpers/link_class.js +11 -2
- package/core/frontend/helpers/match.js +12 -3
- package/core/frontend/helpers/navigation.js +13 -4
- package/core/frontend/helpers/pagination.js +15 -5
- package/core/frontend/helpers/plural.js +8 -2
- package/core/frontend/helpers/post_class.js +1 -1
- package/core/frontend/helpers/prev_post.js +9 -2
- package/core/frontend/helpers/price.js +11 -6
- package/core/frontend/helpers/products.js +2 -1
- package/core/frontend/helpers/reading_time.js +4 -2
- package/core/frontend/helpers/t.js +1 -1
- package/core/frontend/helpers/tags.js +3 -1
- package/core/frontend/helpers/title.js +1 -1
- package/core/frontend/helpers/twitter_url.js +2 -1
- package/core/frontend/helpers/url.js +3 -1
- package/core/frontend/services/proxy.js +34 -57
- package/core/frontend/services/rendering.js +24 -0
- package/core/frontend/services/routing/controllers/channel.js +6 -2
- package/core/frontend/services/routing/controllers/collection.js +6 -2
- package/core/frontend/services/routing/middlewares/page-param.js +6 -2
- package/core/frontend/services/theme-engine/middleware.js +23 -6
- package/core/frontend/services/theme-engine/preview.js +31 -8
- package/core/server/adapters/scheduling/post-scheduling/scheduler-intergation.js +6 -4
- package/core/server/adapters/storage/LocalFileStorage.js +10 -4
- package/core/server/api/canary/custom-theme-settings.js +22 -0
- package/core/server/api/canary/index.js +4 -0
- package/core/server/api/canary/members.js +1 -1
- package/core/server/api/canary/redirects.js +5 -5
- package/core/server/api/canary/settings.js +16 -148
- package/core/server/api/canary/utils/serializers/output/custom-theme-settings.js +13 -0
- package/core/server/api/canary/utils/serializers/output/index.js +4 -0
- package/core/server/api/canary/utils/validators/input/settings.js +23 -1
- package/core/server/api/v2/redirects.js +3 -3
- package/core/server/api/v2/settings.js +3 -4
- package/core/server/api/v3/redirects.js +5 -5
- package/core/server/api/v3/settings.js +16 -136
- package/core/server/api/v3/utils/validators/input/settings.js +23 -1
- package/core/server/data/db/state-manager.js +1 -1
- package/core/server/data/exporter/table-lists.js +3 -1
- package/core/server/data/importer/import-manager.js +398 -0
- package/core/server/data/importer/importers/data/data-importer.js +162 -0
- package/core/server/data/importer/importers/data/index.js +1 -162
- package/core/server/data/importer/index.js +1 -379
- package/core/server/data/migrations/versions/4.16/01-add-custom-theme-settings-table.js +9 -0
- package/core/server/data/migrations/versions/4.17/01-add-custom-theme-settings-permissions.js +21 -0
- package/core/server/data/migrations/versions/4.17/02-add-offers-table.js +19 -0
- package/core/server/data/migrations/versions/4.17/03-add-offers-permissions.js +35 -0
- package/core/server/data/schema/fixtures/fixtures.json +32 -0
- package/core/server/data/schema/schema.js +33 -0
- package/core/server/models/custom-theme-setting.js +9 -0
- package/core/server/models/index.js +2 -0
- package/core/server/services/custom-theme-settings.js +8 -0
- package/core/server/services/members/api.js +4 -1
- package/core/server/services/redirects/index.js +15 -0
- package/core/{frontend → server}/services/redirects/settings.js +13 -6
- package/core/server/services/redirects/validation.js +44 -0
- package/core/{frontend/services/settings → server/services/route-settings}/default-routes.yaml +0 -0
- package/core/server/services/route-settings/default-settings-manager.js +62 -0
- package/core/server/services/route-settings/index.js +32 -1
- package/core/server/services/route-settings/route-settings.js +38 -12
- package/core/server/services/route-settings/settings-loader.js +102 -0
- package/core/{frontend/services/settings → server/services/route-settings}/validate.js +38 -28
- package/core/server/services/route-settings/yaml-parser.js +53 -0
- package/core/server/services/settings/index.js +13 -16
- package/core/server/services/settings/settings-bread-service.js +188 -0
- package/core/server/services/settings/settings-utils.js +32 -0
- package/core/server/services/themes/ThemeStorage.js +5 -4
- package/core/server/services/themes/activation-bridge.js +14 -0
- package/core/server/services/themes/validate.js +5 -2
- 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 +5 -1
- package/core/server/web/members/app.js +3 -0
- package/core/server/web/oauth/app.js +7 -8
- package/core/server/web/shared/middlewares/custom-redirects.js +82 -59
- package/core/server/web/site/routes.js +2 -2
- package/core/shared/config/defaults.json +2 -2
- package/core/shared/config/overrides.json +1 -1
- package/core/shared/custom-theme-settings-cache.js +3 -0
- package/core/shared/i18n/translations/en.json +2 -13
- package/core/shared/labs.js +2 -2
- package/package.json +42 -41
- package/yarn.lock +916 -901
- package/core/built/assets/ghost-dark-faf931d90e92535e6c03ca16793cbe7b.css +0 -1
- package/core/built/assets/ghost.min-7aa074ad556a8455155ac88ceaca03ab.css +0 -1
- package/core/frontend/services/redirects/index.js +0 -9
- package/core/frontend/services/redirects/validation.js +0 -28
- package/core/frontend/services/settings/ensure-settings.js +0 -47
- package/core/frontend/services/settings/index.js +0 -104
- package/core/frontend/services/settings/loader.js +0 -89
- package/core/frontend/services/settings/yaml-parser.js +0 -31
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
// ### Navigation Helper
|
|
2
2
|
// `{{navigation}}`
|
|
3
3
|
// Outputs navigation menu of static urls
|
|
4
|
+
const {SafeString, templates, hbs} = require('../services/rendering');
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
+
const errors = require('@tryghost/errors');
|
|
7
|
+
const tpl = require('@tryghost/tpl');
|
|
6
8
|
const {slugify} = require('@tryghost/string');
|
|
7
9
|
const _ = require('lodash');
|
|
10
|
+
|
|
11
|
+
const messages = {
|
|
12
|
+
invalidData: 'navigation data is not an object or is a function',
|
|
13
|
+
valuesMustBeDefined: 'All values must be defined for label, url and current',
|
|
14
|
+
valuesMustBeString: 'Invalid value, Url and Label must be strings'
|
|
15
|
+
};
|
|
16
|
+
|
|
8
17
|
const createFrame = hbs.handlebars.createFrame;
|
|
9
18
|
|
|
10
19
|
module.exports = function navigation(options) {
|
|
@@ -25,7 +34,7 @@ module.exports = function navigation(options) {
|
|
|
25
34
|
|
|
26
35
|
if (!_.isObject(navigationData) || _.isFunction(navigationData)) {
|
|
27
36
|
throw new errors.IncorrectUsageError({
|
|
28
|
-
message:
|
|
37
|
+
message: tpl(messages.invalidData)
|
|
29
38
|
});
|
|
30
39
|
}
|
|
31
40
|
|
|
@@ -33,7 +42,7 @@ module.exports = function navigation(options) {
|
|
|
33
42
|
return (_.isUndefined(e.label) || _.isUndefined(e.url));
|
|
34
43
|
}).length > 0) {
|
|
35
44
|
throw new errors.IncorrectUsageError({
|
|
36
|
-
message:
|
|
45
|
+
message: tpl(messages.valuesMustBeDefined)
|
|
37
46
|
});
|
|
38
47
|
}
|
|
39
48
|
|
|
@@ -43,7 +52,7 @@ module.exports = function navigation(options) {
|
|
|
43
52
|
(!_.isNull(e.url) && !_.isString(e.url)));
|
|
44
53
|
}).length > 0) {
|
|
45
54
|
throw new errors.IncorrectUsageError({
|
|
46
|
-
message:
|
|
55
|
+
message: tpl(messages.valuesMustBeString)
|
|
47
56
|
});
|
|
48
57
|
}
|
|
49
58
|
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
// ### Pagination Helper
|
|
2
2
|
// `{{pagination}}`
|
|
3
3
|
// Outputs previous and next buttons, along with info about the current page
|
|
4
|
+
const {templates, hbs} = require('../services/rendering');
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
+
const errors = require('@tryghost/errors');
|
|
7
|
+
const tpl = require('@tryghost/tpl');
|
|
6
8
|
const _ = require('lodash');
|
|
9
|
+
|
|
10
|
+
const messages = {
|
|
11
|
+
invalidData: 'The {{pagination}} helper was used outside of a paginated context. See https://ghost.org/docs/themes/helpers/pagination/.',
|
|
12
|
+
valuesMustBeDefined: 'All values must be defined for page, pages, limit and total',
|
|
13
|
+
nextPrevValuesMustBeNumeric: 'Invalid value, Next/Prev must be a number',
|
|
14
|
+
valuesMustBeNumeric: 'Invalid value, check page, pages, limit and total are numbers'
|
|
15
|
+
};
|
|
16
|
+
|
|
7
17
|
const createFrame = hbs.handlebars.createFrame;
|
|
8
18
|
|
|
9
19
|
module.exports = function pagination(options) {
|
|
@@ -14,7 +24,7 @@ module.exports = function pagination(options) {
|
|
|
14
24
|
if (!_.isObject(this.pagination) || _.isFunction(this.pagination)) {
|
|
15
25
|
throw new errors.IncorrectUsageError({
|
|
16
26
|
level: 'normal',
|
|
17
|
-
message:
|
|
27
|
+
message: tpl(messages.invalidData),
|
|
18
28
|
help: 'https://ghost.org/docs/themes/helpers/pagination/'
|
|
19
29
|
});
|
|
20
30
|
}
|
|
@@ -22,20 +32,20 @@ module.exports = function pagination(options) {
|
|
|
22
32
|
if (_.isUndefined(this.pagination.page) || _.isUndefined(this.pagination.pages) ||
|
|
23
33
|
_.isUndefined(this.pagination.total) || _.isUndefined(this.pagination.limit)) {
|
|
24
34
|
throw new errors.IncorrectUsageError({
|
|
25
|
-
message:
|
|
35
|
+
message: tpl(messages.valuesMustBeDefined)
|
|
26
36
|
});
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
if ((!_.isNull(this.pagination.next) && !_.isNumber(this.pagination.next)) ||
|
|
30
40
|
(!_.isNull(this.pagination.prev) && !_.isNumber(this.pagination.prev))) {
|
|
31
41
|
throw new errors.IncorrectUsageError({
|
|
32
|
-
message:
|
|
42
|
+
message: tpl(messages.nextPrevValuesMustBeNumeric)
|
|
33
43
|
});
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
if (!_.isNumber(this.pagination.page) || !_.isNumber(this.pagination.pages) ||
|
|
37
47
|
!_.isNumber(this.pagination.total) || !_.isNumber(this.pagination.limit)) {
|
|
38
|
-
throw new errors.IncorrectUsageError({message:
|
|
48
|
+
throw new errors.IncorrectUsageError({message: tpl(messages.valuesMustBeNumeric)});
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
// CASE: The pagination helper should have access to the pagination properties at the top level.
|
|
@@ -9,15 +9,21 @@
|
|
|
9
9
|
// The 2nd argument is the string that will be output if the variable's value is 0
|
|
10
10
|
// The 3rd argument is the string that will be output if the variable's value is 1
|
|
11
11
|
// The 4th argument is the string that will be output if the variable's value is 2+
|
|
12
|
+
const {SafeString} = require('../services/rendering');
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
+
const errors = require('@tryghost/errors');
|
|
15
|
+
const tpl = require('@tryghost/tpl');
|
|
14
16
|
const isUndefined = require('lodash/isUndefined');
|
|
15
17
|
|
|
18
|
+
const messages = {
|
|
19
|
+
valuesMustBeDefined: 'All values must be defined for empty, singular and plural'
|
|
20
|
+
};
|
|
21
|
+
|
|
16
22
|
module.exports = function plural(number, options) {
|
|
17
23
|
if (isUndefined(options.hash) || isUndefined(options.hash.empty) ||
|
|
18
24
|
isUndefined(options.hash.singular) || isUndefined(options.hash.plural)) {
|
|
19
25
|
throw new errors.IncorrectUsageError({
|
|
20
|
-
message:
|
|
26
|
+
message: tpl(messages.valuesMustBeDefined)
|
|
21
27
|
});
|
|
22
28
|
}
|
|
23
29
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Usage: `{{post_class}}`
|
|
3
3
|
//
|
|
4
4
|
// Output classes for the body element
|
|
5
|
-
const {SafeString} = require('../services/
|
|
5
|
+
const {SafeString} = require('../services/rendering');
|
|
6
6
|
|
|
7
7
|
// We use the name post_class to match the helper for consistency:
|
|
8
8
|
module.exports = function post_class() { // eslint-disable-line camelcase
|
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
// Example usages
|
|
3
3
|
// `{{#prev_post}}<a href ="{{url}}>previous post</a>{{/prev_post}}'
|
|
4
4
|
// `{{#next_post}}<a href ="{{url absolute="true">next post</a>{{/next_post}}'
|
|
5
|
+
const {api, checks} = require('../services/proxy');
|
|
6
|
+
const {hbs} = require('../services/rendering');
|
|
5
7
|
|
|
6
|
-
const
|
|
8
|
+
const logging = require('@tryghost/logging');
|
|
9
|
+
const tpl = require('@tryghost/tpl');
|
|
7
10
|
const get = require('lodash/get');
|
|
8
11
|
const Promise = require('bluebird');
|
|
9
12
|
const moment = require('moment');
|
|
10
13
|
|
|
14
|
+
const messages = {
|
|
15
|
+
mustBeCalledAsBlock: 'The {{{helperName}}} helper must be called as a block. E.g. {{#{helperName}}}...{{/{helperName}}}'
|
|
16
|
+
};
|
|
17
|
+
|
|
11
18
|
const createFrame = hbs.handlebars.createFrame;
|
|
12
19
|
|
|
13
20
|
const buildApiOptions = function buildApiOptions(options, post) {
|
|
@@ -78,7 +85,7 @@ module.exports = function prevNext(options) {
|
|
|
78
85
|
|
|
79
86
|
// Guard against incorrect usage of the helpers
|
|
80
87
|
if (!options.fn || !options.inverse) {
|
|
81
|
-
data.error =
|
|
88
|
+
data.error = tpl(messages.mustBeCalledAsBlock, {helperName: options.name});
|
|
82
89
|
logging.warn(data.error);
|
|
83
90
|
return Promise.resolve();
|
|
84
91
|
}
|
|
@@ -10,10 +10,15 @@
|
|
|
10
10
|
//
|
|
11
11
|
// Returns amount equal to the dominant denomintation of the currency.
|
|
12
12
|
// For example, if 2100 is passed, it will return 21.
|
|
13
|
-
const
|
|
14
|
-
const
|
|
13
|
+
const errors = require('@tryghost/errors');
|
|
14
|
+
const tpl = require('@tryghost/tpl');
|
|
15
15
|
const _ = require('lodash');
|
|
16
16
|
|
|
17
|
+
const messages = {
|
|
18
|
+
attrIsRequired: 'Attribute is required e.g. {{price plan.amount}}',
|
|
19
|
+
attrMustBeNumeric: 'Attribute value should be a number'
|
|
20
|
+
};
|
|
21
|
+
|
|
17
22
|
function formatter({amount, currency, numberFormat = 'short', currencyFormat = 'symbol', locale}) {
|
|
18
23
|
const formatterOptions = {
|
|
19
24
|
style: 'currency',
|
|
@@ -77,20 +82,20 @@ module.exports = function price(planOrAmount, options) {
|
|
|
77
82
|
// CASE: if no amount is passed, e.g. `{{price}}` we throw an error
|
|
78
83
|
if (arguments.length < 2) {
|
|
79
84
|
throw new errors.IncorrectUsageError({
|
|
80
|
-
message:
|
|
85
|
+
message: tpl(messages.attrIsRequired)
|
|
81
86
|
});
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
// CASE: if amount is passed, but it is undefined we throw an error
|
|
85
90
|
if (amount === undefined) {
|
|
86
91
|
throw new errors.IncorrectUsageError({
|
|
87
|
-
message:
|
|
92
|
+
message: tpl(messages.attrIsRequired)
|
|
88
93
|
});
|
|
89
94
|
}
|
|
90
95
|
|
|
91
|
-
if (!isNumber(amount)) {
|
|
96
|
+
if (!_.isNumber(amount)) {
|
|
92
97
|
throw new errors.IncorrectUsageError({
|
|
93
|
-
message:
|
|
98
|
+
message: tpl(messages.attrMustBeNumeric)
|
|
94
99
|
});
|
|
95
100
|
}
|
|
96
101
|
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Returns a string of the products with access to the post.
|
|
5
5
|
// By default, products are separated by commas.
|
|
6
|
+
const {labs} = require('../services/proxy');
|
|
7
|
+
const {SafeString} = require('../services/rendering');
|
|
6
8
|
|
|
7
9
|
const nql = require('@nexes/nql');
|
|
8
10
|
const isString = require('lodash/isString');
|
|
9
|
-
const {SafeString, labs} = require('../services/proxy');
|
|
10
11
|
|
|
11
12
|
function products(options = {}) {
|
|
12
13
|
options = options || {};
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
//
|
|
11
11
|
// Returns estimated reading time for post
|
|
12
12
|
|
|
13
|
-
const {
|
|
14
|
-
const
|
|
13
|
+
const {checks} = require('../services/proxy');
|
|
14
|
+
const {SafeString} = require('../services/rendering');
|
|
15
|
+
|
|
16
|
+
const {readingTime: calculateReadingTime} = require('@tryghost/helpers');
|
|
15
17
|
|
|
16
18
|
module.exports = function reading_time(options) {// eslint-disable-line camelcase
|
|
17
19
|
options = options || {};
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// because often other helpers need that (t) returns a string to be able to work as subexpression; e.g.:
|
|
11
11
|
// {{tags prefix=(t " on ")}}
|
|
12
12
|
|
|
13
|
-
const {themeI18n} = require('../services/
|
|
13
|
+
const {themeI18n} = require('../services/rendering');
|
|
14
14
|
|
|
15
15
|
module.exports = function t(text, options) {
|
|
16
16
|
const bindings = {};
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
// By default, tags are separated by commas.
|
|
6
6
|
//
|
|
7
7
|
// Note that the standard {{#each tags}} implementation is unaffected by this helper
|
|
8
|
-
const {urlService
|
|
8
|
+
const {urlService} = require('../services/proxy');
|
|
9
|
+
const {SafeString, escapeExpression, templates} = require('../services/rendering');
|
|
10
|
+
|
|
9
11
|
const isString = require('lodash/isString');
|
|
10
12
|
const ghostHelperUtils = require('@tryghost/helpers').utils;
|
|
11
13
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Overrides the standard behaviour of `{[title}}` to ensure the content is correctly escaped
|
|
5
5
|
|
|
6
|
-
const {SafeString, escapeExpression} = require('../services/
|
|
6
|
+
const {SafeString, escapeExpression} = require('../services/rendering');
|
|
7
7
|
|
|
8
8
|
module.exports = function title() {
|
|
9
9
|
return new SafeString(escapeExpression(this.title || ''));
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// Usage: `{{twitter_url}}` or `{{twitter_url author.twitter}}`
|
|
3
3
|
//
|
|
4
4
|
// Output a url for a twitter username
|
|
5
|
-
const {socialUrls
|
|
5
|
+
const {socialUrls} = require('../services/proxy');
|
|
6
|
+
const {localUtils} = require('../services/rendering');
|
|
6
7
|
|
|
7
8
|
// We use the name twitter_url to match the helper for consistency:
|
|
8
9
|
module.exports = function twitter_url(username, options) { // eslint-disable-line camelcase
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
// Returns the URL for the current object scope i.e. If inside a post scope will return post permalink
|
|
5
5
|
// `absolute` flag outputs absolute URL, else URL is relative
|
|
6
6
|
|
|
7
|
-
const {
|
|
7
|
+
const {metaData} = require('../services/proxy');
|
|
8
|
+
const {SafeString} = require('../services/rendering');
|
|
9
|
+
|
|
8
10
|
const {getMetaDataUrl} = metaData;
|
|
9
11
|
|
|
10
12
|
module.exports = function url(options) {
|
|
@@ -1,74 +1,51 @@
|
|
|
1
|
-
// This file
|
|
2
|
-
// With the exception of modules like lodash, Bluebird
|
|
3
|
-
// We can later refactor to enforce this something like we did in apps
|
|
4
|
-
const hbs = require('./theme-engine/engine');
|
|
5
|
-
const errors = require('@tryghost/errors');
|
|
6
|
-
|
|
7
|
-
const i18n = require('../../shared/i18n');
|
|
8
|
-
const logging = require('@tryghost/logging');
|
|
1
|
+
// This file contains everything that the helpers and frontend apps require from the core of Ghost
|
|
9
2
|
const settingsCache = require('../../shared/settings-cache');
|
|
10
3
|
const config = require('../../shared/config');
|
|
11
4
|
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
// - bluebird
|
|
15
|
-
// - downsize
|
|
16
|
-
// - moment-timezone
|
|
17
|
-
// - jsonpath
|
|
5
|
+
// Require from the rendering framework
|
|
6
|
+
const {SafeString} = require('./rendering');
|
|
18
7
|
|
|
19
8
|
module.exports = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// This is used to detect if "isPost" is true in prevNext.
|
|
9
|
+
/**
|
|
10
|
+
* Section two: data manipulation
|
|
11
|
+
* Stuff that modifies API data (SDK layer)
|
|
12
|
+
*/
|
|
13
|
+
metaData: require('../meta'),
|
|
14
|
+
socialUrls: require('@tryghost/social-urls'),
|
|
15
|
+
blogIcon: require('../../server/lib/image').blogIcon,
|
|
16
|
+
// Used by router service and {{get}} helper to prepare data for optimal usage in themes
|
|
17
|
+
prepareContextResource(data) {
|
|
18
|
+
(Array.isArray(data) ? data : [data]).forEach((resource) => {
|
|
19
|
+
// feature_image_caption contains HTML, making it a SafeString spares theme devs from triple-curlies
|
|
20
|
+
if (resource.feature_image_caption) {
|
|
21
|
+
resource.feature_image_caption = new SafeString(resource.feature_image_caption);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
// This is used to decide e.g. if a JSON object is a Post, Page, Tag etc
|
|
39
26
|
checks: require('../../server/data/schema').checks,
|
|
40
27
|
|
|
41
|
-
|
|
42
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Section three: Core API
|
|
30
|
+
* Parts of Ghost core that the frontend currently needs
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
// Config! Keys used:
|
|
43
34
|
// isPrivacyDisabled & referrerPolicy used in ghost_head
|
|
44
35
|
config: {
|
|
45
36
|
get: config.get.bind(config),
|
|
46
37
|
isPrivacyDisabled: config.isPrivacyDisabled.bind(config)
|
|
47
38
|
},
|
|
48
39
|
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// Things required from data/meta
|
|
53
|
-
metaData: require('../meta'),
|
|
40
|
+
// TODO: Only expose "get"
|
|
41
|
+
settingsCache: settingsCache,
|
|
54
42
|
|
|
55
|
-
//
|
|
56
|
-
|
|
43
|
+
// TODO: Expose less of the API to make this safe
|
|
44
|
+
api: require('../../server/api'),
|
|
57
45
|
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
// Labs utils for enabling/disabling helpers
|
|
47
|
+
labs: require('../../shared/labs'),
|
|
48
|
+
// URGH... Yuk (unhelpful comment :D)
|
|
61
49
|
urlService: require('./url'),
|
|
62
|
-
urlUtils: require('../../shared/url-utils')
|
|
63
|
-
localUtils: require('./theme-engine/handlebars/utils'),
|
|
64
|
-
|
|
65
|
-
// Used by router service and {{get}} helper to prepare data for optimal usage in themes
|
|
66
|
-
prepareContextResource(data) {
|
|
67
|
-
(Array.isArray(data) ? data : [data]).forEach((resource) => {
|
|
68
|
-
// feature_image_caption contains HTML, making it a SafeString spares theme devs from triple-curlies
|
|
69
|
-
if (resource.feature_image_caption) {
|
|
70
|
-
resource.feature_image_caption = new hbs.SafeString(resource.feature_image_caption);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
}
|
|
50
|
+
urlUtils: require('../../shared/url-utils')
|
|
74
51
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is a loose concept of a frontend rendering framework
|
|
3
|
+
* Note: everything here gets deep-required from the theme-engine
|
|
4
|
+
* This indicates that the theme engine is a set of services, rather than a single service
|
|
5
|
+
* and could do with a refactor.
|
|
6
|
+
*
|
|
7
|
+
* This at least keeps the deep requires in a single place.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const hbs = require('./theme-engine/engine');
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
hbs: hbs,
|
|
14
|
+
SafeString: hbs.SafeString,
|
|
15
|
+
escapeExpression: hbs.escapeExpression,
|
|
16
|
+
// The local template thing, should this be merged with the channels one?
|
|
17
|
+
templates: require('./theme-engine/handlebars/template'),
|
|
18
|
+
|
|
19
|
+
// Theme i18n is separate to common i18n
|
|
20
|
+
themeI18n: require('./theme-engine/i18n'),
|
|
21
|
+
|
|
22
|
+
// TODO: these need a more sensible home
|
|
23
|
+
localUtils: require('./theme-engine/handlebars/utils')
|
|
24
|
+
};
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const debug = require('@tryghost/debug')('services:routing:controllers:channel');
|
|
3
|
-
const
|
|
3
|
+
const tpl = require('@tryghost/tpl');
|
|
4
4
|
const errors = require('@tryghost/errors');
|
|
5
5
|
const security = require('@tryghost/security');
|
|
6
6
|
const themeEngine = require('../../theme-engine');
|
|
7
7
|
const helpers = require('../helpers');
|
|
8
8
|
|
|
9
|
+
const messages = {
|
|
10
|
+
pageNotFound: 'Page not found.'
|
|
11
|
+
};
|
|
12
|
+
|
|
9
13
|
/**
|
|
10
14
|
* @description Channel controller.
|
|
11
15
|
*
|
|
@@ -51,7 +55,7 @@ module.exports = function channelController(req, res, next) {
|
|
|
51
55
|
// CASE: requested page is greater than number of pages we have
|
|
52
56
|
if (pathOptions.page > result.meta.pagination.pages) {
|
|
53
57
|
return next(new errors.NotFoundError({
|
|
54
|
-
message:
|
|
58
|
+
message: tpl(messages.pageNotFound)
|
|
55
59
|
}));
|
|
56
60
|
}
|
|
57
61
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const debug = require('@tryghost/debug')('services:routing:controllers:collection');
|
|
3
|
-
const
|
|
3
|
+
const tpl = require('@tryghost/tpl');
|
|
4
4
|
const errors = require('@tryghost/errors');
|
|
5
5
|
const security = require('@tryghost/security');
|
|
6
6
|
const urlService = require('../../url');
|
|
7
7
|
const themeEngine = require('../../theme-engine');
|
|
8
8
|
const helpers = require('../helpers');
|
|
9
9
|
|
|
10
|
+
const messages = {
|
|
11
|
+
pageNotFound: 'Page not found.'
|
|
12
|
+
};
|
|
13
|
+
|
|
10
14
|
/**
|
|
11
15
|
* @description Collection controller.
|
|
12
16
|
* @param {Object} req
|
|
@@ -50,7 +54,7 @@ module.exports = function collectionController(req, res, next) {
|
|
|
50
54
|
// CASE: requested page is greater than number of pages we have
|
|
51
55
|
if (pathOptions.page > result.meta.pagination.pages) {
|
|
52
56
|
return next(new errors.NotFoundError({
|
|
53
|
-
message:
|
|
57
|
+
message: tpl(messages.pageNotFound)
|
|
54
58
|
}));
|
|
55
59
|
}
|
|
56
60
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
const
|
|
1
|
+
const tpl = require('@tryghost/tpl');
|
|
2
2
|
const errors = require('@tryghost/errors');
|
|
3
3
|
const urlUtils = require('../../../../shared/url-utils');
|
|
4
4
|
|
|
5
|
+
const messages = {
|
|
6
|
+
pageNotFound: 'Page not found.'
|
|
7
|
+
};
|
|
8
|
+
|
|
5
9
|
/**
|
|
6
10
|
* @description Middleware, which validates and interprets the page param e.g. /page/1
|
|
7
11
|
* @param {Object} req
|
|
@@ -21,7 +25,7 @@ module.exports = function handlePageParam(req, res, next, page) {
|
|
|
21
25
|
return urlUtils.redirect301(res, req.originalUrl.replace(pageRegex, '/'));
|
|
22
26
|
} else if (page < 1 || isNaN(page)) {
|
|
23
27
|
return next(new errors.NotFoundError({
|
|
24
|
-
message:
|
|
28
|
+
message: tpl(messages.pageNotFound)
|
|
25
29
|
}));
|
|
26
30
|
} else {
|
|
27
31
|
req.params.page = page;
|
|
@@ -5,6 +5,7 @@ const {api} = require('../proxy');
|
|
|
5
5
|
const errors = require('@tryghost/errors');
|
|
6
6
|
const tpl = require('@tryghost/tpl');
|
|
7
7
|
const settingsCache = require('../../../shared/settings-cache');
|
|
8
|
+
const customThemeSettingsCache = require('../../../shared/custom-theme-settings-cache');
|
|
8
9
|
const labs = require('../../../shared/labs');
|
|
9
10
|
const activeTheme = require('./active');
|
|
10
11
|
const preview = require('./preview');
|
|
@@ -86,12 +87,9 @@ async function getProductAndPricesData() {
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
function getSiteData(
|
|
90
|
+
function getSiteData() {
|
|
90
91
|
let siteData = settingsCache.getPublic();
|
|
91
92
|
|
|
92
|
-
// @TODO: it would be nicer if this was proper middleware somehow...
|
|
93
|
-
siteData = preview.handle(req, siteData);
|
|
94
|
-
|
|
95
93
|
// theme-only computed property added to @site
|
|
96
94
|
if (settingsCache.get('members_signup_access') === 'none') {
|
|
97
95
|
const escapedUrl = encodeURIComponent(urlUtils.urlFor({relativeUrl: '/rss/'}, true));
|
|
@@ -107,13 +105,14 @@ async function updateGlobalTemplateOptions(req, res, next) {
|
|
|
107
105
|
// Static information, same for every request unless the settings change
|
|
108
106
|
// @TODO: bind this once and then update based on events?
|
|
109
107
|
// @TODO: decouple theme layer from settings cache using the Content API
|
|
110
|
-
const siteData = getSiteData(
|
|
108
|
+
const siteData = getSiteData();
|
|
111
109
|
const labsData = labs.getAll();
|
|
112
110
|
|
|
113
111
|
const themeData = {
|
|
114
112
|
posts_per_page: activeTheme.get().config('posts_per_page'),
|
|
115
113
|
image_sizes: activeTheme.get().config('image_sizes')
|
|
116
114
|
};
|
|
115
|
+
const themeSettingsData = customThemeSettingsCache.getAll();
|
|
117
116
|
const productData = await getProductAndPricesData();
|
|
118
117
|
const priceData = calculateLegacyPriceData(productData);
|
|
119
118
|
|
|
@@ -136,7 +135,8 @@ async function updateGlobalTemplateOptions(req, res, next) {
|
|
|
136
135
|
config: themeData,
|
|
137
136
|
price: priceData,
|
|
138
137
|
product,
|
|
139
|
-
products
|
|
138
|
+
products,
|
|
139
|
+
custom: themeSettingsData
|
|
140
140
|
}
|
|
141
141
|
});
|
|
142
142
|
}
|
|
@@ -154,10 +154,26 @@ function updateLocalTemplateData(req, res, next) {
|
|
|
154
154
|
|
|
155
155
|
function updateLocalTemplateOptions(req, res, next) {
|
|
156
156
|
const localTemplateOptions = hbs.getLocalTemplateOptions(res.locals);
|
|
157
|
+
|
|
158
|
+
// adjust @site.url for http/https based on the incoming request
|
|
157
159
|
const siteData = {
|
|
158
160
|
url: urlUtils.urlFor('home', {secure: req.secure, trailingSlash: false}, true)
|
|
159
161
|
};
|
|
160
162
|
|
|
163
|
+
// @TODO: it would be nicer if this was proper middleware somehow...
|
|
164
|
+
const previewData = preview.handle(req, Object.keys(customThemeSettingsCache.getAll()));
|
|
165
|
+
|
|
166
|
+
// strip custom off of preview data so it doesn't get merged into @site
|
|
167
|
+
const customThemeSettingsPreviewData = previewData.custom;
|
|
168
|
+
delete previewData.custom;
|
|
169
|
+
let customData = {};
|
|
170
|
+
if (labs.isSet('customThemeSettings')) {
|
|
171
|
+
customData = customThemeSettingsPreviewData;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// update site data with any preview values from the request
|
|
175
|
+
Object.assign(siteData, previewData);
|
|
176
|
+
|
|
161
177
|
const member = req.member ? {
|
|
162
178
|
uuid: req.member.uuid,
|
|
163
179
|
email: req.member.email,
|
|
@@ -176,6 +192,7 @@ function updateLocalTemplateOptions(req, res, next) {
|
|
|
176
192
|
data: {
|
|
177
193
|
member: member,
|
|
178
194
|
site: siteData,
|
|
195
|
+
custom: customData,
|
|
179
196
|
// @deprecated: a gscan warning for @blog was added before 3.0 which replaced it with @site
|
|
180
197
|
blog: siteData
|
|
181
198
|
}
|
|
@@ -14,34 +14,57 @@ function decodeValue(value) {
|
|
|
14
14
|
return value;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function getPreviewData(previewHeader,
|
|
17
|
+
function getPreviewData(previewHeader, customThemeSettingKeys = []) {
|
|
18
18
|
// Keep the string shorter with short codes for certain parameters
|
|
19
19
|
const supportedSettings = {
|
|
20
20
|
c: 'accent_color',
|
|
21
21
|
icon: 'icon',
|
|
22
22
|
logo: 'logo',
|
|
23
|
-
cover: 'cover_image'
|
|
23
|
+
cover: 'cover_image',
|
|
24
|
+
custom: 'custom',
|
|
25
|
+
d: 'description'
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
let opts = new URLSearchParams(previewHeader);
|
|
27
29
|
|
|
30
|
+
const previewData = {};
|
|
31
|
+
|
|
28
32
|
opts.forEach((value, key) => {
|
|
29
33
|
value = decodeValue(value);
|
|
30
34
|
if (supportedSettings[key]) {
|
|
31
|
-
_.set(
|
|
35
|
+
_.set(previewData, supportedSettings[key], value);
|
|
32
36
|
}
|
|
33
37
|
});
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
if (previewData.custom) {
|
|
40
|
+
try {
|
|
41
|
+
const custom = {};
|
|
42
|
+
const previewCustom = JSON.parse(previewData.custom);
|
|
43
|
+
|
|
44
|
+
if (typeof previewCustom === 'object') {
|
|
45
|
+
customThemeSettingKeys.forEach((key) => {
|
|
46
|
+
custom[key] = previewCustom[key];
|
|
47
|
+
});
|
|
48
|
+
}
|
|
36
49
|
|
|
37
|
-
|
|
50
|
+
previewData.custom = custom;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
previewData.custom = {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
previewData._preview = previewHeader;
|
|
57
|
+
|
|
58
|
+
return previewData;
|
|
38
59
|
}
|
|
39
60
|
|
|
40
61
|
module.exports._PREVIEW_HEADER_NAME = PREVIEW_HEADER_NAME;
|
|
41
|
-
module.exports.handle = (req,
|
|
62
|
+
module.exports.handle = (req, customThemeSettingKeys) => {
|
|
63
|
+
let previewData = {};
|
|
64
|
+
|
|
42
65
|
if (req && req.header(PREVIEW_HEADER_NAME)) {
|
|
43
|
-
|
|
66
|
+
previewData = getPreviewData(req.header(PREVIEW_HEADER_NAME), customThemeSettingKeys);
|
|
44
67
|
}
|
|
45
68
|
|
|
46
|
-
return
|
|
69
|
+
return previewData;
|
|
47
70
|
};
|