ghost 5.115.0 → 5.115.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 (197) hide show
  1. package/components/tryghost-adapter-cache-redis-5.115.1.tgz +0 -0
  2. package/components/tryghost-adapter-manager-5.115.1.tgz +0 -0
  3. package/components/{tryghost-announcement-bar-settings-5.115.0.tgz → tryghost-announcement-bar-settings-5.115.1.tgz} +0 -0
  4. package/components/{tryghost-api-framework-5.115.0.tgz → tryghost-api-framework-5.115.1.tgz} +0 -0
  5. package/components/tryghost-constants-5.115.1.tgz +0 -0
  6. package/components/tryghost-custom-fonts-5.115.1.tgz +0 -0
  7. package/components/{tryghost-custom-theme-settings-service-5.115.0.tgz → tryghost-custom-theme-settings-service-5.115.1.tgz} +0 -0
  8. package/components/{tryghost-data-generator-5.115.0.tgz → tryghost-data-generator-5.115.1.tgz} +0 -0
  9. package/components/tryghost-domain-events-5.115.1.tgz +0 -0
  10. package/components/tryghost-donations-5.115.1.tgz +0 -0
  11. package/components/{tryghost-email-addresses-5.115.0.tgz → tryghost-email-addresses-5.115.1.tgz} +0 -0
  12. package/components/{tryghost-email-content-generator-5.115.0.tgz → tryghost-email-content-generator-5.115.1.tgz} +0 -0
  13. package/components/{tryghost-email-events-5.115.0.tgz → tryghost-email-events-5.115.1.tgz} +0 -0
  14. package/components/tryghost-email-service-5.115.1.tgz +0 -0
  15. package/components/{tryghost-email-suppression-list-5.115.0.tgz → tryghost-email-suppression-list-5.115.1.tgz} +0 -0
  16. package/components/{tryghost-express-dynamic-redirects-5.115.0.tgz → tryghost-express-dynamic-redirects-5.115.1.tgz} +0 -0
  17. package/components/tryghost-ghost-5.115.1.tgz +0 -0
  18. package/components/tryghost-html-to-plaintext-5.115.1.tgz +0 -0
  19. package/components/tryghost-i18n-5.115.1.tgz +0 -0
  20. package/components/{tryghost-importer-handler-content-files-5.115.0.tgz → tryghost-importer-handler-content-files-5.115.1.tgz} +0 -0
  21. package/components/tryghost-in-memory-repository-5.115.1.tgz +0 -0
  22. package/components/{tryghost-job-manager-5.115.0.tgz → tryghost-job-manager-5.115.1.tgz} +0 -0
  23. package/components/{tryghost-link-redirects-5.115.0.tgz → tryghost-link-redirects-5.115.1.tgz} +0 -0
  24. package/components/tryghost-link-replacer-5.115.1.tgz +0 -0
  25. package/components/{tryghost-magic-link-5.115.0.tgz → tryghost-magic-link-5.115.1.tgz} +0 -0
  26. package/components/{tryghost-mailgun-client-5.115.0.tgz → tryghost-mailgun-client-5.115.1.tgz} +0 -0
  27. package/components/tryghost-member-attribution-5.115.1.tgz +0 -0
  28. package/components/{tryghost-member-events-5.115.0.tgz → tryghost-member-events-5.115.1.tgz} +0 -0
  29. package/components/{tryghost-members-api-5.115.0.tgz → tryghost-members-api-5.115.1.tgz} +0 -0
  30. package/components/{tryghost-members-csv-5.115.0.tgz → tryghost-members-csv-5.115.1.tgz} +0 -0
  31. package/components/{tryghost-members-offers-5.115.0.tgz → tryghost-members-offers-5.115.1.tgz} +0 -0
  32. package/components/{tryghost-members-payments-5.115.0.tgz → tryghost-members-payments-5.115.1.tgz} +0 -0
  33. package/components/{tryghost-milestones-5.115.0.tgz → tryghost-milestones-5.115.1.tgz} +0 -0
  34. package/components/{tryghost-minifier-5.115.0.tgz → tryghost-minifier-5.115.1.tgz} +0 -0
  35. package/components/{tryghost-mw-error-handler-5.115.0.tgz → tryghost-mw-error-handler-5.115.1.tgz} +0 -0
  36. package/components/{tryghost-mw-version-match-5.115.0.tgz → tryghost-mw-version-match-5.115.1.tgz} +0 -0
  37. package/components/tryghost-mw-vhost-5.115.1.tgz +0 -0
  38. package/components/tryghost-post-events-5.115.1.tgz +0 -0
  39. package/components/{tryghost-post-revisions-5.115.0.tgz → tryghost-post-revisions-5.115.1.tgz} +0 -0
  40. package/components/{tryghost-posts-service-5.115.0.tgz → tryghost-posts-service-5.115.1.tgz} +0 -0
  41. package/components/{tryghost-prometheus-metrics-5.115.0.tgz → tryghost-prometheus-metrics-5.115.1.tgz} +0 -0
  42. package/components/tryghost-recommendations-5.115.1.tgz +0 -0
  43. package/components/{tryghost-security-5.115.0.tgz → tryghost-security-5.115.1.tgz} +0 -0
  44. package/components/{tryghost-slack-notifications-5.115.0.tgz → tryghost-slack-notifications-5.115.1.tgz} +0 -0
  45. package/components/{tryghost-tiers-5.115.0.tgz → tryghost-tiers-5.115.1.tgz} +0 -0
  46. package/components/tryghost-webmentions-5.115.1.tgz +0 -0
  47. package/content/themes/casper/LICENSE +1 -1
  48. package/content/themes/casper/README.md +1 -1
  49. package/content/themes/source/LICENSE +1 -1
  50. package/content/themes/source/README.md +1 -1
  51. package/content/themes/source/assets/built/screen.css +1 -1
  52. package/content/themes/source/assets/built/screen.css.map +1 -1
  53. package/content/themes/source/assets/css/screen.css +11 -6
  54. package/content/themes/source/partials/feature-image.hbs +2 -2
  55. package/core/boot.js +3 -1
  56. package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +23497 -23041
  57. package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
  58. package/core/built/admin/assets/admin-x-demo/{index-0040480a.mjs → index-15df2af5.mjs} +4 -3
  59. package/core/built/admin/assets/admin-x-demo/{modals-fb35c86c.mjs → modals-8ca61d78.mjs} +67 -65
  60. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-806ef39c.mjs → CodeEditorView-d2e6872f.mjs} +2 -2
  61. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
  62. package/core/built/admin/assets/admin-x-settings/{index-376f847c.mjs → index-8e8821e5.mjs} +2 -2
  63. package/core/built/admin/assets/admin-x-settings/{index-8fa19303.mjs → index-f5cb3db3.mjs} +3104 -3094
  64. package/core/built/admin/assets/admin-x-settings/{modals-36775d71.mjs → modals-e8ae4d46.mjs} +3 -3
  65. package/core/built/admin/assets/{chunk.524.31419fdf6fb3859ecc1e.js → chunk.524.2439684964c164c598ab.js} +6 -6
  66. package/core/built/admin/assets/{chunk.582.08c816d5e4ab766486a7.js → chunk.582.bf5a2bbb2c4eb69ef1e7.js} +10 -10
  67. package/core/built/admin/assets/ghost-327b17ea23cb8c89bd7e6a51e18e8506.css +1 -0
  68. package/core/built/admin/assets/ghost-dark-f30a597ac19632a118939492591c531b.css +1 -0
  69. package/core/built/admin/assets/{ghost-938b3d9c29e3564a53a22f8c8f82d351.js → ghost-df7b9558260aa27d18b195ee895b487d.js} +181 -159
  70. package/core/built/admin/assets/stats/stats.js +11824 -0
  71. package/core/built/admin/index.html +4 -4
  72. package/core/frontend/helpers/ghost_head.js +3 -1
  73. package/core/frontend/src/cards/css/cta.css +1 -1
  74. package/core/server/api/endpoints/slugs.js +6 -2
  75. package/core/server/data/importer/import-manager.js +2 -2
  76. package/core/server/data/importer/importers/importer-revue.js +128 -0
  77. package/core/server/data/importer/importers/json-to-html.js +107 -0
  78. package/core/server/data/migrations/utils/tables.js +2 -4
  79. package/core/server/lib/bootstrap-socket.js +87 -0
  80. package/core/server/lib/package-json/index.js +1 -0
  81. package/core/server/lib/package-json/package-json.js +160 -0
  82. package/core/server/lib/package-json/parse.js +57 -0
  83. package/core/server/models/base/plugins/actions.js +44 -31
  84. package/core/server/models/base/plugins/generate-slug.js +6 -0
  85. package/core/server/notify.js +1 -1
  86. package/core/server/services/activitypub/ActivityPubService.ts +1 -1
  87. package/core/server/services/api-version-compatibility/APIVersionCompatibilityService.js +99 -0
  88. package/core/server/services/api-version-compatibility/VersionNotificationsDataService.js +80 -0
  89. package/core/server/services/api-version-compatibility/extract-api-key.js +57 -0
  90. package/core/server/services/api-version-compatibility/index.js +2 -2
  91. package/core/server/services/api-version-compatibility/mw-api-version-mismatch.js +31 -0
  92. package/core/server/services/audience-feedback/AudienceFeedbackController.js +85 -0
  93. package/core/server/services/audience-feedback/AudienceFeedbackService.js +34 -0
  94. package/core/server/services/audience-feedback/Feedback.js +35 -0
  95. package/core/server/services/audience-feedback/index.js +4 -2
  96. package/core/server/services/auth/session/emails/signin.js +168 -0
  97. package/core/server/services/auth/session/index.js +2 -2
  98. package/core/server/services/auth/session/session-from-token.js +69 -0
  99. package/core/server/services/auth/session/session-service.js +364 -0
  100. package/core/server/services/email-analytics/EmailAnalyticsProviderMailgun.js +62 -0
  101. package/core/server/services/email-analytics/EmailAnalyticsService.js +552 -0
  102. package/core/server/services/email-analytics/EmailAnalyticsServiceWrapper.js +3 -3
  103. package/core/server/services/email-analytics/EventProcessingResult.js +66 -0
  104. package/core/server/services/explore-ping/ExplorePingService.js +106 -0
  105. package/core/server/services/explore-ping/index.js +31 -0
  106. package/core/server/services/identity-tokens/IdentityTokenService.js +30 -0
  107. package/core/server/services/identity-tokens/IdentityTokenService.ts +28 -0
  108. package/core/server/services/identity-tokens/IdentityTokenServiceWrapper.js +1 -1
  109. package/core/server/services/invitations/accept.js +5 -2
  110. package/core/server/services/mail-events/BookshelfMailEventRepository.js +2 -2
  111. package/core/server/services/mail-events/InMemoryMailEventRepository.js +10 -0
  112. package/core/server/services/mail-events/InMemoryMailEventRepository.ts +8 -0
  113. package/core/server/services/mail-events/MailEvent.js +20 -0
  114. package/core/server/services/mail-events/MailEvent.ts +10 -0
  115. package/core/server/services/mail-events/MailEventRepository.js +2 -0
  116. package/core/server/services/mail-events/MailEventRepository.ts +5 -0
  117. package/core/server/services/mail-events/MailEventService.js +124 -0
  118. package/core/server/services/mail-events/MailEventService.ts +169 -0
  119. package/core/server/services/mail-events/index.js +1 -1
  120. package/core/server/services/mail-events/libraries.d.ts +2 -0
  121. package/core/server/services/members/CaptchaService.js +80 -0
  122. package/core/server/services/members/api.js +1 -1
  123. package/core/server/services/members/importer/MembersCSVImporter.js +464 -0
  124. package/core/server/services/members/importer/MembersCSVImporterStripeUtils.js +194 -0
  125. package/core/server/services/members/importer/email-template.js +182 -0
  126. package/core/server/services/members/importer/index.js +30 -0
  127. package/core/server/services/members/members-ssr.js +333 -0
  128. package/core/server/services/members/service.js +2 -2
  129. package/core/server/services/posts/stats/PostStats.js +13 -0
  130. package/core/server/services/route-settings/SettingsPathManager.js +47 -0
  131. package/core/server/services/route-settings/index.js +1 -1
  132. package/core/server/services/stripe/README.md +63 -0
  133. package/core/server/services/stripe/StripeAPI.js +931 -0
  134. package/core/server/services/stripe/StripeMigrations.js +613 -0
  135. package/core/server/services/stripe/StripeService.js +175 -0
  136. package/core/server/services/stripe/WebhookController.js +100 -0
  137. package/core/server/services/stripe/WebhookManager.js +175 -0
  138. package/core/server/services/stripe/events/StripeLiveDisabledEvent.js +23 -0
  139. package/core/server/services/stripe/events/StripeLiveEnabledEvent.js +23 -0
  140. package/core/server/services/stripe/events/index.js +4 -0
  141. package/core/server/services/stripe/service.js +1 -1
  142. package/core/server/services/stripe/services/webhook/CheckoutSessionEventService.js +255 -0
  143. package/core/server/services/stripe/services/webhook/InvoiceEventService.js +70 -0
  144. package/core/server/services/stripe/services/webhook/SubscriptionEventService.js +54 -0
  145. package/core/server/services/themes/loader.js +1 -1
  146. package/core/server/services/themes/to-json.js +1 -1
  147. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  148. package/core/server/web/shared/middleware/cache-control.js +51 -0
  149. package/core/server/web/shared/middleware/index.js +1 -1
  150. package/core/server/web/well-known.js +1 -1
  151. package/core/shared/labs.js +3 -1
  152. package/core/shared/settings-cache/CacheManager.js +64 -6
  153. package/package.json +103 -134
  154. package/tsconfig.tsbuildinfo +1 -1
  155. package/yarn.lock +7 -93
  156. package/components/tryghost-adapter-cache-redis-5.115.0.tgz +0 -0
  157. package/components/tryghost-adapter-manager-5.115.0.tgz +0 -0
  158. package/components/tryghost-api-version-compatibility-service-5.115.0.tgz +0 -0
  159. package/components/tryghost-audience-feedback-5.115.0.tgz +0 -0
  160. package/components/tryghost-bookshelf-repository-5.115.0.tgz +0 -0
  161. package/components/tryghost-bootstrap-socket-5.115.0.tgz +0 -0
  162. package/components/tryghost-captcha-service-5.115.0.tgz +0 -0
  163. package/components/tryghost-constants-5.115.0.tgz +0 -0
  164. package/components/tryghost-custom-fonts-5.115.0.tgz +0 -0
  165. package/components/tryghost-domain-events-5.115.0.tgz +0 -0
  166. package/components/tryghost-donations-5.115.0.tgz +0 -0
  167. package/components/tryghost-email-analytics-provider-mailgun-5.115.0.tgz +0 -0
  168. package/components/tryghost-email-analytics-service-5.115.0.tgz +0 -0
  169. package/components/tryghost-email-service-5.115.0.tgz +0 -0
  170. package/components/tryghost-extract-api-key-5.115.0.tgz +0 -0
  171. package/components/tryghost-ghost-5.115.0.tgz +0 -0
  172. package/components/tryghost-html-to-plaintext-5.115.0.tgz +0 -0
  173. package/components/tryghost-i18n-5.115.0.tgz +0 -0
  174. package/components/tryghost-identity-token-service-5.115.0.tgz +0 -0
  175. package/components/tryghost-importer-revue-5.115.0.tgz +0 -0
  176. package/components/tryghost-in-memory-repository-5.115.0.tgz +0 -0
  177. package/components/tryghost-link-replacer-5.115.0.tgz +0 -0
  178. package/components/tryghost-mail-events-5.115.0.tgz +0 -0
  179. package/components/tryghost-member-attribution-5.115.0.tgz +0 -0
  180. package/components/tryghost-members-importer-5.115.0.tgz +0 -0
  181. package/components/tryghost-members-ssr-5.115.0.tgz +0 -0
  182. package/components/tryghost-members-stripe-service-5.115.0.tgz +0 -0
  183. package/components/tryghost-mw-api-version-mismatch-5.115.0.tgz +0 -0
  184. package/components/tryghost-mw-cache-control-5.115.0.tgz +0 -0
  185. package/components/tryghost-mw-session-from-token-5.115.0.tgz +0 -0
  186. package/components/tryghost-mw-update-user-last-seen-5.115.0.tgz +0 -0
  187. package/components/tryghost-mw-vhost-5.115.0.tgz +0 -0
  188. package/components/tryghost-package-json-5.115.0.tgz +0 -0
  189. package/components/tryghost-post-events-5.115.0.tgz +0 -0
  190. package/components/tryghost-recommendations-5.115.0.tgz +0 -0
  191. package/components/tryghost-referrers-5.115.0.tgz +0 -0
  192. package/components/tryghost-session-service-5.115.0.tgz +0 -0
  193. package/components/tryghost-settings-path-manager-5.115.0.tgz +0 -0
  194. package/components/tryghost-version-notifications-data-service-5.115.0.tgz +0 -0
  195. package/components/tryghost-webmentions-5.115.0.tgz +0 -0
  196. package/core/built/admin/assets/ghost-c2a7c4a1b76550c4219adb2ed4124ce0.css +0 -1
  197. package/core/built/admin/assets/ghost-dark-f91e4a479c6d38d94d5d1b14727871dc.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%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.115%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%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%22685db18f10%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%2215e68d0314%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%22a1e3245cf0%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%22c297b6fa8f%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22cc9549f09d%22%2C%22adminXActivitypubCustomUrl%22%3A%22https%3A%2F%2Fcdn.jsdelivr.net%2Fghost%2Fadmin-x-activitypub%400%2Fdist%2Fadmin-x-activitypub.js%22%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.115%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%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%22685db18f10%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%2253d9d1eb76%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%223812b50e35%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%220667582c6d%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22cc9549f09d%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%2256566f9e00%22%2C%22adminXActivitypubCustomUrl%22%3A%22https%3A%2F%2Fcdn.jsdelivr.net%2Fghost%2Fadmin-x-activitypub%400%2Fdist%2Fadmin-x-activitypub.js%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-0ede59da8efb5e28fa929557f7ff7154.css">
