From fbd41d395916176dde11bb0e417f1210f34eb4ab Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sat, 23 Mar 2024 18:02:45 +0100 Subject: Add blog Site structure follows SEO tips from https://johnnyreilly.com/how-we-fixed-my-seo * The blog pages have as simple of an URL as possible. To this end, the home page of the site is actually the first index page of the blog. * Customize the blog index page BlogListPage component to show the landing page as the first index page. * Rename /archive to /blog to avoid a dated feel. * Remove the date from post URLs using the slug property. --- src/components/ActiveSectionProvider.tsx | 2 +- src/components/Landing.tsx | 31 ++++++++ src/components/landing/sections/Blog.module.css | 28 ++++++++ src/components/landing/sections/Blog.tsx | 95 +++++++++++++++++++++++++ src/components/landing/sections/Hero.module.css | 10 ++- src/css/custom.css | 8 ++- src/pages/index.tsx | 28 -------- src/plugins/compressionPlugin.ts | 10 +-- src/theme/BlogListPage.tsx | 18 +++++ src/theme/BlogSidebar/Desktop.module.css | 53 ++++++++++++++ src/theme/BlogSidebar/Desktop.tsx | 55 ++++++++++++++ src/theme/Footer/Copyright.jsx | 49 ------------- src/theme/Footer/Copyright.tsx | 50 +++++++++++++ src/theme/NavbarItem/NavbarNavLink.jsx | 89 ----------------------- src/theme/NavbarItem/NavbarNavLink.tsx | 91 +++++++++++++++++++++++ src/theme/PaginatorNavLink.tsx | 17 +++++ src/theme/Root.tsx | 11 +++ src/theme/SiteMetadata.jsx | 28 -------- 18 files changed, 470 insertions(+), 203 deletions(-) create mode 100644 src/components/Landing.tsx create mode 100644 src/components/landing/sections/Blog.module.css create mode 100644 src/components/landing/sections/Blog.tsx delete mode 100644 src/pages/index.tsx create mode 100644 src/theme/BlogListPage.tsx create mode 100644 src/theme/BlogSidebar/Desktop.module.css create mode 100644 src/theme/BlogSidebar/Desktop.tsx delete mode 100644 src/theme/Footer/Copyright.jsx create mode 100644 src/theme/Footer/Copyright.tsx delete mode 100644 src/theme/NavbarItem/NavbarNavLink.jsx create mode 100644 src/theme/NavbarItem/NavbarNavLink.tsx create mode 100644 src/theme/PaginatorNavLink.tsx delete mode 100644 src/theme/SiteMetadata.jsx (limited to 'src') diff --git a/src/components/ActiveSectionProvider.tsx b/src/components/ActiveSectionProvider.tsx index 022dbad..0d491ac 100644 --- a/src/components/ActiveSectionProvider.tsx +++ b/src/components/ActiveSectionProvider.tsx @@ -46,7 +46,7 @@ export default function ActiveSectionProvider({ if (hash === lastHashRef.current) { return; } - // Passing `undefined` to `hash` will give contraol pack to react-router. + // Passing `undefined` to `hash` will give control back to react-router. const newURL = `${location.pathname}${location.search}${ hash ?? location.hash }`; diff --git a/src/components/Landing.tsx b/src/components/Landing.tsx new file mode 100644 index 0000000..5eac99b --- /dev/null +++ b/src/components/Landing.tsx @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2023 Kristóf Marussy + * + * SPDX-License-Identifier: MIT + */ + +import type { Props } from '@theme/BlogListPage'; +import Layout from '@theme/Layout'; + +import Blog from './landing/sections/Blog'; +import Contact from './landing/sections/Contact'; +import Hero from './landing/sections/Hero'; +import Publications from './landing/sections/Publications'; +import Research from './landing/sections/Research'; +import Resume from './landing/sections/Resume'; +import TrackActiveSection from './TrackActiveSection'; + +export default function Home(props: Props) { + return ( + + + + + + + + + + + ); +} diff --git a/src/components/landing/sections/Blog.module.css b/src/components/landing/sections/Blog.module.css new file mode 100644 index 0000000..6a38a97 --- /dev/null +++ b/src/components/landing/sections/Blog.module.css @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2024 Kristóf Marussy + * + * SPDX-License-Identifier: MIT + */ + +.row { + margin-bottom: var(--ifm-paragraph-margin-bottom); +} + +.recent-posts { + margin-bottom: 0; + font-size: var(--ifm-h3-font-size); + font-weight: var(--ifm-font-weight-bold); + --casl: 0.5; + letter-spacing: var(--marussy-heading-tracking); +} + +.date { + font-size: 1rem; + font-weight: var(--ifm-font-weight-normal); + --casl: 0; + letter-spacing: 0; +} + +.prev::after { + content: ' »'; +} diff --git a/src/components/landing/sections/Blog.tsx b/src/components/landing/sections/Blog.tsx new file mode 100644 index 0000000..3ac87db --- /dev/null +++ b/src/components/landing/sections/Blog.tsx @@ -0,0 +1,95 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * Copyright (c) 2024 Kristóf Marussy + * + * SPDX-License-Identifier: MIT + */ + +import Link from '@docusaurus/Link'; +import type { Props } from '@theme/BlogListPage'; +import type { Content } from '@theme/BlogPostPage'; +import Translate from '@docusaurus/Translate'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import clsx from 'clsx'; + +import Section from '@site/src/components/landing/Section'; +import Subtitle from '@site/src/components/landing/Subtitle'; + +import styles from './Blog.module.css'; +import React from 'react'; + +const columnLength = 5; + +function Column({ + items, +}: { + items: { readonly content: Content }[]; +}): React.ReactNode { + // Date time format based on + // https://github.com/facebook/docusaurus/blob/6f17d5493877ba38d8b4e0b0d468f44401375c30/packages/docusaurus-theme-common/src/utils/IntlUtils.ts + const { + i18n: { currentLocale, localeConfigs }, + } = useDocusaurusContext(); + const calendar = localeConfigs[currentLocale]!.calendar; + const dateTimeFormat = new Intl.DateTimeFormat(currentLocale, { + calendar, + day: 'numeric', + month: 'long', + year: 'numeric', + timeZone: 'UTC', + }); + + if (items.length === 0) { + return null; + } + + return ( +
+
    + {items.map(({ content }) => ( +
  • + + {content.metadata.title} + {' '} + + on {dateTimeFormat.format(new Date(content.metadata.date))} + +
  • + ))} +
