diff --git a/.github/scripts/docker-compose.yml b/.github/scripts/docker-compose.yml index a8d89941f7d0..b8f14042ced2 100644 --- a/.github/scripts/docker-compose.yml +++ b/.github/scripts/docker-compose.yml @@ -27,13 +27,3 @@ services: ports: - "6379:6379" restart: always - jaeger: - image: jaegertracing/all-in-one:1.58 - container_name: ghost-jaeger - ports: - - "4318:4318" - - "16686:16686" - - "9411:9411" - restart: always - environment: - COLLECTOR_ZIPKIN_HOST_PORT: :9411 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 827e3931e1cb..3c77275c57dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -402,7 +402,7 @@ jobs: jq '[{ name: "Boot time", unit: "s", value: .results[0].median, range: ((.results[0].max - .results[0].min) | tostring) }]' < boot-perf.json > boot-perf-formatted.json - name: Run analysis - uses: benchmark-action/github-action-benchmark@v1.20.3 + uses: benchmark-action/github-action-benchmark@v1.20.1 with: tool: 'customSmallerIsBetter' output-file-path: ghost/core/boot-perf-formatted.json diff --git a/apps/admin-x-activitypub/package.json b/apps/admin-x-activitypub/package.json index 8431203950ef..82fcadfb30d3 100644 --- a/apps/admin-x-activitypub/package.json +++ b/apps/admin-x-activitypub/package.json @@ -25,18 +25,17 @@ "lint": "yarn run lint:code && yarn run lint:test", "lint:code": "eslint --ext .js,.ts,.cjs,.tsx --cache src", "lint:test": "eslint -c test/.eslintrc.cjs --ext .js,.ts,.cjs,.tsx --cache test", - "test:unit": "yarn nx build && vitest run", + "test:unit": "vitest run", "test:acceptance": "NODE_OPTIONS='--experimental-specifier-resolution=node --no-warnings' VITE_TEST=true playwright test", "test:acceptance:slowmo": "TIMEOUT=100000 PLAYWRIGHT_SLOWMO=100 yarn test:acceptance --headed", "test:acceptance:full": "ALL_BROWSERS=1 yarn test:acceptance", "preview": "vite preview" }, "devDependencies": { - "@playwright/test": "1.38.1", "@testing-library/react": "14.1.0", "@tryghost/admin-x-design-system": "0.0.0", "@tryghost/admin-x-framework": "0.0.0", - "@types/react": "18.3.3", + "@types/react": "18.3.2", "@types/react-dom": "18.3.0", "react": "18.3.1", "react-dom": "18.3.1" diff --git a/apps/admin-x-activitypub/public/styles/reader.css b/apps/admin-x-activitypub/public/styles/reader.css deleted file mode 100644 index 4cdaee420e1c..000000000000 --- a/apps/admin-x-activitypub/public/styles/reader.css +++ /dev/null @@ -1,1928 +0,0 @@ - -.gh-whats-new-canvas .gh-canvas-header-content { - margin-bottom: -1px; - padding: 8px 0 16px; - align-items: center; -} - -.gh-whats-new { - flex-grow: 2; - color: var(--darkgrey); - font-size: 1.5rem; - letter-spacing: 0; - margin-top: -24px; -} - -.gh-whats-new-heading { - display: flex; - align-items: center; - font-size: 1.5rem; - letter-spacing: 0; - line-height: 1.3em; - font-weight: 700; - margin: 0; -} - -.gh-whats-new-heading svg { - width: 20px; - height: 20px; - margin-top: -2px; - margin-right: 12px; -} - -.gh-whats-new-heading svg path { - fill: var(--pink); -} - -.gh-wn-header { - position: relative; - display: flex; - align-items: center; - margin: -32px -32px 0; - padding: 18px 18px 12px; - border-top-left-radius: 3px; - border-top-right-radius: 3px; - overflow: hidden; - background-position: center; - background-repeat: no-repeat; - background-size: cover; - background: var(--pink); - background: linear-gradient(135deg, color-mod(var(--pink) h(-10) s(+5%) l(-10%)) 0%, rgba(173,38,180,1) 100%); -} - -.gh-wn-header .background-img { - position: absolute; - top: -30px; - left: 0; -} - -.gh-wn-header h2 { - font-size: 1.3rem; - font-weight: 600; - text-transform: uppercase; - color: #FFF; - margin: 0 8px 4px; -} - -.gh-wn-header svg path { - fill: #fff; -} - -.gh-wn-close { - stroke: #FFF; - opacity: 0.6; - transition: all 0.2s ease-in-out; -} - -.gh-wn-close:hover { - opacity: 1.0; -} - -.gh-wn-entry { - margin: 0 0 5vmin; - padding-bottom: 5vmin; - width: 100%; - border-bottom: 1px solid var(--lightgrey-l2); - color: inherit; - text-decoration: none; -} - -.gh-wn-content { - max-width: 620px; -} - -.gh-whats-new-canvas .gh-wn-content { - margin: 0 auto; -} - -.gh-wn-entry h4 { - font-size: 1.2rem; - font-weight: 500; - letter-spacing: 0; - text-transform: uppercase; - margin: 24px 0 4px; - color: var(--midlightgrey); -} - -.gh-wn-entry h1 { - font-size: 3.7rem; - line-height: 1.3em; - font-weight: 700; - letter-spacing: -0.021em; - color: var(--black); - margin-bottom: 16px; -} - -.gh-whats-new-canvas .gh-wn-entry h1, -.gh-whats-new-canvas .gh-wn-entry h4 { - max-width: 620px; - margin-left: auto; - margin-right: auto; -} - -.gh-wn-entry h2 { - border-bottom: none; - font-size: 1.9rem; - padding-bottom: 0; - margin-bottom: 20px; -} - -.gh-wn-entry p, -.gh-wn-entry li { - line-height: 1.6em; -} - -.gh-wn-entry li { - margin-bottom: 12px; -} - -.gh-wn-entry p { - margin: 0 0 20px; - padding: 0; -} - -.gh-wn-entry figure { - margin-bottom: 24px; - overflow: hidden; -} - -.gh-wn-entry img { - height: auto; -} - -.gh-wn-entry hr { - border-top: 1px solid var(--whitegrey-l1); - margin: 24px 0; -} - - -/* Bookmark card details */ -.gh-wn-entry .kg-bookmark-card { - margin-bottom: 20px; -} - -.gh-wn-entry .kg-bookmark-container { - display: flex; - font-family: var(--font-family); - color: var(--darkgrey); - text-decoration: none; - min-height: 148px; - box-shadow: 0px 2px 5px -1px rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.09); - border-radius: 3px; -} - -.gh-wn-entry .kg-bookmark-content { - display: flex; - flex-direction: column; - flex-grow: 1; - align-items: flex-start; - justify-content: flex-start; - padding: 16px; -} - -.gh-wn-entry .kg-bookmark-title { - font-size: 1.3rem; - line-height: 1.5em; - font-weight: 600; - color: color(var(--midgrey) l(-30%)); -} - -.gh-wn-entry .kg-bookmark-container:hover .kg-bookmark-title { - color: var(--blue); -} - -.gh-wn-entry .kg-bookmark-description { - display: -webkit-box; - font-size: 1.25rem; - line-height: 1.5em; - color: color(var(--midgrey) l(-10%)); - font-weight: 400; - margin-top: 12px; - max-height: 36px; - overflow-y: hidden; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; -} - -.gh-wn-entry .kg-bookmark-thumbnail { - position: relative; - min-width: 40%; - max-height: 100%; -} - -.gh-wn-entry .kg-bookmark-thumbnail img { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - border-radius: 0 3px 3px 0; -} - -.gh-wn-entry .kg-bookmark-metadata { - display: flex; - align-items: center; - font-size: 1.25rem; - font-weight: 400; - color: color(var(--midgrey) l(-10%)); - margin-top: 14px; - flex-wrap: wrap; -} - -.gh-wn-entry .kg-bookmark-icon { - width: 18px; - height: 18px; - margin-right: 8px; -} - -.gh-wn-entry .kg-bookmark-author { - line-height: 1.5em; -} - -.gh-wn-entry .kg-bookmark-author:after { - content: "•"; - margin: 0 6px; -} - -.gh-wn-entry .kg-bookmark-publisher { - overflow: hidden; - line-height: 1.5em; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 160px; -} - -.gh-wn-entry .gh-wn-footer { - margin: 0 -32px -32px; - padding: 14px 32px 16px; - border-top: 1px solid var(--whitegrey); - justify-content: space-between; -} - -.gh-wn-footer { - position: relative; - margin-top: 14px; - margin-bottom: -13px; -} - -.gh-wn-footer:before { - position: absolute; - content: ""; - top: -14px; - left: -32px; - right: -32px; - height: 6px; - background: rgba(255,255,255,0); - box-shadow: - 0 -0.3px 1px rgba(0, 0, 0, 0.03), - 0 -4px 7px rgba(0, 0, 0, 0.06); -} - -.gh-about-container { - display: grid; - grid-template-columns: 2fr 1fr; - grid-gap: 80px; -} - -.gh-whats-new-canvas .gh-about-container { - display: flex; - grid-template-columns: unset; - grid-gap: unset; - margin: 0 auto; - max-width: 920px; - margin-top: 60px; -} - -.gh-about-container h2 { - font-size: 1.65rem; - line-height: 1.4em; - font-weight: 600; - border-bottom: 1px solid var(--lightgrey-l2); - padding-bottom: 12px; - margin-bottom: 12px; -} - -.gh-about-box { - position: sticky; - top: 96px; - right: 0; - display: flex; - flex-grow: 1; - flex-direction: column; - height: max-content; - border-radius: 3px; - min-width: 300px; -} - -.gh-about-box.grey { - border: none; - background: var(--main-color-content-greybg); -} - -@media (max-width: 1380px) { - .gh-wn-content { - max-width: 36vw; - } -} - -@media (max-width: 1120px) { - .gh-wn-content { - max-width: 680px; - } - - .gh-about-box { - position: relative; - top: unset; - right: unset; - } - - .gh-about-container { - grid-template-columns: unset; - grid-template-rows: auto; - grid-gap: 32px; - } - - .gh-whats-new { - grid-row: 3/4; - } - - - .gh-about-header-actions a { - display: none; - } - - .gh-wn-entry iframe { - max-width: 100%; - } -} - -/* Custom card styles -/* ---------------------------------------------------------- */ - -.gh-whats-new .kg-audio-card { - display: flex; - width: 100%; - min-height: 96px; - border-radius: 3px; - box-shadow: inset 0 0 0 1px rgba(124, 139, 154, 0.25); - margin-bottom: 1.5em; -} - -.gh-whats-new .kg-audio-card+.gh-whats-new .kg-audio-card { - margin-top: 1em; -} - -.gh-whats-new .kg-audio-thumbnail { - display: flex; - justify-content: center; - align-items: center; - width: 80px; - min-width: 80px; - margin: 8px; - background: transparent; - object-fit: cover; - aspect-ratio: 1/1; - border-radius: 2px; -} - -.gh-whats-new .kg-audio-thumbnail.placeholder { - background: var(--accent-color); -} - -.gh-whats-new .kg-audio-thumbnail.placeholder svg { - width: 24px; - height: 24px; - fill: white; -} - -.gh-whats-new .kg-audio-player-container { - position: relative; - display: flex; - flex-direction: column; - justify-content: space-between; - flex: 1; - --seek-before-width: 0%; - --volume-before-width: 100%; - --buffered-width: 0%; -} - -.gh-whats-new .kg-audio-title { - width: 100%; - margin: 8px 0 0 0; - padding: 8px 12px; - border: none; - font-family: inherit; - font-size: 1.15em; - font-weight: 700; - line-height: 1.15em; - background: transparent; -} - -.gh-whats-new .kg-audio-player { - display: flex; - flex-grow: 1; - align-items: center; - padding: 8px 12px; -} - -.gh-whats-new .kg-audio-current-time { - min-width: 38px; - padding: 0 4px; - font-family: inherit; - font-size: .85em; - font-weight: 500; - line-height: 1.4em; - white-space: nowrap; -} - -.gh-whats-new .kg-audio-time { - width: 56px; - color: #ababab; - font-family: inherit; - font-size: .85em; - font-weight: 500; - line-height: 1.4em; - white-space: nowrap; -} - -.gh-whats-new .kg-audio-duration { - padding: 0 4px; -} - -.gh-whats-new .kg-audio-play-icon, -.gh-whats-new .kg-audio-pause-icon { - position: relative; - bottom: 1px; - padding: 0px 4px 0 0; - font-size: 0; - background: transparent; -} - -.gh-whats-new .kg-audio-hide { - display: none !important; -} - -.gh-whats-new .kg-audio-play-icon svg, -.gh-whats-new .kg-audio-pause-icon svg { - width: 14px; - height: 14px; - fill: currentColor; -} - -.gh-whats-new .kg-audio-seek-slider { - flex-grow: 1; - margin: 0 4px; - width: 100%; -} - -@media (max-width: 640px) { - .gh-whats-new .kg-audio-seek-slider { - display: none; - } -} - -.gh-whats-new .kg-audio-playback-rate { - min-width: 37px; - padding: 0 4px; - font-family: inherit; - font-size: .85em; - font-weight: 600; - line-height: 1.4em; - text-align: left; - background: transparent; - white-space: nowrap; -} - -@media (max-width: 640px) { - .gh-whats-new .kg-audio-playback-rate { - padding-left: 8px; - } -} - -.gh-whats-new .kg-audio-mute-icon, -.gh-whats-new .kg-audio-unmute-icon { - position: relative; - bottom: -1px; - padding: 0 4px; - font-size: 0; - background: transparent; -} - -@media (max-width: 640px) { - .gh-whats-new .kg-audio-mute-icon, - .gh-whats-new .kg-audio-unmute-icon { - margin-left: auto; - } -} - -.gh-whats-new .kg-audio-mute-icon svg, -.gh-whats-new .kg-audio-unmute-icon svg { - width: 16px; - height: 16px; - fill: currentColor; -} - -.gh-whats-new .kg-audio-volume-slider { - flex-grow: 1; - width: 100%; - min-width: 50px; - max-width: 80px; -} - -@media (max-width: 400px) { - .gh-whats-new .kg-audio-volume-slider { - display: none; - } -} - -.gh-whats-new .kg-audio-seek-slider::before { - content: ""; - position: absolute; - left: 0; - width: var(--seek-before-width) !important; - height: 4px; - cursor: pointer; - background-color: currentColor; - border-radius: 2px; -} - -.gh-whats-new .kg-audio-volume-slider::before { - content: ""; - position: absolute; - left: 0; - width: var(--volume-before-width) !important; - height: 4px; - cursor: pointer; - background-color: currentColor; - border-radius: 2px; -} - -/* Resetting browser styles -/* --------------------------------------------------------------- */ - -.gh-whats-new .kg-audio-player-container input[type=range] { - position: relative; - -webkit-appearance: none; - background: transparent; -} - -.gh-whats-new .kg-audio-player-container input[type=range]:focus { - outline: none; -} - -.gh-whats-new .kg-audio-player-container input[type=range]::-webkit-slider-thumb { - -webkit-appearance: none; -} - -.gh-whats-new .kg-audio-player-container input[type=range]::-ms-track { - cursor: pointer; - border-color: transparent; - color: transparent; - background: transparent; -} - -.gh-whats-new .kg-audio-player-container button { - display: flex; - align-items: center; - border: 0; - cursor: pointer; -} - -.gh-whats-new .kg-audio-player-container input[type="range"] { - height: auto; - padding: 0; - border: 0; -} - -/* Chrome & Safari styles -/* --------------------------------------------------------------- */ - -.gh-whats-new .kg-audio-player-container input[type="range"]::-webkit-slider-runnable-track { - width: 100%; - height: 4px; - cursor: pointer; - background: rgba(124, 139, 154, 0.25); - border-radius: 2px; -} - -.gh-whats-new .kg-audio-player-container input[type="range"]::-webkit-slider-thumb { - position: relative; - box-sizing: content-box; - width: 13px; - height: 13px; - margin: -5px 0 0 0; - border: 0; - cursor: pointer; - background: #fff; - border-radius: 50%; - box-shadow: 0 0 0 1px rgba(0,0,0,.08), 0 1px 4px rgba(0,0,0,0.24); -} - -.gh-whats-new .kg-audio-player-container input[type="range"]:active::-webkit-slider-thumb { - transform: scale(1.2); -} - -/* Firefox styles -/* --------------------------------------------------------------- */ - -.gh-whats-new .kg-audio-player-container input[type="range"]::-moz-range-track { - width: 100%; - height: 4px; - cursor: pointer; - background: rgba(124, 139, 154, 0.25); - border-radius: 2px; -} - -.gh-whats-new .kg-audio-player-container input[type="range"]::-moz-range-progress { - background: currentColor; - border-radius: 2px; -} - -.gh-whats-new .kg-audio-player-container input[type="range"]::-moz-range-thumb { - box-sizing: content-box; - width: 13px; - height: 13px; - border: 0; - cursor: pointer; - background: #fff; - border-radius: 50%; - box-shadow: 0 0 0 1px rgba(0,0,0,.08), 0 1px 4px rgba(0,0,0,0.24); -} - -.gh-whats-new .kg-audio-player-container input[type="range"]:active::-moz-range-thumb { - transform: scale(1.2); -} - -/* Edge & IE styles -/* --------------------------------------------------------------- */ - -.gh-whats-new .kg-audio-player-container input[type="range"]::-ms-track { - width: 100%; - height: 3px; - border: solid transparent; - color: transparent; - cursor: pointer; - background: transparent; -} - -.gh-whats-new .kg-audio-player-container input[type="range"]::-ms-fill-lower { - background: #fff; -} - -.gh-whats-new .kg-audio-player-container input[type="range"]::-ms-fill-upper { - background: currentColor; -} - -.gh-whats-new .kg-audio-player-container input[type="range"]::-ms-thumb { - box-sizing: content-box; - width: 13px; - height: 13px; - border: 0; - cursor: pointer; - background: #fff; - border-radius: 50%; - box-shadow: 0 0 0 1px rgba(0,0,0,.08), 0 1px 4px rgba(0,0,0,0.24); -} - -.gh-whats-new .kg-audio-player-container input[type="range"]:active::-ms-thumb { - transform: scale(1.2); -} - -.gh-whats-new .kg-product-card { - display: flex; - align-items: center; - flex-direction: column; - width: 100%; - margin-bottom: 1.5em; -} - -.gh-whats-new .kg-product-card-container { - display: grid; - grid-template-columns: auto min-content; - align-items: center; - grid-row-gap: 16px; - background: transparent; - max-width: 550px; - width: 100%; -} - -.gh-whats-new .kg-product-card-image { - grid-column: 1 / 3; - justify-self: center; -} - -.gh-whats-new .kg-product-card-title-container { - grid-column: 1 / 2; -} - -.gh-whats-new .kg-product-card h4.kg-product-card-title { - font-family: var(--font-family); - text-decoration: none; - font-weight: 700; - font-size: 1.4em; - margin-top: 0; - margin-bottom: 0; - line-height: 1.15em; - text-transform: none; - color: inherit; -} - -.gh-whats-new .kg-product-card-description { - grid-column: 1 / 3; -} - -.gh-whats-new .kg-product-card .kg-product-card-description p, -.gh-whats-new .kg-product-card .kg-product-card-description ol, -.gh-whats-new .kg-product-card .kg-product-card-description ul { - font-family: var(--font-family); - font-size: 0.9em; - line-height: 1.5em; - opacity: .7; -} - -.gh-whats-new .kg-product-card .kg-product-card-description p:not(:first-of-type) { - margin-top: 0.8em; - margin-bottom: 0; -} - -.gh-whats-new .kg-product-card .kg-product-card-description p:first-of-type { - margin-top: -4px; -} - -.gh-whats-new .kg-product-card .kg-product-card-description ul, -.gh-whats-new .kg-product-card .kg-product-card-description ol { - margin-top: 0.95em; -} - -.gh-whats-new .kg-product-card .kg-product-card-description li+li { - margin-top: 0.2em; -} - -.gh-whats-new .kg-product-card-rating { - display: flex; - align-items: center; - grid-column: 2 / 3; - align-self: start; - justify-self: end; - padding-left: 16px; -} - -@media (max-width: 400px) { - .gh-whats-new .kg-product-card-title-container { - grid-column: 1 / 3; - } - - .gh-whats-new .kg-product-card-rating { - grid-column: 1 / 3; - justify-self: start; - margin-top: -15px; - padding-left: 0; - } -} - -.gh-whats-new .kg-product-card-rating-star { - height: 28px; - width: 20px; -} - -.gh-whats-new .kg-product-card-rating-star svg { - width: 16px; - height: 16px; - fill: currentColor; - opacity: 0.15; -} - -.gh-whats-new .kg-product-card-rating-star svg path { - fill: unset; -} - -.gh-whats-new .kg-product-card-rating-active.kg-product-card-rating-star svg { - opacity: 1; -} - -.gh-whats-new .kg-product-card a.kg-product-card-button { - justify-content: center; - grid-column: 1 / 3; - display: flex; - position: static; - align-items: center; - font-family: var(--font-family); - font-size: 0.95em; - font-weight: 600; - line-height: 1em; - text-decoration: none; - width: 100%; - height: 2.4em; - border-radius: 5px; - padding: 0 1.2em; - transition: opacity 0.2s ease-in-out; - margin: 0; -} - -.gh-whats-new .kg-product-card a.kg-product-card-btn-accent { - background-color: var(--accent-color); - color: #fff; -} - -.gh-whats-new .kg-blockquote-alt { - font-size: 1.5em; - font-style: italic; - line-height: 1.7em; - text-align: center; - padding: 0 2.5em; -} - -@media (max-width: 800px) { - .gh-whats-new .kg-blockquote-alt { - font-size: 1.4em; - padding-left: 2em; - padding-right: 2em; - } -} - -@media (max-width: 600px) { - .gh-whats-new .kg-blockquote-alt { - font-size: 1.2em; - padding-left: 1.75em; - padding-right: 1.75em; - } -} - -.gh-whats-new .kg-button-card { - display: flex; - position: static; - align-items: center; - width: 100%; - justify-content: flex-start; - padding: 30px 0; -} - -.gh-whats-new .kg-button-card.kg-align-left { - justify-content: flex-start; -} - -.gh-whats-new .kg-button-card a.kg-btn { - display: flex; - position: static; - align-items: center; - padding: 0 1.2em; - height: 2.4em; - line-height: 1em; - font-family: var(--font-family); - font-size: 0.95em; - font-weight: 600; - text-decoration: none; - border-radius: 5px; - transition: opacity 0.2s ease-in-out; -} - -.gh-whats-new .kg-button-card a.kg-btn:hover { - opacity: 0.85; -} - -.gh-whats-new .kg-button-card a.kg-btn-accent { - background-color: var(--accent-color); - color: #fff; -} - -.gh-whats-new .kg-callout-card { - display: flex; - padding: 1.2em 1.6em; - border-radius: 3px; -} - -.gh-whats-new .kg-callout-card-grey { - background: rgba(124, 139, 154, 0.13); -} - -.gh-whats-new .kg-callout-card-white { - background: transparent; - box-shadow: inset 0 0 0 1px rgba(124, 139, 154, 0.25); -} - -.gh-whats-new .kg-callout-card-blue { - background: rgba(33, 172, 232, 0.12); -} - -.gh-whats-new .kg-callout-card-green { - background: rgba(52, 183, 67, 0.12); -} - -.gh-whats-new .kg-callout-card-yellow { - background: rgba(240, 165, 15, 0.13); -} - -.gh-whats-new .kg-callout-card-red { - background: rgba(209, 46, 46, 0.11); -} - -.gh-whats-new .kg-callout-card-pink { - background: rgba(225, 71, 174, 0.11); -} - -.gh-whats-new .kg-callout-card-purple { - background: rgba(135, 85, 236, 0.12); -} - -.gh-whats-new .kg-callout-card-accent { - background: var(--ghost-accent-color); - color: #fff; -} - -.gh-whats-new .kg-callout-card-accent a { - color: #fff; -} - -.gh-whats-new .kg-callout-card div.kg-callout-emoji { - padding-right: .8em; - line-height: 1.25em; - font-size: 1.15em; -} - -.gh-whats-new .kg-callout-card div.kg-callout-text { - font-size: .95em; - line-height: 1.5em; -} - -.gh-whats-new .kg-callout-card + .kg-callout-card { - margin-top: 1em; -} - -.gh-whats-new .kg-file-card { - display: flex; -} - -.gh-whats-new .kg-file-card a.kg-file-card-container { - display: flex; - align-items: center; - justify-content: space-between; - color: inherit; - padding: 6px; - min-height: 92px; - border: 1px solid rgb(124 139 154 / 25%); - border-radius: 3px; - transition: all ease-in-out 0.35s; - text-decoration: none; - width: 100%; -} - -.gh-whats-new .kg-file-card a.kg-file-card-container:hover { - border: 1px solid rgb(124 139 154 / 35%); -} - -.gh-whats-new .kg-file-card-contents { - display: flex; - flex-direction: column; - justify-content: space-between; - margin: 4px 8px; -} - -.gh-whats-new .kg-file-card-title { - font-size: 1.15em; - font-weight: 700; - line-height: 1.3em; -} - -.gh-whats-new .kg-file-card-caption { - font-size: 0.95em; - line-height: 1.5em; - opacity: 0.6; -} - -.gh-whats-new .kg-file-card-metadata { - display: inline; - font-size: 0.825em; - line-height: 1.5em; - margin-top: 2px; -} - -.gh-whats-new .kg-file-card-filename { - display: inline; - font-weight: 500; -} - -.gh-whats-new .kg-file-card-filesize { - display: inline-block; - font-size: 0.925em; - opacity: 0.6; -} - -.gh-whats-new .kg-file-card-filesize:before { - display: inline-block; - content: "\2022"; - margin-right: 4px; -} - -.gh-whats-new .kg-file-card-icon { - position: relative; - display: flex; - align-items: center; - justify-content: center; - width: 80px; - min-width: 80px; - height: 100%; -} - -.gh-whats-new .kg-file-card-icon:before { - position: absolute; - display: block; - content: ""; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: currentColor; - opacity: 0.06; - transition: opacity ease-in-out 0.35s; - border-radius: 2px; -} - -.gh-whats-new .kg-file-card a.kg-file-card-container:hover .kg-file-card-icon:before { - opacity: 0.08; -} - -.gh-whats-new .kg-file-card-icon svg { - width: 24px; - height: 24px; - color: var(--ghost-accent-color); -} - -/* Size variations */ -.gh-whats-new .kg-file-card-medium a.kg-file-card-container { - min-height: 72px; -} - -.gh-whats-new .kg-file-card-medium .kg-file-card-caption { - opacity: 1.0; - font-weight: 500; -} - -.gh-whats-new .kg-file-card-small a.kg-file-card-container { - min-height: 52px; -} - -.gh-whats-new .kg-file-card-small .kg-file-card-metadata { - font-size: 1.0em; - margin-top: 0; -} - -.gh-whats-new .kg-file-card-small .kg-file-card-icon svg { - width: 20px; - height: 20px; -} - -.gh-whats-new .kg-file-card + .kg-file-card { - margin-top: 1em; -} - -.gh-whats-new .kg-nft-card { - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - margin-left: auto; - margin-right: auto; -} - -.gh-whats-new .kg-nft-card a.kg-nft-card-container { - position: static; - display: flex; - flex: auto; - flex-direction: column; - text-decoration: none; - font-family: var(--font-family); - font-size: 14px; - font-weight: 400; - box-shadow: 0 2px 6px -2px rgb(0 0 0 / 10%), 0 0 1px rgb(0 0 0 / 40%); - width: 100%; - max-width: 512px; - color: #222; - background: #fff; - border-radius: 5px; - transition: none; -} - -.gh-whats-new .kg-nft-card * { - position: static; -} - -.gh-whats-new .kg-nft-metadata { - padding: 20px; - width: 100%; -} - -.gh-whats-new .kg-nft-image { - border-radius: 5px 5px 0 0; - width: 100%; -} - -.gh-whats-new .kg-nft-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 20px; -} - -.gh-whats-new .kg-nft-header h4.kg-nft-title { - font-family: inherit; - font-size: 19px; - font-weight: 700; - line-height: 1.3em; - min-width: unset; - max-width: unset; - margin: 0; - color: #222; -} - -.gh-whats-new .kg-nft-opensea-logo { - margin-top: 2px; - width: 100px; - object-fit: scale-down; -} - -.gh-whats-new .kg-nft-creator { - font-family: inherit; - line-height: 1.4em; - margin: 4px 0 0; - color: #ababab; -} - -.gh-whats-new .kg-nft-creator span { - font-weight: 500; - color: #222; -} - -.gh-whats-new .kg-nft-card p.kg-nft-description { - font-family: inherit; - font-size: 14px; - line-height: 1.4em; - margin: 20px 0 0; - color: #222; -} - -.gh-whats-new .kg-toggle-card { - background: transparent; - box-shadow: inset 0 0 0 1px rgba(124, 139, 154, 0.25); - border-radius: 4px; - padding: 1.2em; -} - -.gh-whats-new .kg-toggle-card[data-kg-toggle-state="close"] .kg-toggle-content{ - height: 0; - overflow: hidden; - transition: opacity .5s ease, top .35s ease; - opacity: 0; - top: -0.5em; - position: relative; -} - -.gh-whats-new .kg-toggle-content { - height: auto; - opacity: 1; - transition: opacity 1s ease, top .35s ease; - top: 0; - position: relative; -} - -.gh-whats-new .kg-toggle-card[data-kg-toggle-state="close"] svg { - transform: unset; -} - -.gh-whats-new .kg-toggle-heading { - cursor: pointer; - display: flex; - justify-content: space-between; - align-items: flex-start; -} - -.gh-whats-new .kg-toggle-card h4.kg-toggle-heading-text { - font-size: 1.15em; - font-weight: 700; - line-height: 1.3em; - margin-top: 0; - margin-bottom: 0; - text-transform: none; - color: inherit; -} - -.gh-whats-new .kg-toggle-content p:first-of-type { - margin-top: 0.5em; -} - -.gh-whats-new .kg-toggle-card .kg-toggle-content p, -.gh-whats-new .kg-toggle-card .kg-toggle-content ol, -.gh-whats-new .kg-toggle-card .kg-toggle-content ul { - font-size: 0.95em; - line-height: 1.5em; - margin-top: 0.95em; -} - -.gh-whats-new .kg-toggle-card li + li { - margin-top: 0.5em; -} - -.gh-whats-new .kg-toggle-card-icon { - height: 24px; - width: 24px; - display: flex; - justify-content: center; - align-items: center; - margin-left: 1em; - background: none; - border: 0; -} - -.gh-whats-new .kg-toggle-heading svg { - width: 14px; - color: rgba(124, 139, 154, 0.5); - transition: all 0.3s; - transform: rotate(-180deg); -} - -.gh-whats-new .kg-toggle-heading path { - fill: none; - stroke: currentcolor; - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 1.5; - fill-rule: evenodd; -} - -.gh-whats-new .kg-toggle-card + .kg-toggle-card { - margin-top: 1em; -} - -.gh-whats-new .kg-video-card { - position: relative; - --seek-before-width: 0%; - --volume-before-width: 100%; - --buffered-width: 0%; -} - -.gh-whats-new .kg-video-card video { - display: block; - max-width: 100%; - height: auto; -} - -.gh-whats-new .kg-video-container { - position: relative; - display: flex; - flex-direction: column; - align-items: center; -} - -.gh-whats-new .kg-video-overlay { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - display: flex; - justify-content: center; - align-items: center; - background-image: linear-gradient(180deg,rgba(0,0,0,0.3) 0,transparent 70%,transparent 100%); - z-index: 99; - transition: opacity .2s ease-in-out; -} - -.gh-whats-new .kg-video-large-play-icon { - display: flex; - justify-content: center; - align-items: center; - width: 72px; - height: 72px; - padding: 0; - background: rgba(0, 0, 0, 0.5); - border-radius: 50%; - transition: opacity .2s ease-in-out; -} - -.gh-whats-new .kg-video-large-play-icon svg { - width: 20px; - height: auto; - margin-left: 2px; - fill: #fff; -} - -.gh-whats-new .kg-video-player-container { - position: absolute; - bottom: 0; - width: 100%; - height: 80px; - background: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,.5)); - z-index: 99; - transition: opacity .2s ease-in-out; - -} - -.gh-whats-new .kg-video-player { - position: absolute; - bottom: 0; - display: flex; - align-items: center; - width: 100%; - z-index: 99; - padding: 12px 16px; -} - -.gh-whats-new .kg-video-current-time { - min-width: 38px; - padding: 0 4px; - color: #fff; - font-family: inherit; - font-size: .85em; - font-weight: 500; - line-height: 1.4em; - white-space: nowrap; -} - -.gh-whats-new .kg-video-time { - color: rgba(255, 255, 255, 0.6); - font-family: inherit; - font-size: .85em; - font-weight: 500; - line-height: 1.4em; - white-space: nowrap; -} - -.gh-whats-new .kg-video-duration { - padding: 0 4px; -} - -.gh-whats-new .kg-video-play-icon, -.gh-whats-new .kg-video-pause-icon { - position: relative; - padding: 0px 4px 0 0; - font-size: 0; - background: transparent; -} - -.gh-whats-new .kg-video-hide { - display: none !important; -} - -.gh-whats-new .kg-video-hide-animated { - opacity: 0 !important; - transition: opacity .2s ease-in-out; - cursor: initial; -} - -.gh-whats-new .kg-video-play-icon svg, -.gh-whats-new .kg-video-pause-icon svg { - width: 14px; - height: 14px; - fill: #fff; -} - -.gh-whats-new .kg-video-seek-slider { - flex-grow: 1; - margin: 0 4px; -} - -@media (max-width: 520px) { - .gh-whats-new .kg-video-seek-slider { - display: none; - } -} - -.gh-whats-new .kg-video-playback-rate { - min-width: 37px; - padding: 0 4px; - color: #fff; - font-family: inherit; - font-size: .85em; - font-weight: 600; - line-height: 1.4em; - text-align: left; - background: transparent; - white-space: nowrap; -} - -@media (max-width: 520px) { - .gh-whats-new .kg-video-playback-rate { - padding-left: 8px; - } -} - -.gh-whats-new .kg-video-mute-icon, -.gh-whats-new .kg-video-unmute-icon { - position: relative; - bottom: -1px; - padding: 0 4px; - font-size: 0; - background: transparent; -} - -@media (max-width: 520px) { - .gh-whats-new .kg-video-mute-icon, - .gh-whats-new .kg-video-unmute-icon { - margin-left: auto; - } -} - -.gh-whats-new .kg-video-mute-icon svg, -.gh-whats-new .kg-video-unmute-icon svg { - width: 16px; - height: 16px; - fill: #fff; -} - -.gh-whats-new .kg-video-volume-slider { - width: 80px; -} - -@media (max-width: 300px) { - .gh-whats-new .kg-video-volume-slider { - display: none; - } -} - -.gh-whats-new .kg-video-seek-slider::before { - content: ""; - position: absolute; - left: 0; - width: var(--seek-before-width) !important; - height: 4px; - cursor: pointer; - background-color: #EBEEF0; - border-radius: 2px; -} - -.gh-whats-new .kg-video-volume-slider::before { - content: ""; - position: absolute; - left: 0; - width: var(--volume-before-width) !important; - height: 4px; - cursor: pointer; - background-color: #EBEEF0; - border-radius: 2px; -} - -/* Resetting browser styles -/* --------------------------------------------------------------- */ - -.gh-whats-new .kg-video-card input[type=range] { - position: relative; - -webkit-appearance: none; - background: transparent; -} - -.gh-whats-new .kg-video-card input[type=range]:focus { - outline: none; -} - -.gh-whats-new .kg-video-card input[type=range]::-webkit-slider-thumb { - -webkit-appearance: none; -} - -.gh-whats-new .kg-video-card input[type=range]::-ms-track { - cursor: pointer; - border-color: transparent; - color: transparent; - background: transparent; -} - -.gh-whats-new .kg-video-card button { - display: flex; - align-items: center; - border: 0; - cursor: pointer; -} - -.gh-whats-new .kg-video-card input[type="range"] { - height: auto; - padding: 0; - border: 0; -} - -/* Chrome & Safari styles -/* --------------------------------------------------------------- */ - -.gh-whats-new .kg-video-card input[type="range"]::-webkit-slider-runnable-track { - width: 100%; - height: 4px; - cursor: pointer; - background: rgba(255, 255, 255, 0.2); - border-radius: 2px; -} - -.gh-whats-new .kg-video-card input[type="range"]::-webkit-slider-thumb { - position: relative; - box-sizing: content-box; - width: 13px; - height: 13px; - margin: -5px 0 0 0; - border: 0; - cursor: pointer; - background: #fff; - border-radius: 50%; - box-shadow: 0 0 0 1px rgba(0,0,0,.08), 0 1px 4px rgba(0,0,0,0.24); -} - -.gh-whats-new .kg-video-card input[type="range"]:active::-webkit-slider-thumb { - transform: scale(1.2); -} - -/* Firefox styles -/* --------------------------------------------------------------- */ - -.gh-whats-new .kg-video-card input[type="range"]::-moz-range-track { - width: 100%; - height: 4px; - cursor: pointer; - background: rgba(255, 255, 255, 0.2); - border-radius: 2px; -} - -.gh-whats-new .kg-video-card input[type="range"]::-moz-range-progress { - background: #EBEEF0; - border-radius: 2px; -} - -.gh-whats-new .kg-video-card input[type="range"]::-moz-range-thumb { - box-sizing: content-box; - width: 13px; - height: 13px; - border: 0; - cursor: pointer; - background: #fff; - border-radius: 50%; - box-shadow: 0 0 0 1px rgba(0,0,0,.08), 0 1px 4px rgba(0,0,0,0.24); -} - -.gh-whats-new .kg-video-card input[type="range"]:active::-moz-range-thumb { - transform: scale(1.2); -} - -/* Edge & IE styles -/* --------------------------------------------------------------- */ - -.gh-whats-new .kg-video-card input[type="range"]::-ms-track { - width: 100%; - height: 3px; - border: solid transparent; - color: transparent; - cursor: pointer; - background: transparent; -} - -.gh-whats-new .kg-video-card input[type="range"]::-ms-fill-lower { - background: #fff; -} - -.gh-whats-new .kg-video-card input[type="range"]::-ms-fill-upper { - background: #EBEEF0; -} - -.gh-whats-new .kg-video-card input[type="range"]::-ms-thumb { - box-sizing: content-box; - width: 13px; - height: 13px; - border: 0; - cursor: pointer; - background: #fff; - border-radius: 50%; - box-shadow: 0 0 0 1px rgba(0,0,0,.08), 0 1px 4px rgba(0,0,0,0.24); -} - -.gh-whats-new .kg-video-card input[type="range"]:active::-ms-thumb { - transform: scale(1.2); -} - -/* File card styles */ -.gh-whats-new .kg-file-card { - display: flex; -} - -.gh-whats-new .kg-file-card a.kg-file-card-container { - display: flex; - align-items: stretch; - justify-content: space-between; - color: inherit; - padding: 6px; - min-height: 92px; - border: 1px solid rgb(124 139 154 / 25%); - border-radius: 3px; - transition: all ease-in-out 0.35s; - text-decoration: none; - width: 100%; -} - -.gh-whats-new .kg-file-card a.kg-file-card-container:hover { - border: 1px solid rgb(124 139 154 / 35%); -} - -.gh-whats-new .kg-file-card-contents { - display: flex; - flex-direction: column; - justify-content: space-between; - margin: 4px 8px; - width: 100% -} - -.gh-whats-new .kg-file-card-title { - font-size: 1.15em; - font-weight: 700; - line-height: 1.3em; -} - -.gh-whats-new .kg-file-card-caption { - font-size: 0.95em; - line-height: 1.3em; - opacity: 0.6; -} - -.gh-whats-new .kg-file-card-title + .kg-file-card-caption { - margin-top: -6px; -} - -.gh-whats-new .kg-file-card-metadata { - display: inline; - font-size: 0.825em; - line-height: 1.3em; - margin-top: 2px; -} - -.gh-whats-new .kg-file-card-filename { - display: inline; - font-weight: 500; -} - -.gh-whats-new .kg-file-card-filesize { - display: inline-block; - font-size: 0.925em; - opacity: 0.6; -} - -.gh-whats-new .kg-file-card-filesize:before { - display: inline-block; - content: "\2022"; - margin-right: 4px; -} - -.gh-whats-new .kg-file-card-icon { - position: relative; - display: flex; - align-items: center; - justify-content: center; - width: 80px; - min-width: 80px; - height: 100%; -} - -.gh-whats-new .kg-file-card-icon:before { - position: absolute; - display: block; - content: ""; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: currentColor; - opacity: 0.06; - transition: opacity ease-in-out 0.35s; - border-radius: 2px; -} - -.gh-whats-new .kg-file-card a.kg-file-card-container:hover .kg-file-card-icon:before { - opacity: 0.08; -} - -.gh-whats-new .kg-file-card-icon svg { - width: 24px; - height: 24px; - color: var(--ghost-accent-color); -} - -.gh-whats-new .kg-file-card-medium a.kg-file-card-container { - min-height: 72px; -} - -.gh-whats-new .kg-file-card-medium .kg-file-card-caption { - opacity: 1.0; - font-weight: 500; -} - -.gh-whats-new .kg-file-card-small a.kg-file-card-container { - align-items: center; - min-height: 52px; -} - -.gh-whats-new .kg-file-card-small .kg-file-card-metadata { - font-size: 1.0em; - margin-top: 0; -} - -.gh-whats-new .kg-file-card-small .kg-file-card-icon svg { - width: 20px; - height: 20px; -} - -.gh-whats-new .kg-file-card + .kg-file-card { - margin-top: 1em; -} - -/* Header card */ - -.gh-whats-new .kg-header-card { - padding: 12vmin 4em; - min-height: 20vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - margin-bottom: 1.5em; -} - -.gh-whats-new .kg-header-card.kg-size-small { - padding-top: 8vmin; - padding-bottom: 8vmin; - min-height: 12vh; -} - -.gh-whats-new .kg-header-card.kg-size-large { - padding-top: 12vmin; - padding-bottom: 12vmin; - min-height: 40vh; -} - -.gh-whats-new .kg-header-card.kg-align-left { - text-align: left; - align-items: flex-start; -} - -.gh-whats-new .kg-header-card.kg-style-dark { - background: #151515; - color: #ffffff; -} - -.gh-whats-new .kg-header-card.kg-style-light { - background-color: #fafafa; -} - -.gh-whats-new .kg-header-card.kg-style-accent { - background-color: var(--accent-color); -} - -.gh-whats-new .kg-header-card.kg-style-image { - position: relative; - background-color: #e7e7e7; - background-size: cover; - background-position: center; -} - -.gh-whats-new .kg-header-card.kg-style-image::before { - position: absolute; - display: block; - content: ""; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: linear-gradient(0deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.2)); -} - -.gh-whats-new .kg-header-card h2.kg-header-card-header { - font-size: 5em; - font-weight: 700; - line-height: 1.1em; - letter-spacing: -0.01em; - margin: 0; -} - -.gh-whats-new .kg-header-card h2.kg-header-card-header strong { - font-weight: 800; -} - -.gh-whats-new .kg-header-card.kg-size-small h2.kg-header-card-header { - font-size: 4em; -} - -.gh-whats-new .kg-header-card.kg-size-large h2.kg-header-card-header { - font-size: 6em; -} - -.gh-whats-new .kg-header-card h3.kg-header-card-subheader { - font-size: 1.5em; - font-weight: 500; - line-height: 1.4em; - margin: 0; - max-width: 40em; -} - -.gh-whats-new .kg-header-card h2 + h3.kg-header-card-subheader { - margin: 0.35em 0 0; -} - -.gh-whats-new .kg-header-card h3.kg-header-card-subheader strong { - font-weight: 600; -} - -.gh-whats-new .kg-header-card.kg-size-small h3.kg-header-card-subheader { - font-size: 1.25em; -} - -.gh-whats-new .kg-header-card.kg-size-large h3.kg-header-card-subheader { - font-size: 1.75em; -} - -.gh-whats-new .kg-header-card:not(.kg-style-light) h2.kg-header-card-header, -.gh-whats-new .kg-header-card:not(.kg-style-light) h3.kg-header-card-subheader { - color: #ffffff; -} - -.gh-whats-new .kg-header-card.kg-style-accent h3.kg-header-card-subheader, -.gh-whats-new .kg-header-card.kg-style-image h3.kg-header-card-subheader { - opacity: 1.0; -} - -.gh-whats-new .kg-header-card.kg-style-image h2.kg-header-card-header, -.gh-whats-new .kg-header-card.kg-style-image h3.kg-header-card-subheader, -.gh-whats-new .kg-header-card.kg-style-image a.kg-header-card-button { - z-index: 99; -} - -.gh-whats-new .kg-header-card h2.kg-header-card-header a, -.gh-whats-new .kg-header-card h3.kg-header-card-subheader a { - color: var(--ghost-accent-color); -} - -.gh-whats-new .kg-header-card.kg-style-accent h2.kg-header-card-header a, -.gh-whats-new .kg-header-card.kg-style-accent h3.kg-header-card-subheader a, -.gh-whats-new .kg-header-card.kg-style-image h2.kg-header-card-header a, -.gh-whats-new .kg-header-card.kg-style-image h3.kg-header-card-subheader a { - color: #fff; -} - -.gh-whats-new .kg-header-card a.kg-header-card-button { - display: flex; - position: static; - align-items: center; - fill: #fff; - background: #fff; - border-radius: 3px; - outline: none; - font-family: var(--font-family); - font-size: 1.05em; - font-weight: 600; - line-height: 1em; - text-align: center; - text-decoration: none; - letter-spacing: .2px; - white-space: nowrap; - text-overflow: ellipsis; - color: #151515; - height: 2.7em; - padding: 0 1.2em; - transition: opacity .2s ease; -} - -.gh-whats-new .kg-header-card h2 + a.kg-header-card-button, -.gh-whats-new .kg-header-card h3 + a.kg-header-card-button { - margin: 1.75em 0 0; -} - -.gh-whats-new .kg-header-card a.kg-header-card-button:hover { - opacity: 0.85; -} - -.gh-whats-new .kg-header-card.kg-size-large a.kg-header-card-button { - font-size: 1.1em; - height: 2.9em; -} - -.gh-whats-new .kg-header-card.kg-size-large h2 + a.kg-header-card-button, -.gh-whats-new .kg-header-card.kg-size-large h3 + a.kg-header-card-button { - margin-top: 2em; -} - -.gh-whats-new .kg-header-card.kg-size-small a.kg-header-card-button { - height: 2.4em; - font-size: 1em; -} - -.gh-whats-new .kg-header-card.kg-size-small h2 + a.kg-header-card-button, -.gh-whats-new .kg-header-card.kg-size-small h3 + a.kg-header-card-button { - margin-top: 1.5em; -} - -.gh-whats-new .kg-header-card.kg-style-image a.kg-header-card-button, -.gh-whats-new .kg-header-card.kg-style-dark a.kg-header-card-button { - background: #fff; - color: #151515; -} - -.gh-whats-new .kg-header-card.kg-style-light a.kg-header-card-button { - background: var(--ghost-accent-color); - color: #fff; -} - -.gh-whats-new .kg-header-card.kg-style-accent a.kg-header-card-button { - background: #fff; - color: #151515; -} - diff --git a/apps/admin-x-activitypub/src/App.tsx b/apps/admin-x-activitypub/src/App.tsx index 983751ea4581..efb1b2fdd221 100644 --- a/apps/admin-x-activitypub/src/App.tsx +++ b/apps/admin-x-activitypub/src/App.tsx @@ -1,4 +1,4 @@ -import MainContent from './MainContent'; +import ListIndex from './components/ListIndex'; import {DesignSystemApp, DesignSystemAppProps} from '@tryghost/admin-x-design-system'; import {FrameworkProvider, TopLevelFrameworkProps} from '@tryghost/admin-x-framework'; import {RoutingProvider} from '@tryghost/admin-x-framework/routing'; @@ -8,25 +8,16 @@ interface AppProps { designSystem: DesignSystemAppProps; } -const modals = { - paths: { - 'follow-site': 'FollowSite', - 'view-following': 'ViewFollowing', - 'view-followers': 'ViewFollowers' - }, - load: async () => import('./components/modals') -}; - const App: React.FC = ({framework, designSystem}) => { return ( - + - + ); }; -export default App; \ No newline at end of file +export default App; diff --git a/apps/admin-x-activitypub/src/MainContent.tsx b/apps/admin-x-activitypub/src/MainContent.tsx deleted file mode 100644 index 31ddedf3f885..000000000000 --- a/apps/admin-x-activitypub/src/MainContent.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import ActivityPubComponent from './components/ListIndex'; - -const MainContent = () => { - return ; -}; - -export default MainContent; diff --git a/apps/admin-x-activitypub/src/assets/images/ap-welcome.png b/apps/admin-x-activitypub/src/assets/images/ap-welcome.png deleted file mode 100644 index 189768bcc5de..000000000000 Binary files a/apps/admin-x-activitypub/src/assets/images/ap-welcome.png and /dev/null differ diff --git a/apps/admin-x-activitypub/src/components/FollowSite.tsx b/apps/admin-x-activitypub/src/components/FollowSite.tsx deleted file mode 100644 index c137e9f87028..000000000000 --- a/apps/admin-x-activitypub/src/components/FollowSite.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import NiceModal from '@ebay/nice-modal-react'; -import {Modal, TextField, showToast} from '@tryghost/admin-x-design-system'; -import {useFollow} from '@tryghost/admin-x-framework/api/activitypub'; -import {useQueryClient} from '@tryghost/admin-x-framework'; -import {useRouting} from '@tryghost/admin-x-framework/routing'; -import {useState} from 'react'; - -// const sleep = (ms: number) => ( -// new Promise((resolve) => { -// setTimeout(resolve, ms); -// }) -// ); - -const FollowSite = NiceModal.create(() => { - const {updateRoute} = useRouting(); - const modal = NiceModal.useModal(); - const mutation = useFollow(); - const client = useQueryClient(); - - // mutation.isPending - // mutation.isError - // mutation.isSuccess - // mutation.mutate({username: '@index@site.com'}) - // mutation.reset(); - - // State to manage the text field value - const [profileName, setProfileName] = useState(''); - // const [success, setSuccess] = useState(false); - const [errorMessage, setError] = useState(null); - - const handleFollow = async () => { - try { - // Perform the mutation - await mutation.mutateAsync({username: profileName}); - // If successful, set the success state to true - // setSuccess(true); - showToast({ - message: 'Site followed', - type: 'success' - }); - - // // Because we don't return the new follower data from the API, we need to wait a bit to let it process and then update the query. - // // This is a dirty hack and should be replaced with a better solution. - // await sleep(2000); - - modal.remove(); - // Refetch the following data. - // At this point it might not be updated yet, but it will be eventually. - await client.refetchQueries({queryKey: ['FollowingResponseData'], type: 'active'}); - updateRoute(''); - } catch (error) { - // If there's an error, set the error state - setError(errorMessage); - } - }; - - return ( - { - mutation.reset(); - updateRoute(''); - }} - cancelLabel='Cancel' - okLabel='Follow' - size='sm' - title='Follow a Ghost site' - onOk={handleFollow} - > -
- setProfileName(e.target.value)} - /> -
-
- ); -}); - -export default FollowSite; diff --git a/apps/admin-x-activitypub/src/components/ListIndex.tsx b/apps/admin-x-activitypub/src/components/ListIndex.tsx index 7c109c8ba81f..7156dba14575 100644 --- a/apps/admin-x-activitypub/src/components/ListIndex.tsx +++ b/apps/admin-x-activitypub/src/components/ListIndex.tsx @@ -1,282 +1,26 @@ -// 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'; -import {ActorProperties, ObjectProperties, useBrowseFollowersForUser, useBrowseFollowingForUser, useBrowseInboxForUser} from '@tryghost/admin-x-framework/api/activitypub'; -import {Avatar, Button, Heading, List, ListItem, Page, SettingValue, ViewContainer, ViewTab} from '@tryghost/admin-x-design-system'; -import {useBrowseSite} from '@tryghost/admin-x-framework/api/site'; -import {useRouting} from '@tryghost/admin-x-framework/routing'; - -interface ViewArticleProps { - object: ObjectProperties, - onBackToList: () => void; -} - -const ActivityPubComponent: React.FC = () => { - const {updateRoute} = useRouting(); - - // TODO: Replace with actual user ID - const {data: {items: activities = []} = {}} = useBrowseInboxForUser('index'); - const {data: {totalItems: followingCount = 0} = {}} = useBrowseFollowingForUser('index'); - const {data: {totalItems: followersCount = 0} = {}} = useBrowseFollowersForUser('index'); - - const [articleContent, setArticleContent] = useState(null); - const [, setArticleActor] = useState(null); - - const handleViewContent = (object: ObjectProperties, actor: ActorProperties) => { - setArticleContent(object); - setArticleActor(actor); - }; - - const handleBackToList = () => { - setArticleContent(null); - }; - - const [selectedTab, setSelectedTab] = useState('inbox'); - - const tabs: ViewTab[] = [ - { - id: 'inbox', - title: 'Inbox', - contents:
-
    - {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.

    -
    -
    } -
