ghost 4.20.2 → 4.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/.eslintrc.js +1 -1
  2. package/Gruntfile.js +1 -0
  3. package/content/themes/casper/assets/built/screen.css +1 -1
  4. package/content/themes/casper/assets/built/screen.css.map +1 -1
  5. package/content/themes/casper/assets/css/screen.css +263 -50
  6. package/content/themes/casper/default.hbs +12 -3
  7. package/content/themes/casper/index.hbs +25 -23
  8. package/content/themes/casper/package.json +91 -2
  9. package/content/themes/casper/partials/post-card.hbs +1 -1
  10. package/content/themes/casper/post.hbs +18 -14
  11. package/content/themes/casper/yarn.lock +245 -192
  12. package/core/boot.js +5 -0
  13. package/core/bridge.js +14 -0
  14. package/core/built/assets/{chunk.3.777d43e2ce954ba8b2f5.js → chunk.3.1148677ff3b78e5aeaee.js} +60 -60
  15. package/core/built/assets/{ghost-dark-20e2892d4f30d0d1183c9ac725ea37d0.css → ghost-dark-684ad238e1a858c7cb5be6988de7c6f5.css} +1 -1
  16. package/core/built/assets/{ghost.min-57e46fd3b1145ecf2cbd185a13611f3b.css → ghost.min-66e08535f8bb797a8c40e0a2b31f1e9e.css} +1 -1
  17. package/core/built/assets/{ghost.min-07b6a50c54b3e2e190332c28c7255d2f.js → ghost.min-efbfb823467b66f4acc66537d033aa55.js} +1770 -1906
  18. package/core/built/assets/{vendor.min-af502ac4142871500fc424f6a5a254ec.js → vendor.min-7c8fdd90f7ecd2e94328a07ea3b64608.js} +985 -956
  19. package/core/frontend/apps/amp/lib/helpers/amp_content.js +16 -4
  20. package/core/frontend/helpers/asset.js +9 -1
  21. package/core/frontend/helpers/ghost_head.js +13 -1
  22. package/core/frontend/meta/title.js +15 -5
  23. package/core/frontend/services/card-assets/index.js +16 -0
  24. package/core/frontend/services/card-assets/service.js +101 -0
  25. package/core/frontend/services/routing/router-manager.js +5 -3
  26. package/core/frontend/services/theme-engine/config/defaults.json +4 -1
  27. package/core/frontend/services/theme-engine/config/index.js +1 -1
  28. package/core/frontend/src/cards/css/bookmark.css +83 -0
  29. package/core/frontend/src/cards/css/gallery.css +36 -0
  30. package/core/frontend/src/cards/js/gallery.js +8 -0
  31. package/core/frontend/web/middleware/serve-public-file.js +10 -1
  32. package/core/frontend/web/site.js +10 -9
  33. package/core/server/adapters/storage/LocalImagesStorage.js +50 -0
  34. package/core/server/adapters/storage/LocalMediaStorage.js +23 -0
  35. package/core/server/adapters/storage/{LocalFileStorage.js → LocalStorageBase.js} +36 -48
  36. package/core/server/adapters/storage/index.js +1 -1
  37. package/core/server/adapters/storage/utils.js +2 -2
  38. package/core/server/api/canary/index.js +4 -0
  39. package/core/server/api/canary/media.js +22 -0
  40. package/core/server/api/canary/members.js +6 -103
  41. package/core/server/api/canary/membersStripeConnect.js +0 -10
  42. package/core/server/api/canary/redirects.js +1 -6
  43. package/core/server/api/canary/utils/serializers/input/pages.js +8 -0
  44. package/core/server/api/canary/utils/serializers/output/images.js +4 -0
  45. package/core/server/api/canary/utils/serializers/output/index.js +4 -0
  46. package/core/server/api/canary/utils/serializers/output/media.js +28 -0
  47. package/core/server/api/canary/utils/validators/input/index.js +4 -0
  48. package/core/server/api/canary/utils/validators/input/media.js +7 -0
  49. package/core/server/api/v2/redirects.js +1 -6
  50. package/core/server/api/v2/utils/serializers/output/images.js +4 -0
  51. package/core/server/api/v3/members.js +5 -1
  52. package/core/server/api/v3/redirects.js +1 -6
  53. package/core/server/api/v3/utils/serializers/output/images.js +4 -0
  54. package/core/server/data/migrations/utils.js +55 -16
  55. package/core/server/data/migrations/versions/4.22/01-add-is-launch-complete-setting.js +8 -0
  56. package/core/server/data/migrations/versions/4.22/02-update-launch-complete-setting-from-user-data.js +39 -0
  57. package/core/server/data/schema/default-settings.json +8 -0
  58. package/core/server/frontend/ghost.min.css +1 -1
  59. package/core/server/lib/image/blog-icon.js +2 -4
  60. package/core/server/lib/image/image-size.js +1 -1
  61. package/core/server/services/limits.js +3 -6
  62. package/core/server/services/mega/template.js +4 -0
  63. package/core/server/services/members/api.js +1 -1
  64. package/core/server/services/members/emails/signup.js +2 -2
  65. package/core/server/services/members/stripe-connect.js +14 -0
  66. package/core/server/services/offers/service.js +1 -31
  67. package/core/server/services/redirects/api.js +270 -0
  68. package/core/server/services/redirects/index.js +27 -12
  69. package/core/server/services/themes/ThemeStorage.js +5 -5
  70. package/core/server/web/admin/views/default-prod.html +4 -4
  71. package/core/server/web/admin/views/default.html +4 -4
  72. package/core/server/web/api/canary/admin/routes.js +13 -4
  73. package/core/server/web/api/middleware/upload.js +117 -10
  74. package/core/server/web/members/app.js +1 -1
  75. package/core/server/web/shared/middlewares/index.js +0 -4
  76. package/core/shared/config/defaults.json +3 -1
  77. package/core/shared/config/helpers.js +2 -0
  78. package/core/shared/config/overrides.json +8 -0
  79. package/core/shared/labs.js +5 -3
  80. package/package.json +35 -34
  81. package/yarn.lock +997 -1016
  82. package/core/built/assets/img/themes/Editorial-a25a4a34c04dedd858bd5e05ef388b1c.jpg +0 -0
  83. package/core/built/assets/img/themes/Massively-06edf00108429f7fb8e65f190fba34fe.jpg +0 -0
  84. package/core/server/services/redirects/settings.js +0 -234
  85. package/core/server/web/shared/middlewares/custom-redirects.js +0 -128
