ghost 5.92.0 → 5.94.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 (141) hide show
  1. package/components/tryghost-adapter-cache-memory-ttl-5.94.0.tgz +0 -0
  2. package/components/tryghost-adapter-cache-redis-5.94.0.tgz +0 -0
  3. package/components/{tryghost-adapter-manager-5.92.0.tgz → tryghost-adapter-manager-5.94.0.tgz} +0 -0
  4. package/components/{tryghost-announcement-bar-settings-5.92.0.tgz → tryghost-announcement-bar-settings-5.94.0.tgz} +0 -0
  5. package/components/{tryghost-api-framework-5.92.0.tgz → tryghost-api-framework-5.94.0.tgz} +0 -0
  6. package/components/{tryghost-api-version-compatibility-service-5.92.0.tgz → tryghost-api-version-compatibility-service-5.94.0.tgz} +0 -0
  7. package/components/tryghost-audience-feedback-5.94.0.tgz +0 -0
  8. package/components/tryghost-bookshelf-repository-5.94.0.tgz +0 -0
  9. package/components/{tryghost-bootstrap-socket-5.92.0.tgz → tryghost-bootstrap-socket-5.94.0.tgz} +0 -0
  10. package/components/{tryghost-collections-5.92.0.tgz → tryghost-collections-5.94.0.tgz} +0 -0
  11. package/components/{tryghost-constants-5.92.0.tgz → tryghost-constants-5.94.0.tgz} +0 -0
  12. package/components/{tryghost-custom-theme-settings-service-5.92.0.tgz → tryghost-custom-theme-settings-service-5.94.0.tgz} +0 -0
  13. package/components/{tryghost-data-generator-5.92.0.tgz → tryghost-data-generator-5.94.0.tgz} +0 -0
  14. package/components/{tryghost-domain-events-5.92.0.tgz → tryghost-domain-events-5.94.0.tgz} +0 -0
  15. package/components/tryghost-donations-5.94.0.tgz +0 -0
  16. package/components/tryghost-dynamic-routing-events-5.94.0.tgz +0 -0
  17. package/components/tryghost-email-addresses-5.94.0.tgz +0 -0
  18. package/components/tryghost-email-analytics-provider-mailgun-5.94.0.tgz +0 -0
  19. package/components/tryghost-email-analytics-service-5.94.0.tgz +0 -0
  20. package/components/{tryghost-email-content-generator-5.92.0.tgz → tryghost-email-content-generator-5.94.0.tgz} +0 -0
  21. package/components/{tryghost-email-events-5.92.0.tgz → tryghost-email-events-5.94.0.tgz} +0 -0
  22. package/components/tryghost-email-service-5.94.0.tgz +0 -0
  23. package/components/tryghost-email-suppression-list-5.94.0.tgz +0 -0
  24. package/components/tryghost-express-dynamic-redirects-5.94.0.tgz +0 -0
  25. package/components/{tryghost-external-media-inliner-5.92.0.tgz → tryghost-external-media-inliner-5.94.0.tgz} +0 -0
  26. package/components/tryghost-extract-api-key-5.94.0.tgz +0 -0
  27. package/components/tryghost-ghost-5.94.0.tgz +0 -0
  28. package/components/tryghost-html-to-plaintext-5.94.0.tgz +0 -0
  29. package/components/{tryghost-i18n-5.92.0.tgz → tryghost-i18n-5.94.0.tgz} +0 -0
  30. package/components/{tryghost-importer-handler-content-files-5.92.0.tgz → tryghost-importer-handler-content-files-5.94.0.tgz} +0 -0
  31. package/components/{tryghost-importer-revue-5.92.0.tgz → tryghost-importer-revue-5.94.0.tgz} +0 -0
  32. package/components/tryghost-in-memory-repository-5.94.0.tgz +0 -0
  33. package/components/{tryghost-job-manager-5.92.0.tgz → tryghost-job-manager-5.94.0.tgz} +0 -0
  34. package/components/{tryghost-link-redirects-5.92.0.tgz → tryghost-link-redirects-5.94.0.tgz} +0 -0
  35. package/components/tryghost-link-replacer-5.94.0.tgz +0 -0
  36. package/components/{tryghost-link-tracking-5.92.0.tgz → tryghost-link-tracking-5.94.0.tgz} +0 -0
  37. package/components/{tryghost-magic-link-5.92.0.tgz → tryghost-magic-link-5.94.0.tgz} +0 -0
  38. package/components/{tryghost-mail-events-5.92.0.tgz → tryghost-mail-events-5.94.0.tgz} +0 -0
  39. package/components/tryghost-mailgun-client-5.94.0.tgz +0 -0
  40. package/components/{tryghost-member-attribution-5.92.0.tgz → tryghost-member-attribution-5.94.0.tgz} +0 -0
  41. package/components/{tryghost-member-events-5.92.0.tgz → tryghost-member-events-5.94.0.tgz} +0 -0
  42. package/components/{tryghost-members-api-5.92.0.tgz → tryghost-members-api-5.94.0.tgz} +0 -0
  43. package/components/{tryghost-members-csv-5.92.0.tgz → tryghost-members-csv-5.94.0.tgz} +0 -0
  44. package/components/{tryghost-members-events-service-5.92.0.tgz → tryghost-members-events-service-5.94.0.tgz} +0 -0
  45. package/components/{tryghost-members-importer-5.92.0.tgz → tryghost-members-importer-5.94.0.tgz} +0 -0
  46. package/components/tryghost-members-offers-5.94.0.tgz +0 -0
  47. package/components/tryghost-members-payments-5.94.0.tgz +0 -0
  48. package/components/{tryghost-members-ssr-5.92.0.tgz → tryghost-members-ssr-5.94.0.tgz} +0 -0
  49. package/components/{tryghost-members-stripe-service-5.92.0.tgz → tryghost-members-stripe-service-5.94.0.tgz} +0 -0
  50. package/components/{tryghost-mentions-email-report-5.92.0.tgz → tryghost-mentions-email-report-5.94.0.tgz} +0 -0
  51. package/components/tryghost-milestones-5.94.0.tgz +0 -0
  52. package/components/tryghost-minifier-5.94.0.tgz +0 -0
  53. package/components/tryghost-model-to-domain-event-interceptor-5.94.0.tgz +0 -0
  54. package/components/tryghost-mw-api-version-mismatch-5.94.0.tgz +0 -0
  55. package/components/{tryghost-mw-cache-control-5.92.0.tgz → tryghost-mw-cache-control-5.94.0.tgz} +0 -0
  56. package/components/tryghost-mw-error-handler-5.94.0.tgz +0 -0
  57. package/components/tryghost-mw-session-from-token-5.94.0.tgz +0 -0
  58. package/components/tryghost-mw-update-user-last-seen-5.94.0.tgz +0 -0
  59. package/components/tryghost-mw-version-match-5.94.0.tgz +0 -0
  60. package/components/{tryghost-mw-vhost-5.92.0.tgz → tryghost-mw-vhost-5.94.0.tgz} +0 -0
  61. package/components/tryghost-nql-filter-expansions-5.94.0.tgz +0 -0
  62. package/components/tryghost-oembed-service-5.94.0.tgz +0 -0
  63. package/components/{tryghost-package-json-5.92.0.tgz → tryghost-package-json-5.94.0.tgz} +0 -0
  64. package/components/tryghost-post-events-5.94.0.tgz +0 -0
  65. package/components/tryghost-post-revisions-5.94.0.tgz +0 -0
  66. package/components/{tryghost-posts-service-5.92.0.tgz → tryghost-posts-service-5.94.0.tgz} +0 -0
  67. package/components/{tryghost-recommendations-5.92.0.tgz → tryghost-recommendations-5.94.0.tgz} +0 -0
  68. package/components/tryghost-referrers-5.94.0.tgz +0 -0
  69. package/components/{tryghost-security-5.92.0.tgz → tryghost-security-5.94.0.tgz} +0 -0
  70. package/components/{tryghost-session-service-5.92.0.tgz → tryghost-session-service-5.94.0.tgz} +0 -0
  71. package/components/{tryghost-settings-path-manager-5.92.0.tgz → tryghost-settings-path-manager-5.94.0.tgz} +0 -0
  72. package/components/{tryghost-slack-notifications-5.92.0.tgz → tryghost-slack-notifications-5.94.0.tgz} +0 -0
  73. package/components/{tryghost-staff-service-5.92.0.tgz → tryghost-staff-service-5.94.0.tgz} +0 -0
  74. package/components/tryghost-stats-service-5.94.0.tgz +0 -0
  75. package/components/tryghost-tiers-5.94.0.tgz +0 -0
  76. package/components/{tryghost-update-check-service-5.92.0.tgz → tryghost-update-check-service-5.94.0.tgz} +0 -0
  77. package/components/{tryghost-verification-trigger-5.92.0.tgz → tryghost-verification-trigger-5.94.0.tgz} +0 -0
  78. package/components/{tryghost-version-notifications-data-service-5.92.0.tgz → tryghost-version-notifications-data-service-5.94.0.tgz} +0 -0
  79. package/components/tryghost-webmentions-5.94.0.tgz +0 -0
  80. package/core/app.js +22 -0
  81. package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +2 -2
  82. package/core/built/admin/assets/admin-x-activitypub/{index-5eac6af1.mjs → index-51277f76.mjs} +3000 -2661
  83. package/core/built/admin/assets/admin-x-activitypub/modals-c85985ad.mjs +815 -0
  84. package/core/built/admin/assets/{chunk.524.1bc6b183876a00383f90.js → chunk.524.b9da8e9c62fc112baf2b.js} +5 -5
  85. package/core/built/admin/assets/{chunk.582.9682daf3be0962298c43.js → chunk.582.27ddbcbd7a6e7a439db8.js} +6 -6
  86. package/core/built/admin/assets/{chunk.217.2bd17b550359ea58831b.js → chunk.785.78610bd253a9e2419ec9.js} +27 -20
  87. package/core/built/admin/assets/{ghost-acdb9966c24d49e97ac26215cb4933e1.js → ghost-535088daf62c8cbea78a012bb6bbafc6.js} +866 -827
  88. package/core/built/admin/assets/ghost-d1c51d3a2d840dda5aa7b4ffe6057756.css +1 -0
  89. package/core/built/admin/assets/ghost-dark-98b3a7e593292a7cb410609e17727a3c.css +1 -0
  90. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +207 -197
  91. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +26 -26
  92. package/core/built/admin/assets/{vendor-bff75d127fcf4a75c8a83b473bdb308f.js → vendor-649dc64bfd9ecc1bd7361b53680dfdc2.js} +19 -17
  93. package/core/built/admin/index.html +6 -6
  94. package/core/server/data/migrations/versions/5.93/2024-09-03-18-51-01-update-stripe-prices-nickname-length.js +19 -0
  95. package/core/server/data/migrations/versions/5.94/2024-09-03-20-09-40-null-analytics-jobs-timings.js +20 -0
  96. package/core/server/data/schema/schema.js +1 -1
  97. package/core/server/services/email-analytics/EmailAnalyticsServiceWrapper.js +49 -11
  98. package/core/server/services/email-analytics/lib/queries.js +154 -30
  99. package/core/server/services/oembed/service.js +2 -1
  100. package/package.json +150 -150
  101. package/yarn.lock +48 -58
  102. package/components/tryghost-adapter-cache-memory-ttl-5.92.0.tgz +0 -0
  103. package/components/tryghost-adapter-cache-redis-5.92.0.tgz +0 -0
  104. package/components/tryghost-audience-feedback-5.92.0.tgz +0 -0
  105. package/components/tryghost-bookshelf-repository-5.92.0.tgz +0 -0
  106. package/components/tryghost-donations-5.92.0.tgz +0 -0
  107. package/components/tryghost-dynamic-routing-events-5.92.0.tgz +0 -0
  108. package/components/tryghost-email-addresses-5.92.0.tgz +0 -0
  109. package/components/tryghost-email-analytics-provider-mailgun-5.92.0.tgz +0 -0
  110. package/components/tryghost-email-analytics-service-5.92.0.tgz +0 -0
  111. package/components/tryghost-email-service-5.92.0.tgz +0 -0
  112. package/components/tryghost-email-suppression-list-5.92.0.tgz +0 -0
  113. package/components/tryghost-express-dynamic-redirects-5.92.0.tgz +0 -0
  114. package/components/tryghost-extract-api-key-5.92.0.tgz +0 -0
  115. package/components/tryghost-ghost-5.92.0.tgz +0 -0
  116. package/components/tryghost-html-to-plaintext-5.92.0.tgz +0 -0
  117. package/components/tryghost-in-memory-repository-5.92.0.tgz +0 -0
  118. package/components/tryghost-link-replacer-5.92.0.tgz +0 -0
  119. package/components/tryghost-mailgun-client-5.92.0.tgz +0 -0
  120. package/components/tryghost-members-offers-5.92.0.tgz +0 -0
  121. package/components/tryghost-members-payments-5.92.0.tgz +0 -0
  122. package/components/tryghost-milestones-5.92.0.tgz +0 -0
  123. package/components/tryghost-minifier-5.92.0.tgz +0 -0
  124. package/components/tryghost-model-to-domain-event-interceptor-5.92.0.tgz +0 -0
  125. package/components/tryghost-mw-api-version-mismatch-5.92.0.tgz +0 -0
  126. package/components/tryghost-mw-error-handler-5.92.0.tgz +0 -0
  127. package/components/tryghost-mw-session-from-token-5.92.0.tgz +0 -0
  128. package/components/tryghost-mw-update-user-last-seen-5.92.0.tgz +0 -0
  129. package/components/tryghost-mw-version-match-5.92.0.tgz +0 -0
  130. package/components/tryghost-nql-filter-expansions-5.92.0.tgz +0 -0
  131. package/components/tryghost-oembed-service-5.92.0.tgz +0 -0
  132. package/components/tryghost-post-events-5.92.0.tgz +0 -0
  133. package/components/tryghost-post-revisions-5.92.0.tgz +0 -0
  134. package/components/tryghost-referrers-5.92.0.tgz +0 -0
  135. package/components/tryghost-stats-service-5.92.0.tgz +0 -0
  136. package/components/tryghost-tiers-5.92.0.tgz +0 -0
  137. package/components/tryghost-webmentions-5.92.0.tgz +0 -0
  138. package/core/built/admin/assets/admin-x-activitypub/modals-62a088d9.mjs +0 -965
  139. package/core/built/admin/assets/ghost-8c20f019d522f17d318e799568924958.css +0 -1
  140. package/core/built/admin/assets/ghost-dark-b27530aaada81101d641ff1ca5f5df6b.css +0 -1
  141. /package/core/built/admin/assets/{chunk.217.2bd17b550359ea58831b.js.LICENSE.txt → chunk.785.78610bd253a9e2419ec9.js.LICENSE.txt} +0 -0
