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.
Files changed (137) hide show
  1. package/.eslintrc.js +7 -1
  2. package/content/themes/casper/assets/built/screen.css +1 -1
  3. package/content/themes/casper/assets/built/screen.css.map +1 -1
  4. package/content/themes/casper/assets/css/screen.css +1 -1
  5. package/content/themes/casper/default.hbs +2 -2
  6. package/content/themes/casper/package.json +1 -1
  7. package/content/themes/casper/page.hbs +28 -26
  8. package/content/themes/casper/partials/post-card.hbs +2 -2
  9. package/content/themes/casper/post.hbs +67 -65
  10. package/content/themes/casper/tag.hbs +2 -2
  11. package/core/boot.js +7 -7
  12. package/core/bridge.js +4 -3
  13. package/core/built/assets/{chunk.3.4b1d9e20e57164ac9c29.js → chunk.3.b80d3e1e6b8556aaff3c.js} +72 -71
  14. package/core/built/assets/ghost-dark-f7bf2dd8d8c702716f75bfa4ccd92df2.css +1 -0
  15. package/core/built/assets/{ghost.min-e35cfee26d942c364166f57f3dcc9e75.js → ghost.min-52a5420ffcea6bf17761b5c59cf020e2.js} +979 -908
  16. package/core/built/assets/ghost.min-741246f42f000c073999a5363434ea2c.css +1 -0
  17. package/core/built/assets/icons/discount-bubble.svg +1 -0
  18. package/core/built/assets/{vendor.min-ca33abc718f21a51327841d58f8875d0.js → vendor.min-1bfc9d56d27508db88ef417deb55f16f.js} +454 -434
  19. package/core/frontend/apps/amp/lib/helpers/amp_analytics.js +2 -2
  20. package/core/frontend/apps/amp/lib/helpers/amp_components.js +2 -1
  21. package/core/frontend/apps/amp/lib/helpers/amp_content.js +5 -1
  22. package/core/frontend/apps/amp/lib/helpers/amp_style.js +1 -1
  23. package/core/frontend/apps/amp/lib/router.js +8 -4
  24. package/core/frontend/apps/private-blogging/index.js +13 -5
  25. package/core/frontend/apps/private-blogging/lib/helpers/input_password.js +1 -1
  26. package/core/frontend/apps/private-blogging/lib/middleware.js +8 -3
  27. package/core/frontend/helpers/asset.js +10 -2
  28. package/core/frontend/helpers/author.js +5 -3
  29. package/core/frontend/helpers/authors.js +4 -3
  30. package/core/frontend/helpers/body_class.js +1 -1
  31. package/core/frontend/helpers/cancel_link.js +9 -2
  32. package/core/frontend/helpers/concat.js +1 -1
  33. package/core/frontend/helpers/content.js +1 -1
  34. package/core/frontend/helpers/date.js +1 -1
  35. package/core/frontend/helpers/encode.js +1 -1
  36. package/core/frontend/helpers/excerpt.js +2 -1
  37. package/core/frontend/helpers/facebook_url.js +2 -1
  38. package/core/frontend/helpers/foreach.js +11 -2
  39. package/core/frontend/helpers/get.js +14 -3
  40. package/core/frontend/helpers/ghost_foot.js +2 -1
  41. package/core/frontend/helpers/ghost_head.js +10 -1
  42. package/core/frontend/helpers/has.js +8 -3
  43. package/core/frontend/helpers/img_url.js +9 -3
  44. package/core/frontend/helpers/is.js +7 -2
  45. package/core/frontend/helpers/lang.js +1 -1
  46. package/core/frontend/helpers/link.js +11 -2
  47. package/core/frontend/helpers/link_class.js +11 -2
  48. package/core/frontend/helpers/match.js +12 -3
  49. package/core/frontend/helpers/navigation.js +13 -4
  50. package/core/frontend/helpers/pagination.js +15 -5
  51. package/core/frontend/helpers/plural.js +8 -2
  52. package/core/frontend/helpers/post_class.js +1 -1
  53. package/core/frontend/helpers/prev_post.js +9 -2
  54. package/core/frontend/helpers/price.js +11 -6
  55. package/core/frontend/helpers/products.js +2 -1
  56. package/core/frontend/helpers/reading_time.js +4 -2
  57. package/core/frontend/helpers/t.js +1 -1
  58. package/core/frontend/helpers/tags.js +3 -1
  59. package/core/frontend/helpers/title.js +1 -1
  60. package/core/frontend/helpers/twitter_url.js +2 -1
  61. package/core/frontend/helpers/url.js +3 -1
  62. package/core/frontend/services/proxy.js +34 -57
  63. package/core/frontend/services/rendering.js +24 -0
  64. package/core/frontend/services/routing/controllers/channel.js +6 -2
  65. package/core/frontend/services/routing/controllers/collection.js +6 -2
  66. package/core/frontend/services/routing/middlewares/page-param.js +6 -2
  67. package/core/frontend/services/theme-engine/middleware.js +23 -6
  68. package/core/frontend/services/theme-engine/preview.js +31 -8
  69. package/core/server/adapters/scheduling/post-scheduling/scheduler-intergation.js +6 -4
  70. package/core/server/adapters/storage/LocalFileStorage.js +10 -4
  71. package/core/server/api/canary/custom-theme-settings.js +22 -0
  72. package/core/server/api/canary/index.js +4 -0
  73. package/core/server/api/canary/members.js +1 -1
  74. package/core/server/api/canary/redirects.js +5 -5
  75. package/core/server/api/canary/settings.js +16 -148
  76. package/core/server/api/canary/utils/serializers/output/custom-theme-settings.js +13 -0
  77. package/core/server/api/canary/utils/serializers/output/index.js +4 -0
  78. package/core/server/api/canary/utils/validators/input/settings.js +23 -1
  79. package/core/server/api/v2/redirects.js +3 -3
  80. package/core/server/api/v2/settings.js +3 -4
  81. package/core/server/api/v3/redirects.js +5 -5
  82. package/core/server/api/v3/settings.js +16 -136
  83. package/core/server/api/v3/utils/validators/input/settings.js +23 -1
  84. package/core/server/data/db/state-manager.js +1 -1
  85. package/core/server/data/exporter/table-lists.js +3 -1
  86. package/core/server/data/importer/import-manager.js +398 -0
  87. package/core/server/data/importer/importers/data/data-importer.js +162 -0
  88. package/core/server/data/importer/importers/data/index.js +1 -162
  89. package/core/server/data/importer/index.js +1 -379
  90. package/core/server/data/migrations/versions/4.16/01-add-custom-theme-settings-table.js +9 -0
  91. package/core/server/data/migrations/versions/4.17/01-add-custom-theme-settings-permissions.js +21 -0
  92. package/core/server/data/migrations/versions/4.17/02-add-offers-table.js +19 -0
  93. package/core/server/data/migrations/versions/4.17/03-add-offers-permissions.js +35 -0
  94. package/core/server/data/schema/fixtures/fixtures.json +32 -0
  95. package/core/server/data/schema/schema.js +33 -0
  96. package/core/server/models/custom-theme-setting.js +9 -0
  97. package/core/server/models/index.js +2 -0
  98. package/core/server/services/custom-theme-settings.js +8 -0
  99. package/core/server/services/members/api.js +4 -1
  100. package/core/server/services/redirects/index.js +15 -0
  101. package/core/{frontend → server}/services/redirects/settings.js +13 -6
  102. package/core/server/services/redirects/validation.js +44 -0
  103. package/core/{frontend/services/settings → server/services/route-settings}/default-routes.yaml +0 -0
  104. package/core/server/services/route-settings/default-settings-manager.js +62 -0
  105. package/core/server/services/route-settings/index.js +32 -1
  106. package/core/server/services/route-settings/route-settings.js +38 -12
  107. package/core/server/services/route-settings/settings-loader.js +102 -0
  108. package/core/{frontend/services/settings → server/services/route-settings}/validate.js +38 -28
  109. package/core/server/services/route-settings/yaml-parser.js +53 -0
  110. package/core/server/services/settings/index.js +13 -16
  111. package/core/server/services/settings/settings-bread-service.js +188 -0
  112. package/core/server/services/settings/settings-utils.js +32 -0
  113. package/core/server/services/themes/ThemeStorage.js +5 -4
  114. package/core/server/services/themes/activation-bridge.js +14 -0
  115. package/core/server/services/themes/validate.js +5 -2
  116. package/core/server/web/admin/views/default-prod.html +4 -4
  117. package/core/server/web/admin/views/default.html +4 -4
  118. package/core/server/web/api/canary/admin/routes.js +5 -1
  119. package/core/server/web/members/app.js +3 -0
  120. package/core/server/web/oauth/app.js +7 -8
  121. package/core/server/web/shared/middlewares/custom-redirects.js +82 -59
  122. package/core/server/web/site/routes.js +2 -2
  123. package/core/shared/config/defaults.json +2 -2
  124. package/core/shared/config/overrides.json +1 -1
  125. package/core/shared/custom-theme-settings-cache.js +3 -0
  126. package/core/shared/i18n/translations/en.json +2 -13
  127. package/core/shared/labs.js +2 -2
  128. package/package.json +42 -41
  129. package/yarn.lock +916 -901
  130. package/core/built/assets/ghost-dark-faf931d90e92535e6c03ca16793cbe7b.css +0 -1
  131. package/core/built/assets/ghost.min-7aa074ad556a8455155ac88ceaca03ab.css +0 -1
  132. package/core/frontend/services/redirects/index.js +0 -9
  133. package/core/frontend/services/redirects/validation.js +0 -28
  134. package/core/frontend/services/settings/ensure-settings.js +0 -47
  135. package/core/frontend/services/settings/index.js +0 -104
  136. package/core/frontend/services/settings/loader.js +0 -89
  137. package/core/frontend/services/settings/yaml-parser.js +0 -31
