mirror of
https://github.com/bspeice/speice.io
synced 2025-07-04 15:26:13 -04:00
Handle blog series
This commit is contained in:
@ -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 {
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
@ -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';
|
||||
|
120
src/theme/BlogSidebar/Content/index.tsx
Normal file
120
src/theme/BlogSidebar/Content/index.tsx
Normal 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);
|
@ -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);
|
@ -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;
|
||||
}
|
Reference in New Issue
Block a user