Skip to main content

Use Docs Layout in Docusaurus Blog

· 4 min read
Ferdinand Su
PhD Student @ HIT-ICES, Founder & Manager @ HIT-ReFreSH, C# developer.

Docusaurus provides a powerful blog plugin as you can see in my site. However, I dislike two points of the docusaurus blog:

  1. I cannot collapse sidebar in blog pages rather than in docs pages.
  2. Too much blank space in maximumized blog pages.

Through swizzling, I migrated Docs layout to the Blog layout, the steps are as following:

To View Origin Issue: Blog sidebar like in Docs · Issue #9593 · facebook/docusaurus (github.com)

Create swizzling components​

Run yarn swizzle @docusaurus/theme-classic BlogLayout --eject --danger to create the component at src\theme\BlogLayout\index.tsx.

Cut long title​

Some blogs' titles can be too long to display in the sidebar, so we have to cut it and replace the tailing words with "..." firstly.

function DisplayLength(str, shrink = null) {
if (shrink === null) {
let length = 0;
for (let i = 0; i < str.length; i++) {
let code = str.charCodeAt(i);
if (code >= 0x4e00 && code <= 0x9fa5) {
length += 2;
} else {
length += 1;
}
}
return length;
}
let ret = "";
let length = 0;
for (let i = 0; i < str.length; i++) {
let code = str.charCodeAt(i);
if (code >= 0x4e00 && code <= 0x9fa5) {
length += 2;
} else {
length += 1;
}
if (length > shrink) break;
ret += str.charAt(i);
}
return ret;
}

Convert BlogSideBar property to DocsSiderBar format​

function BlogSidebarToDocSidebar(sidebarItems) {
function _itemConvert(item) {
const MAX_LENGTH = 27;// You can use something else, or drop it.
return {
type: "link",
label:
DisplayLength(item.title) > MAX_LENGTH
? DisplayLength(item.title, MAX_LENGTH - 3) + "..."
: item.title,
href: item.permalink,
docId: item.permalink.split("/")[-1],
unlisted: item.unlisted,
};
}
const ret = sidebarItems.map(_itemConvert);
return ret;
}

Replace Layout component​

export default function BlogLayout(props) {
const { sidebar, toc, children, ...layoutProps } = props;
const windowSize = useWindowSize();
const hasSidebar = sidebar && sidebar.items.length > 0;
const newItems = BlogSidebarToDocSidebar(sidebar.items);
const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false);
return (
<Layout {...layoutProps}>
<DocsSidebarProvider name="blogSidebar" items={newItems}>
<div className={styles.docsWrapper}>
<BackToTopButton />
<div className={styles.docRoot}>
{sidebar && (
<DocRootLayoutSidebar
sidebar={newItems}
hiddenSidebarContainer={hiddenSidebarContainer}
setHiddenSidebarContainer={setHiddenSidebarContainer}
/>
)}
<DocRootLayoutMain
hiddenSidebarContainer={hiddenSidebarContainer}
>
{children}
</DocRootLayoutMain>
{windowSize !== "mobile" && toc && (
<div className="col col--2">{toc}</div>
)}
</div>
</div>
</DocsSidebarProvider>
</Layout>
);
}

Full Code​

note

I'm using a global site config file at @site/ifers.config instead of constants in the source code. So ifers.blog.sidebarMaxChar can be replaced by MAX_LENGTH constant if you like.

src\theme\BlogLayout\index.tsx
import React, { useState } from "react";
import clsx from "clsx";
import Layout from "@theme/Layout";
import BlogSidebar from "@theme/BlogSidebar";
import {
DocsSidebarProvider,
useDocRootMetadata,
} from "@docusaurus/theme-common/internal";
import { useWindowSize } from "@docusaurus/theme-common";
import styles from "./styles.module.css";
import DocRootLayoutSidebar from "@theme/DocRoot/Layout/Sidebar";
import DocRootLayoutMain from "@theme/DocRoot/Layout/Main";
import BackToTopButton from "@theme/BackToTopButton";
import ifers from "@site/ifers.config";
function DisplayLength(str, shrink = null) {
if (shrink === null) {
let length = 0;
for (let i = 0; i < str.length; i++) {
let code = str.charCodeAt(i);
if (code >= 0x4e00 && code <= 0x9fa5) {
length += 2;
} else {
length += 1;
}
}
return length;
}
let ret = "";
let length = 0;
for (let i = 0; i < str.length; i++) {
let code = str.charCodeAt(i);
if (code >= 0x4e00 && code <= 0x9fa5) {
length += 2;
} else {
length += 1;
}
if (length > shrink) break;
ret += str.charAt(i);
}
return ret;
}

function BlogSidebarToDocSidebar(sidebarItems) {
function _itemConvert(item) {
return {
type: "link",
label:
DisplayLength(item.title) > ifers.blog.sidebarMaxChar
? DisplayLength(item.title, ifers.blog.sidebarMaxChar - 3) + "..."
: item.title,
href: item.permalink,
docId: item.permalink.split("/")[-1],
unlisted: item.unlisted,
};
}
const ret = sidebarItems.map(_itemConvert);
return ret;
}

export default function BlogLayout(props) {
const { sidebar, toc, children, ...layoutProps } = props;
const windowSize = useWindowSize();
const hasSidebar = sidebar && sidebar.items.length > 0;
const newItems = BlogSidebarToDocSidebar(sidebar.items);
const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false);
return (
<Layout {...layoutProps}>
<DocsSidebarProvider name="blogSidebar" items={newItems}>
<div className={styles.docsWrapper}>
<BackToTopButton />
<div className={styles.docRoot}>
{sidebar && (
<DocRootLayoutSidebar
sidebar={newItems}
hiddenSidebarContainer={hiddenSidebarContainer}
setHiddenSidebarContainer={setHiddenSidebarContainer}
/>
)}
<DocRootLayoutMain
hiddenSidebarContainer={hiddenSidebarContainer}
>
{children}
</DocRootLayoutMain>
{windowSize !== "mobile" && toc && (
<div className="col col--2">{toc}</div>
)}
</div>
</div>
</DocsSidebarProvider>
</Layout>
);
}