@@ -1,12 +1,11 @@
1
- // # Local File System Image Storage module
2
- // The (default) module for storing images, using the local file system
1
+ // # Local File Base Storage module
2
+ // The (default) module for storing files using the local file system
3
3
  const serveStatic = require('../../../shared/express').static;
4
4
 
5
5
  const fs = require('fs-extra');
6
6
  const path = require('path');
7
7
  const Promise = require('bluebird');
8
8
  const moment = require('moment');
9
- const config = require('../../../shared/config');
10
9
  const tpl = require('@tryghost/tpl');
11
10
  const logging = require('@tryghost/logging');
12
11
  const errors = require('@tryghost/errors');
@@ -15,68 +14,57 @@ const urlUtils = require('../../../shared/url-utils');
15
14
  const StorageBase = require('ghost-storage-base');
16
15
 
17
16
  const messages = {
18
- imageNotFound: 'Image not found',
19
- imageNotFoundWithRef: 'Image not found: {img}',
20
- cannotReadImage: 'Could not read image: {img}'
17
+ notFound: 'File not found',
18
+ notFoundWithRef: 'File not found: {file}',
19
+ cannotRead: 'Could not read file: {file}'
21
20
  };
22
21
 
23
- class LocalFileStore extends StorageBase {
24
- constructor() {
25
- super();
26
-
27
- this.storagePath = config.getContentPath('images');
28
- }
29
-
22
+ class LocalStorageBase extends StorageBase {
30
23
  /**
31
- * Saves a buffer in the targetPath
32
- * @param {Buffer} buffer is an instance of Buffer
33
- * @param {String} targetPath path to which the buffer should be written
34
- * @returns {Promise<String>} a URL to retrieve the data
24
+ *
25
+ * @param {Object} options
26
+ * @param {String} options.storagePath
27
+ * @param {String} [options.staticFileURLPrefix]
28
+ * @param {Object} [options.errorMessages]
29
+ * @param {String} [options.errorMessages.notFound]
30
+ * @param {String} [options.errorMessages.notFoundWithRef]
31
+ * @param {String} [options.errorMessages.cannotRead]
35
32
  */
36
- async saveRaw(buffer, targetPath) {
37
- const storagePath = path.join(this.storagePath, targetPath);
38
- const targetDir = path.dirname(storagePath);
39
-
40
- await fs.mkdirs(targetDir);
41
- await fs.writeFile(storagePath, buffer);
42
-
43
- // For local file system storage can use relative path so add a slash
44
- const fullUrl = (
45
- urlUtils.urlJoin('/', urlUtils.getSubdir(),
46
- urlUtils.STATIC_IMAGE_URL_PREFIX,
47
- targetPath)
48
- ).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
33
+ constructor({storagePath, staticFileURLPrefix, errorMessages}) {
34
+ super();
49
35
 
50
- return fullUrl;
36
+ this.storagePath = storagePath;
37
+ this.staticFileURLPrefix = staticFileURLPrefix;
38
+ this.errorMessages = errorMessages || messages;
51
39
  }
52
40
 
53
41
  /**
54
- * Saves the image to storage (the file system)
55
- * - image is the express image object
56
- * - returns a promise which ultimately returns the full url to the uploaded image
42
+ * Saves the file to storage (the file system)
43
+ * - returns a promise which ultimately returns the full url to the uploaded file
57
44
  *
58
- * @param {StorageBase.Image} image
45
+ * @param {StorageBase.Image} file
59
46
  * @param {String} targetDir
60
47
  * @returns {Promise<String>}
61
48
  */
62
- async save(image, targetDir) {
49
+ async save(file, targetDir) {
63
50
  let targetFilename;
64
51
 
65
52
  // NOTE: the base implementation of `getTargetDir` returns the format this.storagePath/YYYY/MM
66
53
  targetDir = targetDir || this.getTargetDir(this.storagePath);
67
54
 
68
- const filename = await this.getUniqueFileName(image, targetDir);
55
+ const filename = await this.getUniqueFileName(file, targetDir);
69
56
 
70
57
  targetFilename = filename;
71
58
  await fs.mkdirs(targetDir);
72
59
 
73
- await fs.copy(image.path, targetFilename);
60
+ await fs.copy(file.path, targetFilename);
74
61
 
75
62
  // The src for the image must be in URI format, not a file system path, which in Windows uses \
76
63
  // For local file system storage can use relative path so add a slash
77
64
  const fullUrl = (
78
- urlUtils.urlJoin('/', urlUtils.getSubdir(),
79
- urlUtils.STATIC_IMAGE_URL_PREFIX,
65
+ urlUtils.urlJoin('/',
66
+ urlUtils.getSubdir(),
67
+ this.staticFileURLPrefix,
80
68
  path.relative(this.storagePath, targetFilename))
81
69
  ).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
82
70
 
@@ -103,7 +91,7 @@ class LocalFileStore extends StorageBase {
103
91
  * @returns {serveStaticContent}
104
92
  */
105
93
  serve() {
106
- const {storagePath} = this;
94
+ const {storagePath, errorMessages} = this;
107
95
 
108
96
  return function serveStaticContent(req, res, next) {
109
97
  const startedAtMoment = moment();
@@ -114,14 +102,14 @@ class LocalFileStore extends StorageBase {
114
102
  maxAge: constants.ONE_YEAR_MS,
115
103
  fallthrough: false,
116
104
  onEnd: () => {
117
- logging.info('LocalFileStorage.serve', req.path, moment().diff(startedAtMoment, 'ms') + 'ms');
105
+ logging.info('LocalStorageBase.serve', req.path, moment().diff(startedAtMoment, 'ms') + 'ms');
118
106
  }
119
107
  }
120
108
  )(req, res, (err) => {
121
109
  if (err) {
122
110
  if (err.statusCode === 404) {
123
111
  return next(new errors.NotFoundError({
124
- message: tpl(messages.imageNotFound),
112
+ message: tpl(errorMessages.notFound),
125
113
  code: 'STATIC_FILE_NOT_FOUND',
126
114
  property: err.path
127
115
  }));
@@ -152,8 +140,8 @@ class LocalFileStore extends StorageBase {
152
140
  }
153
141
 
154
142
  /**
155
- * Reads bytes from disk for a target image
156
- * - path of target image (without content path!)
143
+ * Reads bytes from disk for a target file
144
+ * - path of target file (without content path!)
157
145
  *
158
146
  * @param options
159
147
  */
@@ -171,7 +159,7 @@ class LocalFileStore extends StorageBase {
171
159
  if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
172
160
  return reject(new errors.NotFoundError({
173
161
  err: err,
174
- message: tpl(messages.imageNotFoundWithRef, {img: options.path})
162
+ message: tpl(this.errorMessages.notFoundWithRef, {file: options.path})
175
163
  }));
176
164
  }
177
165
 
@@ -185,7 +173,7 @@ class LocalFileStore extends StorageBase {
185
173
 
186
174
  return reject(new errors.GhostError({
187
175
  err: err,
188
- message: tpl(messages.cannotReadImage, {img: options.path})
176
+ message: tpl(this.errorMessages.cannotRead, {file: options.path})
189
177
  }));
190
178
  }
191
179
 
@@ -195,4 +183,4 @@ class LocalFileStore extends StorageBase {
195
183
  }
196
184
  }
197
185
 
198
- module.exports = LocalFileStore;
186
+ module.exports = LocalStorageBase;
@@ -1,7 +1,7 @@
1
1
  const adapterManager = require('../../services/adapter-manager');
2
2
 
3
3
  /**
4
- * @param {'images'|'videos'|'audios'} [feature] - name for the "feature" to enable through adapter, e.g.: images or videos storage
4
+ * @param {'images'|'media'|'files'} [feature] - name for the "feature" to enable through adapter, e.g.: images or media storage
5
5
  * @returns {Object} adapter instance
6
6
  */
7
7
  function getStorage(feature) {
@@ -12,7 +12,7 @@ const urlUtils = require('../../../shared/url-utils');
12
12
  * @description Takes a url or filepath and returns a filepath with is readable
13
13
  * for the local file storage.
14
14
  */
15
- exports.getLocalFileStoragePath = function getLocalFileStoragePath(imagePath) {
15
+ exports.getLocalImagesStoragePath = function getLocalImagesStoragePath(imagePath) {
16
16
  // The '/' in urlJoin is necessary to add the '/' to `content/images`, if no subdirectory is setup
17
17
  const urlRegExp = new RegExp(`^${urlUtils.urlJoin(
18
18
  urlUtils.urlFor('home', true),
@@ -43,7 +43,7 @@ exports.getLocalFileStoragePath = function getLocalFileStoragePath(imagePath) {
43
43
  */
44
44
 
45
45
  exports.isLocalImage = function isLocalImage(imagePath) {
46
- const localImagePath = this.getLocalFileStoragePath(imagePath);
46
+ const localImagePath = this.getLocalImagesStoragePath(imagePath);
47
47
 
48
48
  if (localImagePath !== imagePath) {
49
49
  return true;
@@ -105,6 +105,10 @@ module.exports = {
105
105
  return shared.pipeline(require('./images'), localUtils);
106
106
  },
107
107
 
108
+ get media() {
109
+ return shared.pipeline(require('./media'), localUtils);
110
+ },
111
+
108
112
  get tags() {
109
113
  return shared.pipeline(require('./tags'), localUtils);
110
114
  },
@@ -0,0 +1,22 @@
1
+ const storage = require('../../adapters/storage');
2
+
3
+ module.exports = {
4
+ docName: 'media',
5
+ upload: {
6
+ statusCode: 201,
7
+ permissions: false,
8
+ async query(frame) {
9
+ let thumbnail = null;
10
+ if (frame.files.thumbnail && frame.files.thumbnail[0]) {
11
+ thumbnail = await storage.getStorage('media').save(frame.files.thumbnail[0]);
12
+ }
13
+
14
+ const file = await storage.getStorage('media').save(frame.files.file[0]);
15
+
16
+ return {
17
+ filePath: file,
18
+ thumbnailPath: thumbnail
19
+ };
20
+ }
21
+ }
22
+ };
@@ -59,8 +59,7 @@ module.exports = {
59
59
  permissions: true,
60
60
  validation: {},
61
61
  async query(frame) {
62
- frame.options.withRelated = ['labels', 'stripeSubscriptions', 'stripeSubscriptions.customer', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct'];
63
- const page = await membersService.api.members.list(frame.options);
62
+ const page = await membersService.api.memberBREADService.browse(frame.options);
64
63
 
65
64
  return page;
66
65
  }
@@ -84,7 +83,7 @@ module.exports = {
84
83
  },
85
84
  permissions: true,
86
85
  async query(frame) {
87
- let member = await membersService.api.memberBREADService.read(frame.data, frame.options);
86
+ const member = await membersService.api.memberBREADService.read(frame.data, frame.options);
88
87
 
89
88
  if (!member) {
90
89
  throw new errors.NotFoundError({
@@ -115,72 +114,9 @@ module.exports = {
115
114
  },
116
115
  permissions: true,
117
116
  async query(frame) {
118
- let member;
119
- frame.options.withRelated = ['stripeSubscriptions', 'products', 'labels', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct'];
120
- if (!labsService.isSet('multipleProducts')) {
121
- delete frame.data.products;
122
- }
123
- try {
124
- if (!membersService.config.isStripeConnected()
125
- && (frame.data.members[0].stripe_customer_id || frame.data.members[0].comped)) {
126
- const property = frame.data.members[0].comped ? 'comped' : 'stripe_customer_id';
127
-
128
- throw new errors.ValidationError({
129
- message: tpl(messages.stripeNotConnected.message),
130
- context: tpl(messages.stripeNotConnected.context),
131
- help: tpl(messages.stripeNotConnected.help),
132
- property
133
- });
134
- }
135
-
136
- member = await membersService.api.members.create(frame.data.members[0], frame.options);
137
-
138
- if (frame.data.members[0].stripe_customer_id) {
139
- await membersService.api.members.linkStripeCustomer({
140
- customer_id: frame.data.members[0].stripe_customer_id,
141
- member_id: member.id
142
- }, frame.options);
143
- }
144
-
145
- if (!labsService.isSet('multipleProducts')) {
146
- if (frame.data.members[0].comped) {
147
- await membersService.api.members.setComplimentarySubscription(member);
148
- }
149
- }
150
-
151
- if (frame.options.send_email) {
152
- await membersService.api.sendEmailWithMagicLink({email: member.get('email'), requestedType: frame.options.email_type});
153
- }
154
-
155
- return member;
156
- } catch (error) {
157
- if (error.code && error.message.toLowerCase().indexOf('unique') !== -1) {
158
- throw new errors.ValidationError({
159
- message: tpl(messages.memberAlreadyExists.message),
160
- context: tpl(messages.memberAlreadyExists.context, {
161
- action: 'add'
162
- })
163
- });
164
- }
165
-
166
- // NOTE: failed to link Stripe customer/plan/subscription or have thrown custom Stripe connection error.
167
- // It's a bit ugly doing regex matching to detect errors, but it's the easiest way that works without
168
- // introducing additional logic/data format into current error handling
169
- const isStripeLinkingError = error.message && (error.message.match(/customer|plan|subscription/g));
170
- if (member && isStripeLinkingError) {
171
- if (error.message.indexOf('customer') && error.code === 'resource_missing') {
172
- error.message = `Member not imported. ${error.message}`;
173
- error.context = tpl(messages.stripeCustomerNotFound.context);
174
- error.help = tpl(messages.stripeCustomerNotFound.help);
175
- }
117
+ const member = await membersService.api.memberBREADService.add(frame.data.members[0], frame.options);
176
118
 
177
- await membersService.api.members.destroy({
178
- id: member.get('id')
179
- }, frame.options);
180
- }
181
-
182
- throw error;
183
- }
119
+ return member;
184
120
  }
185
121
  },
186
122
 
@@ -199,42 +135,9 @@ module.exports = {
199
135
  },
200
136
  permissions: true,
201
137
  async query(frame) {
202
- if (!labsService.isSet('multipleProducts')) {
203
- delete frame.data.products;
204
- }
205
- try {
206
- frame.options.withRelated = ['stripeSubscriptions', 'products', 'labels', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct'];
207
- const member = await membersService.api.members.update(frame.data.members[0], frame.options);
208
-
209
- const hasCompedSubscription = !!member.related('stripeSubscriptions').find(sub => sub.get('plan_nickname') === 'Complimentary' && sub.get('status') === 'active');
210
-
211
- if (!labsService.isSet('multipleProducts')) {
212
- if (typeof frame.data.members[0].comped === 'boolean') {
213
- if (frame.data.members[0].comped && !hasCompedSubscription) {
214
- await membersService.api.members.setComplimentarySubscription(member);
215
- } else if (!(frame.data.members[0].comped) && hasCompedSubscription) {
216
- await membersService.api.members.cancelComplimentarySubscription(member);
217
- }
218
-
219
- await member.load(['stripeSubscriptions', 'products', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct']);
220
- }
221
- }
222
-
223
- await member.load(['stripeSubscriptions.customer', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct']);
224
-
225
- return member;
226
- } catch (error) {
227
- if (error.code && error.message.toLowerCase().indexOf('unique') !== -1) {
228
- throw new errors.ValidationError({
229
- message: tpl(messages.memberAlreadyExists.message),
230
- context: tpl(messages.memberAlreadyExists.context, {
231
- action: 'edit'
232
- })
233
- });
234
- }
138
+ const member = await membersService.api.memberBREADService.edit(frame.data.members[0], frame.options);
235
139
 
236
- throw error;
237
- }
140
+ return member;
238
141
  }
239
142
  },
240
143
 
@@ -1,7 +1,4 @@
1
1
  const membersService = require('../../services/members');
2
- const config = require('../../../shared/config');
3
- const urlUtils = require('../../../shared/url-utils');
4
- const {BadRequestError} = require('@tryghost/errors');
5
2
 
6
3
  module.exports = {
7
4
  docName: 'members_stripe_connect',
@@ -18,13 +15,6 @@ module.exports = {
18
15
  }
19
16
  },
20
17
  query(frame) {
21
- const siteUrl = urlUtils.getSiteUrl();
22
- const productionMode = config.get('env') === 'production';
23
- const siteUrlUsingSSL = /^https/.test(siteUrl);
24
- const cannotConnectToStripe = productionMode && !siteUrlUsingSSL;
25
- if (cannotConnectToStripe) {
26
- throw new BadRequestError('Cannot connect to stripe unless site is using https://');
27
- }
28
18
  // This is something you have to do if you want to use the "framework" with access to the raw req/res
29
19
  frame.response = async function (req, res) {
30
20
  function setSessionProp(prop, val) {
@@ -1,6 +1,5 @@
1
1
  const path = require('path');
2
2
 
3
- const web = require('../../web');
4
3
  const redirects = require('../../services/redirects');
5
4
 
6
5
  module.exports = {
@@ -42,11 +41,7 @@ module.exports = {
42
41
  cacheInvalidate: true
43
42
  },
44
43
  query(frame) {
45
- return redirects.api.setFromFilePath(frame.file.path, frame.file.ext)
46
- .then(() => {
47
- // CASE: trigger that redirects are getting re-registered
48
- web.shared.middlewares.customRedirects.reload();
49
- });
44
+ return redirects.api.setFromFilePath(frame.file.path, frame.file.ext);
50
45
  }
51
46
  }
52
47
  };
@@ -102,6 +102,13 @@ const forceStatusFilter = (frame) => {
102
102
  }
103
103
  };
104
104
 
105
+ const transformPageVisibilityFilters = (frame) => {
106
+ if (frame.data.pages[0].visibility === 'filter' && frame.data.pages[0].visibility_filter) {
107
+ frame.data.pages[0].visibility = frame.data.pages[0].visibility_filter;
108
+ }
109
+ delete frame.data.pages[0].visibility_filter;
110
+ };
111
+
105
112
  module.exports = {
106
113
  browse(apiConfig, frame) {
107
114
  debug('browse');
@@ -180,6 +187,7 @@ module.exports = {
180
187
  });
181
188
  }
182
189
 
190
+ transformPageVisibilityFilters(frame);
183
191
  handlePostsMeta(frame);
184
192
  defaultFormat(frame);
185
193
  defaultRelations(frame);
@@ -8,6 +8,10 @@ module.exports = {
8
8
  return frame.response = {
9
9
  images: [{
10
10
  url: mapper.mapImage(path),
11
+ // NOTE: ref field is here to have reference point on the client
12
+ // for example when substituting existing images in the mobiledoc
13
+ // this field would serve as an identifier to find images to replace
14
+ // once the response is back. Think of it as ID on the client's side.
11
15
  ref: frame.data.ref || null
12
16
  }]
13
17
  };
@@ -85,6 +85,10 @@ module.exports = {
85
85
  return require('./images');
86
86
  },
87
87
 
88
+ get media() {
89
+ return require('./media');
90
+ },
91
+
88
92
  get tags() {
89
93
  return require('./tags');
90
94
  },
@@ -0,0 +1,28 @@
1
+ const config = require('../../../../../../shared/config');
2
+ const {STATIC_MEDIA_URL_PREFIX} = require('@tryghost/constants');
3
+
4
+ function getURL(urlPath) {
5
+ const media = new RegExp('^' + config.getSubdir() + '/' + STATIC_MEDIA_URL_PREFIX);
6
+ const absolute = media.test(urlPath) ? true : false;
7
+
8
+ if (absolute) {
9
+ // Remove the sub-directory from the URL because ghostConfig will add it back.
10
+ urlPath = urlPath.replace(new RegExp('^' + config.getSubdir()), '');
11
+ const baseUrl = config.getSiteUrl().replace(/\/$/, '');
12
+ urlPath = baseUrl + urlPath;
13
+ }
14
+
15
+ return urlPath;
16
+ }
17
+
18
+ module.exports = {
19
+ upload({filePath, thumbnailPath}, apiConfig, frame) {
20
+ return frame.response = {
21
+ media: [{
22
+ url: getURL(filePath),
23
+ thumbnail_url: getURL(thumbnailPath),
24
+ ref: frame.data.ref || null
25
+ }]
26
+ };
27
+ }
28
+ };
@@ -27,6 +27,10 @@ module.exports = {
27
27
  return require('./members');
28
28
  },
29
29
 
30
+ get media() {
31
+ return require('./media');
32
+ },
33
+
30
34
  get settings() {
31
35
  return require('./settings');
32
36
  },
@@ -0,0 +1,7 @@
1
+ const limitService = require('../../../../../services/limits');
2
+
3
+ module.exports = {
4
+ async upload(apiConfig, frame) {
5
+ await limitService.errorIfIsOverLimit('uploads', {currentCount: frame.file.size});
6
+ }
7
+ };
@@ -1,4 +1,3 @@
1
- const web = require('../../web');
2
1
  const redirects = require('../../services/redirects');
3
2
 
4
3
  module.exports = {
@@ -23,11 +22,7 @@ module.exports = {
23
22
  cacheInvalidate: true
24
23
  },
25
24
  query(frame) {
26
- return redirects.api.setFromFilePath(frame.file.path)
27
- .then(() => {
28
- // CASE: trigger that redirects are getting re-registered
29
- web.shared.middlewares.customRedirects.reload();
30
- });
25
+ return redirects.api.setFromFilePath(frame.file.path);
31
26
  }
32
27
  }
33
28
  };
@@ -8,6 +8,10 @@ module.exports = {
8
8
  return frame.response = {
9
9
  images: [{
10
10
  url: mapper.mapImage(path),
11
+ // NOTE: ref field is here to have reference point on the client
12
+ // for example when substituting existing images in the mobiledoc
13
+ // this field would serve as an identifier to find images to replace
14
+ // once the response is back. Think of it as ID on the client's side.
11
15
  ref: frame.data.ref || null
12
16
  }]
13
17
  };
@@ -154,7 +154,11 @@ module.exports = {
154
154
  }
155
155
 
156
156
  if (frame.options.send_email) {
157
- await membersService.api.sendEmailWithMagicLink({email: member.get('email'), requestedType: frame.options.email_type});
157
+ await membersService.api.sendEmailWithMagicLink({
158
+ email: member.get('email'), requestedType: frame.options.email_type, options: {
159
+ forceEmailType: true
160
+ }
161
+ });
158
162
  }
159
163
 
160
164
  return member;
@@ -1,6 +1,5 @@
1
1
  const path = require('path');
2
2
 
3
- const web = require('../../web');
4
3
  const redirects = require('../../services/redirects');
5
4
 
6
5
  module.exports = {
@@ -42,11 +41,7 @@ module.exports = {
42
41
  cacheInvalidate: true
43
42
  },
44
43
  query(frame) {
45
- return redirects.api.setFromFilePath(frame.file.path, frame.file.ext)
46
- .then(() => {
47
- // CASE: trigger that redirects are getting re-registered
48
- web.shared.middlewares.customRedirects.reload();
49
- });
44
+ return redirects.api.setFromFilePath(frame.file.path, frame.file.ext);
50
45
  }
51
46
  }
52
47
  };
@@ -8,6 +8,10 @@ module.exports = {
8
8
  return frame.response = {
9
9
  images: [{
10
10
  url: mapper.mapImage(path),
11
+ // NOTE: ref field is here to have reference point on the client
12
+ // for example when substituting existing images in the mobiledoc
13
+ // this field would serve as an identifier to find images to replace
14
+ // once the response is back. Think of it as ID on the client's side.
11
15
  ref: frame.data.ref || null
12
16
  }]
13
17
  };