@@ -8914,7 +8914,9 @@ e.default={content:'<defs><style>.staff_svg__cls-1{fill:none;stroke:currentColor
8914
8914
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
8915
8915
  e.default={content:'<path d="M23.56 8.73a1.51 1.51 0 00-1.41-1h-6.09a.5.5 0 01-.47-.33l-2.18-6.2A1.52 1.52 0 0012 .25a1.49 1.49 0 00-1.4 1L8.41 7.42a.5.5 0 01-.47.33H1.85a1.5 1.5 0 00-1.41 1 1.52 1.52 0 00.45 1.65l5.18 4.3a.5.5 0 01.16.54l-2.18 6.53a1.5 1.5 0 002.31 1.69l5.34-3.92a.49.49 0 01.59 0l5.35 3.92A1.5 1.5 0 0020 21.77l-2.18-6.53a.5.5 0 01.16-.54l5.19-4.31a1.51 1.51 0 00.39-1.66z"/>',attrs:{viewBox:"0 0 24 24",fill:"currentColor"}}})),define("ember-svg-jar/inlined/star",["exports"],(function(e){"use strict"
8916
8916
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
8917
- e.default={content:'<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12.71 1.436l3.272 6.482 6.296.624a.787.787 0 01.489 1.343l-5.182 5.136 1.921 6.98a.796.796 0 01-1.125.914l-6.383-3.161-6.375 3.157a.794.794 0 01-1.125-.914l1.92-6.98-5.185-5.136a.787.787 0 01.489-1.343l6.296-.624 3.267-6.478a.801.801 0 011.425 0z"/>',attrs:{fill:"none",viewBox:"0 0 24 24"}}})),define("ember-svg-jar/inlined/stats",["exports"],(function(e){"use strict"
8917
+ e.default={content:'<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12.71 1.436l3.272 6.482 6.296.624a.787.787 0 01.489 1.343l-5.182 5.136 1.921 6.98a.796.796 0 01-1.125.914l-6.383-3.161-6.375 3.157a.794.794 0 01-1.125-.914l1.92-6.98-5.185-5.136a.787.787 0 01.489-1.343l6.296-.624 3.267-6.478a.801.801 0 011.425 0z"/>',attrs:{fill:"none",viewBox:"0 0 24 24"}}})),define("ember-svg-jar/inlined/stats-outline",["exports"],(function(e){"use strict"
8918
+ Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
8919
+ e.default={content:'<path d="M1.836 3.027c-.209.03-.452.108-.659.213-.55.277-.926.774-1.113 1.473l-.052.195v14.256l.054.204c.212.797.778 1.336 1.647 1.568l.195.052h20.256l.204-.054c.112-.029.314-.108.45-.174.2-.099.285-.16.46-.326.118-.113.253-.262.3-.332.19-.287.356-.708.39-.986.008-.072.024-.125.035-.118.012.007.021-3.057.021-6.995 0-4.646-.008-7.013-.024-7.023-.013-.008-.024-.05-.024-.094 0-.134-.092-.449-.194-.662-.273-.572-.76-.954-1.482-1.16l-.184-.052-10.068-.003c-5.596-.002-10.132.006-10.212.018m.101 1.524c-.15.046-.284.181-.356.36l-.057.141V19.02l.057.116c.07.144.2.241.405.304.154.048.24.048 10.048.048 8.88 0 9.906-.004 10.019-.037a.586.586 0 00.366-.362l.057-.141V4.98l-.057-.116c-.07-.144-.2-.241-.405-.304-.154-.048-.238-.048-10.059-.046-8.65.001-9.918.006-10.018.037M.012 12.024c0 3.861.003 5.44.006 3.51.003-1.93.003-5.09 0-7.02-.003-1.93-.006-.351-.006 3.51m20.07-4.511a.746.746 0 00-.37.219c-.054.057-.987 1.438-2.072 3.068a649.896 649.896 0 01-1.995 2.987c-.014.015-.489-.442-1.251-1.202-1.4-1.396-1.334-1.343-1.685-1.324a.72.72 0 00-.301.066c-.074.037-.48.425-1.294 1.237l-1.187 1.183-1.193-2.2c-.656-1.21-1.23-2.242-1.277-2.295a.749.749 0 00-1.065-.05c-.123.118-3.298 5.398-3.36 5.586a.809.809 0 00.039.533.83.83 0 00.377.368c.113.052.157.059.327.051.236-.01.386-.076.52-.228.048-.055.637-1.016 1.307-2.136.671-1.12 1.232-2.049 1.247-2.065.02-.02.303.482 1.131 2.01.607 1.121 1.142 2.09 1.189 2.152.122.165.311.256.551.267.342.016.274.07 1.734-1.385l1.29-1.286 1.278 1.275c.882.88 1.311 1.292 1.386 1.33.077.039.164.057.304.065.174.01.21.004.336-.053a.964.964 0 00.209-.13c.087-.081 4.61-6.86 4.686-7.023.16-.342-.02-.799-.377-.966a.879.879 0 00-.484-.054" fill="currentColor" fill-rule="evenodd"/>',attrs:{viewBox:"0 0 24 24"}}})),define("ember-svg-jar/inlined/stats",["exports"],(function(e){"use strict"
8918
8920
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
8919
8921
  e.default={content:'<path d="M14 3h-4v18h4V3zM5 14H1v7h4v-7zm18-5h-4v12h4V9z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/>',attrs:{fill:"none",viewBox:"0 0 24 24"}}})),define("ember-svg-jar/inlined/stripe-verified-partner-badge",["exports"],(function(e){"use strict"
8920
8922
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
@@ -8924,10 +8926,10 @@ e.default={content:'<g fill="none" fill-rule="evenodd"><path d="M.5.5h173v119H.5
8924
8926
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
8925
8927
  e.default={content:'<defs><style>.sun_svg__a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px}</style></defs><circle class="sun_svg__a" cx="12" cy="12" r="4.5"/><path class="sun_svg__a" d="M12 .75V4.5m0 15v3.75M23.25 12H19.5m-15 0H.75m19.5-8.25l-3 3m-10.5 10.5l-3 3m16.5 0l-3-3M6.75 6.75l-3-3"/>',attrs:{viewBox:"0 0 24 24"}}})),define("ember-svg-jar/inlined/suppression-notice-bounced",["exports"],(function(e){"use strict"
8926
8928
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
8927
- e.default={content:'<path d="M37.2 37.127l12.237 12.238M37.2 49.362l12.237-12.237" stroke="red" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"/><path d="M31.316 53.983l-7.99-7.956-8.58 4.433.334-12.717m0 0L5.033 27.697a4.033 4.033 0 01-1.1-3.667 4.106 4.106 0 012.713-3.15L45.98 7.757a4.106 4.106 0 015.28 5.276L45 30m-29.92 7.743l35.006-29.18" stroke="#CAD1D8" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/>',attrs:{fill:"none",viewBox:"0 0 60 60"}}})),define("ember-svg-jar/inlined/suppression-notice-flagged",["exports"],(function(e){"use strict"
8929
+ e.default={content:'<path d="M37.2 37.127l12.237 12.238M37.2 49.362l12.237-12.237" stroke="red" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"/><path d="M31.316 53.983l-7.99-7.956-8.58 4.433.334-12.717m0 0L5.033 27.697a4.033 4.033 0 01-1.1-3.667 4.106 4.106 0 012.713-3.15L45.98 7.757a4.106 4.106 0 015.28 5.276L45 30m-29.92 7.743l35.006-29.18" stroke="#CAD1D8" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/>',attrs:{fill:"none",viewBox:"0 0 60 60"}}}))
8930
+ define("ember-svg-jar/inlined/suppression-notice-flagged",["exports"],(function(e){"use strict"
8928
8931
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
8929
- e.default={content:'<path d="M31.316 53.983l-7.99-7.956-8.58 4.433.334-12.717m0 0L5.033 27.697a4.033 4.033 0 01-1.1-3.667 4.106 4.106 0 012.713-3.15L45.98 7.757a4.106 4.106 0 015.28 5.276L45 30m-29.92 7.743l35.006-29.18" stroke="#CAD1D8" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M41.474 37.2v7.65" stroke="#F50B23" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="41.399" cy="51.1" r="1.5" fill="#F50B23"/>',attrs:{fill:"none",viewBox:"0 0 60 60"}}}))
8930
- define("ember-svg-jar/inlined/sync",["exports"],(function(e){"use strict"
8932
+ e.default={content:'<path d="M31.316 53.983l-7.99-7.956-8.58 4.433.334-12.717m0 0L5.033 27.697a4.033 4.033 0 01-1.1-3.667 4.106 4.106 0 012.713-3.15L45.98 7.757a4.106 4.106 0 015.28 5.276L45 30m-29.92 7.743l35.006-29.18" stroke="#CAD1D8" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M41.474 37.2v7.65" stroke="#F50B23" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="41.399" cy="51.1" r="1.5" fill="#F50B23"/>',attrs:{fill:"none",viewBox:"0 0 60 60"}}})),define("ember-svg-jar/inlined/sync",["exports"],(function(e){"use strict"
8931
8933
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
8932
8934
  e.default={content:'<path d="M4.47 1.867a4.525 4.525 0 015.02 1.378.4.4 0 01-.618.51 3.725 3.725 0 00-6.597 2.369v.159l.817-.817a.4.4 0 11.565.565l-1.48 1.48a.4.4 0 01-.605 0l-1.48-1.48a.4.4 0 11.565-.565l.818.817v-.16A4.526 4.526 0 014.47 1.868zm5.655 3.107a.4.4 0 01.283.117l1.5 1.5a.4.4 0 01-.566.565l-.822-.822A4.525 4.525 0 012.709 9.23a.4.4 0 01.582-.55 3.725 3.725 0 006.427-2.333l-.81.81a.4.4 0 11-.566-.565l1.5-1.5a.4.4 0 01.283-.117z"/>',attrs:{fill:"none",viewBox:"0 0 12 12"}}})),define("ember-svg-jar/inlined/tag",["exports"],(function(e){"use strict"
8933
8935
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
@@ -8996,9 +8998,9 @@ return t||n?r.reduce(((t,r)=>e[r]?t.concat(`<${r} id="${e[r].id}">${e[r].text}</
8996
8998
  return t||n?`aria-labelledby="${r.filter((t=>e[t])).map((t=>e[t].id)).join(" ")}"`:""}function u(e){return Object.keys(e).filter((e=>!r.includes(e))).map((t=>!Ember.isNone(e[t])&&`${t}="${e[t]}"`)).filter((e=>e)).join(" ")}function c(e,t={}){return`<svg ${u(t)}${l(t)}><use xlink:href="${e}" />${a(t)}</svg>`}function d(e,t,r={}){let n=t(e)
8997
8999
  if(!n)return void console.warn(`ember-svg-jar: Missing inline SVG for ${e}`)
8998
9000
  let i=n.attrs?Object.assign({},n.attrs,r):r,{size:o}=r
8999
- return o&&(i.width=parseFloat(i.width)*o||i.width,i.height=parseFloat(i.height)*o||i.height,delete i.size),`<svg ${u(i)}${l(r)}>${a(r)}${n.content}</svg>`}})),define("ember-test-waiters/index",["exports","@ember/test-waiters"],(function(e,t){"use strict"
9000
- Object.defineProperty(e,"__esModule",{value:!0}),Object.keys(t).forEach((function(r){"default"!==r&&"__esModule"!==r&&(r in e&&e[r]===t[r]||Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[r]}}))}))}))
9001
- define("ember-text-measurer/services/text-measurer",["exports"],(function(e){"use strict"
9001
+ return o&&(i.width=parseFloat(i.width)*o||i.width,i.height=parseFloat(i.height)*o||i.height,delete i.size),`<svg ${u(i)}${l(r)}>${a(r)}${n.content}</svg>`}}))
9002
+ define("ember-test-waiters/index",["exports","@ember/test-waiters"],(function(e,t){"use strict"
9003
+ Object.defineProperty(e,"__esModule",{value:!0}),Object.keys(t).forEach((function(r){"default"!==r&&"__esModule"!==r&&(r in e&&e[r]===t[r]||Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[r]}}))}))})),define("ember-text-measurer/services/text-measurer",["exports"],(function(e){"use strict"
9002
9004
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
9003
9005
  e.default=Ember.Service.extend({init(){this._super(...arguments),this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d")},width(e,t=null){return t&&(this.ctx.font=t),this.ctx.measureText(e).width},lines(e,t,r=null){r&&(this.ctx.font=r)
9004
9006
  let n=e.split(/\n/),i=n.length
@@ -9147,7 +9149,8 @@ const e=this.liquidChildDidRender
9147
9149
  this._waitingFor.push(e)
9148
9150
  let t=this.nearestWithProperty("_isLiquidChild")
9149
9151
  t&&t._waitForMe(e)},_waitForAll(){const e=this._waitingFor
9150
- return this._waitingFor=[],Ember.RSVP.Promise.all(e).then((()=>{if(this._waitingFor.length>0)return this._waitForAll()}))}})})),define("liquid-fire/components/liquid-container",["exports","liquid-fire/mixins/growable","liquid-fire/components/liquid-measured","liquid-fire/templates/components/liquid-container","jquery"],(function(e,t,r,n,i){"use strict"
9152
+ return this._waitingFor=[],Ember.RSVP.Promise.all(e).then((()=>{if(this._waitingFor.length>0)return this._waitForAll()}))}})}))
9153
+ define("liquid-fire/components/liquid-container",["exports","liquid-fire/mixins/growable","liquid-fire/components/liquid-measured","liquid-fire/templates/components/liquid-container","jquery"],(function(e,t,r,n,i){"use strict"
9151
9154
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
9152
9155
  e.default=Ember.Component.extend(t.default,{layout:n.default,classNames:["liquid-container"],lockSize(e,t){e.outerWidth(t.width),e.outerHeight(t.height)},unlockSize(){let e=()=>{this.updateAnimatingClass(!1),this.element&&(this.element.style.width="",this.element.style.height="")}
9153
9156
  this._scaling?this._scaling.then(e):e()},updateAnimatingClass(e){this.isDestroyed||(e?this.element.classList.add("liquid-animating"):this.element.classList.remove("liquid-animating"))},didInsertElement(){this._super(...arguments),this._wasInserted=!0},actions:{willTransition(e){if(!this._wasInserted)return
@@ -9162,8 +9165,7 @@ n&&(this._scaling=this.animateGrowth(t,l,a))},afterTransition(e){for(let t=0;t<e
9162
9165
  this.unlockSize()}}})
9163
9166
  function o(e,t){if(!e.view)return
9164
9167
  let n=(0,i.default)(e.view.element),o=n.position()
9165
- t||(t=(0,r.measure)(n)),n.outerWidth(t.width),n.outerHeight(t.height),n.css({position:"absolute",top:o.top,left:o.left})}function s(e){if(e.view&&!e.view.isDestroyed){(0,i.default)(e.view.element).css({width:"",height:"",position:""})}}}))
9166
- define("liquid-fire/components/liquid-if",["exports","liquid-fire/templates/components/liquid-if"],(function(e,t){"use strict"
9168
+ t||(t=(0,r.measure)(n)),n.outerWidth(t.width),n.outerHeight(t.height),n.css({position:"absolute",top:o.top,left:o.left})}function s(e){if(e.view&&!e.view.isDestroyed){(0,i.default)(e.view.element).css({width:"",height:"",position:""})}}})),define("liquid-fire/components/liquid-if",["exports","liquid-fire/templates/components/liquid-if"],(function(e,t){"use strict"
9167
9169
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
9168
9170
  let r=Ember.Component.extend({positionalParams:["predicate"],layout:t.default,tagName:"",helperName:"liquid-if"})
9169
9171
  r.reopenClass({positionalParams:["predicate"]})
@@ -9328,10 +9330,10 @@ e.default=Ember.HTMLBars.template({id:"dS0w+JQh",block:'{"symbols":["container",
9328
9330
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
9329
9331
  e.default=Ember.HTMLBars.template({id:"CXFd+X02",block:'{"symbols":["&default"],"statements":[[18,1,[[32,0]]]],"hasEval":false,"upvars":[]}',moduleName:"liquid-fire/templates/components/liquid-container.hbs"})})),define("liquid-fire/templates/components/liquid-if",["exports"],(function(e){"use strict"
9330
9332
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
9331
- e.default=Ember.HTMLBars.template({id:"HOnDajqQ",block:'{"symbols":["container","valueVersion","valueVersion","&else","&default"],"statements":[[6,[37,0],[[32,0,["containerless"]]],null,[["default","else"],[{"statements":[[2,"\\n "],[8,"liquid-versions",[],[["@value","@matchContext","@use","@rules","@renderWhenFalse","@class"],[[30,[36,0],[[32,0,["inverted"]],[30,[36,0],[[32,0,["predicate"]],false,true],null],[30,[36,0],[[32,0,["predicate"]],true,false],null]],null],[30,[36,1],null,[["helperName"],[[32,0,["helperName"]]]]],[32,0,["use"]],[32,0,["rules"]],[27,[32,4]],[32,0,["class"]]]],[["default"],[{"statements":[[2,"\\n"],[6,[37,0],[[32,3]],null,[["default","else"],[{"statements":[[2," "],[18,5,null],[2,"\\n"]],"parameters":[]},{"statements":[[2," "],[18,4,null],[2,"\\n"]],"parameters":[]}]]],[2," "]],"parameters":[3]}]]],[2,"\\n"]],"parameters":[]},{"statements":[[2," "],[8,"liquid-container",[],[["@id","@class","@growDuration","@growPixelsPerSecond","@growEasing","@shrinkDelay","@growDelay","@enableGrowth"],[[32,0,["containerId"]],[32,0,["class"]],[32,0,["growDuration"]],[32,0,["growPixelsPerSecond"]],[32,0,["growEasing"]],[32,0,["shrinkDelay"]],[32,0,["growDelay"]],[32,0,["enableGrowth"]]]],[["default"],[{"statements":[[2,"\\n "],[8,"liquid-versions",[],[["@value","@notify","@matchContext","@use","@rules","@renderWhenFalse"],[[30,[36,0],[[32,0,["inverted"]],[30,[36,0],[[32,0,["predicate"]],false,true],null],[30,[36,0],[[32,0,["predicate"]],true,false],null]],null],[32,1],[30,[36,1],null,[["helperName"],[[32,0,["helperName"]]]]],[32,0,["use"]],[32,0,["rules"]],[27,[32,4]]]],[["default"],[{"statements":[[2,"\\n"],[6,[37,0],[[32,2]],null,[["default","else"],[{"statements":[[2," "],[18,5,null],[2,"\\n"]],"parameters":[]},{"statements":[[2," "],[18,4,null],[2,"\\n"]],"parameters":[]}]]],[2," "]],"parameters":[2]}]]],[2,"\\n "]],"parameters":[1]}]]],[2,"\\n"]],"parameters":[]}]]]],"hasEval":false,"upvars":["if","hash"]}',moduleName:"liquid-fire/templates/components/liquid-if.hbs"})})),define("liquid-fire/templates/components/liquid-measured",["exports"],(function(e){"use strict"
9333
+ e.default=Ember.HTMLBars.template({id:"HOnDajqQ",block:'{"symbols":["container","valueVersion","valueVersion","&else","&default"],"statements":[[6,[37,0],[[32,0,["containerless"]]],null,[["default","else"],[{"statements":[[2,"\\n "],[8,"liquid-versions",[],[["@value","@matchContext","@use","@rules","@renderWhenFalse","@class"],[[30,[36,0],[[32,0,["inverted"]],[30,[36,0],[[32,0,["predicate"]],false,true],null],[30,[36,0],[[32,0,["predicate"]],true,false],null]],null],[30,[36,1],null,[["helperName"],[[32,0,["helperName"]]]]],[32,0,["use"]],[32,0,["rules"]],[27,[32,4]],[32,0,["class"]]]],[["default"],[{"statements":[[2,"\\n"],[6,[37,0],[[32,3]],null,[["default","else"],[{"statements":[[2," "],[18,5,null],[2,"\\n"]],"parameters":[]},{"statements":[[2," "],[18,4,null],[2,"\\n"]],"parameters":[]}]]],[2," "]],"parameters":[3]}]]],[2,"\\n"]],"parameters":[]},{"statements":[[2," "],[8,"liquid-container",[],[["@id","@class","@growDuration","@growPixelsPerSecond","@growEasing","@shrinkDelay","@growDelay","@enableGrowth"],[[32,0,["containerId"]],[32,0,["class"]],[32,0,["growDuration"]],[32,0,["growPixelsPerSecond"]],[32,0,["growEasing"]],[32,0,["shrinkDelay"]],[32,0,["growDelay"]],[32,0,["enableGrowth"]]]],[["default"],[{"statements":[[2,"\\n "],[8,"liquid-versions",[],[["@value","@notify","@matchContext","@use","@rules","@renderWhenFalse"],[[30,[36,0],[[32,0,["inverted"]],[30,[36,0],[[32,0,["predicate"]],false,true],null],[30,[36,0],[[32,0,["predicate"]],true,false],null]],null],[32,1],[30,[36,1],null,[["helperName"],[[32,0,["helperName"]]]]],[32,0,["use"]],[32,0,["rules"]],[27,[32,4]]]],[["default"],[{"statements":[[2,"\\n"],[6,[37,0],[[32,2]],null,[["default","else"],[{"statements":[[2," "],[18,5,null],[2,"\\n"]],"parameters":[]},{"statements":[[2," "],[18,4,null],[2,"\\n"]],"parameters":[]}]]],[2," "]],"parameters":[2]}]]],[2,"\\n "]],"parameters":[1]}]]],[2,"\\n"]],"parameters":[]}]]]],"hasEval":false,"upvars":["if","hash"]}',moduleName:"liquid-fire/templates/components/liquid-if.hbs"})}))
9334
+ define("liquid-fire/templates/components/liquid-measured",["exports"],(function(e){"use strict"
9332
9335
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
9333
- e.default=Ember.HTMLBars.template({id:"ZXmeP7J9",block:'{"symbols":["&default"],"statements":[[18,1,null],[2,"\\n"]],"hasEval":false,"upvars":[]}',moduleName:"liquid-fire/templates/components/liquid-measured.hbs"})}))
9334
- define("liquid-fire/templates/components/liquid-outlet",["exports"],(function(e){"use strict"
9336
+ e.default=Ember.HTMLBars.template({id:"ZXmeP7J9",block:'{"symbols":["&default"],"statements":[[18,1,null],[2,"\\n"]],"hasEval":false,"upvars":[]}',moduleName:"liquid-fire/templates/components/liquid-measured.hbs"})})),define("liquid-fire/templates/components/liquid-outlet",["exports"],(function(e){"use strict"
9335
9337
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
9336
9338
  e.default=Ember.HTMLBars.template({id:"GqUo9ITF",block:'{"symbols":["outletState","version"],"statements":[[8,"lf-get-outlet-state",[],[[],[]],[["default"],[{"statements":[[6,[37,5],[[30,[36,4],[[32,1],[32,0,["outletName"]]],null]],[["containerId","versionEquality","matchContext","class","use","rules","containerless","growDuration","growPixelsPerSecond","growEasing","shrinkDelay","growDelay","enableGrowth"],[[32,0,["containerId"]],[32,0,["versionEquality"]],[30,[36,3],null,[["outletName","helperName"],[[32,0,["outletName"]],"liquid-outlet"]]],[32,0,["class"]],[32,0,["use"]],[32,0,["rules"]],[32,0,["containerless"]],[32,0,["growDuration"]],[32,0,["growPixelsPerSecond"]],[32,0,["growEasing"]],[32,0,["shrinkDelay"]],[32,0,["growDelay"]],[32,0,["enableGrowth"]]]],[["default"],[{"statements":[[6,[37,2],null,[["outletState"],[[32,2]]],[["default"],[{"statements":[[1,[30,[36,1],[[30,[36,0],[[32,0,["outletName"]]],null]],null]]],"parameters":[]}]]]],"parameters":[2]}]]]],"parameters":[1]}]]]],"hasEval":false,"upvars":["-outlet","component","-with-dynamic-vars","hash","lf-lock-model","liquid-bind"]}',moduleName:"liquid-fire/templates/components/liquid-outlet.hbs"})})),define("liquid-fire/templates/components/liquid-spacer",["exports"],(function(e){"use strict"
9337
9339
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
@@ -9481,7 +9483,8 @@ e.default=Ember.HTMLBars.template({id:"Qr2N4/XN",block:'{"symbols":["@replaceNod
9481
9483
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
9482
9484
  e.default=Ember.HTMLBars.template({id:"P8POU6zX",block:'{"symbols":["stack","item"],"statements":[[10,"div"],[15,0,[31,["liquid-destination ",[32,0,["extraClassesString"]]," ",[30,[36,0],[[32,0,["hasWormholes"]],"has-wormholes"],null]]]],[12],[2,"\\n"],[6,[37,2],[[30,[36,1],[[30,[36,1],[[32,0,["stacks"]]],null]],null]],null,[["default"],[{"statements":[[2," "],[10,"div"],[14,0,"liquid-destination-stack"],[12],[2,"\\n "],[8,"liquid-versions",[],[["@value","@notify","@renderWhenFalse","@name","@matchContext","@stackName"],[[32,1,["lastObject"]],[32,0],true,"liquid-wormhole",[32,0,["matchContext"]],[32,1,["name"]]]],[["default"],[{"statements":[[2,"\\n "],[8,"liquid-append",[],[["@nodes","@replaceNodes","@notify","@click"],[[32,2,["nodes"]],[32,2,["_replaceNodes"]],[32,2,["wormhole"]],[32,2,["wormhole","click"]]]],null],[2,"\\n "]],"parameters":[2]}]]],[2,"\\n "],[13],[2,"\\n"]],"parameters":[1]}]]],[13]],"hasEval":false,"upvars":["if","-track-array","each"]}',moduleName:"liquid-wormhole/templates/components/liquid-destination.hbs"})})),define("liquid-wormhole/templates/components/liquid-wormhole",["exports"],(function(e){"use strict"
9483
9485
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
9484
- e.default=Ember.HTMLBars.template({id:"FcVE2XL/",block:'{"symbols":["details","&default"],"statements":[[6,[37,0],[[32,0,["hasSend"]]],null,[["default","else"],[{"statements":[[6,[37,0],[[27,[32,2]]],null,[["default","else"],[{"statements":[[2," "],[8,[32,0,["sendComponent"]],[[16,1,[32,0,["wormholeId"]]],[16,0,[31,[[32,0,["wormholeClass"]]," liquid-wormhole-element"]]]],[[],[]],[["default"],[{"statements":[[2,"\\n "],[18,2,[[32,1]]],[2,"\\n "]],"parameters":[1]}]]],[2,"\\n"]],"parameters":[]},{"statements":[[2," "],[8,[32,0,["sendComponent"]],[[16,1,[32,0,["wormholeId"]]],[16,0,[31,[[32,0,["wormholeClass"]]," liquid-wormhole-element"]]]],[[],[]],null],[2,"\\n"]],"parameters":[]}]]]],"parameters":[]},{"statements":[[2," "],[10,"div"],[15,1,[32,0,["wormholeId"]]],[15,0,[31,[[32,0,["wormholeClass"]]," liquid-wormhole-element"]]],[12],[2,"\\n "],[18,2,null],[2,"\\n "],[13],[2,"\\n"]],"parameters":[]}]]]],"hasEval":false,"upvars":["if"]}',moduleName:"liquid-wormhole/templates/components/liquid-wormhole.hbs"})})),define("liquid-wormhole/transitions/wormhole",["exports"],(function(e){"use strict"
9486
+ e.default=Ember.HTMLBars.template({id:"FcVE2XL/",block:'{"symbols":["details","&default"],"statements":[[6,[37,0],[[32,0,["hasSend"]]],null,[["default","else"],[{"statements":[[6,[37,0],[[27,[32,2]]],null,[["default","else"],[{"statements":[[2," "],[8,[32,0,["sendComponent"]],[[16,1,[32,0,["wormholeId"]]],[16,0,[31,[[32,0,["wormholeClass"]]," liquid-wormhole-element"]]]],[[],[]],[["default"],[{"statements":[[2,"\\n "],[18,2,[[32,1]]],[2,"\\n "]],"parameters":[1]}]]],[2,"\\n"]],"parameters":[]},{"statements":[[2," "],[8,[32,0,["sendComponent"]],[[16,1,[32,0,["wormholeId"]]],[16,0,[31,[[32,0,["wormholeClass"]]," liquid-wormhole-element"]]]],[[],[]],null],[2,"\\n"]],"parameters":[]}]]]],"parameters":[]},{"statements":[[2," "],[10,"div"],[15,1,[32,0,["wormholeId"]]],[15,0,[31,[[32,0,["wormholeClass"]]," liquid-wormhole-element"]]],[12],[2,"\\n "],[18,2,null],[2,"\\n "],[13],[2,"\\n"]],"parameters":[]}]]]],"hasEval":false,"upvars":["if"]}',moduleName:"liquid-wormhole/templates/components/liquid-wormhole.hbs"})}))
9487
+ define("liquid-wormhole/transitions/wormhole",["exports"],(function(e){"use strict"
9485
9488
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e){let r,n,{use:i}=e
9486
9489
  if(this.oldElement&&(r=this.oldElement.find(".liquid-wormhole-element:last-child"),this.oldElement=null,r.length>0)){const e=r.clone()
9487
9490
  e.addClass("liquid-wormhole-temp-element"),t(e),r.css({visibility:"hidden"}),r.find(".liquid-child").css({visibility:"hidden"})
@@ -9494,8 +9497,7 @@ o="function"==typeof i.handler?i.handler:e.lookup(i.name)
9494
9497
  return o.apply(this,i.args).finally((()=>{this.oldElement&&r&&(this.oldElement.remove(),r.css({visibility:"visible"}),r.find(".liquid-child").css({visibility:"visible"})),this.newElement&&n&&(this.newElement.remove(),n.css({visibility:"visible"}),n.find(".liquid-child").css({visibility:"visible"}))}))}
9495
9498
  let t=e=>{if(!e)return
9496
9499
  let t=e[0].querySelectorAll("[id]")
9497
- if(t.length)for(let r of t)r.setAttribute("id",`${Ember.guidFor(r)}-${r.id}`)}}))
9498
- define("perf-primitives/-constants",["exports"],(function(e){"use strict"
9500
+ if(t.length)for(let r of t)r.setAttribute("id",`${Ember.guidFor(r)}-${r.id}`)}})),define("perf-primitives/-constants",["exports"],(function(e){"use strict"
9499
9501
  Object.defineProperty(e,"__esModule",{value:!0}),e.UNDEFINED_KEY=e.SMALL_ARRAY_LENGTH=e.LARGE_ARRAY_LENGTH=void 0
9500
9502
  e.SMALL_ARRAY_LENGTH=200,e.LARGE_ARRAY_LENGTH=64e3,e.UNDEFINED_KEY=Object.create(null)})),define("perf-primitives/empty-object",["exports"],(function(e){"use strict"
9501
9503
  Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
@@ -9518,4 +9520,4 @@ e.default=class{constructor(e){if(this._data=new t.default,e)for(let t=0;t<e.len
9518
9520
  return this}get(e){let t=this._data[e]
9519
9521
  return t===r.UNDEFINED_KEY?void 0:t}set(e,t){return this._data[e]=t,this}delete(e){return this._data[e]=r.UNDEFINED_KEY,!0}}}))
9520
9522
 
9521
- //# sourceMappingURL=vendor-a3242d3bb7bb2f2ad87cc8ba3d607097.map
9523
+ //# sourceMappingURL=vendor-66e18706cdbe72bd773ca649459072a6.map
@@ -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.92%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%227a9605cc5a%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%220f930972a7%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%221264c822b3%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%22277437e328%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.94%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%226239fc6ccd%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%220f930972a7%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%221264c822b3%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%229aa7d7e927%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-8c20f019d522f17d318e799568924958.css" title="light">
40
+ <link integrity="" rel="stylesheet" href="assets/ghost-d1c51d3a2d840dda5aa7b4ffe6057756.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-bff75d127fcf4a75c8a83b473bdb308f.js"></script>
60
- <script src="assets/chunk.217.2bd17b550359ea58831b.js"></script>
61
- <script src="assets/chunk.524.1bc6b183876a00383f90.js"></script>
62
- <script src="assets/ghost-acdb9966c24d49e97ac26215cb4933e1.js"></script>
59
+ <script src="assets/vendor-649dc64bfd9ecc1bd7361b53680dfdc2.js"></script>
60
+ <script src="assets/chunk.785.78610bd253a9e2419ec9.js"></script>
61
+ <script src="assets/chunk.524.b9da8e9c62fc112baf2b.js"></script>
62
+ <script src="assets/ghost-535088daf62c8cbea78a012bb6bbafc6.js"></script>
63
63
  </body>
64
64
  </html>
@@ -0,0 +1,19 @@
1
+ const logging = require('@tryghost/logging');
2
+ const {createNonTransactionalMigration} = require('../../utils');
3
+ const DatabaseInfo = require('@tryghost/database-info');
4
+
5
+ module.exports = createNonTransactionalMigration(
6
+ async function up(knex) {
7
+ if (DatabaseInfo.isSQLite(knex)) {
8
+ logging.warn('Skipping migration for SQLite3');
9
+ return;
10
+ }
11
+ logging.info('Changing stripe_prices.nickname column from VARCHAR(50) to VARCHAR(255)');
12
+ await knex.schema.alterTable('stripe_prices', function (table) {
13
+ table.string('nickname', 255).alter();
14
+ });
15
+ },
16
+ async function down() {
17
+ logging.warn('Not changing stripe_prices.nickname column');
18
+ }
19
+ );
@@ -0,0 +1,20 @@
1
+ // For information on writing migrations, see https://www.notion.so/ghost/Database-migrations-eb5b78c435d741d2b34a582d57c24253
2
+
3
+ const logging = require('@tryghost/logging');
4
+
5
+ // For DML - data changes
6
+ const {createTransactionalMigration} = require('../../utils');
7
+
8
+ module.exports = createTransactionalMigration(
9
+ async function up(knex) {
10
+ try {
11
+ await knex('jobs')
12
+ .whereIn('name', ['email-analytics-latest-opened', 'email-analytics-latest-others', 'email-analytics-missing'])
13
+ .del();
14
+ } catch (error) {
15
+ logging.info(`Failed to delete email analytics jobs: ${error.message}`);
16
+ }
17
+ },
18
+ // down is a no-op
19
+ async function down() {}
20
+ );
@@ -781,7 +781,7 @@ module.exports = {
781
781
  stripe_price_id: {type: 'string', maxlength: 255, nullable: false, unique: true},
782
782
  stripe_product_id: {type: 'string', maxlength: 255, nullable: false, unique: false, references: 'stripe_products.stripe_product_id'},
783
783
  active: {type: 'boolean', nullable: false},
784
- nickname: {type: 'string', maxlength: 50, nullable: true},
784
+ nickname: {type: 'string', maxlength: 255, nullable: true},
785
785
  // @note: this is longer than originally intended due to a bug - https://github.com/TryGhost/Ghost/pull/15606
786
786
  // so we should decide whether we should reduce it down in the future
787
787
  currency: {type: 'string', maxlength: 191, nullable: false},
@@ -57,11 +57,22 @@ class EmailAnalyticsServiceWrapper {
57
57
  });
58
58
  }
59
59
 
60
- async fetchLatest({maxEvents} = {maxEvents: Infinity}) {
61
- logging.info('[EmailAnalytics] Fetch latest started');
60
+ async fetchLatestOpenedEvents({maxEvents} = {maxEvents: Infinity}) {
61
+ logging.info('[EmailAnalytics] Fetch latest opened events started');
62
62
 
63
63
  const fetchStartDate = new Date();
64
- const totalEvents = await this.service.fetchLatest({maxEvents});
64
+ const totalEvents = await this.service.fetchLatestOpenedEvents({maxEvents});
65
+ const fetchEndDate = new Date();
66
+
67
+ logging.info(`[EmailAnalytics] Fetched ${totalEvents} events and aggregated stats in ${fetchEndDate.getTime() - fetchStartDate.getTime()}ms (latest opens)`);
68
+ return totalEvents;
69
+ }
70
+
71
+ async fetchLatestNonOpenedEvents({maxEvents} = {maxEvents: Infinity}) {
72
+ logging.info('[EmailAnalytics] Fetch latest non-opened events started');
73
+
74
+ const fetchStartDate = new Date();
75
+ const totalEvents = await this.service.fetchLatestNonOpenedEvents({maxEvents});
65
76
  const fetchEndDate = new Date();
66
77
 
67
78
  logging.info(`[EmailAnalytics] Fetched ${totalEvents} events and aggregated stats in ${fetchEndDate.getTime() - fetchStartDate.getTime()}ms (latest)`);
@@ -69,7 +80,7 @@ class EmailAnalyticsServiceWrapper {
69
80
  }
70
81
 
71
82
  async fetchMissing({maxEvents} = {maxEvents: Infinity}) {
72
- logging.info('[EmailAnalytics] Fetch missing started');
83
+ logging.info('[EmailAnalytics] Fetch missing events started');
73
84
 
74
85
  const fetchStartDate = new Date();
75
86
  const totalEvents = await this.service.fetchMissing({maxEvents});
@@ -83,7 +94,7 @@ class EmailAnalyticsServiceWrapper {
83
94
  if (maxEvents < 300) {
84
95
  return 0;
85
96
  }
86
- logging.info('[EmailAnalytics] Fetch scheduled started');
97
+ logging.info('[EmailAnalytics] Fetch scheduled events started');
87
98
 
88
99
  const fetchStartDate = new Date();
89
100
  const totalEvents = await this.service.fetchScheduled({maxEvents});
@@ -100,13 +111,34 @@ class EmailAnalyticsServiceWrapper {
100
111
  }
101
112
  this.fetching = true;
102
113
 
114
+ // NOTE: Data shows we can process ~2500 events per minute on Pro for a large-ish db (150k members).
115
+ // This can vary locally, but we should be conservative with the number of events we fetch.
103
116
  try {
104
- const c1 = await this.fetchLatest({maxEvents: Infinity});
105
- const c2 = await this.fetchMissing({maxEvents: Infinity});
106
-
107
- // Only fetch scheduled if we didn't fetch a lot of normal events
108
- await this.fetchScheduled({maxEvents: 20000 - c1 - c2});
109
-
117
+ // Prioritize opens since they are the most important (only data directly displayed to users)
118
+ const c1 = await this.fetchLatestOpenedEvents({maxEvents: 10000});
119
+ if (c1 >= 10000) {
120
+ this._restartFetch('high opened event count');
121
+ return;
122
+ }
123
+
124
+ // Set limits on how much we fetch without checkings for opened events. During surge events (following newsletter send)
125
+ // we want to make sure we don't spend too much time collecting delivery data.
126
+ const c2 = await this.fetchLatestNonOpenedEvents({maxEvents: 10000 - c1});
127
+ const c3 = await this.fetchMissing({maxEvents: 10000 - c1 - c2});
128
+
129
+ // Always restart immediately instead of waiting for the next scheduled job if we're fetching a lot of events
130
+ if ((c1 + c2 + c3) > 10000) {
131
+ this._restartFetch('high event count');
132
+ return;
133
+ }
134
+
135
+ // Only backfill if we're not currently fetching a lot of events
136
+ const c4 = await this.fetchScheduled({maxEvents: 10000});
137
+ if (c4 > 0) {
138
+ this._restartFetch('scheduled backfill');
139
+ return;
140
+ }
141
+
110
142
  this.fetching = false;
111
143
  } catch (e) {
112
144
  logging.error(e, 'Error while fetching email analytics');
@@ -116,6 +148,12 @@ class EmailAnalyticsServiceWrapper {
116
148
  }
117
149
  this.fetching = false;
118
150
  }
151
+
152
+ _restartFetch(reason) {
153
+ this.fetching = false;
154
+ logging.info(`[EmailAnalytics] Restarting fetch due to ${reason}`);
155
+ this.startFetch();
156
+ }
119
157
  }
120
158
 
121
159
  module.exports = EmailAnalyticsServiceWrapper;
@@ -1,9 +1,29 @@
1
1
  const _ = require('lodash');
2
2
  const debug = require('@tryghost/debug')('services:email-analytics');
3
3
  const db = require('../../../data/db');
4
+ const logging = require('@tryghost/logging');
5
+ const {default: ObjectID} = require('bson-objectid');
4
6
 
5
7
  const MIN_EMAIL_COUNT_FOR_OPEN_RATE = 5;
6
8
 
9
+ /** @typedef {'email-analytics-latest-opened'|'email-analytics-latest-others'|'email-analytics-missing'|'email-analytics-scheduled'} EmailAnalyticsJobName */
10
+ /** @typedef {'delivered'|'opened'|'failed'} EmailAnalyticsEvent */
11
+
12
+ /**
13
+ * Creates a job in the jobs table if it does not already exist.
14
+ * @param {EmailAnalyticsJobName} jobName - The name of the job to create.
15
+ * @returns {Promise<void>}
16
+ */
17
+ async function createJobIfNotExists(jobName) {
18
+ await db.knex('jobs').insert({
19
+ id: new ObjectID().toHexString(),
20
+ name: jobName,
21
+ started_at: new Date(),
22
+ created_at: new Date(),
23
+ status: 'started'
24
+ }).onConflict('name').ignore();
25
+ }
26
+
7
27
  module.exports = {
8
28
  async shouldFetchStats() {
9
29
  // don't fetch stats from Mailgun if we haven't sent any emails
@@ -11,47 +31,151 @@ module.exports = {
11
31
  return emailCount && emailCount.count > 0;
12
32
  },
13
33
 
14
- async getLastSeenEventTimestamp() {
34
+ /**
35
+ * Retrieves the timestamp of the last seen event for the specified email analytics events.
36
+ * @param {EmailAnalyticsJobName} jobName - The name of the job to update.
37
+ * @param {EmailAnalyticsEvent[]} [events=['delivered', 'opened', 'failed']] - The email analytics events to consider.
38
+ * @returns {Promise<Date|null>} The timestamp of the last seen event, or null if no events are found.
39
+ */
40
+ async getLastEventTimestamp(jobName, events = ['delivered', 'opened', 'failed']) {
15
41
  const startDate = new Date();
42
+
43
+ let maxOpenedAt;
44
+ let maxDeliveredAt;
45
+ let maxFailedAt;
46
+ const lastJobRunTimestamp = await this.getLastJobRunTimestamp(jobName);
47
+
48
+ if (lastJobRunTimestamp) {
49
+ debug(`Using job data for ${jobName}`);
50
+ maxOpenedAt = events.includes('opened') ? lastJobRunTimestamp : null;
51
+ maxDeliveredAt = events.includes('delivered') ? lastJobRunTimestamp : null;
52
+ maxFailedAt = events.includes('failed') ? lastJobRunTimestamp : null;
53
+ } else {
54
+ debug(`Job data not found for ${jobName}, using email_recipients data`);
55
+ logging.info(`Job data not found for ${jobName}, using email_recipients data`);
56
+ if (events.includes('opened')) {
57
+ maxOpenedAt = (await db.knex('email_recipients').select(db.knex.raw('MAX(opened_at) as maxOpenedAt')).first()).maxOpenedAt;
58
+ }
59
+ if (events.includes('delivered')) {
60
+ maxDeliveredAt = (await db.knex('email_recipients').select(db.knex.raw('MAX(delivered_at) as maxDeliveredAt')).first()).maxDeliveredAt;
61
+ }
62
+ if (events.includes('failed')) {
63
+ maxFailedAt = (await db.knex('email_recipients').select(db.knex.raw('MAX(failed_at) as maxFailedAt')).first()).maxFailedAt;
64
+ }
65
+
66
+ await createJobIfNotExists(jobName);
67
+ }
16
68
 
17
- // three separate queries is much faster than using max/greatest (with coalesce to handle nulls) across columns
18
- let {maxDeliveredAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(delivered_at) as maxDeliveredAt')).first() || {};
19
- let {maxOpenedAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(opened_at) as maxOpenedAt')).first() || {};
20
- let {maxFailedAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(failed_at) as maxFailedAt')).first() || {};
69
+ // Convert string dates to Date objects for SQLite compatibility
70
+ [maxOpenedAt, maxDeliveredAt, maxFailedAt] = [maxOpenedAt, maxDeliveredAt, maxFailedAt].map(date => (
71
+ date && !(date instanceof Date) ? new Date(date) : date
72
+ ));
21
73
 
22
- if (maxDeliveredAt && !(maxDeliveredAt instanceof Date)) {
23
- // SQLite returns a string instead of a Date
24
- maxDeliveredAt = new Date(maxDeliveredAt);
25
- }
74
+ const lastSeenEventTimestamp = _.max([maxOpenedAt, maxDeliveredAt, maxFailedAt]);
75
+ debug(`getLastEventTimestamp: finished in ${Date.now() - startDate}ms`);
26
76
 
27
- if (maxOpenedAt && !(maxOpenedAt instanceof Date)) {
28
- // SQLite returns a string instead of a Date
29
- maxOpenedAt = new Date(maxOpenedAt);
30
- }
77
+ return lastSeenEventTimestamp;
78
+ },
31
79
 
32
- if (maxFailedAt && !(maxFailedAt instanceof Date)) {
33
- // SQLite returns a string instead of a Date
34
- maxFailedAt = new Date(maxFailedAt);
35
- }
80
+ /**
81
+ * Retrieves the job data for the specified job name.
82
+ * @param {EmailAnalyticsJobName} jobName - The name of the job to retrieve data for.
83
+ * @returns {Promise<Object|null>} The job data, or null if no job data is found.
84
+ */
85
+ async getJobData(jobName) {
86
+ return await db.knex('jobs').select('finished_at', 'started_at').where('name', jobName).first();
87
+ },
88
+
89
+ /**
90
+ * Retrieves the timestamp of the last job run for the specified job name.
91
+ * @param {EmailAnalyticsJobName} jobName - The name of the job to retrieve the last run timestamp for.
92
+ * @returns {Promise<Date|null>} The timestamp of the last job run, or null if no job data is found.
93
+ */
94
+ async getLastJobRunTimestamp(jobName) {
95
+ const jobData = await this.getJobData(jobName);
96
+ return jobData ? jobData.finished_at || jobData.started_at : null;
97
+ },
36
98
 
37
- const lastSeenEventTimestamp = _.max([maxDeliveredAt, maxOpenedAt, maxFailedAt]);
38
- debug(`getLastSeenEventTimestamp: finished in ${Date.now() - startDate}ms`);
99
+ /**
100
+ * Sets the timestamp of the last seen event for the specified email analytics events.
101
+ * @param {EmailAnalyticsJobName} jobName - The name of the job to update.
102
+ * @param {'completed'|'started'} field - The field to update.
103
+ * @param {Date} date - The timestamp of the last seen event.
104
+ * @returns {Promise<void>}
105
+ * @description
106
+ * Updates the `finished_at` or `started_at` column of the specified job in the `jobs` table with the provided timestamp.
107
+ * This is used to keep track of the last time the job was run to avoid expensive queries following reboot.
108
+ */
109
+ async setJobTimestamp(jobName, field, date) {
110
+ // Convert string dates to Date objects for SQLite compatibility
111
+ try {
112
+ debug(`Setting ${field} timestamp for job ${jobName} to ${date}`);
113
+ const updateField = field === 'completed' ? 'finished_at' : 'started_at';
114
+ const status = field === 'completed' ? 'finished' : 'started';
115
+ const result = await db.knex('jobs').update({[updateField]: date, updated_at: new Date(), status: status}).where('name', jobName);
116
+ if (result === 0) {
117
+ await db.knex('jobs').insert({
118
+ id: new ObjectID().toHexString(),
119
+ name: jobName,
120
+ [updateField]: date,
121
+ updated_at: date,
122
+ status: status
123
+ });
124
+ }
125
+ } catch (err) {
126
+ debug(`Error setting ${field} timestamp for job ${jobName}: ${err.message}`);
127
+ }
128
+ },
39
129
 
40
- return lastSeenEventTimestamp;
130
+ /**
131
+ * Sets the status of the specified email analytics job.
132
+ * @param {EmailAnalyticsJobName} jobName - The name of the job to update.
133
+ * @param {'started'|'finished'|'failed'} status - The new status of the job.
134
+ * @returns {Promise<void>}
135
+ * @description
136
+ * Updates the `status` column of the specified job in the `jobs` table with the provided status.
137
+ * This is used to keep track of the current state of the job.
138
+ */
139
+ async setJobStatus(jobName, status) {
140
+ debug(`Setting status for job ${jobName} to ${status}`);
141
+ try {
142
+ const result = await db.knex('jobs')
143
+ .update({
144
+ status: status,
145
+ updated_at: new Date()
146
+ })
147
+ .where('name', jobName);
148
+
149
+ if (result === 0) {
150
+ await db.knex('jobs').insert({
151
+ id: new ObjectID().toHexString(),
152
+ name: jobName,
153
+ status: status,
154
+ created_at: new Date(),
155
+ updated_at: new Date()
156
+ });
157
+ }
158
+ } catch (err) {
159
+ debug(`Error setting status for job ${jobName}: ${err.message}`);
160
+ throw err;
161
+ }
41
162
  },
42
163
 
43
- async aggregateEmailStats(emailId) {
44
- const {totalCount} = await db.knex('emails').select(db.knex.raw('email_count as totalCount')).where('id', emailId).first() || {totalCount: 0};
45
- // use IS NULL here because that will typically match far fewer rows than IS NOT NULL making the query faster
46
- const [undeliveredCount] = await db.knex('email_recipients').count('id as count').whereRaw('email_id = ? AND delivered_at IS NULL', [emailId]);
47
- const [openedCount] = await db.knex('email_recipients').count('id as count').whereRaw('email_id = ? AND opened_at IS NOT NULL', [emailId]);
164
+ async aggregateEmailStats(emailId, updateOpenedCount) {
165
+ const [deliveredCount] = await db.knex('email_recipients').count('id as count').whereRaw('email_id = ? AND delivered_at IS NOT NULL', [emailId]);
48
166
  const [failedCount] = await db.knex('email_recipients').count('id as count').whereRaw('email_id = ? AND failed_at IS NOT NULL', [emailId]);
49
167
 
50
- await db.knex('emails').update({
51
- delivered_count: totalCount - undeliveredCount.count,
52
- opened_count: openedCount.count,
168
+ const updateData = {
169
+ delivered_count: deliveredCount.count,
53
170
  failed_count: failedCount.count
54
- }).where('id', emailId);
171
+ };
172
+
173
+ if (updateOpenedCount) {
174
+ const [openedCount] = await db.knex('email_recipients').count('id as count').whereRaw('email_id = ? AND opened_at IS NOT NULL', [emailId]);
175
+ updateData.opened_count = openedCount.count;
176
+ }
177
+
178
+ await db.knex('emails').update(updateData).where('id', emailId);
55
179
  },
56
180
 
57
181
  async aggregateMemberStats(memberId) {
@@ -78,4 +202,4 @@ module.exports = {
78
202
  .update(updateQuery)
79
203
  .where('id', memberId);
80
204
  }
81
- };
205
+ };
@@ -1,8 +1,9 @@
1
1
  const config = require('../../../shared/config');
2
+ const storage = require('../../adapters/storage');
2
3
  const externalRequest = require('../../lib/request-external');
3
4
 
4
5
  const OEmbed = require('@tryghost/oembed-service');
5
- const oembed = new OEmbed({config, externalRequest});
6
+ const oembed = new OEmbed({config, externalRequest, storage});
6
7
 
7
8
  const NFT = require('./NFTOEmbedProvider');
8
9
  const nft = new NFT({