@@ -2,8 +2,8 @@
2
2
  // Usage: `{{amp_analytics}}`
3
3
  //
4
4
  // Outputs inline scripts used for analytics
5
-
6
- const {SafeString, settingsCache} = require('../../../../services/proxy');
5
+ const {settingsCache} = require('../../../../services/proxy');
6
+ const {SafeString} = require('../../../../services/rendering');
7
7
 
8
8
  function ampComponents() {
9
9
  let components = [];
@@ -8,7 +8,8 @@
8
8
  // By default supported AMP HTML tags (no additional script tag necessary):
9
9
  // amp-img, amp-ad, amp-embed, amp-video and amp-pixel.
10
10
  // (less) dirty requires
11
- const {SafeString, settingsCache} = require('../../../../services/proxy');
11
+ const {settingsCache} = require('../../../../services/proxy');
12
+ const {SafeString} = require('../../../../services/rendering');
12
13
 
13
14
  function ampComponents() {
14
15
  let components = [];
@@ -9,7 +9,11 @@
9
9
  const Promise = require('bluebird');
10
10
 
11
11
  const moment = require('moment');
12
- const {SafeString, logging, errors} = require('../../../../services/proxy');
12
+ const errors = require('@tryghost/errors');
13
+ const logging = require('@tryghost/logging');
14
+
15
+ const {SafeString} = require('../../../../services/rendering');
16
+
13
17
  const amperizeCache = {};
14
18
  let allowedAMPTags = [];
15
19
  let allowedAMPAttributes = {};
@@ -1,4 +1,4 @@
1
- const {SafeString, escapeExpression} = require('../../../../services/proxy');
1
+ const {SafeString, escapeExpression} = require('../../../../services/rendering');
2
2
 
3
3
  module.exports = function amp_style(options) { // eslint-disable-line camelcase
4
4
  if (options.data.site.accent_color) {
@@ -2,14 +2,18 @@ const path = require('path');
2
2
  const express = require('../../../../shared/express');
3
3
  const ampRouter = express.Router('amp');
4
4
 
5
- // Dirty requires
6
- const {i18n} = require('../../../services/proxy');
5
+ const tpl = require('@tryghost/tpl');
7
6
  const errors = require('@tryghost/errors');
8
7
 
8
+ // Dirty requires
9
9
  const urlService = require('../../../services/url');
10
10
  const helpers = require('../../../services/routing/helpers');
11
11
  const templateName = 'amp';
12
12
 
13
+ const messages = {
14
+ pageNotFound: 'Page not found.'
15
+ };
16
+
13
17
  function _renderer(req, res, next) {
14
18
  res.routerOptions = {
15
19
  type: 'custom',
@@ -23,7 +27,7 @@ function _renderer(req, res, next) {
23
27
 
24
28
  // CASE: we only support amp pages for posts that are not static pages
25
29
  if (!data.post || data.post.page) {
26
- return next(new errors.NotFoundError({message: i18n.t('errors.errors.pageNotFound')}));
30
+ return next(new errors.NotFoundError({message: tpl(messages.pageNotFound)}));
27
31
  }
28
32
 
29
33
  // Render Call
@@ -61,7 +65,7 @@ function getPostData(req, res, next) {
61
65
 
62
66
  if (!permalinks) {
63
67
  return next(new errors.NotFoundError({
64
- message: i18n.t('errors.errors.pageNotFound')
68
+ message: tpl(messages.pageNotFound)
65
69
  }));
66
70
  }
67
71
 
@@ -1,11 +1,19 @@
1
- const {i18n} = require('../../services/proxy');
2
- const urlUtils = require('../../../shared/url-utils');
1
+ const tpl = require('@tryghost/tpl');
3
2
  const logging = require('@tryghost/logging');
4
3
  const errors = require('@tryghost/errors');
4
+ const urlUtils = require('../../../shared/url-utils');
5
5
  const middleware = require('./lib/middleware');
6
6
  const router = require('./lib/router');
7
7
  const registerHelpers = require('./lib/helpers');
8
8
 
9
+ const messages = {
10
+ urlCannotContainPrivateSubdir: {
11
+ error: 'private subdirectory not allowed',
12
+ description: 'Your site url in config.js cannot contain a subdirectory called private.',
13
+ help: 'Please rename the subdirectory before restarting'
14
+ }
15
+ };
16
+
9
17
  // routeKeywords.private: 'private'
10
18
  const PRIVATE_KEYWORD = 'private';
11
19
 
@@ -17,9 +25,9 @@ let checkSubdir = function checkSubdir() {
17
25
 
18
26
  if (paths.pop() === PRIVATE_KEYWORD) {
19
27
  logging.error(new errors.GhostError({
20
- message: i18n.t('errors.config.urlCannotContainPrivateSubdir.error'),
21
- context: i18n.t('errors.config.urlCannotContainPrivateSubdir.description'),
22
- help: i18n.t('errors.config.urlCannotContainPrivateSubdir.help')
28
+ message: tpl(messages.urlCannotContainPrivateSubdir.error),
29
+ context: tpl(messages.urlCannotContainPrivateSubdir.description),
30
+ help: tpl(messages.urlCannotContainPrivateSubdir.help)
23
31
  }));
24
32
 
25
33
  // @TODO: why
@@ -4,7 +4,7 @@
4
4
  // Password input used on private.hbs for password-protected blogs
5
5
 
6
6
  // (less) dirty requires
7
- const {SafeString, templates} = require('../../../../services/proxy');
7
+ const {SafeString, templates} = require('../../../../services/rendering');
8
8
 
9
9
  // We use the name input_password to match the helper for consistency:
10
10
  module.exports = function input_password(options) { // eslint-disable-line camelcase
@@ -5,12 +5,17 @@ const path = require('path');
5
5
  const config = require('../../../../shared/config');
6
6
  const urlUtils = require('../../../../shared/url-utils');
7
7
  const constants = require('@tryghost/constants');
8
- const {i18n} = require('../../../services/proxy');
8
+ const tpl = require('@tryghost/tpl');
9
9
  const errors = require('@tryghost/errors');
10
10
  const settingsCache = require('../../../../shared/settings-cache');
11
11
  // routeKeywords.private: 'private'
12
12
  const privateRoute = '/private/';
13
13
 
14
+ const messages = {
15
+ pageNotFound: 'Page not found.',
16
+ wrongPassword: 'Wrong password'
17
+ };
18
+
14
19
  function verifySessionHash(salt, hash) {
15
20
  if (!salt || !hash) {
16
21
  return false;
@@ -95,7 +100,7 @@ const privateBlogging = {
95
100
  // CASE: RSS is disabled for private blogging e.g. they create overhead
96
101
  if (req.path.match(/\/rss\/$/)) {
97
102
  return next(new errors.NotFoundError({
98
- message: i18n.t('errors.errors.pageNotFound')
103
+ message: tpl(messages.pageNotFound)
99
104
  }));
100
105
  }
101
106
 
@@ -156,7 +161,7 @@ const privateBlogging = {
156
161
  return res.redirect(urlUtils.urlFor({relativeUrl: forward}));
157
162
  } else {
158
163
  res.error = {
159
- message: i18n.t('errors.middleware.privateblogging.wrongPassword')
164
+ message: tpl(messages.wrongPassword)
160
165
  };
161
166
  return next();
162
167
  }
@@ -2,16 +2,24 @@
2
2
  // Usage: `{{asset "css/screen.css"}}`
3
3
  //
4
4
  // Returns the path to the specified asset.
5
- const {SafeString, metaData, errors, i18n} = require('../services/proxy');
5
+ const {metaData} = require('../services/proxy');
6
+ const {SafeString} = require('../services/rendering');
7
+
8
+ const errors = require('@tryghost/errors');
9
+ const tpl = require('@tryghost/tpl');
6
10
  const get = require('lodash/get');
7
11
  const {getAssetUrl} = metaData;
8
12
 
13
+ const messages = {
14
+ pathIsRequired: 'The {{asset}} helper must be passed a path'
15
+ };
16
+
9
17
  module.exports = function asset(path, options) {
10
18
  const hasMinFile = get(options, 'hash.hasMinFile');
11
19
 
12
20
  if (!path) {
13
21
  throw new errors.IncorrectUsageError({
14
- message: i18n.t('warnings.helpers.asset.pathIsRequired')
22
+ message: tpl(messages.pathIsRequired)
15
23
  });
16
24
  }
17
25
 
@@ -9,16 +9,18 @@
9
9
  //
10
10
  // Block helper: `{{#author}}{{/author}}`
11
11
  // This is the default handlebars behaviour of dropping into the author object scope
12
- const {urlService, SafeString, escapeExpression, hbs, templates} = require('../services/proxy');
13
- const buildInHelpers = hbs.handlebars.helpers;
12
+ const {urlService} = require('../services/proxy');
13
+ const {SafeString, escapeExpression, hbs, templates} = require('../services/rendering');
14
14
  const isString = require('lodash/isString');
15
15
 
16
+ const builtInHelpers = hbs.handlebars.helpers;
17
+
16
18
  /**
17
19
  * @deprecated: single authors was superceded by multiple authors in Ghost 1.22.0
18
20
  */
19
21
  module.exports = function author(options) {
20
22
  if (options.fn) {
21
- return buildInHelpers.with.call(this, this.author, options);
23
+ return builtInHelpers.with.call(this, this.author, options);
22
24
  }
23
25
 
24
26
  const autolink = isString(options.hash.autolink) && options.hash.autolink === 'false' ? false : true;
@@ -6,9 +6,10 @@
6
6
  // By default, authors are separated by commas.
7
7
  //
8
8
  // Note that the standard {{#each authors}} implementation is unaffected by this helper.
9
+ const {urlService} = require('../services/proxy');
10
+ const {SafeString, escapeExpression, templates} = require('../services/rendering');
9
11
  const isString = require('lodash/isString');
10
- const {SafeString, escapeExpression, templates, urlService} = require('../services/proxy');
11
- const ghostHelperUtils = require('@tryghost/helpers').utils;
12
+ const {utils} = require('@tryghost/helpers');
12
13
 
13
14
  module.exports = function authors(options = {}) {
14
15
  options.hash = options.hash || {};
@@ -38,7 +39,7 @@ module.exports = function authors(options = {}) {
38
39
  }) : escapeExpression(author.name);
39
40
  }
40
41
 
41
- return ghostHelperUtils.visibility.filter(authorsList, visibility, processAuthor);
42
+ return utils.visibility.filter(authorsList, visibility, processAuthor);
42
43
  }
43
44
 
44
45
  if (this.authors && this.authors.length) {
@@ -2,7 +2,7 @@
2
2
  // Usage: `{{body_class}}`
3
3
  //
4
4
  // Output classes for the body element
5
- const {SafeString} = require('../services/proxy');
5
+ const {SafeString} = require('../services/rendering');
6
6
 
7
7
  // We use the name body_class to match the helper for consistency
8
8
  module.exports = function body_class(options) { // eslint-disable-line camelcase
@@ -5,14 +5,21 @@
5
5
  // Outputs cancel/renew links to manage subscription renewal after the subscription period ends.
6
6
  //
7
7
  // Defaults to class="cancel-subscription-link" errorClass="cancel-subscription-error" cancelLabel="Cancel subscription" continueLabel="Continue subscription"
8
+ const {labs} = require('../services/proxy');
9
+ const {templates} = require('../services/rendering');
8
10
 
9
- const {templates, errors, i18n, labs} = require('../services/proxy');
11
+ const errors = require('@tryghost/errors');
12
+ const tpl = require('@tryghost/tpl');
13
+
14
+ const messages = {
15
+ invalidData: 'The {{cancel_link}} helper was used outside of a subscription context. See https://ghost.org/docs/themes/members/#cancel-links.'
16
+ };
10
17
 
11
18
  function cancel_link(options) { // eslint-disable-line camelcase
12
19
  let truncateOptions = (options || {}).hash || {};
13
20
 
14
21
  if (this.id === undefined || this.cancel_at_period_end === undefined) {
15
- throw new errors.IncorrectUsageError({message: i18n.t('warnings.helpers.cancel_link.invalidData')});
22
+ throw new errors.IncorrectUsageError({message: tpl(messages.invalidData)});
16
23
  }
17
24
 
18
25
  const data = {
@@ -1,4 +1,4 @@
1
- const {SafeString} = require('../services/proxy');
1
+ const {SafeString} = require('../services/rendering');
2
2
 
3
3
  module.exports = function concat(...args) {
4
4
  const options = args.pop();
@@ -10,7 +10,7 @@
10
10
  //
11
11
  // Dev flag feature: In case of restricted content access for member-only posts, shows CTA box
12
12
 
13
- const {templates, hbs, SafeString} = require('../services/proxy');
13
+ const {templates, hbs, SafeString} = require('../services/rendering');
14
14
  const downsize = require('downsize');
15
15
  const _ = require('lodash');
16
16
  const createFrame = hbs.handlebars.createFrame;
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // Formats a date using moment-timezone.js. Formats published_at by default but will also take a date as a parameter
5
5
 
6
- const {SafeString} = require('../services/proxy');
6
+ const {SafeString} = require('../services/rendering');
7
7
  const moment = require('moment-timezone');
8
8
  const _ = require('lodash');
9
9
 
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // Returns URI encoded string
6
6
 
7
- const {SafeString} = require('../services/proxy');
7
+ const {SafeString} = require('../services/rendering');
8
8
 
9
9
  module.exports = function encode(string, options) {
10
10
  const uri = string || options;
@@ -5,7 +5,8 @@
5
5
  //
6
6
  // Defaults to words="50"
7
7
 
8
- const {SafeString, metaData} = require('../services/proxy');
8
+ const {SafeString} = require('../services/rendering');
9
+ const {metaData} = require('../services/proxy');
9
10
  const _ = require('lodash');
10
11
  const getMetaDataExcerpt = metaData.getMetaDataExcerpt;
11
12
 
@@ -2,7 +2,8 @@
2
2
  // Usage: `{{facebook_url}}` or `{{facebook_url author.facebook}}`
3
3
  //
4
4
  // Output a url for a facebook username
5
- const {socialUrls, localUtils} = require('../services/proxy');
5
+ const {socialUrls} = require('../services/proxy');
6
+ const {localUtils} = require('../services/rendering');
6
7
 
7
8
  // We use the name facebook_url to match the helper for consistency:
8
9
  module.exports = function facebook_url(username, options) { // eslint-disable-line camelcase
@@ -2,14 +2,23 @@
2
2
  // Usage: `{{#foreach data}}{{/foreach}}`
3
3
  //
4
4
  // Block helper designed for looping through posts
5
+ const {checks} = require('../services/proxy');
6
+ const {hbs} = require('../services/rendering');
7
+
5
8
  const _ = require('lodash');
6
- const {logging, i18n, hbs, checks} = require('../services/proxy');
9
+ const logging = require('@tryghost/logging');
10
+ const tpl = require('@tryghost/tpl');
11
+
7
12
  const {Utils: hbsUtils, handlebars: {createFrame}} = hbs;
8
13
  const ghostHelperUtils = require('@tryghost/helpers').utils;
9
14
 
15
+ const messages = {
16
+ iteratorNeeded: 'Need to pass an iterator to {{#foreach}}'
17
+ };
18
+
10
19
  module.exports = function foreach(items, options) {
11
20
  if (!options) {
12
- logging.warn(i18n.t('warnings.helpers.foreach.iteratorNeeded'));
21
+ logging.warn(tpl(messages.iteratorNeeded));
13
22
  }
14
23
 
15
24
  if (hbsUtils.isFunction(items)) {
@@ -1,11 +1,22 @@
1
1
  // # Get Helper
2
2
  // Usage: `{{#get "posts" limit="5"}}`, `{{#get "tags" limit="all"}}`
3
3
  // Fetches data from the API
4
- const {config, logging, errors, i18n, hbs, api, prepareContextResource} = require('../services/proxy');
4
+ const {config, api, prepareContextResource} = require('../services/proxy');
5
+ const {hbs} = require('../services/rendering');
6
+
7
+ const logging = require('@tryghost/logging');
8
+ const errors = require('@tryghost/errors');
9
+ const tpl = require('@tryghost/tpl');
10
+
5
11
  const _ = require('lodash');
6
12
  const Promise = require('bluebird');
7
13
  const jsonpath = require('jsonpath');
8
14
 
15
+ const messages = {
16
+ mustBeCalledAsBlock: 'The {{{helperName}}} helper must be called as a block. E.g. {{#{helperName}}}...{{/{helperName}}}',
17
+ invalidResource: 'Invalid resource given to get helper'
18
+ };
19
+
9
20
  const createFrame = hbs.handlebars.createFrame;
10
21
 
11
22
  const RESOURCES = {
@@ -121,13 +132,13 @@ module.exports = function get(resource, options) {
121
132
  let returnedRowsCount;
122
133
 
123
134
  if (!options.fn) {
124
- data.error = i18n.t('warnings.helpers.mustBeCalledAsBlock', {helperName: 'get'});
135
+ data.error = tpl(messages.mustBeCalledAsBlock, {helperName: 'get'});
125
136
  logging.warn(data.error);
126
137
  return Promise.resolve();
127
138
  }
128
139
 
129
140
  if (!RESOURCES[resource]) {
130
- data.error = i18n.t('warnings.helpers.get.invalidResource');
141
+ data.error = tpl(messages.invalidResource);
131
142
  logging.warn(data.error);
132
143
  return Promise.resolve(options.inverse(self, {data: data}));
133
144
  }
@@ -2,7 +2,8 @@
2
2
  // Usage: `{{ghost_foot}}`
3
3
  //
4
4
  // Outputs scripts and other assets at the bottom of a Ghost theme
5
- const {SafeString, settingsCache} = require('../services/proxy');
5
+ const {settingsCache} = require('../services/proxy');
6
+ const {SafeString} = require('../services/rendering');
6
7
  const _ = require('lodash');
7
8
 
8
9
  // We use the name ghost_foot to match the helper for consistency:
@@ -2,7 +2,10 @@
2
2
  // Usage: `{{ghost_head}}`
3
3
  //
4
4
  // Outputs scripts and other assets at the top of a Ghost theme
5
- const {metaData, escapeExpression, SafeString, logging, settingsCache, config, blogIcon, urlUtils} = require('../services/proxy');
5
+ const {metaData, settingsCache, config, blogIcon, urlUtils, labs} = require('../services/proxy');
6
+ const {escapeExpression, SafeString} = require('../services/rendering');
7
+
8
+ const logging = require('@tryghost/logging');
6
9
  const _ = require('lodash');
7
10
  const debug = require('@tryghost/debug')('ghost_head');
8
11
  const templateStyles = require('./tpl/styles');
@@ -176,6 +179,12 @@ module.exports = function ghost_head(options) { // eslint-disable-line camelcase
176
179
  head.push('<meta name="generator" content="Ghost ' +
177
180
  escapeExpression(safeVersion) + '" />');
178
181
 
182
+ // Ghost analytics tag
183
+ if (labs.isSet('membersActivity')) {
184
+ const postId = (dataRoot && dataRoot.post) ? dataRoot.post.id : '';
185
+ head.push(writeMetaTag('ghost-analytics-id', postId, 'name'));
186
+ }
187
+
179
188
  head.push('<link rel="alternate" type="application/rss+xml" title="' +
180
189
  escapeExpression(meta.site.title) + '" href="' +
181
190
  escapeExpression(meta.rssUrl) + '" />');
@@ -4,9 +4,14 @@
4
4
  //
5
5
  // Checks if a post has a particular property
6
6
 
7
- const {logging, i18n} = require('../services/proxy');
7
+ const logging = require('@tryghost/logging');
8
+ const tpl = require('@tryghost/tpl');
8
9
  const _ = require('lodash');
9
- const validAttrs = ['tag', 'author', 'slug','visibility', 'id', 'number', 'index', 'any', 'all'];
10
+ const validAttrs = ['tag', 'author', 'slug', 'visibility', 'id', 'number', 'index', 'any', 'all'];
11
+
12
+ const messages = {
13
+ invalidAttribute: 'Invalid or no attribute given to has helper'
14
+ };
10
15
 
11
16
  function handleCount(ctxAttr, data) {
12
17
  if (!data || !_.isFinite(data.length)) {
@@ -155,7 +160,7 @@ module.exports = function has(options) {
155
160
  let result;
156
161
 
157
162
  if (_.isEmpty(attrs)) {
158
- logging.warn(i18n.t('warnings.helpers.has.invalidAttribute'));
163
+ logging.warn(tpl(messages.invalidAttribute));
159
164
  return;
160
165
  }
161
166
 
@@ -6,24 +6,30 @@
6
6
  //
7
7
  // Returns the URL for the current object scope i.e. If inside a post scope will return image permalink
8
8
  // `absolute` flag outputs absolute URL, else URL is relative.
9
+ const {urlUtils} = require('../services/proxy');
9
10
 
10
11
  const url = require('url');
11
12
  const _ = require('lodash');
12
- const {urlUtils, logging, i18n} = require('../services/proxy');
13
+ const logging = require('@tryghost/logging');
14
+ const tpl = require('@tryghost/tpl');
15
+
16
+ const messages = {
17
+ attrIsRequired: 'Attribute is required e.g. {{img_url feature_image}}'
18
+ };
13
19
 
14
20
  const STATIC_IMAGE_URL_PREFIX = `${urlUtils.STATIC_IMAGE_URL_PREFIX}`;
15
21
 
16
22
  module.exports = function imgUrl(requestedImageUrl, options) {
17
23
  // CASE: if no url is passed, e.g. `{{img_url}}` we show a warning
18
24
  if (arguments.length < 2) {
19
- logging.warn(i18n.t('warnings.helpers.img_url.attrIsRequired'));
25
+ logging.warn(tpl(messages.attrIsRequired));
20
26
  return;
21
27
  }
22
28
 
23
29
  // CASE: if url is passed, but it is undefined, then the attribute was
24
30
  // an unknown value, e.g. {{img_url feature_img}} and we also show a warning
25
31
  if (requestedImageUrl === undefined) {
26
- logging.warn(i18n.t('warnings.helpers.img_url.attrIsRequired'));
32
+ logging.warn(tpl(messages.attrIsRequired));
27
33
  return;
28
34
  }
29
35
 
@@ -1,16 +1,21 @@
1
1
  // # Is Helper
2
2
  // Usage: `{{#is "paged"}}`, `{{#is "index, paged"}}`
3
3
  // Checks whether we're in a given context.
4
- const {logging, i18n} = require('../services/proxy');
4
+ const logging = require('@tryghost/logging');
5
+ const tpl = require('@tryghost/tpl');
5
6
  const _ = require('lodash');
6
7
 
8
+ const messages = {
9
+ invalidAttribute: 'Invalid or no attribute given to is helper'
10
+ };
11
+
7
12
  module.exports = function is(context, options) {
8
13
  options = options || {};
9
14
 
10
15
  const currentContext = options.data.root.context;
11
16
 
12
17
  if (!_.isString(context)) {
13
- logging.warn(i18n.t('warnings.helpers.is.invalidAttribute'));
18
+ logging.warn(tpl(messages.invalidAttribute));
14
19
  return;
15
20
  }
16
21
 
@@ -12,7 +12,7 @@
12
12
  // Language tags in HTML and XML
13
13
  // https://www.w3.org/International/articles/language-tags/
14
14
 
15
- const {SafeString} = require('../services/proxy');
15
+ const {SafeString} = require('../services/rendering');
16
16
 
17
17
  module.exports = function lang(options) {
18
18
  const locale = options.data.site.locale;
@@ -1,8 +1,17 @@
1
1
  // # link helper
2
+ const {config} = require('../services/proxy');
3
+ const {SafeString, localUtils} = require('../services/rendering');
4
+
2
5
  const _ = require('lodash');
3
- const {config, SafeString, errors, i18n, localUtils} = require('../services/proxy');
6
+ const errors = require('@tryghost/errors');
7
+ const tpl = require('@tryghost/tpl');
8
+
4
9
  const {buildLinkClasses} = localUtils;
5
10
 
11
+ const messages = {
12
+ hrefIsRequired: 'The {{#link}}{{/link}} helper requires an href="" attribute.'
13
+ };
14
+
6
15
  const managedAttributes = ['href', 'class', 'activeClass', 'parentActiveClass'];
7
16
 
8
17
  function _formatAttrs(attributes) {
@@ -25,7 +34,7 @@ module.exports = function link(options) {
25
34
  // If there is no href provided, this is theme dev error, so we throw an error to make this clear.
26
35
  if (!_.has(options.hash, 'href')) {
27
36
  throw new errors.IncorrectUsageError({
28
- message: i18n.t('warnings.helpers.link.hrefIsRequired')
37
+ message: tpl(messages.hrefIsRequired)
29
38
  });
30
39
  }
31
40
  // If the href attribute is empty, this is probably a dynamic data problem, hard for theme devs to track down
@@ -1,8 +1,17 @@
1
1
  // # link_class helper
2
+ const {config} = require('../services/proxy');
3
+ const {SafeString, localUtils} = require('../services/rendering');
4
+
2
5
  const _ = require('lodash');
3
- const {config, SafeString, errors, i18n, localUtils} = require('../services/proxy');
6
+ const errors = require('@tryghost/errors');
7
+ const tpl = require('@tryghost/tpl');
8
+
4
9
  const {buildLinkClasses} = localUtils;
5
10
 
11
+ const messages = {
12
+ forIsRequired: 'The {{link_class}} helper requires a for="" attribute.'
13
+ };
14
+
6
15
  module.exports = function link_class(options) { // eslint-disable-line camelcase
7
16
  options = options || {};
8
17
  options.hash = options.hash || {};
@@ -11,7 +20,7 @@ module.exports = function link_class(options) { // eslint-disable-line camelcase
11
20
  // If there is no for provided, this is theme dev error, so we throw an error to make this clear.
12
21
  if (!_.has(options.hash, 'for')) {
13
22
  throw new errors.IncorrectUsageError({
14
- message: i18n.t('warnings.helpers.link_class.forIsRequired')
23
+ message: tpl(messages.forIsRequired)
15
24
  });
16
25
  }
17
26
 
@@ -1,6 +1,15 @@
1
- const {logging, i18n, SafeString, labs} = require('../services/proxy');
1
+ const {labs} = require('../services/proxy');
2
+ const {SafeString} = require('../services/rendering');
3
+
4
+ const logging = require('@tryghost/logging');
5
+ const tpl = require('@tryghost/tpl');
6
+
2
7
  const _ = require('lodash');
3
8
 
9
+ const messages = {
10
+ invalidAttribute: 'Invalid or no attribute given to match helper'
11
+ };
12
+
4
13
  /**
5
14
  * This is identical to the built-in if helper, except inverse/fn calls are replaced with false/true
6
15
  * https://github.com/handlebars-lang/handlebars.js/blob/19bdace85a8d0bc5ed3a4dec4071cb08c8d003f2/lib/handlebars/helpers/if.js#L9-L20
@@ -58,7 +67,7 @@ function match(...attrs) {
58
67
  let result;
59
68
 
60
69
  if (_.isEmpty(attrs)) {
61
- logging.warn(i18n.t('warnings.helpers.has.invalidAttribute'));
70
+ logging.warn(tpl(messages.invalidAttribute));
62
71
  return;
63
72
  }
64
73
 
@@ -68,7 +77,7 @@ function match(...attrs) {
68
77
  } else if (attrs.length === 3) {
69
78
  result = handleMatch(attrs[0], attrs[1], attrs[2], options);
70
79
  } else {
71
- logging.warn(i18n.t('warnings.helpers.has.invalidAttribute'));
80
+ logging.warn(tpl(messages.invalidAttribute));
72
81
  return;
73
82
  }
74
83