Skip to content

Commit 1f8a694

Browse files
feat: blog sidebar grouped by year (#587)
* feat: add patch package dependency * feat: add patch to docusaurus * feat: add swizzle for BlogSidebar * build: add yarn script to make sure patch builds * docs: update readme for swizzled components * refactor: update blog count to ALL * docs: update README with Erick's feedback <3
1 parent 38c0c04 commit 1f8a694

File tree

10 files changed

+449
-32
lines changed

10 files changed

+449
-32
lines changed

docusaurus.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ const config: Config = {
224224
},
225225
blog: {
226226
// See `node_modules/@docusaurus/plugin-content-blog/src/pluginOptionSchema.ts` for full undocumented options
227-
blogSidebarCount: 50,
227+
blogSidebarCount: 'ALL',
228228
blogSidebarTitle: 'Latest posts',
229229
blogTitle: `Electron's blog`,
230230
blogDescription: `Keep up to date with what's going on with the Electron project`,

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
"lint": "npx tsc --noEmit && prettier . --check && npm run lint:eslint && npm run lint:markdown",
2828
"lint:fix": "prettier . --write && npm run lint:eslint --fix && npm run lint:markdown --fix",
2929
"pre-build": "npx tsx ./scripts/pre-build.ts",
30-
"prepare": "husky install"
30+
"prepare": "husky install",
31+
"heroku-cleanup": "npx patch-package",
32+
"postinstall": "patch-package"
3133
},
3234
"dependencies": {
3335
"@docusaurus/core": "3.4.0",
@@ -86,6 +88,8 @@
8688
"make-dir": "^3.1.0",
8789
"mdast-util-frontmatter": "^2.0.1",
8890
"mdast-util-to-string": "^2.0.0",
91+
"patch-package": "^8.0.0",
92+
"postinstall-postinstall": "^2.1.0",
8993
"prettier": "^2.8.0",
9094
"remark": "^15.0.0",
9195
"remark-gfm": "^4.0.0",
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
diff --git a/node_modules/@docusaurus/plugin-content-blog/lib/routes.js b/node_modules/@docusaurus/plugin-content-blog/lib/routes.js
2+
index 3400a26..98b1cd8 100644
3+
--- a/node_modules/@docusaurus/plugin-content-blog/lib/routes.js
4+
+++ b/node_modules/@docusaurus/plugin-content-blog/lib/routes.js
5+
@@ -41,6 +41,7 @@ async function buildAllRoutes({ baseUrl, content, actions, options, aliasedSourc
6+
title: blogPost.metadata.title,
7+
permalink: blogPost.metadata.permalink,
8+
unlisted: blogPost.metadata.unlisted,
9+
+ date: blogPost.metadata.date,
10+
})),
11+
};
12+
const modulePath = await createData(`blog-post-list-prop-${pluginId}.json`, sidebar);
13+
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
14+
index f4d4f13..f56deee 100644
15+
--- a/node_modules/@docusaurus/plugin-content-blog/src/plugin-content-blog.d.ts
16+
+++ b/node_modules/@docusaurus/plugin-content-blog/src/plugin-content-blog.d.ts
17+
@@ -469,6 +469,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
18+
title: string;
19+
permalink: string;
20+
unlisted: boolean;
21+
+ date: Date | string;
22+
};
23+
24+
export type BlogSidebar = {
25+
diff --git a/node_modules/@docusaurus/plugin-content-blog/src/routes.ts b/node_modules/@docusaurus/plugin-content-blog/src/routes.ts
26+
index a810ce1..2bf5cee 100644
27+
--- a/node_modules/@docusaurus/plugin-content-blog/src/routes.ts
28+
+++ b/node_modules/@docusaurus/plugin-content-blog/src/routes.ts
29+
@@ -94,6 +94,7 @@ export async function buildAllRoutes({
30+
title: blogPost.metadata.title,
31+
permalink: blogPost.metadata.permalink,
32+
unlisted: blogPost.metadata.unlisted,
33+
+ date: blogPost.metadata.date
34+
})),
35+
};
36+
const modulePath = await createData(
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React from 'react';
2+
import clsx from 'clsx';
3+
import Link from '@docusaurus/Link';
4+
import { translate } from '@docusaurus/Translate';
5+
import type { Props } from '@theme/BlogSidebar/Desktop';
6+
import styles from './styles.module.css';
7+
8+
const SidebarHeader = ({ title }: { title: string }) => (
9+
<div className={clsx(styles.sidebarHeader, 'margin-bottom--md')}>{title}</div>
10+
);
11+
12+
const YearHeader = ({ year }: { year: number }) => (
13+
<h5 className={styles.sidebarItemTitle}>{year}</h5>
14+
);
15+
16+
const SidebarItem = ({ item }: { item: Props['sidebar']['items'][number] }) => (
17+
<li className={styles.sidebarItem}>
18+
<Link
19+
isNavLink
20+
to={item.permalink}
21+
className={styles.sidebarItemLink}
22+
activeClassName={styles.sidebarItemLinkActive}
23+
>
24+
{item.title}
25+
</Link>
26+
</li>
27+
);
28+
29+
export default function BlogSidebarDesktop({ sidebar }: Props) {
30+
let currentYear = null;
31+
32+
return (
33+
<aside className="col col--3">
34+
<nav
35+
className={clsx(styles.sidebar, 'thin-scrollbar')}
36+
aria-label={translate({
37+
id: 'theme.blog.sidebar.navAriaLabel',
38+
message: 'Blog recent posts navigation',
39+
description: 'The ARIA label for recent posts in the blog sidebar',
40+
})}
41+
>
42+
<SidebarHeader title={sidebar.title} />
43+
<ul className={clsx(styles.sidebarItemList, 'clean-list')}>
44+
{sidebar.items.map((item) => {
45+
const itemYear = new Date(item.date).getFullYear();
46+
const yearHeader = currentYear !== itemYear && (
47+
<YearHeader key={itemYear} year={itemYear} />
48+
);
49+
currentYear = itemYear;
50+
51+
return (
52+
<React.Fragment key={item.permalink}>
53+
{yearHeader}
54+
<SidebarItem item={item} />
55+
</React.Fragment>
56+
);
57+
})}
58+
</ul>
59+
</nav>
60+
</aside>
61+
);
62+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
.sidebar {
2+
max-height: calc(100vh - (var(--ifm-navbar-height) + 2rem));
3+
overflow-y: auto;
4+
position: sticky;
5+
top: var(--ifm-navbar-height);
6+
padding: 20px 12px 0 0;
7+
margin-left: -20px;
8+
}
9+
10+
.sidebarHeader {
11+
font-size: var(--ifm-h4-font-size);
12+
font-weight: var(--ifm-font-weight-bold);
13+
padding-left: 12px;
14+
display: block;
15+
}
16+
17+
.sidebarItemTitle {
18+
margin: 0.75rem 0 0.5rem;
19+
color: var(--subtle);
20+
padding-left: 12px;
21+
border-bottom: 0.01rem solid var(--ifm-table-border-color);
22+
padding-bottom: 4px;
23+
}
24+
25+
.sidebarItemList {
26+
font-size: 13px;
27+
}
28+
29+
.sidebarItem {
30+
margin-top: 0.1rem;
31+
line-height: 18px;
32+
}
33+
34+
.sidebarItemLink {
35+
color: var(--ifm-font-color-base);
36+
padding: 4px 8px;
37+
display: block;
38+
border-left: 4px solid transparent;
39+
border-radius: 0.25rem;
40+
line-height: 18px;
41+
}
42+
43+
.sidebarItemLink:hover {
44+
background: var(--ifm-menu-color-background-active);
45+
color: var(--ifm-font-color-base);
46+
text-decoration: none;
47+
}
48+
49+
.sidebarItemLinkActive {
50+
color: var(--ifm-font-color-base);
51+
background: var(--ifm-menu-color-background-active);
52+
border-left-color: var(--ifm-menu-color-active);
53+
font-weight: 700;
54+
}
55+
56+
@media (max-width: 996px) {
57+
.sidebar {
58+
display: none;
59+
}
60+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from 'react';
2+
import Link from '@docusaurus/Link';
3+
import { NavbarSecondaryMenuFiller } from '@docusaurus/theme-common';
4+
import styles from './styles.module.css';
5+
import clsx from 'clsx';
6+
import type { Props } from '@theme/BlogSidebar/Mobile';
7+
8+
export const SidebarHeader = ({ title }: { title: string }) => (
9+
<div className={clsx(styles.sidebarHeader, 'margin-bottom--md')}>{title}</div>
10+
);
11+
12+
export const YearHeader = ({ year }: { year: number }) => (
13+
<h5 className={styles.sidebarItemTitle}>{year}</h5>
14+
);
15+
16+
export const SidebarItem = ({
17+
item,
18+
}: {
19+
item: Props['sidebar']['items'][number];
20+
}) => (
21+
<li className="menu__list-item">
22+
<Link
23+
isNavLink
24+
to={item.permalink}
25+
className="menu__link"
26+
activeClassName="menu__link--active"
27+
>
28+
{item.title}
29+
</Link>
30+
</li>
31+
);
32+
33+
function BlogSidebarMobileSecondaryMenu({ sidebar }: Props) {
34+
let currentYear = null;
35+
36+
return (
37+
<ul className="menu__list blog-menu__list">
38+
{sidebar.items.map((item) => {
39+
const itemYear = new Date(item.date).getFullYear();
40+
const yearHeader = currentYear !== itemYear && (
41+
<YearHeader key={itemYear} year={itemYear} />
42+
);
43+
currentYear = itemYear;
44+
return (
45+
<React.Fragment key={item.permalink}>
46+
{yearHeader}
47+
<SidebarItem item={item} />
48+
</React.Fragment>
49+
);
50+
})}
51+
</ul>
52+
);
53+
}
54+
export default function BlogSidebarMobile(props) {
55+
return (
56+
<NavbarSecondaryMenuFiller
57+
component={BlogSidebarMobileSecondaryMenu}
58+
props={props}
59+
/>
60+
);
61+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.sidebarItemTitle {
2+
margin: 0.75rem 0 0.5rem;
3+
color: var(--subtle);
4+
padding-left: 12px;
5+
border-bottom: 0.01rem solid var(--ifm-table-border-color);
6+
padding-bottom: 4px;
7+
}

src/theme/BlogSidebar/index.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react';
2+
import { useWindowSize } from '@docusaurus/theme-common';
3+
import BlogSidebarDesktop from '@theme/BlogSidebar/Desktop';
4+
import BlogSidebarMobile from '@theme/BlogSidebar/Mobile';
5+
import type { Props } from '@theme/BlogSidebar';
6+
7+
export default function BlogSidebar({ sidebar }: Props): JSX.Element | null {
8+
const windowSize = useWindowSize();
9+
if (!sidebar?.items.length) {
10+
return null;
11+
}
12+
// Mobile sidebar doesn't need to be server-rendered
13+
if (windowSize === 'mobile') {
14+
return <BlogSidebarMobile sidebar={sidebar} />;
15+
}
16+
return <BlogSidebarDesktop sidebar={sidebar} />;
17+
}

src/theme/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ guide in the Docusaurus documentation.
1111

1212
## List of Swizzled components
1313

14+
### `BlogSidebar` (unsafe)
15+
16+
To enhance the functionality of our blog sidebar, we [patched Docusaurus](https://github.com/electron/website/pull/587/files#diff-e196318ff66c78116a07aaa92f5eb7191cf888f36bd942e609a12ff75167f9ed)
17+
to extract the publication date of each blog post from its YAML frontmatter
18+
in the sidebar prop passed to `BlogSidebar` component. Utilizing this
19+
date, we can then group the posts by year, in both desktop and mobile.
20+
This improves the navigational experience for users by making it easier
21+
for them to find content from specific years.
22+
1423
### `DocSidebarItem` (unsafe)
1524

1625
Electron has a lot of platform-specific APIs, and guides to go along with them.

0 commit comments

Comments
 (0)