Skip to main content

Use Docs Layout in Docusaurus Blog

· 4 min read
Ferdinand Su

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.js.

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.js
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>
);
}