- -
- }, - { - id: 'activity', - title: 'Activity', - contents:
- {activities && activities.slice().reverse().map(activity => ( - activity.type === 'Like' && } id='list-item' title={
{activity.actor.name} liked your post {activity.object.name}
}>
- ))} -
- -
- }, - { - id: 'likes', - title: 'Likes', - contents:
-
    - {activities && activities.slice().reverse().map(activity => ( - activity.type === 'Create' && activity.object.type === 'Article' && -
  • handleViewContent(activity.object, activity.actor)}> - -
  • - ))} -
- -
- } - ]; - +const ListIndex = () => { return ( - - {!articleContent ? ( - { - updateRoute('follow-site'); - }, - icon: 'add' - }} - selectedTab={selectedTab} - stickyHeader={true} - tabs={tabs} - toolbarBorder={false} - type='page' - onTabChange={setSelectedTab} - > - - - ) : ( - - )} - - - ); -}; - -const Sidebar: React.FC<{followingCount: number, followersCount: number, updateRoute: (route: string) => void}> = ({followingCount, followersCount, updateRoute}) => ( -
-
-
-
-
updateRoute('/view-following')}> - {followingCount} - Following +
+

ActivityPub Demo

+
+
+

This is a post title

+

This is some very short post content

+

Publish McPublisher