+
+ ); +} + +export default function Blog(props: Props) { + const { + items, + metadata: { nextPage }, + } = props; + const columnLength = Math.max(1, Math.ceil(items.length / 2)); + return ( +
+
+
+ Recent posts +
+
+ + +
+ {nextPage && ( +

+ + + Older Entries + + +

+ )} +
+
+ ); +} diff --git a/src/components/landing/sections/Hero.module.css b/src/components/landing/sections/Hero.module.css index 67e7d54..0d0e16e 100644 --- a/src/components/landing/sections/Hero.module.css +++ b/src/components/landing/sections/Hero.module.css @@ -11,7 +11,7 @@ --ifm-link-hover-color: var(--ifm-link-color); } -@media (max-width: 996px) { +@media (max-width: 576px) { .hero { padding-top: 2rem; padding-bottom: 0; @@ -65,11 +65,17 @@ font-family: var(--ifm-heading-font-family); font-weight: var(--ifm-heading-font-weight); --casl: 1; - font-size: 2.5rem; + font-size: 3rem; letter-spacing: var(--marussy-heading-tracking); white-space: pre; } +@media (max-width: 576px) { + .introduction__name { + font-size: 2.5rem; + } +} + .cta { display: flex; flex-direction: row; diff --git a/src/css/custom.css b/src/css/custom.css index c2cda19..bd6e179 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -84,11 +84,13 @@ kbd { h1, h2, +.markdown > h3, .button { --casl: 1; } -h3 { +h3, +.markdown > h4 { --casl: 0.5; } @@ -107,3 +109,7 @@ h3 { --casl: 0; --mono: 1; } + +.hero__subtitle { + font-weight: 500; +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx deleted file mode 100644 index 60f86ca..0000000 --- a/src/pages/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 Kristóf Marussy - * - * SPDX-License-Identifier: MIT - */ - -import Layout from '@theme/Layout'; - -import Contact from '@site/src/components/landing/sections/Contact'; -import Hero from '@site/src/components/landing/sections/Hero'; -import Publications from '@site/src/components/landing/sections/Publications'; -import Research from '@site/src/components/landing/sections/Research'; -import Resume from '@site/src/components/landing/sections/Resume'; -import TrackActiveSection from '@site/src/components/TrackActiveSection'; - -export default function Home() { - return ( - - - - - - - - - - ); -} diff --git a/src/plugins/compressionPlugin.ts b/src/plugins/compressionPlugin.ts index 7b9feeb..a5bc402 100644 --- a/src/plugins/compressionPlugin.ts +++ b/src/plugins/compressionPlugin.ts @@ -20,10 +20,9 @@ const test = /\.(js|css|svg|txt)$/; export default function compressionPlugin(): Plugin { return { name: 'marussy-compression-plugin', - configureWebpack: (_config, isServer) => - isServer - ? {} - : { + configureWebpack: ({ mode }) => + mode === 'production' + ? { plugins: [ new CompressionPlugin({ test, @@ -39,6 +38,7 @@ export default function compressionPlugin(): Plugin { minRatio, }), ], - }, + } + : {}, }; } diff --git a/src/theme/BlogListPage.tsx b/src/theme/BlogListPage.tsx new file mode 100644 index 0000000..b8c7e01 --- /dev/null +++ b/src/theme/BlogListPage.tsx @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2024 Kristóf Marussy + * + * SPDX-License-Identifier: MIT + */ + +import React from 'react'; +import type { Props } from '@theme/BlogListPage'; +import BlogListPage from '@theme-original/BlogListPage'; + +import Landing from '@site/src/components/Landing'; + +export default function BlogListPageWrapper(props: Props) { + if (props.metadata.permalink === '/') { + return ; + } + return ; +} diff --git a/src/theme/BlogSidebar/Desktop.module.css b/src/theme/BlogSidebar/Desktop.module.css new file mode 100644 index 0000000..4bac5ba --- /dev/null +++ b/src/theme/BlogSidebar/Desktop.module.css @@ -0,0 +1,53 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * Copyright (c) 2024 Kristóf Marussy + * + * SPDX-License-Identifier: MIT + * + * This file was derived from + * https://github.com/facebook/docusaurus/blob/6f17d5493877ba38d8b4e0b0d468f44401375c30/packages/docusaurus-theme-classic/src/theme/BlogSidebar/Desktop/styles.module.css + * via the `swizzle` mechanism of Docusaurus. + * + * It was modified to change the sidebar title styling. + */ + +.sidebar { + max-height: calc(100vh - (var(--ifm-navbar-height) + 2rem)); + overflow-y: auto; + position: sticky; + top: calc(var(--ifm-navbar-height) + 2rem); +} + +.sidebarItemTitle { + font-size: 1.5rem; + font-weight: var(--ifm-font-weight-bold); + --casl: 1; + letter-spacing: var(--marussy-heading-tracking); +} + +.sidebarItemList { + font-size: 0.9rem; +} + +.sidebarItem { + margin-top: 0.7rem; +} + +.sidebarItemLink { + color: var(--ifm-font-color-base); + display: block; +} + +.sidebarItemLink:hover { + text-decoration: none; +} + +.sidebarItemLinkActive { + color: var(--ifm-color-primary) !important; +} + +@media (max-width: 996px) { + .sidebar { + display: none; + } +} diff --git a/src/theme/BlogSidebar/Desktop.tsx b/src/theme/BlogSidebar/Desktop.tsx new file mode 100644 index 0000000..d06f239 --- /dev/null +++ b/src/theme/BlogSidebar/Desktop.tsx @@ -0,0 +1,55 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * Copyright (c) 2024 Kristóf Marussy + * + * SPDX-License-Identifier: MIT + * + * This file was derived from + * https://github.com/facebook/docusaurus/blob/6f17d5493877ba38d8b4e0b0d468f44401375c30/packages/docusaurus-theme-classic/src/theme/BlogSidebar/Desktop/index.tsx + * via the `swizzle` mechanism of Docusaurus. + * + * It was modified to change the sidebar title styling. + */ + +import Link from '@docusaurus/Link'; +import { translate } from '@docusaurus/Translate'; +import { useVisibleBlogSidebarItems } from '@docusaurus/theme-common/internal'; +import type { Props } from '@theme/BlogSidebar/Desktop'; +import React from 'react'; +import clsx from 'clsx'; + +import styles from './Desktop.module.css'; + +export default function BlogSidebarDesktop({ sidebar }: Props) { + const items = useVisibleBlogSidebarItems(sidebar.items); + return ( + + ); +} diff --git a/src/theme/Footer/Copyright.jsx b/src/theme/Footer/Copyright.jsx deleted file mode 100644 index 648bd42..0000000 --- a/src/theme/Footer/Copyright.jsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * Copyright (c) 2024 Kristóf Marussy - * - * SPDX-License-Identifier: MIT - * - * This file was derived from - * https://github.com/facebook/docusaurus/blob/c745021b01a8b88d34e1d772278d7171ad8acdf5/packages/docusaurus-theme-classic/src/theme/Footer/Copyright/index.tsx - * via the `swizzle` mechanism of Docusaurus. - * - * It was modified to embed JSX content directly in the footer. - */ - -import Link from '@docusaurus/Link'; -import clsx from 'clsx'; -import React from 'react'; - -import { CCLicenseLink, LicenseLink } from '@site/src/components/licenses'; - -import styles from './Copyright.module.css'; - -export default function FooterCopyright({ copyright }) { - return ( -
- Most content on this site is{' '} - - CC-BY-4.0 - - . The{' '} - - code for this site - {' '} - is{' '} - - MIT - - .
- -
- ); -} diff --git a/src/theme/Footer/Copyright.tsx b/src/theme/Footer/Copyright.tsx new file mode 100644 index 0000000..3125c05 --- /dev/null +++ b/src/theme/Footer/Copyright.tsx @@ -0,0 +1,50 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * Copyright (c) 2024 Kristóf Marussy + * + * SPDX-License-Identifier: MIT + * + * This file was derived from + * https://github.com/facebook/docusaurus/blob/cc0bceab9c1678303f6237f5526753edc1b12fc3/packages/docusaurus-theme-classic/src/theme/Footer/Copyright/index.tsx + * via the `swizzle` mechanism of Docusaurus. + * + * It was modified to embed JSX content directly in the footer. + */ + +import Link from '@docusaurus/Link'; +import type { Props } from '@theme/Footer/Copyright'; +import clsx from 'clsx'; +import React from 'react'; + +import { CCLicenseLink, LicenseLink } from '@site/src/components/licenses'; + +import styles from './Copyright.module.css'; + +export default function FooterCopyright({ copyright }: Props) { + return ( +
+ Most content on this site is{' '} + + CC-BY-4.0 + + . The{' '} + + code for this site + {' '} + is{' '} + + MIT + + .
+ +
+ ); +} diff --git a/src/theme/NavbarItem/NavbarNavLink.jsx b/src/theme/NavbarItem/NavbarNavLink.jsx deleted file mode 100644 index 81de931..0000000 --- a/src/theme/NavbarItem/NavbarNavLink.jsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * Copyright (c) 2023 Kristóf Marussy - * - * SPDX-License-Identifier: MIT - * - * This file was derived from - * https://github.com/facebook/docusaurus/blob/c745021b01a8b88d34e1d772278d7171ad8acdf5/packages/docusaurus-theme-classic/src/theme/NavbarItem/NavbarNavLink.tsx - * via the `swizzle` mechanism of Docusaurus. - * - * It was modified to correctly track the change of the active anchor - * when `setHash` from `@site/src/components/ActiveSectionProvider` is called. - */ - -import React from 'react'; -import Link from '@docusaurus/Link'; -import useBaseUrl from '@docusaurus/useBaseUrl'; -import isInternalUrl from '@docusaurus/isInternalUrl'; -import { isRegexpStringMatch } from '@docusaurus/theme-common'; -import IconExternalLink from '@theme/Icon/ExternalLink'; -import { useActiveSection } from '@site/src/components/ActiveSectionProvider'; - -export default function NavbarNavLink({ - activeBasePath, - activeBaseRegex, - to, - href, - label, - html, - isDropdownLink, - prependBaseUrlToHref, - ...props -}) { - const { hash: activeSectionHash } = useActiveSection(); - // TODO all this seems hacky - // {to: 'version'} should probably be forbidden, in favor of {to: '/version'} - const toUrl = useBaseUrl(to); - // Check whether the target URL has a hash. - const hashMatch = toUrl?.match(/#.+$/); - const activeBaseUrl = useBaseUrl(activeBasePath); - const normalizedHref = useBaseUrl(href, { forcePrependBaseUrl: true }); - const isExternalLink = label && href && !isInternalUrl(href); - // Link content is set through html XOR label - const linkContentProps = html - ? { dangerouslySetInnerHTML: { __html: html } } - : { - children: ( - <> - {label} - {isExternalLink && ( - - )} - - ), - }; - if (href) { - return ( - - ); - } - return ( - { - if (activeBaseRegex) { - return isRegexpStringMatch(activeBaseRegex, location.pathname); - } - if (activeBaseUrl) { - return location.pathname.startsWith(activeBaseUrl); - } - // Make sure to only highlight links with a hash when hash also matches. - return ( - !hashMatch || hashMatch[0] === (activeSectionHash ?? location.hash) - ); - }, - })} - {...props} - {...linkContentProps} - /> - ); -} diff --git a/src/theme/NavbarItem/NavbarNavLink.tsx b/src/theme/NavbarItem/NavbarNavLink.tsx new file mode 100644 index 0000000..a9d7b04 --- /dev/null +++ b/src/theme/NavbarItem/NavbarNavLink.tsx @@ -0,0 +1,91 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * Copyright (c) 2023 Kristóf Marussy + * + * SPDX-License-Identifier: MIT + * + * This file was derived from + * https://github.com/facebook/docusaurus/blob/c745021b01a8b88d34e1d772278d7171ad8acdf5/packages/docusaurus-theme-classic/src/theme/NavbarItem/NavbarNavLink.tsx + * via the `swizzle` mechanism of Docusaurus. + * + * It was modified to correctly track the change of the active anchor + * when `setHash` from `@site/src/components/ActiveSectionProvider` is called. + */ + +import React from 'react'; +import Link from '@docusaurus/Link'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import isInternalUrl from '@docusaurus/isInternalUrl'; +import { isRegexpStringMatch } from '@docusaurus/theme-common'; +import IconExternalLink from '@theme/Icon/ExternalLink'; +import type { Props } from '@theme/NavbarItem/NavbarNavLink'; + +import { useActiveSection } from '@site/src/components/ActiveSectionProvider'; + +export default function NavbarNavLink({ + activeBasePath, + activeBaseRegex, + to, + href, + label, + html, + isDropdownLink, + prependBaseUrlToHref, + ...props +}: Props) { + const { hash: activeSectionHash } = useActiveSection(); + // TODO all this seems hacky + // {to: 'version'} should probably be forbidden, in favor of {to: '/version'} + const toUrl = useBaseUrl(to); + // Check whether the target URL has a hash. + const hashMatch = toUrl?.match(/#.+$/); + const activeBaseUrl = useBaseUrl(activeBasePath); + const normalizedHref = useBaseUrl(href, { forcePrependBaseUrl: true }); + const isExternalLink = label && href && !isInternalUrl(href); + // Link content is set through html XOR label + const linkContentProps = html + ? { dangerouslySetInnerHTML: { __html: html } } + : { + children: ( + <> + {label} + {isExternalLink && ( + + )} + + ), + }; + if (href) { + return ( + + ); + } + return ( + { + if (activeBaseRegex) { + return isRegexpStringMatch(activeBaseRegex, location.pathname); + } + if (activeBaseUrl) { + return location.pathname.startsWith(activeBaseUrl); + } + // Make sure to only highlight links with a hash when hash also matches. + return ( + !hashMatch || hashMatch[0] === (activeSectionHash ?? location.hash) + ); + }, + })} + {...props} + {...linkContentProps} + /> + ); +} diff --git a/src/theme/PaginatorNavLink.tsx b/src/theme/PaginatorNavLink.tsx new file mode 100644 index 0000000..d0f03f7 --- /dev/null +++ b/src/theme/PaginatorNavLink.tsx @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2024 Kristóf Marussy + * + * SPDX-License-Identifier: MIT + */ + +import React from 'react'; +import type { Props } from '@theme/PaginatorNavLink'; +import PaginatorNavLink from '@theme-original/PaginatorNavLink'; + +export default function PaginatorNavLinkWrapper(props: Props) { + const modifiedProps = { ...props }; + if (modifiedProps.permalink === '/') { + modifiedProps.permalink = '/#blog'; + } + return ; +} diff --git a/src/theme/Root.tsx b/src/theme/Root.tsx index 9459e9f..32462bb 100644 --- a/src/theme/Root.tsx +++ b/src/theme/Root.tsx @@ -4,14 +4,25 @@ * SPDX-License-Identifier: MIT */ +import Head from '@docusaurus/Head'; import type { Props } from '@theme/Root'; import Root from '@theme-original/Root'; import ActiveSectionProvider from '@site/src/components/ActiveSectionProvider'; +import fontURL from '@site/src/fonts/recursive-latin.woff2?url'; export default function RootWrapper(props: Props) { return ( + + + ); diff --git a/src/theme/SiteMetadata.jsx b/src/theme/SiteMetadata.jsx deleted file mode 100644 index 1639677..0000000 --- a/src/theme/SiteMetadata.jsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 Kristóf Marussy - * - * SPDX-License-Identifier: MIT - */ - -import Head from '@docusaurus/Head'; - -import SiteMetadata from '@theme-original/SiteMetadata'; - -import fontURL from '@site/src/fonts/recursive-latin.woff2?url'; - -export default function SiteMetadataWrapper(props) { - return ( - <> - - - - - - ); -} -- cgit v1.2.3-70-g09d2