ghost 5.79.6 → 5.80.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 (192) hide show
  1. package/components/tryghost-adapter-cache-memory-ttl-5.80.1.tgz +0 -0
  2. package/components/tryghost-adapter-cache-redis-5.80.1.tgz +0 -0
  3. package/components/tryghost-adapter-manager-5.80.1.tgz +0 -0
  4. package/components/tryghost-announcement-bar-settings-5.80.1.tgz +0 -0
  5. package/components/tryghost-api-framework-5.80.1.tgz +0 -0
  6. package/components/tryghost-api-version-compatibility-service-5.80.1.tgz +0 -0
  7. package/components/tryghost-audience-feedback-5.80.1.tgz +0 -0
  8. package/components/tryghost-bookshelf-repository-5.80.1.tgz +0 -0
  9. package/components/tryghost-bootstrap-socket-5.80.1.tgz +0 -0
  10. package/components/tryghost-collections-5.80.1.tgz +0 -0
  11. package/components/tryghost-constants-5.80.1.tgz +0 -0
  12. package/components/tryghost-custom-theme-settings-service-5.80.1.tgz +0 -0
  13. package/components/{tryghost-data-generator-5.79.6.tgz → tryghost-data-generator-5.80.1.tgz} +0 -0
  14. package/components/tryghost-domain-events-5.80.1.tgz +0 -0
  15. package/components/tryghost-donations-5.80.1.tgz +0 -0
  16. package/components/tryghost-dynamic-routing-events-5.80.1.tgz +0 -0
  17. package/components/{tryghost-email-addresses-5.79.6.tgz → tryghost-email-addresses-5.80.1.tgz} +0 -0
  18. package/components/tryghost-email-analytics-provider-mailgun-5.80.1.tgz +0 -0
  19. package/components/tryghost-email-analytics-service-5.80.1.tgz +0 -0
  20. package/components/tryghost-email-content-generator-5.80.1.tgz +0 -0
  21. package/components/tryghost-email-events-5.80.1.tgz +0 -0
  22. package/components/tryghost-email-service-5.80.1.tgz +0 -0
  23. package/components/tryghost-email-suppression-list-5.80.1.tgz +0 -0
  24. package/components/tryghost-express-dynamic-redirects-5.80.1.tgz +0 -0
  25. package/components/tryghost-external-media-inliner-5.80.1.tgz +0 -0
  26. package/components/tryghost-extract-api-key-5.80.1.tgz +0 -0
  27. package/components/tryghost-html-to-plaintext-5.80.1.tgz +0 -0
  28. package/components/tryghost-i18n-5.80.1.tgz +0 -0
  29. package/components/tryghost-importer-handler-content-files-5.80.1.tgz +0 -0
  30. package/components/tryghost-importer-revue-5.80.1.tgz +0 -0
  31. package/components/tryghost-in-memory-repository-5.80.1.tgz +0 -0
  32. package/components/tryghost-job-manager-5.80.1.tgz +0 -0
  33. package/components/tryghost-link-redirects-5.80.1.tgz +0 -0
  34. package/components/tryghost-link-replacer-5.80.1.tgz +0 -0
  35. package/components/tryghost-link-tracking-5.80.1.tgz +0 -0
  36. package/components/tryghost-magic-link-5.80.1.tgz +0 -0
  37. package/components/tryghost-mail-events-5.80.1.tgz +0 -0
  38. package/components/tryghost-mailgun-client-5.80.1.tgz +0 -0
  39. package/components/tryghost-member-attribution-5.80.1.tgz +0 -0
  40. package/components/tryghost-member-events-5.80.1.tgz +0 -0
  41. package/components/tryghost-members-api-5.80.1.tgz +0 -0
  42. package/components/tryghost-members-csv-5.80.1.tgz +0 -0
  43. package/components/tryghost-members-events-service-5.80.1.tgz +0 -0
  44. package/components/tryghost-members-importer-5.80.1.tgz +0 -0
  45. package/components/tryghost-members-offers-5.80.1.tgz +0 -0
  46. package/components/tryghost-members-payments-5.80.1.tgz +0 -0
  47. package/components/tryghost-members-ssr-5.80.1.tgz +0 -0
  48. package/components/{tryghost-members-stripe-service-5.79.6.tgz → tryghost-members-stripe-service-5.80.1.tgz} +0 -0
  49. package/components/tryghost-mentions-email-report-5.80.1.tgz +0 -0
  50. package/components/tryghost-milestones-5.80.1.tgz +0 -0
  51. package/components/tryghost-minifier-5.80.1.tgz +0 -0
  52. package/components/tryghost-model-to-domain-event-interceptor-5.80.1.tgz +0 -0
  53. package/components/tryghost-mw-api-version-mismatch-5.80.1.tgz +0 -0
  54. package/components/tryghost-mw-cache-control-5.80.1.tgz +0 -0
  55. package/components/tryghost-mw-error-handler-5.80.1.tgz +0 -0
  56. package/components/tryghost-mw-session-from-token-5.80.1.tgz +0 -0
  57. package/components/tryghost-mw-update-user-last-seen-5.80.1.tgz +0 -0
  58. package/components/tryghost-mw-version-match-5.80.1.tgz +0 -0
  59. package/components/tryghost-mw-vhost-5.80.1.tgz +0 -0
  60. package/components/tryghost-nql-filter-expansions-5.80.1.tgz +0 -0
  61. package/components/tryghost-oembed-service-5.80.1.tgz +0 -0
  62. package/components/tryghost-package-json-5.80.1.tgz +0 -0
  63. package/components/tryghost-post-events-5.80.1.tgz +0 -0
  64. package/components/tryghost-post-revisions-5.80.1.tgz +0 -0
  65. package/components/tryghost-posts-service-5.80.1.tgz +0 -0
  66. package/components/tryghost-recommendations-5.80.1.tgz +0 -0
  67. package/components/tryghost-referrers-5.80.1.tgz +0 -0
  68. package/components/tryghost-security-5.80.1.tgz +0 -0
  69. package/components/tryghost-session-service-5.80.1.tgz +0 -0
  70. package/components/tryghost-settings-path-manager-5.80.1.tgz +0 -0
  71. package/components/{tryghost-slack-notifications-5.79.6.tgz → tryghost-slack-notifications-5.80.1.tgz} +0 -0
  72. package/components/tryghost-staff-service-5.80.1.tgz +0 -0
  73. package/components/tryghost-stats-service-5.80.1.tgz +0 -0
  74. package/components/tryghost-tiers-5.80.1.tgz +0 -0
  75. package/components/tryghost-update-check-service-5.80.1.tgz +0 -0
  76. package/components/tryghost-verification-trigger-5.80.1.tgz +0 -0
  77. package/components/tryghost-version-notifications-data-service-5.80.1.tgz +0 -0
  78. package/components/tryghost-webmentions-5.80.1.tgz +0 -0
  79. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-b3cc8695.mjs → CodeEditorView-cd254a7a.mjs} +2 -2
  80. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
  81. package/core/built/admin/assets/admin-x-settings/{index-ad7f71fe.mjs → index-613b352d.mjs} +3 -3
  82. package/core/built/admin/assets/admin-x-settings/{index-426bc247.mjs → index-b52632c4.mjs} +2 -2
  83. package/core/built/admin/assets/admin-x-settings/{modals-199f6fb7.mjs → modals-99dbacc0.mjs} +118 -119
  84. package/core/built/admin/assets/{chunk.524.c31354988a1d949797bb.js → chunk.524.eecfc0cb2091306f132c.js} +5 -5
  85. package/core/built/admin/assets/{chunk.582.b04ca7f6177f10a3cc29.js → chunk.582.e04c74f002928b557602.js} +4 -4
  86. package/core/built/admin/assets/{ghost-07813a189c828a5814337fa4fbcb1e3f.js → ghost-ea7a51891dfca9b7a04aacfffe7dea4e.js} +4 -4
  87. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +2555 -2555
  88. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +79 -79
  89. package/core/built/admin/index.html +3 -3
  90. package/core/frontend/helpers/get.js +9 -3
  91. package/core/frontend/helpers/tpl/content-cta.hbs +3 -3
  92. package/core/frontend/src/cards/css/audio.css +11 -10
  93. package/core/frontend/src/cards/css/bookmark.css +4 -4
  94. package/core/frontend/src/cards/css/file.css +16 -13
  95. package/core/frontend/src/cards/css/header_v2.css +15 -14
  96. package/core/frontend/src/cards/css/product.css +8 -8
  97. package/core/frontend/src/cards/css/signup.css +18 -16
  98. package/core/frontend/src/cards/css/video.css +3 -3
  99. package/core/server/api/endpoints/collections.js +1 -10
  100. package/core/server/api/endpoints/comments-members.js +1 -0
  101. package/core/server/api/endpoints/labels.js +1 -3
  102. package/core/server/api/endpoints/pages.js +8 -2
  103. package/core/server/api/endpoints/posts.js +8 -5
  104. package/core/server/api/endpoints/schedules.js +7 -2
  105. package/core/server/api/endpoints/settings.js +3 -5
  106. package/core/server/api/endpoints/tags.js +1 -3
  107. package/core/server/api/endpoints/themes.js +2 -3
  108. package/core/server/api/endpoints/users.js +3 -1
  109. package/core/server/api/endpoints/utils/serializers/input/comments.js +14 -0
  110. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +4 -0
  111. package/core/server/models/post.js +6 -0
  112. package/core/server/services/comments/CommentsController.js +86 -16
  113. package/core/server/services/members/api.js +23 -5
  114. package/core/server/services/posts/post-scheduling-service.js +1 -1
  115. package/core/server/web/comments/routes.js +4 -3
  116. package/core/shared/config/defaults.json +1 -1
  117. package/package.json +148 -148
  118. package/yarn.lock +29 -29
  119. package/components/tryghost-adapter-cache-memory-ttl-5.79.6.tgz +0 -0
  120. package/components/tryghost-adapter-cache-redis-5.79.6.tgz +0 -0
  121. package/components/tryghost-adapter-manager-5.79.6.tgz +0 -0
  122. package/components/tryghost-announcement-bar-settings-5.79.6.tgz +0 -0
  123. package/components/tryghost-api-framework-5.79.6.tgz +0 -0
  124. package/components/tryghost-api-version-compatibility-service-5.79.6.tgz +0 -0
  125. package/components/tryghost-audience-feedback-5.79.6.tgz +0 -0
  126. package/components/tryghost-bookshelf-repository-5.79.6.tgz +0 -0
  127. package/components/tryghost-bootstrap-socket-5.79.6.tgz +0 -0
  128. package/components/tryghost-collections-5.79.6.tgz +0 -0
  129. package/components/tryghost-constants-5.79.6.tgz +0 -0
  130. package/components/tryghost-custom-theme-settings-service-5.79.6.tgz +0 -0
  131. package/components/tryghost-domain-events-5.79.6.tgz +0 -0
  132. package/components/tryghost-donations-5.79.6.tgz +0 -0
  133. package/components/tryghost-dynamic-routing-events-5.79.6.tgz +0 -0
  134. package/components/tryghost-email-analytics-provider-mailgun-5.79.6.tgz +0 -0
  135. package/components/tryghost-email-analytics-service-5.79.6.tgz +0 -0
  136. package/components/tryghost-email-content-generator-5.79.6.tgz +0 -0
  137. package/components/tryghost-email-events-5.79.6.tgz +0 -0
  138. package/components/tryghost-email-service-5.79.6.tgz +0 -0
  139. package/components/tryghost-email-suppression-list-5.79.6.tgz +0 -0
  140. package/components/tryghost-express-dynamic-redirects-5.79.6.tgz +0 -0
  141. package/components/tryghost-external-media-inliner-5.79.6.tgz +0 -0
  142. package/components/tryghost-extract-api-key-5.79.6.tgz +0 -0
  143. package/components/tryghost-html-to-plaintext-5.79.6.tgz +0 -0
  144. package/components/tryghost-i18n-5.79.6.tgz +0 -0
  145. package/components/tryghost-importer-handler-content-files-5.79.6.tgz +0 -0
  146. package/components/tryghost-importer-revue-5.79.6.tgz +0 -0
  147. package/components/tryghost-in-memory-repository-5.79.6.tgz +0 -0
  148. package/components/tryghost-job-manager-5.79.6.tgz +0 -0
  149. package/components/tryghost-link-redirects-5.79.6.tgz +0 -0
  150. package/components/tryghost-link-replacer-5.79.6.tgz +0 -0
  151. package/components/tryghost-link-tracking-5.79.6.tgz +0 -0
  152. package/components/tryghost-magic-link-5.79.6.tgz +0 -0
  153. package/components/tryghost-mail-events-5.79.6.tgz +0 -0
  154. package/components/tryghost-mailgun-client-5.79.6.tgz +0 -0
  155. package/components/tryghost-member-attribution-5.79.6.tgz +0 -0
  156. package/components/tryghost-member-events-5.79.6.tgz +0 -0
  157. package/components/tryghost-members-api-5.79.6.tgz +0 -0
  158. package/components/tryghost-members-csv-5.79.6.tgz +0 -0
  159. package/components/tryghost-members-events-service-5.79.6.tgz +0 -0
  160. package/components/tryghost-members-importer-5.79.6.tgz +0 -0
  161. package/components/tryghost-members-offers-5.79.6.tgz +0 -0
  162. package/components/tryghost-members-payments-5.79.6.tgz +0 -0
  163. package/components/tryghost-members-ssr-5.79.6.tgz +0 -0
  164. package/components/tryghost-mentions-email-report-5.79.6.tgz +0 -0
  165. package/components/tryghost-milestones-5.79.6.tgz +0 -0
  166. package/components/tryghost-minifier-5.79.6.tgz +0 -0
  167. package/components/tryghost-model-to-domain-event-interceptor-5.79.6.tgz +0 -0
  168. package/components/tryghost-mw-api-version-mismatch-5.79.6.tgz +0 -0
  169. package/components/tryghost-mw-cache-control-5.79.6.tgz +0 -0
  170. package/components/tryghost-mw-error-handler-5.79.6.tgz +0 -0
  171. package/components/tryghost-mw-session-from-token-5.79.6.tgz +0 -0
  172. package/components/tryghost-mw-update-user-last-seen-5.79.6.tgz +0 -0
  173. package/components/tryghost-mw-version-match-5.79.6.tgz +0 -0
  174. package/components/tryghost-mw-vhost-5.79.6.tgz +0 -0
  175. package/components/tryghost-nql-filter-expansions-5.79.6.tgz +0 -0
  176. package/components/tryghost-oembed-service-5.79.6.tgz +0 -0
  177. package/components/tryghost-package-json-5.79.6.tgz +0 -0
  178. package/components/tryghost-post-events-5.79.6.tgz +0 -0
  179. package/components/tryghost-post-revisions-5.79.6.tgz +0 -0
  180. package/components/tryghost-posts-service-5.79.6.tgz +0 -0
  181. package/components/tryghost-recommendations-5.79.6.tgz +0 -0
  182. package/components/tryghost-referrers-5.79.6.tgz +0 -0
  183. package/components/tryghost-security-5.79.6.tgz +0 -0
  184. package/components/tryghost-session-service-5.79.6.tgz +0 -0
  185. package/components/tryghost-settings-path-manager-5.79.6.tgz +0 -0
  186. package/components/tryghost-staff-service-5.79.6.tgz +0 -0
  187. package/components/tryghost-stats-service-5.79.6.tgz +0 -0
  188. package/components/tryghost-tiers-5.79.6.tgz +0 -0
  189. package/components/tryghost-update-check-service-5.79.6.tgz +0 -0
  190. package/components/tryghost-verification-trigger-5.79.6.tgz +0 -0
  191. package/components/tryghost-version-notifications-data-service-5.79.6.tgz +0 -0
  192. package/components/tryghost-webmentions-5.79.6.tgz +0 -0
