Handle blog series

This commit is contained in:
2024-11-09 20:23:31 -05:00
parent 7580df1dd4
commit 7426890685
13 changed files with 572 additions and 130 deletions

View File

@ -1,6 +1,8 @@
:root {
--ifm-container-width: 1280px;
--ifm-container-width-xl: 1440px;
--ifm-footer-padding-vertical: .5rem;
--ifm-spacing-horizontal: .8rem;
}
.header-github-link:hover {

View File

@ -1,29 +0,0 @@
import React from 'react';
import clsx from 'clsx';
import Layout from '@theme/Layout';
import BlogSidebar from '@theme/BlogSidebar';
import type {Props} from '@theme/BlogLayout';
export default function BlogLayout(props: Props): JSX.Element {
const {sidebar, toc, children, ...layoutProps} = props;
const hasSidebar = sidebar && sidebar.items.length > 0;
return (
<Layout {...layoutProps}>
<div className="container margin-vert--lg">
<div className="row">
<BlogSidebar sidebar={sidebar} />
<main
className={clsx('col', {
'col--8': hasSidebar,
'col--10 col--offset-1': !hasSidebar,
})}>
{children}
</main>
{toc && <div className="col col--2">{toc}</div>}
</div>
</div>
</Layout>
);
}

View File

@ -1,3 +1,7 @@
/**
* Docusaurus typically puts the newer post on the left button,
* and older posts on the right. This file exists to swap them.
*/
import React from 'react';
import Translate, {translate} from '@docusaurus/Translate';
import PaginatorNavLink from '@theme/PaginatorNavLink';

View File

@ -0,0 +1,120 @@
/**
* Use post titles to infer blog post series
*/
import React, { memo, type ReactNode } from 'react';
import Heading, { HeadingType } from '@theme/Heading';
import type { Props } from '@theme/BlogSidebar/Content';
import { BlogSidebarItem } from '@docusaurus/plugin-content-blog';
function BlogSidebarGroup({ title, headingType, children }: { title: string, headingType: HeadingType, children: ReactNode }) {
return (
<div role="group">
<Heading as={headingType}>
{title}
</Heading>
{children}
</div>
);
}
function groupBySeries(items: BlogSidebarItem[], ListComponent: Props["ListComponent"]) {
var returnItems = [];
var seriesItems: BlogSidebarItem[] = [];
function flushSeries() {
if (seriesItems.length === 0) {
return;
}
const seriesTitle = seriesItems[0].title.split(":")[0];
// Strip the series name from the titles
seriesItems = seriesItems.map(item => {
return {
...item,
title: item.title.split(":")[1].trim(),
}
});
// Reverse the display ordering - normally blog items are shown in descending time order,
// but for a series, we want to show ascending order
seriesItems = seriesItems.reverse();
returnItems.push(<>
<BlogSidebarGroup title={seriesTitle} headingType='h4'>
<ul>
<ListComponent items={seriesItems} />
</ul>
</BlogSidebarGroup>
</>);
seriesItems = [];
}
for (const item of items) {
// If this item is part of a series, begin accumulating
if (item.title.includes(":")) {
seriesItems.push(item);
continue;
}
flushSeries();
returnItems.push(<ListComponent items={[item]} />);
}
flushSeries();
return returnItems;
}
function groupByYear(items: BlogSidebarItem[], ListComponent: Props["ListComponent"]) {
var returnItems = [];
var yearItems: BlogSidebarItem[] = [];
function flushSeries() {
if (yearItems.length === 0) {
return;
}
const yearTitle = new Date(yearItems[0].date).getFullYear();
const yearItemsGrouped = groupBySeries(yearItems, ListComponent);
returnItems.push(<>
<BlogSidebarGroup title={String(yearTitle)} headingType='h3'>
{yearItemsGrouped}
</BlogSidebarGroup>
</>);
yearItems = [];
}
for (const item of items) {
if (yearItems.length === 0) {
yearItems.push(item);
continue;
}
const itemYear = new Date(item.date).getFullYear();
const currentYear = new Date(yearItems[0].date).getFullYear();
if (itemYear !== currentYear) {
flushSeries();
}
yearItems.push(item);
}
flushSeries();
return returnItems;
}
function BlogSidebarContent({
items,
yearGroupHeadingClassName,
ListComponent,
}: Props): ReactNode {
return groupByYear(items, ListComponent);
}
export default memo(BlogSidebarContent);

View File

@ -1,50 +0,0 @@
import React, {memo} from 'react';
import clsx from 'clsx';
import {translate} from '@docusaurus/Translate';
import {
useVisibleBlogSidebarItems,
BlogSidebarItemList,
} from '@docusaurus/plugin-content-blog/client';
import BlogSidebarContent from '@theme/BlogSidebar/Content';
import type {Props as BlogSidebarContentProps} from '@theme/BlogSidebar/Content';
import type {Props} from '@theme/BlogSidebar/Desktop';
import styles from './styles.module.css';
const ListComponent: BlogSidebarContentProps['ListComponent'] = ({items}) => {
return (
<BlogSidebarItemList
items={items}
ulClassName={clsx(styles.sidebarItemList, 'clean-list')}
liClassName={styles.sidebarItem}
linkClassName={styles.sidebarItemLink}
linkActiveClassName={styles.sidebarItemLinkActive}
/>
);
};
function BlogSidebarDesktop({sidebar}: Props) {
const items = useVisibleBlogSidebarItems(sidebar.items);
return (
<aside className="col col--2">
<nav
className={clsx(styles.sidebar, 'thin-scrollbar')}
aria-label={translate({
id: 'theme.blog.sidebar.navAriaLabel',
message: 'Blog recent posts navigation',
description: 'The ARIA label for recent posts in the blog sidebar',
})}>
<div className={clsx(styles.sidebarItemTitle, 'margin-bottom--md')}>
{sidebar.title}
</div>
<BlogSidebarContent
items={items}
ListComponent={ListComponent}
yearGroupHeadingClassName={styles.yearGroupHeading}
/>
</nav>
</aside>
);
}
export default memo(BlogSidebarDesktop);

View File

@ -1,45 +0,0 @@
.sidebar {
max-height: calc(100vh - (var(--ifm-navbar-height) + 2rem));
overflow-y: auto;
position: sticky;
top: calc(var(--ifm-navbar-height) + 2rem);
padding-right: 1px;
border-right: 1px solid var(--ifm-toc-border-color);
}
.sidebarItemTitle {
font-size: var(--ifm-h3-font-size);
font-weight: var(--ifm-font-weight-bold);
}
.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;
}
}
.yearGroupHeading {
margin-top: 1.6rem;
margin-bottom: 0.4rem;
}