ghost 5.67.0 → 5.69.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 (200) hide show
  1. package/components/tryghost-adapter-cache-memory-ttl-5.69.0.tgz +0 -0
  2. package/components/tryghost-adapter-cache-redis-5.69.0.tgz +0 -0
  3. package/components/tryghost-adapter-manager-5.69.0.tgz +0 -0
  4. package/components/tryghost-announcement-bar-settings-5.69.0.tgz +0 -0
  5. package/components/tryghost-api-framework-5.69.0.tgz +0 -0
  6. package/components/tryghost-api-version-compatibility-service-5.69.0.tgz +0 -0
  7. package/components/tryghost-audience-feedback-5.69.0.tgz +0 -0
  8. package/components/tryghost-bookshelf-repository-5.69.0.tgz +0 -0
  9. package/components/tryghost-bootstrap-socket-5.69.0.tgz +0 -0
  10. package/components/tryghost-collections-5.69.0.tgz +0 -0
  11. package/components/tryghost-constants-5.69.0.tgz +0 -0
  12. package/components/tryghost-custom-theme-settings-service-5.69.0.tgz +0 -0
  13. package/components/{tryghost-data-generator-5.67.0.tgz → tryghost-data-generator-5.69.0.tgz} +0 -0
  14. package/components/tryghost-domain-events-5.69.0.tgz +0 -0
  15. package/components/tryghost-donations-5.69.0.tgz +0 -0
  16. package/components/tryghost-dynamic-routing-events-5.69.0.tgz +0 -0
  17. package/components/tryghost-email-analytics-provider-mailgun-5.69.0.tgz +0 -0
  18. package/components/tryghost-email-analytics-service-5.69.0.tgz +0 -0
  19. package/components/tryghost-email-content-generator-5.69.0.tgz +0 -0
  20. package/components/tryghost-email-events-5.69.0.tgz +0 -0
  21. package/components/{tryghost-email-service-5.67.0.tgz → tryghost-email-service-5.69.0.tgz} +0 -0
  22. package/components/tryghost-email-suppression-list-5.69.0.tgz +0 -0
  23. package/components/tryghost-event-aware-cache-wrapper-5.69.0.tgz +0 -0
  24. package/components/tryghost-express-dynamic-redirects-5.69.0.tgz +0 -0
  25. package/components/tryghost-external-media-inliner-5.69.0.tgz +0 -0
  26. package/components/tryghost-extract-api-key-5.69.0.tgz +0 -0
  27. package/components/tryghost-html-to-plaintext-5.69.0.tgz +0 -0
  28. package/components/tryghost-i18n-5.69.0.tgz +0 -0
  29. package/components/tryghost-importer-handler-content-files-5.69.0.tgz +0 -0
  30. package/components/tryghost-importer-revue-5.69.0.tgz +0 -0
  31. package/components/tryghost-in-memory-repository-5.69.0.tgz +0 -0
  32. package/components/tryghost-job-manager-5.69.0.tgz +0 -0
  33. package/components/tryghost-link-redirects-5.69.0.tgz +0 -0
  34. package/components/tryghost-link-replacer-5.69.0.tgz +0 -0
  35. package/components/tryghost-link-tracking-5.69.0.tgz +0 -0
  36. package/components/tryghost-magic-link-5.69.0.tgz +0 -0
  37. package/components/tryghost-mail-events-5.69.0.tgz +0 -0
  38. package/components/tryghost-mailgun-client-5.69.0.tgz +0 -0
  39. package/components/tryghost-member-attribution-5.69.0.tgz +0 -0
  40. package/components/tryghost-member-events-5.69.0.tgz +0 -0
  41. package/components/{tryghost-members-api-5.67.0.tgz → tryghost-members-api-5.69.0.tgz} +0 -0
  42. package/components/tryghost-members-csv-5.69.0.tgz +0 -0
  43. package/components/tryghost-members-events-service-5.69.0.tgz +0 -0
  44. package/components/tryghost-members-importer-5.69.0.tgz +0 -0
  45. package/components/tryghost-members-offers-5.69.0.tgz +0 -0
  46. package/components/tryghost-members-payments-5.69.0.tgz +0 -0
  47. package/components/tryghost-members-ssr-5.69.0.tgz +0 -0
  48. package/components/{tryghost-members-stripe-service-5.67.0.tgz → tryghost-members-stripe-service-5.69.0.tgz} +0 -0
  49. package/components/tryghost-mentions-email-report-5.69.0.tgz +0 -0
  50. package/components/tryghost-milestones-5.69.0.tgz +0 -0
  51. package/components/tryghost-minifier-5.69.0.tgz +0 -0
  52. package/components/tryghost-model-to-domain-event-interceptor-5.69.0.tgz +0 -0
  53. package/components/tryghost-mw-api-version-mismatch-5.69.0.tgz +0 -0
  54. package/components/tryghost-mw-cache-control-5.69.0.tgz +0 -0
  55. package/components/tryghost-mw-error-handler-5.69.0.tgz +0 -0
  56. package/components/tryghost-mw-session-from-token-5.69.0.tgz +0 -0
  57. package/components/tryghost-mw-update-user-last-seen-5.69.0.tgz +0 -0
  58. package/components/tryghost-mw-version-match-5.69.0.tgz +0 -0
  59. package/components/tryghost-mw-vhost-5.69.0.tgz +0 -0
  60. package/components/tryghost-nql-filter-expansions-5.69.0.tgz +0 -0
  61. package/components/tryghost-oembed-service-5.69.0.tgz +0 -0
  62. package/components/tryghost-package-json-5.69.0.tgz +0 -0
  63. package/components/tryghost-post-events-5.69.0.tgz +0 -0
  64. package/components/tryghost-post-revisions-5.69.0.tgz +0 -0
  65. package/components/tryghost-posts-service-5.69.0.tgz +0 -0
  66. package/components/tryghost-recommendations-5.69.0.tgz +0 -0
  67. package/components/tryghost-referrers-5.69.0.tgz +0 -0
  68. package/components/tryghost-security-5.69.0.tgz +0 -0
  69. package/components/tryghost-session-service-5.69.0.tgz +0 -0
  70. package/components/tryghost-settings-path-manager-5.69.0.tgz +0 -0
  71. package/components/tryghost-slack-notifications-5.69.0.tgz +0 -0
  72. package/components/tryghost-staff-service-5.69.0.tgz +0 -0
  73. package/components/tryghost-stats-service-5.69.0.tgz +0 -0
  74. package/components/tryghost-tiers-5.69.0.tgz +0 -0
  75. package/components/tryghost-update-check-service-5.69.0.tgz +0 -0
  76. package/components/tryghost-verification-trigger-5.69.0.tgz +0 -0
  77. package/components/tryghost-version-notifications-data-service-5.69.0.tgz +0 -0
  78. package/components/tryghost-webmentions-5.69.0.tgz +0 -0
  79. package/content/themes/source/assets/built/source.js +1 -1
  80. package/content/themes/source/assets/built/source.js.map +1 -1
  81. package/content/themes/source/assets/js/casper.js +1 -1
  82. package/content/themes/source/package.json +1 -1
  83. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-1643e910.mjs → CodeEditorView-b2459562.mjs} +275 -273
  84. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
  85. package/core/built/admin/assets/admin-x-settings/{index-b4c2ef5b.mjs → index-0152ca19.mjs} +4813 -4625
  86. package/core/built/admin/assets/admin-x-settings/{limit-service-c343d244.mjs → limit-service-b0370dbc.mjs} +2 -2
  87. package/core/built/admin/assets/admin-x-settings/{modals-8b0aeacd.mjs → modals-4201e074.mjs} +10184 -9731
  88. package/core/built/admin/assets/{chunk.143.da2301d9edb6ad0b5496.js → chunk.143.6e2a218178c3c44fac33.js} +5 -5
  89. package/core/built/admin/assets/{chunk.178.efec05ab07e1e216d657.js → chunk.178.a7169a78c2b1e9582f2e.js} +4 -4
  90. package/core/built/admin/assets/{chunk.940.b09a2c2049ed9c373dd0.js → chunk.940.159ed6caf77114942465.js} +34 -34
  91. package/core/built/admin/assets/{ghost-193c98abe66483968da753e54917e87a.js → ghost-a012143b70f1e3bffed74252f7a736df.js} +189 -188
  92. package/core/built/admin/assets/ghost-a1d3bcea0bd360af258a27e29323ba35.css +1 -0
  93. package/core/built/admin/assets/ghost-dark-638883e1af34829a72578aad50905f7c.css +1 -0
  94. package/core/built/admin/assets/koenig-lexical/Koenig-editor-1.png +0 -0
  95. package/core/built/admin/assets/koenig-lexical/Koenig-editor-2.png +0 -0
  96. package/core/built/admin/assets/koenig-lexical/assets/fonts/Inter.ttf +0 -0
  97. package/core/built/admin/assets/koenig-lexical/index.css +6 -0
  98. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +129021 -0
  99. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +1442 -0
  100. package/core/built/admin/index.html +5 -5
  101. package/core/frontend/helpers/img_url.js +6 -6
  102. package/core/frontend/helpers/readable_url.js +32 -0
  103. package/core/frontend/helpers/recommendations.js +1 -1
  104. package/core/frontend/helpers/tpl/recommendations.hbs +4 -5
  105. package/core/server/GhostServer.js +9 -0
  106. package/core/server/api/endpoints/incoming-recommendations.js +20 -0
  107. package/core/server/api/endpoints/index.js +4 -0
  108. package/core/server/api/endpoints/posts-public.js +3 -2
  109. package/core/server/api/endpoints/utils/serializers/output/config.js +0 -1
  110. package/core/server/data/migrations/utils/schema.js +31 -2
  111. package/core/server/data/migrations/versions/5.69/2023-10-06-15-06-00-rename-recommendations-reason-to-description.js +3 -0
  112. package/core/server/data/schema/commands.js +20 -0
  113. package/core/server/data/schema/schema.js +1 -1
  114. package/core/server/models/product.js +1 -1
  115. package/core/server/services/newsletters/NewslettersService.js +2 -2
  116. package/core/server/services/oembed/TwitterOEmbedProvider.js +5 -1
  117. package/core/server/services/recommendations/RecommendationServiceWrapper.js +10 -0
  118. package/core/server/services/settings/SettingsBREADService.js +1 -5
  119. package/core/server/web/api/endpoints/admin/routes.js +3 -0
  120. package/core/shared/config/defaults.json +0 -4
  121. package/core/shared/labs.js +3 -4
  122. package/package.json +157 -157
  123. package/yarn.lock +147 -141
  124. package/components/tryghost-adapter-cache-memory-ttl-5.67.0.tgz +0 -0
  125. package/components/tryghost-adapter-cache-redis-5.67.0.tgz +0 -0
  126. package/components/tryghost-adapter-manager-5.67.0.tgz +0 -0
  127. package/components/tryghost-announcement-bar-settings-5.67.0.tgz +0 -0
  128. package/components/tryghost-api-framework-5.67.0.tgz +0 -0
  129. package/components/tryghost-api-version-compatibility-service-5.67.0.tgz +0 -0
  130. package/components/tryghost-audience-feedback-5.67.0.tgz +0 -0
  131. package/components/tryghost-bookshelf-repository-5.67.0.tgz +0 -0
  132. package/components/tryghost-bootstrap-socket-5.67.0.tgz +0 -0
  133. package/components/tryghost-collections-5.67.0.tgz +0 -0
  134. package/components/tryghost-constants-5.67.0.tgz +0 -0
  135. package/components/tryghost-custom-theme-settings-service-5.67.0.tgz +0 -0
  136. package/components/tryghost-domain-events-5.67.0.tgz +0 -0
  137. package/components/tryghost-donations-5.67.0.tgz +0 -0
  138. package/components/tryghost-dynamic-routing-events-5.67.0.tgz +0 -0
  139. package/components/tryghost-email-analytics-provider-mailgun-5.67.0.tgz +0 -0
  140. package/components/tryghost-email-analytics-service-5.67.0.tgz +0 -0
  141. package/components/tryghost-email-content-generator-5.67.0.tgz +0 -0
  142. package/components/tryghost-email-events-5.67.0.tgz +0 -0
  143. package/components/tryghost-email-suppression-list-5.67.0.tgz +0 -0
  144. package/components/tryghost-event-aware-cache-wrapper-5.67.0.tgz +0 -0
  145. package/components/tryghost-express-dynamic-redirects-5.67.0.tgz +0 -0
  146. package/components/tryghost-external-media-inliner-5.67.0.tgz +0 -0
  147. package/components/tryghost-extract-api-key-5.67.0.tgz +0 -0
  148. package/components/tryghost-html-to-plaintext-5.67.0.tgz +0 -0
  149. package/components/tryghost-i18n-5.67.0.tgz +0 -0
  150. package/components/tryghost-importer-handler-content-files-5.67.0.tgz +0 -0
  151. package/components/tryghost-importer-revue-5.67.0.tgz +0 -0
  152. package/components/tryghost-in-memory-repository-5.67.0.tgz +0 -0
  153. package/components/tryghost-job-manager-5.67.0.tgz +0 -0
  154. package/components/tryghost-link-redirects-5.67.0.tgz +0 -0
  155. package/components/tryghost-link-replacer-5.67.0.tgz +0 -0
  156. package/components/tryghost-link-tracking-5.67.0.tgz +0 -0
  157. package/components/tryghost-magic-link-5.67.0.tgz +0 -0
  158. package/components/tryghost-mail-events-5.67.0.tgz +0 -0
  159. package/components/tryghost-mailgun-client-5.67.0.tgz +0 -0
  160. package/components/tryghost-member-attribution-5.67.0.tgz +0 -0
  161. package/components/tryghost-member-events-5.67.0.tgz +0 -0
  162. package/components/tryghost-members-csv-5.67.0.tgz +0 -0
  163. package/components/tryghost-members-events-service-5.67.0.tgz +0 -0
  164. package/components/tryghost-members-importer-5.67.0.tgz +0 -0
  165. package/components/tryghost-members-offers-5.67.0.tgz +0 -0
  166. package/components/tryghost-members-payments-5.67.0.tgz +0 -0
  167. package/components/tryghost-members-ssr-5.67.0.tgz +0 -0
  168. package/components/tryghost-mentions-email-report-5.67.0.tgz +0 -0
  169. package/components/tryghost-milestones-5.67.0.tgz +0 -0
  170. package/components/tryghost-minifier-5.67.0.tgz +0 -0
  171. package/components/tryghost-model-to-domain-event-interceptor-5.67.0.tgz +0 -0
  172. package/components/tryghost-mw-api-version-mismatch-5.67.0.tgz +0 -0
  173. package/components/tryghost-mw-cache-control-5.67.0.tgz +0 -0
  174. package/components/tryghost-mw-error-handler-5.67.0.tgz +0 -0
  175. package/components/tryghost-mw-session-from-token-5.67.0.tgz +0 -0
  176. package/components/tryghost-mw-update-user-last-seen-5.67.0.tgz +0 -0
  177. package/components/tryghost-mw-version-match-5.67.0.tgz +0 -0
  178. package/components/tryghost-mw-vhost-5.67.0.tgz +0 -0
  179. package/components/tryghost-nql-filter-expansions-5.67.0.tgz +0 -0
  180. package/components/tryghost-oembed-service-5.67.0.tgz +0 -0
  181. package/components/tryghost-package-json-5.67.0.tgz +0 -0
  182. package/components/tryghost-post-events-5.67.0.tgz +0 -0
  183. package/components/tryghost-post-revisions-5.67.0.tgz +0 -0
  184. package/components/tryghost-posts-service-5.67.0.tgz +0 -0
  185. package/components/tryghost-recommendations-5.67.0.tgz +0 -0
  186. package/components/tryghost-referrers-5.67.0.tgz +0 -0
  187. package/components/tryghost-security-5.67.0.tgz +0 -0
  188. package/components/tryghost-session-service-5.67.0.tgz +0 -0
  189. package/components/tryghost-settings-path-manager-5.67.0.tgz +0 -0
  190. package/components/tryghost-slack-notifications-5.67.0.tgz +0 -0
  191. package/components/tryghost-staff-service-5.67.0.tgz +0 -0
  192. package/components/tryghost-stats-service-5.67.0.tgz +0 -0
  193. package/components/tryghost-tiers-5.67.0.tgz +0 -0
  194. package/components/tryghost-update-check-service-5.67.0.tgz +0 -0
  195. package/components/tryghost-verification-trigger-5.67.0.tgz +0 -0
  196. package/components/tryghost-version-notifications-data-service-5.67.0.tgz +0 -0
  197. package/components/tryghost-webmentions-5.67.0.tgz +0 -0
  198. package/core/built/admin/assets/ghost-9e454659c8c0896ed10336b640d1b1eb.css +0 -1
  199. package/core/built/admin/assets/ghost-dark-1d24186396fe74946c926faa9faa247b.css +0 -1
  200. /package/core/built/admin/assets/{chunk.940.b09a2c2049ed9c373dd0.js.LICENSE.txt → chunk.940.159ed6caf77114942465.js.LICENSE.txt} +0 -0
