ghost 5.59.4 → 5.61.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 (228) hide show
  1. package/components/tryghost-adapter-cache-memory-ttl-5.61.0.tgz +0 -0
  2. package/components/tryghost-adapter-cache-redis-5.61.0.tgz +0 -0
  3. package/components/tryghost-adapter-manager-5.61.0.tgz +0 -0
  4. package/components/tryghost-announcement-bar-settings-5.61.0.tgz +0 -0
  5. package/components/tryghost-api-framework-5.61.0.tgz +0 -0
  6. package/components/tryghost-api-version-compatibility-service-5.61.0.tgz +0 -0
  7. package/components/tryghost-audience-feedback-5.61.0.tgz +0 -0
  8. package/components/tryghost-bookshelf-repository-5.61.0.tgz +0 -0
  9. package/components/tryghost-bootstrap-socket-5.61.0.tgz +0 -0
  10. package/components/tryghost-collections-5.61.0.tgz +0 -0
  11. package/components/tryghost-constants-5.61.0.tgz +0 -0
  12. package/components/tryghost-custom-theme-settings-service-5.61.0.tgz +0 -0
  13. package/components/{tryghost-data-generator-5.59.4.tgz → tryghost-data-generator-5.61.0.tgz} +0 -0
  14. package/components/tryghost-domain-events-5.61.0.tgz +0 -0
  15. package/components/tryghost-donations-5.61.0.tgz +0 -0
  16. package/components/tryghost-dynamic-routing-events-5.61.0.tgz +0 -0
  17. package/components/tryghost-email-analytics-provider-mailgun-5.61.0.tgz +0 -0
  18. package/components/tryghost-email-analytics-service-5.61.0.tgz +0 -0
  19. package/components/tryghost-email-content-generator-5.61.0.tgz +0 -0
  20. package/components/tryghost-email-events-5.61.0.tgz +0 -0
  21. package/components/tryghost-email-service-5.61.0.tgz +0 -0
  22. package/components/tryghost-email-suppression-list-5.61.0.tgz +0 -0
  23. package/components/tryghost-event-aware-cache-wrapper-5.61.0.tgz +0 -0
  24. package/components/tryghost-express-dynamic-redirects-5.61.0.tgz +0 -0
  25. package/components/tryghost-external-media-inliner-5.61.0.tgz +0 -0
  26. package/components/tryghost-extract-api-key-5.61.0.tgz +0 -0
  27. package/components/tryghost-html-to-plaintext-5.61.0.tgz +0 -0
  28. package/components/tryghost-i18n-5.61.0.tgz +0 -0
  29. package/components/tryghost-importer-handler-content-files-5.61.0.tgz +0 -0
  30. package/components/tryghost-importer-revue-5.61.0.tgz +0 -0
  31. package/components/tryghost-in-memory-repository-5.61.0.tgz +0 -0
  32. package/components/tryghost-job-manager-5.61.0.tgz +0 -0
  33. package/components/tryghost-link-redirects-5.61.0.tgz +0 -0
  34. package/components/tryghost-link-replacer-5.61.0.tgz +0 -0
  35. package/components/tryghost-link-tracking-5.61.0.tgz +0 -0
  36. package/components/tryghost-magic-link-5.61.0.tgz +0 -0
  37. package/components/tryghost-mail-events-5.61.0.tgz +0 -0
  38. package/components/tryghost-mailgun-client-5.61.0.tgz +0 -0
  39. package/components/tryghost-member-attribution-5.61.0.tgz +0 -0
  40. package/components/tryghost-member-events-5.61.0.tgz +0 -0
  41. package/components/tryghost-members-api-5.61.0.tgz +0 -0
  42. package/components/tryghost-members-csv-5.61.0.tgz +0 -0
  43. package/components/{tryghost-members-events-service-5.59.4.tgz → tryghost-members-events-service-5.61.0.tgz} +0 -0
  44. package/components/tryghost-members-importer-5.61.0.tgz +0 -0
  45. package/components/tryghost-members-offers-5.61.0.tgz +0 -0
  46. package/components/tryghost-members-payments-5.61.0.tgz +0 -0
  47. package/components/tryghost-members-ssr-5.61.0.tgz +0 -0
  48. package/components/{tryghost-members-stripe-service-5.59.4.tgz → tryghost-members-stripe-service-5.61.0.tgz} +0 -0
  49. package/components/tryghost-mentions-email-report-5.61.0.tgz +0 -0
  50. package/components/tryghost-milestones-5.61.0.tgz +0 -0
  51. package/components/tryghost-minifier-5.61.0.tgz +0 -0
  52. package/components/tryghost-model-to-domain-event-interceptor-5.61.0.tgz +0 -0
  53. package/components/tryghost-mw-api-version-mismatch-5.61.0.tgz +0 -0
  54. package/components/tryghost-mw-cache-control-5.61.0.tgz +0 -0
  55. package/components/tryghost-mw-error-handler-5.61.0.tgz +0 -0
  56. package/components/tryghost-mw-session-from-token-5.61.0.tgz +0 -0
  57. package/components/tryghost-mw-update-user-last-seen-5.61.0.tgz +0 -0
  58. package/components/tryghost-mw-version-match-5.61.0.tgz +0 -0
  59. package/components/tryghost-mw-vhost-5.61.0.tgz +0 -0
  60. package/components/tryghost-nql-filter-expansions-5.61.0.tgz +0 -0
  61. package/components/tryghost-oembed-service-5.61.0.tgz +0 -0
  62. package/components/tryghost-package-json-5.61.0.tgz +0 -0
  63. package/components/tryghost-post-events-5.61.0.tgz +0 -0
  64. package/components/tryghost-post-revisions-5.61.0.tgz +0 -0
  65. package/components/tryghost-posts-service-5.61.0.tgz +0 -0
  66. package/components/tryghost-recommendations-5.61.0.tgz +0 -0
  67. package/components/tryghost-referrers-5.61.0.tgz +0 -0
  68. package/components/tryghost-security-5.61.0.tgz +0 -0
  69. package/components/tryghost-session-service-5.61.0.tgz +0 -0
  70. package/components/tryghost-settings-path-manager-5.61.0.tgz +0 -0
  71. package/components/tryghost-slack-notifications-5.61.0.tgz +0 -0
  72. package/components/tryghost-staff-service-5.61.0.tgz +0 -0
  73. package/components/tryghost-stats-service-5.61.0.tgz +0 -0
  74. package/components/{tryghost-tiers-5.59.4.tgz → tryghost-tiers-5.61.0.tgz} +0 -0
  75. package/components/tryghost-update-check-service-5.61.0.tgz +0 -0
  76. package/components/tryghost-verification-trigger-5.61.0.tgz +0 -0
  77. package/components/tryghost-version-notifications-data-service-5.61.0.tgz +0 -0
  78. package/components/tryghost-webmentions-5.61.0.tgz +0 -0
  79. package/content/themes/casper/assets/built/casper.js +1 -1
  80. package/content/themes/casper/assets/built/casper.js.map +1 -1
  81. package/content/themes/casper/assets/built/screen.css +1 -1
  82. package/content/themes/casper/assets/built/screen.css.map +1 -1
  83. package/content/themes/casper/assets/css/screen.css +503 -0
  84. package/content/themes/casper/assets/images/default-skin.png +0 -0
  85. package/content/themes/casper/assets/images/default-skin.svg +1 -0
  86. package/content/themes/casper/assets/images/preloader.gif +0 -0
  87. package/content/themes/casper/assets/js/lib/photoswipe-ui-default.min.js +4 -0
  88. package/content/themes/casper/assets/js/lib/photoswipe.min.js +4 -0
  89. package/content/themes/casper/assets/js/lightbox.js +109 -0
  90. package/content/themes/casper/default.hbs +3 -0
  91. package/content/themes/casper/package.json +1 -1
  92. package/content/themes/casper/partials/lightbox.hbs +41 -0
  93. package/core/boot.js +6 -1
  94. package/core/built/admin/assets/{chunk.143.2aa9967c47c24fab0bf5.js → chunk.143.b297dc36977ec0b8aa47.js} +5 -5
  95. package/core/built/admin/assets/{chunk.178.f4ba7628b006dda9bce3.js → chunk.178.308d80b60fce060ca358.js} +4 -4
  96. package/core/built/admin/assets/{ghost-24496fd514838a397808c3a992e5818c.js → ghost-4416ac3815c54eac76c2590d1154a628.js} +51 -54
  97. package/core/built/admin/assets/ghost-8a4e981c272f793157133814ca7c7e84.css +1 -0
  98. package/core/built/admin/assets/ghost-dark-084169b0e968ef763dfbbf63b253e0c6.css +1 -0
  99. package/core/built/admin/assets/{vendor-fff5b0b3c122441beb3170947ae27b9d.js → vendor-3631184082d609038638c1e169a002e7.js} +666 -666
  100. package/core/built/admin/index.html +6 -6
  101. package/core/frontend/helpers/comments.js +3 -3
  102. package/core/frontend/src/cards/css/header_v2.css +10 -9
  103. package/core/frontend/src/cards/css/signup.css +13 -12
  104. package/core/frontend/utils/frontend-apps.js +3 -0
  105. package/core/frontend/web/middleware/cors.js +1 -1
  106. package/core/frontend/web/middleware/handle-image-sizes.js +1 -1
  107. package/core/frontend/web/middleware/serve-public-file.js +4 -4
  108. package/core/frontend/web/site.js +5 -2
  109. package/core/server/api/endpoints/index.js +8 -0
  110. package/core/server/api/endpoints/recommendations-public.js +24 -0
  111. package/core/server/api/endpoints/recommendations.js +71 -0
  112. package/core/server/api/endpoints/utils/serializers/input/settings.js +2 -1
  113. package/core/server/data/exporter/table-lists.js +2 -1
  114. package/core/server/data/migrations/versions/5.61/2023-08-29-10-17-add-recommendations-crud-permissions.js +50 -0
  115. package/core/server/data/migrations/versions/5.61/2023-08-29-11-39-10-add-recommendations-table.js +18 -0
  116. package/core/server/data/migrations/versions/5.61/2023-08-30-07-37-04-add-recommendations-enabled-settings.js +8 -0
  117. package/core/server/data/schema/default-settings/default-settings.json +10 -0
  118. package/core/server/data/schema/fixtures/fixtures.json +35 -5
  119. package/core/server/data/schema/schema.js +12 -0
  120. package/core/server/models/base/bookshelf.js +1 -1
  121. package/core/server/models/base/plugins/raw-knex.js +4 -3
  122. package/core/server/models/collection.js +33 -0
  123. package/core/server/models/post.js +1 -1
  124. package/core/server/models/recommendation.js +10 -0
  125. package/core/server/services/api-version-compatibility/index.js +2 -2
  126. package/core/server/services/api-version-compatibility/mw-version-rewrites.js +1 -1
  127. package/core/server/services/auth/api-key/admin.js +3 -3
  128. package/core/server/services/collections/BookshelfCollectionsRepository.js +27 -14
  129. package/core/server/services/collections/service.js +5 -3
  130. package/core/server/services/members/middleware.js +10 -10
  131. package/core/server/services/mentions/WebmentionMetadata.js +2 -1
  132. package/core/server/services/mentions/service.js +4 -0
  133. package/core/server/services/{collections/intercept-events.js → model-to-domain-event-interceptor/index.js} +9 -1
  134. package/core/server/services/recommendations/RecommendationServiceWrapper.js +57 -0
  135. package/core/server/services/recommendations/index.js +3 -0
  136. package/core/server/web/admin/app.js +2 -2
  137. package/core/server/web/api/endpoints/admin/middleware.js +1 -1
  138. package/core/server/web/api/endpoints/admin/routes.js +9 -3
  139. package/core/server/web/api/endpoints/content/routes.js +3 -0
  140. package/core/server/web/api/middleware/cors.js +1 -1
  141. package/core/server/web/api/middleware/upload.js +2 -2
  142. package/core/server/web/parent/middleware/request-id.js +1 -1
  143. package/core/server/web/shared/middleware/uncapitalise.js +1 -1
  144. package/core/server/web/shared/middleware/url-redirects.js +3 -3
  145. package/core/server/web/well-known.js +1 -1
  146. package/core/shared/config/defaults.json +1 -2
  147. package/core/shared/labs.js +2 -1
  148. package/core/shared/settings-cache/public.js +2 -1
  149. package/index.js +5 -0
  150. package/newrelic.js +53 -0
  151. package/package.json +164 -161
  152. package/yarn.lock +2568 -1183
  153. package/components/tryghost-adapter-cache-memory-ttl-5.59.4.tgz +0 -0
  154. package/components/tryghost-adapter-cache-redis-5.59.4.tgz +0 -0
  155. package/components/tryghost-adapter-manager-5.59.4.tgz +0 -0
  156. package/components/tryghost-announcement-bar-settings-5.59.4.tgz +0 -0
  157. package/components/tryghost-api-framework-5.59.4.tgz +0 -0
  158. package/components/tryghost-api-version-compatibility-service-5.59.4.tgz +0 -0
  159. package/components/tryghost-audience-feedback-5.59.4.tgz +0 -0
  160. package/components/tryghost-bootstrap-socket-5.59.4.tgz +0 -0
  161. package/components/tryghost-collections-5.59.4.tgz +0 -0
  162. package/components/tryghost-constants-5.59.4.tgz +0 -0
  163. package/components/tryghost-custom-theme-settings-service-5.59.4.tgz +0 -0
  164. package/components/tryghost-domain-events-5.59.4.tgz +0 -0
  165. package/components/tryghost-donations-5.59.4.tgz +0 -0
  166. package/components/tryghost-dynamic-routing-events-5.59.4.tgz +0 -0
  167. package/components/tryghost-email-analytics-provider-mailgun-5.59.4.tgz +0 -0
  168. package/components/tryghost-email-analytics-service-5.59.4.tgz +0 -0
  169. package/components/tryghost-email-content-generator-5.59.4.tgz +0 -0
  170. package/components/tryghost-email-events-5.59.4.tgz +0 -0
  171. package/components/tryghost-email-service-5.59.4.tgz +0 -0
  172. package/components/tryghost-email-suppression-list-5.59.4.tgz +0 -0
  173. package/components/tryghost-event-aware-cache-wrapper-5.59.4.tgz +0 -0
  174. package/components/tryghost-express-dynamic-redirects-5.59.4.tgz +0 -0
  175. package/components/tryghost-external-media-inliner-5.59.4.tgz +0 -0
  176. package/components/tryghost-extract-api-key-5.59.4.tgz +0 -0
  177. package/components/tryghost-html-to-plaintext-5.59.4.tgz +0 -0
  178. package/components/tryghost-i18n-5.59.4.tgz +0 -0
  179. package/components/tryghost-importer-handler-content-files-5.59.4.tgz +0 -0
  180. package/components/tryghost-importer-revue-5.59.4.tgz +0 -0
  181. package/components/tryghost-in-memory-repository-5.59.4.tgz +0 -0
  182. package/components/tryghost-job-manager-5.59.4.tgz +0 -0
  183. package/components/tryghost-link-redirects-5.59.4.tgz +0 -0
  184. package/components/tryghost-link-replacer-5.59.4.tgz +0 -0
  185. package/components/tryghost-link-tracking-5.59.4.tgz +0 -0
  186. package/components/tryghost-magic-link-5.59.4.tgz +0 -0
  187. package/components/tryghost-mail-events-5.59.4.tgz +0 -0
  188. package/components/tryghost-mailgun-client-5.59.4.tgz +0 -0
  189. package/components/tryghost-member-attribution-5.59.4.tgz +0 -0
  190. package/components/tryghost-member-events-5.59.4.tgz +0 -0
  191. package/components/tryghost-members-api-5.59.4.tgz +0 -0
  192. package/components/tryghost-members-csv-5.59.4.tgz +0 -0
  193. package/components/tryghost-members-importer-5.59.4.tgz +0 -0
  194. package/components/tryghost-members-offers-5.59.4.tgz +0 -0
  195. package/components/tryghost-members-payments-5.59.4.tgz +0 -0
  196. package/components/tryghost-members-ssr-5.59.4.tgz +0 -0
  197. package/components/tryghost-mentions-email-report-5.59.4.tgz +0 -0
  198. package/components/tryghost-milestones-5.59.4.tgz +0 -0
  199. package/components/tryghost-minifier-5.59.4.tgz +0 -0
  200. package/components/tryghost-model-to-domain-event-interceptor-5.59.4.tgz +0 -0
  201. package/components/tryghost-mw-api-version-mismatch-5.59.4.tgz +0 -0
  202. package/components/tryghost-mw-cache-control-5.59.4.tgz +0 -0
  203. package/components/tryghost-mw-error-handler-5.59.4.tgz +0 -0
  204. package/components/tryghost-mw-session-from-token-5.59.4.tgz +0 -0
  205. package/components/tryghost-mw-update-user-last-seen-5.59.4.tgz +0 -0
  206. package/components/tryghost-mw-version-match-5.59.4.tgz +0 -0
  207. package/components/tryghost-mw-vhost-5.59.4.tgz +0 -0
  208. package/components/tryghost-nql-filter-expansions-5.59.4.tgz +0 -0
  209. package/components/tryghost-oembed-service-5.59.4.tgz +0 -0
  210. package/components/tryghost-package-json-5.59.4.tgz +0 -0
  211. package/components/tryghost-post-events-5.59.4.tgz +0 -0
  212. package/components/tryghost-post-revisions-5.59.4.tgz +0 -0
  213. package/components/tryghost-posts-service-5.59.4.tgz +0 -0
  214. package/components/tryghost-referrers-5.59.4.tgz +0 -0
  215. package/components/tryghost-security-5.59.4.tgz +0 -0
  216. package/components/tryghost-session-service-5.59.4.tgz +0 -0
  217. package/components/tryghost-settings-path-manager-5.59.4.tgz +0 -0
  218. package/components/tryghost-slack-notifications-5.59.4.tgz +0 -0
  219. package/components/tryghost-staff-service-5.59.4.tgz +0 -0
  220. package/components/tryghost-stats-service-5.59.4.tgz +0 -0
  221. package/components/tryghost-update-check-service-5.59.4.tgz +0 -0
  222. package/components/tryghost-verification-trigger-5.59.4.tgz +0 -0
  223. package/components/tryghost-version-notifications-data-service-5.59.4.tgz +0 -0
  224. package/components/tryghost-webmentions-5.59.4.tgz +0 -0
  225. package/core/built/admin/assets/chunk.208.c7452bf9df2ebbf93206.js +0 -12510
  226. package/core/built/admin/assets/chunk.208.c7452bf9df2ebbf93206.js.LICENSE.txt +0 -250
  227. package/core/built/admin/assets/ghost-92cf97efa3969188bb2bec0155536d1d.css +0 -1
  228. package/core/built/admin/assets/ghost-dark-64031cd0a165ea4a338ab05129f863ab.css +0 -1