40
- <link integrity="" rel="stylesheet" href="assets/ghost-c2a7c4a1b76550c4219adb2ed4124ce0.css" title="light">
40
+ <link integrity="" rel="stylesheet" href="assets/ghost-327b17ea23cb8c89bd7e6a51e18e8506.css" title="light">
41
41
 
42
42
 
43
43
  </head>
@@ -58,7 +58,7 @@
58
58
 
59
59
  <script src="assets/vendor-68a4aa424a179a90f5bbc2b750def576.js"></script>
60
60
  <script src="assets/chunk.874.461cb3cf5b6b36915f8c.js"></script>
61
- <script src="assets/chunk.524.31419fdf6fb3859ecc1e.js"></script>
62
- <script src="assets/ghost-938b3d9c29e3564a53a22f8c8f82d351.js"></script>
61
+ <script src="assets/chunk.524.2439684964c164c598ab.js"></script>
62
+ <script src="assets/ghost-df7b9558260aa27d18b195ee895b487d.js"></script>
63
63
  </body>
64
64
  </html>
@@ -151,6 +151,8 @@ function getWebmentionDiscoveryLink() {
151
151
  }
152
152
 
153
153
  function getTinybirdTrackerScript(dataRoot) {
154
+ const src = urlUtils.getSubdir() + '/public/ghost-stats.js';
155
+
154
156
  const endpoint = config.get('tinybird:tracker:endpoint');
155
157
  const token = config.get('tinybird:tracker:token');
156
158
  const datasource = config.get('tinybird:tracker:datasource');
@@ -162,7 +164,7 @@ function getTinybirdTrackerScript(dataRoot) {
162
164
  member_status: dataRoot.member?.status
163
165
  }, (value, key) => `tb_${key}="${value}"`).join(' ');