@@ -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.67%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%22editorUrl%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.69%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%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%22a38c847eaf%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%226c3c017a49%22%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-9e454659c8c0896ed10336b640d1b1eb.css" title="light">
40
+ <link integrity="" rel="stylesheet" href="assets/ghost-a1d3bcea0bd360af258a27e29323ba35.css" title="light">
41
41
 
42
42
 
43
43
  </head>
@@ -57,8 +57,8 @@
57
57
  <div id="ember-basic-dropdown-wormhole"></div>
58
58
 
59
59
  <script src="assets/vendor-71d23939faaa5d1478ead73d917111b9.js"></script>
60
- <script src="assets/chunk.940.b09a2c2049ed9c373dd0.js"></script>
61
- <script src="assets/chunk.143.da2301d9edb6ad0b5496.js"></script>
62
- <script src="assets/ghost-193c98abe66483968da753e54917e87a.js"></script>
60
+ <script src="assets/chunk.940.159ed6caf77114942465.js"></script>
61
+ <script src="assets/chunk.143.6e2a218178c3c44fac33.js"></script>
62
+ <script src="assets/ghost-a012143b70f1e3bffed74252f7a736df.js"></script>
63
63
  </body>
64
64
  </html>
@@ -54,7 +54,7 @@ module.exports = function imgUrl(requestedImageUrl, options) {
54
54
  // ignore errors and just return the original URL
55
55
  }
