diff --git a/.envrc b/.envrc deleted file mode 100644 index 7f0f48d8c3..0000000000 --- a/.envrc +++ /dev/null @@ -1,2 +0,0 @@ -layout node -use flake . diff --git a/.gitignore b/.gitignore index ac45d84b1d..3162730395 100644 --- a/.gitignore +++ b/.gitignore @@ -66,9 +66,6 @@ typings/ # dotenv environment variables file .env -# direnv -.direnv - # IDE .idea/* *.iml diff --git a/apps/admin-x-activitypub/package.json b/apps/admin-x-activitypub/package.json index 0782704d0b..19146b51d3 100644 --- a/apps/admin-x-activitypub/package.json +++ b/apps/admin-x-activitypub/package.json @@ -33,7 +33,7 @@ }, "devDependencies": { "@playwright/test": "1.38.1", - "@testing-library/react": "14.1.0", + "@testing-library/react": "14.3.1", "@tryghost/admin-x-design-system": "0.0.0", "@tryghost/admin-x-framework": "0.0.0", "@types/jest": "29.5.12", diff --git a/apps/admin-x-demo/package.json b/apps/admin-x-demo/package.json index 426e969f5c..abb1fe1b9f 100644 --- a/apps/admin-x-demo/package.json +++ b/apps/admin-x-demo/package.json @@ -32,7 +32,7 @@ "preview": "vite preview" }, "devDependencies": { - "@testing-library/react": "14.1.0", + "@testing-library/react": "14.3.1", "@tryghost/admin-x-design-system": "0.0.0", "@tryghost/admin-x-framework": "0.0.0", "@types/react": "18.3.3", diff --git a/apps/admin-x-design-system/package.json b/apps/admin-x-design-system/package.json index 124ee2b4e6..9c73c08281 100644 --- a/apps/admin-x-design-system/package.json +++ b/apps/admin-x-design-system/package.json @@ -36,7 +36,7 @@ "@storybook/react": "7.6.20", "@storybook/react-vite": "7.6.4", "@storybook/testing-library": "0.2.2", - "@testing-library/react": "14.1.0", + "@testing-library/react": "14.3.1", "@testing-library/react-hooks" : "8.0.1", "@vitejs/plugin-react": "4.2.1", "c8": "8.0.1", diff --git a/apps/admin-x-framework/package.json b/apps/admin-x-framework/package.json index f8ccf034da..ed983ab571 100644 --- a/apps/admin-x-framework/package.json +++ b/apps/admin-x-framework/package.json @@ -68,7 +68,7 @@ "types" ], "devDependencies": { - "@testing-library/react": "14.1.0", + "@testing-library/react": "14.3.1", "@types/mocha": "10.0.1", "c8": "8.0.1", "eslint-plugin-react-hooks": "4.6.0", diff --git a/apps/admin-x-settings/package.json b/apps/admin-x-settings/package.json index 63f09ab344..7200fa4819 100644 --- a/apps/admin-x-settings/package.json +++ b/apps/admin-x-settings/package.json @@ -39,7 +39,7 @@ "dependencies": { "@codemirror/lang-html": "6.4.9", "@tryghost/color-utils": "0.2.2", - "@tryghost/kg-unsplash-selector": "0.2.1", + "@tryghost/kg-unsplash-selector": "0.2.3", "@tryghost/limit-service": "1.2.14", "@tryghost/nql": "0.12.3", "@tryghost/timezone-data": "0.4.3", @@ -49,7 +49,7 @@ }, "devDependencies": { "@playwright/test": "1.38.1", - "@testing-library/react": "14.1.0", + "@testing-library/react": "14.3.1", "@tryghost/admin-x-design-system": "0.0.0", "@tryghost/admin-x-framework": "0.0.0", "@types/react": "18.3.3", diff --git a/apps/comments-ui/package.json b/apps/comments-ui/package.json index 605af91ba1..4be6217a89 100644 --- a/apps/comments-ui/package.json +++ b/apps/comments-ui/package.json @@ -44,16 +44,16 @@ }, "dependencies": { "@headlessui/react": "1.7.19", - "@tiptap/core": "2.5.8", - "@tiptap/extension-blockquote": "2.5.8", - "@tiptap/extension-document": "2.5.8", - "@tiptap/extension-hard-break": "2.5.8", - "@tiptap/extension-link": "2.5.8", - "@tiptap/extension-paragraph": "2.5.8", - "@tiptap/extension-placeholder": "2.5.8", - "@tiptap/extension-text": "2.5.8", - "@tiptap/pm": "2.5.8", - "@tiptap/react": "2.5.8", + "@tiptap/core": "2.5.9", + "@tiptap/extension-blockquote": "2.5.9", + "@tiptap/extension-document": "2.5.9", + "@tiptap/extension-hard-break": "2.5.9", + "@tiptap/extension-link": "2.5.9", + "@tiptap/extension-paragraph": "2.5.9", + "@tiptap/extension-placeholder": "2.5.9", + "@tiptap/extension-text": "2.5.9", + "@tiptap/pm": "2.5.9", + "@tiptap/react": "2.5.9", "react": "17.0.2", "react-dom": "17.0.2", "react-string-replace": "1.1.1" @@ -62,7 +62,7 @@ "@playwright/test": "1.38.1", "@testing-library/jest-dom": "5.17.0", "@testing-library/react": "12.1.5", - "@testing-library/user-event": "14.4.3", + "@testing-library/user-event": "14.5.2", "@tryghost/i18n": "0.0.0", "@vitejs/plugin-react": "4.2.1", "@vitest/coverage-v8": "0.34.3", diff --git a/apps/portal/package.json b/apps/portal/package.json index 395f38a128..af19aa4ec6 100644 --- a/apps/portal/package.json +++ b/apps/portal/package.json @@ -1,6 +1,6 @@ { "name": "@tryghost/portal", - "version": "2.37.10", + "version": "2.38.0", "license": "MIT", "repository": { "type": "git", diff --git a/apps/portal/src/components/pages/AccountHomePage/AccountHomePage.test.js b/apps/portal/src/components/pages/AccountHomePage/AccountHomePage.test.js index ac6679bf47..6b4c6aa143 100644 --- a/apps/portal/src/components/pages/AccountHomePage/AccountHomePage.test.js +++ b/apps/portal/src/components/pages/AccountHomePage/AccountHomePage.test.js @@ -48,4 +48,11 @@ describe('Account Home Page', () => { fireEvent.click(manageBtn); expect(mockOnActionFn).toHaveBeenCalledWith('switchPage', {lastPage: 'accountHome', page: 'accountEmail'}); }); + + test('hides Newsletter toggle if newsletters are disabled', () => { + const siteData = getSiteData({editorDefaultEmailRecipients: 'disabled'}); + const {logoutBtn, utils} = setup({site: siteData}); + expect(logoutBtn).toBeInTheDocument(); + expect(utils.queryByText('Email newsletter')).not.toBeInTheDocument(); + }); }); diff --git a/apps/portal/src/components/pages/AccountHomePage/components/AccountActions.js b/apps/portal/src/components/pages/AccountHomePage/components/AccountActions.js index 6a0a03402f..f243f431ae 100644 --- a/apps/portal/src/components/pages/AccountHomePage/components/AccountActions.js +++ b/apps/portal/src/components/pages/AccountHomePage/components/AccountActions.js @@ -1,6 +1,6 @@ import AppContext from '../../../../AppContext'; import {useContext} from 'react'; -import {hasCommentsEnabled, hasMultipleNewsletters, isEmailSuppressed} from '../../../../utils/helpers'; +import {hasCommentsEnabled, hasMultipleNewsletters, isEmailSuppressed, hasNewsletterSendingEnabled} from '../../../../utils/helpers'; import PaidAccountActions from './PaidAccountActions'; import EmailNewsletterAction from './EmailNewsletterAction'; @@ -19,6 +19,8 @@ const AccountActions = () => { const showEmailPreferences = hasMultipleNewsletters({site}) || hasCommentsEnabled({site}) || isEmailSuppressed({member}); + const showEmailUnsubscribe = hasNewsletterSendingEnabled({site}); + return (
@@ -40,7 +42,13 @@ const AccountActions = () => { { showEmailPreferences ? - : + : <> + } + + { + showEmailUnsubscribe && !showEmailPreferences + ? + : <> }
diff --git a/apps/portal/src/utils/fixtures-generator.js b/apps/portal/src/utils/fixtures-generator.js index 4fda8d6de3..74adfd1369 100644 --- a/apps/portal/src/utils/fixtures-generator.js +++ b/apps/portal/src/utils/fixtures-generator.js @@ -39,6 +39,7 @@ export function getSiteData({ portalButtonSignupText: portal_button_signup_text = 'Subscribe now', portalButtonStyle: portal_button_style = 'icon-and-text', membersSupportAddress: members_support_address = 'support@example.com', + editorDefaultEmailRecipients: editor_default_email_recipients = 'visibility', newsletters = [], commentsEnabled, recommendations = [], @@ -69,7 +70,8 @@ export function getSiteData({ comments_enabled: commentsEnabled !== 'off', newsletters, recommendations, - recommendations_enabled: !!recommendationsEnabled + recommendations_enabled: !!recommendationsEnabled, + editor_default_email_recipients }; } diff --git a/apps/portal/src/utils/helpers.js b/apps/portal/src/utils/helpers.js index 0b45f80893..c178c4c11a 100644 --- a/apps/portal/src/utils/helpers.js +++ b/apps/portal/src/utils/helpers.js @@ -86,6 +86,10 @@ export function getNewsletterFromUuid({site, uuid}) { }); } +export function hasNewsletterSendingEnabled({site}) { + return site?.editor_default_email_recipients === 'visibility'; +} + export function allowCompMemberUpgrade({member}) { return member?.subscriptions?.[0]?.tier?.expiry_at !== undefined; } diff --git a/apps/sodo-search/package.json b/apps/sodo-search/package.json index f7c29adc72..6dbebc5983 100644 --- a/apps/sodo-search/package.json +++ b/apps/sodo-search/package.json @@ -82,7 +82,7 @@ ] }, "devDependencies": { - "@testing-library/jest-dom": "5.16.5", + "@testing-library/jest-dom": "5.17.0", "@testing-library/react": "12.1.5", "@vitejs/plugin-react": "4.2.1", "jsdom": "24.1.1", diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 2ab3e2a09a..0000000000 --- a/flake.lock +++ /dev/null @@ -1,43 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1708501555, - "narHash": "sha256-zJaF0RkdIPbh8LTmnpW/E7tZYpqIE+MePzlWwUNob4c=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "b50a77c03d640716296021ad58950b1bb0345799", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs", - "systems": "systems" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index c7822686b5..0000000000 --- a/flake.nix +++ /dev/null @@ -1,49 +0,0 @@ -{ - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - systems.url = "github:nix-systems/default"; - }; - - outputs = { - systems, - nixpkgs, - ... - } @ inputs: let - yarn_overlay = final: prev: { - yarn = prev.yarn.overrideAttrs(finalAttrs: prevAttrs: { - # This is to make sure that yarn runs the correct node version - # https://github.com/NixOS/nixpkgs/issues/145634#issuecomment-1627476963 - installPhase = prevAttrs.installPhase + '' - ln -fs $out/libexec/yarn/bin/yarn $out/bin/yarn - ln -fs $out/libexec/yarn/bin/yarn.js $out/bin/yarn.js - ln -fs $out/libexec/yarn/bin/yarn $out/bin/yarnpkg - ''; - }); - }; - - # This gives us a central place to set the node version - node_overlay = final: prev: { - nodejs = prev.nodejs-18_x; - }; - - eachSystem = f: - nixpkgs.lib.genAttrs (import systems) ( - system: - f ((nixpkgs.legacyPackages.${system}.extend yarn_overlay).extend node_overlay) - ); - in { - - devShells = eachSystem (pkgs: { - default = pkgs.mkShell { - buildInputs = with pkgs; [ - nodejs - yarn - ]; - - shellHook = '' - echo "node `${pkgs.nodejs}/bin/node --version`" - ''; - }; - }); - }; -} diff --git a/ghost/admin/app/components/koenig-lexical-editor.js b/ghost/admin/app/components/koenig-lexical-editor.js index 8f91477f74..b63e16157f 100644 --- a/ghost/admin/app/components/koenig-lexical-editor.js +++ b/ghost/admin/app/components/koenig-lexical-editor.js @@ -452,8 +452,6 @@ export default class KoenigLexicalEditor extends Component { feature: { collectionsCard: this.feature.collectionsCard, collections: this.feature.collections, - internalLinking: this.feature.internalLinking, - internalLinkingAtLinks: this.feature.internalLinking, contentVisibility: this.feature.contentVisibility }, deprecated: { // todo fix typo @@ -705,7 +703,7 @@ export default class KoenigLexicalEditor extends Component { Loading editor...

}> - +
diff --git a/ghost/admin/app/routes/application.js b/ghost/admin/app/routes/application.js index 24a5f7ad57..49a3362a03 100644 --- a/ghost/admin/app/routes/application.js +++ b/ghost/admin/app/routes/application.js @@ -186,7 +186,7 @@ export default Route.extend(ShortcutsRoute, { beforeSend, ignoreErrors: [ // Browser autoplay policies (this regex covers a few) - /^The play\(\) request was interrupted.*/, + /The play\(\) request was interrupted.*/, /The request is not allowed by the user agent or the platform in the current context/, // Network errors that we don't control diff --git a/ghost/admin/app/services/feature.js b/ghost/admin/app/services/feature.js index b58fd84c45..fdf089fee1 100644 --- a/ghost/admin/app/services/feature.js +++ b/ghost/admin/app/services/feature.js @@ -76,7 +76,6 @@ export default class FeatureService extends Service { @feature('lexicalIndicators') lexicalIndicators; @feature('adminXDemo') adminXDemo; @feature('ActivityPub') ActivityPub; - @feature('internalLinking') internalLinking; @feature('editorExcerpt') editorExcerpt; @feature('contentVisibility') contentVisibility; @feature('publishFlowEndScreen') publishFlowEndScreen; diff --git a/ghost/admin/app/services/search-provider.js b/ghost/admin/app/services/search-provider-basic.js similarity index 98% rename from ghost/admin/app/services/search-provider.js rename to ghost/admin/app/services/search-provider-basic.js index 2bef46af0c..cb73ee2820 100644 --- a/ghost/admin/app/services/search-provider.js +++ b/ghost/admin/app/services/search-provider-basic.js @@ -36,7 +36,7 @@ export const SEARCHABLES = [ } ]; -export default class SearchProviderService extends Service { +export default class SearchProviderBasicService extends Service { @service ajax; @service notifications; @service store; diff --git a/ghost/admin/app/services/search-provider-beta.js b/ghost/admin/app/services/search-provider-flex.js similarity index 98% rename from ghost/admin/app/services/search-provider-beta.js rename to ghost/admin/app/services/search-provider-flex.js index ce40a1800c..92a1b5e6d1 100644 --- a/ghost/admin/app/services/search-provider-beta.js +++ b/ghost/admin/app/services/search-provider-flex.js @@ -45,7 +45,7 @@ export const SEARCHABLES = [ } ]; -export default class SearchProviderService extends Service { +export default class SearchProviderFlexService extends Service { @service ajax; @service notifications; @service store; diff --git a/ghost/admin/app/services/search.js b/ghost/admin/app/services/search.js index 2ba14e6367..f8e3f9e500 100644 --- a/ghost/admin/app/services/search.js +++ b/ghost/admin/app/services/search.js @@ -8,16 +8,16 @@ export default class SearchService extends Service { @service ajax; @service feature; @service notifications; - @service searchProvider; - @service searchProviderBeta; + @service searchProviderBasic; + @service searchProviderFlex; + @service settings; @service store; isContentStale = true; get provider() { - return this.feature.internalLinking - ? this.searchProviderBeta - : this.searchProvider; + const isEnglish = this.settings.locale?.toLowerCase().startsWith('en') ?? true; + return isEnglish ? this.searchProviderFlex : this.searchProviderBasic; } @action diff --git a/ghost/admin/app/styles/layouts/members.css b/ghost/admin/app/styles/layouts/members.css index e9c7383ee5..bfbd1ce2e3 100644 --- a/ghost/admin/app/styles/layouts/members.css +++ b/ghost/admin/app/styles/layouts/members.css @@ -68,6 +68,7 @@ @media (max-width: 1450px) { .members-list-container-stretch { min-height: calc(100vh - 176px); + overflow: hidden; } } diff --git a/ghost/admin/app/styles/patterns/forms.css b/ghost/admin/app/styles/patterns/forms.css index 4eba90853a..bf0a74b085 100644 --- a/ghost/admin/app/styles/patterns/forms.css +++ b/ghost/admin/app/styles/patterns/forms.css @@ -225,7 +225,7 @@ select { .gh-select.error, .error .gh-input-append, select.error { - border-color: var(--red); + border-color: var(--red)!important; } .gh-input:focus, diff --git a/ghost/admin/app/validators/post.js b/ghost/admin/app/validators/post.js index f157e4d2f6..24da716349 100644 --- a/ghost/admin/app/validators/post.js +++ b/ghost/admin/app/validators/post.js @@ -62,12 +62,11 @@ export default BaseValidator.create({ customExcerpt(model) { if (!validator.isLength(model.customExcerpt || '', 0, 300)) { - if (model.feature.editorExcerpt) { - model.errors.add('customExcerpt', 'Excerpt cannot be longer than 300 characters.'); - } else { - model.errors.add('customExcerpt', 'Excerpt cannot be longer than 300 characters.'); - } + const errorMessage = 'Excerpt cannot be longer than 300 characters.'; + model.errors.add('customExcerpt', errorMessage); this.invalidate(); + } else { + model.errors.remove('customExcerpt'); } }, diff --git a/ghost/admin/package.json b/ghost/admin/package.json index 155b57a412..6d68a7e749 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -1,6 +1,6 @@ { "name": "ghost-admin", - "version": "5.89.0", + "version": "5.89.1", "description": "Ember.js admin client for Ghost", "author": "Ghost Foundation", "homepage": "http://ghost.org", @@ -47,9 +47,9 @@ "@tryghost/color-utils": "0.2.2", "@tryghost/ember-promise-modals": "2.0.1", "@tryghost/helpers": "1.1.90", - "@tryghost/kg-clean-basic-html": "4.1.1", - "@tryghost/kg-converters": "1.0.5", - "@tryghost/koenig-lexical": "1.3.13", + "@tryghost/kg-clean-basic-html": "4.1.3", + "@tryghost/kg-converters": "1.0.6", + "@tryghost/koenig-lexical": "1.3.15", "@tryghost/limit-service": "1.2.14", "@tryghost/members-csv": "0.0.0", "@tryghost/nql": "0.12.3", diff --git a/ghost/admin/tests/acceptance/search-test.js b/ghost/admin/tests/acceptance/search-test.js index 3e025285be..ca6871402b 100644 --- a/ghost/admin/tests/acceptance/search-test.js +++ b/ghost/admin/tests/acceptance/search-test.js @@ -2,22 +2,34 @@ import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd'; import {authenticateSession} from 'ember-simple-auth/test-support'; import {click, currentURL, find, findAll, triggerKeyEvent, visit} from '@ember/test-helpers'; import {describe, it} from 'mocha'; -import {enableLabsFlag} from '../helpers/labs-flag'; import {expect} from 'chai'; import {getPosts} from '../../mirage/config/posts'; import {setupApplicationTest} from 'ember-mocha'; import {setupMirage} from 'ember-cli-mirage/test-support'; import {typeInSearch} from 'ember-power-select/test-support/helpers'; +// we have two search providers +// - "flex" which uses the flexsearch engine but is limited to english only +// - "basic" which uses exact string matches in a less performant way but is language agnostic const suites = [{ - name: 'Acceptance: Search', + name: 'Acceptance: Search (flex)', beforeEach() { - // noop + // noop - default locale is 'en' + }, + confirmProvider() { + const searchService = this.owner.lookup('service:search'); + expect(searchService.provider.constructor.name, 'provider name').to.equal('SearchProviderFlexService'); } }, { - name: 'Acceptance: Search (beta)', + name: 'Acceptance: Search (basic)', beforeEach() { - enableLabsFlag(this.server, 'internalLinking'); + this.server.db.settings.update({key: 'locale'}, {value: 'de'}); + }, + confirmProvider() { + const settingsService = this.owner.lookup('service:settings'); + expect(settingsService.locale, 'settings.locale').to.equal('de'); + const searchService = this.owner.lookup('service:search'); + expect(searchService.provider.constructor.name, 'provider name').to.equal('SearchProviderBasicService'); } }]; @@ -48,6 +60,11 @@ suites.forEach((suite) => { return await authenticateSession(); }); + it('is using correct provider', async function () { + await visit('/dashboard'); + suite.confirmProvider.bind(this)(); + }); + it('opens search modal when clicking icon', async function () { await visit('/dashboard'); expect(currentURL(), 'currentURL').to.equal('/dashboard'); diff --git a/ghost/admin/tests/integration/services/search-test.js b/ghost/admin/tests/integration/services/search-test.js index ce453fdeda..cebc03a52e 100644 --- a/ghost/admin/tests/integration/services/search-test.js +++ b/ghost/admin/tests/integration/services/search-test.js @@ -1,18 +1,31 @@ +import {authenticateSession} from 'ember-simple-auth/test-support'; import {describe, it} from 'mocha'; -import {enableLabsFlag} from '../../helpers/labs-flag'; import {expect} from 'chai'; import {setupMirage} from 'ember-cli-mirage/test-support'; import {setupTest} from 'ember-mocha'; +// we have two search providers +// - "flex" which uses the flexsearch engine but is limited to english only +// - "basic" which uses exact string matches in a less performant way but is language agnostic const suites = [{ - name: 'Integration: Service: Search', + name: 'Integration: Service: Search (flex)', beforeEach() { - // noop + // noop - default locale is 'en' + }, + confirmProvider() { + const searchService = this.owner.lookup('service:search'); + expect(searchService.provider.constructor.name, 'provider name').to.equal('SearchProviderFlexService'); } }, { - name: 'Integration: Service: Search (beta)', + name: 'Integration: Service: Search (basic)', beforeEach() { - enableLabsFlag(this.server, 'internalLinking'); + this.server.db.settings.update({key: 'locale'}, {value: 'de'}); + }, + confirmProvider() { + const settingsService = this.owner.lookup('service:settings'); + expect(settingsService.locale, 'settings.locale').to.equal('de'); + const searchService = this.owner.lookup('service:search'); + expect(searchService.provider.constructor.name, 'provider name').to.equal('SearchProviderBasicService'); } }]; @@ -25,9 +38,15 @@ suites.forEach((suite) => { // eslint-disable-next-line no-unused-vars let firstUser, firstPost, secondPost, firstPage, firstTag; - beforeEach(function () { + beforeEach(async function () { + this.server.loadFixtures(); + await authenticateSession(); + suite.beforeEach.bind(this)(); + const settings = this.owner.lookup('service:settings'); + await settings.fetch(); + search = this.owner.lookup('service:search'); // populate store with data we'll be searching @@ -37,6 +56,10 @@ suites.forEach((suite) => { firstTag = this.server.create('tag', {name: 'First tag', slug: 'first-tag'}); }); + it('is using correct provider', async function () { + suite.confirmProvider.bind(this)(); + }); + it('returns urls for search results', async function () { const results = await search.searchTask.perform('first'); diff --git a/ghost/core/core/frontend/meta/blog-logo.js b/ghost/core/core/frontend/meta/blog-logo.js index e472b68be3..dd054ec8e3 100644 --- a/ghost/core/core/frontend/meta/blog-logo.js +++ b/ghost/core/core/frontend/meta/blog-logo.js @@ -11,7 +11,7 @@ function getBlogLogo() { // CASE: no publication logo is updated. We can try to use either an uploaded publication icon // or use the default one to make // Google happy with it. See https://github.com/TryGhost/Ghost/issues/7558 - logo.url = blogIcon.getIconUrl(true); + logo.url = blogIcon.getIconUrl({absolute: true}); } return logo; diff --git a/ghost/core/core/frontend/services/rss/renderer.js b/ghost/core/core/frontend/services/rss/renderer.js index 9a15afc918..ab6be75a63 100644 --- a/ghost/core/core/frontend/services/rss/renderer.js +++ b/ghost/core/core/frontend/services/rss/renderer.js @@ -9,7 +9,7 @@ module.exports.render = function render(res, baseUrl, data) { return rssCache .getXML(baseUrl, rssData) .then(function then(feedXml) { - res.set('Content-Type', 'text/xml; charset=UTF-8'); + res.set('Content-Type', 'application/rss+xml; charset=UTF-8'); res.send(feedXml); }); }; diff --git a/ghost/core/core/server/lib/image/BlogIcon.js b/ghost/core/core/server/lib/image/BlogIcon.js index af673419c0..2238dfc04f 100644 --- a/ghost/core/core/server/lib/image/BlogIcon.js +++ b/ghost/core/core/server/lib/image/BlogIcon.js @@ -98,13 +98,17 @@ class BlogIcon { } /** - * Return URL for Blog icon: [subdirectory or not]favicon.[ico, jpeg, or png] - * Always returns {string} getIconUrl - * @returns {string} [subdirectory or not]favicon.[ico, jpeg, or png] + * Return URL for blog icon, if available: [subdirectory or not]favicon.[ico, jpeg, or png] + * Otherwise, fallbacks to the default Ghost favicon.ico file, if requested + * Otherwise, returns null + * @param {Object} [options] + * @param {boolean} [options.absolute] - if true, return absolute URL. Default: false + * @param {boolean} [options.fallbackToDefault] - if true, fallbacks to Ghost's default favicon.ico when no blog icon is found. Default: true + * @returns {string|null} [subdirectory or not]favicon.[ico, jpeg, or png] or null * @description Checks if we have a custom uploaded icon and the extension of it. If no custom uploaded icon * exists, we're returning the default `favicon.ico` */ - getIconUrl(absolute) { + getIconUrl({absolute = false, fallbackToDefault = true} = {}) { const blogIcon = this.settingsCache.get('icon'); if (blogIcon) { @@ -124,9 +128,13 @@ class BlogIcon { const sizedIcon = blogIcon.replace(/\/content\/images\//, '/content/images/size/w256h256/'); return this.urlUtils.urlFor({relativeUrl: sizedIcon}, absolute ? true : undefined); - } else { + } + + if (fallbackToDefault) { return this.urlUtils.urlFor({relativeUrl: '/favicon.ico'}, absolute ? true : undefined); } + + return null; } /** diff --git a/ghost/core/core/server/services/slack.js b/ghost/core/core/server/services/slack.js index be9916e4bc..e25d2a34a3 100644 --- a/ghost/core/core/server/services/slack.js +++ b/ghost/core/core/server/services/slack.js @@ -110,7 +110,7 @@ function ping(post) { // if it is a post or a test message to check webhook working. text: `Notification from *${blogTitle}* :ghost:`, unfurl_links: true, - icon_url: blogIcon.getIconUrl(true), + icon_url: blogIcon.getIconUrl({absolute: true}), username: slackSettings.username, // We don't want to send attachment if it is a test notification. attachments: [ @@ -141,7 +141,7 @@ function ping(post) { } ], footer: blogTitle, - footer_icon: blogIcon.getIconUrl(true), + footer_icon: blogIcon.getIconUrl({absolute: true}), ts: moment().unix() } ] @@ -150,7 +150,7 @@ function ping(post) { slackData = { text: message, unfurl_links: true, - icon_url: blogIcon.getIconUrl(true), + icon_url: blogIcon.getIconUrl({absolute: true}), username: slackSettings.username }; } diff --git a/ghost/core/core/shared/config/defaults.json b/ghost/core/core/shared/config/defaults.json index 40c28eb70f..006a350acc 100644 --- a/ghost/core/core/shared/config/defaults.json +++ b/ghost/core/core/shared/config/defaults.json @@ -182,7 +182,7 @@ }, "portal": { "url": "https://cdn.jsdelivr.net/ghost/portal@~{version}/umd/portal.min.js", - "version": "2.37" + "version": "2.38" }, "sodoSearch": { "url": "https://cdn.jsdelivr.net/ghost/sodo-search@~{version}/umd/sodo-search.min.js", diff --git a/ghost/core/core/shared/labs.js b/ghost/core/core/shared/labs.js index eb2a483148..f515839fa9 100644 --- a/ghost/core/core/shared/labs.js +++ b/ghost/core/core/shared/labs.js @@ -19,8 +19,7 @@ const GA_FEATURES = [ 'themeErrorsNotification', 'outboundLinkTagging', 'announcementBar', - 'newEmailAddresses', - 'internalLinking' + 'newEmailAddresses' ]; // NOTE: this allowlist is meant to be used to filter out any unexpected diff --git a/ghost/core/core/shared/settings-cache/public.js b/ghost/core/core/shared/settings-cache/public.js index fde8a8667e..30fe80f346 100644 --- a/ghost/core/core/shared/settings-cache/public.js +++ b/ghost/core/core/shared/settings-cache/public.js @@ -45,5 +45,6 @@ module.exports = { recommendations_enabled: 'recommendations_enabled', outbound_link_tagging: 'outbound_link_tagging', default_email_address: 'default_email_address', - support_email_address: 'support_email_address' + support_email_address: 'support_email_address', + editor_default_email_recipients: 'editor_default_email_recipients' }; diff --git a/ghost/core/package.json b/ghost/core/package.json index 3bcac936c0..5187d8053f 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -1,6 +1,6 @@ { "name": "ghost", - "version": "5.89.0", + "version": "5.89.1", "description": "The professional publishing platform", "author": "Ghost Foundation", "homepage": "https://ghost.org", @@ -107,14 +107,14 @@ "@tryghost/importer-handler-content-files": "0.0.0", "@tryghost/importer-revue": "0.0.0", "@tryghost/job-manager": "0.0.0", - "@tryghost/kg-card-factory": "5.0.4", - "@tryghost/kg-converters": "1.0.5", + "@tryghost/kg-card-factory": "5.0.5", + "@tryghost/kg-converters": "1.0.6", "@tryghost/kg-default-atoms": "5.0.3", - "@tryghost/kg-default-cards": "10.0.6", - "@tryghost/kg-default-nodes": "1.1.9", - "@tryghost/kg-html-to-lexical": "1.1.10", - "@tryghost/kg-lexical-html-renderer": "1.1.12", - "@tryghost/kg-mobiledoc-html-renderer": "7.0.4", + "@tryghost/kg-default-cards": "10.0.8", + "@tryghost/kg-default-nodes": "1.1.11", + "@tryghost/kg-html-to-lexical": "1.1.12", + "@tryghost/kg-lexical-html-renderer": "1.1.14", + "@tryghost/kg-mobiledoc-html-renderer": "7.0.6", "@tryghost/limit-service": "1.2.14", "@tryghost/link-redirects": "0.0.0", "@tryghost/link-replacer": "0.0.0", diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap index 75c28d354b..8adfdddb9f 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap @@ -1155,7 +1155,7 @@ exports[`Settings API Edit Can edit a setting 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "4454", + "content-length": "4429", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, diff --git a/ghost/core/test/e2e-api/content/__snapshots__/settings.test.js.snap b/ghost/core/test/e2e-api/content/__snapshots__/settings.test.js.snap index a103edbdab..c61c0a5f86 100644 --- a/ghost/core/test/e2e-api/content/__snapshots__/settings.test.js.snap +++ b/ghost/core/test/e2e-api/content/__snapshots__/settings.test.js.snap @@ -12,6 +12,7 @@ Object { "cover_image": "https://static.ghost.org/v5.0.0/images/publication-cover.jpg", "default_email_address": "noreply@127.0.0.1", "description": "Thoughts, stories and ideas", + "editor_default_email_recipients": "visibility", "facebook": "ghost", "firstpromoter_account": null, "icon": null, diff --git a/ghost/core/test/e2e-api/shared/__snapshots__/version.test.js.snap b/ghost/core/test/e2e-api/shared/__snapshots__/version.test.js.snap index 2e9ea09beb..d2d688d07a 100644 --- a/ghost/core/test/e2e-api/shared/__snapshots__/version.test.js.snap +++ b/ghost/core/test/e2e-api/shared/__snapshots__/version.test.js.snap @@ -1364,6 +1364,7 @@ Object { "cover_image": "https://static.ghost.org/v5.0.0/images/publication-cover.jpg", "default_email_address": "noreply@127.0.0.1", "description": "Thoughts, stories and ideas", + "editor_default_email_recipients": "visibility", "facebook": "ghost", "firstpromoter_account": null, "icon": null, @@ -1466,6 +1467,7 @@ Object { "cover_image": "https://static.ghost.org/v5.0.0/images/publication-cover.jpg", "default_email_address": "noreply@127.0.0.1", "description": "Thoughts, stories and ideas", + "editor_default_email_recipients": "visibility", "facebook": "ghost", "firstpromoter_account": null, "icon": null, diff --git a/ghost/core/test/e2e-frontend/default_routes.test.js b/ghost/core/test/e2e-frontend/default_routes.test.js index 3e03a48b10..7fcc50b8ce 100644 --- a/ghost/core/test/e2e-frontend/default_routes.test.js +++ b/ghost/core/test/e2e-frontend/default_routes.test.js @@ -271,7 +271,7 @@ describe('Default Frontend routing', function () { await request.get('/rss/') .expect(200) .expect('Cache-Control', testUtils.cacheRules.public) - .expect('Content-Type', 'text/xml; charset=utf-8') + .expect('Content-Type', 'application/rss+xml; charset=utf-8') .expect(assertCorrectFrontendHeaders) .expect((res) => { res.text.should.match(//); @@ -283,7 +283,7 @@ describe('Default Frontend routing', function () { await request.get('/author/ghost/rss/') .expect(200) .expect('Cache-Control', testUtils.cacheRules.public) - .expect('Content-Type', 'text/xml; charset=utf-8') + .expect('Content-Type', 'application/rss+xml; charset=utf-8') .expect(assertCorrectFrontendHeaders) .expect((res) => { res.text.should.match(//); @@ -295,7 +295,7 @@ describe('Default Frontend routing', function () { await request.get('/tag/getting-started/rss/') .expect(200) .expect('Cache-Control', testUtils.cacheRules.public) - .expect('Content-Type', 'text/xml; charset=utf-8') + .expect('Content-Type', 'application/rss+xml; charset=utf-8') .expect(assertCorrectFrontendHeaders) .expect((res) => { res.text.should.match(//); @@ -461,7 +461,7 @@ describe('Default Frontend routing', function () { await request.get(`/${settingsCache.get('public_hash')}/rss/`) .expect(200) .expect('Cache-Control', testUtils.cacheRules.private) - .expect('Content-Type', 'text/xml; charset=utf-8') + .expect('Content-Type', 'application/rss+xml; charset=utf-8') .expect(assertCorrectFrontendHeaders) .expect((res) => { res.text.should.match(//); @@ -472,7 +472,7 @@ describe('Default Frontend routing', function () { await request.get(`/tag/getting-started/${settingsCache.get('public_hash')}/rss/`) .expect(200) .expect('Cache-Control', testUtils.cacheRules.private) - .expect('Content-Type', 'text/xml; charset=utf-8') + .expect('Content-Type', 'application/rss+xml; charset=utf-8') .expect(assertCorrectFrontendHeaders) .expect((res) => { res.text.should.match(//); diff --git a/ghost/core/test/regression/mock-express-style/api-vs-frontend.test.js b/ghost/core/test/regression/mock-express-style/api-vs-frontend.test.js index b82ea19546..3090aee06a 100644 --- a/ghost/core/test/regression/mock-express-style/api-vs-frontend.test.js +++ b/ghost/core/test/regression/mock-express-style/api-vs-frontend.test.js @@ -1294,7 +1294,7 @@ describe('Frontend behavior tests', function () { return localUtils.mockExpress.invoke(app, req) .then(function (response) { response.statusCode.should.eql(200); - response.headers['content-type'].should.eql('text/xml; charset=UTF-8'); + response.headers['content-type'].should.eql('application/rss+xml; charset=UTF-8'); }); }); @@ -1448,7 +1448,7 @@ describe('Frontend behavior tests', function () { routes: { '/podcast/rss/': { templates: ['podcast/rss'], - content_type: 'text/xml' + content_type: 'application/rss+xml' }, '/cooking/': { controller: 'channel', @@ -1560,7 +1560,7 @@ describe('Frontend behavior tests', function () { .then(function (response) { response.statusCode.should.eql(200); response.template.should.eql('podcast/rss'); - response.headers['content-type'].should.eql('text/xml; charset=utf-8'); + response.headers['content-type'].should.eql('application/rss+xml'); response.body.match(//g).length.should.eql(2); }); }); diff --git a/ghost/core/test/unit/frontend/services/rss/renderer.test.js b/ghost/core/test/unit/frontend/services/rss/renderer.test.js index 7bf55e1789..2641c7c1c5 100644 --- a/ghost/core/test/unit/frontend/services/rss/renderer.test.js +++ b/ghost/core/test/unit/frontend/services/rss/renderer.test.js @@ -32,7 +32,7 @@ describe('RSS: Renderer', function () { rssCacheStub.firstCall.args.should.eql(['/rss/', {}]); res.set.calledOnce.should.be.true(); - res.set.calledWith('Content-Type', 'text/xml; charset=UTF-8').should.be.true(); + res.set.calledWith('Content-Type', 'application/rss+xml; charset=UTF-8').should.be.true(); res.send.calledOnce.should.be.true(); res.send.calledWith('dummyxml').should.be.true(); @@ -51,7 +51,7 @@ describe('RSS: Renderer', function () { rssCacheStub.firstCall.args.should.eql(['/rss/', {foo: 'bar'}]); res.set.calledOnce.should.be.true(); - res.set.calledWith('Content-Type', 'text/xml; charset=UTF-8').should.be.true(); + res.set.calledWith('Content-Type', 'application/rss+xml; charset=UTF-8').should.be.true(); res.send.calledOnce.should.be.true(); res.send.calledWith('dummyxml').should.be.true(); @@ -71,7 +71,7 @@ describe('RSS: Renderer', function () { rssCacheStub.firstCall.args.should.eql(['/rss/', {foo: 'baz', fizz: 'buzz'}]); res.set.calledOnce.should.be.true(); - res.set.calledWith('Content-Type', 'text/xml; charset=UTF-8').should.be.true(); + res.set.calledWith('Content-Type', 'application/rss+xml; charset=UTF-8').should.be.true(); res.send.calledOnce.should.be.true(); res.send.calledWith('dummyxml').should.be.true(); diff --git a/ghost/core/test/unit/server/lib/image/blog-icon.test.js b/ghost/core/test/unit/server/lib/image/blog-icon.test.js index 5d3e9de9c1..4e9e9996f2 100644 --- a/ghost/core/test/unit/server/lib/image/blog-icon.test.js +++ b/ghost/core/test/unit/server/lib/image/blog-icon.test.js @@ -51,7 +51,7 @@ describe('lib/image: blog icon', function () { } } }}); - blogIcon.getIconUrl(true).should.deepEqual([{relativeUrl: '/content/images/2017/04/my-icon.ico'}, true]); + blogIcon.getIconUrl({absolute: true}).should.deepEqual([{relativeUrl: '/content/images/2017/04/my-icon.ico'}, true]); }); it('custom uploaded png blog icon', function () { @@ -64,7 +64,7 @@ describe('lib/image: blog icon', function () { } } }}); - blogIcon.getIconUrl(true).should.deepEqual([{relativeUrl: '/content/images/size/w256h256/2017/04/my-icon.png'}, true]); + blogIcon.getIconUrl({absolute: true}).should.deepEqual([{relativeUrl: '/content/images/size/w256h256/2017/04/my-icon.png'}, true]); }); it('default ico blog icon', function () { @@ -73,7 +73,17 @@ describe('lib/image: blog icon', function () { }, settingsCache: { get: () => {} }}); - blogIcon.getIconUrl(true).should.deepEqual([{relativeUrl: '/favicon.ico'}, true]); + blogIcon.getIconUrl({absolute: true}).should.deepEqual([{relativeUrl: '/favicon.ico'}, true]); + }); + + it('returns null if no fallback is requested', function () { + const blogIcon = new BlogIcon({config: {}, storageUtils: {}, urlUtils: { + urlFor: (key, boolean) => [key, boolean] + }, settingsCache: { + get: () => {} + }}); + + should.equal(blogIcon.getIconUrl({absolute: true, fallbackToDefault: false}), null); }); }); }); diff --git a/ghost/email-service/package.json b/ghost/email-service/package.json index b8e2d30751..6b66e4fb5d 100644 --- a/ghost/email-service/package.json +++ b/ghost/email-service/package.json @@ -29,7 +29,7 @@ "@tryghost/email-events": "0.0.0", "@tryghost/errors": "1.3.5", "@tryghost/html-to-plaintext": "0.0.0", - "@tryghost/kg-default-cards": "10.0.6", + "@tryghost/kg-default-cards": "10.0.8", "@tryghost/logging": "2.4.18", "@tryghost/tpl": "0.1.32", "@tryghost/validator": "0.2.14", diff --git a/ghost/i18n/locales/bg/portal.json b/ghost/i18n/locales/bg/portal.json index 5984b74417..74093559c4 100644 --- a/ghost/i18n/locales/bg/portal.json +++ b/ghost/i18n/locales/bg/portal.json @@ -10,10 +10,10 @@ "{{memberEmail}} will no longer receive emails when someone replies to your comments.": "{{memberEmail}} повече няма да получава имейли, когато някой отговаря на ваш коментар.", "{{memberEmail}} will no longer receive this newsletter.": "{{memberEmail}} повече няма да получава този бюлетин.", "{{trialDays}} days free": "{{trialDays}} дни безплатен достъп", - "A login link has been sent to your inbox. If it doesn't arrive in 3 minutes, be sure to check your spam folder.": "Връзка за влизане беше изпратена към пощенската Ви кутия. Ако писмото не пристигне до 3 минути, провете дали не е категоризирана като нежелано писмо.", + "A login link has been sent to your inbox. If it doesn't arrive in 3 minutes, be sure to check your spam folder.": "Изпратен Ви е имейл с препратка за влизане. Ако не пристигне до 3 минути, проверете дали не е категоризиран като нежелано писмо.", "Account": "Профил", "Account settings": "Настройки на профила Ви", - "After a free trial ends, you will be charged the regular price for the tier you've chosen. You can always cancel before then.": "След прикючване на безплатния достъп ще бъдете таксувани според обявените цени. Можете да се откажете преди изтичането на безплатния достъп.", + "After a free trial ends, you will be charged the regular price for the tier you've chosen. You can always cancel before then.": "След приключване на безплатния достъп ще бъдете таксувани според обявените цени. Можете да се откажете преди изтичането на безплатния достъп.", "Already a member?": "Вече сте абонат на сайта?", "An unexpected error occured. Please try again or contact support if the error persists.": "Възникна неочаквана грешка. Моля, опитайте отново или потърсете поддръжката ако това се повтаря.", "Back": "Обратно", @@ -83,7 +83,7 @@ "Need more help? Contact support": "Още имате нужда от помощ? Потърсете поддръжката", "Newsletters can be disabled on your account for two reasons: A previous email was marked as spam, or attempting to send an email resulted in a permanent failure (bounce).": "Информационните бюлетини могат да бъдат деактивирани в профила ви по две причини: Предишен имейл е бил маркиран като спам или опитът за изпращане на имейл е довел до траен неуспех (отказ).", "Not receiving emails?": "Не получавате поща?", - "Now check your email!": "Провете си пощенската кутия!", + "Now check your email!": "Проверете си пощенската кутия!", "Once resubscribed, if you still don't see emails in your inbox, check your spam folder. Some inbox providers keep a record of previous spam complaints and will continue to flag emails. If this happens, mark the latest newsletter as 'Not spam' to move it back to your primary inbox.": "След като се абонирате отново, ако все още не виждате имейли във входящата си поща, проверете папката за спам. Някои доставчици пазят история с предишни оплаквания за спам и ще продължат да маркират имейлите. Ако вашият случай е такъв, маркирайте последния бюлетин като 'Не е спам', за да го преместите обратно в основната си пощенска кутия.", "Permanent failure (bounce)": "Постоянен проблем (отскок)", "Plan": "План", @@ -125,7 +125,7 @@ "The email address we have for you is {{memberEmail}} — if that's not correct, you can update it in your .": "Имейлът, който имаме за вас, е {{memberEmail}} - ако не е верен, можете да го актуализирате в областта за .", "There was a problem submitting your feedback. Please try again a little later.": "Имаше проблем при изпращането на обратната връзка. Моля, опитайте отново малко по-късно.", "This site is invite-only, contact the owner for access.": "Сайтът е само с покани. Свържете се със собственика за да получите достъп.", - "To complete signup, click the confirmation link in your inbox. If it doesn't arrive within 3 minutes, check your spam folder!": "За да прикючите вашата регистрация беше изпратена връзка за влизане към пощенската Ви кутия. Ако писмото не пристигне до 3 минути, провете дали не е категоризирана като нежелано писмо.", + "To complete signup, click the confirmation link in your inbox. If it doesn't arrive within 3 minutes, check your spam folder!": "За да приключите регистрацията, последвайте препратката в съобщението, изпратено Ви по имейл. Ако не пристигне до 3 минути, проверете дали не е категоризирано като нежелано писмо.", "Try free for {{amount}} days, then {{originalPrice}}.": "Тествайте безплатно за {{amount}} дни, след това {{originalPrice}}.", "Unlock access to all newsletters by becoming a paid subscriber.": "Отключете достъпа до всички бюлетини, като станете платен абонат.", "Unsubscribe from all emails": "Прекрати изпращането на всякакви писма", diff --git a/ghost/i18n/package.json b/ghost/i18n/package.json index e55ed4b862..db6cdcdd70 100644 --- a/ghost/i18n/package.json +++ b/ghost/i18n/package.json @@ -30,6 +30,6 @@ "mocha": "10.2.0" }, "dependencies": { - "i18next": "23.12.2" + "i18next": "23.12.3" } } diff --git a/ghost/importer-revue/package.json b/ghost/importer-revue/package.json index c2e5e14a2a..3eb22f5075 100644 --- a/ghost/importer-revue/package.json +++ b/ghost/importer-revue/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@tryghost/debug": "0.1.32", - "@tryghost/kg-default-cards": "10.0.6", + "@tryghost/kg-default-cards": "10.0.8", "@tryghost/string": "0.2.12", "lodash": "4.17.21", "papaparse": "5.3.2", diff --git a/ghost/minifier/package.json b/ghost/minifier/package.json index e9db19f5b7..431ca3cfc0 100644 --- a/ghost/minifier/package.json +++ b/ghost/minifier/package.json @@ -28,7 +28,7 @@ "@tryghost/errors": "1.3.5", "@tryghost/tpl": "0.1.32", "csso": "5.0.5", - "terser": "5.31.3", + "terser": "5.31.5", "tiny-glob": "0.2.9" } } diff --git a/ghost/staff-service/lib/StaffServiceEmails.js b/ghost/staff-service/lib/StaffServiceEmails.js index fa15117087..4022de8ae6 100644 --- a/ghost/staff-service/lib/StaffServiceEmails.js +++ b/ghost/staff-service/lib/StaffServiceEmails.js @@ -45,7 +45,7 @@ class StaffServiceEmails { attributionUrl: attribution?.url || '', referrerSource: attribution?.referrerSource, siteTitle: this.settingsCache.get('title'), - siteIconUrl: this.blogIcon.getIconUrl(true), + siteIconUrl: this.blogIcon.getIconUrl({absolute: true, fallbackToDefault: false}), siteUrl: this.urlUtils.getSiteUrl(), siteDomain: this.siteDomain, accentColor: this.settingsCache.get('accent_color'), @@ -105,7 +105,7 @@ class StaffServiceEmails { offerData, subscriptionData, siteTitle: this.settingsCache.get('title'), - siteIconUrl: this.blogIcon.getIconUrl(true), + siteIconUrl: this.blogIcon.getIconUrl({absolute: true, fallbackToDefault: false}), siteUrl: this.urlUtils.getSiteUrl(), siteDomain: this.siteDomain, accentColor: this.settingsCache.get('accent_color'), @@ -156,7 +156,7 @@ class StaffServiceEmails { tierData, subscriptionData, siteTitle: this.settingsCache.get('title'), - siteIconUrl: this.blogIcon.getIconUrl(true), + siteIconUrl: this.blogIcon.getIconUrl({absolute: true, fallbackToDefault: false}), siteUrl: this.urlUtils.getSiteUrl(), siteDomain: this.siteDomain, accentColor: this.settingsCache.get('accent_color'), @@ -186,7 +186,7 @@ class StaffServiceEmails { return { siteTitle: this.settingsCache.get('title'), - siteIconUrl: this.blogIcon.getIconUrl(true), + siteIconUrl: this.blogIcon.getIconUrl({absolute: true, fallbackToDefault: false}), siteUrl: this.urlUtils.getSiteUrl(), siteDomain: this.siteDomain, accentColor: this.settingsCache.get('accent_color'), @@ -287,7 +287,7 @@ class StaffServiceEmails { const templateData = { siteTitle: this.settingsCache.get('title'), siteUrl: this.urlUtils.getSiteUrl(), - siteIconUrl: this.blogIcon.getIconUrl(true), + siteIconUrl: this.blogIcon.getIconUrl({absolute: true, fallbackToDefault: false}), siteDomain: this.siteDomain, fromEmail: this.fromEmailAddress, toEmail: to, diff --git a/ghost/staff-service/lib/email-templates/donation.hbs b/ghost/staff-service/lib/email-templates/donation.hbs index 35a33fc398..420ac9c8d7 100644 --- a/ghost/staff-service/lib/email-templates/donation.hbs +++ b/ghost/staff-service/lib/email-templates/donation.hbs @@ -34,9 +34,9 @@

From:

- +

Amount received:

-

{{donation.amount}}

+

{{donation.amount}}

@@ -61,7 +61,7 @@ -

Don’t want to receive these emails? Manage your preferences here.

+

Don’t want to receive these emails? Manage your preferences here.

diff --git a/ghost/staff-service/lib/email-templates/new-free-signup.hbs b/ghost/staff-service/lib/email-templates/new-free-signup.hbs index 1d9e1fb69e..712314fa42 100644 --- a/ghost/staff-service/lib/email-templates/new-free-signup.hbs +++ b/ghost/staff-service/lib/email-templates/new-free-signup.hbs @@ -33,7 +33,7 @@ {{/if}} -

New free subscriber to {{siteTitle}}

+

You have a new free member

@@ -42,13 +42,13 @@ @@ -95,7 +95,7 @@ diff --git a/ghost/staff-service/lib/email-templates/new-paid-cancellation.hbs b/ghost/staff-service/lib/email-templates/new-paid-cancellation.hbs index 3a44cff119..f9a33bbfe1 100644 --- a/ghost/staff-service/lib/email-templates/new-paid-cancellation.hbs +++ b/ghost/staff-service/lib/email-templates/new-paid-cancellation.hbs @@ -30,71 +30,49 @@

Name:

- + {{#if referrerSource}}

Source:

-

{{referrerSource}}

+

{{referrerSource}}

{{#if attributionTitle}}

Page:

- + {{/if}} {{/if}}
-

Don’t want to receive these emails? Manage your preferences here.

+

Don’t want to receive these emails? Manage your preferences here.

+ {{#if siteIconUrl}} + + + + {{/if}}
{{siteTitle}}
-

Hey there,

-

A paid member's subscription has just been canceled.

- - - - - - - - - - - - - {{#if subscriptionData.cancellationReason}} - - - - {{/if}} - -
- - - - - -
-
- {{memberData.initials}} -
-
-

{{memberData.name}}

- {{#if memberData.showEmail}} - - {{/if}} - {{#unless subscriptionData.cancelNow}} -

Canceled on {{subscriptionData.canceledAt}}

- {{/unless}} -
-
-
-

Tier

-

{{tierData.name}} - {{tierData.details}}

-
- {{#if subscriptionData.cancelNow}} -

Subscription expired on

- {{else}} -

Subscription will expire on

- {{/if}} -

{{subscriptionData.expiryAt}}

-
-

Cancellation reason

-

{{subscriptionData.cancellationReason}}

-
- - +

A paid member's subscription has just been canceled

+
- - - diff --git a/ghost/staff-service/lib/email-templates/new-paid-started.hbs b/ghost/staff-service/lib/email-templates/new-paid-started.hbs index d3b7674207..4c09b96c8c 100644 --- a/ghost/staff-service/lib/email-templates/new-paid-started.hbs +++ b/ghost/staff-service/lib/email-templates/new-paid-started.hbs @@ -26,77 +26,52 @@
- +
+ + + + +
+

Name:

+ +

Tier:

+ + {{#if subscriptionData.cancelNow}} +

Expired on:

+ {{else}} +

Expires on:

+ {{/if}} + + {{#if subscriptionData.cancellationReason}} +

"{{subscriptionData.cancellationReason}}"

+ {{/if}} +
+ - +
View member + + + + + + +
View member
+
@@ -102,21 +80,29 @@
-
-

You can also copy & paste this URL into your browser:

- + + + + + + + +
+

Or copy and paste this URL into your browser:

+ +
-

This message was sent from {{siteDomain}} to {{toEmail}}

+
+

This message was sent from {{siteDomain}} to {{toEmail}}

-

Don’t want to receive these emails? Manage your preferences here.

+
+

Don’t want to receive these emails? Manage your preferences here.

+ {{#if siteIconUrl}} + + + + {{/if}}
{{siteTitle}}
-

Congratulations!

-

You have a new paid member.

- - - - - - - -
- - - - - -
-
- {{memberData.initials}} -
-
-

{{memberData.name}}

- {{#if memberData.showEmail}} - - {{/if}} -

Subscription started on {{subscriptionData.startedOn}}

-
-
- - - - - -
- -

Tier

-
-

{{tierData.name}} - {{#if tierData.details}} - {{tierData.details}}{{/if}} -

- - {{#if offerData}} -

Offer

-
-

{{offerData.name}} - {{offerData.details}}

- {{/if}} - - {{#if referrerSource}} -

Signup info

-
-

Source - - {{referrerSource}} -

- {{#if attributionTitle}} -

Page - - {{attributionTitle}} -

- {{/if}} - {{/if}} - -
- - +

You have a new paid subscriber

+
- - - diff --git a/ghost/staff-service/test/staff-service.test.js b/ghost/staff-service/test/staff-service.test.js index 1de3d71693..7570986cd2 100644 --- a/ghost/staff-service/test/staff-service.test.js +++ b/ghost/staff-service/test/staff-service.test.js @@ -78,10 +78,6 @@ function testCommonPaidSubMailData({member, mailStub, getEmailAlertUsersStub}) { mailStub.calledWith( sinon.match.has('html', sinon.match('$50.00/month')) ).should.be.true(); - - mailStub.calledWith( - sinon.match.has('html', sinon.match('Subscription started on 1 Aug 2022')) - ).should.be.true(); } function testCommonPaidSubCancelMailData({mailStub, getEmailAlertUsersStub}) { @@ -518,9 +514,7 @@ describe('StaffService', function () { member = { name: 'Ghost', email: 'member@example.com', - id: 'abc', - geolocation: '{"country": "France"}', - created_at: '2022-08-01T07:30:39.882Z' + id: 'abc' }; offer = { name: 'Half price', @@ -586,9 +580,7 @@ describe('StaffService', function () { it('sends paid subscription start alert without member name', async function () { let memberData = { email: 'member@example.com', - id: 'abc', - geolocation: '{"country": "France"}', - created_at: '2022-08-01T07:30:39.882Z' + id: 'abc' }; await service.emails.notifyPaidSubscriptionStarted({member: memberData, offer: null, tier, subscription}, options); @@ -740,13 +732,9 @@ describe('StaffService', function () { mailStub.calledOnce.should.be.true(); testCommonPaidSubCancelMailData(stubs); - mailStub.calledWith( - sinon.match.has('html', sinon.match('Canceled on 5 Aug 2022')) - ).should.be.true(); - // Expiration sentence is in the future tense mailStub.calledWith( - sinon.match.has('html', sinon.match('Subscription will expire on')) + sinon.match.has('html', sinon.match('Expires on')) ).should.be.true(); mailStub.calledWith( @@ -760,24 +748,17 @@ describe('StaffService', function () { mailStub.calledWith( sinon.match.has('html', sinon.match('Reason: Changed my mind!')) ).should.be.true(); - mailStub.calledWith( - sinon.match.has('html', sinon.match('Cancellation reason')) - ).should.be.true(); }); it('sends paid subscription cancel alert when sub is canceled without reason', async function () { - await service.emails.notifyPaidSubscriptionCanceled({member, tier, subscription, expiryAt, canceledAt, cancelNow}, options); + await service.emails.notifyPaidSubscriptionCanceled({member, tier, subscription, expiryAt, cancelNow}, options); mailStub.calledOnce.should.be.true(); testCommonPaidSubCancelMailData(stubs); - mailStub.calledWith( - sinon.match.has('html', sinon.match('Canceled on 5 Aug 2022')) - ).should.be.true(); - // Expiration sentence is in the future tense mailStub.calledWith( - sinon.match.has('html', sinon.match('Subscription will expire on')) + sinon.match.has('html', sinon.match('Expires on')) ).should.be.true(); mailStub.calledWith( @@ -788,9 +769,6 @@ describe('StaffService', function () { mailStub.calledWith( sinon.match.has('html', sinon.match('Reason: ')) ).should.be.false(); - mailStub.calledWith( - sinon.match.has('html', sinon.match('Cancellation reason')) - ).should.be.false(); }); it('sends paid subscription cancel alert when subscription is canceled immediately', async function () { @@ -810,7 +788,7 @@ describe('StaffService', function () { // Expiration sentence is in the past tense mailStub.calledWith( - sinon.match.has('html', sinon.match('Subscription expired on')) + sinon.match.has('html', sinon.match('Expired on')) ).should.be.true(); mailStub.calledWith( @@ -821,10 +799,6 @@ describe('StaffService', function () { sinon.match.has('html', 'Offer') ).should.be.false(); - mailStub.calledWith( - sinon.match.has('html', sinon.match('Cancellation reason')) - ).should.be.true(); - mailStub.calledWith( sinon.match.has('html', sinon.match('Reason: Payment failed')) ).should.be.true(); diff --git a/yarn.lock b/yarn.lock index c3ccd86115..f3a5ec3644 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7408,21 +7408,6 @@ lz-string "^1.5.0" pretty-format "^27.0.2" -"@testing-library/jest-dom@5.16.5": - version "5.16.5" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" - integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA== - dependencies: - "@adobe/css-tools" "^4.0.1" - "@babel/runtime" "^7.9.2" - "@types/testing-library__jest-dom" "^5.9.1" - aria-query "^5.0.0" - chalk "^3.0.0" - css.escape "^1.5.1" - dom-accessibility-api "^0.5.6" - lodash "^4.17.15" - redent "^3.0.0" - "@testing-library/jest-dom@5.17.0": version "5.17.0" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz#5e97c8f9a15ccf4656da00fecab505728de81e0c" @@ -7455,85 +7440,80 @@ "@testing-library/dom" "^8.0.0" "@types/react-dom" "<18.0.0" -"@testing-library/react@14.1.0": - version "14.1.0" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.1.0.tgz#01d64915111db99b50f8361d51d7217606805989" - integrity sha512-hcvfZEEyO0xQoZeHmUbuMs7APJCGELpilL7bY+BaJaMP57aWc6q1etFwScnoZDheYjk4ESdlzPdQ33IbsKAK/A== +"@testing-library/react@14.3.1": + version "14.3.1" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.3.1.tgz#29513fc3770d6fb75245c4e1245c470e4ffdd830" + integrity sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ== dependencies: "@babel/runtime" "^7.12.5" "@testing-library/dom" "^9.0.0" "@types/react-dom" "^18.0.0" -"@testing-library/user-event@14.4.3": - version "14.4.3" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.4.3.tgz#af975e367743fa91989cd666666aec31a8f50591" - integrity sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q== +"@testing-library/user-event@14.5.2", "@testing-library/user-event@^14.4.0": + version "14.5.2" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd" + integrity sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ== -"@testing-library/user-event@^14.4.0": - version "14.5.1" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.1.tgz#27337d72046d5236b32fd977edee3f74c71d332f" - integrity sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg== +"@tiptap/core@2.5.9": + version "2.5.9" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.5.9.tgz#1deb0b7c748e24ec32613263e0af8d55a3b3c2ca" + integrity sha512-PPUR+0tbr+wX2G8RG4FEps4qhbnAPEeXK1FUtirLXSRh8vm+TDgafu3sms7wBc4fAyw9zTO/KNNZ90GBe04guA== -"@tiptap/core@2.5.8": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.5.8.tgz#58de366b0d2acb0a6e67a4780de64d619ebd90fa" - integrity sha512-lkWCKyoAoMTxM137MoEsorG7tZ5MZU6O3wMRuZ0P9fcTRY5vd1NWncWuPzuGSJIpL20gwBQOsS6PaQSfR3xjlA== +"@tiptap/extension-blockquote@2.5.9": + version "2.5.9" + resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.5.9.tgz#d873a8496fcf572c69aaac2a7a341e035fdbae22" + integrity sha512-LhGyigmd/v1OjYPeoVK8UvFHbH6ffh175ZuNvseZY4PsBd7kZhrSUiuMG8xYdNX8FxamsxAzr2YpsYnOzu3W7A== -"@tiptap/extension-blockquote@2.5.8": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.5.8.tgz#95880f0b687790dbff85a1c9e83f2afd0011be67" - integrity sha512-P8vDiagtRrUfIewfCKrJe0ddDSjPgOTKzqoM1UXKS+MenT8C/wT4bjiwopAoWP6zMoV0TfHWXah9emllmCfXFA== - -"@tiptap/extension-bubble-menu@^2.5.8": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.5.8.tgz#e39b176c574b9fd2f59c6457724f3f22a22fb1b8" - integrity sha512-COmd1Azudu7i281emZFIESECe7FnvWiRoBoQBVjjWSyq5PVzwJaA3PAlnU7GyNZKtVXMZ4xbrckdyNQfDeVQDA== +"@tiptap/extension-bubble-menu@^2.5.9": + version "2.5.9" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.5.9.tgz#d600bbcaa1d98a99f32b3b8b8c3d35752161200c" + integrity sha512-NddZ8Qn5dgPPa1W4yk0jdhF4tDBh0FwzBpbnDu2Xz/0TUHrA36ugB2CvR5xS1we4zUKckgpVqOqgdelrmqqFVg== dependencies: tippy.js "^6.3.7" -"@tiptap/extension-document@2.5.8": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.5.8.tgz#644f042f1d4a8d3f74af057477cc627da7b54dc7" - integrity sha512-r3rP4ihCJAdp3VRIeqd80etHx7jttzZaKNFX8hkQShHK6eTHwrR92VL0jDE4K+NOE3bxjMsOlYizJYWV042BtA== +"@tiptap/extension-document@2.5.9": + version "2.5.9" + resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.5.9.tgz#13a22b2d3bdc1463844872b1f1c926633df431a8" + integrity sha512-VdNZYDyCzC3W430UdeRXR9IZzPeODSbi5Xz/JEdV93THVp8AC9CrZR7/qjqdBTgbTB54VP8Yr6bKfCoIAF0BeQ== -"@tiptap/extension-floating-menu@^2.5.8": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.5.8.tgz#6af3fa169bf293ab79a671a7b60b5199992a9154" - integrity sha512-qsM6tCyRlXnI/gADrkO/2p0Tldu5aY96CnsXpZMaflMgsO577qhcXD0ReGg17uLXBzJa5xmV8qOik0Ptq3WEWg== +"@tiptap/extension-floating-menu@^2.5.9": + version "2.5.9" + resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.5.9.tgz#b970905f3c1af49a916dcbd477a4302086187974" + integrity sha512-MWJIQQT6e5MgqHny8neeH2Dx926nVPF7sv4p84nX4E0dnkRbEYUP8mCsWYhSUvxxIif6e+yY+4654f2Q9qTx1w== dependencies: tippy.js "^6.3.7" -"@tiptap/extension-hard-break@2.5.8": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.5.8.tgz#95288faad3408b91284d925c3e4dbab66029dd98" - integrity sha512-samZEL0EXzHSmMQ7KyLnfSxdDv3qSjia0JzelfCnFZS6LLcbwjrIjV8ZPxEhJ7UlZqroQdFxPegllkLHZj/MdQ== +"@tiptap/extension-hard-break@2.5.9": + version "2.5.9" + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.5.9.tgz#4f38f06dbeb5fb3e58ff7fc0c48b9db9c4ee4ecd" + integrity sha512-8hQ63SgZRG4BqHOeSfeaowG2eMr2beced018pOGbpHbE3XSYoISkMVuFz4Z8UEVR3W9dTbKo4wxNufSTducocQ== -"@tiptap/extension-link@2.5.8": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.5.8.tgz#f9264afed09bd25c37668303151ab80ba82ef044" - integrity sha512-qfeWR7sG2V7bn8z0f3HMyoR68pFlxYJmLs9cbW30diE9/zKClYEd3zTMPCgJ9yMSagCj4PWkqksIuktAhyRqOQ== +"@tiptap/extension-link@2.5.9": + version "2.5.9" + resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.5.9.tgz#6cb323d36b82700963ad2b9d189a7d07c81c7d6e" + integrity sha512-7v9yRsX7NuiY8DPslIsPIlFqcD8aGBMLqfEGXltJDvuG6kykdr+khEZeWcJ8ihHIL4yWR3/MAgeT2W72Z/nxiQ== dependencies: linkifyjs "^4.1.0" -"@tiptap/extension-paragraph@2.5.8": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.5.8.tgz#5be7e7c4e5c19bd4f512c72d3dfc4e1e6d6dd876" - integrity sha512-AMfD3lfGSiomfkSE2tUourUjVahLtIfWUQew13NTPuWoxAXaSyoCGO0ULkiou/lO3JVUUUmF9+KJrAHWGIARdA== +"@tiptap/extension-paragraph@2.5.9": + version "2.5.9" + resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.5.9.tgz#05210b6e7a9940b1acc09fdd4ec769fc6406da2b" + integrity sha512-HDXGiHTJ/V85dbDMjcFj4XfqyTQZqry6V21ucMzgBZYX60X3gIn7VpQTQnnRjvULSgtfOASSJP6BELc5TyiK0w== -"@tiptap/extension-placeholder@2.5.8": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.5.8.tgz#80fdf02133d94f41363f6fe28f5fc3ef09ac73c6" - integrity sha512-mvRl73OM5jBXVtDRLSTvp8/4+0mS2J2+ZcuiAHjABwEsZRCfJsiqty5NisOxSuy/AQtm8TK2kyt6ZCXQ2VRGig== +"@tiptap/extension-placeholder@2.5.9": + version "2.5.9" + resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.5.9.tgz#c9bebc7e2bba2b0321e360d8a7a358152ffc9137" + integrity sha512-ytKmlSiebtCBXoMPE2cup48DR0rQiekXhLKLkNyt7m8tSXkaRO4eDaFqCqPEXLeQXWdhwWEoPM6Cejaaa3ztkA== -"@tiptap/extension-text@2.5.8": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.5.8.tgz#a9c4de33eec749c8c01d8bd81fb589f581c30dfc" - integrity sha512-CNkD51jRMdcYCqFVOkrnebqBQ6pCD3ZD5z9kO5bOC5UPZKZBkLsWdlrHGAVwosxcGxdJACbqJ0Nj+fMgIw4tNA== +"@tiptap/extension-text@2.5.9": + version "2.5.9" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.5.9.tgz#a5bef0b9c5324511dbc2804a3a5ac8b9b5d5dc4c" + integrity sha512-W0pfiQUPsMkwaV5Y/wKW4cFsyXAIkyOFt7uN5u6LrZ/iW9KZ/IsDODPJDikWp0aeQnXzT9NNQULTpCjbHzzS6g== -"@tiptap/pm@2.5.8": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.5.8.tgz#b18afa77fdf69527b13614a05cfefc8b63e82224" - integrity sha512-CVhHaTG4QNHSkvuh6HHsUR4hE+nbUnk7z+VMUedaqPU8tNqkTwWGCMbiyTc+PCsz0T9Mni7vvBR+EXgEQ3+w4g== +"@tiptap/pm@2.5.9": + version "2.5.9" + resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.5.9.tgz#f97889210374993a1ce78e9ecb23461d0e4644bf" + integrity sha512-YSUaEQVtvZnGzGjif2Tl2o9utE+6tR2Djhz0EqFUcAUEVhOMk7UYUO+r/aPfcCRraIoKKuDQzyCpjKmJicjCUA== dependencies: prosemirror-changeset "^2.2.1" prosemirror-collab "^1.3.1" @@ -7554,13 +7534,13 @@ prosemirror-transform "^1.9.0" prosemirror-view "^1.33.9" -"@tiptap/react@2.5.8": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.5.8.tgz#d6bc68710f084fe0f02855376cf869f8ca2cf6fd" - integrity sha512-twUMm8HV7scUgR/E1hYS9N6JDtKPl7cgDiPjxTynNHc5S5f5Ecv4ns/BZRq3TMZ/JDrp4rghLvgq+ImQsLvPOA== +"@tiptap/react@2.5.9": + version "2.5.9" + resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.5.9.tgz#43f94a2bf1d4c55e82d97ef9d1c97ba20206f7f0" + integrity sha512-NZYAslIb79oxIOFHx9T9ey5oX0aJ1uRbtT2vvrvvyRaO6fKWgAwMYN92bOu5/f2oUVGUp6l7wkYZGdjz/XP5bA== dependencies: - "@tiptap/extension-bubble-menu" "^2.5.8" - "@tiptap/extension-floating-menu" "^2.5.8" + "@tiptap/extension-bubble-menu" "^2.5.9" + "@tiptap/extension-floating-menu" "^2.5.9" "@types/use-sync-external-store" "^0.0.6" use-sync-external-store "^1.2.2" @@ -7827,20 +7807,25 @@ "@tryghost/errors" "^1.3.5" jest-snapshot "^29.0.0" -"@tryghost/kg-card-factory@5.0.4": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@tryghost/kg-card-factory/-/kg-card-factory-5.0.4.tgz#b2de98eaf01edbd5629fb1f4b06eca3a5f95d0ad" - integrity sha512-KcNM4QJONSSOJeQlv9no5wFx+uV2mESX3bYBL2y3c0DqB26NlMaUx0QIAFSbCSinUlCvRFOwEEBQyaACtCOvzQ== +"@tryghost/kg-card-factory@5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@tryghost/kg-card-factory/-/kg-card-factory-5.0.5.tgz#2a7552b5f7ac3bbae99af9dca3da11b95fb4ae1b" + integrity sha512-fZumHdEkR6pzc2BLukMNOnSC/tru9SJ+UP5rEaECN9lpLnLGya6B0fU51y8+Jjn+xl1CDCzeWtvII50CSBNLOQ== "@tryghost/kg-clean-basic-html@4.1.1": version "4.1.1" resolved "https://registry.yarnpkg.com/@tryghost/kg-clean-basic-html/-/kg-clean-basic-html-4.1.1.tgz#132a019abc6b6b6a0948c7e2d3e3ce37d18983b7" integrity sha512-R654qIHRf//FP/1hHLkehTYxZz/Zp5NXomfEuQSezw4uDmOwGn1ME4yZD5TDi5+8ism71tfMeGVVI5XmLOeDLg== -"@tryghost/kg-converters@1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@tryghost/kg-converters/-/kg-converters-1.0.5.tgz#8deb6591b91d0c5e89c529bc39b1f9d0870bec37" - integrity sha512-TpKH0oAlA+yFrQk7d8N0DYlxxQ8bcmoc7Waf3F1tDuioBhi7sFOz88TVxKt5VkPu1/PKGR52c+xhEeDA7BU85A== +"@tryghost/kg-clean-basic-html@4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@tryghost/kg-clean-basic-html/-/kg-clean-basic-html-4.1.3.tgz#be0724ac222a76af1f6a6e2c7e1ea1d41f69c2d1" + integrity sha512-z2TLpPTMDR8onNGV177/B1BUdGIumwJ9Pd8i2GXfBbX8LtTnqswK465iR1CUVrKlwZFXFsqvj1ZFPnt51KWQKQ== + +"@tryghost/kg-converters@1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@tryghost/kg-converters/-/kg-converters-1.0.6.tgz#55353fa9241c4406671fbb82c3f06829d8b750fa" + integrity sha512-QI4o7hTvcs/6qyxY7wD2epLExYlRUCpqi3AEUfgvn35UsNYg4TuMJ6Kxr9Jwdex4DHFwJMEQ0ozrzjxnZEMoLA== dependencies: lodash "^4.17.21" @@ -7849,51 +7834,51 @@ resolved "https://registry.yarnpkg.com/@tryghost/kg-default-atoms/-/kg-default-atoms-5.0.3.tgz#7d0e5af2191f2e0c61ae11d1666905bf924f67ac" integrity sha512-uPE69rKxaiiMEa1vFsEbfX+LCBG2H5D/nqTkuIPAslEGIHYjQUruaXpBfnwtyVHn+dMLuWcO+wDJl5qET8gKEQ== -"@tryghost/kg-default-cards@10.0.6": - version "10.0.6" - resolved "https://registry.yarnpkg.com/@tryghost/kg-default-cards/-/kg-default-cards-10.0.6.tgz#17f10a711814196719c8fa2c6506a7d2ea9f3d7f" - integrity sha512-wOjaqFj8G9hFr6bI85CUAO55aD9B2NoLIVaeqZgzzU6Ix09ZhP4M4abrwQup7hp89PSrGz7noqmac9NTb/Lvxg== +"@tryghost/kg-default-cards@10.0.8": + version "10.0.8" + resolved "https://registry.yarnpkg.com/@tryghost/kg-default-cards/-/kg-default-cards-10.0.8.tgz#ed67a90c1b5fe045c3e2822be8e14f43ccc2ee3a" + integrity sha512-yLao7TDpDDDHjiTqbl+6wmISpx//iuXY00gS7JqNrZ8X7hZ6IvxUdXu1Hq9AZuyzZ6+nqvyqRYgvZ1VycSVtTQ== dependencies: - "@tryghost/kg-markdown-html-renderer" "7.0.5" + "@tryghost/kg-markdown-html-renderer" "7.0.7" "@tryghost/string" "0.2.12" "@tryghost/url-utils" "4.4.8" handlebars "^4.7.6" juice "^10.0.0" lodash "^4.17.21" - luxon "^3.0.0" + luxon "^3.5.0" -"@tryghost/kg-default-nodes@1.1.9": - version "1.1.9" - resolved "https://registry.yarnpkg.com/@tryghost/kg-default-nodes/-/kg-default-nodes-1.1.9.tgz#2f8e0851735ad9daf8ba103fe3d42d9119a366eb" - integrity sha512-xTIkOfusnTHub/pU/Pdw8S5rQ8GLf5ONfS1y8x5v2cqXln1e08lV8wa5gv44WSxuyDzKjVHKKceGkT3rbdVBXg== +"@tryghost/kg-default-nodes@1.1.11": + version "1.1.11" + resolved "https://registry.yarnpkg.com/@tryghost/kg-default-nodes/-/kg-default-nodes-1.1.11.tgz#8f4238868ba2a8c880a2a78b6416c1700c90b015" + integrity sha512-Xh462ufG3wKEVtk/5QLRFanG87/EbTUjeS6glBiMJ63s5aHgrpwUPrKv2H52fWuJt9meg0hmOl6HRrKnYQs88g== dependencies: "@lexical/clipboard" "0.13.1" "@lexical/rich-text" "0.13.1" "@lexical/selection" "0.13.1" "@lexical/utils" "0.13.1" - "@tryghost/kg-clean-basic-html" "4.1.1" - "@tryghost/kg-markdown-html-renderer" "7.0.5" + "@tryghost/kg-clean-basic-html" "4.1.3" + "@tryghost/kg-markdown-html-renderer" "7.0.7" html-minifier "^4.0.0" - jsdom "^24.0.0" + jsdom "^24.1.0" lexical "0.13.1" lodash "^4.17.21" - luxon "^3.3.0" + luxon "^3.5.0" -"@tryghost/kg-default-transforms@1.1.10": - version "1.1.10" - resolved "https://registry.yarnpkg.com/@tryghost/kg-default-transforms/-/kg-default-transforms-1.1.10.tgz#7f4f648e2a8eec8d4af43028111e766feb3975ea" - integrity sha512-T9OZau2npHwtxKw77hXqRWNEErxdM/WKldWtmLKGJiKT7Czx8v9eEpsNmGgJYnXenkyzehJmU1okJlAUqMbcsA== +"@tryghost/kg-default-transforms@1.1.12": + version "1.1.12" + resolved "https://registry.yarnpkg.com/@tryghost/kg-default-transforms/-/kg-default-transforms-1.1.12.tgz#49b84fb6c4df866f1bfc7aadf19015b14136f4de" + integrity sha512-vr12KCgo6amxoVYt65ra88v6c9lcK2Umyv01eGdoURWr1ipUhlbxOj8zEVaMHJd3Wsnp4TiCyvIag3Bb+WwqKQ== dependencies: "@lexical/list" "0.13.1" "@lexical/rich-text" "0.13.1" "@lexical/utils" "0.13.1" - "@tryghost/kg-default-nodes" "1.1.9" + "@tryghost/kg-default-nodes" "1.1.11" lexical "0.13.1" -"@tryghost/kg-html-to-lexical@1.1.10": - version "1.1.10" - resolved "https://registry.yarnpkg.com/@tryghost/kg-html-to-lexical/-/kg-html-to-lexical-1.1.10.tgz#89dcd98e3933485bb0f33ab725dac4080f5a01fe" - integrity sha512-ja0DRLEzQhhOzK4n7HQqUttr0dbDsMaueyXb6+bxovHyog3HFo3A5NYz0DX9UU3qdUhAKBLwrgY5SwrtJ/ysVw== +"@tryghost/kg-html-to-lexical@1.1.12": + version "1.1.12" + resolved "https://registry.yarnpkg.com/@tryghost/kg-html-to-lexical/-/kg-html-to-lexical-1.1.12.tgz#a62459127a20b852afbf72aef7a7a8c8bfe63ddb" + integrity sha512-TtHwzvkA9oMNLBI44C3YiD95oNtNItfH0t6UXkQeq7gkcqoMAv5/fqLaQtSYPs1dycgz0EnZD82nEP8rivP43g== dependencies: "@lexical/clipboard" "0.13.1" "@lexical/headless" "0.13.1" @@ -7901,15 +7886,15 @@ "@lexical/link" "0.13.1" "@lexical/list" "0.13.1" "@lexical/rich-text" "0.13.1" - "@tryghost/kg-default-nodes" "1.1.9" - "@tryghost/kg-default-transforms" "1.1.10" - jsdom "^24.0.0" + "@tryghost/kg-default-nodes" "1.1.11" + "@tryghost/kg-default-transforms" "1.1.12" + jsdom "^24.1.0" lexical "0.13.1" -"@tryghost/kg-lexical-html-renderer@1.1.12": - version "1.1.12" - resolved "https://registry.yarnpkg.com/@tryghost/kg-lexical-html-renderer/-/kg-lexical-html-renderer-1.1.12.tgz#3234331f18b0dfe65e52e8b5821ef2bbc4a7909a" - integrity sha512-AEV+A1ZxSSVjTse7YonMz8AF9pqppEpfAHnwKH6BarSTJAVL8Gbvv/zcISO03UVrUqfqCZnnmmXS2h8iOBrbSA== +"@tryghost/kg-lexical-html-renderer@1.1.14": + version "1.1.14" + resolved "https://registry.yarnpkg.com/@tryghost/kg-lexical-html-renderer/-/kg-lexical-html-renderer-1.1.14.tgz#84d7c8c2e0369206780233a58e41a94455077489" + integrity sha512-ud1/Gt13jF8wO7K0MYNCeJk8QCtPxtLBlBS2bmwkRWrcRBkPEHZre5UJ0esk7Ryiq/+rHxj6IS6xClEyq5mP9g== dependencies: "@lexical/clipboard" "0.13.1" "@lexical/code" "0.13.1" @@ -7917,17 +7902,17 @@ "@lexical/link" "0.13.1" "@lexical/list" "0.13.1" "@lexical/rich-text" "0.13.1" - "@tryghost/kg-default-nodes" "1.1.9" - "@tryghost/kg-default-transforms" "1.1.10" - jsdom "^24.0.0" + "@tryghost/kg-default-nodes" "1.1.11" + "@tryghost/kg-default-transforms" "1.1.12" + jsdom "^24.1.0" lexical "0.13.1" -"@tryghost/kg-markdown-html-renderer@7.0.5": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@tryghost/kg-markdown-html-renderer/-/kg-markdown-html-renderer-7.0.5.tgz#96fb7440291ab9188b1e32c7cc71f6aa5fdec48e" - integrity sha512-Y1f2tKenyfrYIOfOVTI+9/hIVAed2MN/96B5pRBXpn/Bv2/+Sq8RCAFqlLmNwX4o7XeNp51BHZK96Mu+sU/Ltg== +"@tryghost/kg-markdown-html-renderer@7.0.7": + version "7.0.7" + resolved "https://registry.yarnpkg.com/@tryghost/kg-markdown-html-renderer/-/kg-markdown-html-renderer-7.0.7.tgz#c0640f0165a853a8f371edb4a731a04c48ee938e" + integrity sha512-hTTEyMeYLhDCq7vXeCiM6SqcozWZEHjK3fZelW3m6E6FxwpZHGURMoZ30UZVpgIpEp/66fMWAT6fD69+tS3TNQ== dependencies: - "@tryghost/kg-utils" "1.0.26" + "@tryghost/kg-utils" "1.0.28" markdown-it "^14.0.0" markdown-it-footnote "^4.0.0" markdown-it-image-lazy-loading "^2.0.0" @@ -7935,14 +7920,14 @@ markdown-it-mark "^4.0.0" markdown-it-sub "^2.0.0" markdown-it-sup "^2.0.0" - semver "^7.3.5" + semver "^7.6.2" -"@tryghost/kg-mobiledoc-html-renderer@7.0.4": - version "7.0.4" - resolved "https://registry.yarnpkg.com/@tryghost/kg-mobiledoc-html-renderer/-/kg-mobiledoc-html-renderer-7.0.4.tgz#d5a433d9ebed76a1e74ff5792cab2d7f4b844e55" - integrity sha512-C0ncnXc5vsLPQmsEw4xfUmdJnJTL9WsoACpI6970R4/jvAWE3h99TVpSFWVnHwrc/asWxaXZlpcKKH4PJs3VzA== +"@tryghost/kg-mobiledoc-html-renderer@7.0.6": + version "7.0.6" + resolved "https://registry.yarnpkg.com/@tryghost/kg-mobiledoc-html-renderer/-/kg-mobiledoc-html-renderer-7.0.6.tgz#471bcc83546acb4b45dd6d6940709416f2fb28a8" + integrity sha512-jpHKpq7nda6fSO7/kEOb2dbY6Y1NOQ+w7l4snhtRV3rcW1epIZ07OhXwW36GV0hGTVFs3yxc6HMWlKlvIZS0uA== dependencies: - "@tryghost/kg-utils" "1.0.26" + "@tryghost/kg-utils" "1.0.28" mobiledoc-dom-renderer "^0.7.0" simple-dom "^1.4.0" @@ -7953,22 +7938,22 @@ dependencies: "@tryghost/kg-clean-basic-html" "4.1.1" -"@tryghost/kg-unsplash-selector@0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@tryghost/kg-unsplash-selector/-/kg-unsplash-selector-0.2.1.tgz#c9e1658ed8d9469a26ca78c1aec1848fd7d6007c" - integrity sha512-LzgKE7UJ24bvID0c94teXSCrfbAX2/jo8sKaxEKR9P4E4P8COlCme/aygdOUCTblCBgE4KxyO9a+bD3uYW2Qyg== +"@tryghost/kg-unsplash-selector@0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@tryghost/kg-unsplash-selector/-/kg-unsplash-selector-0.2.3.tgz#3b969e62e8410eb0fade25ee2f4944d4935e125b" + integrity sha512-FS4SWZ9hbPA+DvaIbXjLC/EPBqcrdZLWbj9kB96izNYaqyE3awyQNCDmBPcaOnnfqZ8BjVUlxQspxk8vLRH14A== -"@tryghost/kg-utils@1.0.26": - version "1.0.26" - resolved "https://registry.yarnpkg.com/@tryghost/kg-utils/-/kg-utils-1.0.26.tgz#48e18b05d8bcf2c0d3c94f3d500e9b267d6723e6" - integrity sha512-DXx/qJwMYB6mjyhBk1mOg2hA5sXGNVcP2R0FyhkmHKtdNEwP0WIpV7UZt7DIYDT4c4uWFddya+u6yVpqU9Nhxg== +"@tryghost/kg-utils@1.0.28": + version "1.0.28" + resolved "https://registry.yarnpkg.com/@tryghost/kg-utils/-/kg-utils-1.0.28.tgz#cfdaac6f5cbe6f4375f8771e5b4a014bfb53a689" + integrity sha512-OIl+V3j7u9/bWdnkZvnx/cDbRpmCIMI4f9uJbp0sC9dfAgGq83TFg1ZuHXK9QAB6nVCnY36eR2JVawlSYx04PA== dependencies: - semver "^7.3.5" + semver "^7.6.2" -"@tryghost/koenig-lexical@1.3.13": - version "1.3.13" - resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.3.13.tgz#7ffa158d1f28f4f75d0fce763c0a884f98a23f13" - integrity sha512-tjLouQMCPPAXdvBYWVtHp4SeDylT3tF5nh0cy3JPOmG0eJRYGdGwxiEabk6jmzOEjjhkD0kwT+6m3G+vv70dcw== +"@tryghost/koenig-lexical@1.3.15": + version "1.3.15" + resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.3.15.tgz#4d6b2296b810a8fc6cbc5c58db04fd23ff88a48b" + integrity sha512-ANXkZg/Zh+R4roH74veaprwWZJmMMiuxZgRCxzHgEj0PU/US6+aLXTeEMK8VgWiAaKpVjRNJDJsdI0RuRbwiHA== "@tryghost/limit-service@1.2.14": version "1.2.14" @@ -19697,10 +19682,10 @@ i18next-parser@8.13.0: vinyl-fs "^4.0.0" vue-template-compiler "^2.6.11" -i18next@23.12.2, i18next@^23.5.1: - version "23.12.2" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.12.2.tgz#c5b44bb95e4d4a5908a51577fa06c63dc2f650a4" - integrity sha512-XIeh5V+bi8SJSWGL3jqbTEBW5oD6rbP5L+E7dVQh1MNTxxYef0x15rhJVcRb7oiuq4jLtgy2SD8eFlf6P2cmqg== +i18next@23.12.3, i18next@^23.5.1: + version "23.12.3" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.12.3.tgz#e0b811ef218f6d3fdb0b91f1a1eed099a1f7c48b" + integrity sha512-DyigQmrR10V9U2N6pjhbfahW13GY7n8BQD9swN09JuRRropgsksWVi4vRLeex0Qf7zCPnBfIqQfhcBzdZBQBYw== dependencies: "@babel/runtime" "^7.23.2" @@ -21430,7 +21415,7 @@ jscodeshift@^0.15.1: temp "^0.8.4" write-file-atomic "^2.3.0" -jsdom@24.1.1, jsdom@^24.0.0, jsdom@~24.1.0: +jsdom@24.1.1, jsdom@^24.0.0, jsdom@^24.1.0, jsdom@~24.1.0: version "24.1.1" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-24.1.1.tgz#f41df8f4f3b2fbfa7e1bdc5df62c9804fd14a9d0" integrity sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ== @@ -22772,7 +22757,7 @@ ltgt@^2.1.2: resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5" integrity sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA== -luxon@3.5.0, luxon@^3.0.0, luxon@^3.3.0: +luxon@3.5.0, luxon@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.5.0.tgz#6b6f65c5cd1d61d1fd19dbf07ee87a50bf4b8e20" integrity sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ== @@ -28464,7 +28449,7 @@ semver@7.5.3: dependencies: lru-cache "^6.0.0" -semver@7.6.3, semver@^7.0.0, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4: +semver@7.6.3, semver@^7.0.0, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.2: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -30135,10 +30120,10 @@ terser-webpack-plugin@^5.3.10: serialize-javascript "^6.0.1" terser "^5.26.0" -terser@5.31.3, terser@^5.26.0, terser@^5.7.0: - version "5.31.3" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.3.tgz#b24b7beb46062f4653f049eea4f0cd165d0f0c38" - integrity sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA== +terser@5.31.5, terser@^5.26.0, terser@^5.7.0: + version "5.31.5" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.5.tgz#e48b7c65f32d2808e7dad803e4586a0bc3829b87" + integrity sha512-YPmas0L0rE1UyLL/llTWA0SiDOqIcAQYLeUj7cJYzXHlRTAnMSg9pPe4VJ5PlKvTrPQsdVFuiRiwyeNlYgwh2Q== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2"
- +
+ + + + +
+

Name:

+ +

Tier:

+ + {{#if offerData}} +

Offer:

+ + {{/if}} + {{#if referrerSource}} +

Source:

+

{{referrerSource}}

+ {{#if attributionTitle}} +

Page:

+ + {{/if}} + {{/if}} +
+ - +
View member + + + + + + +
View member
+
@@ -104,21 +79,29 @@
-
-

You can also copy & paste this URL into your browser:

- + + + + + + + +
+

Or copy and paste this URL into your browser:

+ +
-

This message was sent from {{siteDomain}} to {{toEmail}}

+
+

This message was sent from {{siteDomain}} to {{toEmail}}

-

Don’t want to receive these emails? Manage your preferences here.

+
+

Don’t want to receive these emails? Manage your preferences here.