+
+
+

This is a post title

+

This is some very short post content

+

Publish McPublisher

-
updateRoute('/view-followers')}> - {followersCount} - Followers +
+

This is a post title

+

This is some very short post content

+

Publish McPublisher

-
-
- 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' /> - -
-
-); - -const ArticleBody: React.FC<{heading: string, image: string|undefined, html: string}> = ({heading, image, html}) => { - // const dangerouslySetInnerHTML = {__html: html}; - // const cssFile = '../index.css'; - const site = useBrowseSite(); - const siteData = site.data?.site; - - const cssContent = articleBodyStyles(siteData?.url.replace(/\/$/, '')); - - const htmlContent = ` - - - ${cssContent} - - -
-

${heading}

-${image && - `
- ${heading} -
` -} -
-
- ${html} -
- - -`; - - return ( - - ); -}; - -const ObjectContentDisplay: React.FC<{actor: ActorProperties, object: ObjectProperties }> = ({actor, object}) => { - const parser = new DOMParser(); - const doc = parser.parseFromString(object.content || '', 'text/html'); - - const plainTextContent = doc.body.textContent; - const timestamp = - new Date(object?.published ?? new Date()).toLocaleDateString('default', {year: 'numeric', month: 'short', day: '2-digit'}) + ', ' + new Date(object?.published ?? new Date()).toLocaleTimeString('default', {hour: '2-digit', minute: '2-digit'}); - - const [isClicked, setIsClicked] = useState(false); - const [isLiked, setIsLiked] = useState(false); - - const handleLikeClick = (event: React.MouseEvent | undefined) => { - event?.stopPropagation(); - setIsClicked(true); - setIsLiked(!isLiked); - setTimeout(() => setIsClicked(false), 300); // Reset the animation class after 300ms - }; - - return ( - <> - {object && ( -
-
- - {actor.name} - {getUsername(actor)} - {timestamp} -
-
-
-
- {object.name} -
-

{plainTextContent}

-
-
-
- {object.image &&
- -
} -
-
- {/*
*/} -
- )} - - ); -}; - -const ViewArticle: React.FC = ({object, onBackToList}) => { - const {updateRoute} = useRouting(); - - const [isClicked, setIsClicked] = useState(false); - const [isLiked, setIsLiked] = useState(false); - - const handleLikeClick = (event: React.MouseEvent | undefined) => { - event?.stopPropagation(); - setIsClicked(true); - setIsLiked(!isLiked); - setTimeout(() => setIsClicked(false), 300); // Reset the animation class after 300ms - }; - - return ( - - -
-
-
-
-
-
-
-
-
-
-
- -
-
-
); }; -export default ActivityPubComponent; +export default ListIndex; diff --git a/apps/admin-x-activitypub/src/components/ViewFollowers.tsx b/apps/admin-x-activitypub/src/components/ViewFollowers.tsx deleted file mode 100644 index bff12ef78c45..000000000000 --- a/apps/admin-x-activitypub/src/components/ViewFollowers.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import {} from '@tryghost/admin-x-framework/api/activitypub'; -import NiceModal from '@ebay/nice-modal-react'; -import getUsername from '../utils/get-username'; -import {Avatar, Button, List, ListItem, Modal} from '@tryghost/admin-x-design-system'; -import {FollowingResponseData, useBrowseFollowersForUser, useFollow} from '@tryghost/admin-x-framework/api/activitypub'; -import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing'; - -interface ViewFollowersModalProps { - following: FollowingResponseData[], - animate?: boolean -} - -const ViewFollowersModal: React.FC = ({}) => { - const {updateRoute} = useRouting(); - // const modal = NiceModal.useModal(); - const mutation = useFollow(); - - const {data: {items = []} = {}} = useBrowseFollowersForUser('inbox'); - - const followers = Array.isArray(items) ? items : [items]; - return ( - { - mutation.reset(); - updateRoute(''); - }} - cancelLabel='' - footer={false} - okLabel='' - size='md' - title='Followers' - topRightContent='close' - > -
- - {followers.map(item => ( - mutation.mutate({username: getUsername(item)})} />} avatar={} detail={getUsername(item)} id='list-item' title={item.name}> - ))} - -
-
- ); -}; - -export default NiceModal.create(ViewFollowersModal); diff --git a/apps/admin-x-activitypub/src/components/ViewFollowing.tsx b/apps/admin-x-activitypub/src/components/ViewFollowing.tsx deleted file mode 100644 index 4a5bc82cd689..000000000000 --- a/apps/admin-x-activitypub/src/components/ViewFollowing.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import {} from '@tryghost/admin-x-framework/api/activitypub'; -import NiceModal from '@ebay/nice-modal-react'; -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'; - -interface ViewFollowingModalProps { - following: FollowingResponseData[], - animate?: boolean -} - -const ViewFollowingModal: React.FC = ({}) => { - const {updateRoute} = useRouting(); - const mutation = useUnfollow(); - - const {data: {items = []} = {}} = useBrowseFollowingForUser('inbox'); - - const following = Array.isArray(items) ? items : [items]; - return ( - { - mutation.reset(); - updateRoute(''); - }} - cancelLabel='' - footer={false} - okLabel='' - size='md' - title='Following' - topRightContent='close' - > -
- - {following.map(item => ( - mutation.mutate({username: getUsername(item)})} />} avatar={} detail={getUsername(item)} id='list-item' title={item.name}> - ))} - - {/* - - -
-
-
- - Platformer Platformer Platformer Platformer Platformer - @index@platformerplatformerplatformerplatformer.news -
-
-
-
-
Unfollow
-
-
*/} -
-
- ); -}; - -export default NiceModal.create(ViewFollowingModal); diff --git a/apps/admin-x-activitypub/src/components/articleBodyStyles.ts b/apps/admin-x-activitypub/src/components/articleBodyStyles.ts deleted file mode 100644 index 4475737005cc..000000000000 --- a/apps/admin-x-activitypub/src/components/articleBodyStyles.ts +++ /dev/null @@ -1,5911 +0,0 @@ -const articleBodyStyles = (siteUrl: string|undefined) => { - return ``; -}; - -export default articleBodyStyles; diff --git a/apps/admin-x-activitypub/src/components/modals.tsx b/apps/admin-x-activitypub/src/components/modals.tsx deleted file mode 100644 index 5764840ba39c..000000000000 --- a/apps/admin-x-activitypub/src/components/modals.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import FollowSite from './FollowSite'; -import ViewFollowers from './ViewFollowers'; -import ViewFollowing from './ViewFollowing'; -import {ModalComponent} from '@tryghost/admin-x-framework/routing'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const modals = {FollowSite, ViewFollowing, ViewFollowers} satisfies {[key: string]: ModalComponent}; - -export default modals; - -export type ModalName = keyof typeof modals; diff --git a/apps/admin-x-activitypub/src/styles/index.css b/apps/admin-x-activitypub/src/styles/index.css index c3b58ac68289..d1f1f198edf8 100644 --- a/apps/admin-x-activitypub/src/styles/index.css +++ b/apps/admin-x-activitypub/src/styles/index.css @@ -1,25 +1 @@ @import '@tryghost/admin-x-design-system/styles.css'; - -.admin-x-base.admin-x-activitypub { - animation-name: none; -} - -@keyframes bump { - 0% { - transform: scale(1); - } - 50% { - transform: scale(1.1); - } - 100% { - transform: scale(1); - } - } - -.bump { -animation: bump 0.3s ease-in-out; -} - -.ap-red-heart path { - fill: #F50B23; -} \ No newline at end of file diff --git a/apps/admin-x-activitypub/src/utils/get-username.ts b/apps/admin-x-activitypub/src/utils/get-username.ts deleted file mode 100644 index 2fd6099e337d..000000000000 --- a/apps/admin-x-activitypub/src/utils/get-username.ts +++ /dev/null @@ -1,12 +0,0 @@ -function getUsername(actor: {preferredUsername: string; id: string|null;}) { - if (!actor.preferredUsername || !actor.id) { - return '@unknown@unknown'; - } - try { - return `@${actor.preferredUsername}@${(new URL(actor.id)).hostname}`; - } catch (err) { - return '@unknown@unknown'; - } -} - -export default getUsername; diff --git a/apps/admin-x-activitypub/test/acceptance/app.test.ts b/apps/admin-x-activitypub/test/acceptance/app.test.ts index 90fa60d84280..9e84a05428c7 100644 --- a/apps/admin-x-activitypub/test/acceptance/app.test.ts +++ b/apps/admin-x-activitypub/test/acceptance/app.test.ts @@ -5,6 +5,6 @@ test.describe('Demo', async () => { test('Renders the list page', async ({page}) => { await page.goto('/'); - await expect(page.locator('body')).toContainText('ActivityPub Inbox'); + await expect(page.locator('body')).toContainText('ActivityPub Demo'); }); }); diff --git a/apps/admin-x-activitypub/test/acceptance/listIndex.test.ts b/apps/admin-x-activitypub/test/acceptance/listIndex.test.ts deleted file mode 100644 index 8e855d1e2151..000000000000 --- a/apps/admin-x-activitypub/test/acceptance/listIndex.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import {expect, test} from '@playwright/test'; -import {mockApi, responseFixtures} from '@tryghost/admin-x-framework/test/acceptance'; - -test.describe('ListIndex', async () => { - test('Renders the list page', async ({page}) => { - const userId = 'index'; - await mockApi({ - page, - requests: { - useBrowseInboxForUser: {method: 'GET', path: `/inbox/${userId}`, response: responseFixtures.activitypubInbox}, - useBrowseFollowingForUser: {method: 'GET', path: `/following/${userId}`, response: responseFixtures.activitypubFollowing} - }, - options: {useActivityPub: true} - }); - - // Printing browser consol logs - page.on('console', (msg) => { - console.log(`Browser console log: ${msg.type()}: ${msg.text()}`); /* eslint-disable-line no-console */ - }); - - await page.goto('/'); - - await expect(page.locator('body')).toContainText('ActivityPub Inbox'); - - // following list - const followingUser = await page.locator('[data-test-following] > li').textContent(); - await expect(followingUser).toEqual('@index@main.ghost.org'); - const followingCount = await page.locator('[data-test-following-count]').textContent(); - await expect(followingCount).toEqual('1'); - - // following button - const followingList = await page.locator('[data-test-following-modal]'); - await expect(followingList).toBeVisible(); - - // activities - const activity = await page.locator('[data-test-activity-heading]').textContent(); - await expect(activity).toEqual('Testing ActivityPub'); - - // click on article - const articleBtn = await page.locator('[data-test-view-article]'); - await articleBtn.click(); - - // article is expanded - const frameLocator = page.frameLocator('#gh-ap-article-iframe'); - const textElement = await frameLocator.locator('[data-test-article-heading]').innerText(); - expect(textElement).toContain('Testing ActivityPub'); - - // go back to list - const backBtn = await page.locator('[data-test-back-button]'); - await backBtn.click(); - }); -}); diff --git a/apps/admin-x-activitypub/test/unit/ListIndex.test.tsx b/apps/admin-x-activitypub/test/unit/ListIndex.test.tsx new file mode 100644 index 000000000000..50459c8584ee --- /dev/null +++ b/apps/admin-x-activitypub/test/unit/ListIndex.test.tsx @@ -0,0 +1,10 @@ +import ListIndex from '../../src/components/ListIndex'; +import {render, screen} from '@testing-library/react'; + +describe('Demo', function () { + it('renders a component', async function () { + render(); + + expect(screen.getAllByRole('heading')[0].textContent).toEqual('ActivityPub Demo'); + }); +}); diff --git a/apps/admin-x-activitypub/test/unit/utils/get-username.test.tsx b/apps/admin-x-activitypub/test/unit/utils/get-username.test.tsx deleted file mode 100644 index 8ca868b1c9ba..000000000000 --- a/apps/admin-x-activitypub/test/unit/utils/get-username.test.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import getUsername from '../../../src/utils/get-username'; - -describe('getUsername', function () { - it('returns the formatted username', async function () { - const user = { - preferredUsername: 'index', - id: 'https://www.platformer.news/' - }; - - const result = getUsername(user); - - expect(result).toBe('@index@www.platformer.news'); - }); - - it('returns a default username if the user object is missing data', async function () { - const user = { - preferredUsername: '', - id: '' - }; - - const result = getUsername(user); - - expect(result).toBe('@unknown@unknown'); - }); - - it('returns a default username if url parsing fails', async function () { - const user = { - preferredUsername: 'index', - id: 'not-a-url' - }; - - const result = getUsername(user); - - expect(result).toBe('@unknown@unknown'); - }); -}); diff --git a/apps/admin-x-demo/package.json b/apps/admin-x-demo/package.json index 426e969f5c4f..0c8088c869ab 100644 --- a/apps/admin-x-demo/package.json +++ b/apps/admin-x-demo/package.json @@ -35,7 +35,7 @@ "@testing-library/react": "14.1.0", "@tryghost/admin-x-design-system": "0.0.0", "@tryghost/admin-x-framework": "0.0.0", - "@types/react": "18.3.3", + "@types/react": "18.3.2", "@types/react-dom": "18.3.0", "react": "18.3.1", "react-dom": "18.3.1" diff --git a/apps/admin-x-design-system/package.json b/apps/admin-x-design-system/package.json index 1b075723a93d..e4e3cad1a0dd 100644 --- a/apps/admin-x-design-system/package.json +++ b/apps/admin-x-design-system/package.json @@ -26,13 +26,13 @@ "tailwind.config.cjs" ], "devDependencies": { - "@codemirror/lang-html": "6.4.9", - "@storybook/addon-essentials": "7.6.20", - "@storybook/addon-interactions": "7.6.20", - "@storybook/addon-links": "7.6.20", + "@codemirror/lang-html": "^6.4.5", + "@storybook/addon-essentials": "7.6.19", + "@storybook/addon-interactions": "7.6.19", + "@storybook/addon-links": "7.6.19", "@storybook/addon-styling": "1.3.7", - "@storybook/blocks": "7.6.20", - "@storybook/react": "7.6.20", + "@storybook/blocks": "7.6.19", + "@storybook/react": "7.6.19", "@storybook/react-vite": "7.6.4", "@storybook/testing-library": "0.2.2", "@testing-library/react": "14.1.0", @@ -41,13 +41,12 @@ "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-refresh": "0.4.3", "eslint-plugin-tailwindcss": "3.13.0", - "jsdom": "24.1.0", "mocha": "10.2.0", - "react": "18.3.1", - "react-dom": "18.3.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", "rollup-plugin-node-builtins": "2.1.2", "sinon": "17.0.0", - "storybook": "7.6.20", + "storybook": "7.6.19", "ts-node": "10.9.2", "typescript": "5.4.5", "vite": "4.5.3", @@ -57,18 +56,18 @@ "@dnd-kit/core": "6.1.0", "@dnd-kit/sortable": "7.0.2", "@ebay/nice-modal-react": "1.2.13", - "@sentry/react": "7.118.0", + "@sentry/react": "7.114.0", "@tailwindcss/forms": "0.5.7", "@tailwindcss/line-clamp": "0.4.4", - "@uiw/react-codemirror": "4.22.2", + "@uiw/react-codemirror": "^4.21.9", "autoprefixer": "10.4.19", "clsx": "2.1.1", - "postcss": "8.4.39", + "postcss": "8.4.38", "postcss-import": "16.1.0", - "react-colorful": "5.6.1", + "react-colorful": "^5.1.2", "react-hot-toast": "2.4.1", "react-select": "5.8.0", - "tailwindcss": "3.4.4" + "tailwindcss": "3.4.3" }, "peerDependencies": { "react": "^18.2.0", diff --git a/apps/admin-x-design-system/src/global/Button.tsx b/apps/admin-x-design-system/src/global/Button.tsx index 2464e98f15a2..5c91bfeb26b3 100644 --- a/apps/admin-x-design-system/src/global/Button.tsx +++ b/apps/admin-x-design-system/src/global/Button.tsx @@ -3,7 +3,7 @@ import React, {HTMLProps} from 'react'; import clsx from 'clsx'; import {LoadingIndicator, LoadingIndicatorColor, LoadingIndicatorSize} from './LoadingIndicator'; -export type ButtonColor = 'clear' | 'light-grey' | 'grey' | 'black' | 'green' | 'red' | 'white' | 'outline'; +export type ButtonColor = 'clear' | 'grey' | 'black' | 'green' | 'red' | 'white' | 'outline'; export type ButtonSize = 'sm' | 'md'; export interface ButtonProps extends Omit, 'label' | 'size' | 'children'> { @@ -75,13 +75,6 @@ const Button: React.FC = ({ loadingIndicatorColor = 'light'; iconColorClass = iconColorClass || 'text-white'; break; - case 'light-grey': - className = clsx( - link ? 'text-grey-800 hover:text-green-400 dark:text-white' : `bg-grey-200 text-black dark:bg-grey-900 dark:text-white ${!disabled && 'hover:!bg-grey-300 dark:hover:!bg-grey-800'}`, - className - ); - loadingIndicatorColor = 'dark'; - break; case 'grey': className = clsx( link ? 'text-black hover:text-grey-800 dark:text-white' : `bg-grey-100 text-black dark:bg-grey-900 dark:text-white ${!disabled && 'hover:!bg-grey-300 dark:hover:!bg-grey-800'}`, @@ -121,7 +114,7 @@ const Button: React.FC = ({ break; default: className = clsx( - link ? ' text-black hover:text-grey-800 dark:text-white' : `text-grey-900 dark:text-white dark:hover:bg-grey-900 ${!disabled && 'hover:bg-grey-200 hover:text-black'}`, + link ? ' text-black hover:text-grey-800 dark:text-white' : `text-black dark:text-white dark:hover:bg-grey-900 ${!disabled && 'hover:bg-grey-200'}`, (outlineOnMobile && !link) && 'border border-grey-300 hover:border-transparent md:border-transparent', className ); diff --git a/apps/admin-x-design-system/src/global/ButtonGroup.stories.tsx b/apps/admin-x-design-system/src/global/ButtonGroup.stories.tsx index 850c8d7baba6..eb007ad9ca14 100644 --- a/apps/admin-x-design-system/src/global/ButtonGroup.stories.tsx +++ b/apps/admin-x-design-system/src/global/ButtonGroup.stories.tsx @@ -33,14 +33,6 @@ export const Default: Story = { } }; -export const Small: Story = { - args: { - buttons: defaultButtons, - link: false, - size: 'sm' - } -}; - const linkButtons: ButtonProps[] = [ { label: 'Cancel', @@ -58,4 +50,21 @@ export const LinkButtons: Story = { buttons: linkButtons, link: true } +}; + +export const WithBackground: Story = { + args: { + buttons: linkButtons, + link: true, + clearBg: false + } +}; + +export const SmallWithBackground: Story = { + args: { + buttons: linkButtons, + link: true, + clearBg: false, + size: 'sm' + } }; \ No newline at end of file diff --git a/apps/admin-x-design-system/src/global/ButtonGroup.tsx b/apps/admin-x-design-system/src/global/ButtonGroup.tsx index 26697d497763..f41d9b0fa4f6 100644 --- a/apps/admin-x-design-system/src/global/ButtonGroup.tsx +++ b/apps/admin-x-design-system/src/global/ButtonGroup.tsx @@ -17,7 +17,7 @@ export interface ButtonGroupProps { const ButtonGroup: React.FC = ({size = 'md', buttons, link, linkWithPadding, clearBg = true, outlineOnMobile, className}) => { let groupColorClasses = clsx( 'flex items-center justify-start rounded', - link ? 'gap-4' : 'gap-2', + link ? 'gap-4' : 'gap-3', className ); @@ -33,7 +33,7 @@ const ButtonGroup: React.FC = ({size = 'md', buttons, link, li return (
{buttons.map(({key, ...props}) => ( -
); diff --git a/apps/admin-x-design-system/src/global/Toast.stories.tsx b/apps/admin-x-design-system/src/global/Toast.stories.tsx index bdb5deb86bab..90cef8de10f3 100644 --- a/apps/admin-x-design-system/src/global/Toast.stories.tsx +++ b/apps/admin-x-design-system/src/global/Toast.stories.tsx @@ -1,6 +1,7 @@ import type {Meta, StoryObj} from '@storybook/react'; import {ReactNode} from 'react'; +import {Toaster} from 'react-hot-toast'; import Button from './Button'; import {ShowToastProps, showToast} from './Toast'; @@ -24,6 +25,7 @@ const meta = { tags: ['autodocs'], decorators: [(_story: () => ReactNode) => ( <> + {_story()} )] 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 5bda018ca5c4..6a3c1e3b66c7 100644 --- a/apps/admin-x-design-system/src/global/form/TextArea.tsx +++ b/apps/admin-x-design-system/src/global/form/TextArea.tsx @@ -18,6 +18,7 @@ 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-design-system/src/global/form/Toggle.tsx b/apps/admin-x-design-system/src/global/form/Toggle.tsx index 4a3df9987f1c..5a056cd21a77 100644 --- a/apps/admin-x-design-system/src/global/form/Toggle.tsx +++ b/apps/admin-x-design-system/src/global/form/Toggle.tsx @@ -9,7 +9,6 @@ export type ToggleDirections = 'ltr' | 'rtl'; export interface ToggleProps { checked?: boolean; disabled?: boolean; - name?: string; error?: boolean; size?: ToggleSizes; label?: React.ReactNode; @@ -34,7 +33,6 @@ const Toggle: React.FC = ({ error, checked, disabled, - name, onChange }) => { const id = useId(); @@ -97,7 +95,6 @@ const Toggle: React.FC = ({ )} disabled={disabled} id={id} - name={name} role="switch" type="checkbox" onChange={onChange} /> diff --git a/apps/admin-x-design-system/src/global/layout/ViewContainer.tsx b/apps/admin-x-design-system/src/global/layout/ViewContainer.tsx index 38289b4bad54..f7878b71db5b 100644 --- a/apps/admin-x-design-system/src/global/layout/ViewContainer.tsx +++ b/apps/admin-x-design-system/src/global/layout/ViewContainer.tsx @@ -251,7 +251,7 @@ const ViewContainer: React.FC = ({ return (
- {(title || actions || headerContent || tabs) && toolbar} + {(title || actions || headerContent) && toolbar}
{mainContent}
diff --git a/apps/admin-x-design-system/src/global/modal/Modal.tsx b/apps/admin-x-design-system/src/global/modal/Modal.tsx index e959a3c89f12..2377c3de3fb2 100644 --- a/apps/admin-x-design-system/src/global/modal/Modal.tsx +++ b/apps/admin-x-design-system/src/global/modal/Modal.tsx @@ -27,7 +27,6 @@ export interface ModalProps { cancelLabel?: string; leftButtonProps?: ButtonProps; buttonsDisabled?: boolean; - okDisabled?: boolean; footer?: boolean | React.ReactNode; header?: boolean; padding?: boolean; @@ -63,7 +62,6 @@ const Modal: React.FC = ({ header, leftButtonProps, buttonsDisabled, - okDisabled, padding = true, onOk, okColor = 'black', @@ -181,7 +179,7 @@ const Modal: React.FC = ({ color: okColor, className: 'min-w-[80px]', onClick: onOk, - disabled: buttonsDisabled || okDisabled, + disabled: buttonsDisabled, loading: okLoading }); } diff --git a/apps/admin-x-design-system/src/settings/SettingGroup.tsx b/apps/admin-x-design-system/src/settings/SettingGroup.tsx index 2a620534fc2b..7c3ab39191c9 100644 --- a/apps/admin-x-design-system/src/settings/SettingGroup.tsx +++ b/apps/admin-x-design-system/src/settings/SettingGroup.tsx @@ -78,7 +78,6 @@ const SettingGroup = forwardRef(function Sett styles += ' border-grey-250 dark:border-grey-925'; - // The links visible before editing const viewButtons: ButtonProps[] = []; if (!hideEditButton) { @@ -90,7 +89,7 @@ const SettingGroup = forwardRef(function Sett { label, key: 'edit', - color: 'clear', + color: 'green', onClick: handleEdit } ); @@ -105,7 +104,6 @@ const SettingGroup = forwardRef(function Sett ); } - // The buttons that show when you are editing const editButtons: ButtonProps[] = [ { label: 'Cancel', @@ -121,10 +119,9 @@ const SettingGroup = forwardRef(function Sett } editButtons.push( { - label: label, + label, key: 'save', - color: saveState === 'unsaved' ? 'green' : 'light-grey', - disabled: saveState !== 'unsaved', + color: 'green', onClick: handleSave } ); @@ -154,35 +151,18 @@ const SettingGroup = forwardRef(function Sett styles ); - if (!isEditing) { - return ( -
-
- {customHeader ? customHeader : - - {customButtons ? customButtons : - (onEditingChange && ) - } - - } - {children} -
- ); - } else { - return ( -
-
- {customHeader ? customHeader : - - {customButtons ? customButtons : - (onEditingChange && ) - } - - } - {children} -
- ); - } + return ( +
+
+ {customHeader ? customHeader : + + {customButtons ? customButtons : + (onEditingChange && )} + + } + {children} +
+ ); }); export default SettingGroup; diff --git a/apps/admin-x-design-system/src/settings/SettingGroupHeader.tsx b/apps/admin-x-design-system/src/settings/SettingGroupHeader.tsx index 892782454383..181196bcf375 100644 --- a/apps/admin-x-design-system/src/settings/SettingGroupHeader.tsx +++ b/apps/admin-x-design-system/src/settings/SettingGroupHeader.tsx @@ -14,10 +14,10 @@ const SettingGroupHeader: React.FC = ({title, descripti {(title || description) &&
{title}{beta && Beta} - {description &&

{description}

} + {description &&

{description}

}
} -
+
{children}
diff --git a/apps/admin-x-design-system/tailwind.config.cjs b/apps/admin-x-design-system/tailwind.config.cjs index 7a614ce4f676..d554015aa496 100644 --- a/apps/admin-x-design-system/tailwind.config.cjs +++ b/apps/admin-x-design-system/tailwind.config.cjs @@ -270,7 +270,7 @@ module.exports = { base: '1.4rem', xs: '1.2rem', sm: '1.3rem', - md: '1.4rem', + md: '1.40rem', lg: '1.65rem', xl: '2rem', '2xl': '2.4rem', diff --git a/apps/admin-x-framework/package.json b/apps/admin-x-framework/package.json index 5381205abd23..ce52902a2caa 100644 --- a/apps/admin-x-framework/package.json +++ b/apps/admin-x-framework/package.json @@ -73,7 +73,6 @@ "c8": "8.0.1", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-refresh": "0.4.3", - "jsdom": "24.1.0", "mocha": "10.2.0", "react": "18.3.1", "react-dom": "18.3.1", @@ -82,14 +81,14 @@ "typescript": "5.4.5" }, "dependencies": { - "@sentry/react": "7.118.0", + "@sentry/react": "7.114.0", "@tanstack/react-query": "4.36.1", "@tryghost/admin-x-design-system": "0.0.0", - "@types/react": "18.3.3", + "@types/react": "18.3.2", "@types/react-dom": "18.3.0", "@vitejs/plugin-react": "4.2.1", - "react": "18.3.1", - "react-dom": "18.3.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", "vite": "4.5.3", "vite-plugin-css-injected-by-js": "^3.3.0", "vite-plugin-svgr": "3.3.0", diff --git a/apps/admin-x-framework/src/api/actions.ts b/apps/admin-x-framework/src/api/actions.ts index 43a00db4a595..e9f015bcde46 100644 --- a/apps/admin-x-framework/src/api/actions.ts +++ b/apps/admin-x-framework/src/api/actions.ts @@ -78,7 +78,7 @@ export const useBrowseActions = createInfiniteQuery({ } }); - const meta = pages[pages.length - 1].meta; + const meta = pages.at(-1)!.meta; return { actions: actions.reverse(), diff --git a/apps/admin-x-framework/src/api/activitypub.ts b/apps/admin-x-framework/src/api/activitypub.ts deleted file mode 100644 index 246292d55607..000000000000 --- a/apps/admin-x-framework/src/api/activitypub.ts +++ /dev/null @@ -1,122 +0,0 @@ -import {createMutation, createQueryWithId} from '../utils/api/hooks'; - -export type FollowItem = { - id: string; - preferredUsername: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [x: string]: any -}; - -export type ObjectProperties = { - '@context': string | (string | object)[]; - type: 'Article' | 'Link'; - name: string; - content: string; - url?: string | undefined; - attributedTo?: string | object[] | undefined; - image?: string; - published?: string; - preview?: {type: string, content: string}; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [x: string]: any; -} - -export type ActorProperties = { - '@context': string | (string | object)[]; - attachment: object[]; - discoverable: boolean; - featured: string; - followers: string; - following: string; - id: string | null; - image: string; - inbox: string; - manuallyApprovesFollowers: boolean; - name: string; - outbox: string; - preferredUsername: string; - publicKey: { - id: string; - owner: string; - publicKeyPem: string; - }; - published: string; - summary: string; - type: 'Person'; - url: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [x: string]: any; -} - -export type Activity = { - '@context': string; - id: string; - type: string; - actor: ActorProperties; - object: ObjectProperties; - to: string; -} - -export type InboxResponseData = { - '@context': string; - id: string; - summary: string; - type: 'OrderedCollection'; - totalItems: number; - items: Activity[]; -} - -export type FollowingResponseData = { - '@context': string; - id: string; - summary: string; - type: string; - totalItems: number; - items: FollowItem[]; -} - -type FollowRequestProps = { - username: string -} - -export const useFollow = createMutation({ - method: 'POST', - useActivityPub: true, - path: data => `/actions/follow/${data.username}` -}); - -export const useUnfollow = createMutation({ - method: 'POST', - useActivityPub: true, - 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}` -}); - -// This is a frontend root, not using the Ghost admin API -export const useBrowseFollowingForUser = createQueryWithId({ - dataType: 'FollowingResponseData', - useActivityPub: true, - headers: { - Accept: 'application/activity+json' - }, - path: id => `/following/${id}` -}); - -// This is a frontend root, not using the Ghost admin API -export const useBrowseFollowersForUser = createQueryWithId({ - dataType: 'FollowingResponseData', - useActivityPub: true, - headers: { - Accept: 'application/activity+json' - }, - path: id => `/followers/${id}` -}); diff --git a/apps/admin-x-framework/src/api/newsletters.ts b/apps/admin-x-framework/src/api/newsletters.ts index b74f8cbbbbac..481a07dfd429 100644 --- a/apps/admin-x-framework/src/api/newsletters.ts +++ b/apps/admin-x-framework/src/api/newsletters.ts @@ -21,7 +21,6 @@ export type Newsletter = { show_header_title: boolean; title_font_category: string; title_alignment: string; - show_excerpt: boolean; show_feature_image: boolean; body_font_category: string; footer_content: string | null; @@ -61,7 +60,7 @@ export const useBrowseNewsletters = createInfiniteQuery { const {pages} = originalData as InfiniteData; const newsletters = pages.flatMap(page => page.newsletters); - const meta = pages[pages.length - 1].meta; + const meta = pages.at(-1)!.meta; return { newsletters: newsletters, diff --git a/apps/admin-x-framework/src/api/tiers.ts b/apps/admin-x-framework/src/api/tiers.ts index 1813846cafcb..6ac9b67566c3 100644 --- a/apps/admin-x-framework/src/api/tiers.ts +++ b/apps/admin-x-framework/src/api/tiers.ts @@ -41,7 +41,7 @@ export const useBrowseTiers = createInfiniteQuery { const {pages} = originalData as InfiniteData; const tiers = pages.flatMap(page => page.tiers); - const meta = pages[pages.length - 1].meta; + const meta = pages.at(-1)!.meta; return { tiers, diff --git a/apps/admin-x-framework/src/api/users.ts b/apps/admin-x-framework/src/api/users.ts index b5c8b855a408..9df81ed45f6a 100644 --- a/apps/admin-x-framework/src/api/users.ts +++ b/apps/admin-x-framework/src/api/users.ts @@ -76,7 +76,7 @@ export const useBrowseUsers = createInfiniteQuery { const {pages} = originalData as InfiniteData; const users = pages.flatMap(page => page.users); - const meta = pages[pages.length - 1].meta; + const meta = pages.at(-1)!.meta; return { users: users, diff --git a/apps/admin-x-framework/src/hooks/useFilterableApi.ts b/apps/admin-x-framework/src/hooks/useFilterableApi.ts index 61b568916a79..6b738e8cd990 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} & {[k in FilterKey]: string} & {[k: string]: unknown}, + Data extends {id: string} & {[Key in FilterKey]: string}, ResponseKey extends string = string, FilterKey extends string = string >({path, filterKey, responseKey, limit = 20}: { @@ -41,27 +41,26 @@ const useFilterableApi = < return response[responseKey]; }; - const loadInitialValues = async (values: string[], key: string) => { - await loadData(''); + return { + loadData, - const data = [...(result.current.data || [])]; - const missingValues = values.filter(value => !result.current.data?.find(item => item[key] === value)); + loadInitialValues: async (ids: string[]) => { + await loadData(''); - if (missingValues.length) { - const additionalData = await fetchApi<{meta?: Meta} & {[k in ResponseKey]: Data[]}>(apiUrl(path, { - filter: `${key}:[${missingValues.join(',')}]`, - limit: 'all' - })); + const data = [...(result.current.data || [])]; + const missingIds = ids.filter(id => !result.current.data?.find(({id: dataId}) => dataId === id)); - data.push(...additionalData[responseKey]); - } + if (missingIds.length) { + const additionalData = await fetchApi<{meta?: Meta} & {[k in ResponseKey]: Data[]}>(apiUrl(path, { + filter: `id:[${missingIds.join(',')}]`, + limit: 'all' + })); - return values.map(value => data.find(item => item[key] === value)!); - }; + data.push(...additionalData[responseKey]); + } - return { - loadData, - loadInitialValues + return ids.map(id => data.find(({id: dataId}) => dataId === id)!); + } }; }; diff --git a/apps/admin-x-framework/src/test/acceptance.ts b/apps/admin-x-framework/src/test/acceptance.ts index b22e4ac92a6a..6bebb95f4e2a 100644 --- a/apps/admin-x-framework/src/test/acceptance.ts +++ b/apps/admin-x-framework/src/test/acceptance.ts @@ -16,8 +16,6 @@ import siteFixture from './responses/site.json'; import themesFixture from './responses/themes.json'; import tiersFixture from './responses/tiers.json'; import usersFixture from './responses/users.json'; -import activitypubInboxFixture from './responses/activitypub/inbox.json'; -import activitypubFollowingFixture from './responses/activitypub/following.json'; import {ActionsResponseType} from '../api/actions'; import {ConfigResponseType} from '../api/config'; @@ -65,9 +63,7 @@ export const responseFixtures = { themes: themesFixture as ThemesResponseType, newsletters: newslettersFixture as NewslettersResponseType, actions: actionsFixture as ActionsResponseType, - latestPost: {posts: [{id: '1', url: `${siteFixture.site.url}/test-post/`}]}, - activitypubInbox: activitypubInboxFixture, - activitypubFollowing: activitypubFollowingFixture + latestPost: {posts: [{id: '1', url: `${siteFixture.site.url}/test-post/`}]} }; const defaultLabFlags = { @@ -149,7 +145,7 @@ export const limitRequests = { browseNewslettersLimit: {method: 'GET', path: '/newsletters/?filter=status%3Aactive&limit=1', response: responseFixtures.newsletters} }; -export async function mockApi>({page, requests, options = {}}: {page: Page, requests: Requests, options?: {useActivityPub?: boolean}}) { +export async function mockApi>({page, requests}: {page: Page, requests: Requests}) { const lastApiRequests: {[key in keyof Requests]?: RequestRecord} = {}; const namedRequests = Object.entries(requests).reduce( @@ -157,11 +153,8 @@ export async function mockApi [] as Array ); - const routeRegex = options?.useActivityPub ? /\/activitypub\// : /\/ghost\/api\/admin\//; - const routeReplaceRegex = options.useActivityPub ? /^.*\/activitypub/ : /^.*\/ghost\/api\/admin/; - - await page.route(routeRegex, async (route) => { - const apiPath = route.request().url().replace(routeReplaceRegex, ''); + await page.route(/\/ghost\/api\/admin\//, async (route) => { + const apiPath = route.request().url().replace(/^.*\/ghost\/api\/admin/, ''); const matchingMock = namedRequests.find((request) => { if (request.method !== route.request().method()) { diff --git a/apps/admin-x-framework/src/test/responses/activitypub/following.json b/apps/admin-x-framework/src/test/responses/activitypub/following.json deleted file mode 100644 index f374bedb4e33..000000000000 --- a/apps/admin-x-framework/src/test/responses/activitypub/following.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://0a2e-129-222-88-174.ngrok-free.app/activitypub/following/deadbeefdeadbeefdeadbeef", - "summary": "Following collection for index", - "type": "Collection", - "totalItems": 1, - "items": [ - { - "id": "https://main.ghost.org/activitypub/actor/deadbeefdeadbeefdeadbeef", - "username": "@index@main.ghost.org" - } - ] - } diff --git a/apps/admin-x-framework/src/test/responses/activitypub/inbox.json b/apps/admin-x-framework/src/test/responses/activitypub/inbox.json deleted file mode 100644 index 550fda9489f7..000000000000 --- a/apps/admin-x-framework/src/test/responses/activitypub/inbox.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://example.com/activitypub/inbox/index", - "summary": "Inbox for index", - "type": "OrderedCollection", - "totalItems": 2, - "orderedItems": [ - { - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://main.ghost.org/activitypub/activity/664cf007fd27b20001a76d72", - "type": "Accept", - "actor": { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - { - "featured": { - "@id": "http://joinmastodon.org/ns#featured", - "@type": "@id" - } - }, - { - "discoverable": { - "@id": "http://joinmastodon.org/ns#discoverable", - "@type": "@id" - } - }, - { - "manuallyApprovesFollowers": { - "@id": "http://joinmastodon.org/ns#manuallyApprovesFollowers", - "@type": "@id" - } - }, - { - "schema": "http://schema.org#", - "PropertyValue": "schema:PropertyValue", - "value": "schema:value" - } - ], - "type": "Person", - "id": "https://main.ghost.org/activitypub/actor/index", - "name": "The Main", - "preferredUsername": "index", - "summary": "The bio for the actor", - "url": "https://main.ghost.org/activitypub/actor/index", - "icon": "", - "image": "", - "published": "1970-01-01T00:00:00Z", - "manuallyApprovesFollowers": false, - "discoverable": true, - "attachment": [ - { - "type": "PropertyValue", - "name": "Website", - "value": "main.ghost.org" - } - ], - "following": "https://main.ghost.org/activitypub/following/index", - "followers": "https://main.ghost.org/activitypub/followers/index", - "inbox": "https://main.ghost.org/activitypub/inbox/index", - "outbox": "https://main.ghost.org/activitypub/outbox/index", - "featured": "https://main.ghost.org/activitypub/featured/index", - "publicKey": { - "id": "https://main.ghost.org/activitypub/actor/index#main-key", - "owner": "https://main.ghost.org/activitypub/actor/index", - "publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\nMIGJAoGBANRpUrwk7x7bJDddHmrYSWVw9enVPMFm5qAW7fTgoZ7x2PoJUIqy/bkqpXZ0SmZs\nsLO3UZm+yN/DqxioD8BnhhD0N8Ydv6+UniT7hE2tHvsMxQIq2jet1auSBZNFmUIWodsBxI/R\ntm+KwFBFk+P+MvVsGZ2K3Rkd4K0dv0/45dtXAgMBAAE=\n-----END RSA PUBLIC KEY-----\n" - } - }, - "object": { - "id": "https://0a2e-129-222-88-174.ngrok-free.app/activitypub/activity/664cf0074daa2f8183ba6ea6", - "type": "Follow" - }, - "to": "https://0a2e-129-222-88-174.ngrok-free.app/activitypub/actor/index" - }, - { - "type": "Create", - "actor": { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - { - "featured": { - "@id": "http://joinmastodon.org/ns#featured", - "@type": "@id" - } - }, - { - "discoverable": { - "@id": "http://joinmastodon.org/ns#discoverable", - "@type": "@id" - } - }, - { - "manuallyApprovesFollowers": { - "@id": "http://joinmastodon.org/ns#manuallyApprovesFollowers", - "@type": "@id" - } - }, - { - "schema": "http://schema.org#", - "PropertyValue": "schema:PropertyValue", - "value": "schema:value" - } - ], - "type": "Person", - "id": "https://main.ghost.org/activitypub/actor/index", - "name": "The Main", - "preferredUsername": "index", - "summary": "The bio for the actor", - "url": "https://main.ghost.org/activitypub/actor/index", - "icon": "", - "image": "", - "published": "1970-01-01T00:00:00Z", - "manuallyApprovesFollowers": false, - "discoverable": true, - "attachment": [ - { - "type": "PropertyValue", - "name": "Website", - "value": "main.ghost.org" - } - ], - "following": "https://main.ghost.org/activitypub/following/index", - "followers": "https://main.ghost.org/activitypub/followers/index", - "inbox": "https://main.ghost.org/activitypub/inbox/index", - "outbox": "https://main.ghost.org/activitypub/outbox/index", - "featured": "https://main.ghost.org/activitypub/featured/index", - "publicKey": { - "id": "https://main.ghost.org/activitypub/actor/index#main-key", - "owner": "https://main.ghost.org/activitypub/actor/index", - "publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\nMIGJAoGBANRpUrwk7x7bJDddHmrYSWVw9enVPMFm5qAW7fTgoZ7x2PoJUIqy/bkqpXZ0SmZs\nsLO3UZm+yN/DqxioD8BnhhD0N8Ydv6+UniT7hE2tHvsMxQIq2jet1auSBZNFmUIWodsBxI/R\ntm+KwFBFk+P+MvVsGZ2K3Rkd4K0dv0/45dtXAgMBAAE=\n-----END RSA PUBLIC KEY-----\n" - } - }, - "object": { - "@context": "https://www.w3.org/ns/activitystreams", - "type": "Article", - "id": "https://main.ghost.org/activitypub/article/my-article/", - "name": "Testing ActivityPub", - "content": "