56
56
  }
57
-
57
+
58
58
  return requestedImageUrl;
59
59
  }
60
60
 
@@ -117,7 +117,7 @@ function getUnsplashImage(imagePath, sizeOptions) {
117
117
  const parsedUrl = new URL(imagePath);
118
118
  const {requestedSize, imageSizes, requestedFormat} = sizeOptions;
119
119
 
120
- if (requestedFormat) {
120
+ if (requestedFormat) {
121
121
  const supportedFormats = ['avif', 'gif', 'jpg', 'png', 'webp'];
122
122
  if (supportedFormats.includes(requestedFormat)) {
123
123
  parsedUrl.searchParams.set('fm', requestedFormat);
@@ -150,13 +150,13 @@ function getUnsplashImage(imagePath, sizeOptions) {
150
150
  }
151
151
 
152
152
  /**
153
- *
154
- * @param {string} imagePath
155
- * @param {Object} sizeOptions
153
+ *
154
+ * @param {string} imagePath
155
+ * @param {Object} sizeOptions
156
156
  * @param {string} sizeOptions.requestedSize
157
157
  * @param {Object[]} sizeOptions.imageSizes
158
158
  * @param {string} [sizeOptions.requestedFormat]
159
- * @returns
159
+ * @returns
160
160
  */
161
161
  function getImageWithSize(imagePath, sizeOptions) {
162
162
  const hasLeadingSlash = imagePath[0] === '/';
@@ -0,0 +1,32 @@
1
+ // # Readable URL helper
2
+ // Usage: `{{readable_url "https://google.com"}}`
3
+ //
4
+ // Returns a human readable URL for the given URL, e.g. google.com for https://www.google.com?query=1#section
5
+
6
+ const logging = require('@tryghost/logging');
7
+ const sentry = require('../../shared/sentry');
8
+ const errors = require('@tryghost/errors');
9
+ const {SafeString} = require('../services/handlebars');
10
+
11
+ function captureError(message) {
12
+ const error = new errors.IncorrectUsageError({message});
13
+ sentry.captureException(error);
14
+ logging.error(error);
15
+ }
16
+
17
+ module.exports = function readableUrl(inputUrl) {
18
+ if (!inputUrl || typeof inputUrl !== 'string') {
19
+ captureError(`Expected a string, received ${inputUrl}.`);
20
+ return new SafeString('');
21
+ }
22
+
23
+ try {
24
+ const url = new URL(inputUrl);
25
+ const readable = url.hostname.replace(/^www\./, '') + url.pathname.replace(/\/$/, '');
26
+
27
+ return new SafeString(readable);
28
+ } catch (e) {
29
+ captureError(`The string "${inputUrl}" could not be parsed as URL.`);
30
+ return new SafeString(inputUrl);
31
+ }
32
+ };
@@ -63,7 +63,7 @@ async function fetchRecommendations(apiOptions) {
63
63
  */
64
64
  function parseOptions(options) {
65
65
  let limit = options.limit ?? 5;
66
- let order = options.order ?? 'createdAt desc';
66
+ let order = options.order ?? 'created_at desc';
67
67
  let filter = options.filter ?? '';
68
68
  let page = options.page ?? 1;
69
69
 
@@ -2,12 +2,11 @@
2
2
  <ul class="recommendations">
3
3
  {{#each recommendations as |rec|}}
4
4
  <li class="recommendation">
5
- <a href="{{rec.url}}">
5
+ <a href="{{rec.url}}" data-recommendation="{{rec.id}}" target="_blank" rel="noopener">
6
6
  <img class="recommendation-favicon" src="{{rec.favicon}}" alt="{{rec.title}}">
7
- <div class="recommendation-content">
8
- <h5 class="recommendation-title">{{rec.title}}</h5>
9
- <p class="recommendation-reason">{{rec.reason}}</p>
10
- </div>
7
+ <span class="recommendation-url">{{readable_url rec.url}}</span>
8
+ <h5 class="recommendation-title">{{rec.title}}</h5>
9
+ <p class="recommendation-description">{{rec.description}}</p>
11
10
  </a>
12
11
  </li>
13
12
  {{/each}}
@@ -4,6 +4,7 @@ const debug = require('@tryghost/debug')('server');
4
4
  const errors = require('@tryghost/errors');
5
5
  const tpl = require('@tryghost/tpl');
6
6
  const logging = require('@tryghost/logging');
7
+ const metrics = require('@tryghost/metrics');
7
8
  const notify = require('./notify');
8
9
  const moment = require('moment');
9
10
  const stoppable = require('stoppable');
@@ -169,8 +170,16 @@ class GhostServer {
169
170
  try {
170
171
  // If we never fully started, there's nothing to stop
171
172
  if (this.httpServer && this.httpServer.listening) {
173
+ // Time how long it takes to close all in-flight requests
174
+ const startTime = Date.now();
175
+
172
176
  // We stop the server first so that no new long running requests or processes can be started
173
177
  await this._stopServer();
178
+
179
+ const shutdownDuration = Date.now() - startTime;
180
+ if (shutdownDuration > 15000) {
181
+ metrics.metric('long-shutdown', shutdownDuration);
182
+ }
174
183
  }
175
184
  // Do all of the cleanup tasks
176
185
  await this._cleanup();
@@ -0,0 +1,20 @@
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
+ 'page'
13
+ ],
14
+ permissions: true,
15
+ validation: {},
16
+ async query(frame) {
17
+ return await recommendations.incomingRecommendationController.browse(frame);
18
+ }
19
+ }
20
+ };
@@ -209,6 +209,10 @@ module.exports = {
209
209
  return apiFramework.pipeline(require('./recommendations'), localUtils);
210
210
  },
211
211
 
212
+ get incomingRecommendations() {
213
+ return apiFramework.pipeline(require('./incoming-recommendations'), localUtils);
214
+ },
215
+
212
216
  /**
213
217
  * Content API Controllers
214
218
  *
@@ -26,8 +26,9 @@ const rejectPrivateFieldsTransformer = input => mapQuery(input, function (value,
26
26
  function generateOptionsData(frame, options) {
27
27
  return options.reduce((memo, option) => {
28
28
  let value = frame.options?.[option];
29
- if (['include', 'fields', 'formats'].includes(option)) {
30
- value = value?.split(',').sort();
29
+
30
+ if (['include', 'fields', 'formats'].includes(option) && typeof value === 'string') {
31
+ value = value.split(',').sort();
31
32
  }
32
33
 
33
34
  if (option === 'page') {
@@ -19,7 +19,6 @@ module.exports = {
19
19
  'emailAnalytics',
20
20
  'hostSettings',
21
21
  'tenor',
22
- 'editor',
23
22
  'pintura',
24
23
  'signupForm'
25
24
  ];
@@ -91,7 +91,35 @@ function createSetNullableMigration(table, column, options = {}) {
91
91
  if (options.disableForeignKeyChecks) {
92
92
  await knex.raw('SET FOREIGN_KEY_CHECKS=1;').transacting(knex);
93
93
  }
94
- }
94
+ }
95
+ }
96
+ );
97
+ }
98
+
99
+ /**
100
+ * @param {string} table
101
+ * @param {string} from
102
+ * @param {string} to
103
+ *
104
+ * @returns {Migration}
105
+ */
106
+ function createRenameColumnMigration(table, from, to) {
107
+ return createNonTransactionalMigration(
108
+ async function up(knex) {
109
+ const hasColumn = await knex.schema.hasColumn(table, to);
110
+ if (hasColumn) {
111
+ logging.warn(`Renaming ${table}.${from} to ${table}.${to} column - skipping as column ${table}.${to} already exists`);
112
+ } else {
113
+ await commands.renameColumn(table, from, to, knex);
114
+ }
115
+ },
116
+ async function down(knex) {
117
+ const hasColumn = await knex.schema.hasColumn(table, from);
118
+ if (hasColumn) {
119
+ logging.warn(`Renaming ${table}.${to} to ${table}.${from} column - skipping as column ${table}.${from} already exists`);
120
+ } else {
121
+ await commands.renameColumn(table, to, from, knex);
122
+ }
95
123
  }
96
124
  );
97
125
  }
@@ -134,7 +162,8 @@ module.exports = {
134
162
  createAddColumnMigration,
135
163
  createDropColumnMigration,
136
164
  createSetNullableMigration,
137
- createDropNullableMigration
165
+ createDropNullableMigration,
166
+ createRenameColumnMigration
138
167
  };
139
168
 
140
169
  /**
@@ -0,0 +1,3 @@
1
+ const {createRenameColumnMigration} = require('../../utils');
2
+
3
+ module.exports = createRenameColumnMigration('recommendations', 'reason', 'description');
@@ -156,6 +156,25 @@ async function dropColumn(tableName, column, transaction = db.knex, columnSpec =
156
156
  }
157
157
  }
158
158
 
159
+ /**
160
+ * @param {string} tableName
161
+ * @param {string} from
162
+ * @param {string} to
163
+ * @param {import('knex').Knex.Transaction} [transaction]
164
+ */
165
+ async function renameColumn(tableName, from, to, transaction = db.knex) {
166
+ logging.info(`Renaming column '${from}' to '${to}' in table '${tableName}'`);
167
+
168
+ if (DatabaseInfo.isMySQL(transaction)) {
169
+ // The knex helper does a lot of interesting things with foreign keys that are slow on bigger MySQL clusters
170
+ return await transaction.raw(`ALTER TABLE \`${tableName}\` RENAME COLUMN \`${from}\` TO \`${to}\`;`);
171
+ }
172
+
173
+ return await transaction.schema.table(tableName, function (table) {
174
+ table.renameColumn(from, to);
175
+ });
176
+ }
177
+
159
178
  /**
160
179
  * Adds an unique index to a table over the given columns.
161
180
  *
@@ -520,6 +539,7 @@ module.exports = {
520
539
  addForeign,
521
540
  dropForeign,
522
541
  addColumn,
542
+ renameColumn,
523
543
  dropColumn,
524
544
  setNullable,
525
545
  dropNullable,
@@ -1076,7 +1076,7 @@ module.exports = {
1076
1076
  excerpt: {type: 'string', maxlength: 2000, nullable: true},
1077
1077
  featured_image: {type: 'string', maxlength: 2000, nullable: true},
1078
1078
  favicon: {type: 'string', maxlength: 2000, nullable: true},
1079
- reason: {type: 'string', maxlength: 2000, nullable: true},
1079
+ description: {type: 'string', maxlength: 2000, nullable: true},
1080
1080
  one_click_subscribe: {type: 'boolean', nullable: false, defaultTo: false},
1081
1081
  created_at: {type: 'dateTime', nullable: false},
1082
1082
  updated_at: {type: 'dateTime', nullable: true}
@@ -82,7 +82,7 @@ const Product = ghostBookshelf.Model.extend({
82
82
  return benefitToSave.name.toLowerCase() === existingBenefit.get('name').toLowerCase();
83
83
  });
84
84
  if (existingBenefitModel) {
85
- benefitToSave.name = existingBenefitModel.get('name');
85
+ benefitToSave.id = existingBenefitModel.id;
86
86
  }
87
87
  });
88
88
 
@@ -199,7 +199,7 @@ class NewslettersService {
199
199
  }
200
200
 
201
201
  let updatedNewsletter;
202
-
202
+
203
203
  try {
204
204
  updatedNewsletter = await this.NewsletterModel.edit(cleanedAttrs, options);
205
205
  } catch (error) {
@@ -215,7 +215,7 @@ class NewslettersService {
215
215
 
216
216
  // Load relations correctly in the response
217
217
  updatedNewsletter = await this.NewsletterModel.findOne({id: updatedNewsletter.id}, {...options, require: true});
218
-
218
+
219
219
  await this.respondWithEmailVerification(updatedNewsletter, emailsToVerify);
220
220
  return updatedNewsletter;
221
221
  }
@@ -23,7 +23,7 @@ class TwitterOEmbedProvider {
23
23
  * @returns {Promise<boolean>}
24
24
  */
25
25
  async canSupportRequest(url) {
26
- return url.host === 'twitter.com' && TWITTER_PATH_REGEX.test(url.pathname);
26
+ return (url.host === 'twitter.com' || url.host === 'x.com') && TWITTER_PATH_REGEX.test(url.pathname);
27
27
  }
28
28
 
29
29
  /**
@@ -33,6 +33,10 @@ class TwitterOEmbedProvider {
33
33
  * @returns {Promise<object>}
34
34
  */
35
35
  async getOEmbedData(url, externalRequest) {
36
+ if (url.host === 'x.com') { // api is still at twitter.com... also not certain how people are getting x urls because twitter currently redirects every x host to twitter
37
+ url = new URL('https://twitter.com' + url.pathname);
38
+ }
39
+
36
40
  const [match, tweetId] = url.pathname.match(TWITTER_PATH_REGEX);
37
41
  if (!match) {
38
42
  return null;
@@ -28,6 +28,11 @@ class RecommendationServiceWrapper {
28
28
  */
29
29
  service;
30
30
 
31
+ /**
32
+ * @type {import('@tryghost/recommendations').IncomingRecommendationController}
33
+ */
34
+ incomingRecommendationController;
35
+
31
36
  /**
32
37
  * @type {import('@tryghost/recommendations').IncomingRecommendationService}
33
38
  */
@@ -51,6 +56,7 @@ class RecommendationServiceWrapper {
51
56
  RecommendationController,
52
57
  WellknownService,
53
58
  BookshelfClickEventRepository,
59
+ IncomingRecommendationController,
54
60
  IncomingRecommendationService,
55
61
  IncomingRecommendationEmailRenderer
56
62
  } = require('@tryghost/recommendations');
@@ -125,6 +131,10 @@ class RecommendationServiceWrapper {
125
131
  service: this.service
126
132
  });
127
133
 
134
+ this.incomingRecommendationController = new IncomingRecommendationController({
135
+ service: this.incomingRecommendationService
136
+ });
137
+
128
138
  if (labs.isSet('recommendations')) {
129
139
  this.service.init().catch(logging.error);
130
140
  this.incomingRecommendationService.init().catch(logging.error);
@@ -65,11 +65,7 @@ class SettingsBREADService {
65
65
  // @todo: need to make this more generic?
66
66
  const adminUrl = urlUtils.urlFor('admin', true);
67
67
  const signinURL = new URL(adminUrl);
68
- signinURL.hash = `/settings/members/?verifyEmail=${token}`;
69
- // NOTE: to be removed in future, this is to ensure that the new settings are used when enabled
70
- if (labsService && labsService.isSet('adminXSettings')) {
71
- signinURL.hash = `/settings-x/portal/edit?verifyEmail=${token}`;
72
- }
68
+ signinURL.hash = `/settings/portal/edit?verifyEmail=${token}`;
73
69
 
74
70
  return signinURL.href;
75
71
  }
@@ -354,5 +354,8 @@ module.exports = function apiRoutes() {
354
354
  router.put('/recommendations/:id', mw.authAdminApi, http(api.recommendations.edit));
355
355
  router.del('/recommendations/:id', mw.authAdminApi, http(api.recommendations.destroy));
356
356
 
357
+ // Incoming recommendations
358
+ router.get('/incoming_recommendations', mw.authAdminApi, http(api.incomingRecommendations.browse));
359
+
357
360
  return router;
358
361
  };
@@ -197,10 +197,6 @@
197
197
  "url": "https://cdn.jsdelivr.net/ghost/comments-ui@~{version}/umd/comments-ui.min.js",
198
198
  "version": "0.13"
199
199
  },
200
- "editor": {
201
- "url": "https://cdn.jsdelivr.net/ghost/koenig-lexical@~{version}/dist/koenig-lexical.umd.js",
202
- "version": "0.4"
203
- },
204
200
  "signupForm": {
205
201
  "url": "https://cdn.jsdelivr.net/ghost/signup-form@~{version}/umd/signup-form.min.js",
206
202
  "version": "0.1"
@@ -19,7 +19,8 @@ const GA_FEATURES = [
19
19
  'themeErrorsNotification',
20
20
  'outboundLinkTagging',
21
21
  'announcementBar',
22
- 'signupForm'
22
+ 'signupForm',
23
+ 'lexicalEditor'
23
24
  ];
24
25
 
25
26
  // NOTE: this allowlist is meant to be used to filter out any unexpected
@@ -27,8 +28,7 @@ const GA_FEATURES = [
27
28
  const BETA_FEATURES = [
28
29
  'i18n',
29
30
  'activitypub',
30
- 'webmentions',
31
- 'lexicalEditor'
31
+ 'webmentions'
32
32
  ];
33
33
 
34
34
  const ALPHA_FEATURES = [
@@ -37,7 +37,6 @@ const ALPHA_FEATURES = [
37
37
  'websockets',
38
38
  'stripeAutomaticTax',
39
39
  'emailCustomization',
40
- 'adminXSettings',
41
40
  'mailEvents',
42
41
  'collectionsCard',
43
42
  'tipsAndDonations',