From 92babfe8dba821a09c74a18bf0003817f64348b7 Mon Sep 17 00:00:00 2001 From: Charles Zhao Date: Thu, 16 Jan 2025 22:08:46 +0800 Subject: [PATCH] fix: fixup --- i18n/de/code.json | 4 +- i18n/es/code.json | 4 +- i18n/fr/code.json | 4 +- i18n/pt-BR/code.json | 4 +- .../use-categorized-tutorial-metadata.ts | 24 ++++--- src/theme/BlogLayout/index.module.scss | 1 + src/theme/BlogPostItem/Header/Info/index.tsx | 14 ++-- src/theme/BlogPostItem/Header/Title/index.tsx | 7 +- .../Header/Title/styles.module.css | 15 +--- .../TitleWithHighlights/index.module.scss | 3 + .../Header/TitleWithHighlights/index.tsx | 44 ++++++++++++ .../index.module.scss | 17 ----- .../TitleWithSelectionDropdown/index.tsx | 29 +++++--- src/theme/BlogPostItem/index.module.scss | 28 +++++++- src/theme/BlogPostItem/index.tsx | 72 ++++++++++++++++++- src/theme/BlogPostItems/index.module.scss | 3 + src/theme/BlogPostItems/index.tsx | 4 +- 17 files changed, 210 insertions(+), 67 deletions(-) create mode 100644 src/theme/BlogPostItem/Header/TitleWithHighlights/index.module.scss create mode 100644 src/theme/BlogPostItem/Header/TitleWithHighlights/index.tsx create mode 100644 src/theme/BlogPostItems/index.module.scss diff --git a/i18n/de/code.json b/i18n/de/code.json index e5b58ff1062..51f8b37ec7a 100644 --- a/i18n/de/code.json +++ b/i18n/de/code.json @@ -483,10 +483,10 @@ "message": "Datenschutz" }, "theme.common.sdk.placeholder": { - "message": "Ihr SDK" + "message": "dein SDK" }, "theme.common.connector.placeholder": { - "message": "Ihr Anbieter" + "message": "dein anbieter" }, "theme.common.sdk.native": { "message": "Nativ" diff --git a/i18n/es/code.json b/i18n/es/code.json index b8c8b3d6d0d..4fc76aafe0b 100644 --- a/i18n/es/code.json +++ b/i18n/es/code.json @@ -483,10 +483,10 @@ "message": "Privacidad" }, "theme.common.sdk.placeholder": { - "message": "Tu SDK" + "message": "tu SDK" }, "theme.common.connector.placeholder": { - "message": "Tu proveedor" + "message": "tu proveedor" }, "theme.common.sdk.native": { "message": "Nativo" diff --git a/i18n/fr/code.json b/i18n/fr/code.json index bbc6b582315..d1089f7ea6a 100644 --- a/i18n/fr/code.json +++ b/i18n/fr/code.json @@ -483,10 +483,10 @@ "message": "Confidentialité" }, "theme.common.sdk.placeholder": { - "message": "Votre SDK" + "message": "ton SDK" }, "theme.common.connector.placeholder": { - "message": "Votre fournisseur" + "message": "ton fournisseur" }, "theme.common.sdk.native": { "message": "Natif" diff --git a/i18n/pt-BR/code.json b/i18n/pt-BR/code.json index 908b8822b68..1f4e9970d5e 100644 --- a/i18n/pt-BR/code.json +++ b/i18n/pt-BR/code.json @@ -483,10 +483,10 @@ "message": "Privacidade" }, "theme.common.sdk.placeholder": { - "message": "Seu SDK" + "message": "seu SDK" }, "theme.common.connector.placeholder": { - "message": "Seu provedor" + "message": "seu provedor" }, "theme.common.sdk.native": { "message": "Nativo" diff --git a/src/hooks/use-categorized-tutorial-metadata.ts b/src/hooks/use-categorized-tutorial-metadata.ts index 01df481aa16..ba8f5367d93 100644 --- a/src/hooks/use-categorized-tutorial-metadata.ts +++ b/src/hooks/use-categorized-tutorial-metadata.ts @@ -1,4 +1,5 @@ import { type DocMetadata } from '@docusaurus/plugin-content-docs'; +import { useMemo } from 'react'; import metadata from '@site/tutorial/build-with-logto/metadata.json'; @@ -47,16 +48,19 @@ const useCategorizedTutorialMetadata = () => { { nativeSdks: [], traditionalSdks: [], spaSdks: [] } ); - return { - allSdks: sdks, - allConnectors: [...socialConnectors, ...emailConnectors, ...smsConnectors], - nativeSdks, - traditionalSdks, - spaSdks, - socialConnectors, - emailConnectors, - smsConnectors, - }; + return useMemo( + () => ({ + allSdks: sdks, + allConnectors: [...socialConnectors, ...emailConnectors, ...smsConnectors], + nativeSdks, + traditionalSdks, + spaSdks, + socialConnectors, + emailConnectors, + smsConnectors, + }), + [sdks, socialConnectors, emailConnectors, smsConnectors, nativeSdks, traditionalSdks, spaSdks] + ); }; export default useCategorizedTutorialMetadata; diff --git a/src/theme/BlogLayout/index.module.scss b/src/theme/BlogLayout/index.module.scss index 6f07cc34808..9434191bfc3 100644 --- a/src/theme/BlogLayout/index.module.scss +++ b/src/theme/BlogLayout/index.module.scss @@ -14,6 +14,7 @@ flex-direction: column; gap: 24px; overflow: hidden; + margin-bottom: 50px; } .toc { diff --git a/src/theme/BlogPostItem/Header/Info/index.tsx b/src/theme/BlogPostItem/Header/Info/index.tsx index 1438845c20f..6a6342f21cc 100644 --- a/src/theme/BlogPostItem/Header/Info/index.tsx +++ b/src/theme/BlogPostItem/Header/Info/index.tsx @@ -47,13 +47,10 @@ function Spacer() { return <>{' · '}; } -export default function BlogPostItemHeaderInfo({ className }: Props): JSX.Element { +export default function BlogPostItemHeaderInfo({ className }: Props) { const { metadata } = useBlogPost(); const { date, readingTime } = metadata; - // Charles edited this to remove the time from generated "Build X with Y tutorials" - const isTutorial = metadata.frontMatter.slug?.startsWith('how-to-build-'); - const dateTimeFormat = useDateTimeFormat({ day: 'numeric', month: 'long', @@ -61,11 +58,18 @@ export default function BlogPostItemHeaderInfo({ className }: Props): JSX.Elemen timeZone: 'UTC', }); + // Charles edited this to remove the time from generated "Build X with Y tutorials" + const isTutorial = metadata.frontMatter.slug?.startsWith('how-to-build-'); + + if (isTutorial) { + return null; + } + const formatDate = (blogDate: string) => dateTimeFormat.format(new Date(blogDate)); return (
- {!isTutorial && } + {readingTime !== undefined && ( <> diff --git a/src/theme/BlogPostItem/Header/Title/index.tsx b/src/theme/BlogPostItem/Header/Title/index.tsx index aa50115c31b..e7ff2f8ba48 100644 --- a/src/theme/BlogPostItem/Header/Title/index.tsx +++ b/src/theme/BlogPostItem/Header/Title/index.tsx @@ -5,6 +5,7 @@ import type { Props } from '@theme/BlogPostItem/Header/Title'; import { clsx } from 'clsx'; import { useCallback, useEffect } from 'react'; +import TitleWithHighlights from '../TitleWithHighlights'; import TitleWithSelectionDropdown from '../TitleWithSelectionDropdown'; import styles from './styles.module.css'; @@ -71,7 +72,11 @@ const Content = ({ className }: Props): JSX.Element => { className={clsx(styles.title, !isBlogPostPage && styles.listTitle, className)} itemProp="headline" > - {isBlogPostPage ? : title} + {isBlogPostPage ? ( + + ) : ( + + )} ); diff --git a/src/theme/BlogPostItem/Header/Title/styles.module.css b/src/theme/BlogPostItem/Header/Title/styles.module.css index 5469a814927..64c62c33bd4 100644 --- a/src/theme/BlogPostItem/Header/Title/styles.module.css +++ b/src/theme/BlogPostItem/Header/Title/styles.module.css @@ -9,18 +9,5 @@ font: var(--font-body-0); font-weight: 500; margin: 0; -} - -.highlight { - color: var(--logto-link-color); -} - -/** - Blog post title should be smaller on smaller devices -**/ -@media (max-width: 576px) { - .listTitle { - font-size: 1.5rem; - line-height: 40px; - } + color: var(--logto-color-text); } diff --git a/src/theme/BlogPostItem/Header/TitleWithHighlights/index.module.scss b/src/theme/BlogPostItem/Header/TitleWithHighlights/index.module.scss new file mode 100644 index 00000000000..75c7f290c0b --- /dev/null +++ b/src/theme/BlogPostItem/Header/TitleWithHighlights/index.module.scss @@ -0,0 +1,3 @@ +.highlight { + color: var(--logto-link-color); +} diff --git a/src/theme/BlogPostItem/Header/TitleWithHighlights/index.tsx b/src/theme/BlogPostItem/Header/TitleWithHighlights/index.tsx new file mode 100644 index 00000000000..e9e7f02f601 --- /dev/null +++ b/src/theme/BlogPostItem/Header/TitleWithHighlights/index.tsx @@ -0,0 +1,44 @@ +import { type PropBlogPostMetadata } from '@docusaurus/plugin-content-blog'; +import { condString } from '@silverhand/essentials'; + +import styles from './index.module.scss'; + +type Props = { + readonly metadata: PropBlogPostMetadata; +}; + +/** + * Escape potential parentheses in the SDK / connector name. + * The result will be used for the regex that splits the title into parts. + */ +const normalizeName = (name: string) => name.replaceAll('(', '\\(').replaceAll(')', '\\)'); + +const TitleWithHighlights = ({ metadata }: Props) => { + const { frontMatter, title } = metadata; + const sdkName = condString('sdk' in frontMatter && frontMatter.sdk); + const connectorName = condString('connector' in frontMatter && frontMatter.connector); + + const titleParts = title + .split(new RegExp(`(${normalizeName(connectorName)}|${normalizeName(sdkName)})`, 'g')) + .filter(Boolean); + + return titleParts.map((part) => { + if (part === sdkName) { + return ( + + {part} + + ); + } + if (part === connectorName) { + return ( + + {part} + + ); + } + return part; + }); +}; + +export default TitleWithHighlights; diff --git a/src/theme/BlogPostItem/Header/TitleWithSelectionDropdown/index.module.scss b/src/theme/BlogPostItem/Header/TitleWithSelectionDropdown/index.module.scss index 47e46be5048..778c06f017b 100644 --- a/src/theme/BlogPostItem/Header/TitleWithSelectionDropdown/index.module.scss +++ b/src/theme/BlogPostItem/Header/TitleWithSelectionDropdown/index.module.scss @@ -27,20 +27,3 @@ user-select: none; } } - -/* Hide dropdown anchors from title on smaller devices */ -@media (max-width: 576px) { - .dropdownAnchor { - padding: 0; - outline: none; - cursor: default; - - &::after { - display: none; - } - - &.active { - outline: none; - } - } -} diff --git a/src/theme/BlogPostItem/Header/TitleWithSelectionDropdown/index.tsx b/src/theme/BlogPostItem/Header/TitleWithSelectionDropdown/index.tsx index 2c6ceb600fe..eb6ee4df5e3 100644 --- a/src/theme/BlogPostItem/Header/TitleWithSelectionDropdown/index.tsx +++ b/src/theme/BlogPostItem/Header/TitleWithSelectionDropdown/index.tsx @@ -20,7 +20,6 @@ import Dropdown from '../SelectionDropdown'; import styles from './index.module.scss'; type BlogPostProps = { - readonly title: string; readonly metadata: PropBlogPostMetadata; }; @@ -53,7 +52,7 @@ const normalizeName = (name: string) => name.replaceAll('(', '\\(').replaceAll(')', '\\)').replaceAll('$', '\\$'); const TitleWithSelectionDropdown = (props: Props) => { - const isBlogPost = 'title' in props; + const isBlogPost = 'metadata' in props; const { onSelectSdk, onSelectConnector } = props; const listViewProps = conditional(!isBlogPost && props); const blogPostProps = conditional(isBlogPost && props); @@ -90,7 +89,7 @@ const TitleWithSelectionDropdown = (props: Props) => { : getConnectorDisplayName(defaultConnector); if (blogPostProps && (!sdkName || !connectorName)) { - return blogPostProps.title; + return blogPostProps.metadata.title; } const listViewTitle = translate({ @@ -100,12 +99,12 @@ const TitleWithSelectionDropdown = (props: Props) => { .replace(sdkTemplateSlot, sdkName || sdkTemplateSlot) .replace(connectorTemplateSlot, connectorName || connectorTemplateSlot); - const normalizedTitle = blogPostProps?.title ?? listViewTitle; + const normalizedTitle = blogPostProps?.metadata.title ?? listViewTitle; const titleParts = normalizedTitle .split( new RegExp( - `(${normalizeName(sdkName || sdkTemplateSlot)}|${normalizeName(connectorName || connectorTemplateSlot)})`, + `(${normalizeName(connectorName || connectorTemplateSlot)}|${normalizeName(sdkName || sdkTemplateSlot)})`, 'g' ) ) @@ -166,7 +165,7 @@ const TitleWithSelectionDropdown = (props: Props) => { > {part === sdkTemplateSlot ? ( - Your SDK + your SDK ) : ( part @@ -194,7 +193,7 @@ const TitleWithSelectionDropdown = (props: Props) => { > {part === connectorTemplateSlot ? ( - Your provider + your provider ) : ( part @@ -221,7 +220,13 @@ const TitleWithSelectionDropdown = (props: Props) => { onClose={() => { setIsDropdownOpen(undefined); }} - onReset={cond(!isBlogPost && onSelectSdk)} + onReset={cond( + !isBlogPost && + (() => { + onSelectSdk?.(undefined); + setIsDropdownOpen(undefined); + }) + )} /> { onClose={() => { setIsDropdownOpen(undefined); }} - onReset={cond(!isBlogPost && onSelectConnector)} + onReset={cond( + !isBlogPost && + (() => { + onSelectConnector?.(undefined); + setIsDropdownOpen(undefined); + }) + )} /> ); diff --git a/src/theme/BlogPostItem/index.module.scss b/src/theme/BlogPostItem/index.module.scss index 8c5f3a4d900..b5b21d9376e 100644 --- a/src/theme/BlogPostItem/index.module.scss +++ b/src/theme/BlogPostItem/index.module.scss @@ -5,5 +5,31 @@ border-radius: 12px; box-shadow: var(--logto-shadow-1); padding: 20px; - width: 100%; + width: 100%; + transition: background-color var(--ifm-transition-fast) ease; + + &:hover { + background-color: var(--logto-container-info-bg); + } +} + +.logo { + display: flex; + align-items: center; + justify-content: center; + width: 60px; + height: 60px; + margin-inline-end: 12px; + border-radius: 10px; + background: var(--logto-container-neutral-bg); + flex-shrink: 0; + + img { + width: 36px; + height: 36px; + } + + + .logo { + margin-inline-end: 28px; + } } diff --git a/src/theme/BlogPostItem/index.tsx b/src/theme/BlogPostItem/index.tsx index 70e975a7533..cef0d1ee541 100644 --- a/src/theme/BlogPostItem/index.tsx +++ b/src/theme/BlogPostItem/index.tsx @@ -1,11 +1,18 @@ import { useBlogPost } from '@docusaurus/plugin-content-blog/client'; +import { type DocMetadata } from '@docusaurus/plugin-content-docs'; +import { condString } from '@silverhand/essentials'; import type { Props } from '@theme/BlogPostItem'; import BlogPostItemContainer from '@theme/BlogPostItem/Container'; import BlogPostItemContent from '@theme/BlogPostItem/Content'; import BlogPostItemFooter from '@theme/BlogPostItem/Footer'; import BlogPostItemHeader from '@theme/BlogPostItem/Header'; +import ThemedImage from '@theme/ThemedImage'; import clsx from 'clsx'; +import { getConnectorDisplayName, getSdkDisplayName } from '@site/plugins/tutorial-generator/utils'; +import useCategorizedTutorialMetadata from '@site/src/hooks/use-categorized-tutorial-metadata'; +import { useCurrentLocalePrefix } from '@site/src/hooks/useCurrentLocalePrefix'; + import styles from './index.module.scss'; // Apply a bottom margin in list view - Charles commented out @@ -14,12 +21,75 @@ import styles from './index.module.scss'; // return !isBlogPostPage ? 'margin-bottom--xl' : undefined; // } +const getLogoFilenames = (locale: string, data?: DocMetadata) => { + const lastSegmentInSlug = data?.slug.slice(data.slug.lastIndexOf('/') + 1) ?? ''; + const logoFilename = condString( + data?.frontMatter.sidebar_custom_props?.logoFilename ?? lastSegmentInSlug + '.svg' + ); + const darkLogoFilename = condString( + data?.frontMatter.sidebar_custom_props?.darkLogoFilename ?? logoFilename + ); + + return { + logoFilename: `${locale}/img/logo/${logoFilename}`, + darkLogoFilename: `${locale}/img/logo/${darkLogoFilename}`, + fallbackLogoFilename: `${locale}/img/logo/broken-image.svg`, + }; +}; + export default function BlogPostItem({ children, className }: Props): JSX.Element { - const { isBlogPostPage } = useBlogPost(); + const locale = useCurrentLocalePrefix(); + const { isBlogPostPage, frontMatter } = useBlogPost(); + const { allConnectors, allSdks } = useCategorizedTutorialMetadata(); + + const isGeneratedTutorial = frontMatter.slug?.startsWith('how-to-build-'); + const isTutorialListView = isGeneratedTutorial && !isBlogPostPage; + + const blogSdkName = condString('sdk' in frontMatter && frontMatter.sdk); + const blogConnectorName = condString('connector' in frontMatter && frontMatter.connector); + + const connectorMetadata = allConnectors.find( + (data) => getConnectorDisplayName(data) === blogConnectorName + ); + const sdkMetadata = allSdks.find((data) => getSdkDisplayName(data) === blogSdkName); + + const sdkLogos = getLogoFilenames(locale, sdkMetadata); + const connectorLogos = getLogoFilenames(locale, connectorMetadata); + return ( + {isTutorialListView && ( + <> +
+ { + // eslint-disable-next-line @silverhand/fp/no-mutation + currentTarget.src = connectorLogos.fallbackLogoFilename; + }} + /> +
+
+ { + // eslint-disable-next-line @silverhand/fp/no-mutation + currentTarget.src = sdkLogos.fallbackLogoFilename; + }} + /> +
+ + )} {children} diff --git a/src/theme/BlogPostItems/index.module.scss b/src/theme/BlogPostItems/index.module.scss new file mode 100644 index 00000000000..28d87bd27e2 --- /dev/null +++ b/src/theme/BlogPostItems/index.module.scss @@ -0,0 +1,3 @@ +.link:hover { + text-decoration: none; +} diff --git a/src/theme/BlogPostItems/index.tsx b/src/theme/BlogPostItems/index.tsx index be7e4f884e7..9cec965cbe5 100644 --- a/src/theme/BlogPostItems/index.tsx +++ b/src/theme/BlogPostItems/index.tsx @@ -3,6 +3,8 @@ import { BlogPostProvider } from '@docusaurus/plugin-content-blog/client'; import BlogPostItem from '@theme/BlogPostItem'; import type { Props } from '@theme/BlogPostItems'; +import styles from './index.module.scss'; + export default function BlogPostItems({ items, component: BlogPostItemComponent = BlogPostItem, @@ -11,7 +13,7 @@ export default function BlogPostItems({ <> {items.map(({ content: BlogPostContent }) => ( - +