Super long test

", - "url": "https://main.ghost.org/my-article/", - "image": "https://main.ghost.org/content/images/2021/08/ghost-logo.png", - "published": "2024-05-09T00:00:00Z", - "attributedTo": { - "type": "Person", - "name": "The Main" - }, - "preview": { - "type": "Link", - "href": "https://main.ghost.org/my-article/", - "name": "Testing ActivityPub" - } - } - } - ] -} diff --git a/apps/admin-x-framework/src/test/responses/newsletters.json b/apps/admin-x-framework/src/test/responses/newsletters.json index 7c94e4ccea8e..96a971542e5c 100644 --- a/apps/admin-x-framework/src/test/responses/newsletters.json +++ b/apps/admin-x-framework/src/test/responses/newsletters.json @@ -19,7 +19,6 @@ "show_header_title": true, "title_font_category": "serif", "title_alignment": "center", - "show_excerpt": true, "show_feature_image": true, "body_font_category": "serif", "footer_content": "", diff --git a/apps/admin-x-framework/src/utils/api/fetchApi.ts b/apps/admin-x-framework/src/utils/api/fetchApi.ts index 352fd2f3db29..09671853402f 100644 --- a/apps/admin-x-framework/src/utils/api/fetchApi.ts +++ b/apps/admin-x-framework/src/utils/api/fetchApi.ts @@ -74,6 +74,10 @@ export const useFetchApi = () => { ...options }); + if (attempts !== 0 && sentryDSN) { + Sentry.captureMessage('Request took multiple attempts', {extra: getErrorData()}); + } + return handleResponse(response) as ResponseData; } catch (error) { retryingMs = Date.now() - startTime; @@ -111,11 +115,10 @@ export const useFetchApi = () => { }; }; -const {apiRoot, activityPubRoot} = getGhostPaths(); +const {apiRoot} = getGhostPaths(); -export const apiUrl = (path: string, searchParams: Record = {}, useActivityPub: boolean = false) => { - const root = useActivityPub ? activityPubRoot : apiRoot; - const url = new URL(`${root}${path}`, window.location.origin); +export const apiUrl = (path: string, searchParams: Record = {}) => { + const url = new URL(`${apiRoot}${path}`, window.location.origin); url.search = new URLSearchParams(searchParams).toString(); return url.toString(); }; diff --git a/apps/admin-x-framework/src/utils/api/hooks.ts b/apps/admin-x-framework/src/utils/api/hooks.ts index d2818cdb9cd7..23bb0a78a84b 100644 --- a/apps/admin-x-framework/src/utils/api/hooks.ts +++ b/apps/admin-x-framework/src/utils/api/hooks.ts @@ -20,11 +20,9 @@ export interface Meta { interface QueryOptions { dataType: string path: string - headers?: Record; defaultSearchParams?: Record; permissions?: string[]; returnData?: (originalData: unknown) => ResponseData; - useActivityPub?: boolean; } type QueryHookOptions = UseQueryOptions & { @@ -33,14 +31,14 @@ type QueryHookOptions = UseQueryOptions & { }; export const createQuery = (options: QueryOptions) => ({searchParams, ...query}: QueryHookOptions = {}): Omit, 'data'> & {data: ResponseData | undefined} => { - const url = apiUrl(options.path, searchParams || options.defaultSearchParams, options?.useActivityPub); + const url = apiUrl(options.path, searchParams || options.defaultSearchParams); const fetchApi = useFetchApi(); const handleError = useHandleError(); const result = useQuery({ enabled: options.permissions ? usePermission(options.permissions) : true, queryKey: [options.dataType, url], - queryFn: () => fetchApi(url, {...options}), + queryFn: () => fetchApi(url), ...query }); @@ -67,7 +65,7 @@ export const createPaginatedQuery = (options const paginatedSearchParams = searchParams || options.defaultSearchParams || {}; paginatedSearchParams.page = page.toString(); - const url = apiUrl(options.path, paginatedSearchParams, options?.useActivityPub); + const url = apiUrl(options.path, paginatedSearchParams); const fetchApi = useFetchApi(); const handleError = useHandleError(); @@ -120,8 +118,8 @@ export const createInfiniteQuery = (options: InfiniteQueryOptions< const nextPageParams = getNextPageParams || options.defaultNextPageParams || (() => ({})); const result = useInfiniteQuery({ - queryKey: [options.dataType, apiUrl(options.path, searchParams || options.defaultSearchParams, options?.useActivityPub)], - queryFn: ({pageParam}) => fetchApi(apiUrl(options.path, pageParam || searchParams || options.defaultSearchParams, options?.useActivityPub)), + queryKey: [options.dataType, apiUrl(options.path, searchParams || options.defaultSearchParams)], + queryFn: ({pageParam}) => fetchApi(apiUrl(options.path, pageParam || searchParams || options.defaultSearchParams)), getNextPageParam: data => nextPageParams(data, searchParams || options.defaultSearchParams || {}), ...query }); @@ -147,7 +145,6 @@ export const createQueryWithId = (options: Omit extends Omit, 'dataType' | 'path'>, Omit { path: (payload: Payload) => string; - headers?: Record; body?: (payload: Payload) => FormData | object; searchParams?: (payload: Payload) => { [key: string]: string; }; invalidateQueries?: { dataType: string; }; @@ -162,7 +159,7 @@ const mutate = ({fetchApi, path, payload, searchParams, o options: Omit, 'path'> }) => { const {defaultSearchParams, body, ...requestOptions} = options; - const url = apiUrl(path, searchParams || defaultSearchParams, options?.useActivityPub); + const url = apiUrl(path, searchParams || defaultSearchParams); const generatedBody = payload && body?.(payload); let requestBody: string | FormData | undefined = undefined; diff --git a/apps/admin-x-framework/src/utils/api/updateQueries.ts b/apps/admin-x-framework/src/utils/api/updateQueries.ts index a797d2fd83b5..bc2890c72352 100644 --- a/apps/admin-x-framework/src/utils/api/updateQueries.ts +++ b/apps/admin-x-framework/src/utils/api/updateQueries.ts @@ -10,7 +10,7 @@ export const insertToQueryCache = (field: string, recordsToInsert? if (typeof currentData === 'object' && 'pages' in currentData) { const {pages} = currentData as InfiniteData; - const lastPage = pages[pages.length - 1]; + const lastPage = pages.at(-1)!; return { ...currentData, pages: pages.slice(0, -1).concat({ diff --git a/apps/admin-x-framework/src/utils/errors.ts b/apps/admin-x-framework/src/utils/errors.ts index b8156d65d0aa..bfbafc71a266 100644 --- a/apps/admin-x-framework/src/utils/errors.ts +++ b/apps/admin-x-framework/src/utils/errors.ts @@ -22,10 +22,10 @@ export class APIError extends Error { errorOptions?: ErrorOptions ) { if (!message && response && response.url.includes('/ghost/api/admin/')) { - message = `Something went wrong while loading ${response.url.replace(/.+\/ghost\/api\/admin\//, '').replace(/\W.*/, '').replace('_', ' ')}, please try again.`; + message = `${response.statusText}, cannot fetch ${response.url.replace(/.+\/ghost\/api\/admin\//, '').replace(/\W.*/, '').replace('_', ' ')}`; } - super(message || 'Something went wrong, please try again.', errorOptions); + super(message || 'Unknown error', errorOptions); } } @@ -48,13 +48,13 @@ export class VersionMismatchError extends JSONError { export class ServerUnreachableError extends APIError { constructor(errorOptions?: ErrorOptions) { - super(undefined, undefined, 'Something went wrong, please try again.', errorOptions); + super(undefined, undefined, 'Server was unreachable', errorOptions); } } export class TimeoutError extends APIError { constructor(errorOptions?: ErrorOptions) { - super(undefined, undefined, 'Request timed out, please try again.', errorOptions); + super(undefined, undefined, 'Request timed out', errorOptions); } } diff --git a/apps/admin-x-framework/src/utils/helpers.ts b/apps/admin-x-framework/src/utils/helpers.ts index 1207ff12c613..d05d5a4ef472 100644 --- a/apps/admin-x-framework/src/utils/helpers.ts +++ b/apps/admin-x-framework/src/utils/helpers.ts @@ -3,7 +3,6 @@ export interface IGhostPaths { adminRoot: string; assetRoot: string; apiRoot: string; - activityPubRoot: string; } export function getGhostPaths(): IGhostPaths { @@ -12,8 +11,7 @@ export function getGhostPaths(): IGhostPaths { const adminRoot = `${subdir}/ghost/`; const assetRoot = `${subdir}/ghost/assets/`; const apiRoot = `${subdir}/ghost/api/admin`; - const activityPubRoot = `${subdir}/.ghost/activitypub`; - return {subdir, adminRoot, assetRoot, apiRoot, activityPubRoot}; + return {subdir, adminRoot, assetRoot, apiRoot}; } export function downloadFile(url: string) { diff --git a/apps/admin-x-framework/src/utils/queryClient.ts b/apps/admin-x-framework/src/utils/queryClient.ts index 26a9cfe719c1..7a3197e9166d 100644 --- a/apps/admin-x-framework/src/utils/queryClient.ts +++ b/apps/admin-x-framework/src/utils/queryClient.ts @@ -13,8 +13,7 @@ const queryClient = window.adminXQueryClient || new QueryClient({ staleTime: 5 * (60 * 1000), // 5 mins cacheTime: 10 * (60 * 1000), // 10 mins // We have custom retry logic for specific errors in fetchApi() - retry: false, - networkMode: 'always' + retry: false } } }); diff --git a/apps/admin-x-framework/test/unit/utils/api/hooks.test.tsx b/apps/admin-x-framework/test/unit/utils/api/hooks.test.tsx index 2099441d9634..1245c3c63f9d 100644 --- a/apps/admin-x-framework/test/unit/utils/api/hooks.test.tsx +++ b/apps/admin-x-framework/test/unit/utils/api/hooks.test.tsx @@ -60,47 +60,12 @@ describe('API hooks', function () { expect(mock.calls.length).toBe(1); expect(mock.calls[0]).toEqual(['http://localhost:3000/ghost/api/admin/test/', { credentials: 'include', - dataType: 'test', - headers: { - 'app-pragma': 'no-cache', - 'x-ghost-version': '5.x' - }, - method: 'GET', - mode: 'cors', - path: '/test/', - signal: expect.any(AbortSignal) - }]); - }); - }); - - it('can add custom headers', async function () { - await withMockFetch({ - json: {test: 1} - }, async (mock) => { - const useTestQuery = createQuery({ - dataType: 'test', - path: '/test/', - headers: {'Content-Type': 'ALOHA'} - }); - - const {result} = renderHook(() => useTestQuery(), {wrapper}); - - await waitFor(() => expect(result.current.isLoading).toBe(false)); - - expect(result.current.data).toEqual({test: 1}); - - expect(mock.calls.length).toBe(1); - expect(mock.calls[0]).toEqual(['http://localhost:3000/ghost/api/admin/test/', { - credentials: 'include', - dataType: 'test', headers: { - 'Content-Type': 'ALOHA', 'app-pragma': 'no-cache', 'x-ghost-version': '5.x' }, method: 'GET', mode: 'cors', - path: '/test/', signal: expect.any(AbortSignal) }]); }); diff --git a/apps/admin-x-settings/package.json b/apps/admin-x-settings/package.json index 63f09ab344c6..c6d267311cb1 100644 --- a/apps/admin-x-settings/package.json +++ b/apps/admin-x-settings/package.json @@ -37,10 +37,10 @@ "preview": "vite preview" }, "dependencies": { - "@codemirror/lang-html": "6.4.9", + "@codemirror/lang-html": "^6.4.5", "@tryghost/color-utils": "0.2.2", - "@tryghost/kg-unsplash-selector": "0.2.1", - "@tryghost/limit-service": "1.2.14", + "@tryghost/kg-unsplash-selector": "^0.1.15", + "@tryghost/limit-service": "^1.2.10", "@tryghost/nql": "0.12.3", "@tryghost/timezone-data": "0.4.3", "react": "18.3.1", @@ -52,9 +52,9 @@ "@testing-library/react": "14.1.0", "@tryghost/admin-x-design-system": "0.0.0", "@tryghost/admin-x-framework": "0.0.0", - "@types/react": "18.3.3", + "@types/react": "18.3.2", "@types/react-dom": "18.3.0", - "@types/validator": "13.12.0", + "@types/validator": "13.11.10", "@vitejs/plugin-react": "4.2.1", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-refresh": "0.4.3", diff --git a/apps/admin-x-settings/src/MainContent.tsx b/apps/admin-x-settings/src/MainContent.tsx index 31898d9b565f..45bafb385a65 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
diff --git a/apps/admin-x-settings/src/components/settings/advanced/CodeInjection.tsx b/apps/admin-x-settings/src/components/settings/advanced/CodeInjection.tsx index 484d52319dd0..aba04ba3c087 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/CodeInjection.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/CodeInjection.tsx @@ -10,7 +10,7 @@ const CodeInjection: React.FC<{ keywords: string[] }> = ({keywords}) => { customHeader={
-
diff --git a/apps/admin-x-settings/src/components/settings/advanced/History.tsx b/apps/admin-x-settings/src/components/settings/advanced/History.tsx index 8b05a073cc24..7f33ae13546f 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/History.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/History.tsx @@ -11,7 +11,7 @@ const History: React.FC<{ keywords: string[] }> = ({keywords}) => { return ( } + customButtons={
} ]; diff --git a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreview.tsx b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreview.tsx index f56790472430..7e94bbea2c77 100644 --- a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreview.tsx +++ b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreview.tsx @@ -106,7 +106,6 @@ const NewsletterPreview: React.FC<{newsletter: Newsletter}> = ({newsletter}) => senderReplyTo={renderReplyToEmail(newsletter, config, supportEmailAddress, defaultEmailAddress)} showBadge={newsletter.show_badge} showCommentCta={showCommentCta} - showExcerpt={newsletter.show_excerpt} showFeatureImage={newsletter.show_feature_image} showFeedback={showFeedback} showLatestPosts={newsletter.show_latest_posts} diff --git a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreviewContent.tsx b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreviewContent.tsx index e5f4642296c6..0bffd1434ac2 100644 --- a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreviewContent.tsx +++ b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreviewContent.tsx @@ -18,7 +18,6 @@ const NewsletterPreviewContent: React.FC<{ headerTitle?: string | null; headerSubtitle?: string | null; showPostTitleSection: boolean; - showExcerpt: boolean; titleAlignment?: string; titleFontCategory?: string; bodyFontCategory?: string; @@ -50,7 +49,6 @@ const NewsletterPreviewContent: React.FC<{ headerTitle, headerSubtitle, showPostTitleSection, - showExcerpt, titleAlignment, titleFontCategory, bodyFontCategory, @@ -77,7 +75,6 @@ const NewsletterPreviewContent: React.FC<{ const showHeader = headerIcon || headerTitle; const {config} = useGlobalData(); const hasNewEmailAddresses = useFeatureFlag('newEmailAddresses'); - const hasNewsletterExcerpt = useFeatureFlag('newsletterExcerpt'); const currentDate = new Date().toLocaleDateString('default', { year: 'numeric', @@ -101,25 +98,6 @@ const NewsletterPreviewContent: React.FC<{

To: Jamie Larson jamie@example.com

; } - let excerptClasses = 'mb-5 text-pretty leading-[1.7] text-black'; - - if (titleFontCategory === 'serif' && bodyFontCategory === 'serif') { - excerptClasses = clsx(excerptClasses, 'mb-8 font-serif text-[2.0rem] leading-tight'); - } else if (titleFontCategory !== 'serif' && bodyFontCategory === 'serif') { - excerptClasses = clsx(excerptClasses, 'mb-8 text-[1.7rem] leading-tight tracking-tight'); - } else if (titleFontCategory === 'serif' && bodyFontCategory !== 'serif') { - excerptClasses = clsx(excerptClasses, 'mb-8 font-serif text-[2.0rem] leading-tight'); - } else { - excerptClasses = clsx(excerptClasses, 'mb-8 text-[1.9rem] leading-tight tracking-tight'); - } - - if (titleAlignment === 'center') { - excerptClasses = clsx( - excerptClasses, - 'text-center' - ); - } - return (
@@ -134,38 +112,34 @@ const NewsletterPreviewContent: React.FC<{
{headerImage && (
- +
)} {showHeader && ( -
+
{headerIcon && } {headerTitle &&

{headerTitle}

} - {headerSubtitle &&
{headerSubtitle}
} + {headerSubtitle &&
{headerSubtitle}
}
)} {showPostTitleSection && ( -
+

Your email newsletter

- {(hasNewsletterExcerpt && showExcerpt) && ( -

A subtitle to highlight key points and engage your readers

- )}
-

+

By {authorPlaceholder} {currentDate}

-

View in browser

+

View in browser

)} @@ -173,21 +147,14 @@ const NewsletterPreviewContent: React.FC<{ {/* Feature image */} {showFeatureImage && ( <> -
+
Feature
-
Feature image caption
+
Feature image caption
)} -
+

This is what your content will look like when you send one of your posts as an email newsletter to your subscribers.

Over there on the right you'll see some settings that allow you to customize the look and feel of this template to make it perfectly suited to your brand. Email templates are exceptionally finnicky to make, but we've spent a long time optimising this one to make it work beautifully across devices, email clients and content types.

So, you can trust that every email you send with Ghost will look great and work well. Just like the rest of your site.

@@ -232,7 +199,7 @@ const NewsletterPreviewContent: React.FC<{

The three latest posts published on your site

-

Posts sent as an email only will never be shown here.

+

Posts sent as an email only will never be shown here.

Latest post @@ -241,7 +208,7 @@ const NewsletterPreviewContent: React.FC<{

Displayed at the bottom of each newsletter

-

Giving your readers one more place to discover your stories.

+

Giving your readers one more place to discover your stories.

Latest post @@ -250,7 +217,7 @@ const NewsletterPreviewContent: React.FC<{

To keep your work front and center

-

Making sure that your audience stays engaged.

+

Making sure that your audience stays engaged.

Latest post @@ -263,7 +230,7 @@ const NewsletterPreviewContent: React.FC<{ {showSubscriptionDetails && (

Subscription details

-

You are receiving this because you are a paid subscriber to {siteTitle}. Your subscription will renew on 17 Jul 2024.

+

You are receiving this because you are a paid subscriber to The Local Host. Your subscription will renew on 17 Jul 2024.

Name: Jamie Larson

@@ -279,9 +246,9 @@ const NewsletterPreviewContent: React.FC<{ {/* Footer */}
-
+
-
+
{siteTitle} © {currentYear} — Unsubscribe
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 8ee3e4af38a2..731b8dc6459b 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[] = [], labelSlugs: string[] = [], offerIds: string[] = []; + const tierIds: string[] = [], labelIds: string[] = [], offerIds: string[] = []; for (const filter of filters) { if (filter.startsWith('label:')) { - labelSlugs.push(filter.replace('label:', '')); + labelIds.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, 'id').then(data => data.map(tierOption)), - labels.loadInitialValues(labelSlugs, 'slug').then(data => data.map(labelOption)), - offers.loadInitialValues(offerIds, 'id').then(data => data.map(offerOption)) + tiers.loadInitialValues(tierIds).then(data => data.map(tierOption)), + labels.loadInitialValues(labelIds).then(data => data.map(labelOption)), + offers.loadInitialValues(offerIds).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/src/components/settings/general/SocialAccounts.tsx b/apps/admin-x-settings/src/components/settings/general/SocialAccounts.tsx index 2516b3c7bba1..37a0a0578047 100644 --- a/apps/admin-x-settings/src/components/settings/general/SocialAccounts.tsx +++ b/apps/admin-x-settings/src/components/settings/general/SocialAccounts.tsx @@ -76,7 +76,7 @@ const SocialAccounts: React.FC<{ keywords: string[] }> = ({keywords}) => { { 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 27e492db49f8..f44833c327b7 100644 --- a/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx +++ b/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx @@ -113,6 +113,10 @@ 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); @@ -349,10 +353,9 @@ 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'} + okLabel={okProps.label || 'Save & close'} size={canAccessSettings(currentUser) ? 'lg' : 'bleed'} stickyFooter={true} testId='user-detail-modal' diff --git a/apps/admin-x-settings/src/components/settings/general/Users.tsx b/apps/admin-x-settings/src/components/settings/general/Users.tsx index 703ef6547e11..aaede23f545d 100644 --- a/apps/admin-x-settings/src/components/settings/general/Users.tsx +++ b/apps/admin-x-settings/src/components/settings/general/Users.tsx @@ -221,7 +221,7 @@ const Users: React.FC<{ keywords: string[], highlight?: boolean }> = ({keywords, }; const buttons = ( -
diff --git a/apps/admin-x-settings/src/components/settings/growth/embedSignup/EmbedSignupForm.tsx b/apps/admin-x-settings/src/components/settings/growth/embedSignup/EmbedSignupForm.tsx index 703a52c622c4..8dffc09960fc 100644 --- a/apps/admin-x-settings/src/components/settings/growth/embedSignup/EmbedSignupForm.tsx +++ b/apps/admin-x-settings/src/components/settings/growth/embedSignup/EmbedSignupForm.tsx @@ -11,7 +11,7 @@ const EmbedSignupForm: React.FC<{ keywords: string[] }> = ({keywords}) => { return ( } + customButtons={
); diff --git a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx index 01e9e5d6f7d4..2fcff193d850 100644 --- a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx @@ -1,4 +1,4 @@ -import NiceModal from '@ebay/nice-modal-react'; +import NiceModal, {useModal} from '@ebay/nice-modal-react'; import React, {useEffect, useRef} from 'react'; import TierDetailPreview from './TierDetailPreview'; import useFeatureFlag from '../../../../hooks/useFeatureFlag'; @@ -17,6 +17,7 @@ export type TierFormState = Partial> & { 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(); @@ -96,6 +97,10 @@ const TierDetailModalContent: React.FC<{tier?: Tier}> = ({tier}) => { } } }, + onSavedStateReset: () => { + modal.remove(); + updateRoute('tiers'); + }, onSaveError: handleError }); @@ -180,11 +185,10 @@ 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'} + okLabel={okProps.label || 'Save & close'} size='lg' testId='tier-detail-modal' title={(tier ? (tier.active ? 'Edit tier' : 'Edit archived tier') : 'New tier')} diff --git a/apps/admin-x-settings/src/components/settings/site/AnnouncementBar.tsx b/apps/admin-x-settings/src/components/settings/site/AnnouncementBar.tsx index d0ecb513c0d1..2bfaae75f91f 100644 --- a/apps/admin-x-settings/src/components/settings/site/AnnouncementBar.tsx +++ b/apps/admin-x-settings/src/components/settings/site/AnnouncementBar.tsx @@ -11,7 +11,7 @@ const AnnouncementBar: React.FC<{ keywords: string[] }> = ({keywords}) => { return ( } + customButtons={
-
+
{{#if (not (or this.loading this.error))}}
{{#each this.entries as |entry|}} {{else}}
@@ -26,7 +26,7 @@
diff --git a/ghost/admin/app/components/editor/modals/publish-flow/complete.hbs b/ghost/admin/app/components/editor/modals/publish-flow/complete.hbs index 505cd3bd8e28..83c6e3116a3f 100644 --- a/ghost/admin/app/components/editor/modals/publish-flow/complete.hbs +++ b/ghost/admin/app/components/editor/modals/publish-flow/complete.hbs @@ -97,7 +97,18 @@

{{else}}

- Back to dashboard + {{#if (feature "onboardingChecklist")}} + Back to dashboard + {{else}} + + {{/if}}

{{/if}} {{/if}} diff --git a/ghost/admin/app/components/editor/publish-management.js b/ghost/admin/app/components/editor/publish-management.js index eb2906522b83..7d97a3190645 100644 --- a/ghost/admin/app/components/editor/publish-management.js +++ b/ghost/admin/app/components/editor/publish-management.js @@ -56,7 +56,7 @@ export default class PublishManagement extends Component { } } - if (isValid && (!this.publishFlowModal || this.publishFlowModal?.isClosing)) { + if (isValid && !this.publishFlowModal || this.publishFlowModal?.isClosing) { this.publishOptions.resetPastScheduledAt(); this.publishFlowModal = this.modals.open(PublishFlowModal, { @@ -83,7 +83,7 @@ export default class PublishManagement extends Component { const isValid = await this._validatePost(); - if (isValid && (!this.updateFlowModal || this.updateFlowModal.isClosing)) { + if (isValid && !this.updateFlowModal || this.updateFlowModal.isClosing) { this.updateFlowModal = this.modals.open(UpdateFlowModal, { publishOptions: this.publishOptions, saveTask: this.publishTask @@ -99,12 +99,10 @@ export default class PublishManagement extends Component { } @action - async openPreview(event, {skipAnimation} = {}) { + openPreview(event, {skipAnimation} = {}) { event?.preventDefault(); - const isValid = await this._validatePost(); - - if (isValid && (!this.previewModal || this.previewModal.isClosing)) { + if (!this.previewModal || this.previewModal.isClosing) { // open publish flow modal underneath to offer quick switching // without restarting the flow or causing flicker diff --git a/ghost/admin/app/components/gh-alert.js b/ghost/admin/app/components/gh-alert.js index 2114ae97a10f..ca22911459a3 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: 'black', - info: 'black' + warn: 'blue', + info: 'blue' }; const type = this.args.message.type; diff --git a/ghost/admin/app/components/gh-editor-feature-image.hbs b/ghost/admin/app/components/gh-editor-feature-image.hbs index 0d934a7d8780..66775bb48d6c 100644 --- a/ghost/admin/app/components/gh-editor-feature-image.hbs +++ b/ghost/admin/app/components/gh-editor-feature-image.hbs @@ -46,11 +46,11 @@ @imageSrc={{@image}} @saveImage={{fn this.saveImage uploader.setFiles}} /> -
-
+
{{#if this.isEditingAlt}} - - {{#if (feature 'editorExcerpt')}} -
- - {{#if @excerptHasTk}} -
- TK -
- {{/if}} -
- {{#if @excerptErrorMessage}} -
- {{@excerptErrorMessage}} -
- {{/if}} -
- {{/if}}
diff --git a/ghost/admin/app/components/gh-koenig-editor-lexical.js b/ghost/admin/app/components/gh-koenig-editor-lexical.js index 2319ae9c8a6b..abc7eb7c4bcc 100644 --- a/ghost/admin/app/components/gh-koenig-editor-lexical.js +++ b/ghost/admin/app/components/gh-koenig-editor-lexical.js @@ -4,13 +4,11 @@ import {action} from '@ember/object'; import {inject as service} from '@ember/service'; import {tracked} from '@glimmer/tracking'; -export default class GhKoenigEditorLexical extends Component { +export default class GhKoenigEditorReactComponent extends Component { @service settings; - @service feature; containerElement = null; titleElement = null; - excerptElement = null; mousedownY = 0; uploadUrl = `${ghostPaths().apiRoot}/images/upload/`; @@ -32,10 +30,6 @@ export default class GhKoenigEditorLexical extends Component { return color; } - get excerpt() { - return this.args.excerpt || ''; - } - @action registerElement(element) { this.containerElement = element; @@ -112,106 +106,25 @@ export default class GhKoenigEditorLexical extends Component { this.titleElement.focus(); } + // move cursor to the editor on + // - Tab + // - Arrow Down/Right when input is empty or caret at end of input + // - Enter, creating an empty paragraph when editor is not empty @action onTitleKeydown(event) { - if (this.feature.editorExcerpt) { - // move cursor to the excerpt on - // - Tab (handled by browser) - // - Arrow Down/Right when input is empty or caret at end of input - // - Enter - const {key} = event; - const {value, selectionStart} = event.target; - - if (key === 'Enter') { - event.preventDefault(); - this.excerptElement?.focus(); - } - - if ((key === 'ArrowDown' || key === 'ArrowRight') && !event.shiftKey) { - const couldLeaveTitle = !value || selectionStart === value.length; - - if (couldLeaveTitle) { - event.preventDefault(); - this.excerptElement?.focus(); - } - } - } else { - // move cursor to the editor on - // - Tab - // - Arrow Down/Right when input is empty or caret at end of input - // - Enter, creating an empty paragraph when editor is not empty - const {editorAPI} = this; - - if (!editorAPI || event.originalEvent.isComposing) { - return; - } - - const {key} = event; - const {value, selectionStart} = event.target; - - const couldLeaveTitle = !value || selectionStart === value.length; - const arrowLeavingTitle = ['ArrowDown', 'ArrowRight'].includes(key) && couldLeaveTitle; - - if (key === 'Enter' || key === 'Tab' || arrowLeavingTitle) { - event.preventDefault(); + const {editorAPI} = this; - if (key === 'Enter' && !editorAPI.editorIsEmpty()) { - editorAPI.insertParagraphAtTop({focus: true}); - } else { - editorAPI.focusEditor({position: 'top'}); - } - } + if (!editorAPI || event.originalEvent.isComposing) { + return; } - } - - // Subtitle ("excerpt") Actions ------------------------------------------- - @action - registerExcerptElement(element) { - this.excerptElement = element; - } - - @action - focusExcerpt() { - this.excerptElement?.focus(); - - // timeout ensures this occurs after the keyboard events - setTimeout(() => { - this.excerptElement?.setSelectionRange(-1, -1); - }, 0); - } - - @action - onExcerptInput(event) { - this.args.setExcerpt?.(event.target.value); - } - - @action - onExcerptKeydown(event) { - // move cursor to the title on - // - Shift+Tab (handled by the browser) - // - Arrow Up/Left when input is empty or caret at start of input - // move cursor to the editor on - // - Tab - // - Arrow Down/Right when input is empty or caret at end of input - // - Enter, creating an empty paragraph when editor is not empty const {key} = event; const {value, selectionStart} = event.target; - if ((key === 'ArrowUp' || key === 'ArrowLeft') && !event.shiftKey) { - const couldLeaveTitle = !value || selectionStart === 0; - - if (couldLeaveTitle) { - event.preventDefault(); - this.focusTitle(); - } - } - - const {editorAPI} = this; const couldLeaveTitle = !value || selectionStart === value.length; - const arrowLeavingTitle = (key === 'ArrowRight' || key === 'ArrowDown') && couldLeaveTitle; + const arrowLeavingTitle = ['ArrowDown', 'ArrowRight'].includes(key) && couldLeaveTitle; - if (key === 'Enter' || (key === 'Tab' && !event.shiftKey) || arrowLeavingTitle) { + if (key === 'Enter' || key === 'Tab' || arrowLeavingTitle) { event.preventDefault(); if (key === 'Enter' && !editorAPI.editorIsEmpty()) { @@ -222,8 +135,6 @@ export default class GhKoenigEditorLexical extends Component { } } - // move cursor to the editor on - // Body actions ------------------------------------------------------------ @action @@ -236,7 +147,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') && this.editorAPI) { + if (!this.skipFocusEditor && event.target.classList.contains('gh-koenig-editor-pane')) { let editorCanvas = this.editorAPI.editorInstance.getRootElement(); let {bottom} = editorCanvas.getBoundingClientRect(); diff --git a/ghost/admin/app/components/gh-member-settings-form.hbs b/ghost/admin/app/components/gh-member-settings-form.hbs index d301aadf30db..f1a7630274eb 100644 --- a/ghost/admin/app/components/gh-member-settings-form.hbs +++ b/ghost/admin/app/components/gh-member-settings-form.hbs @@ -147,7 +147,21 @@ {{/if}} {{/if}} - {{sub.validityDetails}} + {{#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}}
diff --git a/ghost/admin/app/components/gh-member-settings-form.js b/ghost/admin/app/components/gh-member-settings-form.js index bcd9193bf50b..8596af3b455e 100644 --- a/ghost/admin/app/components/gh-member-settings-form.js +++ b/ghost/admin/app/components/gh-member-settings-form.js @@ -1,8 +1,9 @@ import Component from '@glimmer/component'; +import moment from 'moment-timezone'; import {action} from '@ember/object'; -import {didCancel, task} from 'ember-concurrency'; -import {getSubscriptionData} from 'ghost-admin/utils/subscription-data'; +import {getNonDecimal, getSymbol} from 'ghost-admin/utils/currency'; import {inject as service} from '@ember/service'; +import {task} from 'ember-concurrency'; import {tracked} from '@glimmer/tracking'; export default class extends Component { @@ -59,9 +60,41 @@ 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 subsWithPrice = subscriptions.filter(sub => !!sub.price); - let subscriptionData = subsWithPrice.map(sub => getSubscriptionData(sub)); + 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'); + } + } + 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); @@ -104,17 +137,8 @@ export default class extends Component { @action setup() { - try { - this.fetchTiers.perform(); - this.fetchNewsletters.perform(); - } catch (e) { - // Do not throw cancellation errors - if (didCancel(e)) { - return; - } - - throw e; - } + this.fetchTiers.perform(); + this.fetchNewsletters.perform(); } @action diff --git a/ghost/admin/app/components/gh-nav-menu/footer-banner.hbs b/ghost/admin/app/components/gh-nav-menu/footer-banner.hbs deleted file mode 100644 index 7ebaa07e970c..000000000000 --- a/ghost/admin/app/components/gh-nav-menu/footer-banner.hbs +++ /dev/null @@ -1,28 +0,0 @@ -{{#if this.showReferralInvite}} - -{{/if}} - -{{#if (and this.showWhatsNew this.whatsNew.hasNew)}} - {{#let (get this.whatsNew.entries "0") as |entry|}} - - {{/let}} -{{/if}} \ No newline at end of file diff --git a/ghost/admin/app/components/gh-nav-menu/footer-banner.js b/ghost/admin/app/components/gh-nav-menu/footer-banner.js deleted file mode 100644 index fab716af8217..000000000000 --- a/ghost/admin/app/components/gh-nav-menu/footer-banner.js +++ /dev/null @@ -1,89 +0,0 @@ -import Component from '@glimmer/component'; -import envConfig from 'ghost-admin/config/environment'; -import moment from 'moment-timezone'; -import {action} from '@ember/object'; -import {inject as service} from '@ember/service'; -import {task} from 'ember-concurrency'; - -export default class FooterBanner extends Component { - @service session; - @service dashboardStats; - @service feature; - @service membersUtils; - @service modals; - @service settings; - @service whatsNew; - - constructor() { - super(...arguments); - this.loadCurrentMRR.perform(); - } - - get isAdminOrOwner() { - return this.session.user.isAdmin; - } - - get isReferralNotificationNotDismissed() { - return !this.feature.accessibility.referralInviteDismissed; - } - - get stripeLiveModeEnabled() { - // allow testing mode when not in a production environment - const isDevModeStripeEnabled = envConfig.environment !== 'production' && this.membersUtils.isStripeEnabled; - const isLiveEnabled = this.settings.stripeConnectLivemode; - return isDevModeStripeEnabled || isLiveEnabled; - } - - get hasReachedMRR() { - return this.dashboardStats.currentMRR / 100 >= 100; - } - - get showReferralInvite() { - // Conditions to see the referral invite - // 1. Needs to be Owner or Admin - // 2. Stripe is setup and enabled in live mode - // 3. MRR is > $100 - // 4. Notification has not yet been dismissed by the user - return !this.args.hasThemeErrors && this.isAdminOrOwner && this.isReferralNotificationNotDismissed && this.stripeLiveModeEnabled && this.hasReachedMRR; - } - - get showWhatsNew() { - return !this.showReferralInvite && this.whatsNew.hasNewFeatured; - } - - @task - *loadCurrentMRR() { - if (this.isAdminOrOwnern) { - try { - yield this.dashboardStats.loadMrrStats(); - } catch (error) { - // noop - } - } - } - - @action - dismissReferralInvite(event) { - event.preventDefault(); - event.stopPropagation(); - - if (!this.feature.referralInviteDismissed) { - this.feature.referralInviteDismissed = moment().tz(this.settings.timezone); - } - } - - @action - dismissWhatsNewToast(event) { - event.preventDefault(); - event.stopPropagation(); - - // Dismiss - this.whatsNew.seen(); - } - - @action - openFeaturedWhatsNew(href) { - window.open(href, '_blank'); - this.whatsNew.seen(); - } -} diff --git a/ghost/admin/app/components/gh-nav-menu/footer.hbs b/ghost/admin/app/components/gh-nav-menu/footer.hbs index e40db0836310..da96e8510248 100644 --- a/ghost/admin/app/components/gh-nav-menu/footer.hbs +++ b/ghost/admin/app/components/gh-nav-menu/footer.hbs @@ -1,19 +1,12 @@
{{#if this.hasThemeErrors}} -
- -
+ {{/if}} - +
@@ -21,7 +14,7 @@
- {{#if (and this.whatsNew.hasNew (not this.whatsNew.hasNewFeatured))}}{{/if}} + {{#if this.whatsNew.hasNew}}{{/if}}
{{svg-jar "arrow-down" class="w3 mr1 fill-darkgrey"}}
@@ -52,12 +45,12 @@ {{else}}
  • - +
  • {{/if}} diff --git a/ghost/admin/app/components/gh-nav-menu/footer.js b/ghost/admin/app/components/gh-nav-menu/footer.js index b51777f23596..eb9e9eba1fcf 100644 --- a/ghost/admin/app/components/gh-nav-menu/footer.js +++ b/ghost/admin/app/components/gh-nav-menu/footer.js @@ -1,6 +1,5 @@ import Component from '@ember/component'; import ThemeErrorsModal from '../modals/design/theme-errors'; -import WhatsNew from '../modals/whats-new'; import calculatePosition from 'ember-basic-dropdown/utils/calculate-position'; import classic from 'ember-classic-decorator'; import {action} from '@ember/object'; @@ -44,7 +43,7 @@ export default class Footer extends Component { // filter errors that have other UI to display to users that the functionality is not working const filteredErrors = errors?.filter((error) => { if (error.code === 'GS110-NO-MISSING-PAGE-BUILDER-USAGE' && error?.failures?.[0].message.includes(`show_title_and_feature_image`)) { - return false; + return false; } return true; }); @@ -61,9 +60,4 @@ export default class Footer extends Component { return {horizontalPosition, verticalPosition, style}; } - - @action - openWhatsNew() { - return this.modals.open(WhatsNew); - } } diff --git a/ghost/admin/app/components/gh-nav-menu/main.hbs b/ghost/admin/app/components/gh-nav-menu/main.hbs index 47ea632ce7d8..58f9ecc8f74d 100644 --- a/ghost/admin/app/components/gh-nav-menu/main.hbs +++ b/ghost/admin/app/components/gh-nav-menu/main.hbs @@ -18,7 +18,7 @@
      {{#if (gh-user-can-admin this.session.user)}} -
    • +
    • {{svg-jar "house"}} Dashboard
    • {{/if}} diff --git a/ghost/admin/app/components/gh-nav-menu/main.js b/ghost/admin/app/components/gh-nav-menu/main.js index d47997f0a33c..51bce6256a8f 100644 --- a/ghost/admin/app/components/gh-nav-menu/main.js +++ b/ghost/admin/app/components/gh-nav-menu/main.js @@ -23,10 +23,10 @@ export default class Main extends Component.extend(ShortcutsMixin) { @service router; @service session; @service ui; + @service whatsNew; @service membersStats; @service settings; @service explore; - @service notifications; @inject config; diff --git a/ghost/admin/app/components/gh-post-settings-menu.hbs b/ghost/admin/app/components/gh-post-settings-menu.hbs index 1d01dcc5bcee..06963c9ebaf9 100644 --- a/ghost/admin/app/components/gh-post-settings-menu.hbs +++ b/ghost/admin/app/components/gh-post-settings-menu.hbs @@ -93,7 +93,7 @@ {{/if}} {{/if}} - {{#unless (feature 'editorExcerpt')}} + - {{/unless}} {{#unless this.session.user.isAuthorOrContributor}} diff --git a/ghost/admin/app/components/gh-token-input/label-token.js b/ghost/admin/app/components/gh-token-input/label-token.js index 0327c9c3924b..d82786d06814 100644 --- a/ghost/admin/app/components/gh-token-input/label-token.js +++ b/ghost/admin/app/components/gh-token-input/label-token.js @@ -1,17 +1,10 @@ import DraggableObject from 'ember-drag-drop/components/draggable-object'; import classic from 'ember-classic-decorator'; -import {alias} from '@ember/object/computed'; import {attributeBindings, classNames} from '@ember-decorators/component'; -import {computed} from '@ember/object'; @classic @attributeBindings('title') @classNames('label-token') export default class LabelToken extends DraggableObject { - @alias('content.name') name; - - @computed('name') - get title() { - return this.name ?? 'Label'; - } + title = this.name ?? 'Label'; } diff --git a/ghost/admin/app/components/koenig-image-editor.hbs b/ghost/admin/app/components/koenig-image-editor.hbs index f58b2a165e01..f5180629176b 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/components/koenig-lexical-editor.js b/ghost/admin/app/components/koenig-lexical-editor.js index 18e6d2df19c9..da1770b383d2 100644 --- a/ghost/admin/app/components/koenig-lexical-editor.js +++ b/ghost/admin/app/components/koenig-lexical-editor.js @@ -83,28 +83,6 @@ export function decoratePostSearchResult(item, settings) { } } -/** - * Fetches the URLs of all active offers - * @returns {Promise<{label: string, value: string}[]>} - */ -export async function offerUrls() { - let offers = []; - - try { - offers = await this.fetchOffersTask.perform(); - } catch (e) { - // No-op: if offers are not available (e.g. missing permissions), return an empty array - return []; - } - - return offers.map((offer) => { - return { - label: `Offer — ${offer.name}`, - value: this.config.getSiteUrl(offer.code) - }; - }); -} - class ErrorHandler extends React.Component { state = { hasError: false @@ -295,6 +273,8 @@ export default class KoenigLexicalEditor extends Component { }; const fetchAutocompleteLinks = async () => { + const offers = await this.fetchOffersTask.perform(); + const defaults = [ {label: 'Homepage', value: window.location.origin + '/'}, {label: 'Free signup', value: '#/portal/signup/free'} @@ -339,23 +319,18 @@ export default class KoenigLexicalEditor extends Component { return []; }; - const offersLinks = await offerUrls.call(this); + const offersLinks = offers.toArray().map((offer) => { + return { + label: `Offer - ${offer.name}`, + value: this.config.getSiteUrl(offer.code) + }; + }); return [...defaults, ...memberLinks(), ...donationLink(), ...recommendationLink(), ...offersLinks]; }; const fetchLabels = async () => { - let labels = []; - try { - labels = await this.fetchLabelsTask.perform(); - } catch (e) { - // Do not throw cancellation errors - if (didCancel(e)) { - return; - } - - throw e; - } + const labels = await this.fetchLabelsTask.perform(); return labels.map(label => label.name); }; @@ -397,21 +372,12 @@ export default class KoenigLexicalEditor extends Component { if (!didCancel(error)) { throw error; } - return; } - // only published posts/pages and staff with posts have URLs + // only published posts/pages have URLs const filteredResults = []; results.forEach((group) => { - let items = group.options; - - if (group.groupName === 'Posts' || group.groupName === 'Pages') { - items = items.filter(i => i.status === 'published'); - } - - if (group.groupName === 'Staff') { - items = items.filter(i => !/\/404\//.test(i.url)); - } + const items = (group.groupName === 'Posts' || group.groupName === 'Pages') ? group.options.filter(i => i.status === 'published') : group.options; if (items.length === 0) { return; @@ -448,22 +414,18 @@ export default class KoenigLexicalEditor extends Component { fetchCollectionPosts, fetchEmbed, fetchLabels, - renderLabels: !this.session.user.isContributor, feature: { collectionsCard: this.feature.collectionsCard, collections: this.feature.collections, - internalLinking: this.feature.internalLinking, - internalLinkingAtLinks: this.feature.internalLinking, - contentVisibility: this.feature.contentVisibility + internalLinking: this.feature.internalLinking }, - deprecated: { // todo fix typo + deprecated: { headerV1: true // if false, shows header v1 in the menu }, membersEnabled: this.settings.membersSignupAccess === 'all', searchLinks, siteTitle: this.settings.title, - siteDescription: this.settings.description, - siteUrl: this.config.getSiteUrl('/') + siteDescription: this.settings.description }; const cardConfig = Object.assign({}, defaultCardConfig, props.cardConfig, {pinturaConfig: this.pinturaConfig}); diff --git a/ghost/admin/app/components/members-activity/event-type-filter.js b/ghost/admin/app/components/members-activity/event-type-filter.js index 50029841c671..957eafeec0c7 100644 --- a/ghost/admin/app/components/members-activity/event-type-filter.js +++ b/ghost/admin/app/components/members-activity/event-type-filter.js @@ -11,8 +11,7 @@ const ALL_EVENT_TYPES = [ {event: 'email_opened_event', icon: 'filter-dropdown-email-opened', name: 'Email opened', group: 'emails'}, {event: 'email_delivered_event', icon: 'filter-dropdown-email-received', name: 'Email received', group: 'emails'}, {event: 'email_complaint_event', icon: 'filter-dropdown-email-flagged-as-spam', name: 'Email flagged as spam', group: 'emails'}, - {event: 'email_failed_event', icon: 'filter-dropdown-email-bounced', name: 'Email bounced', group: 'emails'}, - {event: 'email_change_event', icon: 'filter-dropdown-email-address-changed', name: 'Email address changed', group: 'emails'} + {event: 'email_failed_event', icon: 'filter-dropdown-email-bounced', name: 'Email bounced', group: 'emails'} ]; export default class MembersActivityEventTypeFilter extends Component { diff --git a/ghost/admin/app/components/members/filter.js b/ghost/admin/app/components/members/filter.js index 6993d1c4e79f..475ddf53eb71 100644 --- a/ghost/admin/app/components/members/filter.js +++ b/ghost/admin/app/components/members/filter.js @@ -4,8 +4,8 @@ import nql from '@tryghost/nql-lang'; import {AUDIENCE_FEEDBACK_FILTER, CREATED_AT_FILTER, EMAIL_CLICKED_FILTER, EMAIL_COUNT_FILTER, EMAIL_FILTER, EMAIL_OPENED_COUNT_FILTER, EMAIL_OPENED_FILTER, EMAIL_OPEN_RATE_FILTER, EMAIL_SENT_FILTER, LABEL_FILTER, LAST_SEEN_FILTER, NAME_FILTER, NEWSLETTERS_FILTERS, NEXT_BILLING_DATE_FILTER, OFFERS_FILTER, PLAN_INTERVAL_FILTER, SIGNUP_ATTRIBUTION_FILTER, STATUS_FILTER, SUBSCRIBED_FILTER, SUBSCRIPTION_ATTRIBUTION_FILTER, SUBSCRIPTION_START_DATE_FILTER, SUBSCRIPTION_STATUS_FILTER, TIER_FILTER} from './filters'; import {TrackedArray} from 'tracked-built-ins'; import {action} from '@ember/object'; -import {didCancel, task} from 'ember-concurrency'; import {inject as service} from '@ember/service'; +import {task} from 'ember-concurrency'; import {tracked} from '@glimmer/tracking'; function escapeNqlString(value) { @@ -234,19 +234,9 @@ export default class MembersFilter extends Component { async parseDefaultFilters() { // we need to make sure all the filters are loaded before parsing the default filter // otherwise the filter will be parsed with the wrong properties - try { - await this.fetchTiers.perform(); - await this.fetchNewsletters.perform(); - await this.fetchOffers.perform(); - } catch (e) { - // Do not throw cancellation errors - if (didCancel(e)) { - return; - } - - throw e; - } - + await this.fetchTiers.perform(); + await this.fetchNewsletters.perform(); + await this.fetchOffers.perform(); if (this.args.defaultFilterParam) { // check if it is different before parsing const validFilters = this.validFilters; diff --git a/ghost/admin/app/components/modal-member-tier.js b/ghost/admin/app/components/modal-member-tier.js index 733d08e3e6b6..bc94dc2d1eeb 100644 --- a/ghost/admin/app/components/modal-member-tier.js +++ b/ghost/admin/app/components/modal-member-tier.js @@ -1,8 +1,8 @@ import ModalComponent from 'ghost-admin/components/modal-base'; import moment from 'moment-timezone'; import {action} from '@ember/object'; -import {didCancel, task} from 'ember-concurrency'; import {inject as service} from '@ember/service'; +import {task} from 'ember-concurrency'; import {tracked} from '@glimmer/tracking'; export default class ModalMemberTier extends ModalComponent { @@ -77,16 +77,7 @@ export default class ModalMemberTier extends ModalComponent { @action setup() { this.loadingTiers = true; - try { - this.fetchTiers.perform(); - } catch (e) { - // Do not throw cancellation errors - if (didCancel(e)) { - return; - } - - throw e; - } + this.fetchTiers.perform(); } @action diff --git a/ghost/admin/app/components/modal-post-history.hbs b/ghost/admin/app/components/modal-post-history.hbs index 3989c29db590..4795ea810327 100644 --- a/ghost/admin/app/components/modal-post-history.hbs +++ b/ghost/admin/app/components/modal-post-history.hbs @@ -1,6 +1,6 @@ {{!-- template-lint-disable no-invalid-interactive --}}
      -
      +
      {{#if this.selectedHTML}} {{{this.selectedHTML}}} @@ -9,31 +9,15 @@
      {{#if this.selectedRevision.feature_image}} - {{this.selectedRevision.feature_image_alt}} + {{/if}} {{#if this.selectedRevision.feature_image_caption}} -

      {{{this.selectedRevision.feature_image_caption}}}

      +

      {{{this.selectedRevision.feature_image_caption}}}

      {{/if}}
      -
      - {{this.currentTitle}} -
      - {{#if (feature "editorExcerpt")}} -
      - {{this.selectedRevision.custom_excerpt}} -
      -
      - {{/if}} - +
      {{this.currentTitle}}
      +
      @@ -44,8 +28,7 @@ aria-label="Close meta data panel" class="back settings-menu-header-action" data-test-button="close-psm-subview" - type="button" - {{on "click" this.closeModal}} + type="button" {{action "closeModal"}} {{on "mousedown" (optional this.noop)}} > {{svg-jar "arrow-left"}} @@ -56,19 +39,19 @@