From c1df0c9d3daed654c5bbed6727e33a7654f4b4e5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 02:10:18 +0000 Subject: [PATCH 001/131] Update dependency @types/node to v20.14.8 --- ghost/ghost/package.json | 2 +- ghost/recommendations/package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ghost/ghost/package.json b/ghost/ghost/package.json index 8ff33c7c8c..e7b68660c3 100644 --- a/ghost/ghost/package.json +++ b/ghost/ghost/package.json @@ -21,7 +21,7 @@ ], "devDependencies": { "@nestjs/testing": "10.3.9", - "@types/node": "20.14.7", + "@types/node": "20.14.8", "@types/sinon": "^17.0.3", "@types/supertest": "^6.0.2", "c8": "8.0.1", diff --git a/ghost/recommendations/package.json b/ghost/recommendations/package.json index f268483991..530e773076 100644 --- a/ghost/recommendations/package.json +++ b/ghost/recommendations/package.json @@ -22,7 +22,7 @@ "build" ], "devDependencies": { - "@types/node": "20.14.7", + "@types/node": "20.14.8", "c8": "8.0.1", "mocha": "10.2.0", "sinon": "15.2.0", diff --git a/yarn.lock b/yarn.lock index 67f9f6029d..0e2d9ba872 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8678,10 +8678,10 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@20.14.7", "@types/node@>=10.0.0", "@types/node@>=13.7.0", "@types/node@>=8.1.0": - version "20.14.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.7.tgz#342cada27f97509eb8eb2dbc003edf21ce8ab5a8" - integrity sha512-uTr2m2IbJJucF3KUxgnGOZvYbN0QgkGyWxG6973HCpMYFy2KfcgYuIwkJQMQkt1VbBMlvWRbpshFTLxnxCZjKQ== +"@types/node@*", "@types/node@20.14.8", "@types/node@>=10.0.0", "@types/node@>=13.7.0", "@types/node@>=8.1.0": + version "20.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.8.tgz#45c26a2a5de26c3534a9504530ddb3b27ce031ac" + integrity sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA== dependencies: undici-types "~5.26.4" From d5013199b367ff83ba05b92ac9e937266b7ba785 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Mon, 24 Jun 2024 10:01:36 +0200 Subject: [PATCH 002/131] Fixed handling objects as API input parameters fix https://linear.app/tryghost/issue/SLO-155/paramsmap-is-not-a-function-an-unexpected-error-occurred-please-try - in the case you provide an object to the API, this code will throw an error because it can't map over an object - we can just assert that params should be an array and throw an error otherwise --- ghost/api-framework/lib/utils/options.js | 9 +++++++++ ghost/api-framework/test/util/options.test.js | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/ghost/api-framework/lib/utils/options.js b/ghost/api-framework/lib/utils/options.js index 22dd575925..90b5d6eaa4 100644 --- a/ghost/api-framework/lib/utils/options.js +++ b/ghost/api-framework/lib/utils/options.js @@ -1,4 +1,5 @@ const _ = require('lodash'); +const {IncorrectUsageError} = require('@tryghost/errors'); /** * @description Helper function to prepare params for internal usages. @@ -15,6 +16,14 @@ const trimAndLowerCase = (params) => { params = params.split(','); } + // If we don't have an array at this point, something is wrong, so we should throw an + // error to avoid trying to .map over something else + if (!_.isArray(params)) { + throw new IncorrectUsageError({ + message: 'Params must be a string or array' + }); + } + return params.map((item) => { return item.trim().toLowerCase(); }); diff --git a/ghost/api-framework/test/util/options.test.js b/ghost/api-framework/test/util/options.test.js index d9812a41ce..ad926f8991 100644 --- a/ghost/api-framework/test/util/options.test.js +++ b/ghost/api-framework/test/util/options.test.js @@ -20,4 +20,12 @@ describe('util/options', function () { it('accepts parameters in form of an array', function () { optionsUtil.trimAndLowerCase([' PeanUt', ' buTTer ']).should.eql(['peanut', 'butter']); }); + + it('throws error for invalid object input', function () { + try { + optionsUtil.trimAndLowerCase({name: 'peanut'}); + } catch (err) { + err.message.should.eql('Params must be a string or array'); + } + }); }); From e0057fd30fa4f21caf5d3b95efc5fe817ed1f642 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:40:40 +0000 Subject: [PATCH 003/131] Update storybook monorepo to v7.6.20 --- apps/admin-x-design-system/package.json | 12 +- apps/signup-form/package.json | 12 +- yarn.lock | 503 ++++++++++++------------ 3 files changed, 263 insertions(+), 264 deletions(-) diff --git a/apps/admin-x-design-system/package.json b/apps/admin-x-design-system/package.json index 46c6c2fe51..5838f21487 100644 --- a/apps/admin-x-design-system/package.json +++ b/apps/admin-x-design-system/package.json @@ -27,12 +27,12 @@ ], "devDependencies": { "@codemirror/lang-html": "6.4.9", - "@storybook/addon-essentials": "7.6.19", - "@storybook/addon-interactions": "7.6.19", - "@storybook/addon-links": "7.6.19", + "@storybook/addon-essentials": "7.6.20", + "@storybook/addon-interactions": "7.6.20", + "@storybook/addon-links": "7.6.20", "@storybook/addon-styling": "1.3.7", - "@storybook/blocks": "7.6.19", - "@storybook/react": "7.6.19", + "@storybook/blocks": "7.6.20", + "@storybook/react": "7.6.20", "@storybook/react-vite": "7.6.4", "@storybook/testing-library": "0.2.2", "@testing-library/react": "14.1.0", @@ -47,7 +47,7 @@ "react-dom": "18.3.1", "rollup-plugin-node-builtins": "2.1.2", "sinon": "17.0.0", - "storybook": "7.6.19", + "storybook": "7.6.20", "ts-node": "10.9.2", "typescript": "5.4.5", "vite": "4.5.3", diff --git a/apps/signup-form/package.json b/apps/signup-form/package.json index 613d9ffa54..80ad04e248 100644 --- a/apps/signup-form/package.json +++ b/apps/signup-form/package.json @@ -40,12 +40,12 @@ }, "devDependencies": { "@playwright/test": "1.38.1", - "@storybook/addon-essentials": "7.6.19", - "@storybook/addon-interactions": "7.6.19", - "@storybook/addon-links": "7.6.19", + "@storybook/addon-essentials": "7.6.20", + "@storybook/addon-interactions": "7.6.20", + "@storybook/addon-links": "7.6.20", "@storybook/addon-styling": "1.3.7", - "@storybook/blocks": "7.6.19", - "@storybook/react": "7.6.19", + "@storybook/blocks": "7.6.20", + "@storybook/react": "7.6.20", "@storybook/react-vite": "7.6.4", "@storybook/testing-library": "0.2.2", "@tailwindcss/line-clamp": "0.4.4", @@ -63,7 +63,7 @@ "postcss-import": "16.1.0", "prop-types": "15.8.1", "rollup-plugin-node-builtins": "2.1.2", - "storybook": "7.6.19", + "storybook": "7.6.20", "stylelint": "15.10.3", "tailwindcss": "3.4.4", "vite": "4.5.3", diff --git a/yarn.lock b/yarn.lock index 0e2d9ba872..a8f5cf5636 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6338,120 +6338,120 @@ "@stdlib/utils-constructor-name" "^0.0.x" "@stdlib/utils-global" "^0.0.x" -"@storybook/addon-actions@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-7.6.19.tgz#f131faf51c2baf036aa0fff33d77a1fa74e22ad0" - integrity sha512-ATLrA5QKFJt7tIAScRHz5T3eBQ+RG3jaZk08L7gChvyQZhei8knWwePElZ7GaWbCr9BgznQp1lQUUXq/UUblAQ== +"@storybook/addon-actions@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-7.6.20.tgz#4264d1fba6e889f28f717ebb23c55b7d774a2f60" + integrity sha512-c/GkEQ2U9BC/Ew/IMdh+zvsh4N6y6n7Zsn2GIhJgcu9YEAa5aF2a9/pNgEGBMOABH959XE8DAOMERw/5qiLR8g== dependencies: - "@storybook/core-events" "7.6.19" + "@storybook/core-events" "7.6.20" "@storybook/global" "^5.0.0" "@types/uuid" "^9.0.1" dequal "^2.0.2" polished "^4.2.2" uuid "^9.0.0" -"@storybook/addon-backgrounds@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-7.6.19.tgz#cd4cf6a6415c32a0e813573553efa592400c6c2f" - integrity sha512-Nu3LAZODRSV2e5bOroKm/Jp6BIFzwu/nJxD5OvLWkkwNCh+vDXUFbbaVrZf5xRL+fHd9iLFPtWbJQpF/w7UsCw== +"@storybook/addon-backgrounds@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-7.6.20.tgz#a84758c07b236181f2d67966a7c159d0b3bc1abb" + integrity sha512-a7ukoaXT42vpKsMxkseIeO3GqL0Zst2IxpCTq5dSlXiADrcemSF/8/oNpNW9C4L6F1Zdt+WDtECXslEm017FvQ== dependencies: "@storybook/global" "^5.0.0" memoizerific "^1.11.3" ts-dedent "^2.0.0" -"@storybook/addon-controls@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-7.6.19.tgz#86c0433b3d5e2e74f6ca3e58e492730d27f0220f" - integrity sha512-cl6PCNEwihDjuWIUsKTyDNKk+/IE4J3oMbSY5AZV/9Z0jJbpMV2shVm5DMZm5LhCCVcu5obWcxCIa4FMIMJAMQ== +"@storybook/addon-controls@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-7.6.20.tgz#5487064259a71f10b0aab04a4b7745ecf948e4cc" + integrity sha512-06ZT5Ce1sZW52B0s6XuokwjkKO9GqHlTUHvuflvd8wifxKlCmRvNUxjBvwh+ccGJ49ZS73LbMSLFgtmBEkCxbg== dependencies: - "@storybook/blocks" "7.6.19" + "@storybook/blocks" "7.6.20" lodash "^4.17.21" ts-dedent "^2.0.0" -"@storybook/addon-docs@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-7.6.19.tgz#4f0866072a5105e667ed75bd388584bb46206884" - integrity sha512-nv+9SR/NOtM8Od2esOXHcg0NQT8Pk8BMUyGwZu5Q3MLI4JxNVEG65dY0IP2j6Knc4UtlvQTpM0f7m5xp4seHjQ== +"@storybook/addon-docs@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-7.6.20.tgz#0bff85bdbdca58c9535384a4ded69dadb2fe7e4e" + integrity sha512-XNfYRhbxH5JP7B9Lh4W06PtMefNXkfpV39Gaoih5HuqngV3eoSL4RikZYOMkvxRGQ738xc6axySU3+JKcP1OZg== dependencies: "@jest/transform" "^29.3.1" "@mdx-js/react" "^2.1.5" - "@storybook/blocks" "7.6.19" - "@storybook/client-logger" "7.6.19" - "@storybook/components" "7.6.19" - "@storybook/csf-plugin" "7.6.19" - "@storybook/csf-tools" "7.6.19" + "@storybook/blocks" "7.6.20" + "@storybook/client-logger" "7.6.20" + "@storybook/components" "7.6.20" + "@storybook/csf-plugin" "7.6.20" + "@storybook/csf-tools" "7.6.20" "@storybook/global" "^5.0.0" "@storybook/mdx2-csf" "^1.0.0" - "@storybook/node-logger" "7.6.19" - "@storybook/postinstall" "7.6.19" - "@storybook/preview-api" "7.6.19" - "@storybook/react-dom-shim" "7.6.19" - "@storybook/theming" "7.6.19" - "@storybook/types" "7.6.19" + "@storybook/node-logger" "7.6.20" + "@storybook/postinstall" "7.6.20" + "@storybook/preview-api" "7.6.20" + "@storybook/react-dom-shim" "7.6.20" + "@storybook/theming" "7.6.20" + "@storybook/types" "7.6.20" fs-extra "^11.1.0" remark-external-links "^8.0.0" remark-slug "^6.0.0" ts-dedent "^2.0.0" -"@storybook/addon-essentials@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-7.6.19.tgz#1f2a7af9388e1f50b3cfd2fd9ee5fd763a1ac35a" - integrity sha512-SC33ZEQ5YaOt9wDkrdZmwQgqPWo9om/gqnyif06eug3SwrTe9JjO5iq1PIBfQodLD9MAxr9cwBvO0NG505oszQ== +"@storybook/addon-essentials@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-7.6.20.tgz#149c22b51a7abd8977acaaf2e1941c5b5dcb2fd5" + integrity sha512-hCupSOiJDeOxJKZSgH0x5Mb2Xqii6mps21g5hpxac1XjhQtmGflShxi/xOHhK3sNqrbgTSbScfpUP3hUlZO/2Q== dependencies: - "@storybook/addon-actions" "7.6.19" - "@storybook/addon-backgrounds" "7.6.19" - "@storybook/addon-controls" "7.6.19" - "@storybook/addon-docs" "7.6.19" - "@storybook/addon-highlight" "7.6.19" - "@storybook/addon-measure" "7.6.19" - "@storybook/addon-outline" "7.6.19" - "@storybook/addon-toolbars" "7.6.19" - "@storybook/addon-viewport" "7.6.19" - "@storybook/core-common" "7.6.19" - "@storybook/manager-api" "7.6.19" - "@storybook/node-logger" "7.6.19" - "@storybook/preview-api" "7.6.19" + "@storybook/addon-actions" "7.6.20" + "@storybook/addon-backgrounds" "7.6.20" + "@storybook/addon-controls" "7.6.20" + "@storybook/addon-docs" "7.6.20" + "@storybook/addon-highlight" "7.6.20" + "@storybook/addon-measure" "7.6.20" + "@storybook/addon-outline" "7.6.20" + "@storybook/addon-toolbars" "7.6.20" + "@storybook/addon-viewport" "7.6.20" + "@storybook/core-common" "7.6.20" + "@storybook/manager-api" "7.6.20" + "@storybook/node-logger" "7.6.20" + "@storybook/preview-api" "7.6.20" ts-dedent "^2.0.0" -"@storybook/addon-highlight@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/addon-highlight/-/addon-highlight-7.6.19.tgz#7e88fe924e822426ef54c33ad1522a9676ef57aa" - integrity sha512-/pApl0oiVU1CQ8xETRNDLDthMBjeTmvFnTRq8RJ9m0JYTrSsoyHDmj9zS4K1k9gReqijE7brslhP8d2tblBpNw== +"@storybook/addon-highlight@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/addon-highlight/-/addon-highlight-7.6.20.tgz#d118e4cce549238d866bbbe4d49b9509afda01a7" + integrity sha512-7/x7xFdFyqCki5Dm3uBePldUs9l98/WxJ7rTHQuYqlX7kASwyN5iXPzuhmMRUhlMm/6G6xXtLabIpzwf1sFurA== dependencies: "@storybook/global" "^5.0.0" -"@storybook/addon-interactions@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/addon-interactions/-/addon-interactions-7.6.19.tgz#b04bd3281724e276a9d549e314121fd2d32e5286" - integrity sha512-lMQDu6JT2LXDWcRnIGvrKRk/W+67zOtUNpDKwoVuvM5eHVJcza5SPV6v8yXDLCHLOt7RZ15h6LT2uXabfKpcww== +"@storybook/addon-interactions@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/addon-interactions/-/addon-interactions-7.6.20.tgz#9471575cb3699d12a7011299ce6bca1cb8aea252" + integrity sha512-uH+OIxLtvfnnmdN3Uf8MwzfEFYtaqSA6Hir6QNPc643se0RymM8mULN0rzRyvspwd6OagWdtOxsws3aHk02KTA== dependencies: "@storybook/global" "^5.0.0" - "@storybook/types" "7.6.19" + "@storybook/types" "7.6.20" jest-mock "^27.0.6" polished "^4.2.2" ts-dedent "^2.2.0" -"@storybook/addon-links@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-7.6.19.tgz#7b48683533ada47268a60393fb1930415e67113f" - integrity sha512-qMIFfcsMf4olxhYUHUV2ZJhxphh6Xpf1DMd0lxKqAibfxl/sX1m0rJkyiqWSBxbCmAy/pwdgqEOJ1lpDUsJ33w== +"@storybook/addon-links@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-7.6.20.tgz#c6bedc7bdc0112ce4cb3f1bfc701445df696598d" + integrity sha512-iomSnBD90CA4MinesYiJkFX2kb3P1Psd/a1Y0ghlFEsHD4uMId9iT6sx2s16DYMja0SlPkrbWYnGukqaCjZpRw== dependencies: "@storybook/csf" "^0.1.2" "@storybook/global" "^5.0.0" ts-dedent "^2.0.0" -"@storybook/addon-measure@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-7.6.19.tgz#4f12580eff40038a8d514be1059b2d063e5d0ed4" - integrity sha512-n+cfhVXXouBv9oQr3a77vvip5dTznaNoBDWMafP2ohauc8jBlAxeBwCjk5r3pyThMRIFCTG/ypZrhiJcSJT3bw== +"@storybook/addon-measure@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-7.6.20.tgz#c764009ce3e980b5b67e462ad0de5986c38cdfab" + integrity sha512-i2Iq08bGfI7gZbG6Lb8uF/L287tnaGUR+2KFEmdBjH6+kgjWLiwfpanoPQpy4drm23ar0gUjX+L3Ri03VI5/Xg== dependencies: "@storybook/global" "^5.0.0" tiny-invariant "^1.3.1" -"@storybook/addon-outline@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/addon-outline/-/addon-outline-7.6.19.tgz#519cdba12d3f27f1282897d2abe8762a5640b45f" - integrity sha512-Tt4MrfjK5j/Mdh8nJ8ccVyh78Dy7aiEPxO31YVvr5XUkge0pDi1PX328mHRDPur0i56NM8ssVbekWBZr+9MxlA== +"@storybook/addon-outline@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/addon-outline/-/addon-outline-7.6.20.tgz#0ebe829b6d8d269f691a110f3b34884b1df8ee74" + integrity sha512-TdsIQZf/TcDsGoZ1XpO+9nBc4OKqcMIzY4SrI8Wj9dzyFLQ37s08gnZr9POci8AEv62NTUOVavsxcafllkzqDQ== dependencies: "@storybook/global" "^5.0.0" ts-dedent "^2.0.0" @@ -6480,15 +6480,15 @@ sass-loader "^13.2.2" style-loader "^3.3.2" -"@storybook/addon-toolbars@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-7.6.19.tgz#4800d5d8679334b4a3353bb7e4933af5a6545d4b" - integrity sha512-+qGbPP2Vo/HoPiS4EJopZ127HGculCV74Hkz6ot7ob6AkYdA1yLMPzWns/ZXNIWm6ab3jV+iq+mQCM/i1qJzvA== +"@storybook/addon-toolbars@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-7.6.20.tgz#c1cd31c6a8f98d3ec4853157134ca143d065d31a" + integrity sha512-5Btg4i8ffWTDHsU72cqxC8nIv9N3E3ObJAc6k0llrmPBG/ybh3jxmRfs8fNm44LlEXaZ5qrK/petsXX3UbpIFg== -"@storybook/addon-viewport@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-7.6.19.tgz#602aa2c0431f4b8119f9cf4e668eb3155ad5bcb7" - integrity sha512-OQQtJ2kYwImbvE9QiC3I3yR0O0EBgNjq+XSaSS4ixJrvUyesfuB7Lm7RkubhEEiP4yANi9OlbzsqZelmPOnk6w== +"@storybook/addon-viewport@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-7.6.20.tgz#882571f4b0f405e1cf2cfad9a1f74b30d22f9a93" + integrity sha512-i8mIw8BjLWAVHEQsOTE6UPuEGQvJDpsu1XZnOCkpfTfPMz73m+3td/PmLG7mMT2wPnLu9IZncKLCKTAZRbt/YQ== dependencies: memoizerific "^1.11.3" @@ -6500,22 +6500,22 @@ "@storybook/client-logger" "7.0.20" "@storybook/manager-api" "7.0.20" -"@storybook/blocks@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/blocks/-/blocks-7.6.19.tgz#11bd3126245be33df091b48df9f25c1c07920825" - integrity sha512-/c/bVQRmyRPoviJhPrFdLfubRcrnZWTwkjxsCvrOTJ/UDOyEl0t/H8yY1mGq7KWWTdbIznnZWhAIofHnH4/Esw== +"@storybook/blocks@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/blocks/-/blocks-7.6.20.tgz#1cc142f1c238616f0f3a9f900965c651e7ee7c52" + integrity sha512-xADKGEOJWkG0UD5jbY4mBXRlmj2C+CIupDL0/hpzvLvwobxBMFPKZIkcZIMvGvVnI/Ui+tJxQxLSuJ5QsPthUw== dependencies: - "@storybook/channels" "7.6.19" - "@storybook/client-logger" "7.6.19" - "@storybook/components" "7.6.19" - "@storybook/core-events" "7.6.19" + "@storybook/channels" "7.6.20" + "@storybook/client-logger" "7.6.20" + "@storybook/components" "7.6.20" + "@storybook/core-events" "7.6.20" "@storybook/csf" "^0.1.2" - "@storybook/docs-tools" "7.6.19" + "@storybook/docs-tools" "7.6.20" "@storybook/global" "^5.0.0" - "@storybook/manager-api" "7.6.19" - "@storybook/preview-api" "7.6.19" - "@storybook/theming" "7.6.19" - "@storybook/types" "7.6.19" + "@storybook/manager-api" "7.6.20" + "@storybook/preview-api" "7.6.20" + "@storybook/theming" "7.6.20" + "@storybook/types" "7.6.20" "@types/lodash" "^4.14.167" color-convert "^2.0.1" dequal "^2.0.2" @@ -6529,15 +6529,15 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/builder-manager@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/builder-manager/-/builder-manager-7.6.19.tgz#1356fab233181a06c8cea9094b53c84aac218bae" - integrity sha512-Dt5OLh97xeWh4h2mk9uG0SbCxBKHPhIiHLHAKEIDzIZBdwUhuyncVNDPHW2NlXM+S7U0/iKs2tw05waqh2lHvg== +"@storybook/builder-manager@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/builder-manager/-/builder-manager-7.6.20.tgz#d550a3f209012e4e383e61320ea756cddfdb416e" + integrity sha512-e2GzpjLaw6CM/XSmc4qJRzBF8GOoOyotyu3JrSPTYOt4RD8kjUsK4QlismQM1DQRu8i39aIexxmRbiJyD74xzQ== dependencies: "@fal-works/esbuild-plugin-global-externals" "^2.1.2" - "@storybook/core-common" "7.6.19" - "@storybook/manager" "7.6.19" - "@storybook/node-logger" "7.6.19" + "@storybook/core-common" "7.6.20" + "@storybook/manager" "7.6.20" + "@storybook/node-logger" "7.6.20" "@types/ejs" "^3.1.1" "@types/find-cache-dir" "^3.2.1" "@yarnpkg/esbuild-plugin-pnp" "^3.0.0-rc.10" @@ -6578,13 +6578,13 @@ resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-7.0.20.tgz#a681c3d463b4099b001dae9edeb3a607a8f4854a" integrity sha512-AL5GGSQ8WTDUoh3gitKEzo3fu7Vq5okXq2pAknAZlQA2Oio+HHO5nMeXvOfGdvo/tzbpNE3n5utmCJz006xrCA== -"@storybook/channels@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-7.6.19.tgz#730fa74f7800e2069707f8a880996ca6fc8957ab" - integrity sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA== +"@storybook/channels@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-7.6.20.tgz#33d8292b1b16d7f504bf751c57a792477d1c3a9e" + integrity sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A== dependencies: - "@storybook/client-logger" "7.6.19" - "@storybook/core-events" "7.6.19" + "@storybook/client-logger" "7.6.20" + "@storybook/core-events" "7.6.20" "@storybook/global" "^5.0.0" qs "^6.10.0" telejson "^7.2.0" @@ -6602,23 +6602,23 @@ telejson "^7.2.0" tiny-invariant "^1.3.1" -"@storybook/cli@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/cli/-/cli-7.6.19.tgz#b4f36ccd51e02ceadb92c3e2341f82ee1bbd6598" - integrity sha512-7OVy7nPgkLfgivv6/dmvoyU6pKl9EzWFk+g9izyQHiM/jS8jOiEyn6akG8Ebj6k5pWslo5lgiXUSW+cEEZUnqQ== +"@storybook/cli@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/cli/-/cli-7.6.20.tgz#498625db5f2447e8e1ad34827a7803c5940527f0" + integrity sha512-ZlP+BJyqg7HlnXf7ypjG2CKMI/KVOn03jFIiClItE/jQfgR6kRFgtjRU7uajh427HHfjv9DRiur8nBzuO7vapA== dependencies: "@babel/core" "^7.23.2" "@babel/preset-env" "^7.23.2" "@babel/types" "^7.23.0" "@ndelangen/get-tarball" "^3.0.7" - "@storybook/codemod" "7.6.19" - "@storybook/core-common" "7.6.19" - "@storybook/core-events" "7.6.19" - "@storybook/core-server" "7.6.19" - "@storybook/csf-tools" "7.6.19" - "@storybook/node-logger" "7.6.19" - "@storybook/telemetry" "7.6.19" - "@storybook/types" "7.6.19" + "@storybook/codemod" "7.6.20" + "@storybook/core-common" "7.6.20" + "@storybook/core-events" "7.6.20" + "@storybook/core-server" "7.6.20" + "@storybook/csf-tools" "7.6.20" + "@storybook/node-logger" "7.6.20" + "@storybook/telemetry" "7.6.20" + "@storybook/types" "7.6.20" "@types/semver" "^7.3.4" "@yarnpkg/fslib" "2.10.3" "@yarnpkg/libzip" "2.3.0" @@ -6655,10 +6655,10 @@ dependencies: "@storybook/global" "^5.0.0" -"@storybook/client-logger@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-7.6.19.tgz#a6f91af8cdc640ace9903674b6340ad8173238cc" - integrity sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ== +"@storybook/client-logger@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-7.6.20.tgz#1d6e93443091cccd50e269371aa786172d0c4659" + integrity sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ== dependencies: "@storybook/global" "^5.0.0" @@ -6669,18 +6669,18 @@ dependencies: "@storybook/global" "^5.0.0" -"@storybook/codemod@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/codemod/-/codemod-7.6.19.tgz#ac6690a5ef18903cdb132a005b3c520f96ae92f8" - integrity sha512-bmHE0iEEgWZ65dXCmasd+GreChjPiWkXu2FEa0cJmNz/PqY12GsXGls4ke1TkNTj4gdSZnbtJxbclPZZnib2tQ== +"@storybook/codemod@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/codemod/-/codemod-7.6.20.tgz#0aa7e0c1aacc605c7691b4b06baef0a9abefe114" + integrity sha512-8vmSsksO4XukNw0TmqylPmk7PxnfNfE21YsxFa7mnEBmEKQcZCQsNil4ZgWfG0IzdhTfhglAN4r++Ew0WE+PYA== dependencies: "@babel/core" "^7.23.2" "@babel/preset-env" "^7.23.2" "@babel/types" "^7.23.0" "@storybook/csf" "^0.1.2" - "@storybook/csf-tools" "7.6.19" - "@storybook/node-logger" "7.6.19" - "@storybook/types" "7.6.19" + "@storybook/csf-tools" "7.6.20" + "@storybook/node-logger" "7.6.20" + "@storybook/types" "7.6.20" "@types/cross-spawn" "^6.0.2" cross-spawn "^7.0.3" globby "^11.0.2" @@ -6689,29 +6689,29 @@ prettier "^2.8.0" recast "^0.23.1" -"@storybook/components@7.6.19", "@storybook/components@^7.0.12": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/components/-/components-7.6.19.tgz#c9e36100faef6310455daf5bf915a21447c332d1" - integrity sha512-8Zw/RQ4crzKkUR7ojxvRIj8vktKiBBO8Nq93qv4JfDqDWrcR7cro0hOlZgmZmrzbFunBBt6WlsNNO6nVP7R4Xw== +"@storybook/components@7.6.20", "@storybook/components@^7.0.12": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-7.6.20.tgz#09d044923142d2e087a1c4a43dec6731a42d2871" + integrity sha512-0d8u4m558R+W5V+rseF/+e9JnMciADLXTpsILrG+TBhwECk0MctIWW18bkqkujdCm8kDZr5U2iM/5kS1Noy7Ug== dependencies: "@radix-ui/react-select" "^1.2.2" "@radix-ui/react-toolbar" "^1.0.4" - "@storybook/client-logger" "7.6.19" + "@storybook/client-logger" "7.6.20" "@storybook/csf" "^0.1.2" "@storybook/global" "^5.0.0" - "@storybook/theming" "7.6.19" - "@storybook/types" "7.6.19" + "@storybook/theming" "7.6.20" + "@storybook/types" "7.6.20" memoizerific "^1.11.3" use-resize-observer "^9.1.0" util-deprecate "^1.0.2" -"@storybook/core-client@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-7.6.19.tgz#ca47b71bfebb0e1e5cb112b5b88e9f7b0c88eee4" - integrity sha512-F0V9nzcEnj6DIpnw2ilrxsV4d9ibyyQS+Wi2uQtXy+wCQQm9PeBVqrOywjXAY2F9pcoftXOaepfhp8jrxX4MXw== +"@storybook/core-client@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-7.6.20.tgz#831681d64194e4d604a859ed3eb452981f6824c5" + integrity sha512-upQuQQinLmlOPKcT8yqXNtwIucZ4E4qegYZXH5HXRWoLAL6GQtW7sUVSIuFogdki8OXRncr/dz8OA+5yQyYS4w== dependencies: - "@storybook/client-logger" "7.6.19" - "@storybook/preview-api" "7.6.19" + "@storybook/client-logger" "7.6.20" + "@storybook/preview-api" "7.6.20" "@storybook/core-client@7.6.4": version "7.6.4" @@ -6721,14 +6721,14 @@ "@storybook/client-logger" "7.6.4" "@storybook/preview-api" "7.6.4" -"@storybook/core-common@7.6.19", "@storybook/core-common@^7.0.12": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-7.6.19.tgz#fdbfc3c5b24d12e74ef9d1f0e15cd4b1ee2cd02c" - integrity sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q== +"@storybook/core-common@7.6.20", "@storybook/core-common@^7.0.12": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-7.6.20.tgz#3a2a3ae570bd13dc34726178c0eb36cf6a64e2a4" + integrity sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw== dependencies: - "@storybook/core-events" "7.6.19" - "@storybook/node-logger" "7.6.19" - "@storybook/types" "7.6.19" + "@storybook/core-events" "7.6.20" + "@storybook/node-logger" "7.6.20" + "@storybook/types" "7.6.20" "@types/find-cache-dir" "^3.2.1" "@types/node" "^18.0.0" "@types/node-fetch" "^2.6.4" @@ -6784,10 +6784,10 @@ resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-7.0.20.tgz#53878b736463c30141115ccdfba401bcf2645cba" integrity sha512-gUBQsbcDmRufmg8LdH7D57c/9BQ+cPi2vBcXdudmxeJFafGwDmLRu1mlv9rxlW4kicn/LZWJjKXtq4XXzF4OGg== -"@storybook/core-events@7.6.19", "@storybook/core-events@^7.0.12": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-7.6.19.tgz#cfa7d4581ad6cff1ee7eeade31d602a7d879d2b7" - integrity sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg== +"@storybook/core-events@7.6.20", "@storybook/core-events@^7.0.12": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-7.6.20.tgz#6648d661d1c96841a4c2a710a35759b01b6a06a1" + integrity sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ== dependencies: ts-dedent "^2.0.0" @@ -6798,26 +6798,26 @@ dependencies: ts-dedent "^2.0.0" -"@storybook/core-server@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-7.6.19.tgz#07f4f0d4b05dd7dc52e9c60be97df7b5cb3150a9" - integrity sha512-7mKL73Wv5R2bEl0kJ6QJ9bOu5YY53Idu24QgvTnUdNsQazp2yUONBNwHIrNDnNEXm8SfCi4Mc9o0mmNRMIoiRA== +"@storybook/core-server@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-7.6.20.tgz#fa143fbcad64fb7b0f0dc6d555d083c506a44ab4" + integrity sha512-qC5BdbqqwMLTdCwMKZ1Hbc3+3AaxHYWLiJaXL9e8s8nJw89xV8c8l30QpbJOGvcDmsgY6UTtXYaJ96OsTr7MrA== dependencies: "@aw-web-design/x-default-browser" "1.4.126" "@discoveryjs/json-ext" "^0.5.3" - "@storybook/builder-manager" "7.6.19" - "@storybook/channels" "7.6.19" - "@storybook/core-common" "7.6.19" - "@storybook/core-events" "7.6.19" + "@storybook/builder-manager" "7.6.20" + "@storybook/channels" "7.6.20" + "@storybook/core-common" "7.6.20" + "@storybook/core-events" "7.6.20" "@storybook/csf" "^0.1.2" - "@storybook/csf-tools" "7.6.19" + "@storybook/csf-tools" "7.6.20" "@storybook/docs-mdx" "^0.1.0" "@storybook/global" "^5.0.0" - "@storybook/manager" "7.6.19" - "@storybook/node-logger" "7.6.19" - "@storybook/preview-api" "7.6.19" - "@storybook/telemetry" "7.6.19" - "@storybook/types" "7.6.19" + "@storybook/manager" "7.6.20" + "@storybook/node-logger" "7.6.20" + "@storybook/preview-api" "7.6.20" + "@storybook/telemetry" "7.6.20" + "@storybook/types" "7.6.20" "@types/detect-port" "^1.3.0" "@types/node" "^18.0.0" "@types/pretty-hrtime" "^1.0.0" @@ -6830,7 +6830,6 @@ express "^4.17.3" fs-extra "^11.1.0" globby "^11.0.2" - ip "^2.0.1" lodash "^4.17.21" open "^8.4.0" pretty-hrtime "^1.0.3" @@ -6845,12 +6844,12 @@ watchpack "^2.2.0" ws "^8.2.3" -"@storybook/csf-plugin@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-7.6.19.tgz#5c88767f6b4f47826c9fc7fdf028132ff243239b" - integrity sha512-yUP0xfJyR8e6fmCgKoEt4c1EvslF8dZ8wtwVLE5hnC3kfs7xt8RVDiKLB/9NhYjY3mD/oOesX60HqRXDgJQHwA== +"@storybook/csf-plugin@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-7.6.20.tgz#0e79e58d5ed47dfb472b1dc202b0e754c21ec33b" + integrity sha512-dzBzq0dN+8WLDp6NxYS4G7BCe8+vDeDRBRjHmM0xb0uJ6xgQViL8SDplYVSGnk3bXE/1WmtvyRzQyTffBnaj9Q== dependencies: - "@storybook/csf-tools" "7.6.19" + "@storybook/csf-tools" "7.6.20" unplugin "^1.3.1" "@storybook/csf-plugin@7.6.4": @@ -6861,17 +6860,17 @@ "@storybook/csf-tools" "7.6.4" unplugin "^1.3.1" -"@storybook/csf-tools@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-7.6.19.tgz#5925f313b8ac4ebdd1c0b9d71279e18bbaab269a" - integrity sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ== +"@storybook/csf-tools@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-7.6.20.tgz#fdd9fa9459720a627e83e31d3839721dbc655f22" + integrity sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ== dependencies: "@babel/generator" "^7.23.0" "@babel/parser" "^7.23.0" "@babel/traverse" "^7.23.2" "@babel/types" "^7.23.0" "@storybook/csf" "^0.1.2" - "@storybook/types" "7.6.19" + "@storybook/types" "7.6.20" fs-extra "^11.1.0" recast "^0.23.1" ts-dedent "^2.0.0" @@ -6903,14 +6902,14 @@ resolved "https://registry.yarnpkg.com/@storybook/docs-mdx/-/docs-mdx-0.1.0.tgz#33ba0e39d1461caf048b57db354b2cc410705316" integrity sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg== -"@storybook/docs-tools@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/docs-tools/-/docs-tools-7.6.19.tgz#d63b68af1d425f4b94a3a229af611bbaf822f4b4" - integrity sha512-JuwV6wtm7Hb7Kb5ValChfxy4J7XngfrSQNpvwsDCSBNVcQUv2y843hvclpa26Ptfr/c7zpUX8r9FGSaMDy+2aQ== +"@storybook/docs-tools@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/docs-tools/-/docs-tools-7.6.20.tgz#2a6dd402c880e24ec6bec8411beee89cfe69f932" + integrity sha512-Bw2CcCKQ5xGLQgtexQsI1EGT6y5epoFzOINi0FSTGJ9Wm738nRp5LH3dLk1GZLlywIXcYwOEThb2pM+pZeRQxQ== dependencies: - "@storybook/core-common" "7.6.19" - "@storybook/preview-api" "7.6.19" - "@storybook/types" "7.6.19" + "@storybook/core-common" "7.6.20" + "@storybook/preview-api" "7.6.20" + "@storybook/types" "7.6.20" "@types/doctrine" "^0.0.3" assert "^2.1.0" doctrine "^3.0.0" @@ -6955,19 +6954,19 @@ telejson "^7.0.3" ts-dedent "^2.0.0" -"@storybook/manager-api@7.6.19", "@storybook/manager-api@^7.0.12": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/manager-api/-/manager-api-7.6.19.tgz#d08787dabe4143cf34d5805a023499889d572032" - integrity sha512-dVCx1Q+HZEA4U08XqYljiG88BeS3I3ahnPAQLZAeWQXQRkoc9G2jMgLNPKYPIqEtq7Xrn6SRlFMIofhwWrwZpg== +"@storybook/manager-api@7.6.20", "@storybook/manager-api@^7.0.12": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/manager-api/-/manager-api-7.6.20.tgz#225ff7dea3dbdb2e82bb5568babdaace4071c32e" + integrity sha512-gOB3m8hO3gBs9cBoN57T7jU0wNKDh+hi06gLcyd2awARQlAlywnLnr3s1WH5knih6Aq+OpvGBRVKkGLOkaouCQ== dependencies: - "@storybook/channels" "7.6.19" - "@storybook/client-logger" "7.6.19" - "@storybook/core-events" "7.6.19" + "@storybook/channels" "7.6.20" + "@storybook/client-logger" "7.6.20" + "@storybook/core-events" "7.6.20" "@storybook/csf" "^0.1.2" "@storybook/global" "^5.0.0" - "@storybook/router" "7.6.19" - "@storybook/theming" "7.6.19" - "@storybook/types" "7.6.19" + "@storybook/router" "7.6.20" + "@storybook/theming" "7.6.20" + "@storybook/types" "7.6.20" dequal "^2.0.2" lodash "^4.17.21" memoizerific "^1.11.3" @@ -6975,42 +6974,42 @@ telejson "^7.2.0" ts-dedent "^2.0.0" -"@storybook/manager@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/manager/-/manager-7.6.19.tgz#d7ee419e48feed79c1d9f0f469efa7742a2ef3d2" - integrity sha512-fZWQcf59x4P0iiBhrL74PZrqKJAPuk9sWjP8BIkGbf8wTZtUunbY5Sv4225fOL4NLJbuX9/RYLUPoxQ3nucGHA== +"@storybook/manager@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/manager/-/manager-7.6.20.tgz#eb619fe8d33446e581a7b1c3050644c196364d39" + integrity sha512-0Cf6WN0t7yEG2DR29tN5j+i7H/TH5EfPppg9h9/KiQSoFHk+6KLoy2p5do94acFU+Ro4+zzxvdCGbcYGKuArpg== "@storybook/mdx2-csf@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@storybook/mdx2-csf/-/mdx2-csf-1.1.0.tgz#97f6df04d0bf616991cc1005a073ac004a7281e5" integrity sha512-TXJJd5RAKakWx4BtpwvSNdgTDkKM6RkXU8GK34S/LhidQ5Pjz3wcnqb0TxEkfhK/ztbP8nKHqXFwLfa2CYkvQw== -"@storybook/node-logger@7.6.19", "@storybook/node-logger@^7.0.12": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-7.6.19.tgz#8a911288ee8052cf2c77cf5e2db2367b1b852b43" - integrity sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA== +"@storybook/node-logger@7.6.20", "@storybook/node-logger@^7.0.12": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-7.6.20.tgz#c0ca90cf68cf31d84cdcf53c76cec22769407ece" + integrity sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw== "@storybook/node-logger@7.6.4": version "7.6.4" resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-7.6.4.tgz#e471ee117bbd329522113dd98e5a42e83e259f9c" integrity sha512-GDkEnnDj4Op+PExs8ZY/P6ox3wg453CdEIaR8PR9TxF/H/T2fBL6puzma3hN2CMam6yzfAL8U+VeIIDLQ5BZdQ== -"@storybook/postinstall@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-7.6.19.tgz#8f75f43ea786a26a8677dedbedc5e8f947c56456" - integrity sha512-s6p1vpgMfn+QGDfCK2YNdyyWKidUgb3nGicB81FANRyzYqGB//QlJlghEc2LKCIQbGIZQiwP3l8PdZQmczEJRw== +"@storybook/postinstall@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-7.6.20.tgz#5a77ce7913375b11bd7c72388798854bd8507b91" + integrity sha512-AN4WPeNma2xC2/K/wP3I/GMbBUyeSGD3+86ZFFJFO1QmE/Zea6E+1aVlTd1iKHQUcNkZ9bZTrqkhPGVYx10pIw== -"@storybook/preview-api@7.6.19", "@storybook/preview-api@^7.0.12": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-7.6.19.tgz#5f4b36489a7662e4671030dc2580cc11a1dd854e" - integrity sha512-04hdMSQucroJT4dBjQzRd7ZwH2hij8yx2nm5qd4HYGkd1ORkvlH6GOLph4XewNJl5Um3xfzFQzBhvkqvG0WaCQ== +"@storybook/preview-api@7.6.20", "@storybook/preview-api@^7.0.12": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-7.6.20.tgz#688a435ee2cfe57eeb1e3053c18025a9e0a03bbb" + integrity sha512-3ic2m9LDZEPwZk02wIhNc3n3rNvbi7VDKn52hDXfAxnL5EYm7yDICAkaWcVaTfblru2zn0EDJt7ROpthscTW5w== dependencies: - "@storybook/channels" "7.6.19" - "@storybook/client-logger" "7.6.19" - "@storybook/core-events" "7.6.19" + "@storybook/channels" "7.6.20" + "@storybook/client-logger" "7.6.20" + "@storybook/core-events" "7.6.20" "@storybook/csf" "^0.1.2" "@storybook/global" "^5.0.0" - "@storybook/types" "7.6.19" + "@storybook/types" "7.6.20" "@types/qs" "^6.9.5" dequal "^2.0.2" lodash "^4.17.21" @@ -7045,10 +7044,10 @@ resolved "https://registry.yarnpkg.com/@storybook/preview/-/preview-7.6.4.tgz#36cca1d10fd3729f1a68789d95bfc400e33aa616" integrity sha512-p9xIvNkgXgTpSRphOMV9KpIiNdkymH61jBg3B0XyoF6IfM1S2/mQGvC89lCVz1dMGk2SrH4g87/WcOapkU5ArA== -"@storybook/react-dom-shim@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-7.6.19.tgz#c6472be7c53b8d9d3ed3007f5fc5d41daa76ac09" - integrity sha512-tpt2AC1428d1gF4fetMkpkeFZ1WdDr1CLKoLbSInWQZ7i96nbnIMIA9raR/W8ai1bo55KSz9Bq5ytC/1Pac2qQ== +"@storybook/react-dom-shim@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-7.6.20.tgz#20b902663474b731c22b211ec29c7fd0e86b4b7f" + integrity sha512-SRvPDr9VWcS24ByQOVmbfZ655y5LvjXRlsF1I6Pr9YZybLfYbu3L5IicfEHT4A8lMdghzgbPFVQaJez46DTrkg== "@storybook/react-dom-shim@7.6.4": version "7.6.4" @@ -7068,18 +7067,18 @@ magic-string "^0.30.0" react-docgen "^7.0.0" -"@storybook/react@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/react/-/react-7.6.19.tgz#a15b41fc98a80086b2f40d025f34f8641b3dea8b" - integrity sha512-uKShAAp1/pRki1YnRjBveH/jAD3f8V0W2WP1LxTQqnKVFkl01mTbDZ/9ZIK6rVTSILUlmsk3fwsNyRbOKVgBGQ== +"@storybook/react@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/react/-/react-7.6.20.tgz#e326969851b96e9a6bea6fdd81f624de052ddbb6" + integrity sha512-i5tKNgUbTNwlqBWGwPveDhh9ktlS0wGtd97A1ZgKZc3vckLizunlAFc7PRC1O/CMq5PTyxbuUb4RvRD2jWKwDA== dependencies: - "@storybook/client-logger" "7.6.19" - "@storybook/core-client" "7.6.19" - "@storybook/docs-tools" "7.6.19" + "@storybook/client-logger" "7.6.20" + "@storybook/core-client" "7.6.20" + "@storybook/docs-tools" "7.6.20" "@storybook/global" "^5.0.0" - "@storybook/preview-api" "7.6.19" - "@storybook/react-dom-shim" "7.6.19" - "@storybook/types" "7.6.19" + "@storybook/preview-api" "7.6.20" + "@storybook/react-dom-shim" "7.6.20" + "@storybook/types" "7.6.20" "@types/escodegen" "^0.0.6" "@types/estree" "^0.0.51" "@types/node" "^18.0.0" @@ -7131,23 +7130,23 @@ memoizerific "^1.11.3" qs "^6.10.0" -"@storybook/router@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/router/-/router-7.6.19.tgz#b9805344d35bb00c9139787f7c561603ffe0d3c2" - integrity sha512-q2/AvY8rG0znFEfbg50OIhkS5yQ6OmyzdCdztoEsDDdsbq87YPmsDj7k8Op1EkTa2T5CB8XhBOCQDtcj7gUUtg== +"@storybook/router@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-7.6.20.tgz#ffa6a3ba1790e86f1d2364c27d3511f7975742a6" + integrity sha512-mCzsWe6GrH47Xb1++foL98Zdek7uM5GhaSlrI7blWVohGa0qIUYbfJngqR4ZsrXmJeeEvqowobh+jlxg3IJh+w== dependencies: - "@storybook/client-logger" "7.6.19" + "@storybook/client-logger" "7.6.20" memoizerific "^1.11.3" qs "^6.10.0" -"@storybook/telemetry@7.6.19": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-7.6.19.tgz#c0e11bea942057fd8eef66680c39e0cd4e0d9970" - integrity sha512-rA5xum4I36M57iiD3uzmW0MOdpl0vEpHWBSAa5hK0a0ALPeY9TgAsQlI/0dSyNYJ/K7aczEEN6d4qm1NC4u10A== +"@storybook/telemetry@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-7.6.20.tgz#5b3705eb5100b21070d76767dde1040ed5d9b35b" + integrity sha512-dmAOCWmOscYN6aMbhCMmszQjoycg7tUPRVy2kTaWg6qX10wtMrvEtBV29W4eMvqdsoRj5kcvoNbzRdYcWBUOHQ== dependencies: - "@storybook/client-logger" "7.6.19" - "@storybook/core-common" "7.6.19" - "@storybook/csf-tools" "7.6.19" + "@storybook/client-logger" "7.6.20" + "@storybook/core-common" "7.6.20" + "@storybook/csf-tools" "7.6.20" chalk "^4.1.0" detect-package-manager "^2.0.1" fetch-retry "^5.0.2" @@ -7173,13 +7172,13 @@ "@storybook/global" "^5.0.0" memoizerific "^1.11.3" -"@storybook/theming@7.6.19", "@storybook/theming@^7.0.12": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-7.6.19.tgz#f5032d74d5c0cf5f7c7a389a0b9d2d3bc5e62a25" - integrity sha512-sAho13MmtA80ctOaLn8lpkQBsPyiqSdLcOPH5BWFhatQzzBQCpTAKQk+q/xGju8bNiPZ+yQBaBzbN8SfX8ceCg== +"@storybook/theming@7.6.20", "@storybook/theming@^7.0.12": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-7.6.20.tgz#c932cd82c27314979d22d0e7867268e301f5f97c" + integrity sha512-iT1pXHkSkd35JsCte6Qbanmprx5flkqtSHC6Gi6Umqoxlg9IjiLPmpHbaIXzoC06DSW93hPj5Zbi1lPlTvRC7Q== dependencies: "@emotion/use-insertion-effect-with-fallbacks" "^1.0.0" - "@storybook/client-logger" "7.6.19" + "@storybook/client-logger" "7.6.20" "@storybook/global" "^5.0.0" memoizerific "^1.11.3" @@ -7193,12 +7192,12 @@ "@types/express" "^4.7.0" file-system-cache "^2.0.0" -"@storybook/types@7.6.19", "@storybook/types@^7.0.12": - version "7.6.19" - resolved "https://registry.yarnpkg.com/@storybook/types/-/types-7.6.19.tgz#ec73c9afb6003c57e260e1709441af4db9f50190" - integrity sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg== +"@storybook/types@7.6.20", "@storybook/types@^7.0.12": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/types/-/types-7.6.20.tgz#b8d62b30914b35e6750b1f4937da532432f02890" + integrity sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q== dependencies: - "@storybook/channels" "7.6.19" + "@storybook/channels" "7.6.20" "@types/babel__core" "^7.0.0" "@types/express" "^4.7.0" file-system-cache "2.3.0" @@ -20077,7 +20076,7 @@ ip-regex@4.3.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== -ip@^2.0.0, ip@^2.0.1: +ip@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== @@ -29172,12 +29171,12 @@ store2@^2.14.2: resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.2.tgz#56138d200f9fe5f582ad63bc2704dbc0e4a45068" integrity sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w== -storybook@7.6.19: - version "7.6.19" - resolved "https://registry.yarnpkg.com/storybook/-/storybook-7.6.19.tgz#3edb81cfd26d8f710e562419f38bc39ff25da84c" - integrity sha512-xWD1C4vD/4KMffCrBBrUpsLUO/9uNpm8BVW8+Vcb30gkQDfficZ0oziWkmLexpT53VSioa24iazGXMwBqllYjQ== +storybook@7.6.20: + version "7.6.20" + resolved "https://registry.yarnpkg.com/storybook/-/storybook-7.6.20.tgz#6204ff0c28471536a1a64cb16d1c97872dd33f95" + integrity sha512-Wt04pPTO71pwmRmsgkyZhNo4Bvdb/1pBAMsIFb9nQLykEdzzpXjvingxFFvdOG4nIowzwgxD+CLlyRqVJqnATw== dependencies: - "@storybook/cli" "7.6.19" + "@storybook/cli" "7.6.20" stream-browserify@^2.0.1: version "2.0.2" From 897481b3b4cd6d7d9764533964796a99940621d1 Mon Sep 17 00:00:00 2001 From: Michael Barrett Date: Mon, 24 Jun 2024 14:28:42 +0100 Subject: [PATCH 004/131] Added time field to slow get helper logging (#20427) refs [CFR-36](https://linear.app/tryghost/issue/CFR-36/pull-out-response-time-from-ghost-logs-message-field-for-get-helper) Added time field to slow get helper logging to make it easier to query and filter on this value in elastic without having to parse the message field --- ghost/core/core/frontend/helpers/get.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ghost/core/core/frontend/helpers/get.js b/ghost/core/core/frontend/helpers/get.js index 85277b6255..9373ee686c 100644 --- a/ghost/core/core/frontend/helpers/get.js +++ b/ghost/core/core/frontend/helpers/get.js @@ -299,7 +299,7 @@ module.exports = async function get(resource, options) { try { const spanName = `{{#get "${resource}"${apiOptionsString}}} ${data.member ? 'member' : 'public'}`; const result = await Sentry.startSpan({ - op: 'frontend.helpers.get', + op: 'frontend.helpers.get', name: spanName, tags: { resource, @@ -313,11 +313,11 @@ module.exports = async function get(resource, options) { if (response[resource] && response[resource].length) { response[resource].forEach(prepareContextResource); } - + // used for logging details of slow requests returnedRowsCount = response[resource] && response[resource].length; span?.setTag('returnedRows', returnedRowsCount); - + // block params allows the theme developer to name the data using something like // `{{#get "posts" as |result pageInfo|}}` const blockParams = [response[resource]]; @@ -325,7 +325,7 @@ module.exports = async function get(resource, options) { response.pagination = response.meta.pagination; blockParams.push(response.meta.pagination); } - + // Call the main template function const rendered = options.fn(response, { data: data, @@ -357,7 +357,9 @@ module.exports = async function get(resource, options) { apiOptions, returnedRows: returnedRowsCount } - })); + }), { + time: totalMs + }); } } } From 4f6842b99adb7b69ad6c5878ea97231c2c4b9d82 Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Mon, 24 Jun 2024 09:13:20 -0500 Subject: [PATCH 005/131] Added composite index to posts table for type,status (#20437) ref https://linear.app/tryghost/issue/CFR-35 - performance improvement intended for the content api/get helpers The posts table is shared by posts and pages and seldom is queried for both. It makes sense to add an index on type, and from the perspective of the content API, also on status as you're almost only ever querying for published posts or published pages. --- .../server/api/endpoints/utils/serializers/input/posts.js | 4 ++-- .../5.87/2024-06-20-17-02-15-add-posts-type-status-index.js | 4 ++++ ghost/core/core/server/data/schema/schema.js | 3 +++ .../unit/api/canary/utils/serializers/input/posts.test.js | 2 +- ghost/core/test/unit/server/data/schema/integrity.test.js | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 ghost/core/core/server/data/migrations/versions/5.87/2024-06-20-17-02-15-add-posts-type-status-index.js diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/input/posts.js b/ghost/core/core/server/api/endpoints/utils/serializers/input/posts.js index f0494c1327..94d0ae86f0 100644 --- a/ghost/core/core/server/api/endpoints/utils/serializers/input/posts.js +++ b/ghost/core/core/server/api/endpoints/utils/serializers/input/posts.js @@ -47,8 +47,8 @@ function removeSourceFormats(frame) { */ function selectAllAllowedColumns(frame) { if (!frame.options.columns && !frame.options.selectRaw) { - // Because we're returning columns directly from the table we need to remove info columns like @@UNIQUE_CONSTRAINTS@@ - frame.options.selectRaw = _.keys(_.omit(postsSchema, ['lexical','mobiledoc','@@UNIQUE_CONSTRAINTS@@'])).join(','); + // Because we're returning columns directly from the schema we need to remove info columns like @@UNIQUE_CONSTRAINTS@@ and @@INDEXES@@ + frame.options.selectRaw = _.keys(_.omit(postsSchema, ['lexical','mobiledoc','@@INDEXES@@','@@UNIQUE_CONSTRAINTS@@'])).join(','); } else if (frame.options.columns) { frame.options.columns = frame.options.columns.filter((column) => { return !['mobiledoc', 'lexical'].includes(column); diff --git a/ghost/core/core/server/data/migrations/versions/5.87/2024-06-20-17-02-15-add-posts-type-status-index.js b/ghost/core/core/server/data/migrations/versions/5.87/2024-06-20-17-02-15-add-posts-type-status-index.js new file mode 100644 index 0000000000..375725294f --- /dev/null +++ b/ghost/core/core/server/data/migrations/versions/5.87/2024-06-20-17-02-15-add-posts-type-status-index.js @@ -0,0 +1,4 @@ +// For information on writing migrations, see https://www.notion.so/ghost/Database-migrations-eb5b78c435d741d2b34a582d57c24253 +const {createAddIndexMigration} = require('../../utils'); + +module.exports = createAddIndexMigration('posts',['type','status']); \ No newline at end of file diff --git a/ghost/core/core/server/data/schema/schema.js b/ghost/core/core/server/data/schema/schema.js index 33b4c03374..d63f74febf 100644 --- a/ghost/core/core/server/data/schema/schema.js +++ b/ghost/core/core/server/data/schema/schema.js @@ -93,6 +93,9 @@ module.exports = { canonical_url: {type: 'text', maxlength: 2000, nullable: true}, newsletter_id: {type: 'string', maxlength: 24, nullable: true, references: 'newsletters.id'}, show_title_and_feature_image: {type: 'boolean', nullable: false, defaultTo: true}, + '@@INDEXES@@': [ + ['type','status'] + ], '@@UNIQUE_CONSTRAINTS@@': [ ['slug', 'type'] ] diff --git a/ghost/core/test/unit/api/canary/utils/serializers/input/posts.test.js b/ghost/core/test/unit/api/canary/utils/serializers/input/posts.test.js index dbd2cbcda8..3ec4b6f5c1 100644 --- a/ghost/core/test/unit/api/canary/utils/serializers/input/posts.test.js +++ b/ghost/core/test/unit/api/canary/utils/serializers/input/posts.test.js @@ -115,7 +115,7 @@ describe('Unit: endpoints/utils/serializers/input/posts', function () { serializers.input.posts.browse(apiConfig, frame); const columns = Object.keys(postsSchema); const parsedSelectRaw = frame.options.selectRaw.split(',').map(column => column.trim()); - parsedSelectRaw.should.eql(columns.filter(column => !['mobiledoc', 'lexical','@@UNIQUE_CONSTRAINTS@@'].includes(column))); + parsedSelectRaw.should.eql(columns.filter(column => !['mobiledoc', 'lexical','@@UNIQUE_CONSTRAINTS@@','@@INDEXES@@'].includes(column))); }); it('strips mobiledoc and lexical columns from a specified columns option', function () { diff --git a/ghost/core/test/unit/server/data/schema/integrity.test.js b/ghost/core/test/unit/server/data/schema/integrity.test.js index 59125074a2..9f6feb5d85 100644 --- a/ghost/core/test/unit/server/data/schema/integrity.test.js +++ b/ghost/core/test/unit/server/data/schema/integrity.test.js @@ -35,7 +35,7 @@ const validateRouteSettings = require('../../../../../core/server/services/route */ describe('DB version integrity', function () { // Only these variables should need updating - const currentSchemaHash = 'a04351620a75f14dfbd9158381df7f87'; + const currentSchemaHash = '45c8072332176e0fe5a4fdff58fb2113'; const currentFixturesHash = 'a489d615989eab1023d4b8af0ecee7fd'; const currentSettingsHash = '5c957ceb48c4878767d7d3db484c592d'; const currentRoutesHash = '3d180d52c663d173a6be791ef411ed01'; From b9240271fec32fbdc7cce44e46d42e31041c64c1 Mon Sep 17 00:00:00 2001 From: Sag Date: Mon, 24 Jun 2024 16:14:09 +0200 Subject: [PATCH 006/131] Added config to hide labels from the signup card for contributors (#20429) ref https://linear.app/tryghost/issue/SLO-127 - problem: contributors see an empty list of labels in the Signup card, even if some exist - cause: contributors do not have permission to browse labels - solution: hide the label input entirely for contributors in the Signup card, based on the new `renderLabels` config parameter --- ghost/admin/app/components/koenig-lexical-editor.js | 1 + ghost/admin/package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ghost/admin/app/components/koenig-lexical-editor.js b/ghost/admin/app/components/koenig-lexical-editor.js index 0e4979b232..846ed45211 100644 --- a/ghost/admin/app/components/koenig-lexical-editor.js +++ b/ghost/admin/app/components/koenig-lexical-editor.js @@ -448,6 +448,7 @@ export default class KoenigLexicalEditor extends Component { fetchCollectionPosts, fetchEmbed, fetchLabels, + renderLabels: !this.session.user.isContributor, feature: { collectionsCard: this.feature.collectionsCard, collections: this.feature.collections, diff --git a/ghost/admin/package.json b/ghost/admin/package.json index 7dba036d35..61d0a7fed9 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -48,7 +48,7 @@ "@tryghost/helpers": "1.1.90", "@tryghost/kg-clean-basic-html": "4.1.1", "@tryghost/kg-converters": "1.0.5", - "@tryghost/koenig-lexical": "1.2.7", + "@tryghost/koenig-lexical": "1.2.8", "@tryghost/limit-service": "1.2.14", "@tryghost/members-csv": "0.0.0", "@tryghost/nql": "0.12.3", diff --git a/yarn.lock b/yarn.lock index a8f5cf5636..c380ae6ce0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7939,10 +7939,10 @@ dependencies: semver "^7.3.5" -"@tryghost/koenig-lexical@1.2.7": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.2.7.tgz#115f8f53d5e39a586cf7cb206ce0c962decab844" - integrity sha512-GzefNAJb/ta0/g38bGHtP8CzDfuRJO3AUtTsj0cfYQk4aEruAsSi54kjk+jc3d5DenXqzJF8iqAd0ZRCWAo3CA== +"@tryghost/koenig-lexical@1.2.8": + version "1.2.8" + resolved "https://registry.npmjs.org/@tryghost/koenig-lexical/-/koenig-lexical-1.2.8.tgz#fa92c6dd065a4f4ed1388391898b3d75d6b2cd13" + integrity sha512-c4Igsw57s6u3b/EeO++Ov/GSGok9z7nCt8Jo1o2mHhOwFEgBwv4rQg+4xbxqwcTX4Fdnapr4IdhRcuoQle8HlA== "@tryghost/limit-service@1.2.14": version "1.2.14" From b10b81b7d768b22d2309be1047d50b6b13b3e16f Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Mon, 24 Jun 2024 10:17:45 -0500 Subject: [PATCH 007/131] Prevented pages content api queries from returning mobiledoc or lexical fields (#20454) ref https://linear.app/tryghost/issue/CFR-43/ ref 9d9a421 We recently stopped `select *` from posts when making Content API requests. This is now being applied to the pages endpoint to help improve performance. These fields were already being stripped out in the output serializer, and they will now no longer be returned from the db at all, reducing the amount of data transferred. --- .../utils/serializers/input/pages.js | 33 +++++++++- .../utils/serializers/input/pages.test.js | 60 +++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js b/ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js index 19c58aa306..afd6da5745 100644 --- a/ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js +++ b/ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js @@ -7,6 +7,7 @@ const slugFilterOrder = require('./utils/slug-filter-order'); const localUtils = require('../../index'); const mobiledoc = require('../../../../../lib/mobiledoc'); const postsMetaSchema = require('../../../../../data/schema').tables.posts_meta; +const postsSchema = require('../../../../../data/schema').tables.posts; const clean = require('./utils/clean'); const lexical = require('../../../../../lib/lexical'); const sentry = require('../../../../../../shared/sentry'); @@ -24,6 +25,33 @@ function removeSourceFormats(frame) { } } +/** + * Selects all allowed columns for the given frame. + * + * This removes the lexical and mobiledoc columns from the query. This is a performance improvement as we never intend + * to expose those columns in the content API and they are very large datasets to be passing around and de/serializing. + * + * NOTE: This is only intended for the Content API. We need these fields within Admin API responses. + * + * @param {Object} frame - The frame object. + */ +function selectAllAllowedColumns(frame) { + if (!frame.options.columns && !frame.options.selectRaw) { + // Because we're returning columns directly from the schema we need to remove info columns like @@UNIQUE_CONSTRAINTS@@ and @@INDEXES@@ + frame.options.selectRaw = _.keys(_.omit(postsSchema, ['lexical','mobiledoc','@@INDEXES@@','@@UNIQUE_CONSTRAINTS@@'])).join(','); + } else if (frame.options.columns) { + frame.options.columns = frame.options.columns.filter((column) => { + return !['mobiledoc', 'lexical'].includes(column); + }); + } else if (frame.options.selectRaw) { + frame.options.selectRaw = frame.options.selectRaw.split(',').map((column) => { + return column.trim(); + }).filter((column) => { + return !['mobiledoc', 'lexical'].includes(column); + }).join(','); + } +} + function defaultRelations(frame) { if (frame.options.withRelated) { return; @@ -97,7 +125,10 @@ module.exports = { forcePageFilter(frame); if (localUtils.isContentAPI(frame)) { - removeSourceFormats(frame); + // CASE: the content api endpoint for posts should not return mobiledoc or lexical + removeSourceFormats(frame); // remove from the format field + selectAllAllowedColumns(frame); // remove from any specified column or selectRaw options + setDefaultOrder(frame); forceVisibilityColumn(frame); } diff --git a/ghost/core/test/unit/api/canary/utils/serializers/input/pages.test.js b/ghost/core/test/unit/api/canary/utils/serializers/input/pages.test.js index 6a30fd39ca..ed8ed3cf78 100644 --- a/ghost/core/test/unit/api/canary/utils/serializers/input/pages.test.js +++ b/ghost/core/test/unit/api/canary/utils/serializers/input/pages.test.js @@ -1,6 +1,7 @@ const should = require('should'); const sinon = require('sinon'); const serializers = require('../../../../../../../core/server/api/endpoints/utils/serializers'); +const postsSchema = require('../../../../../../../core/server/data/schema').tables.posts; const mobiledocLib = require('@tryghost/html-to-mobiledoc'); @@ -67,6 +68,65 @@ describe('Unit: endpoints/utils/serializers/input/pages', function () { frame.options.formats.should.containEql('html'); frame.options.formats.should.containEql('plaintext'); }); + + describe('Content API', function () { + it('selects all columns from the posts schema but mobiledoc and lexical when no columns are specified', function () { + const apiConfig = {}; + const frame = { + apiType: 'content', + options: { + context: {} + } + }; + + serializers.input.pages.browse(apiConfig, frame); + const columns = Object.keys(postsSchema); + const parsedSelectRaw = frame.options.selectRaw.split(',').map(column => column.trim()); + parsedSelectRaw.should.eql(columns.filter(column => !['mobiledoc', 'lexical','@@UNIQUE_CONSTRAINTS@@','@@INDEXES@@'].includes(column))); + }); + + it('strips mobiledoc and lexical columns from a specified columns option', function () { + const apiConfig = {}; + const frame = { + apiType: 'content', + options: { + context: {}, + columns: ['id', 'mobiledoc', 'lexical', 'visibility'] + } + }; + + serializers.input.pages.browse(apiConfig, frame); + frame.options.columns.should.eql(['id', 'visibility']); + }); + + it('forces visibility column if columns are specified', function () { + const apiConfig = {}; + const frame = { + apiType: 'content', + options: { + context: {}, + columns: ['id'] + } + }; + + serializers.input.pages.browse(apiConfig, frame); + frame.options.columns.should.eql(['id', 'visibility']); + }); + + it('strips mobiledoc and lexical columns from a specified selectRaw option', function () { + const apiConfig = {}; + const frame = { + apiType: 'content', + options: { + context: {}, + selectRaw: 'id, mobiledoc, lexical' + } + }; + + serializers.input.posts.browse(apiConfig, frame); + frame.options.selectRaw.should.eql('id'); + }); + }); }); describe('read', function () { From 725ebc3e9f0b23d0b1be699f3aa9dbdef9265580 Mon Sep 17 00:00:00 2001 From: Sag Date: Mon, 24 Jun 2024 17:33:39 +0200 Subject: [PATCH 008/131] Fixed invalid tierId handling during member paid checkout (#20455) - fixes https://linear.app/tryghost/issue/SLO-90 --- .../lib/controllers/RouterController.js | 18 ++++++- .../test/unit/lib/controllers/router.test.js | 52 ++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/ghost/members-api/lib/controllers/RouterController.js b/ghost/members-api/lib/controllers/RouterController.js index b5af1d8599..3ef803d5a0 100644 --- a/ghost/members-api/lib/controllers/RouterController.js +++ b/ghost/members-api/lib/controllers/RouterController.js @@ -10,6 +10,7 @@ const messages = { notFound: 'Not Found.', offerNotFound: 'This offer does not exist.', offerArchived: 'This offer is archived.', + tierNotFound: 'This tier does not exist.', tierArchived: 'This tier is archived.', existingSubscription: 'A subscription exists for this Member.', unableToCheckout: 'Unable to initiate checkout session', @@ -258,9 +259,22 @@ module.exports = class RouterController { tier = await this._tiersService.api.read(offer.tier.id); cadence = offer.cadence; - } else { + } else if (tierId) { offer = null; - tier = await this._tiersService.api.read(tierId); + + try { + // If the tierId is not a valid ID, the following line will throw + tier = await this._tiersService.api.read(tierId); + + if (!tier) { + throw undefined; + } + } catch (err) { + throw new BadRequestError({ + message: tpl(messages.tierNotFound), + context: 'Tier with id "' + tierId + '" not found' + }); + } } if (tier.status === 'archived') { diff --git a/ghost/members-api/test/unit/lib/controllers/router.test.js b/ghost/members-api/test/unit/lib/controllers/router.test.js index ef1d5dd7fb..cd85e09321 100644 --- a/ghost/members-api/test/unit/lib/controllers/router.test.js +++ b/ghost/members-api/test/unit/lib/controllers/router.test.js @@ -162,7 +162,7 @@ describe('RouterController', function () { } }); - it('returns a BadRequestError if offer is not found', async function () { + it('returns a BadRequestError if offer is not found by offerId', async function () { offersAPI = { getOffer: sinon.stub().resolves(null) }; @@ -184,6 +184,56 @@ describe('RouterController', function () { assert.equal(error.context, 'Offer with id "invalid" not found'); } }); + + it('returns a BadRequestError if tier is not found by tierId', async function () { + tiersService = { + api: { + read: sinon.stub().resolves(null) + } + }; + + const routerController = new RouterController({ + tiersService, + paymentsService, + offersAPI, + stripeAPIService, + labsService + }); + + try { + await routerController._getSubscriptionCheckoutData({tierId: 'invalid', cadence: 'year'}); + + assert.fail('Expected function to throw BadRequestError'); + } catch (error) { + assert(error instanceof errors.BadRequestError, 'Error should be an instance of BadRequestError'); + assert.equal(error.context, 'Tier with id "invalid" not found'); + } + }); + + it('returns a BadRequestError if fetching tier by tierId throws', async function () { + tiersService = { + api: { + read: sinon.stub().rejects(new Error('Fail to fetch tier')) + } + }; + + const routerController = new RouterController({ + tiersService, + paymentsService, + offersAPI, + stripeAPIService, + labsService + }); + + try { + await routerController._getSubscriptionCheckoutData({tierId: 'invalid', cadence: 'year'}); + + assert.fail('Expected function to throw BadRequestError'); + } catch (error) { + assert(error instanceof errors.BadRequestError, 'Error should be an instance of BadRequestError'); + assert.equal(error.context, 'Tier with id "invalid" not found'); + } + }); }); afterEach(function () { From b81839f2fe8030c54a359c56cc11bcd4a05f3b2a Mon Sep 17 00:00:00 2001 From: Fabien O'Carroll Date: Thu, 20 Jun 2024 11:26:57 +0700 Subject: [PATCH 009/131] Updated ActivityPub Admin X to work with Fedify API --- .../src/components/ListIndex.tsx | 2 +- .../src/components/ViewFollowers.tsx | 11 ++++++----- .../src/components/ViewFollowing.tsx | 7 ++++--- .../src/utils/get-username-from-following.ts | 12 ------------ apps/admin-x-framework/src/api/activitypub.ts | 19 ++++++++++++++----- 5 files changed, 25 insertions(+), 26 deletions(-) delete mode 100644 apps/admin-x-activitypub/src/utils/get-username-from-following.ts diff --git a/apps/admin-x-activitypub/src/components/ListIndex.tsx b/apps/admin-x-activitypub/src/components/ListIndex.tsx index 76c2b3e401..996533aa3c 100644 --- a/apps/admin-x-activitypub/src/components/ListIndex.tsx +++ b/apps/admin-x-activitypub/src/components/ListIndex.tsx @@ -16,7 +16,7 @@ const ActivityPubComponent: React.FC = () => { const {updateRoute} = useRouting(); // TODO: Replace with actual user ID - const {data: {orderedItems: activities = []} = {}} = useBrowseInboxForUser('index'); + const {data: {items: activities = []} = {}} = useBrowseInboxForUser('index'); const {data: {totalItems: followingCount = 0} = {}} = useBrowseFollowingForUser('index'); const {data: {totalItems: followersCount = 0} = {}} = useBrowseFollowersForUser('index'); diff --git a/apps/admin-x-activitypub/src/components/ViewFollowers.tsx b/apps/admin-x-activitypub/src/components/ViewFollowers.tsx index b1a92fda9a..bff12ef78c 100644 --- a/apps/admin-x-activitypub/src/components/ViewFollowers.tsx +++ b/apps/admin-x-activitypub/src/components/ViewFollowers.tsx @@ -1,8 +1,8 @@ import {} from '@tryghost/admin-x-framework/api/activitypub'; import NiceModal from '@ebay/nice-modal-react'; -import getUsernameFromFollowing from '../utils/get-username-from-following'; +import getUsername from '../utils/get-username'; import {Avatar, Button, List, ListItem, Modal} from '@tryghost/admin-x-design-system'; -import {FollowingResponseData, useBrowseFollowersForUser, useUnfollow} from '@tryghost/admin-x-framework/api/activitypub'; +import {FollowingResponseData, useBrowseFollowersForUser, useFollow} from '@tryghost/admin-x-framework/api/activitypub'; import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing'; interface ViewFollowersModalProps { @@ -13,10 +13,11 @@ interface ViewFollowersModalProps { const ViewFollowersModal: React.FC = ({}) => { const {updateRoute} = useRouting(); // const modal = NiceModal.useModal(); - const mutation = useUnfollow(); + const mutation = useFollow(); - const {data: {orderedItems: followers = []} = {}} = useBrowseFollowersForUser('inbox'); + const {data: {items = []} = {}} = useBrowseFollowersForUser('inbox'); + const followers = Array.isArray(items) ? items : [items]; return ( { @@ -33,7 +34,7 @@ const ViewFollowersModal: React.FC
{followers.map(item => ( - mutation.mutate({username: item.username})} />} avatar={} detail={getUsernameFromFollowing(item)} id='list-item' title={item.name}> + mutation.mutate({username: getUsername(item)})} />} avatar={} detail={getUsername(item)} id='list-item' title={item.name}> ))}
diff --git a/apps/admin-x-activitypub/src/components/ViewFollowing.tsx b/apps/admin-x-activitypub/src/components/ViewFollowing.tsx index dc5dc2021c..4a5bc82cd6 100644 --- a/apps/admin-x-activitypub/src/components/ViewFollowing.tsx +++ b/apps/admin-x-activitypub/src/components/ViewFollowing.tsx @@ -1,6 +1,6 @@ import {} from '@tryghost/admin-x-framework/api/activitypub'; import NiceModal from '@ebay/nice-modal-react'; -import getUsernameFromFollowing from '../utils/get-username-from-following'; +import getUsername from '../utils/get-username'; import {Avatar, Button, List, ListItem, Modal} from '@tryghost/admin-x-design-system'; import {FollowingResponseData, useBrowseFollowingForUser, useUnfollow} from '@tryghost/admin-x-framework/api/activitypub'; import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing'; @@ -14,8 +14,9 @@ const ViewFollowingModal: React.FC const {updateRoute} = useRouting(); const mutation = useUnfollow(); - const {data: {orderedItems: following = []} = {}} = useBrowseFollowingForUser('inbox'); + const {data: {items = []} = {}} = useBrowseFollowingForUser('inbox'); + const following = Array.isArray(items) ? items : [items]; return ( { @@ -32,7 +33,7 @@ const ViewFollowingModal: React.FC
{following.map(item => ( - mutation.mutate({username: getUsernameFromFollowing(item)})} />} avatar={} detail={getUsernameFromFollowing(item)} id='list-item' title={item.name}> + mutation.mutate({username: getUsername(item)})} />} avatar={} detail={getUsername(item)} id='list-item' title={item.name}> ))} {/* diff --git a/apps/admin-x-activitypub/src/utils/get-username-from-following.ts b/apps/admin-x-activitypub/src/utils/get-username-from-following.ts deleted file mode 100644 index 598caa5f6e..0000000000 --- a/apps/admin-x-activitypub/src/utils/get-username-from-following.ts +++ /dev/null @@ -1,12 +0,0 @@ -function getUsernameFromFollowing(followItem: {username: string; id: string|null;}) { - if (!followItem.username || !followItem.id) { - return '@unknown@unknown'; - } - try { - return `@${followItem.username}@${(new URL(followItem.id)).hostname}`; - } catch (err) { - return '@unknown@unknown'; - } -} - -export default getUsernameFromFollowing; diff --git a/apps/admin-x-framework/src/api/activitypub.ts b/apps/admin-x-framework/src/api/activitypub.ts index 83ecfdd95c..246292d556 100644 --- a/apps/admin-x-framework/src/api/activitypub.ts +++ b/apps/admin-x-framework/src/api/activitypub.ts @@ -2,7 +2,7 @@ import {createMutation, createQueryWithId} from '../utils/api/hooks'; export type FollowItem = { id: string; - username: string, + preferredUsername: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any [x: string]: any }; @@ -63,7 +63,7 @@ export type InboxResponseData = { summary: string; type: 'OrderedCollection'; totalItems: number; - orderedItems: Activity[]; + items: Activity[]; } export type FollowingResponseData = { @@ -72,7 +72,7 @@ export type FollowingResponseData = { summary: string; type: string; totalItems: number; - orderedItems: FollowItem[]; + items: FollowItem[]; } type FollowRequestProps = { @@ -82,19 +82,22 @@ type FollowRequestProps = { export const useFollow = createMutation({ method: 'POST', useActivityPub: true, - path: data => `/follow/${data.username}` + path: data => `/actions/follow/${data.username}` }); export const useUnfollow = createMutation({ method: 'POST', useActivityPub: true, - path: data => `/unfollow/${data.username}` + path: data => `/actions/unfollow/${data.username}` }); // This is a frontend root, not using the Ghost admin API export const useBrowseInboxForUser = createQueryWithId({ dataType: 'InboxResponseData', useActivityPub: true, + headers: { + Accept: 'application/activity+json' + }, path: id => `/inbox/${id}` }); @@ -102,6 +105,9 @@ export const useBrowseInboxForUser = createQueryWithId({ export const useBrowseFollowingForUser = createQueryWithId({ dataType: 'FollowingResponseData', useActivityPub: true, + headers: { + Accept: 'application/activity+json' + }, path: id => `/following/${id}` }); @@ -109,5 +115,8 @@ export const useBrowseFollowingForUser = createQueryWithId({ dataType: 'FollowingResponseData', useActivityPub: true, + headers: { + Accept: 'application/activity+json' + }, path: id => `/followers/${id}` }); From 79bc84c54578e8928274ce6d20a093b20bbdd306 Mon Sep 17 00:00:00 2001 From: Djordje Vlaisavljevic Date: Mon, 24 Jun 2024 17:39:29 +0100 Subject: [PATCH 010/131] Added empty state ref https://linear.app/tryghost/issue/MOM-246/add-empty-state-design --- .../src/assets/images/ap-welcome.png | Bin 0 -> 81406 bytes .../src/components/ListIndex.tsx | 48 +++++++++++++----- 2 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 apps/admin-x-activitypub/src/assets/images/ap-welcome.png diff --git a/apps/admin-x-activitypub/src/assets/images/ap-welcome.png b/apps/admin-x-activitypub/src/assets/images/ap-welcome.png new file mode 100644 index 0000000000000000000000000000000000000000..189768bcc5de3732fffe01d8c895eca90cd93110 GIT binary patch literal 81406 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 I<*g$B2d-Y^E&u=k literal 0 HcmV?d00001 diff --git a/apps/admin-x-activitypub/src/components/ListIndex.tsx b/apps/admin-x-activitypub/src/components/ListIndex.tsx index 996533aa3c..f0537fe3f5 100644 --- a/apps/admin-x-activitypub/src/components/ListIndex.tsx +++ b/apps/admin-x-activitypub/src/components/ListIndex.tsx @@ -1,4 +1,5 @@ // import NiceModal from '@ebay/nice-modal-react'; +// import ActivityPubWelcomeImage from '../assets/images/ap-welcome.png'; import React, {useState} from 'react'; import articleBodyStyles from './articleBodyStyles'; import getUsername from '../utils/get-username'; @@ -40,12 +41,20 @@ const ActivityPubComponent: React.FC = () => { title: 'Inbox', contents:
      - {activities && activities.slice().reverse().map(activity => ( + {activities && activities.some(activity => activity.type === 'Create' && activity.object.type === 'Article') ? (activities.slice().reverse().map(activity => ( activity.type === 'Create' && activity.object.type === 'Article' &&
    • handleViewContent(activity.object, activity.actor)}>
    • - ))} + ))) :
      +
      + {/* Ghost site logos */} + Welcome to ActivityPub +

      We’re so glad to have you on board! At the moment, you can follow other Ghost sites and enjoy their content right here inside Ghost.

      +

      You can see all of the users on the right—find your favorite ones and give them a follow.

      +
      +
      }
    @@ -108,18 +117,31 @@ const ActivityPubComponent: React.FC = () => { }; const Sidebar: React.FC<{followingCount: number, followersCount: number, updateRoute: (route: string) => void}> = ({followingCount, followersCount, updateRoute}) => ( -
    -
    -
    -
    updateRoute('/view-following')}> - {followingCount} - Following -
    -
    updateRoute('/view-followers')}> - {followersCount} - Followers +
    +
    +
    +
    +
    updateRoute('/view-following')}> + {followingCount} + Following +
    +
    updateRoute('/view-followers')}> + {followersCount} + Followers +
    +
    +
    + Explore +
    + + + + + +
    ); @@ -241,7 +263,7 @@ const ViewArticle: React.FC = ({object, onBackToList}) => {
    -
    +
    -
    +
    Explore
    - - - + {}} />} avatar={} detail='829 followers' hideActions={true} title='404 Media' /> + {}} />} avatar={} detail='791 followers' hideActions={true} title='The Browser' /> + {}} />} avatar={} detail='854 followers' hideActions={true} title='Welcome to Hell World' />
    From 21a2a8236e95f9066a896bde7dc7085961614af9 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 24 Jun 2024 21:35:29 +0100 Subject: [PATCH 012/131] Added analytic events to internal linking feature closes https://linear.app/tryghost/issue/MOM-77 closes https://linear.app/tryghost/issue/MOM-78 - bumps Koenig to support events - adds `siteUrl` pass-through to Koenig to allow differentiation between internal and external URLs --- ghost/admin/app/components/koenig-lexical-editor.js | 3 ++- ghost/admin/package.json | 4 ++-- yarn.lock | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ghost/admin/app/components/koenig-lexical-editor.js b/ghost/admin/app/components/koenig-lexical-editor.js index 846ed45211..18e6d2df19 100644 --- a/ghost/admin/app/components/koenig-lexical-editor.js +++ b/ghost/admin/app/components/koenig-lexical-editor.js @@ -462,7 +462,8 @@ export default class KoenigLexicalEditor extends Component { membersEnabled: this.settings.membersSignupAccess === 'all', searchLinks, siteTitle: this.settings.title, - siteDescription: this.settings.description + siteDescription: this.settings.description, + siteUrl: this.config.getSiteUrl('/') }; const cardConfig = Object.assign({}, defaultCardConfig, props.cardConfig, {pinturaConfig: this.pinturaConfig}); diff --git a/ghost/admin/package.json b/ghost/admin/package.json index 61d0a7fed9..cba933754f 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -48,7 +48,7 @@ "@tryghost/helpers": "1.1.90", "@tryghost/kg-clean-basic-html": "4.1.1", "@tryghost/kg-converters": "1.0.5", - "@tryghost/koenig-lexical": "1.2.8", + "@tryghost/koenig-lexical": "1.2.9", "@tryghost/limit-service": "1.2.14", "@tryghost/members-csv": "0.0.0", "@tryghost/nql": "0.12.3", @@ -206,4 +206,4 @@ } } } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index c380ae6ce0..3110d31d47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7939,10 +7939,10 @@ dependencies: semver "^7.3.5" -"@tryghost/koenig-lexical@1.2.8": - version "1.2.8" - resolved "https://registry.npmjs.org/@tryghost/koenig-lexical/-/koenig-lexical-1.2.8.tgz#fa92c6dd065a4f4ed1388391898b3d75d6b2cd13" - integrity sha512-c4Igsw57s6u3b/EeO++Ov/GSGok9z7nCt8Jo1o2mHhOwFEgBwv4rQg+4xbxqwcTX4Fdnapr4IdhRcuoQle8HlA== +"@tryghost/koenig-lexical@1.2.9": + version "1.2.9" + resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.2.9.tgz#ab922c51b2ddaa8bb361fd7e4c5760bb6e902055" + integrity sha512-1PrM2qCJFyMpUSxrzL8IuzY2OAYbeUKuVSEX/GKOxk/zZRib0Q5dsfjrhVv3iurvBg2Gmg5yyzXC60FDqXLR9w== "@tryghost/limit-service@1.2.14": version "1.2.14" From 516a2e1ff6799a0151a47f2f87e8e161e401678f Mon Sep 17 00:00:00 2001 From: Sag Date: Tue, 25 Jun 2024 10:52:34 +0200 Subject: [PATCH 013/131] Reduced Sentry replays sample rate to 50% (#20458) fixes https://linear.app/tryghost/issue/SLO-156 - we have reached our 10k replays per month quota in 20 days, by using a 100% error sampling rate - we would need a sampling rate < 0.64% to stay under the quota - from now on, we will be using a 50% error sampling rate to have a bit of margin, and have a rounder number that is easier to reason about (1 out of 2 error sessions are recorded in Sentry) --- ghost/admin/app/routes/application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ghost/admin/app/routes/application.js b/ghost/admin/app/routes/application.js index 3a64878bc2..0416014779 100644 --- a/ghost/admin/app/routes/application.js +++ b/ghost/admin/app/routes/application.js @@ -203,7 +203,7 @@ export default Route.extend(ShortcutsRoute, { try { // Session Replay on errors // Docs: https://docs.sentry.io/platforms/javascript/session-replay - sentryConfig.replaysOnErrorSampleRate = 1.0; + sentryConfig.replaysOnErrorSampleRate = 0.5; sentryConfig.integrations.push( // Replace with `Sentry.replayIntegration()` once we've migrated to @sentry/ember 8.x // Docs: https://docs.sentry.io/platforms/javascript/migration/v7-to-v8/#removal-of-sentryreplay-package From 68dcec143e8ee2262ea75acf1affe2745f9c5c88 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 09:27:38 +0000 Subject: [PATCH 014/131] Update dependency ember-auto-import to v2.7.4 --- ghost/admin/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ghost/admin/package.json b/ghost/admin/package.json index cba933754f..723ed96ae5 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -71,7 +71,7 @@ "element-resize-detector": "1.2.4", "ember-ajax": "5.1.2", "ember-assign-helper": "0.4.0", - "ember-auto-import": "2.7.3", + "ember-auto-import": "2.7.4", "ember-classic-decorator": "3.0.1", "ember-cli": "3.24.0", "ember-cli-app-version": "5.0.0", diff --git a/yarn.lock b/yarn.lock index 3110d31d47..09ed6cab7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15005,10 +15005,10 @@ ember-assign-polyfill@^2.6.0: ember-cli-babel "^7.20.5" ember-cli-version-checker "^2.0.0" -ember-auto-import@2.7.3, "ember-auto-import@^1.12.1 || ^2.4.3", ember-auto-import@^2.2.3, ember-auto-import@^2.4.1: - version "2.7.3" - resolved "https://registry.yarnpkg.com/ember-auto-import/-/ember-auto-import-2.7.3.tgz#08421f35cd9628327e1dd5d9df40673a1d7beb71" - integrity sha512-EQzStGYxNvTPYWCFh0X57HFAzAvA2rHHRgBeWNDKHQ/rENNlHw0c0e0i1XebwEfv+yGHOodE4dN+f/mrYkQXLw== +ember-auto-import@2.7.4, "ember-auto-import@^1.12.1 || ^2.4.3", ember-auto-import@^2.2.3, ember-auto-import@^2.4.1: + version "2.7.4" + resolved "https://registry.yarnpkg.com/ember-auto-import/-/ember-auto-import-2.7.4.tgz#ca99570eb3d6165968df797a4750aa58073852b5" + integrity sha512-6CdXSegJJc8nwwK7+1lIcBUnMVrJRNd4ZdMgcKbCAwPvcGxMgRVBddSzrX/+q/UuflvTEO26Dk1g7Z6KHMXUhw== dependencies: "@babel/core" "^7.16.7" "@babel/plugin-proposal-class-properties" "^7.16.7" From 03113313cee2aad5877c1dec4d5be38d0893fcf9 Mon Sep 17 00:00:00 2001 From: Sanne de Vries <65487235+sanne-san@users.noreply.github.com> Date: Tue, 25 Jun 2024 12:26:30 +0200 Subject: [PATCH 015/131] =?UTF-8?q?=F0=9F=8E=A8=20Updated=20editor=20toolb?= =?UTF-8?q?ar=20and=20action=20button=20designs=20(#20405)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit REF https://linear.app/tryghost/issue/MOM-238 - Updated feature image action button styles - Aligned button and tooltip styles with the rest of the editor - Updated `koenig-lexical` version to pull in new toolbar design --- ghost/admin/app/components/gh-editor-feature-image.hbs | 2 +- ghost/admin/app/components/koenig-image-editor.hbs | 4 ++-- ghost/admin/app/styles/layouts/editor.css | 6 +++--- ghost/admin/app/styles/spirit/_custom-styles.css | 6 +++--- ghost/admin/package.json | 2 +- ghost/admin/public/assets/icons/koenig/kg-edit.svg | 6 ------ ghost/admin/public/assets/icons/koenig/kg-trash.svg | 5 ++++- ghost/admin/public/assets/icons/koenig/kg-wand.svg | 3 +++ yarn.lock | 8 ++++---- 9 files changed, 21 insertions(+), 21 deletions(-) delete mode 100644 ghost/admin/public/assets/icons/koenig/kg-edit.svg create mode 100644 ghost/admin/public/assets/icons/koenig/kg-wand.svg diff --git a/ghost/admin/app/components/gh-editor-feature-image.hbs b/ghost/admin/app/components/gh-editor-feature-image.hbs index 66775bb48d..c6390324a2 100644 --- a/ghost/admin/app/components/gh-editor-feature-image.hbs +++ b/ghost/admin/app/components/gh-editor-feature-image.hbs @@ -46,7 +46,7 @@ @imageSrc={{@image}} @saveImage={{fn this.saveImage uploader.setFiles}} /> -
    diff --git a/ghost/admin/app/components/koenig-image-editor.hbs b/ghost/admin/app/components/koenig-image-editor.hbs index f518062917..f58b2a165e 100644 --- a/ghost/admin/app/components/koenig-image-editor.hbs +++ b/ghost/admin/app/components/koenig-image-editor.hbs @@ -6,10 +6,10 @@ {{/if}} diff --git a/ghost/admin/app/styles/layouts/editor.css b/ghost/admin/app/styles/layouts/editor.css index 42914d25d0..f8aab8958c 100644 --- a/ghost/admin/app/styles/layouts/editor.css +++ b/ghost/admin/app/styles/layouts/editor.css @@ -581,7 +581,7 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone { padding: 0; background: var(--white); color: var(--darkgrey); - border-radius: 4px; + border-radius: 6px; transition: all .1s ease-in; opacity: 0; } @@ -604,7 +604,7 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone { } .gh-editor-feature-image .image-action svg path { - fill: var(--darkgrey); + stroke-width: 2; } .gh-editor-feature-image-add { @@ -1037,7 +1037,7 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone { .gh-editor .editor-preview h3, .gh-editor .editor-preview h4, .gh-editor .editor-preview h5, -.gh-editor .editor-preview h6, { +.gh-editor .editor-preview h6 { font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, "Open Sans", "Helvetica Neue", sans-serif; } diff --git a/ghost/admin/app/styles/spirit/_custom-styles.css b/ghost/admin/app/styles/spirit/_custom-styles.css index de443ae1a4..870b2278a5 100644 --- a/ghost/admin/app/styles/spirit/_custom-styles.css +++ b/ghost/admin/app/styles/spirit/_custom-styles.css @@ -184,9 +184,9 @@ button, .btn-base { bottom: calc(100% + 4px); left: 50%; white-space: nowrap; - padding: 3px 7px; - border-radius: 3px; - background-color: var(--darkgrey); + padding: 4px 10px; + border-radius: 6px; + background-color: var(--black); color: var(--white); content: attr(data-tooltip); text-align: center; diff --git a/ghost/admin/package.json b/ghost/admin/package.json index 723ed96ae5..b3f3eb3883 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -48,7 +48,7 @@ "@tryghost/helpers": "1.1.90", "@tryghost/kg-clean-basic-html": "4.1.1", "@tryghost/kg-converters": "1.0.5", - "@tryghost/koenig-lexical": "1.2.9", + "@tryghost/koenig-lexical": "1.3.0", "@tryghost/limit-service": "1.2.14", "@tryghost/members-csv": "0.0.0", "@tryghost/nql": "0.12.3", diff --git a/ghost/admin/public/assets/icons/koenig/kg-edit.svg b/ghost/admin/public/assets/icons/koenig/kg-edit.svg deleted file mode 100644 index 8b4bd26cf4..0000000000 --- a/ghost/admin/public/assets/icons/koenig/kg-edit.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/ghost/admin/public/assets/icons/koenig/kg-trash.svg b/ghost/admin/public/assets/icons/koenig/kg-trash.svg index 2c16a4fb27..9e232fc94a 100644 --- a/ghost/admin/public/assets/icons/koenig/kg-trash.svg +++ b/ghost/admin/public/assets/icons/koenig/kg-trash.svg @@ -1 +1,4 @@ - + + + + \ No newline at end of file diff --git a/ghost/admin/public/assets/icons/koenig/kg-wand.svg b/ghost/admin/public/assets/icons/koenig/kg-wand.svg new file mode 100644 index 0000000000..d604063224 --- /dev/null +++ b/ghost/admin/public/assets/icons/koenig/kg-wand.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 09ed6cab7e..256fa3b64f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7939,10 +7939,10 @@ dependencies: semver "^7.3.5" -"@tryghost/koenig-lexical@1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.2.9.tgz#ab922c51b2ddaa8bb361fd7e4c5760bb6e902055" - integrity sha512-1PrM2qCJFyMpUSxrzL8IuzY2OAYbeUKuVSEX/GKOxk/zZRib0Q5dsfjrhVv3iurvBg2Gmg5yyzXC60FDqXLR9w== +"@tryghost/koenig-lexical@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.3.0.tgz#03bfaa55a5c2da64b2ddfb4c1ee271a62dc4a11d" + integrity sha512-qeKenjk3fRDPjxzqDqIDDAWVZTTKfk8JPMxyPjUzyUilWFavztRfxuo+QHqsjTGAp+TnaBTWADoq2WHbgKSA+A== "@tryghost/limit-service@1.2.14": version "1.2.14" From df16fe1cf47ea141bf6152077d877cdbc810fd39 Mon Sep 17 00:00:00 2001 From: Sanne de Vries <65487235+sanne-san@users.noreply.github.com> Date: Wed, 26 Jun 2024 10:37:01 +0200 Subject: [PATCH 016/131] Added contentVisibility feature flag to FeatureService (#20465) REF MOM-221 --- ghost/admin/app/services/feature.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ghost/admin/app/services/feature.js b/ghost/admin/app/services/feature.js index b386cdd38a..cc71f91672 100644 --- a/ghost/admin/app/services/feature.js +++ b/ghost/admin/app/services/feature.js @@ -84,6 +84,7 @@ export default class FeatureService extends Service { @feature('internalLinking') internalLinking; @feature('editorExcerpt') editorExcerpt; @feature('newsletterExcerpt') newsletterExcerpt; + @feature('contentVisibility') contentVisibility; _user = null; From 6099a14082eb23a45f48a4d9803545f5efafc249 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Wed, 26 Jun 2024 16:05:51 +0700 Subject: [PATCH 017/131] Fixed flaky admin test in Publish+Send Flow (#20463) ref ONC-109 - Attempt to fix flaky Admin test, "Publish flow members enabled can schedule publish+send" - Adjusted the time calculation to the nearest minute to avoid off-by-one minute errors - Added `waitFor` to ensure elements are present and stable before making assertions. - Rounded the new scheduled date and time to the nearest minute to maintain consistency - Included extra `waitFor` and `settled` calls to allow time for UI elements to fully load and reflect changes before assertions. --- ghost/admin/tests/acceptance/editor/publish-flow-test.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ghost/admin/tests/acceptance/editor/publish-flow-test.js b/ghost/admin/tests/acceptance/editor/publish-flow-test.js index 255f6e92dd..8277dd63d6 100644 --- a/ghost/admin/tests/acceptance/editor/publish-flow-test.js +++ b/ghost/admin/tests/acceptance/editor/publish-flow-test.js @@ -1,6 +1,6 @@ import loginAsRole from '../../helpers/login-as-role'; import moment from 'moment-timezone'; -import {blur, click, fillIn, find, findAll} from '@ember/test-helpers'; +import {blur, click, fillIn, find, findAll, waitFor} from '@ember/test-helpers'; import {clickTrigger, removeMultipleOption, selectChoose} from 'ember-power-select/test-support/helpers'; import {disableMailgun, enableMailgun} from '../../helpers/mailgun'; import {disableMembers, enableMembers} from '../../helpers/members'; @@ -332,22 +332,24 @@ describe('Acceptance: Publish flow', function () { .text('Right now'); const siteTz = this.server.db.settings.findBy({key: 'timezone'}).value; - const plus10 = moment().tz(siteTz).add(10, 'minutes').set({}); + const plus10 = moment().tz(siteTz).add(10, 'minutes').startOf('minute'); await click('[data-test-setting="publish-at"] [data-test-setting-title]'); await click('[data-test-radio="schedule"]'); // date + time inputs are shown, defaults to now+5 mins + await waitFor('[data-test-setting="publish-at"] [data-test-date-time-picker-datepicker]'); expect(find('[data-test-setting="publish-at"] [data-test-date-time-picker-datepicker]'), 'datepicker').to.exist; expect(find('[data-test-setting="publish-at"] [data-test-date-time-picker-date-input]'), 'initial datepicker value') .to.have.value(plus10.format('YYYY-MM-DD')); + await waitFor('[data-test-setting="publish-at"] [data-test-date-time-picker-time-input]'); expect(find('[data-test-setting="publish-at"] [data-test-date-time-picker-time-input]'), 'time input').to.exist; expect(find('[data-test-setting="publish-at"] [data-test-date-time-picker-time-input]'), 'initial time input value') .to.have.value(plus10.format('HH:mm')); // can set a new date and time - const newDate = moment().tz(siteTz).add(4, 'days').add(5, 'hours').set('second', 0); + const newDate = moment().tz(siteTz).add(4, 'days').add(5, 'hours').startOf('minute'); await fillIn('[data-test-setting="publish-at"] [data-test-date-time-picker-date-input]', newDate.format('YYYY-MM-DD')); await blur('[data-test-setting="publish-at"] [data-test-date-time-picker-date-input]'); await fillIn('[data-test-setting="publish-at"] [data-test-date-time-picker-time-input]', newDate.format('HH:mm')); From 6c07b1cff9cbf044f9aa698f6d0fbf7985648b1d Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Wed, 26 Jun 2024 10:57:36 +0200 Subject: [PATCH 018/131] Fixed TypeError when editor is focussed when not loaded fix https://linear.app/tryghost/issue/SLO-162/typeerror-thiseditorapi-is-null - if the editor does not load for some reason (network issue), and the editor area is clicked, we throw an error because we don't protect against a null `editorAPI` - this adds that check --- ghost/admin/app/components/gh-koenig-editor-lexical.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ghost/admin/app/components/gh-koenig-editor-lexical.js b/ghost/admin/app/components/gh-koenig-editor-lexical.js index 8dcf8d6d35..2319ae9c8a 100644 --- a/ghost/admin/app/components/gh-koenig-editor-lexical.js +++ b/ghost/admin/app/components/gh-koenig-editor-lexical.js @@ -236,7 +236,7 @@ export default class GhKoenigEditorLexical extends Component { // otherwise the browser will defocus the editor and the cursor will disappear @action focusEditor(event) { - if (!this.skipFocusEditor && event.target.classList.contains('gh-koenig-editor-pane')) { + if (!this.skipFocusEditor && event.target.classList.contains('gh-koenig-editor-pane') && this.editorAPI) { let editorCanvas = this.editorAPI.editorInstance.getRootElement(); let {bottom} = editorCanvas.getBoundingClientRect(); From 5f5293cf6d71654cc2001d4a1a151dedac976498 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Wed, 26 Jun 2024 11:39:29 +0200 Subject: [PATCH 019/131] Excluded errors caused by browser power-saving settings fix https://linear.app/tryghost/issue/SLO-164/error-aborterror-the-play-request-was-interrupted-because-video-only - in the case that the browser has power-saving settings enabled, we get an error in Sentry - this error does not affect the user experience, so it should be safe to ignore - this adds an exclusion to Sentry to ignore these errors --- ghost/admin/app/utils/sentry.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ghost/admin/app/utils/sentry.js b/ghost/admin/app/utils/sentry.js index 1056046b2f..5bb84b3018 100644 --- a/ghost/admin/app/utils/sentry.js +++ b/ghost/admin/app/utils/sentry.js @@ -14,6 +14,11 @@ export function beforeSend(event, hint) { return null; } + // Ignore errors that are due to browser power saving settings + if (exception?.message?.includes('The play() request was interrupted because video-only background media was paused to save power.')) { + return null; + } + // if the error value includes a model id then overwrite it to improve grouping if (event.exception && event.exception.values && event.exception.values.length > 0) { const pattern = /<(post|page):[a-f0-9]+>/; From e34c36007e8264e5ee4b02e07ede886b43fafde1 Mon Sep 17 00:00:00 2001 From: Sanne de Vries <65487235+sanne-san@users.noreply.github.com> Date: Wed, 26 Jun 2024 12:02:25 +0200 Subject: [PATCH 020/131] Updated frontend styles for bookmark card (#20468) REF DES-263 - Added default white background color and sans-serif font to bookmark card --- ghost/core/core/frontend/src/cards/css/bookmark.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ghost/core/core/frontend/src/cards/css/bookmark.css b/ghost/core/core/frontend/src/cards/css/bookmark.css index e14af83b01..d04d74902c 100644 --- a/ghost/core/core/frontend/src/cards/css/bookmark.css +++ b/ghost/core/core/frontend/src/cards/css/bookmark.css @@ -12,6 +12,7 @@ .kg-bookmark-card a.kg-bookmark-container, .kg-bookmark-card a.kg-bookmark-container:hover { display: flex; + background: #fff; text-decoration: none; border-radius: 6px; border: 1px solid rgb(124 139 154 / 25%); @@ -28,6 +29,7 @@ justify-content: flex-start; padding: 20px; overflow: hidden; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; } .kg-bookmark-title { From dd39576de0b96e9b321e776b82b6aad78827ef72 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Wed, 26 Jun 2024 12:36:32 +0200 Subject: [PATCH 021/131] Added more errors to Sentry exclusion list fix https://linear.app/tryghost/issue/SLO-165/add-more-errors-to-allowlist - we don't want to capture Sentry errors for these because they are out of our control (like the user's internet connection dropping out) --- ghost/admin/app/routes/application.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ghost/admin/app/routes/application.js b/ghost/admin/app/routes/application.js index 0416014779..21054a4df1 100644 --- a/ghost/admin/app/routes/application.js +++ b/ghost/admin/app/routes/application.js @@ -185,6 +185,11 @@ export default Route.extend(ShortcutsRoute, { release: `ghost@${this.config.version}`, beforeSend, ignoreErrors: [ + // Network errors that we don't control + /Server was unreachable/, + /NetworkError when attempting to fetch resource./, + /Failed to fetch/, + /Load failed/, // TransitionAborted errors surface from normal application behaviour // - https://github.com/emberjs/ember.js/issues/12505 /^TransitionAborted$/, From 019f417c7d14c150fce40cb4380e9733b92af7be Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Wed, 26 Jun 2024 12:37:46 +0200 Subject: [PATCH 022/131] Moved error exclusion to correct place - adding it to ignoreErrors is better than beforeSend because it's built for this purpose and we've just looking at the error message --- ghost/admin/app/routes/application.js | 3 +++ ghost/admin/app/utils/sentry.js | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ghost/admin/app/routes/application.js b/ghost/admin/app/routes/application.js index 21054a4df1..706d1325b7 100644 --- a/ghost/admin/app/routes/application.js +++ b/ghost/admin/app/routes/application.js @@ -185,6 +185,9 @@ export default Route.extend(ShortcutsRoute, { release: `ghost@${this.config.version}`, beforeSend, ignoreErrors: [ + // Browser autoplay policies + /The play() request was interrupted because video-only background media was paused to save power./, + // Network errors that we don't control /Server was unreachable/, /NetworkError when attempting to fetch resource./, diff --git a/ghost/admin/app/utils/sentry.js b/ghost/admin/app/utils/sentry.js index 5bb84b3018..1056046b2f 100644 --- a/ghost/admin/app/utils/sentry.js +++ b/ghost/admin/app/utils/sentry.js @@ -14,11 +14,6 @@ export function beforeSend(event, hint) { return null; } - // Ignore errors that are due to browser power saving settings - if (exception?.message?.includes('The play() request was interrupted because video-only background media was paused to save power.')) { - return null; - } - // if the error value includes a model id then overwrite it to improve grouping if (event.exception && event.exception.values && event.exception.values.length > 0) { const pattern = /<(post|page):[a-f0-9]+>/; From 43bb83f7bba48764bec11bef398a179d766870f7 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Wed, 26 Jun 2024 14:20:48 +0200 Subject: [PATCH 023/131] Extracted stats aggregation function to util ref https://linear.app/tryghost/issue/SLO-168/rangeerror-maximum-call-stack-size-exceeded - this extracts a function to a util so we can unit test it - this function is about to be optimized but having unit tests allows us to make the change with confidence --- ghost/admin/app/services/dashboard-stats.js | 38 +--- ghost/admin/app/utils/merge-stats-by-date.js | 31 +++ .../unit/utils/merge-stats-by-date-test.js | 184 ++++++++++++++++++ 3 files changed, 219 insertions(+), 34 deletions(-) create mode 100644 ghost/admin/app/utils/merge-stats-by-date.js create mode 100644 ghost/admin/tests/unit/utils/merge-stats-by-date-test.js diff --git a/ghost/admin/app/services/dashboard-stats.js b/ghost/admin/app/services/dashboard-stats.js index e23b17b51a..0fc19af58b 100644 --- a/ghost/admin/app/services/dashboard-stats.js +++ b/ghost/admin/app/services/dashboard-stats.js @@ -3,6 +3,8 @@ import moment from 'moment-timezone'; import {task} from 'ember-concurrency'; import {tracked} from '@glimmer/tracking'; +import mergeStatsByDate from 'ghost-admin/utils/merge-stats-by-date'; + /** * @typedef MrrStat * @type {Object} @@ -461,39 +463,7 @@ export default class DashboardStatsService extends Service { } } - function mergeDates(list, entry) { - const [current, ...rest] = list; - - if (!current) { - return entry ? [entry] : []; - } - - if (!entry) { - return mergeDates(rest, { - date: current.date, - count: current.count, - positiveDelta: current.positive_delta, - negativeDelta: current.negative_delta, - signups: current.signups, - cancellations: current.cancellations - }); - } - - if (current.date === entry.date) { - return mergeDates(rest, { - date: entry.date, - count: entry.count + current.count, - positiveDelta: entry.positiveDelta + current.positive_delta, - negativeDelta: entry.negativeDelta + current.negative_delta, - signups: entry.signups + current.signups, - cancellations: entry.cancellations + current.cancellations - }); - } - - return [entry].concat(mergeDates(list)); - } - - const subscriptionCountStats = mergeDates(result.stats); + const subscriptionCountStats = mergeStatsByDate(result.stats); this.paidMembersByCadence = paidMembersByCadence; this.paidMembersByTier = paidMembersByTier; @@ -524,7 +494,7 @@ export default class DashboardStatsService extends Service { this.memberCountStats = this.dashboardMocks.memberCountStats; return; } - + const stats = yield this.membersStats.fetchMemberCount(); this.memberCountStats = stats.stats.map((d) => { return { diff --git a/ghost/admin/app/utils/merge-stats-by-date.js b/ghost/admin/app/utils/merge-stats-by-date.js new file mode 100644 index 0000000000..797c4f0d9e --- /dev/null +++ b/ghost/admin/app/utils/merge-stats-by-date.js @@ -0,0 +1,31 @@ +export default function mergeDates(list, entry) { + const [current, ...rest] = list; + + if (!current) { + return entry ? [entry] : []; + } + + if (!entry) { + return mergeDates(rest, { + date: current.date, + count: current.count, + positiveDelta: current.positive_delta, + negativeDelta: current.negative_delta, + signups: current.signups, + cancellations: current.cancellations + }); + } + + if (current.date === entry.date) { + return mergeDates(rest, { + date: entry.date, + count: entry.count + current.count, + positiveDelta: entry.positiveDelta + current.positive_delta, + negativeDelta: entry.negativeDelta + current.negative_delta, + signups: entry.signups + current.signups, + cancellations: entry.cancellations + current.cancellations + }); + } + + return [entry].concat(mergeDates(list)); +} diff --git a/ghost/admin/tests/unit/utils/merge-stats-by-date-test.js b/ghost/admin/tests/unit/utils/merge-stats-by-date-test.js new file mode 100644 index 0000000000..62480d478a --- /dev/null +++ b/ghost/admin/tests/unit/utils/merge-stats-by-date-test.js @@ -0,0 +1,184 @@ +import mergeStatsByDate from 'ghost-admin/utils/merge-stats-by-date'; +import {describe, it} from 'mocha'; +import {expect} from 'chai'; + +const STATS_DATA = [ + { + date: '2024-06-22', + tier: '111111111111111111111111', + cadence: 'month', + positive_delta: 0, + negative_delta: 0, + signups: 0, + cancellations: 0, + count: 456 + }, + { + date: '2024-06-22', + tier: '111111111111111111111111', + cadence: 'year', + positive_delta: 1, + negative_delta: 1, + signups: 0, + cancellations: 0, + count: 1354 + }, + { + date: '2024-06-23', + tier: '111111111111111111111111', + cadence: 'month', + positive_delta: 0, + negative_delta: 0, + signups: 0, + cancellations: 0, + count: 456 + }, + { + date: '2024-06-23', + tier: '111111111111111111111111', + cadence: 'year', + positive_delta: 1, + negative_delta: 1, + signups: 0, + cancellations: 0, + count: 1354 + }, + { + date: '2024-06-23', + tier: '111111111111111111111113', + cadence: 'year', + positive_delta: 0, + negative_delta: 0, + signups: 0, + cancellations: 0, + count: 400 + }, + { + date: '2024-06-24', + tier: '111111111111111111111111', + cadence: 'year', + positive_delta: 3, + negative_delta: 2, + signups: 1, + cancellations: 0, + count: 1355 + }, + { + date: '2024-06-24', + tier: '111111111111111111111113', + cadence: 'year', + positive_delta: 2, + negative_delta: 1, + signups: 2, + cancellations: 1, + count: 401 + }, + { + date: '2024-06-24', + tier: '111111111111111111111112', + cadence: 'year', + positive_delta: 1, + negative_delta: 0, + signups: 1, + cancellations: 0, + count: 55 + }, + { + date: '2024-06-25', + tier: '111111111111111111111111', + cadence: 'month', + positive_delta: 0, + negative_delta: 1, + signups: 0, + cancellations: 1, + count: 455 + }, + { + date: '2024-06-25', + tier: '111111111111111111111111', + cadence: 'year', + positive_delta: 2, + negative_delta: 5, + signups: 1, + cancellations: 4, + count: 1352 + }, + { + date: '2024-06-25', + tier: '111111111111111111111113', + cadence: 'year', + positive_delta: 1, + negative_delta: 2, + signups: 1, + cancellations: 2, + count: 400 + }, + { + date: '2024-06-26', + tier: '111111111111111111111111', + cadence: 'year', + positive_delta: 2, + negative_delta: 2, + signups: 0, + cancellations: 0, + count: 1352 + }, + { + date: '2024-06-26', + tier: '111111111111111111111113', + cadence: 'year', + positive_delta: 0, + negative_delta: 0, + signups: 0, + cancellations: 0, + count: 400 + } +]; + +describe('mergeStatsByDate', function () { + it('merges stats as expected', function () { + const result = mergeStatsByDate(STATS_DATA); + expect(result).to.deep.equal([ + { + date: '2024-06-22', + count: 1810, + positiveDelta: 1, + negativeDelta: 1, + signups: 0, + cancellations: 0 + }, + { + date: '2024-06-23', + count: 2210, + positiveDelta: 1, + negativeDelta: 1, + signups: 0, + cancellations: 0 + }, + { + date: '2024-06-24', + count: 1811, + positiveDelta: 6, + negativeDelta: 3, + signups: 4, + cancellations: 1 + }, + { + date: '2024-06-25', + count: 2207, + positiveDelta: 3, + negativeDelta: 8, + signups: 2, + cancellations: 7 + }, + { + date: '2024-06-26', + count: 1752, + positiveDelta: 2, + negativeDelta: 2, + signups: 0, + cancellations: 0 + } + ]); + }); +}); From f250898a3b66948294e54ce52b71769e9d4a427b Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Wed, 26 Jun 2024 15:01:50 +0200 Subject: [PATCH 024/131] Optimized stats aggregation code for Admin dashboard fix https://linear.app/tryghost/issue/SLO-168/rangeerror-maximum-call-stack-size-exceeded - this code takes the API output and reduces it down to collect together stats per date - the current code is recursive, and we've seen errors with the recursion hitting a `RangeError: Maximum call stack size exceeded` error - as well as that, we're doing a lot of array concat'ing and cloning, which burns memory and CPU time - instead, we can just use `.reduce` - the new implementation is much faster than the existing one (1ms vs 85ms) and uses no recursion, so those errors should go away - I've also verified that the output is the same between the two functions --- ghost/admin/app/utils/merge-stats-by-date.js | 47 +++++++++----------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/ghost/admin/app/utils/merge-stats-by-date.js b/ghost/admin/app/utils/merge-stats-by-date.js index 797c4f0d9e..bdd881f897 100644 --- a/ghost/admin/app/utils/merge-stats-by-date.js +++ b/ghost/admin/app/utils/merge-stats-by-date.js @@ -1,31 +1,26 @@ -export default function mergeDates(list, entry) { - const [current, ...rest] = list; +export default function mergeStatsByDate(list) { + const reducedStatsByDate = list.reduce((acc, current) => { + const currentDate = current.date; - if (!current) { - return entry ? [entry] : []; - } + if (!acc[currentDate]) { + acc[currentDate] = { + date: currentDate, + count: 0, + positiveDelta: 0, + negativeDelta: 0, + signups: 0, + cancellations: 0 + }; + } - if (!entry) { - return mergeDates(rest, { - date: current.date, - count: current.count, - positiveDelta: current.positive_delta, - negativeDelta: current.negative_delta, - signups: current.signups, - cancellations: current.cancellations - }); - } + acc[currentDate].count += current.count; + acc[currentDate].positiveDelta += current.positive_delta; + acc[currentDate].negativeDelta += current.negative_delta; + acc[currentDate].signups += current.signups; + acc[currentDate].cancellations += current.cancellations; - if (current.date === entry.date) { - return mergeDates(rest, { - date: entry.date, - count: entry.count + current.count, - positiveDelta: entry.positiveDelta + current.positive_delta, - negativeDelta: entry.negativeDelta + current.negative_delta, - signups: entry.signups + current.signups, - cancellations: entry.cancellations + current.cancellations - }); - } + return acc; + }, {}); - return [entry].concat(mergeDates(list)); + return Object.values(reducedStatsByDate); } From dfc27b02c8bd5e50859f7d617f52739eed100e8f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:48:17 +0100 Subject: [PATCH 025/131] Update Koenig packages (#20453) closes https://linear.app/tryghost/issue/MOM-247 - includes a few fixes for errors we've seen in our reporting --- ghost/admin/package.json | 2 +- ghost/core/package.json | 6 ++--- yarn.lock | 50 ++++++++++++++++++++-------------------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/ghost/admin/package.json b/ghost/admin/package.json index b3f3eb3883..80965aab09 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -48,7 +48,7 @@ "@tryghost/helpers": "1.1.90", "@tryghost/kg-clean-basic-html": "4.1.1", "@tryghost/kg-converters": "1.0.5", - "@tryghost/koenig-lexical": "1.3.0", + "@tryghost/koenig-lexical": "1.3.1", "@tryghost/limit-service": "1.2.14", "@tryghost/members-csv": "0.0.0", "@tryghost/nql": "0.12.3", diff --git a/ghost/core/package.json b/ghost/core/package.json index e8291f2c40..5d0a334bea 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -111,9 +111,9 @@ "@tryghost/kg-converters": "1.0.5", "@tryghost/kg-default-atoms": "5.0.3", "@tryghost/kg-default-cards": "10.0.6", - "@tryghost/kg-default-nodes": "1.1.3", - "@tryghost/kg-html-to-lexical": "1.1.4", - "@tryghost/kg-lexical-html-renderer": "1.1.4", + "@tryghost/kg-default-nodes": "1.1.4", + "@tryghost/kg-html-to-lexical": "1.1.5", + "@tryghost/kg-lexical-html-renderer": "1.1.5", "@tryghost/kg-mobiledoc-html-renderer": "7.0.4", "@tryghost/limit-service": "1.2.14", "@tryghost/link-redirects": "0.0.0", diff --git a/yarn.lock b/yarn.lock index 256fa3b64f..b7f95baacb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7835,10 +7835,10 @@ lodash "^4.17.21" luxon "^3.0.0" -"@tryghost/kg-default-nodes@1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@tryghost/kg-default-nodes/-/kg-default-nodes-1.1.3.tgz#4e9969562e676c1dd3c7a364564db14ff9c7d445" - integrity sha512-M/j8lM2pvd22utBDOF1pORB7vcDUiZr9rtZkz74ZATKnw7Lsz1FeZIkoOfOglgpUriBxR+HpJ0p3YDcCNy3Mhw== +"@tryghost/kg-default-nodes@1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@tryghost/kg-default-nodes/-/kg-default-nodes-1.1.4.tgz#4a057903ce5529fa64c1b0901f520b6086e2a0ae" + integrity sha512-GdGon7HU+L/kla0DlavKh6MBQCnaNMSVaeMEasGir5JAc/NcVw7RB5vqg0OCLHpEsJBPoLcaOF0s/DPKc6xFvg== dependencies: "@lexical/clipboard" "0.13.1" "@lexical/rich-text" "0.13.1" @@ -7852,21 +7852,21 @@ lodash "^4.17.21" luxon "^3.3.0" -"@tryghost/kg-default-transforms@1.1.4": - version "1.1.4" - resolved "https://registry.yarnpkg.com/@tryghost/kg-default-transforms/-/kg-default-transforms-1.1.4.tgz#cebaa8f1acc0d6efe57da3321950e70579a8069e" - integrity sha512-GShtUY+5NtWASdl/r0apyVovpq/JWzdAlEO0qeGt6rp/ILTYfycibHHgkwfjVVSG4b3XyVBqk0g84BEEmfYFBw== +"@tryghost/kg-default-transforms@1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@tryghost/kg-default-transforms/-/kg-default-transforms-1.1.5.tgz#7ea7914ecc5090edf69ee780e5ba4ee905cde890" + integrity sha512-LfZj5MODOoyoZlQqHeDzO0orTgaVUO1ivNbPWG/PSVBm+9i+QKtVXrg9K7L0JV084UJE6AFIdBNJM/4gCj+QKA== dependencies: "@lexical/list" "0.13.1" "@lexical/rich-text" "0.13.1" "@lexical/utils" "0.13.1" - "@tryghost/kg-default-nodes" "1.1.3" + "@tryghost/kg-default-nodes" "1.1.4" lexical "0.13.1" -"@tryghost/kg-html-to-lexical@1.1.4": - version "1.1.4" - resolved "https://registry.yarnpkg.com/@tryghost/kg-html-to-lexical/-/kg-html-to-lexical-1.1.4.tgz#24c4cef9be31277b52d77b61dd8d81af40874e5c" - integrity sha512-6Cc7RXNW0kmXu6HuY9HqJfGvIkyovcj2m3EoVtW/qB1t0bamtQvMk6aT9caohpQb7tOUMcENf72hRZ/2fdITYA== +"@tryghost/kg-html-to-lexical@1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@tryghost/kg-html-to-lexical/-/kg-html-to-lexical-1.1.5.tgz#8c502a1ee396a05c3d351150e91aaa7467a9ddda" + integrity sha512-JSv5Mw7Jrfa0ao3GgITvo2z6hPpJ0PASLcDmPXxYEbMjXBWOFhzFRi70ON1jmFEFKlBAwKS2eY5eyQvyc5jH0w== dependencies: "@lexical/clipboard" "0.13.1" "@lexical/headless" "0.13.1" @@ -7874,15 +7874,15 @@ "@lexical/link" "0.13.1" "@lexical/list" "0.13.1" "@lexical/rich-text" "0.13.1" - "@tryghost/kg-default-nodes" "1.1.3" - "@tryghost/kg-default-transforms" "1.1.4" + "@tryghost/kg-default-nodes" "1.1.4" + "@tryghost/kg-default-transforms" "1.1.5" jsdom "^24.0.0" lexical "0.13.1" -"@tryghost/kg-lexical-html-renderer@1.1.4": - version "1.1.4" - resolved "https://registry.yarnpkg.com/@tryghost/kg-lexical-html-renderer/-/kg-lexical-html-renderer-1.1.4.tgz#6c516d7c9e173f48f28d103b0ab66ac008f4a481" - integrity sha512-4wVeGnl0HoIz8k0S7v1YIZWcUcZxC/vHnwN54FMa0bZeHusg78d4cLeZa7ky1kpC3P424a9Ujd1wKebfqDnKBw== +"@tryghost/kg-lexical-html-renderer@1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@tryghost/kg-lexical-html-renderer/-/kg-lexical-html-renderer-1.1.5.tgz#9163694889d2dd704bf9efc07304a46b6a386677" + integrity sha512-RNP7MKs1orWRsFSb5Cy0KYEF5ExQ8MQ0nKTW3jXiEnYRYVI1ClgUm2j6P9vm5uibF/GWlCM8H4JbTc/Bnl58mw== dependencies: "@lexical/clipboard" "0.13.1" "@lexical/code" "0.13.1" @@ -7890,8 +7890,8 @@ "@lexical/link" "0.13.1" "@lexical/list" "0.13.1" "@lexical/rich-text" "0.13.1" - "@tryghost/kg-default-nodes" "1.1.3" - "@tryghost/kg-default-transforms" "1.1.4" + "@tryghost/kg-default-nodes" "1.1.4" + "@tryghost/kg-default-transforms" "1.1.5" jsdom "^24.0.0" lexical "0.13.1" prettier "3.2.5" @@ -7939,10 +7939,10 @@ dependencies: semver "^7.3.5" -"@tryghost/koenig-lexical@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.3.0.tgz#03bfaa55a5c2da64b2ddfb4c1ee271a62dc4a11d" - integrity sha512-qeKenjk3fRDPjxzqDqIDDAWVZTTKfk8JPMxyPjUzyUilWFavztRfxuo+QHqsjTGAp+TnaBTWADoq2WHbgKSA+A== +"@tryghost/koenig-lexical@1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.3.1.tgz#27a49c796f2e892f6a7c5d114985ef2fafb7f3d2" + integrity sha512-j4+LefCJv4aUmM4207q/BsCHfNUE6yHPPzXCaxjMX0dCJLwURRpCbJ67CPV4+KGH8PJe4eHzktX0zJV/A9iHGg== "@tryghost/limit-service@1.2.14": version "1.2.14" From 2e593ebceea5309f654c0b780e0b8414a6e5ff7b Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Wed, 26 Jun 2024 16:29:02 -0500 Subject: [PATCH 026/131] Improved performance fetching posts (#20460) ref https://linear.app/tryghost/issue/ONC-111 - added composite index to posts_tags for post_id,tag_id for faster lookup - added composite index to posts for updated_at; this is commonly used by get helpers on the front end to display data like the latest posts In testing, this provided a very dramatic improvement for simple get helper requests like 'filter="id:-{{post.id}}+tag:sampleTag" limit="3"' which are by default sorted by updated_at desc. I'm not entirely clear why when sorting by published_at we do not need a composite index - so far it doesn't seem to be necessary. This should cover the primary cases for get helpers - the latest posts with a given tag or set of tags. --- ...-12-08-20-add-posts-tags-post-tag-index.js | 26 +++++++++++++++++++ ...add-posts-type-status-updated-at-index.js} | 2 +- ghost/core/core/server/data/schema/schema.js | 7 +++-- .../unit/server/data/schema/integrity.test.js | 2 +- 4 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 ghost/core/core/server/data/migrations/versions/5.87/2024-06-25-12-08-20-add-posts-tags-post-tag-index.js rename ghost/core/core/server/data/migrations/versions/5.87/{2024-06-20-17-02-15-add-posts-type-status-index.js => 2024-06-25-12-08-45-add-posts-type-status-updated-at-index.js} (93%) diff --git a/ghost/core/core/server/data/migrations/versions/5.87/2024-06-25-12-08-20-add-posts-tags-post-tag-index.js b/ghost/core/core/server/data/migrations/versions/5.87/2024-06-25-12-08-20-add-posts-tags-post-tag-index.js new file mode 100644 index 0000000000..4aa38673db --- /dev/null +++ b/ghost/core/core/server/data/migrations/versions/5.87/2024-06-25-12-08-20-add-posts-tags-post-tag-index.js @@ -0,0 +1,26 @@ +const logging = require('@tryghost/logging'); +const {createNonTransactionalMigration} = require('../../utils'); +const {addIndex, dropIndex} = require('../../../schema/commands'); + +module.exports = createNonTransactionalMigration( + async function up(knex) { + await addIndex('posts_tags', ['post_id', 'tag_id'], knex); + }, + async function down(knex) { + try { + await dropIndex('posts_tags', ['post_id', 'tag_id'], knex); + } catch (err) { + if (err.code === 'ER_DROP_INDEX_FK') { + logging.error({ + message: 'Error dropping index over posts_tags(post_id, tag_id), re-adding index for post_id' + }); + + await addIndex('posts_tags', ['post_id'], knex); + await dropIndex('posts_tags', ['post_id', 'tag_id'], knex); + return; + } + + throw err; + } + } +); \ No newline at end of file diff --git a/ghost/core/core/server/data/migrations/versions/5.87/2024-06-20-17-02-15-add-posts-type-status-index.js b/ghost/core/core/server/data/migrations/versions/5.87/2024-06-25-12-08-45-add-posts-type-status-updated-at-index.js similarity index 93% rename from ghost/core/core/server/data/migrations/versions/5.87/2024-06-20-17-02-15-add-posts-type-status-index.js rename to ghost/core/core/server/data/migrations/versions/5.87/2024-06-25-12-08-45-add-posts-type-status-updated-at-index.js index 375725294f..2415fe1d44 100644 --- a/ghost/core/core/server/data/migrations/versions/5.87/2024-06-20-17-02-15-add-posts-type-status-index.js +++ b/ghost/core/core/server/data/migrations/versions/5.87/2024-06-25-12-08-45-add-posts-type-status-updated-at-index.js @@ -1,4 +1,4 @@ // For information on writing migrations, see https://www.notion.so/ghost/Database-migrations-eb5b78c435d741d2b34a582d57c24253 const {createAddIndexMigration} = require('../../utils'); -module.exports = createAddIndexMigration('posts',['type','status']); \ No newline at end of file +module.exports = createAddIndexMigration('posts',['type','status','updated_at']); \ No newline at end of file diff --git a/ghost/core/core/server/data/schema/schema.js b/ghost/core/core/server/data/schema/schema.js index d63f74febf..f2e7052f8c 100644 --- a/ghost/core/core/server/data/schema/schema.js +++ b/ghost/core/core/server/data/schema/schema.js @@ -94,7 +94,7 @@ module.exports = { newsletter_id: {type: 'string', maxlength: 24, nullable: true, references: 'newsletters.id'}, show_title_and_feature_image: {type: 'boolean', nullable: false, defaultTo: true}, '@@INDEXES@@': [ - ['type','status'] + ['type','status','updated_at'] ], '@@UNIQUE_CONSTRAINTS@@': [ ['slug', 'type'] @@ -298,7 +298,10 @@ module.exports = { id: {type: 'string', maxlength: 24, nullable: false, primary: true}, post_id: {type: 'string', maxlength: 24, nullable: false, references: 'posts.id'}, tag_id: {type: 'string', maxlength: 24, nullable: false, references: 'tags.id'}, - sort_order: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0} + sort_order: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0}, + '@@INDEXES@@': [ + ['post_id','tag_id'] + ] }, invites: { id: {type: 'string', maxlength: 24, nullable: false, primary: true}, diff --git a/ghost/core/test/unit/server/data/schema/integrity.test.js b/ghost/core/test/unit/server/data/schema/integrity.test.js index 9f6feb5d85..9e8ea29125 100644 --- a/ghost/core/test/unit/server/data/schema/integrity.test.js +++ b/ghost/core/test/unit/server/data/schema/integrity.test.js @@ -35,7 +35,7 @@ const validateRouteSettings = require('../../../../../core/server/services/route */ describe('DB version integrity', function () { // Only these variables should need updating - const currentSchemaHash = '45c8072332176e0fe5a4fdff58fb2113'; + const currentSchemaHash = 'ce97eff9bf1b3c215fed1271f9275f83'; const currentFixturesHash = 'a489d615989eab1023d4b8af0ecee7fd'; const currentSettingsHash = '5c957ceb48c4878767d7d3db484c592d'; const currentRoutesHash = '3d180d52c663d173a6be791ef411ed01'; From 0cf3d4d3d5d9742e11b7bb0e2dc14e15976b8e2f Mon Sep 17 00:00:00 2001 From: Fabien 'egg' O'Carroll Date: Thu, 27 Jun 2024 11:27:25 +0700 Subject: [PATCH 027/131] Updated ActivityPub API root path (#20471) ref https://linear.app/tryghost/issue/MOM-201 We will be proxying to `/.ghost/activitypub` rather than just `/activitypub` --- apps/admin-x-framework/src/utils/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/admin-x-framework/src/utils/helpers.ts b/apps/admin-x-framework/src/utils/helpers.ts index f3aaf6c1e3..1207ff12c6 100644 --- a/apps/admin-x-framework/src/utils/helpers.ts +++ b/apps/admin-x-framework/src/utils/helpers.ts @@ -12,7 +12,7 @@ export function getGhostPaths(): IGhostPaths { const adminRoot = `${subdir}/ghost/`; const assetRoot = `${subdir}/ghost/assets/`; const apiRoot = `${subdir}/ghost/api/admin`; - const activityPubRoot = `${subdir}/activitypub`; + const activityPubRoot = `${subdir}/.ghost/activitypub`; return {subdir, adminRoot, assetRoot, apiRoot, activityPubRoot}; } From aa0110c842167a93b15a74b988c97b617a182bbd Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Thu, 27 Jun 2024 08:52:27 +0200 Subject: [PATCH 028/131] Adjusted Sentry ignore list to cover more browser play errors fix https://linear.app/tryghost/issue/SLO-172/error-aborterror-the-play-request-was-interrupted-because-the-media - there are a few error messages we can ignore here, as browsers output slightly different messages for various types of these errors, which don't affect the user --- ghost/admin/app/routes/application.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ghost/admin/app/routes/application.js b/ghost/admin/app/routes/application.js index 706d1325b7..6c05f8be48 100644 --- a/ghost/admin/app/routes/application.js +++ b/ghost/admin/app/routes/application.js @@ -185,8 +185,8 @@ export default Route.extend(ShortcutsRoute, { release: `ghost@${this.config.version}`, beforeSend, ignoreErrors: [ - // Browser autoplay policies - /The play() request was interrupted because video-only background media was paused to save power./, + // Browser autoplay policies (this regex covers a few) + /The play() request was interrupted/, // Network errors that we don't control /Server was unreachable/, From ecf52d468513a8ccceab110ed4a6f1bff334f2f6 Mon Sep 17 00:00:00 2001 From: Michael Barrett Date: Thu, 27 Jun 2024 09:30:07 +0100 Subject: [PATCH 029/131] Removed request queue enablement flag (#20466) refs [CFR-26](https://linear.app/tryghost/issue/CFR-26/remove-request-queue-config-flag) Removed request queue enablement flag and updated the logic so that the request queue is enabled when there is explicit configuration for it. --- ghost/core/core/server/web/parent/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ghost/core/core/server/web/parent/app.js b/ghost/core/core/server/web/parent/app.js index bbc698966a..289eb44eac 100644 --- a/ghost/core/core/server/web/parent/app.js +++ b/ghost/core/core/server/web/parent/app.js @@ -29,7 +29,7 @@ module.exports = function setupParentApp() { // Enable request queuing if configured const queueConfig = config.get('optimization:requestQueue'); - if (queueConfig?.enabled === true) { + if (queueConfig) { parentApp.use(mw.queueRequest(queueConfig)); } From ad77cec0082660af727502f2c46c9110baab044a Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Thu, 27 Jun 2024 10:16:34 +0200 Subject: [PATCH 030/131] Removed unused type field - this is no longer used and causes a React warning because we end up passing it to a textarea --- apps/admin-x-design-system/src/global/form/TextArea.tsx | 1 - .../settings/growth/embedSignup/EmbedSignupSidebar.tsx | 1 - .../growth/recommendations/RecommendationDescriptionForm.tsx | 1 - .../settings/membership/stripe/StripeConnectModal.tsx | 2 +- 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/admin-x-design-system/src/global/form/TextArea.tsx b/apps/admin-x-design-system/src/global/form/TextArea.tsx index 6a3c1e3b66..5bda018ca5 100644 --- a/apps/admin-x-design-system/src/global/form/TextArea.tsx +++ b/apps/admin-x-design-system/src/global/form/TextArea.tsx @@ -18,7 +18,6 @@ export interface TextAreaProps extends HTMLProps { error?: boolean; placeholder?: string; hint?: React.ReactNode; - clearBg?: boolean; fontStyle?: FontStyles; className?: string; onChange?: (event: React.ChangeEvent) => void; diff --git a/apps/admin-x-settings/src/components/settings/growth/embedSignup/EmbedSignupSidebar.tsx b/apps/admin-x-settings/src/components/settings/growth/embedSignup/EmbedSignupSidebar.tsx index 508d284974..2c9945f889 100644 --- a/apps/admin-x-settings/src/components/settings/growth/embedSignup/EmbedSignupSidebar.tsx +++ b/apps/admin-x-settings/src/components/settings/growth/embedSignup/EmbedSignupSidebar.tsx @@ -129,7 +129,6 @@ const EmbedSignupSidebar: React.FC = ({selectedLayout, /> + {submitEnabled &&
    ); From f9a6610823e9a7f8cde9a7c49327cb57b956d1a7 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Thu, 27 Jun 2024 11:51:15 +0200 Subject: [PATCH 031/131] Added AbortError to list of excluded errors fix https://linear.app/tryghost/issue/SLO-175/error-aborterror-the-operation-was-aborted - this error can occur when a user's browser navigates away mid-request, which causes the request to be aborted. However, we don't control this, nor do we particularly care, so we can just ignore it --- ghost/admin/app/routes/application.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ghost/admin/app/routes/application.js b/ghost/admin/app/routes/application.js index 6c05f8be48..5f7684c8f9 100644 --- a/ghost/admin/app/routes/application.js +++ b/ghost/admin/app/routes/application.js @@ -193,6 +193,8 @@ export default Route.extend(ShortcutsRoute, { /NetworkError when attempting to fetch resource./, /Failed to fetch/, /Load failed/, + /The operation was aborted./, + // TransitionAborted errors surface from normal application behaviour // - https://github.com/emberjs/ember.js/issues/12505 /^TransitionAborted$/, From 7bffe5b79a812dbb459f08319b901c49923aff44 Mon Sep 17 00:00:00 2001 From: Princi Vershwal Date: Thu, 27 Jun 2024 17:35:19 +0530 Subject: [PATCH 032/131] Added option param to skip distinct from count query for members API ref https://linear.app/tryghost/issue/SLO-173/removed-distinct-from-member-count-query Performance of GET /members API can be improved by dropping the distinct from the total members count query. select count(distinct members.id) as aggregate from `members`; // 275ms select count(*) as aggregate from `members`; // 30ms In this case we know that the result set will always be unique. --- .../core/server/models/base/plugins/crud.js | 5 + ghost/core/package.json | 2 +- .../lib/services/MemberBREADService.js | 3 + yarn.lock | 161 ++++++++++-------- 4 files changed, 101 insertions(+), 70 deletions(-) diff --git a/ghost/core/core/server/models/base/plugins/crud.js b/ghost/core/core/server/models/base/plugins/crud.js index 614d5b8dfc..46ab3b1b34 100644 --- a/ghost/core/core/server/models/base/plugins/crud.js +++ b/ghost/core/core/server/models/base/plugins/crud.js @@ -120,6 +120,11 @@ module.exports = function (Bookshelf) { }); } + //option param to skip distinct from count query, distinct adds a lot of latency and in this case the result set will always be unique. + if (unfilteredOptions.useBasicCount) { + options.useBasicCount = unfilteredOptions.useBasicCount; + } + const response = await itemCollection.fetchPage(options); // Attributes are being filtered here, so they are not leaked into calling layer // where models are serialized to json and do not do more filtering. diff --git a/ghost/core/package.json b/ghost/core/package.json index 5d0a334bea..fe4a7aebc7 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -76,7 +76,7 @@ "@tryghost/api-framework": "0.0.0", "@tryghost/api-version-compatibility-service": "0.0.0", "@tryghost/audience-feedback": "0.0.0", - "@tryghost/bookshelf-plugins": "0.6.17", + "@tryghost/bookshelf-plugins": "0.6.19", "@tryghost/bootstrap-socket": "0.0.0", "@tryghost/collections": "0.0.0", "@tryghost/color-utils": "0.2.2", diff --git a/ghost/members-api/lib/services/MemberBREADService.js b/ghost/members-api/lib/services/MemberBREADService.js index 51292b6297..66faef1a94 100644 --- a/ghost/members-api/lib/services/MemberBREADService.js +++ b/ghost/members-api/lib/services/MemberBREADService.js @@ -393,6 +393,9 @@ module.exports = class MemberBREADService { withRelated.add('email_recipients.email'); } + //option param to skip distinct from count query, distinct adds a lot of latency and in this case the result set will always be unique. + options.useBasicCount = true; + const page = await this.memberRepository.list({ ...options, withRelated: Array.from(withRelated) diff --git a/yarn.lock b/yarn.lock index b7f95baacb..8a742d8cbb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7556,95 +7556,95 @@ ajv "^6.12.6" lodash "^4.17.11" -"@tryghost/bookshelf-collision@^0.1.42": - version "0.1.42" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-collision/-/bookshelf-collision-0.1.42.tgz#a00db529828b4288381fc882f26ef9e891c178a2" - integrity sha512-WCm7xgHR1HQ9NrJ4AGzOo54dfyztGJBKnKoQK7b3fyRwOXr04e1REGhjCkrDAOnXv0TEKUK4y+loF5d96gKWkg== +"@tryghost/bookshelf-collision@^0.1.43": + version "0.1.43" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-collision/-/bookshelf-collision-0.1.43.tgz#6491d47c642565735b0a0cbec65925862a7b88ee" + integrity sha512-RPe5ZpkkETlAmQxuVUKZ6LstYokfb1+mJCt6NMYFIcrXAPW1WSS7l11rCBs7WcuhvVKc6yPD+fwD4MmHuVtJ0w== dependencies: - "@tryghost/errors" "^1.3.2" + "@tryghost/errors" "^1.3.3" lodash "^4.17.21" moment-timezone "^0.5.33" -"@tryghost/bookshelf-custom-query@^0.1.25": - version "0.1.25" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-custom-query/-/bookshelf-custom-query-0.1.25.tgz#b3f7e0e44eb63905631ac0be6de6e500d6fc8087" - integrity sha512-OjKpAL2P9X7BFKOKVD2oAsftUK01AWUBBIuO+FaGosOetIGEeyMZeUV9zI4jB5Us6JNLDiknYzaVZQMd9NsD6w== +"@tryghost/bookshelf-custom-query@^0.1.26": + version "0.1.26" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-custom-query/-/bookshelf-custom-query-0.1.26.tgz#57a0f1772710d2f2866847b333e03b2d2d7f1e3c" + integrity sha512-p4ocob5jchlsPZLw5bycCQwDL026t3HVB1kxZnZHmkdGluTEBpL5/rYCRJj/kGG2zWX/1wzhP1cFFiLCOQjfsQ== -"@tryghost/bookshelf-eager-load@^0.1.29": - version "0.1.29" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-eager-load/-/bookshelf-eager-load-0.1.29.tgz#8d68a709ce3b91055436833f42942de1ced39f09" - integrity sha512-zz1a0D3HER4i2dSRxkA+hTeYZqIdai8Zk+tPYmGTBsm4SrqfQFMR0B9U+FyA8Yn6lCproQx2xFeiCWhThGp9nA== - dependencies: - "@tryghost/debug" "^0.1.30" - lodash "^4.17.21" - -"@tryghost/bookshelf-filter@^0.5.14": - version "0.5.14" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-filter/-/bookshelf-filter-0.5.14.tgz#14f5279ccb605a872ae16e5ac49959eb267f4d8d" - integrity sha512-h1dEV9Uq3CvSRCSwTs7Xcx6st2k2aTf0qxqmZppMkiuBKwuijR/e27AFi+n0VfTInYpjkzaiWzAaTs0oijNLJA== - dependencies: - "@tryghost/debug" "^0.1.30" - "@tryghost/errors" "^1.3.2" - "@tryghost/nql" "^0.12.3" - "@tryghost/tpl" "^0.1.30" - -"@tryghost/bookshelf-has-posts@^0.1.30": +"@tryghost/bookshelf-eager-load@^0.1.30": version "0.1.30" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-has-posts/-/bookshelf-has-posts-0.1.30.tgz#7b842b1defcbd7dce002622ad6be4c4b2a104253" - integrity sha512-KCUSsaVspWhy4TLfruh6Vc4GaecMtBZ2I6zTu1Pei14U/nkhQgiXanpnFGPSzfcswbNyNRxGcOJcqOnr5/BQJg== + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-eager-load/-/bookshelf-eager-load-0.1.30.tgz#3227ee336a073422f60c588daee7a338457f4a5e" + integrity sha512-bMnBc4LelXsp+cfC9ovebvWr9lV0QTkThWLKSbVi/FRUV5JiMJTGRLMNNwv0bZfH9MBalynDBpvaHv1t+G1qcw== dependencies: - "@tryghost/debug" "^0.1.30" + "@tryghost/debug" "^0.1.31" lodash "^4.17.21" -"@tryghost/bookshelf-include-count@^0.3.13": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-include-count/-/bookshelf-include-count-0.3.13.tgz#0f802d52eda658bd886caebe218f993780ece0fe" - integrity sha512-kz9ziI9yMh4ccyTWkG03WFXhFlVMaA5tJMGVD1AMUQ+0dS/7TnwT1QuUVjspxIYow7LuhuMXDRafgwwOPTuBBA== +"@tryghost/bookshelf-filter@^0.5.15": + version "0.5.15" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-filter/-/bookshelf-filter-0.5.15.tgz#4979da196a8010a6262299d1927d905e3e500083" + integrity sha512-++k8dUOhccPikPDaRLFNdtiNgHLnlx5e64j/6IGTY4F8L/NeqEj/CxziR9Wxw5sV9USUMsg42fDulZXZ0dJgcg== dependencies: - "@tryghost/debug" "^0.1.30" + "@tryghost/debug" "^0.1.31" + "@tryghost/errors" "^1.3.3" + "@tryghost/nql" "^0.12.3" + "@tryghost/tpl" "^0.1.31" + +"@tryghost/bookshelf-has-posts@^0.1.31": + version "0.1.31" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-has-posts/-/bookshelf-has-posts-0.1.31.tgz#e37622cdf2cdf537afb75a907428942af1587833" + integrity sha512-QJxytGKcBdo3aa+FZeKRglm0+Zb7rwLT9D8CEvCPJ8OxbbsKlCgjTm9Z2feeF7ui+2WQk9Mi8eV7q/wozkTfrw== + dependencies: + "@tryghost/debug" "^0.1.31" lodash "^4.17.21" -"@tryghost/bookshelf-order@^0.1.25": - version "0.1.25" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-order/-/bookshelf-order-0.1.25.tgz#0dbdf6353ad9218e8a2091a14d86fcfb697e4fb6" - integrity sha512-JdHg34oVQ9qUldmp8OgQ352VumbYIV/fQLRLy0lst4AkL6UO9MwV3aCnw74gsLA2Z44KIVhPf9oDfQbuFfmJIw== +"@tryghost/bookshelf-include-count@^0.3.14": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-include-count/-/bookshelf-include-count-0.3.14.tgz#1fb400a8e4bb2cf1f73e06ff8749d986072b2b12" + integrity sha512-m01Phu29/HcDb1Y84dKRkjGaxDTPrSqebunJm7PBOpF4nR5Qy+w//2df/QB9XvD+rdUCwzLT+yi9E6zgfPdnng== + dependencies: + "@tryghost/debug" "^0.1.31" + lodash "^4.17.21" + +"@tryghost/bookshelf-order@^0.1.26": + version "0.1.26" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-order/-/bookshelf-order-0.1.26.tgz#f45212f708e28fc9b613bdaf93891a8fe625bc9c" + integrity sha512-9R8sFqjgeD02lPDxbeweP8QY290rEgMCs7Fnqu4MX77eU4Rf8q8Tfg92Wsmfljie3JLPZYhItIR1OSqQpRd3xg== dependencies: lodash "^4.17.21" -"@tryghost/bookshelf-pagination@^0.1.44": - version "0.1.44" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-pagination/-/bookshelf-pagination-0.1.44.tgz#597c93c33735f74c22662d4635f9e9c6b26a81cb" - integrity sha512-Le6W5nORcwn3Mnincg7dSwAOc/+nbYwPV5wRaml0+iuh67qJJHkoWt/BZgMrps/c7/zFlAUY05Pts29gCoqXAw== +"@tryghost/bookshelf-pagination@^0.1.46": + version "0.1.46" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-pagination/-/bookshelf-pagination-0.1.46.tgz#6622dbf9afbfe8be7fa6ae13315c06a8c2866e3b" + integrity sha512-dsikUD00DyW+C6GMZiuTIHkipJbzNlx47Rb160ZAPhahuma8LoAr4GAUMxgxEjFVwjveg45v4ZGZjn7tpkMtgQ== dependencies: - "@tryghost/errors" "^1.3.2" - "@tryghost/tpl" "^0.1.30" + "@tryghost/errors" "^1.3.3" + "@tryghost/tpl" "^0.1.31" lodash "^4.17.21" -"@tryghost/bookshelf-plugins@0.6.17": - version "0.6.17" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-plugins/-/bookshelf-plugins-0.6.17.tgz#875b23d5d8abd24c64bba33e0660aadfddad1c7e" - integrity sha512-sstie43C0UhwN/cf55q/C3XxygEgLVCnYXhHkiTSZhL1CBqIFoNxRUgO3piKdMq8smx7C1Q0zROT/T0onNDchg== +"@tryghost/bookshelf-plugins@0.6.19": + version "0.6.19" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-plugins/-/bookshelf-plugins-0.6.19.tgz#e5a51d6973a00ba7801aad6f13b3103e59d6acc4" + integrity sha512-/9UTHm1kMy8fJpAroEPpB8eU4MFGBmBr+GAr1ejm2jfSZBgeJFDEoWNDGJNvplnadtsbogprn6L2AAKD//kMBg== dependencies: - "@tryghost/bookshelf-collision" "^0.1.42" - "@tryghost/bookshelf-custom-query" "^0.1.25" - "@tryghost/bookshelf-eager-load" "^0.1.29" - "@tryghost/bookshelf-filter" "^0.5.14" - "@tryghost/bookshelf-has-posts" "^0.1.30" - "@tryghost/bookshelf-include-count" "^0.3.13" - "@tryghost/bookshelf-order" "^0.1.25" - "@tryghost/bookshelf-pagination" "^0.1.44" - "@tryghost/bookshelf-search" "^0.1.25" - "@tryghost/bookshelf-transaction-events" "^0.2.13" + "@tryghost/bookshelf-collision" "^0.1.43" + "@tryghost/bookshelf-custom-query" "^0.1.26" + "@tryghost/bookshelf-eager-load" "^0.1.30" + "@tryghost/bookshelf-filter" "^0.5.15" + "@tryghost/bookshelf-has-posts" "^0.1.31" + "@tryghost/bookshelf-include-count" "^0.3.14" + "@tryghost/bookshelf-order" "^0.1.26" + "@tryghost/bookshelf-pagination" "^0.1.46" + "@tryghost/bookshelf-search" "^0.1.26" + "@tryghost/bookshelf-transaction-events" "^0.2.14" -"@tryghost/bookshelf-search@^0.1.25": - version "0.1.25" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-search/-/bookshelf-search-0.1.25.tgz#9815de91a21d0d3b59b4fbf18e521b1e4b393e0a" - integrity sha512-eU39sSFQ/KCLXrEO2uPXmrNWU31vz7Hq9LdzKe38cyZVHG/ntccMNvvPqPdHbqqkW2E9KoQ67Q61CYGuK446Og== +"@tryghost/bookshelf-search@^0.1.26": + version "0.1.26" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-search/-/bookshelf-search-0.1.26.tgz#7051d52a6fe3bebd5c4a280a976ebbede0cc55df" + integrity sha512-ALO7B+Y7jb8GWl8Z5oC57lb85INGj7JBXxCIc40g1+otori6qUuWmooCY/3AymeSr4TdKg5PMmgavSB7KnVUkw== -"@tryghost/bookshelf-transaction-events@^0.2.13": - version "0.2.13" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-transaction-events/-/bookshelf-transaction-events-0.2.13.tgz#986a9a8e88bb63963a5bbc850a1be73ede8a2829" - integrity sha512-q0Ww0Bvf1t2wjdJD3J54idN07SF1gQSLPqMRvywEmP1IWbqbXdO4UvAkDQkJuWs8P/AiihXZgO5ykzJFhsq0MQ== +"@tryghost/bookshelf-transaction-events@^0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-transaction-events/-/bookshelf-transaction-events-0.2.14.tgz#194d375bd7400096169e709b4f071b1296171e05" + integrity sha512-tv1DLxHszuEbERUNzJBRLKZHh3ZlIu+8alGQPLN4F4okC1biZQIiiRp9qHaTFvTpzgY4uDD5ysH4HtdRoVV7yw== "@tryghost/bunyan-rotating-filestream@^0.0.7": version "0.0.7" @@ -7699,6 +7699,14 @@ "@tryghost/root-utils" "^0.3.28" debug "^4.3.1" +"@tryghost/debug@^0.1.31": + version "0.1.31" + resolved "https://registry.yarnpkg.com/@tryghost/debug/-/debug-0.1.31.tgz#242fff08b1380dc711e27134d48de2cc07042761" + integrity sha512-h2uYaAsr71oUvHHBaBWq3pY9ej/0jYnx8i4AhcTwAnEFxmPF0yEB14Hb3oxYhYKnlm5rD7u64CvoCtoM3vmyew== + dependencies: + "@tryghost/root-utils" "^0.3.29" + debug "^4.3.1" + "@tryghost/elasticsearch@^3.0.19": version "3.0.19" resolved "https://registry.yarnpkg.com/@tryghost/elasticsearch/-/elasticsearch-3.0.19.tgz#a0a94b667c83575a57775027aea5cb4ff4f216ef" @@ -7729,7 +7737,7 @@ focus-trap "^6.7.2" postcss-preset-env "^7.3.1" -"@tryghost/errors@1.3.1", "@tryghost/errors@1.3.2", "@tryghost/errors@^1.2.26", "@tryghost/errors@^1.2.3", "@tryghost/errors@^1.3.2": +"@tryghost/errors@1.3.1", "@tryghost/errors@1.3.2", "@tryghost/errors@^1.2.26", "@tryghost/errors@^1.2.3", "@tryghost/errors@^1.3.2", "@tryghost/errors@^1.3.3": version "1.3.2" resolved "https://registry.yarnpkg.com/@tryghost/errors/-/errors-1.3.2.tgz#3612f6f59ca07e37d1095f9eb31ff6a069a3b7f2" integrity sha512-Aqi2vz7HHwN6p2juIYUu8vpMtaKavjULBKNnL0l1req9qXjPs90i/HV8zhvK0ceeWuPdEXaCkfHSRr/yxG3/uw== @@ -8081,6 +8089,14 @@ caller "^1.0.1" find-root "^1.1.0" +"@tryghost/root-utils@^0.3.29": + version "0.3.29" + resolved "https://registry.yarnpkg.com/@tryghost/root-utils/-/root-utils-0.3.29.tgz#d6e941b586da8d14f3fbb82e44e20503377f8cf6" + integrity sha512-IM/Yt0iR+y4a6t7Lt5QQrgonlTXxocmAOQN9IOge4BmRlCwXL3RzTJo2jIqVxMHWlMpaK65jVL4frWQ+ROBUxw== + dependencies: + caller "^1.0.1" + find-root "^1.1.0" + "@tryghost/server@^0.1.37": version "0.1.37" resolved "https://registry.yarnpkg.com/@tryghost/server/-/server-0.1.37.tgz#04ee5671b19a4a5be05e361e293d47eb9c6c2482" @@ -8113,6 +8129,13 @@ dependencies: lodash.template "^4.5.0" +"@tryghost/tpl@^0.1.31": + version "0.1.31" + resolved "https://registry.yarnpkg.com/@tryghost/tpl/-/tpl-0.1.31.tgz#3aad44ef36b6a3af4f4c9c33c320bc7ac565cfab" + integrity sha512-AJVKeGq8/5ZHCJyKJQAB26/KXXsoRmRZUY+viJ6fgTV4eMwgsWh24Dv2L3gzOCiUi1iPYNf3MZaPnZqSquyxHg== + dependencies: + lodash.template "^4.5.0" + "@tryghost/url-utils@4.4.8": version "4.4.8" resolved "https://registry.yarnpkg.com/@tryghost/url-utils/-/url-utils-4.4.8.tgz#fb867d8bd59a640dc67ab61c1a3d921bd12a8c2b" From 430a2ca383043b852c802dc8399d12f872348794 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 09:59:33 +0000 Subject: [PATCH 033/131] Update dependency testem to v3.15.0 --- ghost/admin/package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ghost/admin/package.json b/ghost/admin/package.json index 80965aab09..34687d202c 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -145,7 +145,7 @@ "react-dom": "18.3.1", "reframe.js": "4.0.2", "semver": "7.6.2", - "testem": "3.14.0", + "testem": "3.15.0", "tracked-built-ins": "3.3.0", "util": "0.12.5", "validator": "7.2.0", diff --git a/yarn.lock b/yarn.lock index 8a742d8cbb..4fa7bef362 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18023,7 +18023,7 @@ fined@^1.0.1: object.pick "^1.2.0" parse-filepath "^1.0.1" -fireworm@^0.7.0: +fireworm@^0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/fireworm/-/fireworm-0.7.2.tgz#bc5736515b48bd30bf3293a2062e0b0e0361537a" integrity sha512-GjebTzq+NKKhfmDxjKq3RXwQcN9xRmZWhnnuC9L+/x5wBQtR0aaQM50HsjrzJ2wc28v1vSdfOpELok0TKR4ddg== @@ -30049,10 +30049,10 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -testem@3.14.0, testem@^3.2.0: - version "3.14.0" - resolved "https://registry.yarnpkg.com/testem/-/testem-3.14.0.tgz#ba563f5940d4c4c37e0b3db6d56b001b9aed80d4" - integrity sha512-hpybTZhio6DXUM7s0HsE8EOnN8zuA6LdNcz3EsTpQSnD56Cj6gSuFQx82wDKZQ6OmM1kvIBebxP+rEoOYBgCOA== +testem@3.15.0, testem@^3.2.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/testem/-/testem-3.15.0.tgz#c1f66ff2f6d0e9606c77c4de6b62bca088632d1a" + integrity sha512-vI1oQsjJW4QdVaH6ZmfNErzH7nzs0KzHJluocnfvbz1XRYGJKkIMGKWfsbD8MGGJOg+uzXcEek0/2W7BmGR4ug== dependencies: "@xmldom/xmldom" "^0.8.0" backbone "^1.1.2" @@ -30063,7 +30063,7 @@ testem@3.14.0, testem@^3.2.0: consolidate "^0.16.0" execa "^1.0.0" express "^4.10.7" - fireworm "^0.7.0" + fireworm "^0.7.2" glob "^7.0.4" http-proxy "^1.13.1" js-yaml "^3.2.5" From 34b903a12ba6136e7e89ecc8c6385e30ffd71f39 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Thu, 27 Jun 2024 15:15:39 +0200 Subject: [PATCH 034/131] Added browser autoplay error to Sentry ignore list fix https://linear.app/tryghost/issue/SLO-179/notallowederror-the-request-is-not-allowed-by-the-user-agent-or-the - this adds another browser error to the Sentry ignore list, as we don't have control over it, and it doesn't affect the user --- ghost/admin/app/routes/application.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ghost/admin/app/routes/application.js b/ghost/admin/app/routes/application.js index 5f7684c8f9..5eb27060f1 100644 --- a/ghost/admin/app/routes/application.js +++ b/ghost/admin/app/routes/application.js @@ -187,6 +187,7 @@ export default Route.extend(ShortcutsRoute, { ignoreErrors: [ // Browser autoplay policies (this regex covers a few) /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 /Server was unreachable/, From 5dfee47fca11c8fb8eba4ffa0457d234ce71bd34 Mon Sep 17 00:00:00 2001 From: Sag Date: Thu, 27 Jun 2024 17:47:26 +0200 Subject: [PATCH 035/131] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20default=20recipi?= =?UTF-8?q?ents=20setting=20not=20showing=20label=20filters=20(#20480)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes https://linear.app/tryghost/issue/SLO-171 - problem: when the Default Recipient setting is set to "Specific people" and is filtered by a label, we were not able to render the label correctly - cause: during the rendering, we look for labels by `id`, but they're stored by `slug` in the database setting `editor_default_email_recipients_filter` - solution: allow to look by the relevant key, by introducing a programmatic `key` to search for Before the fix: https://github.com/TryGhost/Ghost/assets/6225080/aed5fc31-6409-4986-aafe-557073c7f355 After the fix: https://github.com/TryGhost/Ghost/assets/6225080/f35b2607-5f22-42be-b1bb-92f35ccc9ab7 --- .../src/hooks/useFilterableApi.ts | 39 ++++++++++--------- .../email/useDefaultRecipientsOptions.tsx | 10 ++--- .../email/defaultRecipients.test.ts | 29 ++++++++++++++ 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/apps/admin-x-framework/src/hooks/useFilterableApi.ts b/apps/admin-x-framework/src/hooks/useFilterableApi.ts index 6b738e8cd9..61b568916a 100644 --- a/apps/admin-x-framework/src/hooks/useFilterableApi.ts +++ b/apps/admin-x-framework/src/hooks/useFilterableApi.ts @@ -7,7 +7,7 @@ const escapeNqlString = (value: string) => { }; const useFilterableApi = < - Data extends {id: string} & {[Key in FilterKey]: string}, + Data extends {id: string} & {[k in FilterKey]: string} & {[k: string]: unknown}, ResponseKey extends string = string, FilterKey extends string = string >({path, filterKey, responseKey, limit = 20}: { @@ -41,26 +41,27 @@ const useFilterableApi = < return response[responseKey]; }; + const loadInitialValues = async (values: string[], key: string) => { + await loadData(''); + + const data = [...(result.current.data || [])]; + const missingValues = values.filter(value => !result.current.data?.find(item => item[key] === value)); + + if (missingValues.length) { + const additionalData = await fetchApi<{meta?: Meta} & {[k in ResponseKey]: Data[]}>(apiUrl(path, { + filter: `${key}:[${missingValues.join(',')}]`, + limit: 'all' + })); + + data.push(...additionalData[responseKey]); + } + + return values.map(value => data.find(item => item[key] === value)!); + }; + return { loadData, - - loadInitialValues: async (ids: string[]) => { - await loadData(''); - - const data = [...(result.current.data || [])]; - const missingIds = ids.filter(id => !result.current.data?.find(({id: dataId}) => dataId === id)); - - if (missingIds.length) { - const additionalData = await fetchApi<{meta?: Meta} & {[k in ResponseKey]: Data[]}>(apiUrl(path, { - filter: `id:[${missingIds.join(',')}]`, - limit: 'all' - })); - - data.push(...additionalData[responseKey]); - } - - return ids.map(id => data.find(({id: dataId}) => dataId === id)!); - } + loadInitialValues }; }; diff --git a/apps/admin-x-settings/src/components/settings/email/useDefaultRecipientsOptions.tsx b/apps/admin-x-settings/src/components/settings/email/useDefaultRecipientsOptions.tsx index 731b8dc645..8ee3e4af38 100644 --- a/apps/admin-x-settings/src/components/settings/email/useDefaultRecipientsOptions.tsx +++ b/apps/admin-x-settings/src/components/settings/email/useDefaultRecipientsOptions.tsx @@ -62,11 +62,11 @@ const useDefaultRecipientsOptions = (selectedOption: string, defaultEmailRecipie const initSelectedSegments = async () => { const filters = defaultEmailRecipientsFilter?.split(',') || []; - const tierIds: string[] = [], labelIds: string[] = [], offerIds: string[] = []; + const tierIds: string[] = [], labelSlugs: string[] = [], offerIds: string[] = []; for (const filter of filters) { if (filter.startsWith('label:')) { - labelIds.push(filter.replace('label:', '')); + labelSlugs.push(filter.replace('label:', '')); } else if (filter.startsWith('offer_redemptions:')) { offerIds.push(filter.replace('offer_redemptions:', '')); } else if (isObjectId(filter)) { @@ -75,9 +75,9 @@ const useDefaultRecipientsOptions = (selectedOption: string, defaultEmailRecipie } const options = await Promise.all([ - tiers.loadInitialValues(tierIds).then(data => data.map(tierOption)), - labels.loadInitialValues(labelIds).then(data => data.map(labelOption)), - offers.loadInitialValues(offerIds).then(data => data.map(offerOption)) + tiers.loadInitialValues(tierIds, 'id').then(data => data.map(tierOption)), + labels.loadInitialValues(labelSlugs, 'slug').then(data => data.map(labelOption)), + offers.loadInitialValues(offerIds, 'id').then(data => data.map(offerOption)) ]).then(results => results.flat()); setSelectedSegments(filters.map(filter => options.find(option => option.value === filter)!)); diff --git a/apps/admin-x-settings/test/acceptance/email/defaultRecipients.test.ts b/apps/admin-x-settings/test/acceptance/email/defaultRecipients.test.ts index 26751488e2..565c3dd9d8 100644 --- a/apps/admin-x-settings/test/acceptance/email/defaultRecipients.test.ts +++ b/apps/admin-x-settings/test/acceptance/email/defaultRecipients.test.ts @@ -104,4 +104,33 @@ test.describe('Default recipient settings', async () => { ] }); }); + + test('renders existing default recipients filters correctly', async ({page}) => { + await mockApi({page, requests: { + ...globalDataRequests, + browseTiers: {method: 'GET', path: '/tiers/?filter=&limit=20', response: responseFixtures.tiers}, + browseLabels: {method: 'GET', path: '/labels/?filter=&limit=20', response: responseFixtures.labels}, + browseOffers: {method: 'GET', path: '/offers/?filter=&limit=20', response: responseFixtures.offers}, + browseSettings: {...globalDataRequests.browseSettings, response: updatedSettingsResponse([ + { + key: 'editor_default_email_recipients', + value: 'filter' + }, + { + key: 'editor_default_email_recipients_filter', + value: '645453f4d254799990dd0e22,label:first-label,offer_redemptions:6487ea6464fca78ec2fff5fe' + } + ])} + }}); + + await page.goto('/'); + + const section = page.getByTestId('default-recipients'); + await section.getByRole('button', {name: 'Edit'}).click(); + + await expect(section.getByText('Specific people')).toHaveCount(1); + await expect(section.getByText('Basic Supporter')).toHaveCount(1); + await expect(section.getByText('first-label')).toHaveCount(1); + await expect(section.getByText('First offer')).toHaveCount(1); + }); }); From 0d60c749577f9f79dd058db9eb62b0ef0de21161 Mon Sep 17 00:00:00 2001 From: Ghost CI <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 16:27:27 +0000 Subject: [PATCH 036/131] v5.87.0 --- ghost/admin/package.json | 4 ++-- ghost/core/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ghost/admin/package.json b/ghost/admin/package.json index 34687d202c..d17f2cfce5 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -1,6 +1,6 @@ { "name": "ghost-admin", - "version": "5.86.2", + "version": "5.87.0", "description": "Ember.js admin client for Ghost", "author": "Ghost Foundation", "homepage": "http://ghost.org", @@ -206,4 +206,4 @@ } } } -} +} \ No newline at end of file diff --git a/ghost/core/package.json b/ghost/core/package.json index fe4a7aebc7..af9f32b906 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -1,6 +1,6 @@ { "name": "ghost", - "version": "5.86.2", + "version": "5.87.0", "description": "The professional publishing platform", "author": "Ghost Foundation", "homepage": "https://ghost.org", From 41d8240d506e9ed12f200cd0bd83cdad61918ffe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 01:56:59 +0000 Subject: [PATCH 037/131] Update dependency mysql2 to v3.10.2 --- ghost/core/package.json | 2 +- yarn.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ghost/core/package.json b/ghost/core/package.json index af9f32b906..d37ff90e29 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -216,7 +216,7 @@ "moment": "2.24.0", "moment-timezone": "0.5.23", "multer": "1.4.4", - "mysql2": "3.10.1", + "mysql2": "3.10.2", "nconf": "0.12.1", "node-jose": "2.2.0", "path-match": "1.2.4", diff --git a/yarn.lock b/yarn.lock index 4fa7bef362..e689652d48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7691,7 +7691,7 @@ resolved "https://registry.yarnpkg.com/@tryghost/database-info/-/database-info-0.3.24.tgz#a7bc3519dd6c035aadd0e0c0dac03b55637390f5" integrity sha512-V5IHVJSyC8wrby/5YtFa1ajQ4qB3wfqc+AwlemDAEH2pZ5n5ygdb2qayeo4kwWPK0xn2Vul5KiRQK0bX+OePdw== -"@tryghost/debug@0.1.30", "@tryghost/debug@^0.1.13", "@tryghost/debug@^0.1.26", "@tryghost/debug@^0.1.30": +"@tryghost/debug@0.1.30": version "0.1.30" resolved "https://registry.yarnpkg.com/@tryghost/debug/-/debug-0.1.30.tgz#1daaac3a6bf63d82f28ba62f515c95dae29b12b4" integrity sha512-bW/RBkF5QU9iwASVSzdwFM2ba41OWpqe8ffsmblJ+e6z0NPZYuul/n72E/SWyBa8jTVpMLIoxiKG/wO2ajzHWg== @@ -7699,7 +7699,7 @@ "@tryghost/root-utils" "^0.3.28" debug "^4.3.1" -"@tryghost/debug@^0.1.31": +"@tryghost/debug@^0.1.13", "@tryghost/debug@^0.1.26", "@tryghost/debug@^0.1.30", "@tryghost/debug@^0.1.31": version "0.1.31" resolved "https://registry.yarnpkg.com/@tryghost/debug/-/debug-0.1.31.tgz#242fff08b1380dc711e27134d48de2cc07042761" integrity sha512-h2uYaAsr71oUvHHBaBWq3pY9ej/0jYnx8i4AhcTwAnEFxmPF0yEB14Hb3oxYhYKnlm5rD7u64CvoCtoM3vmyew== @@ -8081,7 +8081,7 @@ got "13.0.0" lodash "^4.17.21" -"@tryghost/root-utils@0.3.28", "@tryghost/root-utils@^0.3.24", "@tryghost/root-utils@^0.3.28": +"@tryghost/root-utils@0.3.28": version "0.3.28" resolved "https://registry.yarnpkg.com/@tryghost/root-utils/-/root-utils-0.3.28.tgz#43ae0047927a7753c9b526ea12ce6e382ec7fb1f" integrity sha512-/izwMw9tCJIQ3DVHumzEWgKhKAw5FwTgrrYcCNHl89yijJKaVRBOJUhlB/u2ST6UWfhahodjaYauq7ymTItaeg== @@ -8089,7 +8089,7 @@ caller "^1.0.1" find-root "^1.1.0" -"@tryghost/root-utils@^0.3.29": +"@tryghost/root-utils@^0.3.24", "@tryghost/root-utils@^0.3.28", "@tryghost/root-utils@^0.3.29": version "0.3.29" resolved "https://registry.yarnpkg.com/@tryghost/root-utils/-/root-utils-0.3.29.tgz#d6e941b586da8d14f3fbb82e44e20503377f8cf6" integrity sha512-IM/Yt0iR+y4a6t7Lt5QQrgonlTXxocmAOQN9IOge4BmRlCwXL3RzTJo2jIqVxMHWlMpaK65jVL4frWQ+ROBUxw== @@ -8122,14 +8122,14 @@ resolved "https://registry.yarnpkg.com/@tryghost/timezone-data/-/timezone-data-0.4.3.tgz#dad18d1c119ebf705b9720417684efe00081f149" integrity sha512-YrPm2m51/Ddqp+/9CclSv+tKqHkbv7iUu3BgiHJofGhs6ek0QQSpahgiBmJmi/o3R6V6DFbG7LT5qVEtkvMzKg== -"@tryghost/tpl@0.1.30", "@tryghost/tpl@^0.1.30": +"@tryghost/tpl@0.1.30": version "0.1.30" resolved "https://registry.yarnpkg.com/@tryghost/tpl/-/tpl-0.1.30.tgz#a012fd800e58d46a7d4146e2bde5f55892d8ae15" integrity sha512-EhbwSYCTFdK9cXyesCNPFwd3pF6an72vwTeQ9A9IjTVDd4or3wBc6lUwOifxWdYarxhaq2sX67gOn2Js6NbJWg== dependencies: lodash.template "^4.5.0" -"@tryghost/tpl@^0.1.31": +"@tryghost/tpl@^0.1.30", "@tryghost/tpl@^0.1.31": version "0.1.31" resolved "https://registry.yarnpkg.com/@tryghost/tpl/-/tpl-0.1.31.tgz#3aad44ef36b6a3af4f4c9c33c320bc7ac565cfab" integrity sha512-AJVKeGq8/5ZHCJyKJQAB26/KXXsoRmRZUY+viJ6fgTV4eMwgsWh24Dv2L3gzOCiUi1iPYNf3MZaPnZqSquyxHg== @@ -23785,10 +23785,10 @@ mv@~2: ncp "~2.0.0" rimraf "~2.4.0" -mysql2@3.10.1: - version "3.10.1" - resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.10.1.tgz#c39b8faf24ef4fd56330ef269122471a22d19198" - integrity sha512-6zo1T3GILsXMCex3YEu7hCz2OXLUarxFsxvFcUHWMpkPtmZLeTTWgRdc1gWyNJiYt6AxITmIf9bZDRy/jAfWew== +mysql2@3.10.2: + version "3.10.2" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.10.2.tgz#37297d5d75d2958e37adc9cd4db865354832d7a4" + integrity sha512-KCXPEvAkO0RcHPr362O5N8tFY2fXvbjfkPvRY/wGumh4EOemo9Hm5FjQZqv/pCmrnuxGu5OxnSENG0gTXqKMgQ== dependencies: denque "^2.1.0" generate-function "^2.3.1" From f561f362f48a0f3bdea2d7bd6b4b725edb734b75 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 01:57:23 +0000 Subject: [PATCH 038/131] Update dependency postcss to v8.4.39 --- apps/admin-x-design-system/package.json | 2 +- apps/comments-ui/package.json | 2 +- apps/signup-form/package.json | 2 +- ghost/core/package.json | 2 +- yarn.lock | 10 +++++----- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/admin-x-design-system/package.json b/apps/admin-x-design-system/package.json index 5838f21487..1145c74f94 100644 --- a/apps/admin-x-design-system/package.json +++ b/apps/admin-x-design-system/package.json @@ -63,7 +63,7 @@ "@uiw/react-codemirror": "4.22.1", "autoprefixer": "10.4.19", "clsx": "2.1.1", - "postcss": "8.4.38", + "postcss": "8.4.39", "postcss-import": "16.1.0", "react-colorful": "5.6.1", "react-hot-toast": "2.4.1", diff --git a/apps/comments-ui/package.json b/apps/comments-ui/package.json index 07eace299f..99bdd6ffe8 100644 --- a/apps/comments-ui/package.json +++ b/apps/comments-ui/package.json @@ -74,7 +74,7 @@ "eslint-plugin-react-refresh": "0.4.3", "eslint-plugin-tailwindcss": "3.13.0", "jsdom": "24.1.0", - "postcss": "8.4.38", + "postcss": "8.4.39", "tailwindcss": "3.4.4", "vite": "4.5.3", "vite-plugin-css-injected-by-js": "3.3.0", diff --git a/apps/signup-form/package.json b/apps/signup-form/package.json index 80ad04e248..be36380994 100644 --- a/apps/signup-form/package.json +++ b/apps/signup-form/package.json @@ -59,7 +59,7 @@ "eslint-plugin-react-refresh": "0.4.3", "eslint-plugin-tailwindcss": "3.13.0", "jsdom": "24.1.0", - "postcss": "8.4.38", + "postcss": "8.4.39", "postcss-import": "16.1.0", "prop-types": "15.8.1", "rollup-plugin-node-builtins": "2.1.2", diff --git a/ghost/core/package.json b/ghost/core/package.json index d37ff90e29..02fdc50d35 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -258,7 +258,7 @@ "mock-knex": "TryGhost/mock-knex#d8b93b1c20d4820323477f2c60db016ab3e73192", "nock": "13.3.3", "papaparse": "5.3.2", - "postcss": "8.4.38", + "postcss": "8.4.39", "postcss-cli": "11.0.0", "rewire": "6.0.0", "should": "13.2.3", diff --git a/yarn.lock b/yarn.lock index e689652d48..e9f1fec498 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26289,13 +26289,13 @@ postcss-values-parser@^4.0.0: is-url-superb "^4.0.0" postcss "^7.0.5" -postcss@8.4.38, postcss@^8.1.4, postcss@^8.2.14, postcss@^8.2.15, postcss@^8.3.11, postcss@^8.4.19, postcss@^8.4.23, postcss@^8.4.27, postcss@^8.4.4: - version "8.4.38" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" - integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== +postcss@8.4.39, postcss@^8.1.4, postcss@^8.2.14, postcss@^8.2.15, postcss@^8.3.11, postcss@^8.4.19, postcss@^8.4.23, postcss@^8.4.27, postcss@^8.4.4: + version "8.4.39" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.39.tgz#aa3c94998b61d3a9c259efa51db4b392e1bde0e3" + integrity sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw== dependencies: nanoid "^3.3.7" - picocolors "^1.0.0" + picocolors "^1.0.1" source-map-js "^1.2.0" postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5: From fa430666bf9149efaa6e8c6f6b544320760190b4 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Mon, 1 Jul 2024 09:54:30 +0200 Subject: [PATCH 039/131] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20staff=20user=20m?= =?UTF-8?q?odal=20not=20showing=20correct=20example=20URL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix https://linear.app/tryghost/issue/ENG-928/regression-author-url-slug-preview-broken - simple change to append the slug to the end of the example hint - also added test --- .../src/components/settings/general/users/ProfileBasics.tsx | 2 +- .../test/acceptance/general/users/profile.test.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/admin-x-settings/src/components/settings/general/users/ProfileBasics.tsx b/apps/admin-x-settings/src/components/settings/general/users/ProfileBasics.tsx index cac841a3fb..98864d1ede 100644 --- a/apps/admin-x-settings/src/components/settings/general/users/ProfileBasics.tsx +++ b/apps/admin-x-settings/src/components/settings/general/users/ProfileBasics.tsx @@ -33,7 +33,7 @@ const BasicInputs: React.FC = ({errors, clearError, user, setUs onKeyDown={() => clearError('email')} /> { await modal.getByLabel('Full name').fill('New Admin'); await modal.getByLabel('Email').fill('newadmin@test.com'); await modal.getByLabel('Slug').fill('newadmin'); + await expect(modal.getByText('https://example.com/author/newadmin')).toBeVisible(); await modal.getByLabel('Location').fill('some location'); await modal.getByLabel('Website').fill('https://example.com'); await modal.getByLabel('Facebook profile').fill('fb'); From 5f36bef4514c9a9e2bc393c22a9fd4b7fbd16ec4 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Mon, 1 Jul 2024 10:00:40 +0200 Subject: [PATCH 040/131] Changed "commented" link in member feed to redirect to post fix https://linear.app/tryghost/issue/ENG-1217/activity-log-link-for-comments-goes-to-wrong-place - the post analytics page does not contain any comments, so it's not the most intuitive location to point the user. Instead, we can send them to the frontend of the post, where they can view comments --- ghost/admin/app/helpers/parse-member-event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ghost/admin/app/helpers/parse-member-event.js b/ghost/admin/app/helpers/parse-member-event.js index f0581eb3b6..0a723477d8 100644 --- a/ghost/admin/app/helpers/parse-member-event.js +++ b/ghost/admin/app/helpers/parse-member-event.js @@ -330,7 +330,7 @@ export default class ParseMemberEventHelper extends Helper { * Get internal route props for a clickable object */ getRoute(event) { - if (['comment_event', 'click_event', 'feedback_event'].includes(event.type)) { + if (['click_event', 'feedback_event'].includes(event.type)) { if (event.data.post) { return { name: 'posts.analytics', From 95a4895e8f049e1a393359733abf8a2d56df73ea Mon Sep 17 00:00:00 2001 From: Sanne de Vries <65487235+sanne-san@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:43:26 +0200 Subject: [PATCH 041/131] Center aligned feature image in email template (#20491) REF DES-380 - Center aligned feature image in email template - Updated feature image css in editor to better display image overlay and improve caption spacing --- .../components/gh-editor-feature-image.hbs | 2 +- ghost/admin/app/styles/layouts/editor.css | 51 ++++++++++++------- .../__snapshots__/email-previews.test.js.snap | 4 +- .../__snapshots__/batch-sending.test.js.snap | 2 +- .../email-templates/partials/styles-old.hbs | 1 + .../lib/email-templates/partials/styles.hbs | 1 + .../lib/email-templates/template-old.hbs | 2 +- .../lib/email-templates/template.hbs | 2 +- 8 files changed, 41 insertions(+), 24 deletions(-) diff --git a/ghost/admin/app/components/gh-editor-feature-image.hbs b/ghost/admin/app/components/gh-editor-feature-image.hbs index c6390324a2..0d934a7d87 100644 --- a/ghost/admin/app/components/gh-editor-feature-image.hbs +++ b/ghost/admin/app/components/gh-editor-feature-image.hbs @@ -50,7 +50,7 @@ {{svg-jar "koenig/kg-trash"}} -
    +
    {{#if this.isEditingAlt}} section.gh-editor-fullscreen { +.gh-main>section.gh-editor-fullscreen { position: fixed; top: 0; left: 0; @@ -449,7 +449,7 @@ .gh-editor-feedback-dropdown { min-width: 400px; border-radius: 3px; - box-shadow: 0 0 0 1px rgba(0,0,0,.04), 0 8px 20px -3px rgba(0,0,0,.2); + box-shadow: 0 0 0 1px rgba(0, 0, 0, .04), 0 8px 20px -3px rgba(0, 0, 0, .2); padding: 20px; background-color: #fff; background-clip: padding-box; @@ -553,13 +553,20 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone { height: 4px; } +.gh-editor-feature-image { + position: relative; +} + +.gh-editor-feature-image img { + display: block; +} .gh-editor-feature-image-overlay { position: absolute; top: 0; left: 0; right: 0; - bottom: 100px; - background-image: linear-gradient(180deg,rgba(0,0,0,.2),transparent 40%,transparent); + bottom: 0; + background-image: linear-gradient(180deg, rgba(0, 0, 0, .2), transparent 40%, transparent); transition: all .1s ease-in; opacity: 0; } @@ -677,12 +684,19 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone { stroke: var(--midgrey-l2); } +.gh-editor-feature-image-caption-container { + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + padding: .8rem 0; +} .gh-editor-feature-image-alttext, .gh-editor-feature-image-caption { width: 100%; min-height: 24px; - margin: 0 0 1.2rem 0; - padding: 0; + margin: 0; + padding: 0 3.6rem 0 0; outline: none; border-width: 0; border-style: none; @@ -1058,7 +1072,7 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone { width: 100%; height: 100%; border: 2px solid var(--blue); - background-color: rgba(255,255,255,0.6); + background-color: rgba(255, 255, 255, 0.6); } .gh-editor-drop-target .drop-target-message { @@ -1109,6 +1123,7 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone { position: relative; vertical-align: bottom; } + .editor-toolbar .fa-check:before { position: absolute; right: 3px; @@ -1197,8 +1212,8 @@ figure { left: -20px; width: 20px; height: 100%; - background: rgb(255,255,255); - background: linear-gradient(90deg, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); + background: rgb(255, 255, 255); + background: linear-gradient(90deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%); z-index: 999; opacity: 0; transition: all 250ms ease-out; @@ -1211,22 +1226,22 @@ figure { right: 0; width: 20px; height: 100%; - background: rgb(255,255,255); - background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 100%); + background: rgb(255, 255, 255); + background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); z-index: 999; } .ember-power-select-option[aria-current="true"] .kg-settings-link-url.scroller::before { opacity: 1; left: 0; - background: linear-gradient(90deg, rgba(244,245,245,1) 0%, rgba(244,245,245,0) 100%); + background: linear-gradient(90deg, rgba(244, 245, 245, 1) 0%, rgba(244, 245, 245, 0) 100%); } .ember-power-select-option[aria-current="true"] .kg-settings-link-url::after { - background: linear-gradient(90deg, rgba(244,245,245,0) 0%, rgba(244,245,245,1) 100%); + background: linear-gradient(90deg, rgba(244, 245, 245, 0) 0%, rgba(244, 245, 245, 1) 100%); } -.kg-settings-link-url > span { +.kg-settings-link-url>span { display: inline-block; font-weight: 400; font-size: 1.2rem; @@ -1251,4 +1266,4 @@ figure { color: var(--red); font-weight: 300; letter-spacing: 0.3px; -} +} \ No newline at end of file diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap index c36be50fb4..456afd4202 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap @@ -1148,7 +1148,7 @@ table.body h2 span {
+ \\" align=\\"center\\" style=\\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; vertical-align: top; color: #000000; padding-bottom: 30px; width: 100%; text-align: center;\\" width=\\"100%\\" valign=\\"top\\"> + \\" align=\\"center\\" style=\\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; vertical-align: top; color: #000000; width: 100%; text-align: center; padding-bottom: 10px;\\" width=\\"100%\\" valign=\\"top\\">\\"Testing diff --git a/ghost/email-service/lib/email-templates/partials/styles-old.hbs b/ghost/email-service/lib/email-templates/partials/styles-old.hbs index ccf76f8693..4b2ba31394 100644 --- a/ghost/email-service/lib/email-templates/partials/styles-old.hbs +++ b/ghost/email-service/lib/email-templates/partials/styles-old.hbs @@ -463,6 +463,7 @@ figure blockquote p { .feature-image { padding-bottom: 30px; width: 100%; + text-align: center; } .feature-image-row:first-child > td, diff --git a/ghost/email-service/lib/email-templates/partials/styles.hbs b/ghost/email-service/lib/email-templates/partials/styles.hbs index 34e0533eb3..8b27fd6522 100644 --- a/ghost/email-service/lib/email-templates/partials/styles.hbs +++ b/ghost/email-service/lib/email-templates/partials/styles.hbs @@ -454,6 +454,7 @@ figure blockquote p { .feature-image { padding-bottom: 30px; width: 100%; + text-align: center; } .feature-image-with-caption { diff --git a/ghost/email-service/lib/email-templates/template-old.hbs b/ghost/email-service/lib/email-templates/template-old.hbs index aee60dbeea..15d0b5705e 100644 --- a/ghost/email-service/lib/email-templates/template-old.hbs +++ b/ghost/email-service/lib/email-templates/template-old.hbs @@ -115,7 +115,7 @@ {{#if post.feature_image_caption }} feature-image-with-caption {{/if}} - "> Date: Mon, 1 Jul 2024 13:35:38 +0200 Subject: [PATCH 042/131] Unify "Save" and "Close" buttons in Settings (#20430) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DES-27 There are two patterns used in settings modals for action buttons: 1. [Cancel] and [Save & close] (sometimes it's [Cancel] and [OK], inconsistently) — example: Staff details, Tier details, Navigation, Recommendation 2. [Close] and [Save] — example: Design settings, Portal, Newsletter details etc. This is confusing and leaves people confused and uncertain about what's going to happen in one or the other case. --- .../settings/general/UserDetailModal.tsx | 7 ++----- .../recommendations/EditRecommendationModal.tsx | 8 ++------ .../membership/tiers/TierDetailModal.tsx | 10 +++------- .../acceptance/general/users/profile.test.ts | 17 +++++++++-------- .../test/acceptance/general/users/roles.test.ts | 5 ++++- .../test/acceptance/membership/tiers.test.ts | 13 ++++++++----- 6 files changed, 28 insertions(+), 32 deletions(-) diff --git a/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx b/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx index f44833c327..27e492db49 100644 --- a/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx +++ b/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx @@ -113,10 +113,6 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => { onSave: async (values) => { await updateUser?.(values); }, - onSavedStateReset: () => { - mainModal.remove(); - navigateOnClose(); - }, onSaveError: handleError }); const setUserData = (newData: User) => updateForm(() => newData); @@ -353,9 +349,10 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => { animate={canAccessSettings(currentUser)} backDrop={canAccessSettings(currentUser)} buttonsDisabled={okProps.disabled} + cancelLabel='Close' dirty={saveState === 'unsaved'} okColor={okProps.color} - okLabel={okProps.label || 'Save & close'} + okLabel={okProps.label || 'Save'} size={canAccessSettings(currentUser) ? 'lg' : 'bleed'} stickyFooter={true} testId='user-detail-modal' diff --git a/apps/admin-x-settings/src/components/settings/growth/recommendations/EditRecommendationModal.tsx b/apps/admin-x-settings/src/components/settings/growth/recommendations/EditRecommendationModal.tsx index b31d71d772..aa493250e1 100644 --- a/apps/admin-x-settings/src/components/settings/growth/recommendations/EditRecommendationModal.tsx +++ b/apps/admin-x-settings/src/components/settings/growth/recommendations/EditRecommendationModal.tsx @@ -27,10 +27,6 @@ const EditRecommendationModal: React.FC { await editRecommendation(state); }, - onSavedStateReset: () => { - modal.remove(); - updateRoute('recommendations'); - }, onSaveError: handleError, onValidate: (state) => { const newErrors = validateDescriptionForm(state); @@ -76,10 +72,10 @@ const EditRecommendationModal: React.FC> & { const TierDetailModalContent: React.FC<{tier?: Tier}> = ({tier}) => { const isFreeTier = tier?.type === 'free'; - const modal = useModal(); const {updateRoute} = useRouting(); const {mutateAsync: updateTier} = useEditTier(); const {mutateAsync: createTier} = useAddTier(); @@ -97,10 +96,6 @@ const TierDetailModalContent: React.FC<{tier?: Tier}> = ({tier}) => { } } }, - onSavedStateReset: () => { - modal.remove(); - updateRoute('tiers'); - }, onSaveError: handleError }); @@ -185,10 +180,11 @@ const TierDetailModalContent: React.FC<{tier?: Tier}> = ({tier}) => { updateRoute('tiers'); }} buttonsDisabled={okProps.disabled} + cancelLabel='Close' dirty={saveState === 'unsaved'} leftButtonProps={leftButtonProps} okColor={okProps.color} - okLabel={okProps.label || 'Save & close'} + okLabel={okProps.label || 'Save'} size='lg' testId='tier-detail-modal' title={(tier ? (tier.active ? 'Edit tier' : 'Edit archived tier') : 'New tier')} diff --git a/apps/admin-x-settings/test/acceptance/general/users/profile.test.ts b/apps/admin-x-settings/test/acceptance/general/users/profile.test.ts index 46768e1d97..4c7f10ae28 100644 --- a/apps/admin-x-settings/test/acceptance/general/users/profile.test.ts +++ b/apps/admin-x-settings/test/acceptance/general/users/profile.test.ts @@ -35,23 +35,23 @@ test.describe('User profile', async () => { // Validation failures await modal.getByLabel('Full name').fill(''); - await modal.getByRole('button', {name: 'Save & close'}).click(); + await modal.getByRole('button', {name: 'Save'}).click(); await expect(modal).toContainText('Name is required'); await modal.getByLabel('Email').fill('test'); - await modal.getByRole('button', {name: 'Save & close'}).click(); + await modal.getByRole('button', {name: 'Save'}).click(); await expect(modal).toContainText('Enter a valid email address'); await modal.getByLabel('Location').fill(new Array(195).join('a')); - await modal.getByRole('button', {name: 'Save & close'}).click(); + await modal.getByRole('button', {name: 'Save'}).click(); await expect(modal).toContainText('Location is too long'); await modal.getByLabel('Bio').fill(new Array(210).join('a')); - await modal.getByRole('button', {name: 'Save & close'}).click(); + await modal.getByRole('button', {name: 'Save'}).click(); await expect(modal).toContainText('Bio is too long'); await modal.getByLabel('Website').fill('not-a-website'); - await modal.getByRole('button', {name: 'Save & close'}).click(); + await modal.getByRole('button', {name: 'Save'}).click(); await expect(modal).toContainText('Enter a valid URL'); const facebookInput = modal.getByLabel('Facebook profile'); @@ -166,9 +166,10 @@ test.describe('User profile', async () => { await modal.getByLabel(/Paid member cancellations/).check(); await modal.getByLabel(/Milestones/).uncheck(); - await modal.getByRole('button', {name: 'Save & close'}).click(); + await modal.getByRole('button', {name: 'Save'}).click(); await expect(modal.getByRole('button', {name: 'Saved'})).toBeVisible(); + await modal.getByRole('button', {name: 'Close'}).click(); await expect(listItem.getByText('New Admin')).toBeVisible(); await expect(listItem.getByText('newadmin@test.com')).toBeVisible(); @@ -314,7 +315,7 @@ test.describe('User profile', async () => { await modal.getByLabel('Full name').fill('Updated'); - await modal.getByRole('button', {name: 'Cancel'}).click(); + await modal.getByRole('button', {name: 'Close'}).click(); await expect(page.getByTestId('confirmation-modal')).toHaveText(/leave/i); @@ -374,7 +375,7 @@ test.describe('User profile', async () => { await listItem.getByRole('button', {name: 'Edit'}).click(); await expect(modal.getByTestId('api-keys')).toBeHidden(); - await modal.getByRole('button', {name: 'Cancel'}).click(); + await modal.getByRole('button', {name: 'Close'}).click(); // Can see and regenerate your own staff token diff --git a/apps/admin-x-settings/test/acceptance/general/users/roles.test.ts b/apps/admin-x-settings/test/acceptance/general/users/roles.test.ts index 47b30b2b17..67367dc010 100644 --- a/apps/admin-x-settings/test/acceptance/general/users/roles.test.ts +++ b/apps/admin-x-settings/test/acceptance/general/users/roles.test.ts @@ -69,10 +69,12 @@ test.describe('User roles', async () => { await modal.locator('input[value=editor]').check(); - await modal.getByRole('button', {name: 'Save & close'}).click(); + await modal.getByRole('button', {name: 'Save'}).click(); await expect(modal.getByRole('button', {name: 'Saved'})).toBeVisible(); + await modal.getByRole('button', {name: 'Close'}).click(); + await expect(activeTab).toHaveText(/No authors found./); await section.getByRole('tab', {name: 'Editors'}).click(); @@ -146,6 +148,7 @@ test.describe('User roles', async () => { await modal.getByLabel('Full name').fill('New name'); await modal.getByRole('button', {name: 'Save'}).click(); + await modal.getByRole('button', {name: 'Close'}).click(); await expect(modal).toBeHidden(); }); diff --git a/apps/admin-x-settings/test/acceptance/membership/tiers.test.ts b/apps/admin-x-settings/test/acceptance/membership/tiers.test.ts index 732c8737d6..8a5ecd02b2 100644 --- a/apps/admin-x-settings/test/acceptance/membership/tiers.test.ts +++ b/apps/admin-x-settings/test/acceptance/membership/tiers.test.ts @@ -18,7 +18,7 @@ test.describe('Tier settings', async () => { const modal = page.getByTestId('tier-detail-modal'); - await modal.getByRole('button', {name: 'Save & close'}).click(); + await modal.getByRole('button', {name: 'Save'}).click(); await expect(modal).toHaveText(/Enter a name for the tier/); await expect(modal).toHaveText(/Amount must be at least \$1/); @@ -51,7 +51,8 @@ test.describe('Tier settings', async () => { browseTiers: {method: 'GET', path: '/tiers/', response: {tiers: [...responseFixtures.tiers.tiers, newTier]}} }}); - await modal.getByRole('button', {name: 'Save & close'}).click(); + await modal.getByRole('button', {name: 'Save'}).click(); + await modal.getByRole('button', {name: 'Close'}).click(); // await expect(section.getByTestId('tier-card').filter({hasText: /Plus/})).toHaveText(/Plus tier/); // await expect(section.getByTestId('tier-card').filter({hasText: /Plus/})).toHaveText(/\$8\/month/); @@ -103,7 +104,7 @@ test.describe('Tier settings', async () => { // Failing validations await modal.getByLabel('Name').fill(''); - await modal.getByRole('button', {name: 'Save & close'}).click(); + await modal.getByRole('button', {name: 'Save'}).click(); await expect(modal).toHaveText(/Enter a name for the tier/); @@ -132,7 +133,8 @@ test.describe('Tier settings', async () => { // Save changes - await modal.getByRole('button', {name: 'Save & close'}).click(); + await modal.getByRole('button', {name: 'Save'}).click(); + await modal.getByRole('button', {name: 'Close'}).click(); await expect(section.getByTestId('tier-card').filter({hasText: /Supporter/})).toHaveText(/Supporter updated/); await expect(section.getByTestId('tier-card').filter({hasText: /Supporter/})).toHaveText(/Supporter description/); @@ -185,7 +187,8 @@ test.describe('Tier settings', async () => { await modal.getByRole('button', {name: 'Add'}).click(); await modal.getByLabel('New benefit').fill('Second benefit'); - await modal.getByRole('button', {name: 'Save & close'}).click(); + await modal.getByRole('button', {name: 'Save'}).click(); + await modal.getByRole('button', {name: 'Close'}).click(); await expect(section.getByTestId('tier-card').filter({hasText: /Free/})).toHaveText(/Free tier description/); From a146709c167f7ce53ff7d94b0f3dfefeadc72351 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Mon, 1 Jul 2024 13:45:31 +0200 Subject: [PATCH 043/131] Cleaned up unused core dependencies - analytics-node usage was removed a while back - juice is used by a different package now --- ghost/core/package.json | 2 -- yarn.lock | 80 ++--------------------------------------- 2 files changed, 2 insertions(+), 80 deletions(-) diff --git a/ghost/core/package.json b/ghost/core/package.json index 02fdc50d35..1264d054e2 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -171,7 +171,6 @@ "@tryghost/webmentions": "0.0.0", "@tryghost/zip": "1.1.43", "amperize": "0.6.1", - "analytics-node": "6.2.0", "body-parser": "1.20.2", "bookshelf": "1.2.0", "bookshelf-relations": "2.7.0", @@ -206,7 +205,6 @@ "json-stable-stringify": "1.1.1", "jsonpath": "1.1.1", "jsonwebtoken": "8.5.1", - "juice": "9.1.0", "keypair": "1.0.4", "knex": "2.4.2", "knex-migrator": "5.2.1", diff --git a/yarn.lock b/yarn.lock index e9f1fec498..9f756e4534 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5042,14 +5042,6 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@segment/loosely-validate-event@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz#87dfc979e5b4e7b82c5f1d8b722dfd5d77644681" - integrity sha512-ZMCSfztDBqwotkl848ODgVcAmN4OItEWDCkshcKz0/W6gGSQayuuCtWV/MlodFivAZD793d6UgANd6wCXUfrIw== - dependencies: - component-type "^1.2.1" - join-component "^1.1.0" - "@selderee/plugin-htmlparser2@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.6.0.tgz#27e994afd1c2cb647ceb5406a185a5574188069d" @@ -9742,20 +9734,6 @@ amperize@0.6.1: uuid "^7.0.1" validator "^12.0.0" -analytics-node@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/analytics-node/-/analytics-node-6.2.0.tgz#8ae2ebc73d85e5b2aac8d366b974ad36996f629d" - integrity sha512-NLU4tCHlWt0tzEaFQL7NIoWhq2KmQSmz0JvyS2lYn6fc4fEjTMSabhJUx8H1r5995FX8fE3rZ15uIHU6u+ovlQ== - dependencies: - "@segment/loosely-validate-event" "^2.0.0" - axios "^0.27.2" - axios-retry "3.2.0" - lodash.isstring "^4.0.1" - md5 "^2.2.1" - ms "^2.0.0" - remove-trailing-slash "^0.1.0" - uuid "^8.3.2" - ansi-colors@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" @@ -10309,21 +10287,6 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios-retry@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/axios-retry/-/axios-retry-3.2.0.tgz#eb48e72f90b177fde62329b2896aa8476cfb90ba" - integrity sha512-RK2cLMgIsAQBDhlIsJR5dOhODPigvel18XUv1dDXW+4k1FzebyfRk+C+orot6WPZOYFKSfhLwHPwVmTVOODQ5w== - dependencies: - is-retry-allowed "^1.1.0" - -axios@^0.27.2: - version "0.27.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== - dependencies: - follow-redirects "^1.14.9" - form-data "^4.0.0" - axios@^1.0.0, axios@^1.3.3, axios@^1.6.0: version "1.6.5" resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.5.tgz#2c090da14aeeab3770ad30c3a1461bc970fb0cd8" @@ -12786,11 +12749,6 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -charenc@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" - integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== - charm@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/charm/-/charm-1.0.2.tgz#8add367153a6d9a581331052c4090991da995e35" @@ -13391,11 +13349,6 @@ component-emitter@^1.2.1, component-emitter@^1.3.0, component-emitter@~1.3.0: resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== -component-type@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-type/-/component-type-1.2.1.tgz#8a47901700238e4fc32269771230226f24b415a9" - integrity sha512-Kgy+2+Uwr75vAi6ChWXgHuLvd+QLD7ssgpaRq2zCvt80ptvAfMc/hijcJxXkBa2wMlEZcJvC2H8Ubo+A9ATHIg== - compress-commons@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" @@ -13794,11 +13747,6 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -crypt@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== - crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -18132,7 +18080,7 @@ focus-trap@^6.7.2: dependencies: tabbable "^5.3.3" -follow-redirects@^1.0.0, follow-redirects@^1.14.9, follow-redirects@^1.15.4: +follow-redirects@^1.0.0, follow-redirects@^1.15.4: version "1.15.5" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== @@ -20215,7 +20163,7 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@^1.1.5, is-buffer@~1.1.6: +is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -20569,11 +20517,6 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-retry-allowed@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - is-set@^2.0.1, is-set@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" @@ -21163,11 +21106,6 @@ jiti@^1.18.2, jiti@^1.21.0: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== -join-component@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.1.0.tgz#b8417b750661a392bee2c2537c68b2a9d4977cd5" - integrity sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ== - jose@4.15.7, jose@^4.14.6: version "4.15.7" resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.7.tgz#96ad68d786632bd03c9068aa281810dbbe1b60d8" @@ -22934,15 +22872,6 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -md5@^2.2.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" - integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== - dependencies: - charenc "0.0.2" - crypt "0.0.2" - is-buffer "~1.1.6" - mdast-util-compact@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz#d531bb7667b5123abf20859be086c4d06c894593" @@ -27638,11 +27567,6 @@ remove-trailing-separator@^1.0.1, remove-trailing-separator@^1.1.0: resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== -remove-trailing-slash@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz#be2285a59f39c74d1bce4f825950061915e3780d" - integrity sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA== - repeat-element@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" From 09f2ccfca47dde9d84a25da8ab78b08c96a07fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20der=20Winden?= Date: Mon, 1 Jul 2024 14:14:38 +0200 Subject: [PATCH 044/131] Updated copy for default recipients hint (#20496) Fixes https://linear.app/tryghost/issue/DES-179/inappropriate-copy-in-default-recipient-settings The hint for _default recipients_ referenced the wrong setting. It now reflects the right one. --- .../src/components/settings/email/DefaultRecipients.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/admin-x-settings/src/components/settings/email/DefaultRecipients.tsx b/apps/admin-x-settings/src/components/settings/email/DefaultRecipients.tsx index 6fd922a924..69bb2c0326 100644 --- a/apps/admin-x-settings/src/components/settings/email/DefaultRecipients.tsx +++ b/apps/admin-x-settings/src/components/settings/email/DefaultRecipients.tsx @@ -125,7 +125,7 @@ const DefaultRecipients: React.FC<{ keywords: string[] }> = ({keywords}) => { const form = (
@@ -1360,7 +1360,7 @@ exports[`Email Preview API Read can read post email preview with fields 4: [head 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": "28343", + "content-length": "28380", "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/integration/services/email-service/__snapshots__/batch-sending.test.js.snap b/ghost/core/test/integration/services/email-service/__snapshots__/batch-sending.test.js.snap index 842178e287..82664a983f 100644 --- a/ghost/core/test/integration/services/email-service/__snapshots__/batch-sending.test.js.snap +++ b/ghost/core/test/integration/services/email-service/__snapshots__/batch-sending.test.js.snap @@ -422,7 +422,7 @@ table.body h2 span {
\\"Testing
Testing feature image caption
- +
@@ -1824,7 +1824,7 @@ Ghost: Independent technology for modern publishingBeautiful, modern publishing
- +
diff --git a/yarn.lock b/yarn.lock index 1e6f0ca775..85a9e95fee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7753,12 +7753,12 @@ dependencies: lodash-es "^4.17.11" -"@tryghost/html-to-mobiledoc@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@tryghost/html-to-mobiledoc/-/html-to-mobiledoc-3.1.0.tgz#64cb333c8f0b857f53edf289867a39d6113ccefb" - integrity sha512-CL1IAL3mRrWPs2LcDcPptN2O2w9EY0mb/ofkOycQYaHoPPt0JgPOPeJdmIwRfddzGkEK1WA1yWnqKa4zSyIGRw== +"@tryghost/html-to-mobiledoc@3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@tryghost/html-to-mobiledoc/-/html-to-mobiledoc-3.1.2.tgz#aded56f17df3d13824eb4e9a1aaeda6dd87b42e6" + integrity sha512-x6C69fA87QhHVO24GI7SPeyNyk7XCNf7tL1RPGzRwt/eiYLEgg/YzbBoSO89dvPIwhimaJUgCCN9+gg8Ll7T3A== dependencies: - "@tryghost/kg-parser-plugins" "4.1.0" + "@tryghost/kg-parser-plugins" "4.1.1" "@tryghost/mobiledoc-kit" "^0.12.4-ghost.1" jsdom "^24.0.0" @@ -7800,11 +7800,6 @@ resolved "https://registry.yarnpkg.com/@tryghost/kg-card-factory/-/kg-card-factory-5.0.4.tgz#b2de98eaf01edbd5629fb1f4b06eca3a5f95d0ad" integrity sha512-KcNM4QJONSSOJeQlv9no5wFx+uV2mESX3bYBL2y3c0DqB26NlMaUx0QIAFSbCSinUlCvRFOwEEBQyaACtCOvzQ== -"@tryghost/kg-clean-basic-html@4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@tryghost/kg-clean-basic-html/-/kg-clean-basic-html-4.1.0.tgz#f4979d99e65a9202e3d3376f90ff08de8ac56578" - integrity sha512-exXUpTA1z0zyk3F3ZXcT7oy8b3acbpuHWMqiowdb2HI0/6LTGaBdmm866fhBKedgB59lIWRt511djUnUIILh+A== - "@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" @@ -7852,6 +7847,23 @@ lodash "^4.17.21" luxon "^3.3.0" +"@tryghost/kg-default-nodes@1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@tryghost/kg-default-nodes/-/kg-default-nodes-1.1.5.tgz#d2b8452344173344cda6715b07500a00a1d9d4e7" + integrity sha512-bYCSTZR6oyHSA9iW4V5eRtmdb9ubdYu1y2K36NwmVLQBefFN9o//x32kL56+oNbQjspq9OSsyNgKwovBLks64w== + 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" + html-minifier "^4.0.0" + jsdom "^24.0.0" + lexical "0.13.1" + lodash "^4.17.21" + luxon "^3.3.0" + "@tryghost/kg-default-transforms@1.1.5": version "1.1.5" resolved "https://registry.yarnpkg.com/@tryghost/kg-default-transforms/-/kg-default-transforms-1.1.5.tgz#7ea7914ecc5090edf69ee780e5ba4ee905cde890" @@ -7863,6 +7875,17 @@ "@tryghost/kg-default-nodes" "1.1.4" lexical "0.13.1" +"@tryghost/kg-default-transforms@1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@tryghost/kg-default-transforms/-/kg-default-transforms-1.1.6.tgz#44564988a57a9f9dabff18eecdd60484854d3d7e" + integrity sha512-DDOMKhJZ5f2GcsJGjOYMCv/LsV9yPjvv6ggxD3RYEua/NOaG9cVodkz2O1imrMdqimHrkP3mjiX8GkOcDqDOyw== + dependencies: + "@lexical/list" "0.13.1" + "@lexical/rich-text" "0.13.1" + "@lexical/utils" "0.13.1" + "@tryghost/kg-default-nodes" "1.1.5" + lexical "0.13.1" + "@tryghost/kg-html-to-lexical@1.1.5": version "1.1.5" resolved "https://registry.yarnpkg.com/@tryghost/kg-html-to-lexical/-/kg-html-to-lexical-1.1.5.tgz#8c502a1ee396a05c3d351150e91aaa7467a9ddda" @@ -7879,10 +7902,10 @@ jsdom "^24.0.0" lexical "0.13.1" -"@tryghost/kg-lexical-html-renderer@1.1.5": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@tryghost/kg-lexical-html-renderer/-/kg-lexical-html-renderer-1.1.5.tgz#9163694889d2dd704bf9efc07304a46b6a386677" - integrity sha512-RNP7MKs1orWRsFSb5Cy0KYEF5ExQ8MQ0nKTW3jXiEnYRYVI1ClgUm2j6P9vm5uibF/GWlCM8H4JbTc/Bnl58mw== +"@tryghost/kg-lexical-html-renderer@1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@tryghost/kg-lexical-html-renderer/-/kg-lexical-html-renderer-1.1.6.tgz#738b81d0bf8f64fb5bb38530985c1f4ed458357e" + integrity sha512-9/DaQMqZDX9+PK71oOWX2dAeh2oMw/gMgyHeCQmUyjK1e/4rE3QXQOKVr/X8ytib/d/9kL9NLcVbCCJ+PYgOLQ== dependencies: "@lexical/clipboard" "0.13.1" "@lexical/code" "0.13.1" @@ -7890,11 +7913,10 @@ "@lexical/link" "0.13.1" "@lexical/list" "0.13.1" "@lexical/rich-text" "0.13.1" - "@tryghost/kg-default-nodes" "1.1.4" - "@tryghost/kg-default-transforms" "1.1.5" + "@tryghost/kg-default-nodes" "1.1.5" + "@tryghost/kg-default-transforms" "1.1.6" jsdom "^24.0.0" lexical "0.13.1" - prettier "3.2.5" "@tryghost/kg-markdown-html-renderer@7.0.5": version "7.0.5" @@ -7920,17 +7942,17 @@ mobiledoc-dom-renderer "^0.7.0" simple-dom "^1.4.0" -"@tryghost/kg-parser-plugins@4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@tryghost/kg-parser-plugins/-/kg-parser-plugins-4.1.0.tgz#60f6492d1b0d71558c51f7b0c45d905ae5aa8b0a" - integrity sha512-sHwOc+9ObNBFccgbSxnbdgygLTPLI0PpjUwGsVJ7NAPQBEISHsQzHMYpWTCGatgdB+B+KmwTtBeGcvn3DIXagw== +"@tryghost/kg-parser-plugins@4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@tryghost/kg-parser-plugins/-/kg-parser-plugins-4.1.1.tgz#a1f74ad3c1de0c940f487145e84212021491681e" + integrity sha512-IRhX8k/iIdirSjAt3S4cjpasAQ6Rsspp1oeOP21781DJNlFG7Ld7xiI+f+3/cZzF2GlBgfka0dhcRp2a2MSzrA== dependencies: - "@tryghost/kg-clean-basic-html" "4.1.0" + "@tryghost/kg-clean-basic-html" "4.1.1" -"@tryghost/kg-unsplash-selector@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@tryghost/kg-unsplash-selector/-/kg-unsplash-selector-0.2.0.tgz#e811612fd66f20182a1d7f06e48a86bf94896ce6" - integrity sha512-oVMUiKFwOk5bUwNmW2DrICfs9lRtqHIOrV2hnJF8cBrhKLPnTSDrYTrrz04FTkq1ApISJ0y9pLZA6eepZpzTUQ== +"@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-utils@1.0.26": version "1.0.26" @@ -7939,10 +7961,10 @@ dependencies: semver "^7.3.5" -"@tryghost/koenig-lexical@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.3.1.tgz#27a49c796f2e892f6a7c5d114985ef2fafb7f3d2" - integrity sha512-j4+LefCJv4aUmM4207q/BsCHfNUE6yHPPzXCaxjMX0dCJLwURRpCbJ67CPV4+KGH8PJe4eHzktX0zJV/A9iHGg== +"@tryghost/koenig-lexical@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.3.2.tgz#e285a5deef831b1f52cead6a2ad4f5a4ceb4cb9c" + integrity sha512-YJB6gK/RNvnthisXfkrpnLr2+xgqRwD80uU0VCC9CF+t25J7MvazxIY67WMeuToXl6J6ntiQp5pdFSeEzB4Rrg== "@tryghost/limit-service@1.2.14": version "1.2.14" @@ -26320,11 +26342,6 @@ pretender@3.4.7, pretender@^3.4.7: fake-xml-http-request "^2.1.2" route-recognizer "^0.3.3" -prettier@3.2.5: - version "3.2.5" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" - integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== - prettier@^2.8.0: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" From 90033eff2d07059c81d6c2ee205ce56198a9201d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 06:15:50 +0000 Subject: [PATCH 052/131] Update dependency @tryghost/kg-html-to-lexical to v1.1.6 --- ghost/core/package.json | 2 +- yarn.lock | 40 ++++++---------------------------------- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/ghost/core/package.json b/ghost/core/package.json index 09e75e9dcb..b3040354f9 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -112,7 +112,7 @@ "@tryghost/kg-default-atoms": "5.0.3", "@tryghost/kg-default-cards": "10.0.6", "@tryghost/kg-default-nodes": "1.1.5", - "@tryghost/kg-html-to-lexical": "1.1.5", + "@tryghost/kg-html-to-lexical": "1.1.6", "@tryghost/kg-lexical-html-renderer": "1.1.6", "@tryghost/kg-mobiledoc-html-renderer": "7.0.4", "@tryghost/limit-service": "1.2.14", diff --git a/yarn.lock b/yarn.lock index 85a9e95fee..a590a8414f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7830,23 +7830,6 @@ lodash "^4.17.21" luxon "^3.0.0" -"@tryghost/kg-default-nodes@1.1.4": - version "1.1.4" - resolved "https://registry.yarnpkg.com/@tryghost/kg-default-nodes/-/kg-default-nodes-1.1.4.tgz#4a057903ce5529fa64c1b0901f520b6086e2a0ae" - integrity sha512-GdGon7HU+L/kla0DlavKh6MBQCnaNMSVaeMEasGir5JAc/NcVw7RB5vqg0OCLHpEsJBPoLcaOF0s/DPKc6xFvg== - 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" - html-minifier "^4.0.0" - jsdom "^24.0.0" - lexical "0.13.1" - lodash "^4.17.21" - luxon "^3.3.0" - "@tryghost/kg-default-nodes@1.1.5": version "1.1.5" resolved "https://registry.yarnpkg.com/@tryghost/kg-default-nodes/-/kg-default-nodes-1.1.5.tgz#d2b8452344173344cda6715b07500a00a1d9d4e7" @@ -7864,17 +7847,6 @@ lodash "^4.17.21" luxon "^3.3.0" -"@tryghost/kg-default-transforms@1.1.5": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@tryghost/kg-default-transforms/-/kg-default-transforms-1.1.5.tgz#7ea7914ecc5090edf69ee780e5ba4ee905cde890" - integrity sha512-LfZj5MODOoyoZlQqHeDzO0orTgaVUO1ivNbPWG/PSVBm+9i+QKtVXrg9K7L0JV084UJE6AFIdBNJM/4gCj+QKA== - dependencies: - "@lexical/list" "0.13.1" - "@lexical/rich-text" "0.13.1" - "@lexical/utils" "0.13.1" - "@tryghost/kg-default-nodes" "1.1.4" - lexical "0.13.1" - "@tryghost/kg-default-transforms@1.1.6": version "1.1.6" resolved "https://registry.yarnpkg.com/@tryghost/kg-default-transforms/-/kg-default-transforms-1.1.6.tgz#44564988a57a9f9dabff18eecdd60484854d3d7e" @@ -7886,10 +7858,10 @@ "@tryghost/kg-default-nodes" "1.1.5" lexical "0.13.1" -"@tryghost/kg-html-to-lexical@1.1.5": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@tryghost/kg-html-to-lexical/-/kg-html-to-lexical-1.1.5.tgz#8c502a1ee396a05c3d351150e91aaa7467a9ddda" - integrity sha512-JSv5Mw7Jrfa0ao3GgITvo2z6hPpJ0PASLcDmPXxYEbMjXBWOFhzFRi70ON1jmFEFKlBAwKS2eY5eyQvyc5jH0w== +"@tryghost/kg-html-to-lexical@1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@tryghost/kg-html-to-lexical/-/kg-html-to-lexical-1.1.6.tgz#69079cfb9afba8b0f7e7ee1f0cf22a54e7cbcff4" + integrity sha512-2wY7FSojXP+ytHs4yAa/WM/6Xn7VSsGDUJZudCbV0enByF5mFaISdDGL7EqjrSTfHCLJcw13ibtt+VEbD0XBFA== dependencies: "@lexical/clipboard" "0.13.1" "@lexical/headless" "0.13.1" @@ -7897,8 +7869,8 @@ "@lexical/link" "0.13.1" "@lexical/list" "0.13.1" "@lexical/rich-text" "0.13.1" - "@tryghost/kg-default-nodes" "1.1.4" - "@tryghost/kg-default-transforms" "1.1.5" + "@tryghost/kg-default-nodes" "1.1.5" + "@tryghost/kg-default-transforms" "1.1.6" jsdom "^24.0.0" lexical "0.13.1" From 186c6f3c421800837f7c77014e19f82f0ff21df4 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Mon, 1 Jul 2024 14:53:49 +0200 Subject: [PATCH 053/131] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20unexpected=20lea?= =?UTF-8?q?ve=20confirmation=20after=20Cmd+S=20on=20member=20profile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix https://linear.app/tryghost/issue/ENG-779/%F0%9F%90%9B-cmds-does-not-save-member-profile-changes - previously, pressing Cmd+S on a member profile would save the profile, but the dirty attributes weren't being cleaned, so the application would trigger the leave confirmation when exiting - now, we've fixed the code to keep a dynamic scratch member, - long term, we should get rid of the scratch model, but this still allows us to fix the bug for now --- ghost/admin/app/controllers/member.js | 13 +++++++------ ghost/admin/app/routes/member.js | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ghost/admin/app/controllers/member.js b/ghost/admin/app/controllers/member.js index 0978126d08..a6606ec8bf 100644 --- a/ghost/admin/app/controllers/member.js +++ b/ghost/admin/app/controllers/member.js @@ -29,6 +29,7 @@ export default class MemberController extends Controller { @tracked showImpersonateMemberModal = false; @tracked modalLabel = null; @tracked showLabelModal = false; + @tracked scratchMember = null; _previousLabels = null; _previousNewsletters = null; @@ -56,6 +57,12 @@ export default class MemberController extends Controller { set member(member) { this.model = member; + + if (member !== this.scratchMember?.member) { + const scratchMember = EmberObject.create({member}); + SCRATCH_PROPS.forEach(prop => defineProperty(scratchMember, prop, boundOneWay(`member.${prop}`))); + this.scratchMember = scratchMember; + } } get dirtyAttributes() { @@ -92,12 +99,6 @@ export default class MemberController extends Controller { return options; } - get scratchMember() { - let scratchMember = EmberObject.create({member: this.member}); - SCRATCH_PROPS.forEach(prop => defineProperty(scratchMember, prop, boundOneWay(`member.${prop}`))); - return scratchMember; - } - get subscribedAt() { let memberSince = moment(this.member.createdAtUTC).from(moment()); let createdDate = moment(this.member.createdAtUTC).format('D MMM YYYY'); diff --git a/ghost/admin/app/routes/member.js b/ghost/admin/app/routes/member.js index ab9bdc8c75..3f70cd7bf5 100644 --- a/ghost/admin/app/routes/member.js +++ b/ghost/admin/app/routes/member.js @@ -34,6 +34,7 @@ export default class MembersRoute extends AdminRoute { setupController(controller, member, transition) { super.setupController(...arguments); + controller.member = member; controller.setInitialRelationshipValues(); From 9522ef8ca8ff0a40d0d2460c73a1ac46a6fcbf8f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:38:41 +0000 Subject: [PATCH 054/131] Update nest monorepo to v10.3.10 --- ghost/ghost/package.json | 8 +++---- yarn.lock | 48 ++++++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/ghost/ghost/package.json b/ghost/ghost/package.json index e7b68660c3..ec1dd677c5 100644 --- a/ghost/ghost/package.json +++ b/ghost/ghost/package.json @@ -20,7 +20,7 @@ "build" ], "devDependencies": { - "@nestjs/testing": "10.3.9", + "@nestjs/testing": "10.3.10", "@types/node": "20.14.8", "@types/sinon": "^17.0.3", "@types/supertest": "^6.0.2", @@ -34,9 +34,9 @@ "typescript": "5.4.5" }, "dependencies": { - "@nestjs/common": "10.3.9", - "@nestjs/core": "10.3.9", - "@nestjs/platform-express": "10.3.9", + "@nestjs/common": "10.3.10", + "@nestjs/core": "10.3.10", + "@nestjs/platform-express": "10.3.10", "@tryghost/errors": "1.3.2", "bson-objectid": "2.0.4", "express": "4.19.2", diff --git a/yarn.lock b/yarn.lock index a590a8414f..6dbe1d4f36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3770,44 +3770,44 @@ pump "^3.0.0" tar-fs "^2.1.1" -"@nestjs/common@10.3.9": - version "10.3.9" - resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.3.9.tgz#4e85d8fa6ae201a1c5f49d4d09b205bc3672ed1f" - integrity sha512-JAQONPagMa+sy/fcIqh/Hn3rkYQ9pQM51vXCFNOM5ujefxUVqn3gwFRMN8Y1+MxdUHipV+8daEj2jEm0IqJzOA== +"@nestjs/common@10.3.10": + version "10.3.10" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.3.10.tgz#d8825d55a50a04e33080c9188e6a5b03235d19f2" + integrity sha512-H8k0jZtxk1IdtErGDmxFRy0PfcOAUg41Prrqpx76DQusGGJjsaovs1zjXVD1rZWaVYchfT1uczJ6L4Kio10VNg== dependencies: uid "2.0.2" iterare "1.2.1" - tslib "2.6.2" + tslib "2.6.3" -"@nestjs/core@10.3.9": - version "10.3.9" - resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.3.9.tgz#fe3645eb4974423de5503a19d08f85a67693e72f" - integrity sha512-NzZUfWAmaf8sqhhwoRA+CuqxQe+P4Rz8PZp5U7CdCbjyeB9ZVGcBkihcJC9wMdtiOWHRndB2J8zRfs5w06jK3w== +"@nestjs/core@10.3.10": + version "10.3.10" + resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.3.10.tgz#508090c3ca36488a8e24a9e5939c2f37426e48f4" + integrity sha512-ZbQ4jovQyzHtCGCrzK5NdtW1SYO2fHSsgSY1+/9WdruYCUra+JDkWEXgZ4M3Hv480Dl3OXehAmY1wCOojeMyMQ== dependencies: uid "2.0.2" "@nuxtjs/opencollective" "0.3.2" fast-safe-stringify "2.1.1" iterare "1.2.1" path-to-regexp "3.2.0" - tslib "2.6.2" + tslib "2.6.3" -"@nestjs/platform-express@10.3.9": - version "10.3.9" - resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.3.9.tgz#769c6027f8b3d1e144218403762710f96a174821" - integrity sha512-si/UzobP6YUtYtCT1cSyQYHHzU3yseqYT6l7OHSMVvfG1+TqxaAqI6nmrix02LO+l1YntHRXEs3p+v9a7EfrSQ== +"@nestjs/platform-express@10.3.10": + version "10.3.10" + resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.3.10.tgz#45fa006605913d5aaa31bf99073136ad639939b4" + integrity sha512-wK2ow3CZI2KFqWeEpPmoR300OB6BcBLxARV1EiClJLCj4S1mZsoCmS0YWgpk3j1j6mo0SI8vNLi/cC2iZPEPQA== dependencies: body-parser "1.20.2" cors "2.8.5" express "4.19.2" multer "1.4.4-lts.1" - tslib "2.6.2" + tslib "2.6.3" -"@nestjs/testing@10.3.9": - version "10.3.9" - resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-10.3.9.tgz#27fb0e23b129147f8de100ac40645ebf6a865c3a" - integrity sha512-z24SdpZIRtYyM5s2vnu7rbBosXJY/KcAP7oJlwgFa/h/z/wg8gzyoKy5lhibH//OZNO+pYKajV5wczxuy5WeAg== +"@nestjs/testing@10.3.10": + version "10.3.10" + resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-10.3.10.tgz#6ed3c821fddf868665cc5ddc8591ee6eaab8a916" + integrity sha512-i3HAtVQJijxNxJq1k39aelyJlyEIBRONys7IipH/4r8W0J+M1V+y5EKDOyi4j1SdNSb/vmNyWpZ2/ewZjl3kRA== dependencies: - tslib "2.6.2" + tslib "2.6.3" "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" @@ -30417,10 +30417,10 @@ tsconfig-paths@^4.1.2: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.6.2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tslib@2.6.3, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== tslib@^1.11.1, tslib@^1.13.0, tslib@^1.9.0: version "1.14.1" From e6b1f8a8bfb7b7c747efef5e75d83a56564e9df7 Mon Sep 17 00:00:00 2001 From: Princi Vershwal Date: Tue, 2 Jul 2024 12:17:13 +0530 Subject: [PATCH 055/131] Fixed analytics sources to not be case sensitive (#20506) fixes https://linear.app/tryghost/issue/ENG-925/analytics-sources-shouldnt-be-case-sensitive --- ghost/admin/app/services/dashboard-stats.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ghost/admin/app/services/dashboard-stats.js b/ghost/admin/app/services/dashboard-stats.js index 0fc19af58b..0c17716fff 100644 --- a/ghost/admin/app/services/dashboard-stats.js +++ b/ghost/admin/app/services/dashboard-stats.js @@ -250,7 +250,7 @@ export default class DashboardStatsService extends Service { return stat.date >= moment().add(-this.chartDays, 'days').format('YYYY-MM-DD'); }).reduce((acc, stat) => { const statSource = stat.source ?? ''; - const existingSource = acc.find(s => s.source === statSource); + const existingSource = acc.find(s => s.source.toLowerCase() === statSource.toLowerCase()); if (existingSource) { existingSource.signups += stat.signups || 0; existingSource.paidConversions += stat.paidConversions || 0; From 62aad6fd84cacebf2c268d58e988329dcd8e0601 Mon Sep 17 00:00:00 2001 From: Princi Vershwal Date: Tue, 2 Jul 2024 14:11:32 +0530 Subject: [PATCH 056/131] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20analytics=20sour?= =?UTF-8?q?ces=20to=20not=20be=20case=20sensitive=20(#20506)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes https://linear.app/tryghost/issue/ENG-925/analytics-sources-shouldnt-be-case-sensitive --- ghost/admin/app/services/dashboard-stats.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ghost/admin/app/services/dashboard-stats.js b/ghost/admin/app/services/dashboard-stats.js index 0c17716fff..8ffcb7d54b 100644 --- a/ghost/admin/app/services/dashboard-stats.js +++ b/ghost/admin/app/services/dashboard-stats.js @@ -242,7 +242,6 @@ export default class DashboardStatsService extends Service { if (!this.memberAttributionStats) { return []; } - return this.memberAttributionStats.filter((stat) => { if (this.chartDays === 'all') { return true; From 04fdd2e29ecc53e39ef6a8bb027caa3e81214046 Mon Sep 17 00:00:00 2001 From: Sodbileg Gansukh Date: Tue, 2 Jul 2024 16:45:33 +0800 Subject: [PATCH 057/131] Display more useful error messages upon theme activation (#20509) ref DES-75 --- .../site/theme/AdvancedThemeSettings.tsx | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx b/apps/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx index 93c855ee36..e9b0fe590d 100644 --- a/apps/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx +++ b/apps/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx @@ -1,6 +1,8 @@ +import InvalidThemeModal, {FatalErrors} from './InvalidThemeModal'; import NiceModal from '@ebay/nice-modal-react'; import React from 'react'; import {Button, ButtonProps, ConfirmationModal, List, ListItem, Menu, ModalPage, showToast} from '@tryghost/admin-x-design-system'; +import {JSONError} from '@tryghost/admin-x-framework/errors'; import {Theme, isActiveTheme, isDefaultTheme, isDeletableTheme, isLegacyTheme, useActivateTheme, useDeleteTheme} from '@tryghost/admin-x-framework/api/themes'; import {downloadFile, getGhostPaths} from '@tryghost/admin-x-framework/helpers'; import {useHandleError} from '@tryghost/admin-x-framework/hooks'; @@ -57,7 +59,26 @@ const ThemeActions: React.FC = ({ message:
{theme.name} is now your active theme
}); } catch (e) { - handleError(e); + let fatalErrors: FatalErrors | null = null; + if (e instanceof JSONError && e.response?.status === 422 && e.data?.errors) { + fatalErrors = (e.data.errors as any) as FatalErrors; + } else { + handleError(e); + } + let title = 'Invalid Theme'; + let prompt = <>This theme is invalid and cannot be activated. Fix the following errors and re-upload the theme; + + if (fatalErrors) { + NiceModal.show(InvalidThemeModal, { + title, + prompt, + fatalErrors, + onRetry: async (modal) => { + modal?.remove(); + handleActivate(); + } + }); + } } }; From 23075b7bf8a3b4f292db75661998ffe12e4e34d6 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Tue, 2 Jul 2024 11:34:04 +0200 Subject: [PATCH 058/131] Optimized aggregating member attribution statistics - the existing code creates a new moment instance, takes away some days and then formats the result - this is run for every entry of the member attribution stats, which means dashboards for big sites with a lot of attribution data become slow - this value doesn't change across each iteration of the filter, so we can just extract it out and calculate it once - this commit removes this code block from the flamegraph completely --- ghost/admin/app/services/dashboard-stats.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ghost/admin/app/services/dashboard-stats.js b/ghost/admin/app/services/dashboard-stats.js index 8ffcb7d54b..c55d5f5363 100644 --- a/ghost/admin/app/services/dashboard-stats.js +++ b/ghost/admin/app/services/dashboard-stats.js @@ -242,11 +242,14 @@ export default class DashboardStatsService extends Service { if (!this.memberAttributionStats) { return []; } + + const firstChartDay = moment().add(-this.chartDays, 'days').format('YYYY-MM-DD'); + return this.memberAttributionStats.filter((stat) => { if (this.chartDays === 'all') { return true; } - return stat.date >= moment().add(-this.chartDays, 'days').format('YYYY-MM-DD'); + return stat.date >= firstChartDay; }).reduce((acc, stat) => { const statSource = stat.source ?? ''; const existingSource = acc.find(s => s.source.toLowerCase() === statSource.toLowerCase()); From 18719e21680a65d3cc187351e0fdc3a25d610a01 Mon Sep 17 00:00:00 2001 From: Sanne de Vries <65487235+sanne-san@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:24:14 +0200 Subject: [PATCH 059/131] Updated password reset notification (#20510) REF DES-540 --- ghost/admin/app/components/gh-alert.js | 4 ++-- ghost/admin/app/controllers/reset.js | 5 ++++- ghost/admin/app/controllers/signin.js | 9 ++++----- ghost/admin/app/styles/components/notifications.css | 4 ++-- ghost/admin/app/styles/layouts/flow.css | 13 ++++++++++++- ghost/admin/app/styles/patterns/forms.css | 4 ++-- ghost/admin/app/templates/signin.hbs | 2 +- ghost/admin/tests/acceptance/password-reset-test.js | 6 +++--- .../tests/integration/components/gh-alert-test.js | 4 ++-- 9 files changed, 32 insertions(+), 19 deletions(-) diff --git a/ghost/admin/app/components/gh-alert.js b/ghost/admin/app/components/gh-alert.js index ca22911459..2114ae97a1 100644 --- a/ghost/admin/app/components/gh-alert.js +++ b/ghost/admin/app/components/gh-alert.js @@ -9,8 +9,8 @@ export default class GhAlert extends Component { const typeMapping = { success: 'green', error: 'red', - warn: 'blue', - info: 'blue' + warn: 'black', + info: 'black' }; const type = this.args.message.type; diff --git a/ghost/admin/app/controllers/reset.js b/ghost/admin/app/controllers/reset.js index 0c045c9762..2ce6090ee8 100644 --- a/ghost/admin/app/controllers/reset.js +++ b/ghost/admin/app/controllers/reset.js @@ -62,7 +62,10 @@ export default class ResetController extends Controller.extend(ValidationEngine) password_reset: [{newPassword, ne2Password, token}] } }); - this.notifications.showAlert(resp.password_reset[0].message, {type: 'warn', delayed: true, key: 'password.reset'}); + this.notifications.showNotification( + resp.password_reset[0].message, + {type: 'info', delayed: true, key: 'password.reset'} + ); this.session.authenticate('authenticator:cookie', email, newPassword); return true; } catch (error) { diff --git a/ghost/admin/app/controllers/signin.js b/ghost/admin/app/controllers/signin.js index 3ef67c2569..1c4531cf3a 100644 --- a/ghost/admin/app/controllers/signin.js +++ b/ghost/admin/app/controllers/signin.js @@ -25,6 +25,7 @@ export default class SigninController extends Controller.extend(ValidationEngine @tracked submitting = false; @tracked loggingIn = false; + @tracked flowNotification = ''; @tracked flowErrors = ''; @tracked passwordResetEmailSent = false; @@ -123,21 +124,19 @@ export default class SigninController extends Controller.extend(ValidationEngine let notifications = this.notifications; this.flowErrors = ''; + this.flowNotification = ''; // This is a bit dirty, but there's no other way to ensure the properties are set as well as 'forgotPassword' this.hasValidated.addObject('identification'); try { yield this.validate({property: 'forgotPassword'}); yield this.ajax.post(forgottenUrl, {data: {password_reset: [{email}]}}); - notifications.showAlert( - 'Please check your email for instructions.', - {type: 'info', key: 'forgot-password.send.success'} - ); + this.flowNotification = 'An email with password reset instructions has been sent.'; return true; } catch (error) { // ValidationEngine throws "undefined" for failed validation if (!error) { - return this.flowErrors = 'We need your email address to reset your password!'; + return this.flowErrors = 'We need your email address to reset your password.'; } if (isVersionMismatchError(error)) { diff --git a/ghost/admin/app/styles/components/notifications.css b/ghost/admin/app/styles/components/notifications.css index 8c008a4764..20fcb6e6da 100644 --- a/ghost/admin/app/styles/components/notifications.css +++ b/ghost/admin/app/styles/components/notifications.css @@ -312,8 +312,8 @@ /* ---------------------------------------------------------- */ .gh-alert-black { - border-bottom: color-mod(var(--darkgrey) lightness(-10%)) 1px solid; - background: var(--darkgrey); + border-bottom: 1px solid var(--black); + background: var(--black); color: #fff; } .gh-alert-black a { diff --git a/ghost/admin/app/styles/layouts/flow.css b/ghost/admin/app/styles/layouts/flow.css index bf22e34cc6..c669f6ea04 100644 --- a/ghost/admin/app/styles/layouts/flow.css +++ b/ghost/admin/app/styles/layouts/flow.css @@ -271,7 +271,18 @@ .gh-setup .gh-flow-content .main-error { margin-top: 16px; color: var(--red); - font-size: 1.35rem; + font-size: 1.4rem; + line-height: 1.5; + text-align: center; + text-wrap: balance; +} + +.gh-flow-content .main-notification, +.gh-setup .gh-flow-content .main-notification { + margin-top: 16px; + color: var(--black); + font-size: 1.4rem; + font-weight: 400; line-height: 1.5; text-align: center; text-wrap: balance; diff --git a/ghost/admin/app/styles/patterns/forms.css b/ghost/admin/app/styles/patterns/forms.css index 82e216964b..4eba90853a 100644 --- a/ghost/admin/app/styles/patterns/forms.css +++ b/ghost/admin/app/styles/patterns/forms.css @@ -231,14 +231,14 @@ select.error { .gh-input:focus, .gh-input.focus { outline: 0; - border-color: color-mod(var(--green)) !important; + border-color: var(--green); box-shadow: inset 0 0 0 1px var(--green); background: var(--white); } .error .gh-input:focus, .error .gh-input.focus { - border-color: color-mod(var(--red)) !important; + border-color: var(--red); box-shadow: inset 0 0 0 1px var(--red); } diff --git a/ghost/admin/app/templates/signin.hbs b/ghost/admin/app/templates/signin.hbs index 9c1991a999..01cc2e82c7 100644 --- a/ghost/admin/app/templates/signin.hbs +++ b/ghost/admin/app/templates/signin.hbs @@ -73,7 +73,7 @@ data-test-button="sign-in" /> -

{{if this.flowErrors this.flowErrors}} 

+

{{if this.flowErrors this.flowErrors this.flowNotification}} 

{{/if}} diff --git a/ghost/admin/tests/acceptance/password-reset-test.js b/ghost/admin/tests/acceptance/password-reset-test.js index 6dc0a8bc35..2c86c75611 100644 --- a/ghost/admin/tests/acceptance/password-reset-test.js +++ b/ghost/admin/tests/acceptance/password-reset-test.js @@ -17,7 +17,7 @@ describe('Acceptance: Password Reset', function () { await click('.forgotten-link'); // an alert with instructions is displayed - expect(findAll('.gh-alert-blue').length, 'alert count') + expect(findAll('[data-test-flow-notification]').length, 'alert count') .to.equal(1); }); @@ -41,7 +41,7 @@ describe('Acceptance: Password Reset', function () { // error message shown expect(find('p.main-error').textContent.trim(), 'error message') - .to.equal('We need your email address to reset your password!'); + .to.equal('We need your email address to reset your password.'); // invalid email provided await fillIn('input[name="identification"]', 'test'); @@ -61,7 +61,7 @@ describe('Acceptance: Password Reset', function () { // error message expect(find('p.main-error').textContent.trim(), 'error message') - .to.equal('We need your email address to reset your password!'); + .to.equal('We need your email address to reset your password.'); // unknown email provided await fillIn('input[name="identification"]', 'unknown@example.com'); diff --git a/ghost/admin/tests/integration/components/gh-alert-test.js b/ghost/admin/tests/integration/components/gh-alert-test.js index 92c9612078..64863ea3e1 100644 --- a/ghost/admin/tests/integration/components/gh-alert-test.js +++ b/ghost/admin/tests/integration/components/gh-alert-test.js @@ -45,11 +45,11 @@ describe('Integration: Component: gh-alert', function () { this.message.type = 'warn'; await settled(); - expect(alert, 'warn class is yellow').to.have.class('gh-alert-blue'); + expect(alert, 'warn class is black').to.have.class('gh-alert-black'); this.message.type = 'info'; await settled(); - expect(alert, 'info class is blue').to.have.class('gh-alert-blue'); + expect(alert, 'info class is black').to.have.class('gh-alert-black'); }); it('closes notification through notifications service', async function () { From a4107b8202fa4764a37404f8fa60e4cd325ba6aa Mon Sep 17 00:00:00 2001 From: Fabien 'egg' O'Carroll Date: Tue, 2 Jul 2024 20:58:20 +0700 Subject: [PATCH 060/131] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20incorrect=20memb?= =?UTF-8?q?er=20subscription=20details=20in=20Admin=20(#20476)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes https://linear.app/tryghost/issue/ENG-642 - When a subscription is in the `canceled` state the corresponding Member has no access to the Ghost site. The only time a Member will continue to have access if their subscription is due to cancel at the period end is if it is still in an active state, which is one of `active` `trialing` `unpaid` or `past_due` - When a subscription is canceled immediately (i.e. before the end of the current billing period), we now render "Ended" without a date, because we don't store the cancellation date in the subscription object. We previously used "Ended {current_period_end}" which would sometimes lead to dates in the future - Bonus: refactored code and added unit tests --------- Co-authored-by: Sag --- .../components/gh-member-settings-form.hbs | 16 +- .../app/components/gh-member-settings-form.js | 39 +- ghost/admin/app/utils/subscription-data.js | 98 +++++ .../unit/utils/subscription-data-test.js | 341 ++++++++++++++++++ 4 files changed, 443 insertions(+), 51 deletions(-) create mode 100644 ghost/admin/app/utils/subscription-data.js create mode 100644 ghost/admin/tests/unit/utils/subscription-data-test.js diff --git a/ghost/admin/app/components/gh-member-settings-form.hbs b/ghost/admin/app/components/gh-member-settings-form.hbs index f1a7630274..d301aadf30 100644 --- a/ghost/admin/app/components/gh-member-settings-form.hbs +++ b/ghost/admin/app/components/gh-member-settings-form.hbs @@ -147,21 +147,7 @@ {{/if}} {{/if}} - {{#if sub.isComplimentary}} - {{#if sub.compExpiry}} - Expires {{sub.compExpiry}} - {{/if}} - {{else}} - {{#if sub.hasEnded}} - Ended {{sub.validUntil}} - {{else if sub.willEndSoon}} - Has access until {{sub.validUntil}} - {{else if sub.trialUntil}} - Ends {{sub.trialUntil}} - {{else}} - Renews {{sub.validUntil}} - {{/if}} - {{/if}} + {{sub.validityDetails}} diff --git a/ghost/admin/app/components/gh-member-settings-form.js b/ghost/admin/app/components/gh-member-settings-form.js index d7f4162dec..bcd9193bf5 100644 --- a/ghost/admin/app/components/gh-member-settings-form.js +++ b/ghost/admin/app/components/gh-member-settings-form.js @@ -1,8 +1,7 @@ import Component from '@glimmer/component'; -import moment from 'moment-timezone'; import {action} from '@ember/object'; import {didCancel, task} from 'ember-concurrency'; -import {getNonDecimal, getSymbol} from 'ghost-admin/utils/currency'; +import {getSubscriptionData} from 'ghost-admin/utils/subscription-data'; import {inject as service} from '@ember/service'; import {tracked} from '@glimmer/tracking'; @@ -60,41 +59,9 @@ export default class extends Component { return typeof value.id !== 'undefined' && self.findIndex(element => (element.tier_id || element.id) === (value.tier_id || value.id)) === index; }); - let subscriptionData = subscriptions.filter((sub) => { - return !!sub.price; - }).map((sub) => { - const periodEnded = sub.current_period_end && new Date(sub.current_period_end) < new Date(); - const data = { - ...sub, - attribution: { - ...sub.attribution, - referrerSource: sub.attribution?.referrer_source || 'Unknown', - referrerMedium: sub.attribution?.referrer_medium || '-' - }, - startDate: sub.start_date ? moment(sub.start_date).format('D MMM YYYY') : '-', - validUntil: sub.current_period_end ? moment(sub.current_period_end).format('D MMM YYYY') : '-', - hasEnded: sub.status === 'canceled' && periodEnded, - willEndSoon: sub.cancel_at_period_end || (sub.status === 'canceled' && !periodEnded), - cancellationReason: sub.cancellation_reason, - price: { - ...sub.price, - currencySymbol: getSymbol(sub.price.currency), - nonDecimalAmount: getNonDecimal(sub.price.amount) - }, - isComplimentary: !sub.id - }; - if (sub.trial_end_at) { - const inTrialMode = moment(sub.trial_end_at).isAfter(new Date(), 'day'); - if (inTrialMode) { - data.trialUntil = moment(sub.trial_end_at).format('D MMM YYYY'); - } - } + let subsWithPrice = subscriptions.filter(sub => !!sub.price); + let subscriptionData = subsWithPrice.map(sub => getSubscriptionData(sub)); - if (!sub.id && sub.tier?.expiry_at) { - data.compExpiry = moment(sub.tier.expiry_at).utc().format('D MMM YYYY'); - } - return data; - }); return tiers.map((tier) => { let tierSubscriptions = subscriptionData.filter((subscription) => { return subscription?.price?.tier?.tier_id === (tier.tier_id || tier.id); diff --git a/ghost/admin/app/utils/subscription-data.js b/ghost/admin/app/utils/subscription-data.js new file mode 100644 index 0000000000..347265ace4 --- /dev/null +++ b/ghost/admin/app/utils/subscription-data.js @@ -0,0 +1,98 @@ +import moment from 'moment-timezone'; +import {getNonDecimal, getSymbol} from 'ghost-admin/utils/currency'; + +export function getSubscriptionData(sub) { + const data = { + ...sub, + attribution: { + ...sub.attribution, + referrerSource: sub.attribution?.referrer_source || 'Unknown', + referrerMedium: sub.attribution?.referrer_medium || '-' + }, + startDate: sub.start_date ? moment(sub.start_date).format('D MMM YYYY') : '-', + validUntil: validUntil(sub), + hasEnded: isCanceled(sub), + willEndSoon: isSetToCancel(sub), + cancellationReason: sub.cancellation_reason, + price: { + ...sub.price, + currencySymbol: getSymbol(sub.price.currency), + nonDecimalAmount: getNonDecimal(sub.price.amount) + }, + isComplimentary: isComplimentary(sub), + compExpiry: compExpiry(sub), + trialUntil: trialUntil(sub) + }; + + data.validityDetails = validityDetails(data); + + return data; +} + +export function validUntil(sub) { + // If a subscription has been canceled immediately, don't render the end of validity date + // Reason: we don't store the exact cancelation date in the subscription object + if (sub.status === 'canceled' && !sub.cancel_at_period_end) { + return ''; + } + + // Otherwise, show the current period end date + if (sub.current_period_end) { + return moment(sub.current_period_end).format('D MMM YYYY'); + } + + return ''; +} + +export function isActive(sub) { + return ['active', 'trialing', 'past_due', 'unpaid'].includes(sub.status); +} + +export function isComplimentary(sub) { + return !sub.id; +} + +export function isCanceled(sub) { + return sub.status === 'canceled'; +} + +export function isSetToCancel(sub) { + return sub.cancel_at_period_end && isActive(sub); +} + +export function compExpiry(sub) { + if (!sub.id && sub.tier && sub.tier.expiry_at) { + return moment(sub.tier.expiry_at).utc().format('D MMM YYYY'); + } + + return undefined; +} + +export function trialUntil(sub) { + const inTrialMode = sub.trial_end_at && moment(sub.trial_end_at).isAfter(new Date(), 'day'); + if (inTrialMode) { + return moment(sub.trial_end_at).format('D MMM YYYY'); + } + + return undefined; +} + +export function validityDetails(data) { + if (data.isComplimentary && data.compExpiry) { + return `Expires ${data.compExpiry}`; + } + + if (data.hasEnded) { + return `Ended ${data.validUntil}`; + } + + if (data.willEndSoon) { + return `Has access until ${data.validUntil}`; + } + + if (data.trialUntil) { + return `Ends ${data.trialUntil}`; + } + + return `Renews ${data.validUntil}`; +} diff --git a/ghost/admin/tests/unit/utils/subscription-data-test.js b/ghost/admin/tests/unit/utils/subscription-data-test.js new file mode 100644 index 0000000000..be7a64bb78 --- /dev/null +++ b/ghost/admin/tests/unit/utils/subscription-data-test.js @@ -0,0 +1,341 @@ +import moment from 'moment-timezone'; +import {compExpiry, getSubscriptionData, isActive, isCanceled, isComplimentary, isSetToCancel, trialUntil, validUntil, validityDetails} from 'ghost-admin/utils/subscription-data'; +import {describe, it} from 'mocha'; +import {expect} from 'chai'; + +describe.only('Unit: Util: subscription-data', function () { + describe('validUntil', function () { + it('returns the end of the current billing period when the subscription is canceled at the end of the period', function () { + let sub = { + status: 'canceled', + cancel_at_period_end: true, + current_period_end: '2021-05-31' + }; + expect(validUntil(sub)).to.equal('31 May 2021'); + }); + + it('returns an empty string when the subscription is canceled immediately', function () { + let sub = { + status: 'canceled', + cancel_at_period_end: false, + current_period_end: '2021-05-31' + }; + expect(validUntil(sub)).to.equal(''); + }); + + it('returns the end of the current billing period when the subscription is active', function () { + let sub = { + status: 'active', + cancel_at_period_end: false, + current_period_end: '2021-05-31' + }; + expect(validUntil(sub)).to.equal('31 May 2021'); + }); + + it('returns the end of the current billing period when the subscription is in trial', function () { + let sub = { + status: 'trialing', + cancel_at_period_end: false, + current_period_end: '2021-05-31' + }; + expect(validUntil(sub)).to.equal('31 May 2021'); + }); + + it('returns the end of the current billing period when the subscription is past_due', function () { + let sub = { + status: 'past_due', + cancel_at_period_end: false, + current_period_end: '2021-05-31' + }; + expect(validUntil(sub)).to.equal('31 May 2021'); + }); + + it('returns the end of the current billing period when the subscription is unpaid', function () { + let sub = { + status: 'unpaid', + cancel_at_period_end: false, + current_period_end: '2021-05-31' + }; + expect(validUntil(sub)).to.equal('31 May 2021'); + }); + + // Extra data safety check, mainly for imported subscriptions + it('returns an empty string if the subcription is canceled immediately and has no current_period_start', function () { + let sub = { + status: 'canceled', + cancel_at_period_end: false + }; + expect(validUntil(sub)).to.equal(''); + }); + + // Extra data safety check, mainly for imported subscriptions + it('returns an empty string if the subscription has no current_period_end', function () { + let sub = { + status: 'active', + cancel_at_period_end: false + }; + expect(validUntil(sub)).to.equal(''); + }); + }); + + describe('isActive', function () { + it('returns true for active subscriptions', function () { + let sub = {status: 'active'}; + expect(isActive(sub)).to.be.true; + }); + + it('returns true for trialing subscriptions', function () { + let sub = {status: 'trialing'}; + expect(isActive(sub)).to.be.true; + }); + + it('returns true for past_due subscriptions', function () { + let sub = {status: 'past_due'}; + expect(isActive(sub)).to.be.true; + }); + + it('returns true for unpaid subscriptions', function () { + let sub = {status: 'unpaid'}; + expect(isActive(sub)).to.be.true; + }); + + it('returns false for canceled subscriptions', function () { + let sub = {status: 'canceled'}; + expect(isActive(sub)).to.be.false; + }); + }); + + describe('isComplimentary', function () { + it('returns true for complimentary subscriptions', function () { + let sub = {id: null}; + expect(isComplimentary(sub)).to.be.true; + }); + + it('returns false for paid subscriptions', function () { + let sub = {id: 'sub_123'}; + expect(isComplimentary(sub)).to.be.false; + }); + }); + + describe('isCanceled', function () { + it('returns true for canceled subscriptions', function () { + let sub = {status: 'canceled'}; + expect(isCanceled(sub)).to.be.true; + }); + + it('returns false for active subscriptions', function () { + let sub = {status: 'active'}; + expect(isCanceled(sub)).to.be.false; + }); + }); + + describe('isSetToCancel', function () { + it('returns true for subscriptions set to cancel at the end of the period', function () { + let sub = {status: 'active', cancel_at_period_end: true}; + expect(isSetToCancel(sub)).to.be.true; + }); + + it('returns false for canceled subscriptions', function () { + let sub = {status: 'canceled', cancel_at_period_end: true}; + expect(isSetToCancel(sub)).to.be.false; + }); + }); + + describe('trialUntil', function () { + it('returns the trial end date for subscriptions in trial', function () { + let sub = {status: 'trialing', trial_end_at: '2222-05-31'}; + expect(trialUntil(sub)).to.equal('31 May 2222'); + }); + + it('returns undefined for subscriptions not in trial', function () { + let sub = {status: 'active'}; + expect(trialUntil(sub)).to.be.undefined; + }); + }); + + describe('compExpiry', function () { + it('returns the complimentary expiry date for complimentary subscriptions', function () { + let sub = {id: null, tier: {expiry_at: moment.utc('2021-05-31').toISOString()}}; + expect(compExpiry(sub)).to.equal('31 May 2021'); + }); + + it('returns undefined for paid subscriptions', function () { + let sub = {id: 'sub_123'}; + expect(compExpiry(sub)).to.be.undefined; + }); + }); + + describe('validityDetails', function () { + it('returns "Expires {compExpiry}" for expired complimentary subscriptions', function () { + let data = { + isComplimentary: true, + compExpiry: '31 May 2021' + }; + expect(validityDetails(data)).to.equal('Expires 31 May 2021'); + }); + + it('returns "Ended {validUntil}" for canceled subscriptions', function () { + let data = { + hasEnded: true, + validUntil: '31 May 2021' + }; + expect(validityDetails(data)).to.equal('Ended 31 May 2021'); + }); + + it('returns "Has access until {validUntil}" for set to cancel subscriptions', function () { + let data = { + willEndSoon: true, + validUntil: '31 May 2021' + }; + expect(validityDetails(data)).to.equal('Has access until 31 May 2021'); + }); + + it('returns "Ends {validUntil}" for trial subscriptions', function () { + let data = { + trialUntil: '31 May 2021' + }; + expect(validityDetails(data)).to.equal('Ends 31 May 2021'); + }); + + it('returns "Renews {validUntil}" for active subscriptions', function () { + let data = { + validUntil: '31 May 2021' + }; + expect(validityDetails(data)).to.equal('Renews 31 May 2021'); + }); + }); + + describe('getSubscriptionData', function () { + it('returns the correct data for an active subscription', function () { + let sub = { + id: 'defined', + status: 'active', + cancel_at_period_end: false, + current_period_end: '2021-05-31', + trial_end_at: null, + tier: null, + price: { + currency: 'usd', + amount: 5000 + } + }; + let data = getSubscriptionData(sub); + + expect(data).to.include({ + isComplimentary: false, + compExpiry: undefined, + hasEnded: false, + validUntil: '31 May 2021', + willEndSoon: false, + trialUntil: undefined, + validityDetails: 'Renews 31 May 2021' + }); + }); + + it('returns the correct data for a trial subscription', function () { + let sub = { + id: 'defined', + status: 'trialing', + cancel_at_period_end: false, + current_period_end: '2222-05-31', + trial_end_at: '2222-05-31', + tier: null, + price: { + currency: 'usd', + amount: 5000 + } + }; + let data = getSubscriptionData(sub); + + expect(data).to.include({ + isComplimentary: false, + compExpiry: undefined, + hasEnded: false, + validUntil: '31 May 2222', + willEndSoon: false, + trialUntil: '31 May 2222', + validityDetails: 'Ends 31 May 2222' + }); + }); + + it('returns the correct data for an immediately canceled subscription', function () { + let sub = { + id: 'defined', + status: 'canceled', + cancel_at_period_end: false, + current_period_end: '2021-05-31', + trial_end_at: null, + tier: null, + price: { + currency: 'usd', + amount: 5000 + } + }; + let data = getSubscriptionData(sub); + + expect(data).to.include({ + isComplimentary: false, + compExpiry: undefined, + hasEnded: true, + validUntil: '', + willEndSoon: false, + trialUntil: undefined, + validityDetails: 'Ended ' + }); + }); + + it('returns the correct data for a subscription set to cancel at the end of the period', function () { + let sub = { + id: 'defined', + status: 'active', + cancel_at_period_end: true, + current_period_end: '2021-05-31', + trial_end_at: null, + tier: null, + price: { + currency: 'usd', + amount: 5000 + } + }; + let data = getSubscriptionData(sub); + + expect(data).to.include({ + isComplimentary: false, + compExpiry: undefined, + hasEnded: false, + validUntil: '31 May 2021', + willEndSoon: true, + trialUntil: undefined, + validityDetails: 'Has access until 31 May 2021' + }); + }); + + it('returns the correct data for a complimentary subscription', function () { + let sub = { + id: null, + status: 'active', + cancel_at_period_end: false, + current_period_end: '2021-05-31', + trial_end_at: null, + tier: { + expiry_at: moment.utc('2021-05-31').toISOString() + }, + price: { + currency: 'usd', + amount: 0 + } + }; + let data = getSubscriptionData(sub); + + expect(data).to.include({ + isComplimentary: true, + compExpiry: '31 May 2021', + hasEnded: false, + validUntil: '31 May 2021', + willEndSoon: false, + trialUntil: undefined, + validityDetails: 'Expires 31 May 2021' + }); + }); + }); +}); From 92a84f77fd1f3d9dcb03a874e55d8f9537cff81e Mon Sep 17 00:00:00 2001 From: Sag Date: Tue, 2 Jul 2024 16:10:23 +0200 Subject: [PATCH 061/131] Removed leftover .only on Admin unit tests (#20513) no issue --- ghost/admin/tests/unit/utils/subscription-data-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ghost/admin/tests/unit/utils/subscription-data-test.js b/ghost/admin/tests/unit/utils/subscription-data-test.js index be7a64bb78..5b396b20d8 100644 --- a/ghost/admin/tests/unit/utils/subscription-data-test.js +++ b/ghost/admin/tests/unit/utils/subscription-data-test.js @@ -3,7 +3,7 @@ import {compExpiry, getSubscriptionData, isActive, isCanceled, isComplimentary, import {describe, it} from 'mocha'; import {expect} from 'chai'; -describe.only('Unit: Util: subscription-data', function () { +describe('Unit: Util: subscription-data', function () { describe('validUntil', function () { it('returns the end of the current billing period when the subscription is canceled at the end of the period', function () { let sub = { From 3618632129a10c648fd5cb068713430caf6baa5e Mon Sep 17 00:00:00 2001 From: Sanne de Vries <65487235+sanne-san@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:26:12 +0200 Subject: [PATCH 062/131] Updated password updated successfully notification copy (#20512) REF DES-540 --- .../api/endpoints/utils/serializers/output/authentication.js | 2 +- .../api/admin/__snapshots__/authentication.test.js.snap | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/output/authentication.js b/ghost/core/core/server/api/endpoints/utils/serializers/output/authentication.js index d041a915b5..a3410985a3 100644 --- a/ghost/core/core/server/api/endpoints/utils/serializers/output/authentication.js +++ b/ghost/core/core/server/api/endpoints/utils/serializers/output/authentication.js @@ -4,7 +4,7 @@ const debug = require('@tryghost/debug')('api:endpoints:utils:serializers:output const messages = { checkEmailForInstructions: 'Check your email for further instructions.', - passwordChanged: 'Password changed successfully.', + passwordChanged: 'Password updated', invitationAccepted: 'Invitation accepted.' }; diff --git a/ghost/core/test/regression/api/admin/__snapshots__/authentication.test.js.snap b/ghost/core/test/regression/api/admin/__snapshots__/authentication.test.js.snap index 3193220da6..825e3e030b 100644 --- a/ghost/core/test/regression/api/admin/__snapshots__/authentication.test.js.snap +++ b/ghost/core/test/regression/api/admin/__snapshots__/authentication.test.js.snap @@ -754,7 +754,7 @@ exports[`Authentication API Password reset reset password 1: [body] 1`] = ` Object { "password_reset": Array [ Object { - "message": "Password changed successfully.", + "message": "Password updated", }, ], } @@ -764,7 +764,7 @@ exports[`Authentication API Password reset reset password 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": "65", + "content-length": "51", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, From fe31ee34e8c4dce6bfdd81ef8960c5c2fefd0518 Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Tue, 2 Jul 2024 09:27:44 -0500 Subject: [PATCH 063/131] Revert "Improved performance in Admin Posts view (#20503)" (#20514) ref https://linear.app/tryghost/issue/ONC-111 This reverts commit 3d9d5522719fafc99d6a354862f10c2e606b782b. This commit broke bulk post actions which we do not have tests for, so we will need to address that as well as add tests. --- .../admin/app/components/posts-list/list.hbs | 41 ++++------------- ghost/admin/app/routes/posts.js | 45 ++++++------------- ghost/admin/app/templates/posts.hbs | 21 ++------- ghost/admin/tests/acceptance/content-test.js | 39 +++++++++------- 4 files changed, 49 insertions(+), 97 deletions(-) diff --git a/ghost/admin/app/components/posts-list/list.hbs b/ghost/admin/app/components/posts-list/list.hbs index b1ab3acd99..4755c76d62 100644 --- a/ghost/admin/app/components/posts-list/list.hbs +++ b/ghost/admin/app/components/posts-list/list.hbs @@ -1,39 +1,14 @@ - {{!-- always order as scheduled, draft, remainder --}} - {{#if (or @model.scheduledPosts (or @model.draftPosts @model.publishedAndSentPosts))}} - {{#if @model.scheduledPosts}} - {{#each @model.scheduledPosts as |post|}} - - - - {{/each}} - {{/if}} - {{#if (and @model.draftPosts (or (not @model.scheduledPosts) (and @model.scheduledPosts @model.scheduledPosts.reachedInfinity)))}} - {{#each @model.draftPosts as |post|}} - - - - {{/each}} - {{/if}} - {{#if (and @model.publishedAndSentPosts (and (or (not @model.scheduledPosts) @model.scheduledPosts.reachedInfinity) (or (not @model.draftPosts) @model.draftPosts.reachedInfinity)))}} - {{#each @model.publishedAndSentPosts as |post|}} - - - - {{/each}} - {{/if}} + {{#each @model as |post|}} + + + {{else}} {{yield}} - {{/if}} + {{/each}} {{!-- The currently selected item or items are passed to the context menu --}} diff --git a/ghost/admin/app/routes/posts.js b/ghost/admin/app/routes/posts.js index f4e6966403..93e7d5d4ab 100644 --- a/ghost/admin/app/routes/posts.js +++ b/ghost/admin/app/routes/posts.js @@ -1,5 +1,4 @@ import AuthenticatedRoute from 'ghost-admin/routes/authenticated'; -import RSVP from 'rsvp'; import {action} from '@ember/object'; import {assign} from '@ember/polyfills'; import {isBlank} from '@ember/utils'; @@ -47,46 +46,36 @@ export default class PostsRoute extends AuthenticatedRoute { totalPagesParam: 'meta.pagination.pages' }; - // type filters are actually mapping statuses assign(filterParams, this._getTypeFilters(params.type)); if (params.type === 'featured') { filterParams.featured = true; } - // authors and contributors can only view their own posts if (user.isAuthor) { + // authors can only view their own posts filterParams.authors = user.slug; } else if (user.isContributor) { + // Contributors can only view their own draft posts filterParams.authors = user.slug; - // otherwise we need to filter by author if present + // filterParams.status = 'draft'; } else if (params.author) { filterParams.authors = params.author; } + let filter = this._filterString(filterParams); + if (!isBlank(filter)) { + queryParams.filter = filter; + } + + if (!isBlank(params.order)) { + queryParams.order = params.order; + } + let perPage = this.perPage; + let paginationSettings = assign({perPage, startingPage: 1}, paginationParams, queryParams); - const filterStatuses = filterParams.status; - let models = {}; - if (filterStatuses.includes('scheduled')) { - let scheduledPostsParams = {...queryParams, order: params.order || 'published_at desc', filter: this._filterString({...filterParams, status: 'scheduled'})}; - models.scheduledPosts = this.infinity.model('post', assign({perPage, startingPage: 1}, paginationParams, scheduledPostsParams)); - } - if (filterStatuses.includes('draft')) { - let draftPostsParams = {...queryParams, order: params.order || 'updated_at desc', filter: this._filterString({...filterParams, status: 'draft'})}; - models.draftPosts = this.infinity.model('post', assign({perPage, startingPage: 1}, paginationParams, draftPostsParams)); - } - if (filterStatuses.includes('published') || filterStatuses.includes('sent')) { - let publishedAndSentPostsParams; - if (filterStatuses.includes('published') && filterStatuses.includes('sent')) { - publishedAndSentPostsParams = {...queryParams, order: params.order || 'published_at desc', filter: this._filterString({...filterParams, status: '[published,sent]'})}; - } else { - publishedAndSentPostsParams = {...queryParams, order: params.order || 'published_at desc', filter: this._filterString({...filterParams, status: filterStatuses.includes('published') ? 'published' : 'sent'})}; - } - models.publishedAndSentPosts = this.infinity.model('post', assign({perPage, startingPage: 1}, paginationParams, publishedAndSentPostsParams)); - } - - return RSVP.hash(models); + return this.infinity.model(this.modelName, paginationSettings); } // trigger a background load of all tags and authors for use in filter dropdowns @@ -131,12 +120,6 @@ export default class PostsRoute extends AuthenticatedRoute { }; } - /** - * Returns an object containing the status filter based on the given type. - * - * @param {string} type - The type of filter to generate (draft, published, scheduled, sent). - * @returns {Object} - An object containing the status filter. - */ _getTypeFilters(type) { let status = '[draft,scheduled,published,sent]'; diff --git a/ghost/admin/app/templates/posts.hbs b/ghost/admin/app/templates/posts.hbs index abe3f88a47..f0d0b6bbe8 100644 --- a/ghost/admin/app/templates/posts.hbs +++ b/ghost/admin/app/templates/posts.hbs @@ -30,7 +30,7 @@
  • @@ -51,26 +51,11 @@
  • - {{!-- only show one infinity loader wheel at a time - always order as scheduled, draft, remainder --}} - {{#if @model.scheduledPosts}} - {{/if}} - {{#if (and @model.draftPosts (or (not @model.scheduledPosts) (and @model.scheduledPosts @model.scheduledPosts.reachedInfinity)))}} - - {{/if}} - {{#if (and @model.publishedAndSentPosts (and (or (not @model.scheduledPosts) @model.scheduledPosts.reachedInfinity) (or (not @model.draftPosts) @model.draftPosts.reachedInfinity)))}} - - {{/if}} -
    + {{outlet}} diff --git a/ghost/admin/tests/acceptance/content-test.js b/ghost/admin/tests/acceptance/content-test.js index 996f4eec00..da312358da 100644 --- a/ghost/admin/tests/acceptance/content-test.js +++ b/ghost/admin/tests/acceptance/content-test.js @@ -1,6 +1,6 @@ import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; import {beforeEach, describe, it} from 'mocha'; -import {blur, click, currentURL, fillIn, find, findAll, visit} from '@ember/test-helpers'; +import {blur, click, currentURL, fillIn, find, findAll, settled, visit} from '@ember/test-helpers'; import {clickTrigger, selectChoose} from 'ember-power-select/test-support/helpers'; import {expect} from 'chai'; import {setupApplicationTest} from 'ember-mocha'; @@ -41,7 +41,7 @@ describe('Acceptance: Content', function () { return await authenticateSession(); }); - it('displays and filters posts', async function () { + it.skip('displays and filters posts', async function () { await visit('/posts'); // Not checking request here as it won't be the last request made // Displays all posts + pages @@ -81,29 +81,38 @@ describe('Acceptance: Content', function () { // show all posts await selectChoose('[data-test-type-select]', 'All posts'); - // Posts are ordered scheduled -> draft -> published/sent - // check API request is correct - we submit one request for scheduled, one for drafts, and one for published+sent - [lastRequest] = this.server.pretender.handledRequests.slice(-3); - expect(lastRequest.queryParams.filter, '"all" request status filter').to.have.string('status:scheduled'); - [lastRequest] = this.server.pretender.handledRequests.slice(-2); - expect(lastRequest.queryParams.filter, '"all" request status filter').to.have.string('status:draft'); + // API request is correct [lastRequest] = this.server.pretender.handledRequests.slice(-1); - expect(lastRequest.queryParams.filter, '"all" request status filter').to.have.string('status:[published,sent]'); - - // check order display is correct - let postIds = findAll('[data-test-post-id]').map(el => el.getAttribute('data-test-post-id')); - expect(postIds, 'post order').to.deep.equal([scheduledPost.id, draftPost.id, publishedPost.id, authorPost.id]); + expect(lastRequest.queryParams.filter, '"all" request status filter').to.have.string('status:[draft,scheduled,published]'); // show all posts by editor - await selectChoose('[data-test-type-select]', 'Published posts'); await selectChoose('[data-test-author-select]', editor.name); // API request is correct [lastRequest] = this.server.pretender.handledRequests.slice(-1); expect(lastRequest.queryParams.filter, '"editor" request status filter') - .to.have.string('status:published'); + .to.have.string('status:[draft,scheduled,published]'); expect(lastRequest.queryParams.filter, '"editor" request filter param') .to.have.string(`authors:${editor.slug}`); + + // Post status is only visible when members is enabled + expect(find('[data-test-visibility-select]'), 'access dropdown before members enabled').to.not.exist; + let featureService = this.owner.lookup('service:feature'); + featureService.set('members', true); + await settled(); + expect(find('[data-test-visibility-select]'), 'access dropdown after members enabled').to.exist; + + await selectChoose('[data-test-visibility-select]', 'Paid members-only'); + [lastRequest] = this.server.pretender.handledRequests.slice(-1); + expect(lastRequest.queryParams.filter, '"visibility" request filter param') + .to.have.string('visibility:[paid,tiers]+status:[draft,scheduled,published]'); + + // Displays editor post + // TODO: implement "filter" param support and fix mirage post->author association + // expect(find('[data-test-post-id]').length, 'editor post count').to.equal(1); + // expect(find(`[data-test-post-id="${authorPost.id}"]`), 'author post').to.exist; + + // TODO: test tags dropdown }); // TODO: skipped due to consistently random failures on Travis From a046ee324ee68d31a87c60a2b535fd7c4f5fa3c7 Mon Sep 17 00:00:00 2001 From: Sanne de Vries <65487235+sanne-san@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:37:56 +0200 Subject: [PATCH 064/131] Fix scroll on settings page for editor users (#20516) REF DES-352 --- apps/admin-x-settings/src/MainContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/admin-x-settings/src/MainContent.tsx b/apps/admin-x-settings/src/MainContent.tsx index 45bafb385a..31898d9b56 100644 --- a/apps/admin-x-settings/src/MainContent.tsx +++ b/apps/admin-x-settings/src/MainContent.tsx @@ -61,7 +61,7 @@ const MainContent: React.FC = () => { if (isEditorUser(currentUser)) { return ( -
    +
    Settings
    From b36c2356fc16f4ff11bcf94131d44d01073f0c08 Mon Sep 17 00:00:00 2001 From: Michael Barrett Date: Tue, 2 Jul 2024 16:00:19 +0100 Subject: [PATCH 065/131] Added custom redirects ReDoS validation (#20515) refs [ENG-709](https://linear.app/tryghost/issue/ENG-709/%F0%9F%90%9B-bad-redirects-causing-container-tear-down) Added validation to prevent RegEx's susceptible to ReDoS from being used with custom redirects. Also moved error details out of `context` and into `errorDetails` to be consistent with error logging elsewhere as well as fix issue in admin-x where blank screen would be shown when an error occurred during redirects upload (due to logic not accounting for `context` being an object) --- .../services/custom-redirects/validation.js | 24 ++++++++++++++++--- ghost/core/package.json | 1 + .../custom-redirects/validation.test.js | 20 ++++++++++++++++ yarn.lock | 14 +++++++++++ 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/ghost/core/core/server/services/custom-redirects/validation.js b/ghost/core/core/server/services/custom-redirects/validation.js index aaba74b0f7..1107a01c42 100644 --- a/ghost/core/core/server/services/custom-redirects/validation.js +++ b/ghost/core/core/server/services/custom-redirects/validation.js @@ -1,6 +1,7 @@ const _ = require('lodash'); const tpl = require('@tryghost/tpl'); const errors = require('@tryghost/errors'); +const {isSafePattern} = require('redos-detector'); const messages = { redirectsWrongFormat: 'Incorrect redirects file format.', @@ -33,18 +34,35 @@ const validate = (redirects) => { if (!redirect.from || !redirect.to) { throw new errors.ValidationError({ message: tpl(messages.redirectsWrongFormat), - context: redirect, help: tpl(messages.redirectsHelp) }); } + // Ensure valid regex try { - // each 'from' property should be a valid RegExp string new RegExp(redirect.from); } catch (error) { throw new errors.ValidationError({ message: tpl(messages.invalidRedirectsFromRegex), - context: redirect, + errorDetails: { + redirect, + invalid: true + }, + help: tpl(messages.redirectsHelp) + }); + } + + // Ensure safe regex + const analysis = isSafePattern(redirect.from); + + if (analysis.safe === false) { + throw new errors.ValidationError({ + message: tpl(messages.invalidRedirectsFromRegex), + errorDetails: { + redirect, + unsafe: true, + reason: analysis.error + }, help: tpl(messages.redirectsHelp) }); } diff --git a/ghost/core/package.json b/ghost/core/package.json index b3040354f9..b724bf19f4 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -219,6 +219,7 @@ "node-jose": "2.2.0", "path-match": "1.2.4", "probe-image-size": "7.2.3", + "redos-detector": "5.1.0", "rss": "1.2.2", "sanitize-html": "2.13.0", "semver": "7.6.2", diff --git a/ghost/core/test/unit/server/services/custom-redirects/validation.test.js b/ghost/core/test/unit/server/services/custom-redirects/validation.test.js index a3a6f35b7a..a0e61861b9 100644 --- a/ghost/core/test/unit/server/services/custom-redirects/validation.test.js +++ b/ghost/core/test/unit/server/services/custom-redirects/validation.test.js @@ -39,6 +39,26 @@ describe('UNIT: custom redirects validation', function () { should.fail('should have thrown'); } catch (err) { err.message.should.equal('Incorrect RegEx in redirects file.'); + err.errorDetails.redirect.should.equal(config[0]); + err.errorDetails.invalid.should.be.true(); + } + }); + + it('throws for an invalid redirects config having unsafe RegExp in from field', function () { + const config = [{ + permanent: true, + from: '^\/episodes\/([a-z0-9-]+)+\/$', // Unsafe due to the surplus + at the end causing infinite backtracking + to: '/' + }]; + + try { + validate(config); + should.fail('should have thrown'); + } catch (err) { + err.message.should.equal('Incorrect RegEx in redirects file.'); + err.errorDetails.redirect.should.equal(config[0]); + err.errorDetails.unsafe.should.be.true(); + err.errorDetails.reason.should.equal('hitMaxBacktracks'); } }); diff --git a/yarn.lock b/yarn.lock index 6dbe1d4f36..c46e9870f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27331,6 +27331,13 @@ redis-parser@^3.0.0: dependencies: redis-errors "^1.0.0" +redos-detector@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/redos-detector/-/redos-detector-5.1.0.tgz#67660c896a48490e80b35557f876a529680f0f8d" + integrity sha512-08en/ij0//HwKZdKlelRZGQKmQhmKMQPVJPD+1THfYm64mZhLPOG0NBa47+DrOF53DyaRsCFyD7JHJaYITnt9g== + dependencies: + regjsparser "0.10.0" + reflect-metadata@0.1.14: version "0.1.14" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.14.tgz#24cf721fe60677146bb77eeb0e1f9dece3d65859" @@ -27452,6 +27459,13 @@ regjsgen@^0.2.0: resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" integrity sha512-x+Y3yA24uF68m5GA+tBjbGYo64xXVJpbToBaWCoSNSc1hdk6dfctaRWrNFTVJZIIhL5GxW8zwjoixbnifnK59g== +regjsparser@0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.10.0.tgz#b1ed26051736b436f22fdec1c8f72635f9f44892" + integrity sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA== + dependencies: + jsesc "~0.5.0" + regjsparser@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" From bec647412fd13f4c5a312328985a47593aa3f351 Mon Sep 17 00:00:00 2001 From: Princi Vershwal Date: Tue, 2 Jul 2024 21:13:32 +0530 Subject: [PATCH 066/131] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20url=20decoding?= =?UTF-8?q?=20issue=20-=20=20URLs=20sent=20in=20emails=20containing=20a=20?= =?UTF-8?q?%=20can=20now=20be=20updated(#20518)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes https://linear.app/tryghost/issue/ENG-447/🐛-urls-sent-in-emails-containing-a-percent-can-not-be-updated URLs were decoded before making a search query to the db. This is the reason the `%2F` character gets converted to `/`. This decoding is not required. --- ghost/link-tracking/lib/LinkClickTrackingService.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/ghost/link-tracking/lib/LinkClickTrackingService.js b/ghost/link-tracking/lib/LinkClickTrackingService.js index 0c0e746e04..9b63729f10 100644 --- a/ghost/link-tracking/lib/LinkClickTrackingService.js +++ b/ghost/link-tracking/lib/LinkClickTrackingService.js @@ -105,9 +105,6 @@ class LinkClickTrackingService { * @throws {errors.BadRequestError} */ #parseLinkFilter(filter) { - // decode filter to manage any encoded uri components - filter = decodeURIComponent(filter); - try { const filterJson = nql(filter).parse(); const postId = filterJson?.$and?.[0]?.post_id; From 31ea0ba6a395372dff0f0086aa8da12fdf949a48 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:26:14 +0000 Subject: [PATCH 067/131] Update metascraper --- ghost/oembed-service/package.json | 16 +- yarn.lock | 323 +++++++++++------------------- 2 files changed, 120 insertions(+), 219 deletions(-) diff --git a/ghost/oembed-service/package.json b/ghost/oembed-service/package.json index d80318feba..8708ee9d69 100644 --- a/ghost/oembed-service/package.json +++ b/ghost/oembed-service/package.json @@ -30,15 +30,15 @@ "cheerio": "0.22.0", "iconv-lite": "0.6.3", "lodash": "4.17.21", - "metascraper": "5.41.0", - "metascraper-author": "5.42.5", - "metascraper-description": "5.42.0", - "metascraper-image": "5.42.0", - "metascraper-logo": "5.42.0", + "metascraper": "5.45.15", + "metascraper-author": "5.45.10", + "metascraper-description": "5.45.10", + "metascraper-image": "5.45.10", + "metascraper-logo": "5.45.10", "metascraper-logo-favicon": "5.42.0", - "metascraper-publisher": "5.42.0", - "metascraper-title": "5.42.0", - "metascraper-url": "5.40.0", + "metascraper-publisher": "5.45.10", + "metascraper-title": "5.45.10", + "metascraper-url": "5.45.10", "tough-cookie": "4.1.4" } } diff --git a/yarn.lock b/yarn.lock index c46e9870f9..bf82b1c0f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3573,15 +3573,15 @@ integrity sha512-IXQp8N68L2fkk7p7RckBBhT/KwAX04GooIGjwzmY5THQanQvsmJpYgwC7A1Io2XDXBJzlGelQkP/C1SRM/aq8w== "@keyvhq/core@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@keyvhq/core/-/core-2.0.0.tgz#518311a59dbc4d6b6fd56852c15f483edb9fc7c9" - integrity sha512-fi3+F7GNImn1j4r6UFhsHRwN8a05uhUlrbNWZgnkX0h1NzcBEPNNqqMOE4KSASJwH2e9Eh/jm+bEfto58csNgg== + version "2.1.1" + resolved "https://registry.npmjs.org/@keyvhq/core/-/core-2.1.1.tgz#438ea23a6a7af183af8fa6a194e1b334512c428d" + integrity sha512-wVnnVFWmtAvQP8v/Ugm8KSl4glrVZjb5uqVc1n5tbGzj45lZhG7F/YxCJ6qHGDfBtDEw5cp1nJ2qImdmaG/JEQ== dependencies: json-buffer "~3.0.1" "@keyvhq/memoize@~2.0.3": version "2.0.3" - resolved "https://registry.yarnpkg.com/@keyvhq/memoize/-/memoize-2.0.3.tgz#7833170048f0f1dbf808ec727a4dce33d29e9a36" + resolved "https://registry.npmjs.org/@keyvhq/memoize/-/memoize-2.0.3.tgz#7833170048f0f1dbf808ec727a4dce33d29e9a36" integrity sha512-ID5Br2OshdhyD4G0g1dm7915Ol2ee6RmCPMDSEh7QFtkbHrtzkgJfYS60na6tGWRBqViAMrCByBfD6uSinjAgg== dependencies: "@keyvhq/core" "^2.0.0" @@ -3728,31 +3728,32 @@ "@types/mdx" "^2.0.0" "@types/react" ">=16" -"@metascraper/helpers@^5.40.0", "@metascraper/helpers@^5.42.5": - version "5.42.5" - resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.42.5.tgz#196444c3be7fb288fe48a7f0a1a3bb4ecf4db05f" - integrity sha512-7uikENEzlIGflROdwCbyH+xzn59y/Yw2WeQRgZpIaapinVXnxkNCd8Ny3FsOkEHdCAna96eJV1Wf15s3iT8XlQ== +"@metascraper/helpers@^5.40.0", "@metascraper/helpers@^5.45.10": + version "5.45.10" + resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.45.10.tgz#59ced77f599767cb9a53ff231de81ef5b9b1cfe9" + integrity sha512-IyEAvhCmsjXu5+PC5Lyce5neC0cHHfqBqcVgv0H6+PtNYqfiZ+5a4QWez+PqSsNFWVV58FKAxkFm2nJg2AKOXA== dependencies: audio-extensions "0.0.0" - chrono-node "~2.6.4" + chrono-node "~2.7.6" condense-whitespace "~2.0.0" + data-uri-utils "~1.0.8" entities "~4.5.0" file-extension "~4.0.5" has-values "~2.0.1" image-extensions "~1.1.0" is-relative-url "~3.0.0" - is-uri "~1.2.4" + is-uri "~1.2.6" iso-639-3 "~2.2.0" isostring "0.0.1" - jsdom "~22.1.0" + jsdom "~24.1.0" lodash "~4.17.21" memoize-one "~6.0.0" microsoft-capitalize "~1.0.5" mime "~3.0.0" normalize-url "~6.1.0" - re2 "~1.20.6" + re2 "~1.21.0" smartquotes "~2.3.2" - tldts "~6.0.12" + tldts "~6.1.24" url-regex-safe "~4.0.0" video-extensions "~1.2.0" @@ -7529,11 +7530,6 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@tootallnate/once@2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" - integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== - "@tryghost/adapter-base-cache@0.1.12": version "0.1.12" resolved "https://registry.yarnpkg.com/@tryghost/adapter-base-cache/-/adapter-base-cache-0.1.12.tgz#1773df40d33878d14f6e07c42a9e32062e8880d5" @@ -9503,7 +9499,7 @@ dependencies: argparse "^2.0.1" -abab@^2.0.3, abab@^2.0.5, abab@^2.0.6: +abab@^2.0.3, abab@^2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== @@ -12879,10 +12875,10 @@ chrome-trace-event@^1.0.2: resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== -chrono-node@~2.6.4: - version "2.6.4" - resolved "https://registry.yarnpkg.com/chrono-node/-/chrono-node-2.6.4.tgz#c6532450d03bb41b5bd6f5509909bf158a739d9e" - integrity sha512-weCpfagfISvUMleIIqCi12AL9iQYn1ybX/6RB9qolynvHNvYlfdJete51uyB8TmwDTgEeKFEq0I5p/SHhOfhsw== +chrono-node@~2.7.6: + version "2.7.6" + resolved "https://registry.yarnpkg.com/chrono-node/-/chrono-node-2.7.6.tgz#46d338e5c515b4dcedc5b5f56b1239b0217bf4aa" + integrity sha512-yugKSRLHc6B6kXxm/DwNc94zhaddAjCSO9IOGH3w7NIWNM+gUoLl/2/XLndiw4I+XhU4H2LOhC5Ab2JjS6JWsA== dependencies: dayjs "^1.10.0" @@ -14109,13 +14105,6 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -cssstyle@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-3.0.0.tgz#17ca9c87d26eac764bb8cfd00583cff21ce0277a" - integrity sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg== - dependencies: - rrweb-cssom "^0.6.0" - cssstyle@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.0.1.tgz#ef29c598a1e90125c870525490ea4f354db0660a" @@ -14160,6 +14149,18 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-uri-to-buffer@~5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-5.0.1.tgz#db89a9e279c2ffe74f50637a59a32fb23b3e4d7c" + integrity sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg== + +data-uri-utils@~1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/data-uri-utils/-/data-uri-utils-1.0.8.tgz#4651354c3293b02d7de69996a39f20e12fade011" + integrity sha512-LHm6O/aHmTdSsIKGI6d/BJ8gQyBiai/5g57s1XKDHFecVWbq0HYlEXheohwiLbpsEHjpdHNf+D50Q/onMnNIYQ== + dependencies: + data-uri-to-buffer "~5.0.0" + data-urls@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" @@ -14169,15 +14170,6 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -data-urls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-4.0.0.tgz#333a454eca6f9a5b7b0f1013ff89074c3f522dd4" - integrity sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g== - dependencies: - abab "^2.0.6" - whatwg-mimetype "^3.0.0" - whatwg-url "^12.0.0" - data-urls@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde" @@ -14697,13 +14689,6 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" -domexception@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" - integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== - dependencies: - webidl-conversions "^7.0.0" - domhandler@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" @@ -19355,13 +19340,6 @@ html-encoding-sniffer@^2.0.1: dependencies: whatwg-encoding "^1.0.5" -html-encoding-sniffer@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" - integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== - dependencies: - whatwg-encoding "^2.0.0" - html-encoding-sniffer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448" @@ -19538,15 +19516,6 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" - integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== - dependencies: - "@tootallnate/once" "2" - agent-base "6" - debug "4" - http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" @@ -20594,10 +20563,10 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-uri@~1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-uri/-/is-uri-1.2.4.tgz#d098837e45701eaedce1b53de68b8102465fbd46" - integrity sha512-8sHi5gEARwpMYwJD9uSAkU9Bb7YkSagcM10EYqSe+osqOErXln4VL+EgLSG40e9lVTpcpygpvb9Z6ohZpECDGA== +is-uri@~1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/is-uri/-/is-uri-1.2.6.tgz#f8f5f55d9b997493f470bf10ca214a3838b1e818" + integrity sha512-kNciklu//Ki8BUmRseLTfG/WW55qDHavf3MKUic8wvXR3d7etbSMoQPTpjvDeLVekESSgJM4AG+BESIKU02u3A== dependencies: parse-uri "~1.0.3" punycode2 "~1.0.0" @@ -21181,7 +21150,7 @@ jscodeshift@^0.15.1: temp "^0.8.4" write-file-atomic "^2.3.0" -jsdom@24.1.0, jsdom@^24.0.0: +jsdom@24.1.0, jsdom@^24.0.0, jsdom@~24.1.0: version "24.1.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-24.1.0.tgz#0cffdabd42c506788bfecd160e8ac22d4387f971" integrity sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA== @@ -21241,35 +21210,6 @@ jsdom@^16.4.0: ws "^7.4.6" xml-name-validator "^3.0.0" -jsdom@~22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.1.0.tgz#0fca6d1a37fbeb7f4aac93d1090d782c56b611c8" - integrity sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw== - dependencies: - abab "^2.0.6" - cssstyle "^3.0.0" - data-urls "^4.0.0" - decimal.js "^10.4.3" - domexception "^4.0.0" - form-data "^4.0.0" - html-encoding-sniffer "^3.0.0" - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.1" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.4" - parse5 "^7.1.2" - rrweb-cssom "^0.6.0" - saxes "^6.0.0" - symbol-tree "^3.2.4" - tough-cookie "^4.1.2" - w3c-xmlserializer "^4.0.0" - webidl-conversions "^7.0.0" - whatwg-encoding "^2.0.0" - whatwg-mimetype "^3.0.0" - whatwg-url "^12.0.1" - ws "^8.13.0" - xml-name-validator "^4.0.0" - jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -23018,30 +22958,30 @@ merge@^1.2.0: resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== -metascraper-author@5.42.5: - version "5.42.5" - resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-5.42.5.tgz#c1e43222d998181e724777f38cbe7b595d3e9ca6" - integrity sha512-/pO1KxtutfWmXtI09i1S7b3PNkBc/i/6u6+Gv/bhuCg0xDlKlRYm02fMIzeJXzeh/UeWGoFDnSvenDPZT0KWjA== +metascraper-author@5.45.10: + version "5.45.10" + resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-5.45.10.tgz#c0ae6952513d7ba2fe4eff9dce8dbd073b565901" + integrity sha512-S00rvCOqoHrM3WzTRzeIg/TP5Pi35IqtFjsmYIhiqIzTlHHHWvsV2ZBnHjPQkwyu90kADWQh3Cem2BYYk1fvDw== dependencies: - "@metascraper/helpers" "^5.42.5" + "@metascraper/helpers" "^5.45.10" -metascraper-description@5.42.0: - version "5.42.0" - resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.42.0.tgz#1c0d4186b99b381e8ff94af063cd8d1effd03138" - integrity sha512-/n2mtoUtBc6/6eyv4C26c4VqyI10MNm8sSWY86n7CzLoVemXnMKDsDmdesRjaCmuipnVAbPlYKrNGng6zcCj+Q== +metascraper-description@5.45.10: + version "5.45.10" + resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.45.10.tgz#61588e6b1206fc60f54f2becccbdd1b0502263c0" + integrity sha512-SfIq7mvcU6QvX6RfdIdzvY37UBf1T59wflbEiSlMVMutuAFsx+2JhvlImK5eMDdH1SEL4bmgkmvAiE/oQYXD0g== dependencies: - "@metascraper/helpers" "^5.40.0" + "@metascraper/helpers" "^5.45.10" -metascraper-image@5.42.0: - version "5.42.0" - resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.42.0.tgz#c70b76d028dcdbce2b082eea0dc2cf6558c5947a" - integrity sha512-8Opdextb86J0HDMGR1+wWNRHT6jLS6WOkCe0RAEJ9xLoDqr+H9YPVopBAFNLodRwMSO5ZhO4mARHGUkyJUxvMA== +metascraper-image@5.45.10: + version "5.45.10" + resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.45.10.tgz#20fc0433604fbf559204e88e47d7faf3f82240bb" + integrity sha512-s7Kt3tGjVHmE2bYT+lGGFMZe4QeIYVwAdHvFT4JIRCDTDQaxCHfry4+HnGc6nnGiJHSyzgNX+4eZtzELP+pS4w== dependencies: - "@metascraper/helpers" "^5.40.0" + "@metascraper/helpers" "^5.45.10" metascraper-logo-favicon@5.42.0: version "5.42.0" - resolved "https://registry.yarnpkg.com/metascraper-logo-favicon/-/metascraper-logo-favicon-5.42.0.tgz#6fc0f750ed77e5839111b37b210f32bc92ce6d89" + resolved "https://registry.npmjs.org/metascraper-logo-favicon/-/metascraper-logo-favicon-5.42.0.tgz#6fc0f750ed77e5839111b37b210f32bc92ce6d89" integrity sha512-Fk9OQn9WBpKOeIYHs+73z7jd8RoHxU+HtBHM4tY88lvHozEXA7qIUl07NM3rURLJo8vQNT7zXcJ1l7VCHDfqfQ== dependencies: "@keyvhq/memoize" "~2.0.3" @@ -23049,44 +22989,44 @@ metascraper-logo-favicon@5.42.0: lodash "~4.17.21" reachable-url "~1.7.1" -metascraper-logo@5.42.0: - version "5.42.0" - resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.42.0.tgz#53a432d7e34415e1c2b5272629d1dfc38f5ff632" - integrity sha512-Plrr0XXaKAf2pKwTf1T6HYSRolXZWxr190Yv2uj7eG5VGuewp8eQh4VvnHP8AHhYlT+siQD/8CwhhEF9fg179g== +metascraper-logo@5.45.10: + version "5.45.10" + resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.45.10.tgz#c0789e8f506c0138dbff7017f5ea602267e3e91f" + integrity sha512-HpIUuczIgefUgmlpDRLRzgYE4P0gD7Z2VsgSI5Qa8sgXqhEGC45UPn6J+LaQuhTaBirVNCHhgqX5uHG4EPDhAw== dependencies: - "@metascraper/helpers" "^5.40.0" + "@metascraper/helpers" "^5.45.10" lodash "~4.17.21" -metascraper-publisher@5.42.0: - version "5.42.0" - resolved "https://registry.yarnpkg.com/metascraper-publisher/-/metascraper-publisher-5.42.0.tgz#cf5e19d9395bebce924eb637c8ca49bfcc3b1128" - integrity sha512-Ff7hkhY8HPpa695vTBFfuF1fXtDFwkwX2aTZb+5MsTTuMoJTJEVkGACUc3lzwMzrnrIx3NS4gC1SXz3tO8rnIQ== +metascraper-publisher@5.45.10: + version "5.45.10" + resolved "https://registry.yarnpkg.com/metascraper-publisher/-/metascraper-publisher-5.45.10.tgz#31ecaa735da8a05314af90cefa7161051522f945" + integrity sha512-LW5TrmeA3TNPqTCaSmDrs+WuID56OJaqZZffIeG/VtataeoLBPr5rr1c5pN+YNsB8RqW7Y8HpNBDlxs4GdkNuA== dependencies: - "@metascraper/helpers" "^5.40.0" + "@metascraper/helpers" "^5.45.10" -metascraper-title@5.42.0: - version "5.42.0" - resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.42.0.tgz#778caa253502358bc246042635546b7bfced6c1a" - integrity sha512-sd+UzbJfWGG3yOrXH/TkwG4Z8W42SYKemmYPSUvY0I5HJ0KQreVdMTzQLG2s4r6oAhSMoe69gf+7iY/A9hUVWw== +metascraper-title@5.45.10: + version "5.45.10" + resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.45.10.tgz#8d3c1b374647508b445fc122c980911de12696bb" + integrity sha512-GGlA7zqkHOe1mAIeRm+V3cHdvveqNO1vX6US8+COorRj9q0tyOMfBFnvmpZp6wE/bX8yGk/r1PVKOdDvAMzBTA== dependencies: - "@metascraper/helpers" "^5.40.0" + "@metascraper/helpers" "^5.45.10" -metascraper-url@5.40.0: - version "5.40.0" - resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.40.0.tgz#d148880babc8d36de9b4e77cbaca9b23fdcfaddb" - integrity sha512-4mVpStLcXFekKZGeVWg07IY+CrsmEkVVqXdTw5+cTDUq3sxK13l73/qJQkqGMAFgGnfK0jbS+rkoWJ7VFnD6yg== +metascraper-url@5.45.10: + version "5.45.10" + resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.45.10.tgz#b7498db4ce2d592fad4bc68a4a3831f11f6ae0c0" + integrity sha512-rlT5I1W06U/WPzM6CR5i+IIm9WYi6eyLGanLDD1C+pMBZqptSqWO9lWBDJQmqZGEuWKMqD7sMij9vZ+IZPBdFQ== dependencies: - "@metascraper/helpers" "^5.40.0" + "@metascraper/helpers" "^5.45.10" -metascraper@5.41.0: - version "5.41.0" - resolved "https://registry.yarnpkg.com/metascraper/-/metascraper-5.41.0.tgz#b89a06af1805729e7bffcc77616280311c7fda71" - integrity sha512-Hb51/Vz6MxpowlK0De/j3L4ez5oHjhLDy4+pOIkpmrko9v+PDRu5HSsSh3YFhIFvf6yM4/AHz6OWtb+utmU1bg== +metascraper@5.45.15: + version "5.45.15" + resolved "https://registry.yarnpkg.com/metascraper/-/metascraper-5.45.15.tgz#08ecde5a3ebb3ca3c783fac295940fe92dd85cb4" + integrity sha512-TzJIMHkhfhEEuA18CBE+vyov1ESYbpuMzIc/LILHWwpdWWEwmABh/pEX1AD3dsSLCMbcDLQUd4imlhAS6yqxdw== dependencies: - "@metascraper/helpers" "^5.40.0" + "@metascraper/helpers" "^5.45.10" cheerio "~1.0.0-rc.12" lodash "~4.17.21" - whoops "~4.1.4" + whoops "~4.1.7" methods@^1.1.2, methods@~1.1.2: version "1.1.2" @@ -23756,10 +23696,10 @@ named-placeholders@^1.1.3: dependencies: lru-cache "^7.14.1" -nan@^2.12.1, nan@^2.14.0, nan@^2.18.0: - version "2.18.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" - integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== +nan@^2.12.1, nan@^2.14.0, nan@^2.20.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" + integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw== nanoclone@^0.2.1: version "0.2.1" @@ -23979,10 +23919,10 @@ node-gyp@8.x: tar "^6.1.2" which "^2.0.2" -node-gyp@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-10.0.1.tgz#205514fc19e5830fa991e4a689f9e81af377a966" - integrity sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg== +node-gyp@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-10.1.0.tgz#75e6f223f2acb4026866c26a2ead6aab75a8ca7e" + integrity sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA== dependencies: env-paths "^2.2.0" exponential-backoff "^3.1.1" @@ -24307,7 +24247,7 @@ numbered@^1.1.0: resolved "https://registry.yarnpkg.com/numbered/-/numbered-1.1.0.tgz#9fcd79564c73a84b9574e8370c3d8e58fe3c133c" integrity sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g== -nwsapi@^2.2.0, nwsapi@^2.2.10, nwsapi@^2.2.4: +nwsapi@^2.2.0, nwsapi@^2.2.10: version "2.2.10" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.10.tgz#0b77a68e21a0b483db70b11fad055906e867cda8" integrity sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ== @@ -26786,7 +26726,7 @@ punycode@^1.2.4: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== -punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0, punycode@^2.3.1: +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -26965,19 +26905,19 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -re2@~1.20.6: - version "1.20.9" - resolved "https://registry.yarnpkg.com/re2/-/re2-1.20.9.tgz#3e6e5b73cdd911cdbdfe5133cc6670600e33871b" - integrity sha512-ZYcPTFr5ha2xq3WQjBDTF9CWPSDK1z28MLh5UFRxc//7X8BNQ3A7yR7ITnP0jO346661ertdKVFqw1qoL3FMEQ== +re2@~1.21.0: + version "1.21.3" + resolved "https://registry.yarnpkg.com/re2/-/re2-1.21.3.tgz#4bced725a837cbe73d7d42b702a0d46671e6d00a" + integrity sha512-GI+KoGkHT4kxTaX+9p0FgNB1XUnCndO9slG5qqeEoZ7kbf6Dk6ohQVpmwKVeSp7LPLn+g6Q3BaCopz4oHuBDuQ== dependencies: install-artifact-from-github "^1.3.5" - nan "^2.18.0" - node-gyp "^10.0.1" + nan "^2.20.0" + node-gyp "^10.1.0" reachable-url@~1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/reachable-url/-/reachable-url-1.7.1.tgz#4d56f534d088c084e7d9884dafadf2c43d074c66" - integrity sha512-SCk7V5cwUw246mPhJ7uaqMcxa5iJNlzfztCvcDK1EgqexW2ghLWWrAGZCBNg/lT0ZqjJnLcsKPfGWxmmoABvOw== + version "1.7.2" + resolved "https://registry.npmjs.org/reachable-url/-/reachable-url-1.7.2.tgz#15b9eee6dd4ec390bc9ad7c4e8acf9079f61f27d" + integrity sha512-aJHwaTbbLjYcbMmzD/6xQ785vbEs8TTwoUg/Y/+faSk0TxPcsUxFk9bwsYRyDJID/krm1ZATBn3U+JSP1Vv2Bg== dependencies: got "~11.8.0" p-reflect "~2.1.0" @@ -30135,17 +30075,17 @@ tlds@^1.242.0: resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.242.0.tgz#da136a9c95b0efa1a4cd57dca8ef240c08ada4b7" integrity sha512-aP3dXawgmbfU94mA32CJGHmJUE1E58HCB1KmlKRhBNtqBL27mSQcAEmcaMaQ1Za9kIVvOdbxJD3U5ycDy7nJ3w== -tldts-core@^6.0.14: - version "6.0.14" - resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.0.14.tgz#66a325500fc46826b85719b588715f23e167b0e7" - integrity sha512-ESYhU/bgs6jiHlnl5h029f+0dB7EKRiTaxM/jHLZ6powScbmsgsrFcFjmyrjDgCvI/BRY79TEBBClmqLNEPyjQ== +tldts-core@^6.1.30: + version "6.1.30" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.30.tgz#96ea6ae4064bee4611795bf28bdcfba347a95baa" + integrity sha512-CPlL58/oIvnovk5KTHIho/B0bMuvPkZrcC7f4pfQH+BBPY/mMz6CekiIdhjFxk9XZZJNirbwh1rRTSo4e5KXQA== -tldts@~6.0.12: - version "6.0.14" - resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.0.14.tgz#bd126027d456b61e833aa4a49b85f4e7074c3f97" - integrity sha512-mYU7xwVGfiiC4lkWr4h3Q6U4kfAq3aWP1KsJZyRlVVeDQ3ZSBLmE20543dWSqI0U799PNzhpHObex5n60TeBGw== +tldts@~6.1.24: + version "6.1.30" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.30.tgz#75d6481e9729dd779f67ce7e7e68f8eddfd26d74" + integrity sha512-NErlfxa+LPJynXZ07f86N6ylkXhYaOL4rB2k+qwF69cdvol1IJHmlouXE8c7Hrqr1BiYNWL4qbdTxaX+szfOpQ== dependencies: - tldts-core "^6.0.14" + tldts-core "^6.1.30" tmp@0.0.28: version "0.0.28" @@ -30284,7 +30224,7 @@ totalist@^3.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.0.tgz#4ef9c58c5f095255cdc3ff2a0a55091c57a3a1bd" integrity sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw== -tough-cookie@4.1.4, tough-cookie@^4.0.0, tough-cookie@^4.1.2, tough-cookie@^4.1.4: +tough-cookie@4.1.4, tough-cookie@^4.0.0, tough-cookie@^4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== @@ -30309,13 +30249,6 @@ tr46@^2.1.0: dependencies: punycode "^2.1.1" -tr46@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" - integrity sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw== - dependencies: - punycode "^2.3.0" - tr46@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec" @@ -31361,13 +31294,6 @@ w3c-xmlserializer@^2.0.0: dependencies: xml-name-validator "^3.0.0" -w3c-xmlserializer@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" - integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== - dependencies: - xml-name-validator "^4.0.0" - w3c-xmlserializer@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" @@ -31591,13 +31517,6 @@ whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-encoding@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" - integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== - dependencies: - iconv-lite "0.6.3" - whatwg-encoding@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" @@ -31615,24 +31534,11 @@ whatwg-mimetype@^2.3.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== -whatwg-mimetype@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" - integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== - whatwg-mimetype@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== -whatwg-url@^12.0.0, whatwg-url@^12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-12.0.1.tgz#fd7bcc71192e7c3a2a97b9a8d6b094853ed8773c" - integrity sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ== - dependencies: - tr46 "^4.1.1" - webidl-conversions "^7.0.0" - whatwg-url@^14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.0.0.tgz#00baaa7fd198744910c4b1ef68378f2200e4ceb6" @@ -31712,10 +31618,10 @@ which@^4.0.0: dependencies: isexe "^3.1.1" -whoops@~4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/whoops/-/whoops-4.1.4.tgz#f7981b697f76e229dd9c01dbf379a3cbc7b12f7e" - integrity sha512-SXjaHhIbfLTLshNIXZ7/Gb76NUKsSvO0r0UpHGZiecWCi7zkKk9Og7Fy2s01xp5Co3R39nUhzYWdAu5FNGvMbg== +whoops@~4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/whoops/-/whoops-4.1.7.tgz#7579470180f1d0ec16fd67e952f4f602966b694d" + integrity sha512-weRPO7XE2Oko2/KQkddqvj6uAjLDOoiJdrXiE2ymgiV5Ugefi0/GJZPkeNr6OUUMfcqGAwUUPIZrG6UdSuBk2g== dependencies: clean-stack "~3.0.0" mimic-fn "~3.1.0" @@ -31860,7 +31766,7 @@ write-file-atomic@^5.0.1: imurmurhash "^0.1.4" signal-exit "^4.0.1" -ws@8.17.1, ws@^8.13.0, ws@^8.17.0, ws@^8.2.3: +ws@8.17.1, ws@^8.17.0, ws@^8.2.3: version "8.17.1" resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== @@ -31897,11 +31803,6 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== -xml-name-validator@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" - integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== - xml-name-validator@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" From 7fde7665e223426eb47f77acd9ddb521b9a03ec4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:05:22 +0000 Subject: [PATCH 068/131] Update dependency @uiw/react-codemirror to v4.22.2 --- apps/admin-x-design-system/package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/admin-x-design-system/package.json b/apps/admin-x-design-system/package.json index 1145c74f94..1b075723a9 100644 --- a/apps/admin-x-design-system/package.json +++ b/apps/admin-x-design-system/package.json @@ -60,7 +60,7 @@ "@sentry/react": "7.118.0", "@tailwindcss/forms": "0.5.7", "@tailwindcss/line-clamp": "0.4.4", - "@uiw/react-codemirror": "4.22.1", + "@uiw/react-codemirror": "4.22.2", "autoprefixer": "10.4.19", "clsx": "2.1.1", "postcss": "8.4.39", diff --git a/yarn.lock b/yarn.lock index bf82b1c0f6..96c4fbcbee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9055,10 +9055,10 @@ "@typescript-eslint/types" "6.9.1" eslint-visitor-keys "^3.4.1" -"@uiw/codemirror-extensions-basic-setup@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.22.1.tgz#d1cf724022187069aa36926f36e19ccdd5cd20e1" - integrity sha512-Iz8eFaZBNrwjaAADszOxOv2byDMn4rqob/luuSPAzJjTrSn5KawRXcoNLoWGPGNO6Mils6bIly/g2LaU34otNw== +"@uiw/codemirror-extensions-basic-setup@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.22.2.tgz#a114dc9ebad6de41a441c8aca655d9c34934a7d9" + integrity sha512-zcHGkldLFN3cGoI5XdOGAkeW24yaAgrDEYoyPyWHODmPiNwybQQoZGnH3qUdzZwUaXtAcLWoAeOPzfNRW2yGww== dependencies: "@codemirror/autocomplete" "^6.0.0" "@codemirror/commands" "^6.0.0" @@ -9068,16 +9068,16 @@ "@codemirror/state" "^6.0.0" "@codemirror/view" "^6.0.0" -"@uiw/react-codemirror@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.22.1.tgz#8e9a924c8876a6787cf8e92212333156a2d42658" - integrity sha512-yrq9FdGZ6E4Rh+7W0xyirSEeESGyG/k54/DfFqSk40fqel/3x/3fqjIImEZUYPxxgFPmZ3RtP+O0Em46nwRvgg== +"@uiw/react-codemirror@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.22.2.tgz#18dcb79e31cf34e0704366f3041da93ff3c64109" + integrity sha512-okCSl+WJG63gRx8Fdz7v0C6RakBQnbb3pHhuzIgDB+fwhipgFodSnu2n9oOsQesJ5YQ7mSOcKMgX0JEsu4nnfQ== dependencies: "@babel/runtime" "^7.18.6" "@codemirror/commands" "^6.1.0" "@codemirror/state" "^6.1.1" "@codemirror/theme-one-dark" "^6.0.0" - "@uiw/codemirror-extensions-basic-setup" "4.22.1" + "@uiw/codemirror-extensions-basic-setup" "4.22.2" codemirror "^6.0.0" "@vitejs/plugin-react@4.2.1": From 8d33c9d64f0208c6877348be7cb276739c8648c6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:15:33 +0000 Subject: [PATCH 069/131] Update dependency lib0 to v0.2.94 --- ghost/core/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ghost/core/package.json b/ghost/core/package.json index b724bf19f4..ee044de1d7 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -208,7 +208,7 @@ "keypair": "1.0.4", "knex": "2.4.2", "knex-migrator": "5.2.1", - "lib0": "0.2.88", + "lib0": "0.2.94", "lodash": "4.17.21", "luxon": "3.4.4", "moment": "2.24.0", diff --git a/yarn.lock b/yarn.lock index 96c4fbcbee..24a796506a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21766,10 +21766,10 @@ lexical@0.13.1: resolved "https://registry.yarnpkg.com/lexical/-/lexical-0.13.1.tgz#0abffe9bc05a7a9da8a6128ea478bf08c11654db" integrity sha512-jaqRYzVEfBKbX4FwYpd/g+MyOjRaraAel0iQsTrwvx3hyN0bswUZuzb6H6nGlFSjcdrc77wKpyKwoWj4aUd+Bw== -lib0@0.2.88, lib0@^0.2.85, lib0@^0.2.86: - version "0.2.88" - resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.88.tgz#18618e0c3b63f6260255eb760f9247d9cc6c6a5b" - integrity sha512-KyroiEvCeZcZEMx5Ys+b4u4eEBbA1ch7XUaBhYpwa/nPMrzTjUhI4RfcytmQfYoTBPcdyx+FX6WFNIoNuJzJfQ== +lib0@0.2.94, lib0@^0.2.85, lib0@^0.2.86: + version "0.2.94" + resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.94.tgz#fc28b4b65f816599f1e2f59d3401e231709535b3" + integrity sha512-hZ3p54jL4Wpu7IOg26uC7dnEWiMyNlUrb9KoG7+xYs45WkQwpVvKFndVq2+pqLYKe1u8Fp3+zAfZHVvTK34PvQ== dependencies: isomorphic.js "^0.2.4" From 895e3719bd6ffbfd7a995d57dc3de237c5d1f662 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Tue, 2 Jul 2024 21:43:47 +0200 Subject: [PATCH 070/131] =?UTF-8?q?Revert=20"=F0=9F=90=9B=20Fixed=20unexpe?= =?UTF-8?q?cted=20leave=20confirmation=20after=20Cmd+S=20on=20member=20pro?= =?UTF-8?q?file"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 186c6f3c421800837f7c77014e19f82f0ff21df4. --- ghost/admin/app/controllers/member.js | 13 ++++++------- ghost/admin/app/routes/member.js | 1 - 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ghost/admin/app/controllers/member.js b/ghost/admin/app/controllers/member.js index a6606ec8bf..0978126d08 100644 --- a/ghost/admin/app/controllers/member.js +++ b/ghost/admin/app/controllers/member.js @@ -29,7 +29,6 @@ export default class MemberController extends Controller { @tracked showImpersonateMemberModal = false; @tracked modalLabel = null; @tracked showLabelModal = false; - @tracked scratchMember = null; _previousLabels = null; _previousNewsletters = null; @@ -57,12 +56,6 @@ export default class MemberController extends Controller { set member(member) { this.model = member; - - if (member !== this.scratchMember?.member) { - const scratchMember = EmberObject.create({member}); - SCRATCH_PROPS.forEach(prop => defineProperty(scratchMember, prop, boundOneWay(`member.${prop}`))); - this.scratchMember = scratchMember; - } } get dirtyAttributes() { @@ -99,6 +92,12 @@ export default class MemberController extends Controller { return options; } + get scratchMember() { + let scratchMember = EmberObject.create({member: this.member}); + SCRATCH_PROPS.forEach(prop => defineProperty(scratchMember, prop, boundOneWay(`member.${prop}`))); + return scratchMember; + } + get subscribedAt() { let memberSince = moment(this.member.createdAtUTC).from(moment()); let createdDate = moment(this.member.createdAtUTC).format('D MMM YYYY'); diff --git a/ghost/admin/app/routes/member.js b/ghost/admin/app/routes/member.js index 3f70cd7bf5..ab9bdc8c75 100644 --- a/ghost/admin/app/routes/member.js +++ b/ghost/admin/app/routes/member.js @@ -34,7 +34,6 @@ export default class MembersRoute extends AdminRoute { setupController(controller, member, transition) { super.setupController(...arguments); - controller.member = member; controller.setInitialRelationshipValues(); From 6c6d3b6ce434e0ecc0161e87827f03dceb99433f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 09:06:29 +0000 Subject: [PATCH 071/131] Update dependency jose to v4.15.9 --- ghost/admin/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ghost/admin/package.json b/ghost/admin/package.json index c1c2b045f6..fe0fe9e578 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -172,7 +172,7 @@ "*.js": "eslint" }, "dependencies": { - "jose": "4.15.7", + "jose": "4.15.9", "path-browserify": "1.0.1", "webpack": "5.92.1" }, diff --git a/yarn.lock b/yarn.lock index 24a796506a..b1a0977bbc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21064,10 +21064,10 @@ jiti@^1.18.2, jiti@^1.21.0: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== -jose@4.15.7, jose@^4.14.6: - version "4.15.7" - resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.7.tgz#96ad68d786632bd03c9068aa281810dbbe1b60d8" - integrity sha512-L7ioP+JAuZe8v+T5+zVI9Tx8LtU8BL7NxkyDFVMv+Qr3JW0jSoYDedLtodaXwfqMpeCyx4WXFNyu9tJt4WvC1A== +jose@4.15.9, jose@^4.14.6: + version "4.15.9" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.9.tgz#9b68eda29e9a0614c042fa29387196c7dd800100" + integrity sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA== jquery-deferred@^0.3.0: version "0.3.1" From be77080f3925903db85dc2cbb48c31907dd252df Mon Sep 17 00:00:00 2001 From: Sanne de Vries <65487235+sanne-san@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:43:51 +0200 Subject: [PATCH 072/131] Updated typography and spacing for callout cards and blockquotes (#20525) REF DES-542 --- .../email-templates/partials/styles-old.hbs | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/ghost/email-service/lib/email-templates/partials/styles-old.hbs b/ghost/email-service/lib/email-templates/partials/styles-old.hbs index 4b2ba31394..a8f19b8573 100644 --- a/ghost/email-service/lib/email-templates/partials/styles-old.hbs +++ b/ghost/email-service/lib/email-templates/partials/styles-old.hbs @@ -172,7 +172,7 @@ blockquote.kg-blockquote-alt { text-align: center; font-size: 1.2em; font-style: italic; - color: #999999; + color: #738a94; } blockquote p { @@ -844,10 +844,6 @@ a[data-flickr-embed] img { border-radius: 3px; } -.kg-callout-card p { - margin: 0 -} - .kg-callout-card-grey { background: #eef0f2; } @@ -1350,6 +1346,15 @@ a[data-flickr-embed] img { font-size: 16px !important; } + table.body .kg-callout-card { + padding: 16px 24px !important; + } + + table.body .kg-callout-text { + font-size: 16px !important; + line-height: 1.5em !important; + } + table.body pre { white-space: pre-wrap !important; word-break: break-word !important; @@ -1565,7 +1570,7 @@ a[data-flickr-embed] img { } table.body blockquote { - font-size: 17px; + font-size: 16px !important; line-height: 1.6em; margin-bottom: 0; padding-left: 15px; @@ -1573,13 +1578,12 @@ a[data-flickr-embed] img { table.body blockquote.kg-blockquote-alt { border-left: 0 none !important; - margin: 0 0 2.5em 0 !important; - padding: 0 50px 0 50px !important; - font-size: 1.2em; - } - - table.body blockquote + * { - margin-top: 1.5em !important; + margin: 0 !important; + padding-left: 20px !important; + padding-right: 20px !important; + padding-bottom: 1.5em !important; + font-size: 18px !important; + line-height: 1.4em !important; } table.body hr { From 6e0b009034db05fc91167b3e098e909887f7d0a0 Mon Sep 17 00:00:00 2001 From: Sag Date: Wed, 3 Jul 2024 13:12:01 +0200 Subject: [PATCH 073/131] =?UTF-8?q?=F0=9F=8E=A8=20Added=20'Payment=20faile?= =?UTF-8?q?d'=20subscription=20cancellation=20reason=20(#20527)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ref https://linear.app/tryghost/issue/ENG-1254 - we currently only store a cancellation reason when a member cancels manually in Portal - we now also store "Payment failed" when the cancellation is automatic due to several payment failures --- .../test/e2e-api/members/webhooks.test.js | 151 +++++++++++++++++- .../lib/repositories/MemberRepository.js | 15 +- 2 files changed, 159 insertions(+), 7 deletions(-) diff --git a/ghost/core/test/e2e-api/members/webhooks.test.js b/ghost/core/test/e2e-api/members/webhooks.test.js index d91b32ba1f..a0089d8a42 100644 --- a/ghost/core/test/e2e-api/members/webhooks.test.js +++ b/ghost/core/test/e2e-api/members/webhooks.test.js @@ -221,7 +221,7 @@ describe('Members API', function () { let canceledPaidMember; - it('Handles cancellation of paid subscriptions correctly', async function () { + it('Handles cancellation of paid subscriptions at the end of the billing cycle', async function () { const customer_id = createStripeID('cust'); const subscription_id = createStripeID('sub'); @@ -256,8 +256,143 @@ describe('Members API', function () { // Create a new customer in Stripe set(customer, { id: customer_id, - name: 'Test Member', - email: 'cancel-paid-test@email.com', + name: 'Cancel me at the end of the billing cycle', + email: 'cancel-me-at-the-end-of-cycle@test.com', + subscriptions: { + type: 'list', + data: [subscription] + } + }); + + // Make sure this customer has a corresponding member in the database + // And all the subscriptions are setup correctly + const initialMember = await createMemberFromStripe(); + assert.equal(initialMember.status, 'paid', 'The member initial status should be paid'); + assert.equal(initialMember.tiers.length, 1, 'The member should have one tier'); + should(initialMember.subscriptions).match([ + { + status: 'active' + } + ]); + + // Check whether MRR and status has been set + await assertSubscription(initialMember.subscriptions[0].id, { + subscription_id: subscription.id, + status: 'active', + cancel_at_period_end: false, + plan_amount: 500, + plan_interval: 'month', + plan_currency: 'usd', + mrr: 500 + }); + + // Set the subscription to cancel at the end of the period + set(subscription, { + ...subscription, + status: 'active', + cancel_at_period_end: true, + metadata: { + cancellation_reason: 'I want to break free' + } + }); + + // Send the webhook call to announce the cancelation + const webhookPayload = JSON.stringify({ + type: 'customer.subscription.updated', + data: { + object: subscription + } + }); + const webhookSignature = stripe.webhooks.generateTestHeaderString({ + payload: webhookPayload, + secret: process.env.WEBHOOK_SECRET + }); + + await membersAgent.post('/webhooks/stripe/') + .body(webhookPayload) + .header('content-type', 'application/json') + .header('stripe-signature', webhookSignature) + .expectStatus(200); + + // Check that the subscription has been set to cancel and has saved the cancellation reason + const {body: body2} = await adminAgent.get('/members/' + initialMember.id + '/'); + assert.equal(body2.members.length, 1, 'The member does not exist'); + const updatedMember = body2.members[0]; + should(updatedMember.subscriptions).match([ + { + status: 'active', + cancel_at_period_end: true, + cancellation_reason: 'I want to break free' + } + ]); + + // Check whether MRR and cancel_at_period_end has been set + await assertSubscription(initialMember.subscriptions[0].id, { + subscription_id: subscription.id, + status: 'active', + cancel_at_period_end: true, + plan_amount: 500, + plan_interval: 'month', + plan_currency: 'usd', + mrr: 0 + }); + + // Check that there is a canceled event + await assertMemberEvents({ + eventType: 'MemberPaidSubscriptionEvent', + memberId: updatedMember.id, + asserts: [ + { + type: 'created', + mrr_delta: 500 + }, + { + type: 'canceled', + mrr_delta: -500 + } + ] + }); + + canceledPaidMember = updatedMember; + }); + + it('Handles immediate cancellation of paid subscriptions', async function () { + const customer_id = createStripeID('cust'); + const subscription_id = createStripeID('sub'); + + // Create a new subscription in Stripe + set(subscription, { + id: subscription_id, + customer: customer_id, + status: 'active', + items: { + type: 'list', + data: [{ + id: 'item_123', + price: { + id: 'price_123', + product: 'product_123', + active: true, + nickname: 'Monthly', + currency: 'usd', + recurring: { + interval: 'month' + }, + unit_amount: 500, + type: 'recurring' + } + }] + }, + start_date: Date.now() / 1000, + current_period_end: Date.now() / 1000 + (60 * 60 * 24 * 31), + cancel_at_period_end: false + }); + + // Create a new customer in Stripe + set(customer, { + id: customer_id, + name: 'Cancel me now', + email: 'cancel-me-immediately@test.com', subscriptions: { type: 'list', data: [subscription] @@ -289,12 +424,15 @@ describe('Members API', function () { // Cancel the previously created subscription in Stripe set(subscription, { ...subscription, - status: 'canceled' + status: 'canceled', + cancellation_details: { + reason: 'payment_failed' + } }); // Send the webhook call to announce the cancelation const webhookPayload = JSON.stringify({ - type: 'customer.subscription.updated', + type: 'customer.subscription.deleted', data: { object: subscription } @@ -318,7 +456,8 @@ describe('Members API', function () { assert.equal(updatedMember.tiers.length, 0, 'The member should have no products'); should(updatedMember.subscriptions).match([ { - status: 'canceled' + status: 'canceled', + cancellation_reason: 'Payment failed' } ]); diff --git a/ghost/members-api/lib/repositories/MemberRepository.js b/ghost/members-api/lib/repositories/MemberRepository.js index 5dbe014994..63f6e7de1d 100644 --- a/ghost/members-api/lib/repositories/MemberRepository.js +++ b/ghost/members-api/lib/repositories/MemberRepository.js @@ -992,7 +992,7 @@ module.exports = class MemberRepository { subscription_id: subscription.id, status: subscription.status, cancel_at_period_end: subscription.cancel_at_period_end, - cancellation_reason: subscription.metadata && subscription.metadata.cancellation_reason || null, + cancellation_reason: this.getCancellationReason(subscription), current_period_end: new Date(subscription.current_period_end * 1000), start_date: new Date(subscription.start_date * 1000), default_payment_card_last4: paymentMethod && paymentMethod.card && paymentMethod.card.last4 || null, @@ -1301,6 +1301,19 @@ module.exports = class MemberRepository { } } + getCancellationReason(subscription) { + // Case: manual cancellation in Portal + if (subscription.metadata && subscription.metadata.cancellation_reason) { + return subscription.metadata.cancellation_reason; + + // Case: Automatic cancellation due to several payment failures + } else if (subscription.cancellation_details && subscription.cancellation_details.reason && subscription.cancellation_details.reason === 'payment_failed') { + return 'Payment failed'; + } + + return null; + } + async getSubscription(data, options) { if (!this._stripeAPIService.configured) { throw new errors.BadRequestError({message: tpl(messages.noStripeConnection, {action: 'get Stripe Subscription'})}); From e393676e8db5a157246cce74cdd34c7828c50a4c Mon Sep 17 00:00:00 2001 From: Sanne de Vries <65487235+sanne-san@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:35:17 +0200 Subject: [PATCH 074/131] Removed duplicate email template and styles files (#20528) Refs https://ghost.slack.com/archives/C02G9E68C/p1720003723371169 - These duplicate files have been lingering since working on an email customisation feature that was never released. --- .../__snapshots__/email-previews.test.js.snap | 895 +++-- .../__snapshots__/batch-sending.test.js.snap | 2968 +++++++++++------ .../__snapshots__/cards.test.js.snap | 629 ++-- ghost/email-service/lib/EmailRenderer.js | 19 +- .../email-templates/partials/styles-old.hbs | 1692 ---------- .../lib/email-templates/partials/styles.hbs | 503 +-- .../lib/email-templates/template-old.hbs | 251 -- .../lib/email-templates/template.hbs | 32 +- 8 files changed, 3263 insertions(+), 3726 deletions(-) delete mode 100644 ghost/email-service/lib/email-templates/partials/styles-old.hbs delete mode 100644 ghost/email-service/lib/email-templates/template-old.hbs diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap index 456afd4202..da380bd059 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap @@ -118,11 +118,10 @@ Object { Post with email-only card - + This is the actual post content... - +
    - - +
      +  
    - +
    -
    + - - + - -
    +
    - + - +
    @@ -495,24 +554,24 @@ table.body h2 span {
    - Post with email-only card + + Post with email-only card
    - + - - @@ -554,7 +613,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • 1 Jan 1970 - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -520,7 +579,7 @@ table.body h2 span {
    +

    Hey Jamie %%{unknown}%%

    Welcome to your first Ghost email!

    This is the actual post content...

    Another email card with a similar replacement, Jamie

    @@ -537,14 +596,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - + - -
    +
    - + - +
    @@ -1122,24 +1240,24 @@ table.body h2 span {
    - HTML Ipsum + + HTML Ipsum
    - + + \\" align=\\"center\\" style=\\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; vertical-align: top; color: #15212A; padding-bottom: 30px; width: 100%; text-align: center;\\" width=\\"100%\\" valign=\\"top\\"> + - @@ -1168,14 +1287,14 @@ table.body h2 span { - @@ -1185,7 +1304,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • 1 Jan 2015 - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -1148,12 +1266,13 @@ table.body h2 span {
    + -

    HTML Ipsum Presents

    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis.

    Header Level 2

    1. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
    2. Aliquam tincidunt mauris eu risus.

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.

    Header Level 3

    • Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
    • Aliquam tincidunt mauris eu risus.
    #header h1 a{display: block;width: 300px;height: 80px;}
    +

    HTML Ipsum Presents

    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis.

    Header Level 2

    1. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
    2. Aliquam tincidunt mauris eu risus.

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.

    Header Level 3

    • Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
    • Aliquam tincidunt mauris eu risus.
    #header h1 a{display: block;width: 300px;height: 80px;}
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - + - -
    +
    - + - +
    @@ -1800,24 +1979,24 @@ table.body h2 span {
    - Post with email-only card + + Post with email-only card
    - + - - @@ -1859,7 +2038,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • 1 Jan 1970 - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -1825,7 +2004,7 @@ table.body h2 span {
    +

    Testing links in email excerpt and apostrophes '

    @@ -1842,14 +2021,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -2801,24 +3039,24 @@ table.body h2 span {
    - Post with email-only card + + Post with email-only card
    - + - - @@ -2860,7 +3098,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • 1 Jan 1970 - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -2826,7 +3064,7 @@ table.body h2 span {
    +

    Testing links in email excerpt and apostrophes '

    @@ -2843,14 +3081,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -3835,24 +4132,24 @@ table.body h2 span {
    - Post with email-only card + + Post with email-only card
    - + - - @@ -3894,7 +4191,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • 1 Jan 1970 - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -3860,7 +4157,7 @@ table.body h2 span {
    +

    Testing links in email excerpt and apostrophes '

    @@ -3877,14 +4174,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -395,24 +454,24 @@ table.body h2 span {
    - A random test post + + A random test post
    - + + \\" align=\\"center\\" style=\\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; vertical-align: top; color: #15212A; padding-bottom: 30px; text-align: center; width: 100%; padding: 0; font-size: 13px;\\" width=\\"100%\\" valign=\\"top\\">\\"Testing + - + - - @@ -462,7 +526,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -422,13 +481,18 @@ table.body h2 span {
    \\"Testing
    Testing feature image caption +
    + Testing feature image caption +
    +
    + @@ -445,14 +509,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -1001,24 +1129,24 @@ table.body h2 span {
    - This is a test post title + + This is a test post title
    - + - - @@ -1060,7 +1188,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -1026,7 +1154,7 @@ table.body h2 span {
    +

    Hello world

    @@ -1043,14 +1171,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -1593,24 +1780,24 @@ table.body h2 span {
    - This is a test post title + + This is a test post title
    - + - - @@ -1652,7 +1839,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -1618,7 +1805,7 @@ table.body h2 span {
    +

    Hello world

    @@ -1635,14 +1822,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -2185,24 +2431,24 @@ table.body h2 span {
    - This is a test post title + + This is a test post title
    - + - - @@ -2244,7 +2490,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -2210,7 +2456,7 @@ table.body h2 span {
    +

    Hello world

    @@ -2227,14 +2473,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - - @@ -2812,7 +3117,7 @@ table.body h2 span { - + - - +
    +
    +
    - + - +
    @@ -2778,7 +3083,7 @@ table.body h2 span {
    +

    Hello world

    @@ -2795,14 +3100,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -3317,24 +3681,24 @@ table.body h2 span {
    - This is a test post title + + This is a test post title
    - + - - @@ -3376,7 +3740,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -3342,7 +3706,7 @@ table.body h2 span {
    +

    Hello world

    @@ -3359,14 +3723,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -3909,24 +4332,24 @@ table.body h2 span {
    - This is a test post title + + This is a test post title
    - + - -
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -3934,7 +4357,7 @@ table.body h2 span {
    +

    Hello world

    @@ -3948,18 +4371,18 @@ table.body h2 span {
    - + - @@ -4007,7 +4430,7 @@ table.body h2 span { - + - - +
    + -
    + \\"More - + \\"Less - + \\"Comment\\" @@ -3967,20 +4390,20 @@ table.body h2 span {
    -
    + \\"More -

    More like this

    +

    More like this

    -
    + \\"Less -

    Less like this

    +

    Less like this

    -
    + \\"Comment\\" -

    Comment

    +

    Comment

    @@ -3990,14 +4413,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -4595,24 +5077,24 @@ table.body h2 span {
    - This is a test post title + + This is a test post title
    - + - -
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -4620,7 +5102,7 @@ table.body h2 span {
    +

    Hello world

    @@ -4634,10 +5116,10 @@ table.body h2 span {
    - + - @@ -4675,7 +5157,7 @@ table.body h2 span { - + - - +
    + -
    + \\"Comment\\" @@ -4645,10 +5127,10 @@ table.body h2 span {
    -
    + \\"Comment\\" -

    Comment

    +

    Comment

    @@ -4658,14 +5140,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -6601,24 +7142,24 @@ table.body h2 span {
    - This is the main post title + + This is the main post title
    - + - -
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -6626,7 +7167,7 @@ table.body h2 span {
    +

    Hello world

    @@ -6641,19 +7182,19 @@ table.body h2 span {
    -

    Keep reading

    +
    +

    Keep reading

    -
    + - @@ -6663,15 +7204,15 @@ table.body h2 span {
    -

    - This is a test post title +

    +

    + This is a test post title

    -

    - Hello world +

    + Hello world

    -
    + - @@ -6681,15 +7222,15 @@ table.body h2 span {
    -

    - This is a test post title +

    +

    + This is a test post title

    -

    - Hello world +

    + Hello world

    -
    + - @@ -6702,14 +7243,14 @@ table.body h2 span { - @@ -6719,7 +7260,7 @@ table.body h2 span { - + - - +
    -

    - This is a test post title +

    +

    + This is a test post title

    -

    - Hello world +

    + Hello world

    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -7338,24 +7938,24 @@ table.body h2 span {
    - This is a test post title + + This is a test post title
    - + - -
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -7363,7 +7963,7 @@ table.body h2 span {
    +

    Hello world

    @@ -7379,17 +7979,17 @@ table.body h2 span {
    -

    Subscription details

    -

    +

    +

    Subscription details

    +

    You are receiving this because you are a paid subscriber to Ghost. Your subscription has been canceled and will expire on date. You can resume your subscription via your account settings.

    - - @@ -7417,7 +8017,7 @@ table.body h2 span { - + - - +
    -

    Name: not provided

    -

    Email: canceled-paid@example.com

    -

    Member since: date

    +
    +

    Name: not provided

    +

    Email: canceled-paid@example.com

    +

    Member since: date

    Manage subscription → @@ -7400,14 +8000,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -7985,24 +8644,24 @@ table.body h2 span {
    - This is a test post title + + This is a test post title
    - + - -
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -8010,7 +8669,7 @@ table.body h2 span {
    +

    Hello world

    @@ -8026,17 +8685,17 @@ table.body h2 span {
    -

    Subscription details

    -

    +

    +

    Subscription details

    +

    You are receiving this because you are a complimentary subscriber to Ghost.

    - - @@ -8064,7 +8723,7 @@ table.body h2 span { - + - - +
    -

    Name: not provided

    -

    Email: subscription-box-comped@example.com

    -

    Member since: date

    +
    +

    Name: not provided

    +

    Email: subscription-box-comped@example.com

    +

    Member since: date

    Manage subscription → @@ -8047,14 +8706,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -8632,24 +9350,24 @@ table.body h2 span {
    - This is a test post title + + This is a test post title
    - + - -
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -8657,7 +9375,7 @@ table.body h2 span {
    +

    Hello world

    @@ -8673,17 +9391,17 @@ table.body h2 span {
    -

    Subscription details

    -

    +

    +

    Subscription details

    +

    You are receiving this because you are a free subscriber to Ghost.

    - - @@ -8711,7 +9429,7 @@ table.body h2 span { - + - - +
    -

    Name: not provided

    -

    Email: subscription-box-1@example.com

    -

    Member since: date

    +
    +

    Name: not provided

    +

    Email: subscription-box-1@example.com

    +

    Member since: date

    Manage subscription → @@ -8694,14 +9412,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -9279,24 +10056,24 @@ table.body h2 span {
    - This is a test post title + + This is a test post title
    - + - -
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -9304,7 +10081,7 @@ table.body h2 span {
    +

    Hello world

    @@ -9320,17 +10097,17 @@ table.body h2 span {
    -

    Subscription details

    -

    +

    +

    Subscription details

    +

    You are receiving this because you are a paid subscriber to Ghost. Your subscription will renew on date.

    - - @@ -9358,7 +10135,7 @@ table.body h2 span { - + - - +
    -

    Name: not provided

    -

    Email: paid@example.com

    -

    Member since: date

    +
    +

    Name: not provided

    +

    Email: paid@example.com

    +

    Member since: date

    Manage subscription → @@ -9341,14 +10118,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -9926,24 +10762,24 @@ table.body h2 span {
    - This is a test post title + + This is a test post title
    - + - -
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -9951,7 +10787,7 @@ table.body h2 span {
    +

    Hello world

    @@ -9967,17 +10803,17 @@ table.body h2 span {
    -

    Subscription details

    -

    +

    +

    Subscription details

    +

    You are receiving this because you are a trialing subscriber to Ghost. Your free trial ends on date, at which time you will be charged the regular price. You can always cancel before then.

    - - @@ -10005,7 +10841,7 @@ table.body h2 span { - + - - +
    -

    Name: not provided

    -

    Email: trialing-paid@example.com

    -

    Member since: date

    +
    +

    Name: not provided

    +

    Email: trialing-paid@example.com

    +

    Member since: date

    Manage subscription → @@ -9988,14 +10824,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -10573,24 +11468,24 @@ table.body h2 span {
    - A random test post + + A random test post
    - + - - @@ -10632,7 +11527,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -10598,7 +11493,7 @@ table.body h2 span {
    +

    Hello {first_name},

    Hey Simon, Hey Simon,

    @@ -10615,14 +11510,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -11167,24 +12121,24 @@ table.body h2 span {
    - A random test post + + A random test post
    - + - - @@ -11226,7 +12180,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -11192,7 +12146,7 @@ table.body h2 span {
    +

    Hello {first_name},

    Hey there, Hey ,

    @@ -11209,14 +12163,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -395,24 +454,24 @@ table.body h2 span {
    - A random test post + + A random test post
    - + - - @@ -454,7 +513,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -420,7 +479,7 @@ table.body h2 span {
    +

    This is a paragraph test.

    @@ -437,14 +496,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -987,24 +1105,24 @@ table.body h2 span {
    - A random test post + + A random test post
    - + - - @@ -1046,7 +1164,7 @@ table.body h2 span { - + - - +
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -1012,7 +1130,7 @@ table.body h2 span {
    +

    This is a paragraph

    @@ -1029,14 +1147,14 @@ table.body h2 span {
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
      +  
    - +
    -
    + - - - + - -
    +
    +
    - + - +
    @@ -1607,24 +1784,24 @@ Ghost: Independent technology for modern publishingBeautiful, modern publishing
    - A random test post + + A random test post
    - + -
    + - - - - +
    + By Joe Bloggs • date - View in browser + + View in browser
    - View in browser +
    + View in browser
    @@ -1632,23 +1809,23 @@ Ghost: Independent technology for modern publishingBeautiful, modern publishing
    + -

    This is just a simple paragraph, no frills.

    This is block quote
    This is a...different block quote

    This is a heading!

    Here's a smaller heading.

    \\"Cows
    A lovely cow

    A heading

    +

    This is just a simple paragraph, no frills.

    This is block quote
    This is a...different block quote

    This is a heading!

    Here's a smaller heading.

    \\"Cows
    A lovely cow

    A heading

    and a paragraph (in markdown!)

    A paragraph inside an HTML card.

    And another one, with some bold text.

    -

    A gallery.

    +

    A gallery.

    - +
    -
    Ghost: Independent technology for modern publishing
    -
    Beautiful, modern publishing with newsletters and premium subscriptions built-in. Used by Sky, 404Media, Lever News, Tangle, The Browser, and thousands more.
    -

    Hey Vinz,

    💡
    I had an idea...

    Hey Vinz,

    💡
    I had an idea...
    -

    Spoiler alert!

    +

    Spoiler alert!

    Just kidding

    -
    +
    -
    + - -
    + - + + - -
    - Sample + + Sample
    + - -
    - + + - 1:45 • Click to play audio + + 1:45 • Click to play audio
    @@ -1757,13 +1934,13 @@ Ghost: Independent technology for modern publishingBeautiful, modern publishing - - - +
    + +
      
    @@ -1777,59 +1954,59 @@ Ghost: Independent technology for modern publishingBeautiful, modern publishing -

    A lovely video of a woman on the beach doing nothing.

    +

    A lovely video of a woman on the beach doing nothing.

    - - - - - -
    +
    -

    Make a blog!

    +
    +

    Make a blog!

    +
    +

    with Ghost

    +
    +
    -
    -

    Good news everyone!

    -

    This header renders properly in all email clients!

    +
    +

    Good news everyone!

    +

    This header renders properly in all email clients!

    - - - +
    + +
      
    @@ -1841,27 +2018,27 @@ Ghost: Independent technology for modern publishingBeautiful, modern publishing -

    Some text.

    A blockquote

    Some more text.

    console.log('Hello world!');

    A tiny little script.

    +

    Some text.

    A blockquote

    Some more text.

    console.log('Hello world!');

    A tiny little script.

    -
    + -
    + -
    - test +
    + test
    -
    - A tiny text file. +
    + A tiny text file.
    - - - @@ -1902,7 +2079,7 @@ Ghost: Independent technology for modern publishingBeautiful, modern publishing - + - {{post.title}} - {{>styles}} - - - {{preheader}} -
    - test.txt • 16 Bytes +
    + test.txt • 16 Bytes
    + @@ -1885,14 +2062,14 @@ Ghost: Independent technology for modern publishingBeautiful, modern publishing
    + - + - +
    Ghost © 2024 – UnsubscribeGhost © 2024 – Unsubscribe
    \\"Powered\\"Powered
      
    - - - - - - - - - - - - diff --git a/ghost/email-service/lib/email-templates/template.hbs b/ghost/email-service/lib/email-templates/template.hbs index dd7b911538..15d0b5705e 100644 --- a/ghost/email-service/lib/email-templates/template.hbs +++ b/ghost/email-service/lib/email-templates/template.hbs @@ -22,7 +22,7 @@
    - + @@ -44,12 +44,12 @@ {{/if}} {{#if (or showHeaderIcon showHeaderTitle showHeaderName) }} - - +