Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: blog sidebar grouped by year #587

Merged
merged 7 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
"lint": "npx tsc --noEmit && prettier . --check && npm run lint:eslint && npm run lint:markdown",
"lint:fix": "prettier . --write && npm run lint:eslint --fix && npm run lint:markdown --fix",
"pre-build": "npx tsx ./scripts/pre-build.ts",
"prepare": "husky install"
"prepare": "husky install",
"heroku-cleanup": "npx patch-package",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For other reviewers, this is necessary to ensure the patches get applied correctly in the Heroku environment, which installs dependencies a bit differently.

"postinstall": "patch-package"
},
"dependencies": {
"@docusaurus/core": "3.4.0",
Expand Down Expand Up @@ -86,6 +88,8 @@
"make-dir": "^3.1.0",
"mdast-util-frontmatter": "^2.0.1",
"mdast-util-to-string": "^2.0.0",
"patch-package": "^8.0.0",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.8.0",
"remark": "^15.0.0",
"remark-gfm": "^4.0.0",
Expand Down
36 changes: 36 additions & 0 deletions patches/@docusaurus+plugin-content-blog+3.4.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
diff --git a/node_modules/@docusaurus/plugin-content-blog/lib/routes.js b/node_modules/@docusaurus/plugin-content-blog/lib/routes.js
index 3400a26..98b1cd8 100644
--- a/node_modules/@docusaurus/plugin-content-blog/lib/routes.js
+++ b/node_modules/@docusaurus/plugin-content-blog/lib/routes.js
@@ -41,6 +41,7 @@ async function buildAllRoutes({ baseUrl, content, actions, options, aliasedSourc
title: blogPost.metadata.title,
permalink: blogPost.metadata.permalink,
unlisted: blogPost.metadata.unlisted,
+ date: blogPost.metadata.date,
})),
};
const modulePath = await createData(`blog-post-list-prop-${pluginId}.json`, sidebar);
diff --git a/node_modules/@docusaurus/plugin-content-blog/src/plugin-content-blog.d.ts b/node_modules/@docusaurus/plugin-content-blog/src/plugin-content-blog.d.ts
index f4d4f13..f56deee 100644
--- a/node_modules/@docusaurus/plugin-content-blog/src/plugin-content-blog.d.ts
+++ b/node_modules/@docusaurus/plugin-content-blog/src/plugin-content-blog.d.ts
@@ -469,6 +469,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
title: string;
permalink: string;
unlisted: boolean;
+ date: Date | string;
};

export type BlogSidebar = {
diff --git a/node_modules/@docusaurus/plugin-content-blog/src/routes.ts b/node_modules/@docusaurus/plugin-content-blog/src/routes.ts
index a810ce1..2bf5cee 100644
--- a/node_modules/@docusaurus/plugin-content-blog/src/routes.ts
+++ b/node_modules/@docusaurus/plugin-content-blog/src/routes.ts
@@ -94,6 +94,7 @@ export async function buildAllRoutes({
title: blogPost.metadata.title,
permalink: blogPost.metadata.permalink,
unlisted: blogPost.metadata.unlisted,
+ date: blogPost.metadata.date
})),
};
const modulePath = await createData(
62 changes: 62 additions & 0 deletions src/theme/BlogSidebar/Desktop/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import { translate } from '@docusaurus/Translate';
import type { Props } from '@theme/BlogSidebar/Desktop';
import styles from './styles.module.css';

const SidebarHeader = ({ title }: { title: string }) => (
<div className={clsx(styles.sidebarHeader, 'margin-bottom--md')}>{title}</div>
);

const YearHeader = ({ year }: { year: number }) => (
<h5 className={styles.sidebarItemTitle}>{year}</h5>
);

const SidebarItem = ({ item }: { item: Props['sidebar']['items'][number] }) => (
<li className={styles.sidebarItem}>
<Link
isNavLink
to={item.permalink}
className={styles.sidebarItemLink}
activeClassName={styles.sidebarItemLinkActive}
>
{item.title}
</Link>
</li>
);

export default function BlogSidebarDesktop({ sidebar }: Props) {
let currentYear = null;

return (
<aside className="col col--3">
<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',
})}
>
<SidebarHeader title={sidebar.title} />
<ul className={clsx(styles.sidebarItemList, 'clean-list')}>
{sidebar.items.map((item) => {
const itemYear = new Date(item.date).getFullYear();
const yearHeader = currentYear !== itemYear && (
<YearHeader key={itemYear} year={itemYear} />
);
currentYear = itemYear;

return (
<React.Fragment key={item.permalink}>
{yearHeader}
<SidebarItem item={item} />
</React.Fragment>
);
})}
</ul>
</nav>
</aside>
);
}
60 changes: 60 additions & 0 deletions src/theme/BlogSidebar/Desktop/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.sidebar {
max-height: calc(100vh - (var(--ifm-navbar-height) + 2rem));
overflow-y: auto;
position: sticky;
top: var(--ifm-navbar-height);
padding: 20px 12px 0 0;
margin-left: -20px;
}