@@ -8,7 +8,7 @@
8
8
  <title>Ghost Admin</title>
9
9
 
10
10
 
11
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.59%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22ember-websockets%22%3A%7B%22socketIO%22%3Atrue%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.61%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22ember-websockets%22%3A%7B%22socketIO%22%3Atrue%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -37,7 +37,7 @@
37
37
  </style>
38
38
 
39
39
  <link integrity="" rel="stylesheet" href="assets/vendor-3e6947aa681f0fb82b193090e520dc73.css">
40
- <link integrity="" rel="stylesheet" href="assets/ghost-92cf97efa3969188bb2bec0155536d1d.css" title="light">
40
+ <link integrity="" rel="stylesheet" href="assets/ghost-8a4e981c272f793157133814ca7c7e84.css" title="light">
41
41
 
42
42
 
43
43
  </head>
@@ -56,9 +56,9 @@
56
56
 
57
57
  <div id="ember-basic-dropdown-wormhole"></div>
58
58
 
59
- <script src="assets/vendor-fff5b0b3c122441beb3170947ae27b9d.js"></script>
60
- <script src="assets/chunk.208.c7452bf9df2ebbf93206.js"></script>
61
- <script src="assets/chunk.143.2aa9967c47c24fab0bf5.js"></script>
62
- <script src="assets/ghost-24496fd514838a397808c3a992e5818c.js"></script>
59
+ <script src="assets/vendor-3631184082d609038638c1e169a002e7.js"></script>
60
+ <script src="assets/chunk.208.dbf172ad32f72f21a5dc.js"></script>
61
+ <script src="assets/chunk.143.b297dc36977ec0b8aa47.js"></script>
62
+ <script src="assets/ghost-4416ac3815c54eac76c2590d1154a628.js"></script>
63
63
  </body>
