跳到主要内容

使用 docusaurus 构建开源电子书

创建电子书

npx create-docusaurus@latest kubernetes-guide classic --typescript

安装依赖

npm install --save docusaurus-plugin-sass sass @giscus/react raw-loader path-browserify flexanalytics/plugin-image-zoom @docusaurus/plugin-ideal-image@latest @docusaurus/plugin-pwa@latest

自定义样式

src/css/custom.css 重命名为 src/css/custom.scss,覆盖为以下内容:

src/css/custom.scss
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/

/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}

/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}

/* 代码高亮行的样式 */
.code-block-highlighted-line {
background-color: rgb(72, 77, 91);
span[class*='codeLineNumber'] {
background-color: rgb(72, 77, 91);
}
}
.code-block-add-line {
background-color: #213227;
span[class*='codeLineNumber'] {
background-color: #213227;
}
}
.code-block-update-line {
background-color: #362d1e;
span[class*='codeLineNumber'] {
background-color: #362d1e;
}
}
.code-block-error-line {
background-color: #ff000020;
span[class*='codeLineNumber'] {
background-color: #ff000020;
}
}

清理不需要的文件

因为是电子书,不需要默认的首页,也不需要博客页面,直接用电子书作为首页。很多默认生成的文件是不需要的,可以删除:

rm -rf src/components/HomepageFeatures
rm -rf src/pages/*
rm -rf blog
rm -rf docs
rm -rf static/img/*
rm sidebars.js

创建自定义组件

giscus 评论组件

src/components/Comment.tsx
import React from 'react'
import { useThemeConfig, useColorMode } from '@docusaurus/theme-common'
import Giscus, { GiscusProps } from '@giscus/react'
import { useLocation } from '@docusaurus/router';

const defaultConfig: Partial<GiscusProps> = {
id: 'comments',
mapping: 'specific',
reactionsEnabled: '1',
emitMetadata: '0',
inputPosition: 'top',
loading: 'lazy',
strict: '1', // 用根据路径标题自动生成的 sha1 值,精确匹配 github discussion,避免路径重叠(比如父和子路径)时评论加载串了
lang: 'zh-CN',
}

export default function Comment(): JSX.Element {
const themeConfig = useThemeConfig()

// merge default config
const giscus = { ...defaultConfig, ...themeConfig.giscus }

if (!giscus.repo || !giscus.repoId || !giscus.categoryId) {
throw new Error(
'You must provide `repo`, `repoId`, and `categoryId` to `themeConfig.giscus`.',
)
}

const path = useLocation().pathname.replace(/^\/|\/$/g, '');
const firstSlashIndex = path.indexOf('/');
var subPath: string = ""
if (firstSlashIndex !== -1) {
subPath = path.substring(firstSlashIndex + 1)
} else {
subPath = "index"
}

giscus.term = subPath
giscus.theme =
useColorMode().colorMode === 'dark' ? 'transparent_dark' : 'light'

return (
<Giscus {...giscus} />
)
}

增强代码块 FileBlock

src/components/FileBlock.tsx
import React from 'react';
import CodeBlock from '@theme/CodeBlock';
import { useLocation } from '@docusaurus/router';
import * as path from 'path-browserify';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';

let extToLang = new Map([
["sh", "bash"],
["yml", "yaml"]
]);

export default function FileBlock({ file, showFileName, ...prop }: { file: string, showFileName?: boolean }) {
// get url path without "/" prefix and suffix
var urlPath = useLocation().pathname.replace(/^\/|\/$/g, '');

// remove locale prefix in urlPath
const { i18n } = useDocusaurusContext()
if (i18n.currentLocale != i18n.defaultLocale) {
urlPath = urlPath.replace(/^[^\/]*\/?/g, '')
}

// find file content according to topPath and file path param
var filepath = ""
if (file.startsWith("@site/")) {
filepath = file.replace(/^@site\//g, '')
} else {
filepath = "codeblock/" + file
}

// load file raw content according to filepath
var content = require('!!raw-loader!@site/' + filepath)?.default
content = content.replace(/\t/g, " "); // replace tab to 2 spaces

// infer language of code block based on filename extension if language is not set
const filename = path.basename(file);
if (!prop.language) {
var language = path.extname(filename).replace(/^\./, '')
const langMappingName = extToLang.get(language)
if (langMappingName) {
language = langMappingName
}
prop.language = language
}

// set title to filename if showFileName is set and title is not set
if (!prop.title && showFileName) {
prop.title = filename
}

return (
<CodeBlock {...prop}>
{content}
</CodeBlock>
);
}