.sidebarHeader {
font-size: var(--ifm-h4-font-size);
font-weight: var(--ifm-font-weight-bold);
padding-left: 12px;
display: block;
}

.sidebarItemTitle {
margin: 0.75rem 0 0.5rem;
color: var(--subtle);
padding-left: 12px;
border-bottom: 0.01rem solid var(--ifm-table-border-color);
padding-bottom: 4px;
}

.sidebarItemList {
font-size: 13px;
}

.sidebarItem {
margin-top: 0.1rem;
line-height: 18px;
}

.sidebarItemLink {
color: var(--ifm-font-color-base);
padding: 4px 8px;
display: block;
border-left: 4px solid transparent;
border-radius: 0.25rem;
line-height: 18px;
}

.sidebarItemLink:hover {
background: var(--ifm-menu-color-background-active);
color: var(--ifm-font-color-base);
text-decoration: none;
}

.sidebarItemLinkActive {
color: var(--ifm-font-color-base);
background: var(--ifm-menu-color-background-active);
border-left-color: var(--ifm-menu-color-active);
font-weight: 700;
}

@media (max-width: 996px) {
.sidebar {
display: none;
}
}
61 changes: 61 additions & 0 deletions src/theme/BlogSidebar/Mobile/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import Link from '@docusaurus/Link';
import { NavbarSecondaryMenuFiller } from '@docusaurus/theme-common';
import styles from './styles.module.css';
import clsx from 'clsx';
import type { Props } from '@theme/BlogSidebar/Mobile';

export const SidebarHeader = ({ title }: { title: string }) => (
<div className={clsx(styles.sidebarHeader, 'margin-bottom--md')}>{title}</div>
);

export const YearHeader = ({ year }: { year: number }) => (
<h5 className={styles.sidebarItemTitle}>{year}</h5>
);

export const SidebarItem = ({
item,
}: {
item: Props['sidebar']['items'][number];
}) => (
<li className="menu__list-item">
<Link
isNavLink
to={item.permalink}
className="menu__link"
activeClassName="menu__link--active"
>
{item.title}
</Link>
</li>
);

function BlogSidebarMobileSecondaryMenu({ sidebar }: Props) {
let currentYear = null;

return (
<ul className="menu__list blog-menu__list">
{sidebar.items.map((item) => {
const itemYear = new Date(item.date).getFullYear();
const yearHeader = currentYear !== itemYear && (
<YearHeader key={itemYear} year={itemYear} />
);
currentYear = itemYear;
return (
<React.Fragment key={item.permalink}>
{yearHeader}
<SidebarItem item={item} />
</React.Fragment>
);
})}
</ul>
);
}
export default function BlogSidebarMobile(props) {
return (
<NavbarSecondaryMenuFiller
component={BlogSidebarMobileSecondaryMenu}
props={props}
/>
);
}
7 changes: 7 additions & 0 deletions src/theme/BlogSidebar/Mobile/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.sidebarItemTitle {
margin: 0.75rem 0 0.5rem;
color: var(--subtle);
padding-left: 12px;
border-bottom: 0.01rem solid var(--ifm-table-border-color);
padding-bottom: 4px;
}
17 changes: 17 additions & 0 deletions src/theme/BlogSidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import { useWindowSize } from '@docusaurus/theme-common';
import BlogSidebarDesktop from '@theme/BlogSidebar/Desktop';
import BlogSidebarMobile from '@theme/BlogSidebar/Mobile';
import type { Props } from '@theme/BlogSidebar';

export default function BlogSidebar({ sidebar }: Props): JSX.Element | null {
const windowSize = useWindowSize();
if (!sidebar?.items.length) {
return null;
}
// Mobile sidebar doesn't need to be server-rendered
if (windowSize === 'mobile') {
return <BlogSidebarMobile sidebar={sidebar} />;
}
return <BlogSidebarDesktop sidebar={sidebar} />;
}
Loading