64
64
  </html>
@@ -1,5 +1,5 @@
1
1
  const {SafeString} = require('../services/handlebars');
2
- const {urlUtils, getFrontendKey, settingsCache} = require('../services/proxy');
2
+ const {labs, urlUtils, getFrontendKey, settingsCache} = require('../services/proxy');
3
3
  const {getFrontendAppConfig, getDataAttributes} = require('../utils/frontend-apps');
4
4
 
5
5
  module.exports = async function comments(options) {
@@ -50,14 +50,14 @@ module.exports = async function comments(options) {
50
50
  }
51
51
 
52
52
  const frontendKey = await getFrontendKey();
53
- const {scriptUrl, stylesUrl} = getFrontendAppConfig('comments');
53
+ const {scriptUrl} = getFrontendAppConfig('comments');
54
54
 
55
55
  const data = {
56
+ locale: labs.isSet('i18n') ? (settingsCache.get('locale') || 'en') : undefined,
56
57
  'ghost-comments': urlUtils.getSiteUrl(),
57
58
  api: urlUtils.urlFor('api', {type: 'content'}, true),
58
59
  admin: urlUtils.urlFor('admin', true),
59
60
  key: frontendKey,
60
- styles: stylesUrl,
61
61
  title: title,
62
62
  count: count,
63
63
  'post-id': this.id,
@@ -35,30 +35,30 @@
35
35
  align-items: flex-start;
36
36
  justify-content: center;
37
37
  height: 100%;
38
- padding: 6.4vmax 4vmax;
38
+ padding: min(6.4vmax, 120px) min(4vmax, 80px);
39
39
  background-size: cover;
40
40
  background-position: center;
41
41
  text-align: left;
42
42
  }
43
43
 
44
44
  .kg-width-wide .kg-header-card-text {
45
- padding: 10vmax 6.4vmax;
45
+ padding: min(10vmax, 220px) min(6.4vmax, 140px);
46
46
  }
47
47
 
48
48
  .kg-width-full .kg-header-card-text {
49
- padding: 12vmax 0;
49
+ padding: min(12vmax, 260px) 0;
50
50
  }
51
51
 
52
52
  .kg-layout-split .kg-header-card-text {
53
- padding: 12vmax 4vmax;
53
+ padding: min(12vmax, 260px) min(4vmax, 80px);
54
54
  }
55
55
 
56
56
  .kg-layout-split.kg-content-wide .kg-header-card-text {
57
- padding: 10vmax 0 10vmax 4vmax;
57
+ padding: min(10vmax, 220px) 0 min(10vmax, 220px) min(4vmax, 80px);
58
58
  }
59
59
 
60
60
  .kg-layout-split.kg-content-wide.kg-swapped .kg-header-card-text {
61
- padding: 10vmax 4vmax 10vmax 0;
61
+ padding: min(10vmax, 220px) min(4vmax, 80px) min(10vmax, 220px) 0;
62
62
  }
63
63
 
64
64
  .kg-swapped .kg-header-card-text {
@@ -93,6 +93,7 @@
93
93
  /* Split layout image */
94
94
 
95
95
  .kg-header-card-content .kg-header-card-image {
96
+ width: 100%;
96
97
  /* this will force the image to follow the signup card height */
97
98
  height: 0;
98
99
  min-height: 100%;
@@ -160,7 +161,7 @@
160
161
  }
161
162
 
162
163
  .kg-header-card.kg-width-full h3.kg-header-card-subheading:not(.kg-layout-split h3.kg-header-card-subheading) {
163
- max-width: 65vmax;
164
+ max-width: min(65vmax, 1200px);
164
165
  font-size: clamp(1.05em, 2vw, 2.6rem);
165
166
  }
166
167
 
@@ -227,12 +228,12 @@
227
228
  }
228
229
 
229
230
  .kg-width-wide .kg-header-card-text {
230
- padding: 6.4vmax 4vmax;
231
+ padding: min(6.4vmax, 120px) min(4vmax, 80px);
231
232
  }
232
233
 
233
234
  .kg-layout-split.kg-content-wide .kg-header-card-text,
234
235
  .kg-layout-split.kg-content-wide.kg-swapped .kg-header-card-text {
235
- padding: 9.6vmax 0;
236
+ padding: min(9.6vmax, 180px) 0;
236
237
  }
237
238
 
238
239
  .kg-header-card.kg-width-full h3.kg-header-card-subheading:not(.kg-layout-split h3.kg-header-card-subheading) {
@@ -28,30 +28,30 @@
28
28
  align-items: flex-start;
29
29
  justify-content: center;
30
30
  height: 100%;
31
- padding: 4vmax;
31
+ padding: min(4vmax, 80px);
32
32
  background-size: cover;
33
33
  background-position: center;
34
34
  text-align: left;
35
35
  }
36
36
 
37
37
  .kg-width-wide .kg-signup-card-text {
38
- padding: 6.4vmax;
38
+ padding: min(6.4vmax, 120px);
39
39
  }
40
40
 
41
41
  .kg-width-full .kg-signup-card-text {
42
- padding: 12vmax 0;
42
+ padding: min(12vmax, 260px) 0;
43
43
  }
44
44
 
45
45
  .kg-layout-split .kg-signup-card-text {
46
- padding: 12vmax 4vmax;
46
+ padding: min(12vmax, 260px) min(4vmax, 80px);
47
47
  }
48
48
 
49
49
  .kg-layout-split.kg-content-wide .kg-signup-card-text {
50
- padding: 10vmax 0 10vmax 4vmax;
50
+ padding: min(10vmax, 220px) 0 min(10vmax, 220px) min(4vmax, 80px);
51
51
  }
52
52
 
53
53
  .kg-layout-split.kg-content-wide.kg-swapped .kg-signup-card-text {
54
- padding: 10vmax 4vmax 10vmax 0;
54
+ padding: min(10vmax, 220px) min(4vmax, 80px) min(10vmax, 220px) 0;
55
55
  }
56
56
 
57
57
  .kg-swapped .kg-signup-card-text {
@@ -86,6 +86,7 @@
86
86
  /* Split layout image */
87
87
 
88
88
  .kg-signup-card-content .kg-signup-card-image {
89
+ width: 100%;
89
90
  /* this will force the image to follow the signup card height */
90
91
  height: 0;
91
92
  min-height: 100%;
@@ -153,7 +154,7 @@
153
154
  }
154
155
 
155
156
  .kg-signup-card.kg-width-full h3.kg-signup-card-subheading:not(.kg-layout-split h3.kg-signup-card-subheading) {
156
- max-width: 65vmax;
157
+ max-width: min(65vmax, 1200px);
157
158
  font-size: clamp(1.05em, 2vw, 2.6rem);
158
159
  }
159
160
 
@@ -176,17 +177,17 @@
176
177
 
177
178
  .kg-signup-card-heading + .kg-signup-card-form,
178
179
  .kg-signup-card-subheading + .kg-signup-card-form {
179
- margin: 2.4vmax 0 0;
180
+ margin: min(2.4vmax, 48px) 0 0;
180
181
  }
181
182
 
182
183
  .kg-width-wide .kg-signup-card-heading + .kg-signup-card-form,
183
184
  .kg-width-wide .kg-signup-card-subheading + .kg-signup-card-form {
184
- margin: 3.2vmax 0 0;
185
+ margin: min(3.2vmax, 64px) 0 0;
185
186
  }
186
187
 
187
188
  .kg-width-full .kg-signup-card-heading + .kg-signup-card-form,
188
189
  .kg-width-full .kg-signup-card-subheading + .kg-signup-card-form {
189
- margin: 4vmax 0 0;
190
+ margin: min(4vmax, 80px) 0 0;
190
191
  }
191
192
 
192
193
  .kg-signup-card-fields {
@@ -338,12 +339,12 @@
338
339
  }
339
340
 
340
341
  .kg-width-wide .kg-signup-card-text {
341
- padding: 6.4vmax 4vmax;
342
+ padding: min(6.4vmax, 120px) min(4vmax, 80px);
342
343
  }
343
344
 
344
345
  .kg-layout-split.kg-content-wide .kg-signup-card-text,
345
346
  .kg-layout-split.kg-content-wide.kg-swapped .kg-signup-card-text {
346
- padding: 9.6vmax 0;
347
+ padding: min(9.6vmax, 180px) 0;
347
348
  }
348
349
 
349
350
  .kg-signup-card.kg-width-full h3.kg-signup-card-subheading:not(.kg-layout-split h3.kg-signup-card-subheading) {
@@ -24,6 +24,9 @@ function getDataAttributes(data) {
24
24
  return dataAttributes;
25
25
  }
26
26
  Object.entries(data).forEach(([key, value]) => {
27
+ if (value === undefined) {
28
+ return;
29
+ }
27
30
  dataAttributes += `data-${key}="${value}" `;
28
31
  });
29
32
 
@@ -60,7 +60,7 @@ function corsOptionsDelegate(req, callback) {
60
60
  * @param {Express.Response} res
61
61
  * @param {Function} next
62
62
  */
63
- const handleCaching = (req, res, next) => {
63
+ const handleCaching = function handleCachingCors(req, res, next) {
64
64
  const method = req.method && req.method.toUpperCase && req.method.toUpperCase();
65
65
  if (method === 'OPTIONS') {
66
66
  // @NOTE: try to add native support for dynamic 'vary' header value in 'cors' module
@@ -12,7 +12,7 @@ const FORMAT_PATH_REGEX = /^\/format\/([^./]+)\//;
12
12
 
13
13
  const TRAILING_SLASH_REGEX = /\/+$/;
14
14
 
15
- module.exports = function (req, res, next) {
15
+ module.exports = function handleImageSizes(req, res, next) {
16
16
  // In admin we need to read images and calculate the average color (blocked by CORS otherwise)
17
17
  res.setHeader('Access-Control-Allow-Origin', '*');
18
18
 
@@ -26,7 +26,7 @@ function matchCacheKey(req, cache) {
26
26
  return true;
27
27
  }
28
28
 
29
- function createPublicFileMiddleware(location, file, mime, maxAge) {
29
+ function createPublicFileMiddleware(location, file, mime, maxAge, options = {}) {
30
30
  let cache;
31
31
  // These files are provided by Ghost, and therefore live inside of the core folder
32
32
  const staticFilePath = config.get('paths').publicFilePath;
@@ -45,7 +45,7 @@ function createPublicFileMiddleware(location, file, mime, maxAge) {
45
45
  }
46
46
 
47
47
  // send image files directly and let express handle content-length, etag, etc
48
- if (mime.match(/^image/)) {
48
+ if (mime.match(/^image/) || options.disableServerCache) {
49
49
  return res.sendFile(filePath, (err) => {
50
50
  if (err && err.status === 404) {
51
51
  // ensure we're triggering basic asset 404 and not a templated 404
@@ -101,8 +101,8 @@ function createPublicFileMiddleware(location, file, mime, maxAge) {
101
101
 
102
102
  // ### servePublicFile Middleware
103
103
  // Handles requests to robots.txt and favicon.ico (and caches them)
104
- function servePublicFile(location, file, type, maxAge) {
105
- const publicFileMiddleware = createPublicFileMiddleware(location, file, type, maxAge);
104
+ function servePublicFile(location, file, type, maxAge, options = {}) {
105
+ const publicFileMiddleware = createPublicFileMiddleware(location, file, type, maxAge, options);
106
106
 
107
107
  return function servePublicFileMiddleware(req, res, next) {
108
108
  if (req.path === '/' + file) {
@@ -98,6 +98,9 @@ module.exports = function setupSiteApp(routerConfig) {
98
98
  (req, res, next) => membersService.api.middleware.wellKnown(req, res, next)
99
99
  );
100
100
 
101
+ // Recommendations well-known
102
+ siteApp.use(mw.servePublicFile('built', '.well-known/recommendations.json', 'application/json', config.get('caching:publicAssets:maxAge'), {disableServerCache: true}));
103
+
101
104
  // setup middleware for internal apps
102
105
  // @TODO: refactor this to be a proper app middleware hook for internal apps
103
106
  config.get('apps:internal').forEach((appName) => {
@@ -129,7 +132,7 @@ module.exports = function setupSiteApp(routerConfig) {
129
132
  siteApp.use(shared.middleware.prettyUrls);
130
133
 
131
134
  // ### Caching
132
- siteApp.use(function (req, res, next) {
135
+ siteApp.use(function frontendCaching(req, res, next) {
133
136
  // Site frontend is cacheable UNLESS request made by a member or site is in private mode
134
137
  if (req.member || res.isPrivateBlog) {
135
138
  return shared.middleware.cacheControl('private')(req, res, next);
@@ -138,7 +141,7 @@ module.exports = function setupSiteApp(routerConfig) {
138
141
  }
139
142
  });
140
143
 
141
- siteApp.use(function (req, res, next) {
144
+ siteApp.use(function memberPageViewMiddleware(req, res, next) {
142
145
  if (req.member) {
143
146
  // This event needs memberLastSeenAt to avoid doing un-necessary database queries when updating `last_seen_at`
144
147
  DomainEvents.dispatch(MemberPageViewEvent.create({url: req.url, memberId: req.member.id, memberLastSeenAt: req.member.last_seen_at}, new Date()));
@@ -205,6 +205,10 @@ module.exports = {
205
205
  return apiFramework.pipeline(require('./mail-events'), localUtils);
206
206
  },
207
207
 
208
+ get recommendations() {
209
+ return apiFramework.pipeline(require('./recommendations'), localUtils);
210
+ },
211
+
208
212
  /**
209
213
  * Content API Controllers
210
214
  *
@@ -255,5 +259,9 @@ module.exports = {
255
259
 
256
260
  get feedbackMembers() {
257
261
  return apiFramework.pipeline(require('./feedback-members'), localUtils, 'members');
262
+ },
263
+
264
+ get recommendationsPublic() {
265
+ return apiFramework.pipeline(require('./recommendations-public'), localUtils, 'content');
258
266
  }
259
267
  };
@@ -0,0 +1,24 @@
1
+ const recommendations = require('../../services/recommendations');
2
+
3
+ module.exports = {
4
+ docName: 'recommendations',
5
+
6
+ browse: {
7
+ headers: {
8
+ cacheInvalidate: false
9
+ },
10
+ options: [
11
+ 'limit',
12
+ 'fields',
13
+ 'filter',
14
+ 'order',
15
+ 'debug',
16
+ 'page'
17
+ ],
18
+ permissions: true,
19
+ validation: {},
20
+ async query() {
21
+ return await recommendations.controller.listRecommendations();
22
+ }
23
+ }
24
+ };
@@ -0,0 +1,71 @@
1
+ const recommendations = require('../../services/recommendations');
2
+
3
+ module.exports = {
4
+ docName: 'recommendations',
5
+
6
+ browse: {
7
+ headers: {
8
+ cacheInvalidate: false
9
+ },
10
+ options: [],
11
+ permissions: true,
12
+ validation: {},
13
+ async query() {
14
+ return await recommendations.controller.listRecommendations();
15
+ }
16
+ },
17
+
18
+ add: {
19
+ statusCode: 201,
20
+ headers: {
21
+ cacheInvalidate: true
22
+ },
23
+ options: [],
24
+ validation: {},
25
+ permissions: true,
26
+ async query(frame) {
27
+ return await recommendations.controller.addRecommendation(frame);
28
+ }
29
+ },
30
+
31
+ edit: {
32
+ headers: {
33
+ cacheInvalidate: true
34
+ },
35
+ options: [
36
+ 'id'
37
+ ],
38
+ validation: {
39
+ options: {
40
+ id: {
41
+ required: true
42
+ }
43
+ }
44
+ },
45
+ permissions: true,
46
+ async query(frame) {
47
+ return await recommendations.controller.editRecommendation(frame);
48
+ }
49
+ },
50
+
51
+ destroy: {
52
+ statusCode: 204,
53
+ headers: {
54
+ cacheInvalidate: true
55
+ },
56
+ options: [
57
+ 'id'
58
+ ],
59
+ validation: {
60
+ options: {
61
+ id: {
62
+ required: true
63
+ }
64
+ }
65
+ },
66
+ permissions: true,
67
+ query(frame) {
68
+ return recommendations.controller.deleteRecommendation(frame);
69
+ }
70
+ }
71
+ };
@@ -70,7 +70,8 @@ const EDITABLE_SETTINGS = [
70
70
  'pintura_js_url',
71
71
  'pintura_css_url',
72
72
  'donations_currency',
73
- 'donations_suggested_amount'
73
+ 'donations_suggested_amount',
74
+ 'recommendations_enabled'
74
75
  ];
75
76
 
76
77
  module.exports = {
@@ -50,7 +50,8 @@ const BACKUP_TABLES = [
50
50
  'email_spam_complaint_events',
51
51
  'milestones',
52
52
  'collections',
53
- 'collections_posts'
53
+ 'collections_posts',
54
+ 'recommendations'
54
55
  ];
55
56
 
56
57
  // NOTE: exposing only tables which are going to be included in a "default" export file
@@ -0,0 +1,50 @@
1
+ const {combineTransactionalMigrations, addPermissionWithRoles} = require('../../utils');
2
+
3
+ module.exports = combineTransactionalMigrations(
4
+ addPermissionWithRoles({
5
+ name: 'Browse recommendations',
6
+ action: 'browse',
7
+ object: 'recommendation'
8
+ }, [
9
+ 'Administrator',
10
+ 'Admin Integration',
11
+ 'Editor',
12
+ 'Author',
13
+ 'Contributor'
14
+ ]),
15
+ addPermissionWithRoles({
16
+ name: 'Read recommendations',
17
+ action: 'read',
18
+ object: 'recommendation'
19
+ }, [
20
+ 'Administrator',
21
+ 'Admin Integration',
22
+ 'Editor',
23
+ 'Author',
24
+ 'Contributor'
25
+ ]),
26
+ addPermissionWithRoles({
27
+ name: 'Edit recommendations',
28
+ action: 'edit',
29
+ object: 'recommendation'
30
+ }, [
31
+ 'Administrator',
32
+ 'Admin Integration'
33
+ ]),
34
+ addPermissionWithRoles({
35
+ name: 'Add recommendations',
36
+ action: 'add',
37
+ object: 'recommendation'
38
+ }, [
39
+ 'Administrator',
40
+ 'Admin Integration'
41
+ ]),
42
+ addPermissionWithRoles({
43
+ name: 'Delete recommendations',
44
+ action: 'destroy',
45
+ object: 'recommendation'
46
+ }, [
47
+ 'Administrator',
48
+ 'Admin Integration'
49
+ ])
50
+ );
@@ -0,0 +1,18 @@
1
+ const {addTable} = require('../../utils');
2
+
3
+ module.exports = addTable('recommendations', {
4
+ id: {type: 'string', maxlength: 24, nullable: false, primary: true},
5
+
6
+ url: {type: 'string', maxlength: 2000, nullable: false},
7
+ title: {type: 'string', maxlength: 2000, nullable: false},
8
+
9
+ excerpt: {type: 'string', maxlength: 2000, nullable: true},
10
+ featured_image: {type: 'string', maxlength: 2000, nullable: true},
11
+ favicon: {type: 'string', maxlength: 2000, nullable: true},
12
+
13
+ reason: {type: 'string', maxlength: 2000, nullable: true},
14
+ one_click_subscribe: {type: 'boolean', nullable: false, defaultTo: false},
15
+
16
+ created_at: {type: 'dateTime', nullable: false},
17
+ updated_at: {type: 'dateTime', nullable: true}
18
+ });
@@ -0,0 +1,8 @@
1
+ const {addSetting} = require('../../utils');
2
+
3
+ module.exports = addSetting({
4
+ key: 'recommendations_enabled',
5
+ value: 'false',
6
+ type: 'boolean',
7
+ group: 'recommendations'
8
+ });
@@ -565,5 +565,15 @@
565
565
  },
566
566
  "type": "number"
567
567
  }
568
+ },
569
+ "recommendations": {
570
+ "recommendations_enabled": {
571
+ "defaultValue": "false",
572
+ "validations": {
573
+ "isEmpty": false,
574
+ "isIn": [["true", "false"]]
575
+ },
576
+ "type": "boolean"
577
+ }
568
578
  }
569
579
  }
@@ -686,6 +686,31 @@
686
686
  "name": "Delete collections",
687
687
  "action_type": "destroy",
688
688
  "object_type": "collection"
689
+ },
690
+ {
691
+ "name": "Browse recommendations",
692
+ "action_type": "browse",
693
+ "object_type": "recommendation"
694
+ },
695
+ {
696
+ "name": "Read recommendations",
697
+ "action_type": "read",
698
+ "object_type": "recommendation"
699
+ },
700
+ {
701
+ "name": "Edit recommendations",
702
+ "action_type": "edit",
703
+ "object_type": "recommendation"
704
+ },
705
+ {
706
+ "name": "Add recommendations",
707
+ "action_type": "add",
708
+ "object_type": "recommendation"
709
+ },
710
+ {
711
+ "name": "Delete recommendations",
712
+ "action_type": "destroy",
713
+ "object_type": "recommendation"
689
714
  }
690
715
  ]
691
716
  },
@@ -831,7 +856,8 @@
831
856
  "comment": "all",
832
857
  "link": "all",
833
858
  "mention": "browse",
834
- "collection": "all"
859
+ "collection": "all",
860
+ "recommendation": "all"
835
861
  },
836
862
  "DB Backup Integration": {
837
863
  "db": "all"
@@ -873,7 +899,8 @@
873
899
  "comment": "all",
874
900
  "link": "all",
875
901
  "mention": "browse",
876
- "collection": "all"
902
+ "collection": "all",
903
+ "recommendation": "all"
877
904
  },
878
905
  "Editor": {
879
906
  "notification": "all",
@@ -891,7 +918,8 @@
891
918
  "label": ["browse", "read"],
892
919
  "product": ["browse", "read"],
893
920
  "newsletter": ["browse", "read"],
894
- "collection": "all"
921
+ "collection": "all",
922
+ "recommendation": ["browse", "read"]
895
923
  },
896
924
  "Author": {
897
925
  "post": ["browse", "read", "add"],
@@ -907,7 +935,8 @@
907
935
  "label": ["browse", "read"],
908
936
  "product": ["browse", "read"],
909
937
  "newsletter": ["browse", "read"],
910
- "collection": ["browse", "read", "add"]
938
+ "collection": ["browse", "read", "add"],
939
+ "recommendation": ["browse", "read"]
911
940
  },
912
941
  "Contributor": {
913
942
  "post": ["browse", "read", "add"],
@@ -920,7 +949,8 @@
920
949
  "email_preview": "read",
921
950
  "email": "read",
922
951
  "snippet": ["browse", "read"],
923
- "collection": ["browse", "read"]
952
+ "collection": ["browse", "read"],
953
+ "recommendation": ["browse", "read"]
924
954
  }
925
955
  }
926
956
  },
@@ -1067,5 +1067,17 @@ module.exports = {
1067
1067
  collection_id: {type: 'string', maxlength: 24, nullable: false, references: 'collections.id', cascadeDelete: true},
1068
1068
  post_id: {type: 'string', maxlength: 24, nullable: false, references: 'posts.id', cascadeDelete: true},
1069
1069
  sort_order: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0}
1070
+ },
1071
+ recommendations: {
1072
+ id: {type: 'string', maxlength: 24, nullable: false, primary: true},
1073
+ url: {type: 'string', maxlength: 2000, nullable: false},
1074
+ title: {type: 'string', maxlength: 2000, nullable: false},
1075
+ excerpt: {type: 'string', maxlength: 2000, nullable: true},
1076
+ featured_image: {type: 'string', maxlength: 2000, nullable: true},
1077
+ favicon: {type: 'string', maxlength: 2000, nullable: true},
1078
+ reason: {type: 'string', maxlength: 2000, nullable: true},
1079
+ one_click_subscribe: {type: 'boolean', nullable: false, defaultTo: false},
1080
+ created_at: {type: 'dateTime', nullable: false},
1081
+ updated_at: {type: 'dateTime', nullable: true}
1070
1082
  }
1071
1083
  };
@@ -80,7 +80,7 @@ ghostBookshelf.plugin('bookshelf-relations', {
80
80
  };
81
81
 
82
82
  // CASE: disable after hook for specific relations
83
- if (['permissions_roles', 'members_newsletters', 'collections_posts'].indexOf(existing.relatedData.joinTableName) !== -1) {
83
+ if (['permissions_roles', 'members_newsletters'].indexOf(existing.relatedData.joinTableName) !== -1) {
84
84
  return Promise.resolve();
85
85
  }
86
86