164
166
 
165
- return `<script defer src="/public/ghost-stats.js" data-stringify-payload="false" ${datasource ? `data-datasource="${datasource}"` : ''} data-storage="localStorage" data-host="${endpoint}" data-token="${token}" ${tbParams}></script>`;
167
+ return `<script defer src="${src}" data-stringify-payload="false" ${datasource ? `data-datasource="${datasource}"` : ''} data-storage="localStorage" data-host="${endpoint}" data-token="${token}" ${tbParams}></script>`;
166
168
  }
167
169
 
168
170
  function getHCaptchaScript() {
@@ -95,7 +95,7 @@
95
95
  .kg-cta-content {
96
96
  display: flex;
97
97
  padding: 1.5em;
98
- gap: 1.5em 1.5em 1.7em;
98
+ gap: 1.5em;
99
99
  }
100
100
 
101
101
  @media (max-width: 600px) {
@@ -21,7 +21,8 @@ const controller = {
21
21
  },
22
22
  options: [
23
23
  'include',
24
- 'type'
24
+ 'type',
25
+ 'id'
25
26
  ],
26
27
  data: [
27
28
  'name'
@@ -32,6 +33,9 @@ const controller = {
32
33
  type: {
33
34
  required: true,
34
35
  values: Object.keys(allowedTypes)
36
+ },
37
+ id: {
38
+ required: false
35
39
  }
36
40
  },
37
41
  data: {
@@ -41,7 +45,7 @@ const controller = {
41
45
  }
42
46
  },
43
47
  query(frame) {
44
- return models.Base.Model.generateSlug(allowedTypes[frame.options.type], frame.data.name, {status: 'all'})
48
+ return models.Base.Model.generateSlug(allowedTypes[frame.options.type], frame.data.name, {status: 'all', modelId: frame.options.id})
45
49
  .then((slug) => {
46
50
  if (!slug) {
47
51
  return Promise.reject(new errors.InternalServerError({
@@ -16,7 +16,7 @@ const RevueHandler = require('./handlers/revue');
16
16
  const JSONHandler = require('./handlers/json');
17
17
  const MarkdownHandler = require('./handlers/markdown');
18
18
  const ContentFileImporter = require('./importers/ContentFileImporter');
19
- const RevueImporter = require('@tryghost/importer-revue');
19
+ const RevueImporter = require('./importers/importer-revue');
20
20
  const DataImporter = require('./importers/data');
21
21
  const urlUtils = require('../../../shared/url-utils');
22
22
  const {GhostMailer} = require('../../services/mail');
@@ -225,7 +225,7 @@ class ImportManager {
225
225
 
226
226
  try {
227
227
  await extract(filePath, tmpDir);
228
-
228
+
229
229
  // Set permissions for all extracted files
230
230
  const files = glob.sync('**/*', {cwd: tmpDir, nodir: true});
231
231
  await Promise.all(files.map(file => fs.chmod(path.join(tmpDir, file), 0o644)));
@@ -0,0 +1,128 @@
1
+ const debug = require('@tryghost/debug')('importer:revue');
2
+ const {slugify} = require('@tryghost/string');
3
+ const papaparse = require('papaparse');
4
+ const _ = require('lodash');
5
+
6
+ const JSONToHTML = require('./json-to-html');
7
+
8
+ /**
9
+ * Build posts out of the issue and item data
10
+ *
11
+ * @param {Object} revueData
12
+ * @return {Array}
13
+ */
14
+ const fetchPostsFromData = (revueData) => {
15
+ const itemData = JSON.parse(revueData.items);
16
+ const issueData = papaparse.parse(revueData.issues, {
17
+ header: true,
18
+ skipEmptyLines: true,
19
+ transform(value, header) {
20
+ if (header === 'id') {
21
+ return parseInt(value);
22
+ }
23
+ return value;
24
+ }
25
+ });
26
+
27
+ const posts = [];
28
+
29
+ issueData.data.forEach((postMeta) => {
30
+ // Convert issues to posts
31
+ if (!postMeta.subject) {
32
+ return;
33
+ }
34
+
35
+ const revuePostID = postMeta.id;
36
+ let postHTML = JSONToHTML.cleanCsvHTML(postMeta.description);
37
+
38
+ const postItems = _.filter(itemData, {issue_id: revuePostID});
39
+ const sortedPostItems = _.sortBy(postItems, o => o.order);
40
+
41
+ if (postItems) {
42
+ const convertedItems = JSONToHTML.itemsToHtml(sortedPostItems);
43
+ postHTML = `${postHTML}${convertedItems}`;
44
+ }
45
+
46
+ const postDate = JSONToHTML.getPostDate(postMeta);
47
+ const postSlug = slugify(postMeta.subject).slice(0, 190);
48
+
49
+ posts.push({
50
+ comment_id: revuePostID,
51
+ title: postMeta.subject,
52
+ slug: postSlug,
53
+ status: JSONToHTML.getPostStatus(postMeta),
54
+ visibility: 'public',
55
+ created_at: postDate,
56
+ published_at: postDate,
57
+ updated_at: postDate,
58
+ html: postHTML
59
+ });
60
+ });
61
+
62
+ return posts;
63
+ };
64
+
65
+ /**
66
+ *
67
+ * @param {*} revueData
68
+ */
69
+ const buildSubscriberList = (revueData) => {
70
+ const subscribers = [];
71
+
72
+ const subscriberData = papaparse.parse(revueData.subscribers, {
73
+ header: true,
74
+ skipEmptyLines: true
75
+ });
76
+
77
+ subscriberData.data.forEach((subscriber) => {
78
+ subscribers.push({
79
+ email: subscriber.email,
80
+ name: `${subscriber.first_name} ${subscriber.last_name}`.trim(),
81
+ created_at: subscriber.created_at,
82
+ subscribed: true
83
+ });
84
+ });
85
+
86
+ return subscribers;
87
+ };
88
+
89
+ const RevueImporter = {
90
+ type: 'revue',
91
+ preProcess: function (importData) {
92
+ debug('preProcess');
93
+ importData.preProcessedByRevue = true;
94
+
95
+ // TODO: this should really be in doImport
96
+ // No posts to prePprocess, return early
97
+ if (!importData?.revue?.revue?.issues) {
98
+ return importData;
99
+ }
100
+
101
+ // This processed data goes to the data importer
102
+ importData.data = {
103
+ meta: {version: '5.0.0'},
104
+ data: {}
105
+ };
106
+
107
+ importData.data.data.posts = this.importPosts(importData.revue.revue);
108
+
109
+ // No subscribers to import, we're done
110
+ if (!importData?.revue?.revue?.subscribers) {
111
+ return importData;
112
+ }
113
+
114
+ importData.data.data.revue_subscribers = this.importSubscribers(importData.revue.revue);
115
+
116
+ return importData;
117
+ },
118
+ doImport: function (importData) {
119
+ debug('doImport');
120
+
121
+ return importData;
122
+ },
123
+
124
+ importPosts: fetchPostsFromData,
125
+ importSubscribers: buildSubscriberList
126
+ };
127
+
128
+ module.exports = RevueImporter;
@@ -0,0 +1,107 @@
1
+ const SimpleDom = require('simple-dom');
2
+ const serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap);
3
+ const imageCard = require('@tryghost/kg-default-cards/lib/cards/image.js');
4
+ const embedCard = require('@tryghost/kg-default-cards/lib/cards/embed.js');
5
+
6
+ // Take the array of items for a specific post and return the converted HTML
7
+ const itemsToHtml = (items) => {
8
+ let itemHTMLChunks = [];
9
+ items.forEach((item) => {
10
+ let type = item.item_type;
11
+
12
+ if (type === 'header') {
13
+ itemHTMLChunks.push(`<h3>${item.title}</h3>`);
14
+ } else if (type === 'text') {
15
+ itemHTMLChunks.push(item.description); // THis is basic text HTML with <p>, <b>, <a>, etc (no media)
16
+ } else if (type === 'image') {
17
+ // We have 2 values to work with here. `image` is smaller and most suitable, and `original_image_url` is the full-res that would need to be resized
18
+ // - item.image (https://s3.amazonaws.com/revue/items/images/019/005/542/web/anita-austvika-C-JUrfmYqcw-unsplash.jpg?1667924147)
19
+ // - item.original_image_url (https://s3.amazonaws.com/revue/items/images/019/005/542/original/anita-austvika-C-JUrfmYqcw-unsplash.jpg?1667924147)
20
+ let cardOpts = {
21
+ env: {dom: new SimpleDom.Document()},
22
+ payload: {
23
+ src: item.image,
24
+ caption: item.description
25
+ }
26
+ };
27
+
28
+ itemHTMLChunks.push(serializer.serialize(imageCard.render(cardOpts)));
29
+ } else if (type === 'link') {
30
+ // This could be a bookmark, or it could be a paragraph of text with a linked header, there's no way to tell
31
+ // The safest option here is to output an image with text under it
32
+ let cardOpts = {
33
+ env: {dom: new SimpleDom.Document()},
34
+ payload: {
35
+ src: item.image,
36
+ caption: item.title,
37
+ href: item.url
38
+ }
39
+ };
40
+ itemHTMLChunks.push(serializer.serialize(imageCard.render(cardOpts)));
41
+
42
+ let linkHTML = `<h4><a href="${item.url}">${item.title}</a></h4>${item.description}`;
43
+ itemHTMLChunks.push(linkHTML);
44
+ } else if (type === 'tweet') {
45
+ // Should this be an oEmbed call? Probably.
46
+ itemHTMLChunks.push(`<figure class="kg-card kg-embed-card">
47
+ <blockquote class="twitter-tweet"><a href="${item.url}"></a></blockquote>
48
+ <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
49
+ </figure>`);
50
+ } else if (type === 'video') {
51
+ const isLongYouTube = /youtube.com/.test(item.url);
52
+ const isShortYouTube = /youtu.be/.test(item.url);
53
+ const isVimeo = /vimeo.com/.test(item.url);
54
+ let videoHTML = '';
55
+ if (isLongYouTube) {
56
+ let videoID = item.url.replace(/https?:\/\/(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]*)/gi, '$1');
57
+ videoHTML = `<iframe width="200" height="113" src="https://www.youtube.com/embed/${videoID}?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`;
58
+ } else if (isShortYouTube) {
59
+ let videoID = item.url.replace(/https?:\/\/(?:www\.)?youtu\.be\/([a-zA-Z0-9_-]*)/gi, '$1');
60
+ videoHTML = `<iframe width="200" height="113" src="https://www.youtube.com/embed/${videoID}?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`;
61
+ } else if (isVimeo) {
62
+ let videoID = item.url.replace(/https?:\/\/(?:www\.)?vimeo\.com\/([0-9]+)/gi, '$1');
63
+ videoHTML = `<iframe src="https://player.vimeo.com/video/${videoID}" width="200" height="113" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>`;
64
+ }
65
+ let cardOpts = {
66
+ env: {dom: new SimpleDom.Document()},
67
+ payload: {
68
+ html: videoHTML,
69
+ caption: item.description
70
+ }
71
+ };
72
+
73
+ itemHTMLChunks.push(serializer.serialize(embedCard.render(cardOpts)));
74
+ }
75
+ });
76
+ return itemHTMLChunks.join('\n');
77
+ };
78
+
79
+ const getPostDate = (data) => {
80
+ const isPublished = (data.sent_at) ? true : false; // This is how we determine is a post is published or not
81
+ const postDate = (isPublished) ? new Date(data.sent_at) : new Date();
82
+
83
+ return postDate.toISOString();
84
+ };
85
+
86
+ const getPostStatus = (data) => {
87
+ const isPublished = (data.sent_at) ? true : false; // This is how we determine is a post is published or not
88
+ return (isPublished) ? 'published' : 'draft';
89
+ };
90
+
91
+ const cleanCsvHTML = (data) => {
92
+ // Blockquotes need to have some sort of wrapping elements around all contents
93
+ // Wrap all content in <p> tags. The HTML to Mobiledoc parse can handle duplicate <p> tags.
94
+ data = data.replace(/<blockquote.*?>(.*?)<\/blockquote>/gm, '<blockquote><p>$1</p></blockquote>');
95
+
96
+ // These exports have a lot of <p><br></p> that we don't want
97
+ data = data.replace(/<p><br><\/p>/gm, '');
98
+
99
+ return data;
100
+ };
101
+
102
+ module.exports = {
103
+ itemsToHtml,
104
+ getPostDate,
105
+ getPostStatus,
106
+ cleanCsvHTML
107
+ };
@@ -5,8 +5,7 @@ const {createIrreversibleMigration, createNonTransactionalMigration} = require('
5
5
  /**
6
6
  * Creates a migrations which will add a new table from schema.js to the database
7
7
  * @param {string} name - table name
8
- * @param {Object} tableSpec - copy of table schema definition as defined in schema.js at the moment of writing the migration,
9
- * this parameter MUST be present, otherwise @daniellockyer will hunt you down
8
+ * @param {Object} tableSpec - copy of table schema definition as defined in schema.js at the moment of writing the migration, this parameter MUST be present
10
9
  *
11
10
  * @returns {Object} migration object returning config/up/down properties
12
11
  */
@@ -60,8 +59,7 @@ function dropTables(names) {
60
59
  /**
61
60
  * Creates a migration which will drop an existing table and then re-add a new table based on provided spec
62
61
  * @param {string} name - table name
63
- * @param {Object} tableSpec - copy of table schema definition as defined in schema.js at the moment of writing the migration,
64
- * this parameter MUST be present, otherwise @daniellockyer will hunt you down
62
+ * @param {Object} tableSpec - copy of table schema definition as defined in schema.js at the moment of writing the migration, this parameter MUST be present
65
63
  *
66
64
  * @returns {Object} migration object returning config/up/down properties
67
65
  */
@@ -0,0 +1,87 @@
1
+ const logging = require('@tryghost/logging');
2
+
3
+ module.exports.connectAndSend = (socketAddress, message) => {
4
+ // Very basic guard against bad calls
5
+ if (!socketAddress || !socketAddress.host || !socketAddress.port || !logging || !logging.info || !logging.warn || !message) {
6
+ return Promise.resolve();
7
+ }
8
+
9
+ const net = require('net');
10
+ const client = new net.Socket();
11
+
12
+ return new Promise((resolve) => {
13
+ const connect = (options = {}) => {
14
+ let wasResolved = false;
15
+
16
+ const waitTimeout = setTimeout(() => {
17
+ logging.info('Bootstrap socket timed out.');
18
+
19
+ if (!client.destroyed) {
20
+ client.destroy();
21
+ }
22
+
23
+ if (wasResolved) {
24
+ return;
25
+ }
26
+
27
+ wasResolved = true;
28
+ resolve();
29
+ }, 1000 * 5);
30
+
31
+ client.connect(socketAddress.port, socketAddress.host, () => {
32
+ if (waitTimeout) {
33
+ clearTimeout(waitTimeout);
34
+ }
35
+
36
+ client.write(JSON.stringify(message));
37
+
38
+ if (wasResolved) {
39
+ return;
40
+ }
41
+
42
+ wasResolved = true;
43
+ resolve();
44
+ });
45
+
46
+ client.on('close', () => {
47
+ logging.info('Bootstrap client was closed.');
48
+
49
+ if (waitTimeout) {
50
+ clearTimeout(waitTimeout);
51
+ }
52
+ });
53
+
54
+ client.on('error', (err) => {
55
+ logging.warn(`Can't connect to the bootstrap socket (${socketAddress.host} ${socketAddress.port}) ${err.code}.`);
56
+
57
+ client.removeAllListeners();
58
+
59
+ if (waitTimeout) {
60
+ clearTimeout(waitTimeout);
61
+ }
62
+
63
+ if (options.tries < 3) {
64
+ logging.warn(`Tries: ${options.tries}`);
65
+
66
+ // retry
67
+ logging.warn('Retrying...');
68
+
69
+ options.tries = options.tries + 1;
70
+ const retryTimeout = setTimeout(() => {
71
+ clearTimeout(retryTimeout);
72
+ connect(options);
73
+ }, 150);
74
+ } else {
75
+ if (wasResolved) {
76
+ return;
77
+ }
78
+
79
+ wasResolved = true;
80
+ resolve();
81
+ }
82
+ });
83
+ };
84
+
85
+ connect({tries: 0});
86
+ });
87
+ };
@@ -0,0 +1 @@
1
+ module.exports = require('./package-json');
@@ -0,0 +1,160 @@
1
+ /*
2
+ * # Package Utils
3
+ *
4
+ * Ghost has support for several different types of sub-packages:
5
+ * - Themes: have always been packages, but we're going to lean more heavily on npm & package.json in future
6
+ * - Adapters: replace fundamental pieces like storage, will become npm modules
7
+ *
8
+ * These utils facilitate loading, reading, managing etc, packages from the file system.
9
+ *
10
+ */
11
+ const _ = require('lodash');
12
+ const fs = require('fs-extra');
13
+ const join = require('path').join;
14
+ const errors = require('@tryghost/errors');
15
+ const parse = require('./parse');
16
+
17
+ const notAPackageRegex = /^\.|_messages|README.md|node_modules|bower_components/i;
18
+ const packageJSONPath = 'package.json';
19
+
20
+ /**
21
+ * @typedef {object} PackageList
22
+ * @typedef {object} Package
23
+ */
24
+
25
+ /**
26
+ * Recursively read directory and find the packages in it
27
+ *
28
+ * @param {string} absolutePath
29
+ * @param {string} packageName
30
+ * @returns {Promise<Package>}
31
+ */
32
+ async function processPackage(absolutePath, packageName) {
33
+ const pkg = {
34
+ name: packageName,
35
+ path: absolutePath
36
+ };
37
+
38
+ try {
39
+ const packageJSON = await parse(join(absolutePath, packageJSONPath));
40
+ pkg['package.json'] = packageJSON;
41
+ } catch (err) {
42
+ // ignore invalid package.json for now,
43
+ // because Ghost does not rely/use them at the moment
44
+ // in the future, this .catch() will need to be removed,
45
+ // so that error is thrown on invalid json syntax
46
+ pkg['package.json'] = null;
47
+ }
48
+
49
+ return pkg;
50
+ }
51
+
52
+ /**
53
+ * ### Filter Packages
54
+ * Normalizes packages read by readPackages so that the themes module can use them.
55
+ * Iterates over each package and return an array of objects which are simplified representations of the package
56
+ * with 3 properties:
57
+ *
58
+ * @typedef {object} SimplePackage
59
+ * @prop {string} name - the package name
60
+ * @prop {object|boolean} package - contents of the package.json or false if there isn't one
61
+ * @prop {boolean} active - set to true if this package is active
62
+ *
63
+ * This data structure is used for listings of packages provided over the API and as such
64
+ * deliberately combines multiple sources of information in order to be efficient.
65
+ *
66
+ * TODO: simplify the package.json representation to contain only fields we use
67
+ *
68
+ * @param {PackageList} packages object made up of packages keyed by name as returned by readPackages
69
+ * @param {array|string} [active] optional set of names of packages that are active
70
+ * @returns {Array<SimplePackage>} array of objects with useful info about themes
71
+ */
72
+ function filter(packages, active) {
73
+ // turn active into an array if it isn't one, so this function can deal with lists and one-offs
74
+ if (!Array.isArray(active)) {
75
+ active = [active];
76
+ }
77
+
78
+ return _.reduce(packages, function (result, pkg, key) {
79
+ let item = {};
80
+ if (!key.match(notAPackageRegex)) {
81
+ item = {
82
+ name: key,
83
+ package: pkg['package.json'] || false,
84
+ active: _.indexOf(active, key) !== -1
85
+ };
86
+
87
+ result.push(item);
88
+ }
89
+
90
+ return result;
91
+ }, []);
92
+ }
93
+
94
+ /**
95
+ * @param {string} packagePath
96
+ * @param {string} packageName
97
+ * @returns {Promise<Package>}
98
+ */
99
+ async function readPackage(packagePath, packageName) {
100
+ const absolutePath = join(packagePath, packageName);
101
+
102
+ try {
103
+ const stat = await fs.stat(absolutePath);
104
+ if (!stat.isDirectory()) {
105
+ return {};
106
+ }
107
+
108
+ const pkg = await processPackage(absolutePath, packageName);
109
+ const res = {};
110
+ res[packageName] = pkg;
111
+ return res;
112
+ } catch (err) {
113
+ return Promise.reject(new errors.NotFoundError({
114
+ message: 'Package not found',
115
+ err: err,
116
+ help: 'path: ' + packagePath,
117
+ context: 'name: ' + packageName
118
+ }));
119
+ }
120
+ }
121
+
122
+ /**
123
+ * @param {string} packagePath
124
+ * @returns {Promise<PackageList>}
125
+ */
126
+ async function readPackages(packagePath) {
127
+ const files = await fs.promises.readdir(packagePath, {withFileTypes: true});
128
+ const packages = await Promise.all(files.map(async (file) => {
129
+ // Filter out things which are not packages by regex
130
+ if (file.name.match(notAPackageRegex)) {
131
+ return false;
132
+ }
133
+
134
+ if (file.isSymbolicLink()) {
135
+ try {
136
+ const packageFileOrig = await fs.stat(join(packagePath, file.name));
137
+ return packageFileOrig.isDirectory();
138
+ } catch (err) {
139
+ // if there's an error reading the symlink, we should just return false
140
+ return false;
141
+ }
142
+ }
143
+
144
+ // Check the remaining items to ensure they are a directory
145
+ return file.isDirectory();
146
+ }))
147
+ .then(results => files.filter((_v, index) => results[index]))
148
+ .then(packageFiles => Promise.all(packageFiles.map((packageFile) => {
149
+ const absolutePath = join(packagePath, packageFile.name);
150
+ return processPackage(absolutePath, packageFile.name);
151
+ })));
152
+
153
+ return _.keyBy(packages, 'name');
154
+ }
155
+
156
+ module.exports = {
157
+ filter,
158
+ readPackage,
159
+ readPackages
160
+ };
@@ -0,0 +1,57 @@
1
+ const fs = require('fs-extra');
2
+ const errors = require('@tryghost/errors');
3
+ const tpl = require('@tryghost/tpl');
4
+
5
+ const messages = {
6
+ couldNotReadPackage: 'Could not read package.json file',
7
+ nameOrVersionMissing: '"name" or "version" is missing from theme package.json file.',
8
+ willBeRequired: 'This will be required in future. Please see {url}',
9
+ themeFileIsMalformed: 'Theme package.json file is malformed'
10
+ };
11
+
12
+ /**
13
+ * Parse package.json and validate it has
14
+ * all the required fields
15
+ *
16
+ * @param {string} path
17
+ * @returns {Promise<object>}
18
+ */
19
+ async function parse(path) {
20
+ let source;
21
+ let json;
22
+
23
+ try {
24
+ source = await fs.readFile(path, {encoding: 'utf8'});
25
+ } catch (readError) {
26
+ throw new errors.IncorrectUsageError({
27
+ message: tpl(messages.couldNotReadPackage),
28
+ context: path,
29
+ err: readError
30
+ });
31
+ }
32
+
33
+ try {
34
+ json = JSON.parse(source);
35
+ } catch (parseError) {
36
+ throw new errors.IncorrectUsageError({
37
+ message: tpl(messages.themeFileIsMalformed),
38
+ context: path,
39
+ err: parseError,
40
+ help: tpl(messages.willBeRequired, {url: 'https://ghost.org/docs/themes/'})
41
+ });
42
+ }
43
+
44
+ const hasRequiredKeys = json.name && json.version;
45
+
46
+ if (!hasRequiredKeys) {
47
+ throw new errors.IncorrectUsageError({
48
+ message: tpl(messages.nameOrVersionMissing),
49
+ context: path,
50
+ help: tpl(messages.willBeRequired, {url: 'https://ghost.org/docs/themes/'})
51
+ });
52
+ }
53
+
54
+ return json;
55
+ }
56
+
57
+ module.exports = parse;