@@ -122,7 +122,7 @@ module.exports = {
122
122
 
123
123
  edit: {
124
124
  headers: {
125
- cacheInvalidate: true
125
+ cacheInvalidate: false
126
126
  },
127
127
  permissions: {
128
128
  unsafeAttrsObject(frame) {
@@ -134,10 +134,8 @@ module.exports = {
134
134
 
135
135
  let result = await settingsBREADService.edit(frame.data.settings, frame.options, stripeConnectData);
136
136
 
137
- if (_.isEmpty(result)) {
138
- this.headers.cacheInvalidate = false;
139
- } else {
140
- this.headers.cacheInvalidate = true;
137
+ if (!_.isEmpty(result)) {
138
+ frame.setHeader('X-Cache-Invalidate', '/*');
141
139
  }
142
140
 
143
141
  // We need to return all settings here, because we have calculated settings that might change
@@ -124,9 +124,7 @@ module.exports = {
124
124
  }
125
125
 
126
126
  if (model.wasChanged()) {
127
- this.headers.cacheInvalidate = true;
128
- } else {
129
- this.headers.cacheInvalidate = false;
127
+ frame.setHeader('X-Cache-Invalidate', '/*');
130
128
  }
131
129
 
132
130
  return model;
@@ -91,7 +91,7 @@ module.exports = {
91
91
  const {theme, themeOverridden} = await themeService.api.installFromGithub(frame.options.ref);
92
92
 
93
93
  if (themeOverridden) {
94
- this.headers.cacheInvalidate = true;
94
+ frame.setHeader('X-Cache-Invalidate', '/*');
95
95
  }
96
96
 
97
97
  events.emit('theme.uploaded', {name: theme.name});
@@ -125,8 +125,7 @@ module.exports = {
125
125
  return themeService.api.setFromZip(zip)
126
126
  .then(({theme, themeOverridden}) => {
127
127
  if (themeOverridden) {
128
- // CASE: clear cache
129
- this.headers.cacheInvalidate = true;
128
+ frame.setHeader('X-Cache-Invalidate', '/*');
130
129
  }
131
130
  events.emit('theme.uploaded', {name: theme.name});
132
131
  return theme;
@@ -170,7 +170,9 @@ module.exports = {
170
170
  }));
171
171
  }
172
172
 
173
- this.headers.cacheInvalidate = shouldInvalidateCacheAfterChange(model);
173
+ if (shouldInvalidateCacheAfterChange(model)) {
174
+ frame.setHeader('X-Cache-Invalidate', '/*');
175
+ }
174
176
 
175
177
  return model;
176
178
  });
@@ -14,5 +14,19 @@ module.exports = {
14
14
  }
15
15
  return relation;
16
16
  });
17
+ },
18
+
19
+ browse(apiConfig, frame) {
20
+ // for top-level comments we show newest comments first and paginate to older
21
+ if (!frame.options.order) {
22
+ frame.options.order = 'created_at DESC, id DESC';
23
+ }
24
+ },
25
+
26
+ replies(apiConfig, frame) {
27
+ // for replies we show the oldest comments first and paginate to newer
28
+ if (!frame.options.order) {
29
+ frame.options.order = 'created_at ASC, id ASC';
30
+ }
17
31
  }
18
32
  };
@@ -66,6 +66,10 @@ module.exports = async (model, frame, options = {}) => {
66
66
  jsonModel.tiers = tiersData ? tiersData.filter(t => t.type === 'paid') : [];
67
67
  }
68
68
 
69
+ if (jsonModel.visibility === 'tiers' && Array.isArray(jsonModel.tiers)) {
70
+ jsonModel.tiers = jsonModel.tiers.filter(t => t.type === 'paid');
71
+ }
72
+
69
73
  if (!['members', 'public', 'paid', 'tiers'].includes(jsonModel.visibility)) {
70
74
  const tiers = await postsService.getProductsFromVisibilityFilter(jsonModel.visibility);
71
75
 
@@ -1004,6 +1004,12 @@ Post = ghostBookshelf.Model.extend({
1004
1004
  this.set('tiers', this.get('tiers').map(t => ({
1005
1005
  id: t.id
1006
1006
  })));
1007
+
1008
+ // Don't associate the free tier with the post
1009
+ const freeTier = await ghostBookshelf.model('Product').findOne({type: 'free'}, {require: false, transacting: options.transacting ?? undefined});
1010
+ if (freeTier) {
1011
+ this.set('tiers', this.get('tiers').filter(t => t.id !== freeTier.id));
1012
+ }
1007
1013
  }
1008
1014
 
1009
1015
  if (labs.isSet('collectionsCard') && this.get('type') === 'post' && (newStatus === 'published' || olderStatus === 'published')) {
@@ -35,6 +35,22 @@ module.exports = class CommentsController {
35
35
  * @param {Frame} frame
36
36
  */
37
37
  async browse(frame) {
38
+ if (frame.options.post_id) {
39
+ if (frame.options.filter) {
40
+ frame.options.mongoTransformer = function (query) {
41
+ return {
42
+ $and: [
43
+ {
44
+ post_id: frame.options.post_id
45
+ },
46
+ query
47
+ ]
48
+ };
49
+ };
50
+ } else {
51
+ frame.options.filter = `post_id:${frame.options.post_id}`;
52
+ }
53
+ }
38
54
  return this.service.getComments(frame.options);
39
55
  }
40
56
 
@@ -58,20 +74,33 @@ module.exports = class CommentsController {
58
74
  async edit(frame) {
59
75
  this.#checkMember(frame);
60
76
 
77
+ let result;
61
78
  if (frame.data.comments[0].status === 'deleted') {
62
- return await this.service.deleteComment(
79
+ result = await this.service.deleteComment(
63
80
  frame.options.id,
64
81
  frame?.options?.context?.member?.id,
65
82
  frame.options
66
83
  );
84
+ } else {
85
+ result = await this.service.editCommentContent(
86
+ frame.options.id,
87
+ frame?.options?.context?.member?.id,
88
+ frame.data.comments[0].html,
89
+ frame.options
90
+ );
67
91
  }
68
92
 
69
- return await this.service.editCommentContent(
70
- frame.options.id,
71
- frame?.options?.context?.member?.id,
72
- frame.data.comments[0].html,
73
- frame.options
74
- );
93
+ if (result) {
94
+ const postId = result.get('post_id');
95
+ const parentId = result.get('parent_id');
96
+ const pathsToInvalidate = [
97
+ postId ? `/api/members/comments/post/${postId}/` : null,
98
+ parentId ? `/api/members/comments/${parentId}/replies/` : null
99
+ ].filter(path => path !== null);
100
+ frame.setHeader('X-Cache-Invalidate', pathsToInvalidate.join(', '));
101
+ }
102
+
103
+ return result;
75
104
  }
76
105
 
77
106
  /**
@@ -81,21 +110,34 @@ module.exports = class CommentsController {
81
110
  this.#checkMember(frame);
82
111
  const data = frame.data.comments[0];
83
112
 
113
+ let result;
84
114
  if (data.parent_id) {
85
- return await this.service.replyToComment(
115
+ result = await this.service.replyToComment(
86
116
  data.parent_id,
87
117
  frame.options.context.member.id,
88
118
  data.html,
89
119
  frame.options
90
120
  );
121
+ } else {
122
+ result = await this.service.commentOnPost(
123
+ data.post_id,
124
+ frame.options.context.member.id,
125
+ data.html,
126
+ frame.options
127
+ );
91
128
  }
92
129
 
93
- return await this.service.commentOnPost(
94
- data.post_id,
95
- frame.options.context.member.id,
96
- data.html,
97
- frame.options
98
- );
130
+ if (result) {
131
+ const postId = result.get('post_id');
132
+ const parentId = result.get('parent_id');
133
+ const pathsToInvalidate = [
134
+ postId ? `/api/members/comments/post/${postId}/` : null,
135
+ parentId ? `/api/members/comments/${parentId}/replies/` : null
136
+ ].filter(path => path !== null);
137
+ frame.setHeader('X-Cache-Invalidate', pathsToInvalidate.join(', '));
138
+ }
139
+
140
+ return result;
99
141
  }
100
142
 
101
143
  async destroy() {
@@ -120,11 +162,25 @@ module.exports = class CommentsController {
120
162
  async like(frame) {
121
163
  this.#checkMember(frame);
122
164
 
123
- return await this.service.likeComment(
165
+ const result = await this.service.likeComment(
124
166
  frame.options.id,
125
167
  frame.options?.context?.member,
126
168
  frame.options
127
169
  );
170
+
171
+ const comment = await this.service.getCommentByID(frame.options.id);
172
+
173
+ if (comment) {
174
+ const postId = comment.get('post_id');
175
+ const parentId = comment.get('parent_id');
176
+ const pathsToInvalidate = [
177
+ postId ? `/api/members/comments/post/${postId}/` : null,
178
+ parentId ? `/api/members/comments/${parentId}/replies/` : null
179
+ ].filter(path => path !== null);
180
+ frame.setHeader('X-Cache-Invalidate', pathsToInvalidate.join(', '));
181
+ }
182
+
183
+ return result;
128
184
  }
129
185
 
130
186
  /**
@@ -133,11 +189,25 @@ module.exports = class CommentsController {
133
189
  async unlike(frame) {
134
190
  this.#checkMember(frame);
135
191
 
136
- return await this.service.unlikeComment(
192
+ const result = await this.service.unlikeComment(
137
193
  frame.options.id,
138
194
  frame.options?.context?.member,
139
195
  frame.options
140
196
  );
197
+
198
+ const comment = await this.service.getCommentByID(frame.options.id);
199
+
200
+ if (comment) {
201
+ const postId = comment.get('post_id');
202
+ const parentId = comment.get('parent_id');
203
+ const pathsToInvalidate = [
204
+ postId ? `/api/members/comments/post/${postId}/` : null,
205
+ parentId ? `/api/members/comments/${parentId}/replies/` : null
206
+ ].filter(path => path !== null);
207
+ frame.setHeader('X-Cache-Invalidate', pathsToInvalidate.join(', '));
208
+ }
209
+
210
+ return result;
141
211
  }
142
212
 
143
213
  /**
@@ -27,6 +27,24 @@ const ghostMailer = new mail.GhostMailer();
27
27
 
28
28
  module.exports = createApiInstance;
29
29
 
30
+ function trimLeadingWhitespace(strings, ...values) {
31
+ // Interweave the strings with the
32
+ // substitution vars first.
33
+ let output = '';
34
+ for (let i = 0; i < values.length; i++) {
35
+ output += strings[i] + values[i];
36
+ }
37
+ output += strings[values.length];
38
+
39
+ // Split on newlines.
40
+ const lines = output.split(/(?:\r\n|\n|\r)/);
41
+
42
+ // Rip out the leading whitespace on each line.
43
+ return lines.map((line) => {
44
+ return line.trimStart();
45
+ }).join('\n').trim();
46
+ }
47
+
30
48
  function createApiInstance(config) {
31
49
  const membersApiInstance = MembersApi({
32
50
  tokenConfig: config.getTokenConfig(),
@@ -75,7 +93,7 @@ function createApiInstance(config) {
75
93
  const siteTitle = settingsCache.get('title');
76
94
  switch (type) {
77
95
  case 'subscribe':
78
- return `
96
+ return trimLeadingWhitespace`
79
97
  ${t(`Hey there,`)}
80
98
 
81
99
  ${t('You\'re one tap away from subscribing to {{siteTitle}} — please confirm your email address with this link:', {siteTitle, interpolation: {escapeValue: false}})}
@@ -92,7 +110,7 @@ function createApiInstance(config) {
92
110
  ${t('If you did not make this request, you can simply delete this message.')} ${t('You will not be subscribed.')}
93
111
  `;
94
112
  case 'signup':
95
- return `
113
+ return trimLeadingWhitespace`
96
114
  ${t(`Hey there,`)}
97
115
 
98
116
  ${t('Tap the link below to complete the signup process for {{siteTitle}}, and be automatically signed in:', {siteTitle, interpolation: {escapeValue: false}})}
@@ -109,7 +127,7 @@ function createApiInstance(config) {
109
127
  ${t('If you did not make this request, you can simply delete this message.')} ${t('You will not be signed up, and no account will be created for you.')}
110
128
  `;
111
129
  case 'signup-paid':
112
- return `
130
+ return trimLeadingWhitespace`
113
131
  ${t(`Hey there,`)}
114
132
 
115
133
  ${t('Thank you for subscribing to {{siteTitle}}. Tap the link below to be automatically signed in:', {siteTitle, interpolation: {escapeValue: false}})}
@@ -126,7 +144,7 @@ function createApiInstance(config) {
126
144
  ${t('Thank you for subscribing to {{siteTitle}}!', {siteTitle, interpolation: {escapeValue: false}})}
127
145
  `;
128
146
  case 'updateEmail':
129
- return `
147
+ return trimLeadingWhitespace`
130
148
  ${t(`Hey there,`)}
131
149
 
132
150
  ${t('Please confirm your email address with this link:')}
@@ -142,7 +160,7 @@ function createApiInstance(config) {
142
160
  `;
143
161
  case 'signin':
144
162
  default:
145
- return `
163
+ return trimLeadingWhitespace`
146
164
  ${t(`Hey there,`)}
147
165
 
148
166
  ${t('Welcome back! Use this link to securely sign in to your {{siteTitle}} account:', {siteTitle, interpolation: {escapeValue: false}})}
@@ -55,7 +55,7 @@ class PostSchedulingService {
55
55
  *
56
56
  * @param {Object} scheduledResource post or page resource object
57
57
  * @param {Object} preScheduledResource post or page resource object in state before publishing
58
- * @returns {Boolean|Object}
58
+ * @returns {boolean|{value: string}}
59
59
  */
60
60
  handleCacheInvalidation(scheduledResource, preScheduledResource) {
61
61
  if (
@@ -11,16 +11,17 @@ module.exports = function apiRoutes() {
11
11
  const router = express.Router('comment api');
12
12
  router.use(bodyParser.json({limit: '50mb'}));
13
13
 
14
- // Global handling for member session, ensures a member is logged in to the frontend
15
- router.use(membersService.middleware.loadMemberSession);
16
-
17
14
  const countsCache = shared.middleware.cacheControl(
18
15
  'public',
19
16
  {maxAge: config.get('caching:commentsCountAPI:maxAge')}
20
17
  );
21
18
  router.get('/counts', countsCache, http(api.commentsMembers.counts));
22
19
 
20
+ // Authenticated Routes
21
+ router.use(membersService.middleware.loadMemberSession);
22
+
23
23
  router.get('/', http(api.commentsMembers.browse));
24
+ router.get('/post/:post_id', http(api.commentsMembers.browse));
24
25
  router.get('/:id', http(api.commentsMembers.read));
25
26
  router.post('/', http(api.commentsMembers.add));
26
27
  router.put('/:id', http(api.commentsMembers.edit));
@@ -195,7 +195,7 @@
195
195
  },
196
196
  "comments": {
197
197
  "url": "https://cdn.jsdelivr.net/ghost/comments-ui@~{version}/umd/comments-ui.min.js",
198
- "version": "0.13"
198
+ "version": "0.16"
199
199
  },
200
200
  "signupForm": {
201
201
  "url": "https://cdn.jsdelivr.net/ghost/signup-form@~{version}/umd/signup-form.min.js",