Docsify turns one or more Markdown files into a Website, with no build process required.
## Features
- No statically built HTML files
- Simple and lightweight
- Smart full-text search plugin
- Multiple themes
- Useful plugin API
- Emoji support
## Quick Start
Get going fast by using a static web server or GitHub Pages with this ready-to-use [Docsify Template](https://github.com/docsifyjs/docsify-template), review the [quick start tutorial](https://docsify.js.org/#/quickstart) or jump right into a CodeSandbox example site with the button below.
[](https://codesandbox.io/s/307qqv236)
## Showcase
A large collection of showcase projects are included in [awesome-docsify](https://github.com/docsifyjs/awesome-docsify#showcase).
## Links
- [Documentation](https://docsify.js.org)
- [Docsify CLI (Command Line Interface)](https://github.com/docsifyjs/docsify-cli)
- CDN: [UNPKG](https://unpkg.com/docsify/) | [jsDelivr](https://cdn.jsdelivr.net/npm/docsify/) | [cdnjs](https://cdnjs.com/libraries/docsify)
- [`develop` branch preview](https://docsify-preview.vercel.app/)
- [Awesome docsify](https://github.com/docsifyjs/awesome-docsify)
- [Community chat](https://discord.gg/3NwKFyR)
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).
## Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/docsify/contribute)]
## Sponsors
Thank you for supporting this project! ❤️ [[Become a sponsor](https://opencollective.com/docsify/contribute)]
## Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
## License
[MIT](LICENSE)
================================================
FILE: SECURITY.md
================================================
# Security Policy
If you believe you have found a security vulnerability in docsify, please report it to us asap.
## Reporting a Vulnerability
**Please do not report security vulnerabilities through our public GitHub issues.**
Send email to us via :email: maintainers@docsifyjs.org.
Please include as much of the following information as possible to help us better understand the possible issue:
- Type of issue (e.g. cross-site scripting)
- Full paths of source file(s) related to the manifestation of the issue
- The location of the affected source code (tag/branch/commit or direct URL)
- Any special configuration required to reproduce the issue
- Step-by-step instructions to reproduce the issue
- Proof-of-concept or exploit code
- Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
Thank you in advance.
================================================
FILE: babel.config.json
================================================
{
"presets": [
[
"@babel/preset-env",
{
"targets": "defaults"
}
]
]
}
================================================
FILE: build/cover.js
================================================
import fs from 'fs';
import { relative } from './util.js';
const read = fs.readFileSync;
const write = fs.writeFileSync;
const pkgPath = relative(import.meta, '..', 'package.json');
const pkg = JSON.parse(read(pkgPath).toString());
const version = process.env.VERSION || pkg.version;
const file = relative(import.meta, '..', 'docs', '_coverpage.md');
let cover = read(file, 'utf8').toString();
console.log('Replace version number in cover page...');
cover = cover.replace(
/(\S+)?<\/small>/g,
/* html */ `${version}`,
);
write(file, cover);
================================================
FILE: build/emoji.js
================================================
import fs from 'fs';
import path from 'path';
import axios from 'axios';
const filePaths = {
emojiMarkdown: path.resolve(process.cwd(), 'docs', 'emoji.md'),
emojiJS: path.resolve(
process.cwd(),
'src',
'core',
'render',
'emoji-data.js',
),
};
async function getEmojiData() {
const emojiDataURL = 'https://api.github.com/emojis';
console.info(`- Fetching emoji data from ${emojiDataURL}`);
const response = await axios.get(emojiDataURL);
const baseURL = Object.values(response.data)
.find(url => /unicode\//)
.split('unicode/')[0];
const data = { ...response.data };
// Remove base URL from emoji URLs
Object.entries(data).forEach(
([key, value]) => (data[key] = value.replace(baseURL, '')),
);
console.info(`- Retrieved ${Object.keys(data).length} emoji entries`);
return {
baseURL,
data,
};
}
function writeEmojiPage(emojiData) {
const isExistingPage = fs.existsSync(filePaths.emojiMarkdown);
const emojiPage =
(isExistingPage && fs.readFileSync(filePaths.emojiMarkdown, 'utf8')) ||
'\n\n';
const emojiRegEx = /(\n)([\s\S]*)(\n)/;
const emojiMatch = emojiPage.match(emojiRegEx);
const emojiMarkdownStart = emojiMatch[1].trim();
const emojiMarkdown = emojiMatch[2].trim();
const emojiMarkdownEnd = emojiMatch[3].trim();
const newEmojiMarkdown = Object.keys(emojiData.data)
.reduce(
(preVal, curVal) =>
(preVal += `:${curVal}: ` + '`' + `:${curVal}:` + '`' + '\n\n'),
'',
)
.trim();
if (emojiMarkdown !== newEmojiMarkdown) {
const newEmojiPage = emojiPage.replace(
emojiMatch[0],
`${emojiMarkdownStart}\n\n${newEmojiMarkdown}\n\n${emojiMarkdownEnd}`,
);
fs.writeFileSync(filePaths.emojiMarkdown, newEmojiPage);
console.info(
`- ${!isExistingPage ? 'Created' : 'Updated'}: ${filePaths.emojiMarkdown}`,
);
} else {
console.info(`- No changes: ${filePaths.emojiMarkdown}`);
}
}
function writeEmojiJS(emojiData) {
const isExistingPage = fs.existsSync(filePaths.emojiJS);
const emojiJS = isExistingPage && fs.readFileSync(filePaths.emojiJS, 'utf8');
const newEmojiJS = [
'// =============================================================================',
'// DO NOT EDIT: This file is auto-generated by an /build/emoji.js',
'// =============================================================================\n',
`export default ${JSON.stringify(emojiData, {}, 2)}`,
].join('\n');
if (!emojiJS || emojiJS !== newEmojiJS) {
fs.writeFileSync(filePaths.emojiJS, newEmojiJS);
console.info(
`- ${!isExistingPage ? 'Created' : 'Updated'}: ${filePaths.emojiJS}`,
);
} else {
console.info(`- No changes: ${filePaths.emojiJS}`);
}
}
console.info('Build emoji');
const emojiData = await getEmojiData();
writeEmojiPage(emojiData);
writeEmojiJS(emojiData);
console.info('Finish update');
================================================
FILE: build/release.sh
================================================
set -e
if [[ -z $1 ]]; then
echo "Enter new version: "
read VERSION
else
VERSION=$1
fi
RELEASE_TAG=${RELEASE_TAG:-""}
if [[ -n "$RELEASE_TAG" && "$VERSION" != *"$RELEASE_TAG"* ]]; then
RELEASE_MSG="$VERSION ($RELEASE_TAG)"
else
RELEASE_MSG="$VERSION"
fi
read -p "Releasing $VERSION $RELEASE_TAG - are you sure? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Releasing $VERSION ..."
# Update version (don't commit or tag yet)
npm --no-git-tag-version version "$VERSION"
# Build and test
npm run build
npm run test:update:snapshot
npm run test
git stash
npm run build:v4 # builds legacy v4 lib/ and themes/ folders for backwards compat while people transition to v5.
git stash pop
# Changelog
npx conventional-changelog -p angular -i CHANGELOG.md -s
# Commit all changes
git add -A
git commit -m "[release] $RELEASE_MSG"
# Tag and push
git tag "v$VERSION"
git push origin "v$VERSION"
git push
# Publish to npm
if [[ -z $RELEASE_TAG ]]; then
npm publish
else
npm publish --tag "$RELEASE_TAG"
fi
npm run clean:v4 # clean up legacy v4 build files
fi
================================================
FILE: build/util.js
================================================
import url from 'url';
import path from 'path';
/** Get a new path relative to the current module (pass import.meta). */
export const relative = (meta, ...to) =>
path.resolve(path.dirname(url.fileURLToPath(meta.url)), ...to);
================================================
FILE: docs/.nojekyll
================================================
================================================
FILE: docs/CNAME
================================================
docsify.js.org
================================================
FILE: docs/README.md
================================================
# docsify
> A magical documentation site generator.
## What it is
Docsify turns your Markdown files into a documentation website instantly. Unlike most other documentation site generator tools, it doesn't need to build HTML files. Instead, it dynamically loads and parses your Markdown files and displays them as a website. To get started, create an `index.html` file and [deploy it on GitHub Pages](deploy.md) (for more details see the [Quick start](quickstart.md) guide).
## Features
- No statically built HTML files
- Simple and lightweight
- Smart full-text search plugin
- Multiple themes
- Useful plugin API
- Emoji support
## Examples
Check out the [Showcase](https://github.com/docsifyjs/awesome-docsify#showcase) to see docsify in use.
## Donate
Please consider donating if you think docsify is helpful to you or that my work is valuable. I am happy if you can help me [buy a cup of coffee](https://github.com/QingWei-Li/donate). :heart:
## Community
Users and the development team are usually in the [Discord server](https://discord.gg/3NwKFyR).
================================================
FILE: docs/_coverpage.md
================================================

# docsify 5.0.0-rc.4 :id=docsify
> A magical documentation site generator
- Simple and lightweight
- No statically built HTML files
- Multiple themes
[Get Started](#docsify)
[GitHub](https://github.com/docsifyjs/docsify/)
================================================
FILE: docs/_media/example-with-yaml.md
================================================
---
author: John Smith
date: 2020-1-1
---
> This is from the `example-with-yaml.md`
================================================
FILE: docs/_media/example.html
================================================
Example HTML Page
================================================
FILE: docs/_media/example.js
================================================
import fetch from 'fetch';
const URL = 'https://example.com';
const PORT = 8080;
/// [demo]
const result = fetch(`${URL}:${PORT}`)
.then(response => {
return response.json();
})
.then(myJson => {
console.log(JSON.stringify(myJson));
});
/// [demo]
result.then(console.log).catch(console.error);
================================================
FILE: docs/_media/example.md
================================================
> This is from the `example.md`
================================================
FILE: docs/_navbar.md
================================================
- Translations
- [English](/)
- [简体中文](/zh-cn/)
================================================
FILE: docs/_sidebar.md
================================================
- Getting started
- [Quick start](quickstart.md)
- [Adding pages](adding-pages.md)
- [Cover page](cover.md)
- [Custom navbar](custom-navbar.md)
- Customization
- [Configuration](configuration.md)
- [Themes](themes.md)
- [List of Plugins](plugins.md)
- [Write a Plugin](write-a-plugin.md)
- [Markdown configuration](markdown.md)
- [Language highlighting](language-highlight.md)
- [Emoji](emoji.md)
- Guide
- [Deploy](deploy.md)
- [Helpers](helpers.md)
- [Vue compatibility](vue.md)
- [CDN](cdn.md)
- [Offline Mode (PWA)](pwa.md)
- [Embed Files](embed-files.md)
- [UI Kit](ui-kit.md)
- Upgrading
- [v4 to v5](v5-upgrade.md)
* [Awesome docsify](awesome.md)
* [Changelog](changelog.md)
================================================
FILE: docs/adding-pages.md
================================================
# Adding pages
If you need more pages, you can simply create more markdown files in your docsify directory. If you create a file named `guide.md`, then it is accessible via `/#/guide`.
For example, the directory structure is as follows:
```text
.
└── docs
├── README.md
├── guide.md
└── zh-cn
├── README.md
└── guide.md
```
Matching routes
```text
docs/README.md => http://domain.com
docs/guide.md => http://domain.com/#/guide
docs/zh-cn/README.md => http://domain.com/#/zh-cn/
docs/zh-cn/guide.md => http://domain.com/#/zh-cn/guide
```
## Sidebar
In order to have a sidebar, you can create your own `_sidebar.md` (see [this documentation's sidebar](https://github.com/docsifyjs/docsify/blob/main/docs/_sidebar.md) for an example):
First, you need to set `loadSidebar` to **true**. Details are available in the [configuration paragraph](configuration#loadsidebar).
```html
```
Create the `_sidebar.md`:
```markdown
- [Home](/)
- [Page 1](page-1.md)
```
To create section headers:
```markdown
- Section Header 1
- [Home](/)
- [Page 1](page-1.md)
- Section Header 2
- [Page 2](page-2.md)
- [Page 3](page-3.md)
```
You need to create a `.nojekyll` in `./docs` to prevent GitHub Pages from ignoring files that begin with an underscore.
> [!IMPORTANT] Docsify only looks for `_sidebar.md` in the current folder, and uses that, otherwise it falls back to the one configured using `window.$docsify.loadSidebar` config.
Example file structure:
```text
└── docs/
├── _sidebar.md
├── index.md
├── getting-started.md
└── running-services.md
```
## Nested Sidebars
You may want the sidebar to update after navigation to reflect the current directory. This can be done by adding a `_sidebar.md` file to each folder.
`_sidebar.md` is loaded from each level directory. If the current directory doesn't have `_sidebar.md`, it will fall back to the parent directory. If, for example, the current path is `/guide/quick-start`, the `_sidebar.md` will be loaded from `/guide/_sidebar.md`.
You can specify `alias` to avoid unnecessary fallback.
```html
```
> [!IMPORTANT] You can create a `README.md` file in a subdirectory to use it as the landing page for the route.
## Set Page Titles from Sidebar Selection
A page's `title` tag is generated from the _selected_ sidebar item name. For better SEO, you can customize the title by specifying a string after the filename.
```markdown
- [Home](/)
- [Guide](guide.md 'The greatest guide in the world')
```
## Table of Contents
Once you've created `_sidebar.md`, the sidebar content is automatically generated based on the headers in the markdown files.
A custom sidebar can also automatically generate a table of contents by setting a `subMaxLevel`, compare [subMaxLevel configuration](configuration#submaxlevel).
```html
```
## Ignoring Subheaders
When `subMaxLevel` is set, each header is automatically added to the table of contents by default. If you want to ignore a specific header, add `` to it.
```markdown
# Getting Started
## Header
This header won't appear in the sidebar table of contents.
```
To ignore all headers on a specific page, you can use `` on the first header of the page.
```markdown
# Getting Started
## Header
This header won't appear in the sidebar table of contents.
```
Both `` and `` will not be rendered on the page when used.
And the `{docsify-ignore}` and `{docsify-ignore-all}` can do the samething as well.
================================================
FILE: docs/cdn.md
================================================
# CDN
The docsify [npm package](https://www.npmjs.com/package/docsify) is auto-published to CDNs with each release. The contents can be viewed on each CDN.
Docsify recommends [jsDelivr](//cdn.jsdelivr.net) as its preferred CDN:
- https://cdn.jsdelivr.net/npm/docsify/
Other CDNs are available and may be required in locations where jsDelivr is not available:
- https://cdnjs.com/libraries/docsify
- https://unpkg.com/browse/docsify/
- https://www.bootcdn.cn/docsify/
## Specifying versions
Note the `@` version lock in the CDN URLs below. This allows specifying the latest major, minor, patch, or specific [semver](https://semver.org) version number.
- MAJOR versions include breaking changes
`1.0.0` → `2.0.0`
- MINOR versions include non-breaking new functionality
`1.0.0` → `1.1.0`
- PATCH versions include non-breaking bug fixes
`1.0.0` → `1.0.1`
Uncompressed resources are available by omitting the `.min` from the filename.
## Latest "major" version
Specifying the latest major version allows your site to receive all non-breaking enhancements ("minor" updates) and bug fixes ("patch" updates) as they are released. This is good option for those who prefer a zero-maintenance way of keeping their site up to date with minimal risk as new versions are published.
> [!TIP] When a new major version is released, you will need to manually update the major version number after the `@` symbol in your CDN URLs.
```html
```
## Specific version
Specifying an exact version prevents any future updates from affecting your site. This is good option for those who prefer to manually update their resources as new versions are published.
> [!TIP] When a new version is released, you will need to manually update the version number after the `@` symbol in your CDN URLs.
```html
```
================================================
FILE: docs/configuration.md
================================================
# Configuration
You can configure Docsify by defining `window.$docsify` as an object:
```html
```
The config can also be defined as a function, in which case the first argument is the Docsify `vm` instance. The function should return a config object. This can be useful for referencing `vm` in places like the markdown configuration:
```html
```
## alias
- Type: `Object`
Set the route alias. You can freely manage routing rules. Supports RegExp.
Do note that order matters! If a route can be matched by multiple aliases, the one you declared first takes precedence.
```js
window.$docsify = {
alias: {
'/foo/(.*)': '/bar/$1', // supports regexp
'/zh-cn/changelog': '/changelog',
'/changelog':
'https://raw.githubusercontent.com/docsifyjs/docsify/main/CHANGELOG',
// You may need this if you use routerMode:'history'.
'/.*/_sidebar.md': '/_sidebar.md', // See #301
},
};
```
> **Note** If you change [`routerMode`](#routermode) to `'history'`, you may
> want to configure an alias for your `_sidebar.md` and `_navbar.md` files.
## auto2top
- Type: `Boolean`
- Default: `false`
Scrolls to the top of the screen when the route is changed.
```js
window.$docsify = {
auto2top: true,
};
```
## autoHeader
- Type: `Boolean`
- Default: `false`
If `loadSidebar` and `autoHeader` are both enabled, for each link in `_sidebar.md`, prepend a header to the page before converting it to HTML — but only if the page does not already contain an H1 heading.
For more details, see [#78](https://github.com/docsifyjs/docsify/issues/78).
```js
window.$docsify = {
loadSidebar: true,
autoHeader: true,
};
```
## basePath
- Type: `String`
Base path of the website. You can set it to another directory or another domain name.
```js
window.$docsify = {
basePath: '/path/',
// Load the files from another site
basePath: 'https://docsify.js.org/',
// Even can load files from other repo
basePath:
'https://raw.githubusercontent.com/ryanmcdermott/clean-code-javascript/master/',
};
```
## catchPluginErrors
- Type: `Boolean`
- Default: `true`
Determines if Docsify should handle uncaught _synchronous_ plugin errors automatically. This can prevent plugin errors from affecting docsify's ability to properly render live site content.
## cornerExternalLinkTarget
- Type: `String`
- Default: `'_blank'`
Target to open external link at the top right corner. Default `'_blank'` (new window/tab)
```js
window.$docsify = {
cornerExternalLinkTarget: '_self', // default: '_blank'
};
```
## coverpage
- Type: `Boolean|String|String[]|Object`
- Default: `false`
Activate the [cover feature](cover.md). If true, it will load from `_coverpage.md`.
```js
window.$docsify = {
coverpage: true,
// Custom file name
coverpage: 'cover.md',
// multiple covers
coverpage: ['/', '/zh-cn/'],
// multiple covers and custom file name
coverpage: {
'/': 'cover.md',
'/zh-cn/': 'cover.md',
},
};
```
## el
- Type: `String`
- Default: `'#app'`
The DOM element to be mounted on initialization. It can be a CSS selector string or an actual [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement).
```js
window.$docsify = {
el: '#app',
};
```
## executeScript
- Type: `Boolean`
- Default: `null`
Execute the script on the page. Only parses the first script tag ([demo](themes)). If Vue is detected, this is `true` by default.
```js
window.$docsify = {
executeScript: true,
};
```
```markdown
## This is test
```
Note that if you are running an external script, e.g. an embedded jsfiddle demo, make sure to include the [external-script](plugins.md?id=external-script) plugin.
## ext
- Type: `String`
- Default: `'.md'`
Request file extension.
```js
window.$docsify = {
ext: '.md',
};
```
## externalLinkRel
- Type: `String`
- Default: `'noopener'`
Default `'noopener'` (no opener) prevents the newly opened external page (when [externalLinkTarget](#externallinktarget) is `'_blank'`) from having the ability to control our page. No `rel` is set when it's not `'_blank'`. See [this post](https://mathiasbynens.github.io/rel-noopener/) for more information about why you may want to use this option.
```js
window.$docsify = {
externalLinkRel: '', // default: 'noopener'
};
```
## externalLinkTarget
- Type: `String`
- Default: `'_blank'`
Target to open external links inside the markdown. Default `'_blank'` (new window/tab)
```js
window.$docsify = {
externalLinkTarget: '_self', // default: '_blank'
};
```
## fallbackLanguages
- Type: `Array`
List of languages that will fallback to the default language when a page is requested and it doesn't exist for the given locale.
Example:
- try to fetch the page of `/de/overview`. If this page exists, it'll be displayed.
- then try to fetch the default page `/overview` (depending on the default language). If this page exists, it'll be displayed.
- then display the 404 page.
```js
window.$docsify = {
fallbackLanguages: ['fr', 'de'],
};
```
## fallbackDefaultLanguage
- Type: `String`
- Default: `''`
When a page is requested and it doesn't exist for the given locale, Docsify will fallback to the language specified by this option.
For example, in the scenario described above, if `/de/overview` does not exist and `fallbackDefaultLanguage` is configured as `zh-cn`, Docsify will fetch `/zh-cn/overview` instead of `/overview`.
```js
window.$docsify = {
fallbackLanguages: ['fr', 'de'],
fallbackDefaultLanguage: 'zh-cn', // default: ''
};
```
## formatUpdated
- Type: `String|Function`
We can display the file update date through **{docsify-updated}** variable. And format it by `formatUpdated`.
See https://github.com/lukeed/tinydate#patterns
```js
window.$docsify = {
formatUpdated: '{MM}/{DD} {HH}:{mm}',
formatUpdated(time) {
// ...
return time;
},
};
```
## hideSidebar
- Type : `Boolean`
- Default: `false`
This option will completely hide your sidebar and won't render any content on the side.
```js
window.$docsify = {
hideSidebar: true,
};
```
## homepage
- Type: `String`
- Default: `'README.md'`
`README.md` in your docs folder will be treated as the homepage for your website, but sometimes you may need to serve another file as your homepage.
```js
window.$docsify = {
// Change to /home.md
homepage: 'home.md',
// Or use the readme in your repo
homepage:
'https://raw.githubusercontent.com/docsifyjs/docsify/main/README.md',
};
```
## keyBindings
- Type: `Boolean|Object`
- Default: `Object`
- \\ Toggle the sidebar menu
- / Focus on [search](plugins#full-text-search) field. Also supports alt / ctrl + k.
Binds key combination(s) to a custom callback function.
Key `bindings` are defined as case insensitive string values separated by `+`. Modifier key values include `alt`, `ctrl`, `meta`, and `shift`. Non-modifier key values should match the keyboard event's [key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key) or [code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code) value.
The `callback` function receive a [keydown event](https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event) as an argument.
> [!IMPORTANT] Let site visitors know your custom key bindings are available! If a binding is associated with a DOM element, consider inserting a `` element as a visual cue (e.g., alt + a) or adding [title](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title) and [aria-keyshortcuts](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-keyshortcuts) attributes for hover/focus hints.
```js
window.$docsify = {
keyBindings: {
// Custom key binding
myCustomBinding: {
bindings: ['alt+a', 'shift+a'],
callback(event) {
alert('Hello, World!');
},
},
},
};
```
Key bindings can be disabled entirely or individually by setting the binding configuration to `false`.
```js
window.$docsify = {
// Disable all key bindings
keyBindings: false,
};
```
```js
window.$docsify = {
keyBindings: {
// Disable individual key bindings
focusSearch: false,
toggleSidebar: false,
},
};
```
## loadNavbar
- Type: `Boolean|String`
- Default: `false`
Loads navbar from the Markdown file `_navbar.md` if **true**, else loads it from the path specified.
```js
window.$docsify = {
// load from _navbar.md
loadNavbar: true,
// load from nav.md
loadNavbar: 'nav.md',
};
```
## loadSidebar
- Type: `Boolean|String`
- Default: `false`
Loads sidebar from the Markdown file `_sidebar.md` if **true**, else loads it from the path specified.
```js
window.$docsify = {
// load from _sidebar.md
loadSidebar: true,
// load from summary.md
loadSidebar: 'summary.md',
};
```
## logo
- Type: `String`
Website logo as it appears in the sidebar. You can resize it using CSS.
> [!IMPORTANT] Logo will only be visible if `name` prop is also set. See [name](#name) configuration.
```js
window.$docsify = {
logo: '/_media/icon.svg',
};
```
## markdown
- Type: `Function`
See [Markdown configuration](markdown.md).
```js
window.$docsify = {
// object
markdown: {
smartypants: true,
renderer: {
link() {
// ...
},
},
},
// function
markdown(marked, renderer) {
// ...
return marked;
},
};
```
## maxLevel
- Type: `Number`
- Default: `6`
Maximum Table of content level.
```js
window.$docsify = {
maxLevel: 4,
};
```
## mergeNavbar
- Type: `Boolean`
- Default: `false`
Navbar will be merged with the sidebar on smaller screens.
```js
window.$docsify = {
mergeNavbar: true,
};
```
## name
- Type: `Boolean | String`
Website name as it appears in the sidebar.
```js
window.$docsify = {
name: 'docsify',
};
```
The name field can also contain custom HTML for easier customization:
```js
window.$docsify = {
name: 'docsify',
};
```
If `true`, the website name will be inferred from the document's `` tag.
```js
window.$docsify = {
name: true,
};
```
If `false` or empty, no name will be displayed.
```js
window.$docsify = {
name: false,
};
```
## nameLink
- Type: `String`
- Default: `'window.location.pathname'`
The URL that the website `name` links to.
```js
window.$docsify = {
nameLink: '/',
// For each route
nameLink: {
'/zh-cn/': '#/zh-cn/',
'/': '#/',
},
};
```
## nativeEmoji
- Type: `Boolean`
- Default: `false`
Render emoji shorthand codes using GitHub-style emoji images or native emoji characters.
```js
window.$docsify = {
nativeEmoji: true,
};
```
```markdown
:smile:
:partying_face:
:joy:
:+1:
:-1:
```
GitHub-style images when `false`:
Native characters when `true`:
To render shorthand codes as text, replace `:` characters with the `:` HTML entity.
```markdown
:100:
```
## noCompileLinks
- Type: `Array`
Sometimes we do not want docsify to handle our links. See [#203](https://github.com/docsifyjs/docsify/issues/203). We can skip compiling of certain links by specifying an array of strings. Each string is converted into to a regular expression (`RegExp`) and the _whole_ href of a link is matched against it.
```js
window.$docsify = {
noCompileLinks: ['/foo', '/bar/.*'],
};
```
## noEmoji
- Type: `Boolean`
- Default: `false`
Disabled emoji parsing and render all emoji shorthand as text.
```js
window.$docsify = {
noEmoji: true,
};
```
```markdown
:100:
```
To disable emoji parsing of individual shorthand codes, replace `:` characters with the `:` HTML entity.
```markdown
:100:
:100:
```
## notFoundPage
- Type: `Boolean|String|Object`
- Default: `false`
Display default "404 - Not Found" message:
```js
window.$docsify = {
notFoundPage: false,
};
```
Load the `_404.md` file:
```js
window.$docsify = {
notFoundPage: true,
};
```
Load the customized path of the 404 page:
```js
window.$docsify = {
notFoundPage: 'my404.md',
};
```
Load the right 404 page according to the localization:
```js
window.$docsify = {
notFoundPage: {
'/': '_404.md',
'/de': 'de/_404.md',
},
};
```
> Note: The options for fallbackLanguages don't work with the `notFoundPage` options.
## onlyCover
- Type: `Boolean`
- Default: `false`
Only coverpage is loaded when visiting the home page.
```js
window.$docsify = {
onlyCover: false,
};
```
## plugins
See [Plugins](./plugins.md).
## relativePath
- Type: `Boolean`
- Default: `false`
If **true**, links are relative to the current context.
For example, the directory structure is as follows:
```text
.
└── docs
├── README.md
├── guide.md
└── zh-cn
├── README.md
├── guide.md
└── config
└── example.md
```
With relative path **enabled** and current URL `http://domain.com/zh-cn/README`, given links will resolve to:
```text
guide.md => http://domain.com/zh-cn/guide
config/example.md => http://domain.com/zh-cn/config/example
../README.md => http://domain.com/README
/README.md => http://domain.com/README
```
```js
window.$docsify = {
// Relative path enabled
relativePath: true,
// Relative path disabled (default value)
relativePath: false,
};
```
## repo
- Type: `String`
Configure the repository url, or a string of `username/repo`, to add the [GitHub Corner](http://tholman.com/github-corners/) widget in the top right corner of the site.
```js
window.$docsify = {
repo: 'docsifyjs/docsify',
// or
repo: 'https://github.com/docsifyjs/docsify/',
};
```
If undefined or empty, no GitHub corner will be displayed.
## requestHeaders
- Type: `Object`
Set the request resource headers.
```js
window.$docsify = {
requestHeaders: {
'x-token': 'xxx',
},
};
```
Such as setting the cache
```js
window.$docsify = {
requestHeaders: {
'cache-control': 'max-age=600',
},
};
```
## routerMode
Configure the URL format that the paths of your site will use.
- Type: `String`
- Default: `'hash'`
```js
window.$docsify = {
routerMode: 'history', // default: 'hash'
};
```
For statically-deployed sites (f.e. on GitHub Pages) hash-based routing is
simpler to set up. For websites that can re-write URLs, the history-based format
is better (especially for search-engine optimization, hash-based routing is not
so search-engine friendly)
Hash-based routing means all URL paths will be prefixed with `/#/` in the
address bar. This is a trick that allows the site to load `/index.html`, then it
uses the path that follows the `#` to determine what markdown files to load. For
example, a complete hash-based URL may look like this:
`https://example.com/#/path/to/page`. The browser will actually load
`https://example.com` (assuming your static server serves
`index.html` by default, as most do), and then the Docsify JavaScript code will
look at the `/#/...` and determine the markdown file to load and render.
Additionally, when clicking on a link, the Docsify router will change the
content after the hash dynamically. The value of `location.pathname` will still be
`/` no matter what. The parts of a hash path are _not_ sent to the server when
visiting such a URL in a browser.
On the other hand, history-based routing means the Docsify JavaScript will use
the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API)
to dynamically change the URL without using a `#`. This means that all URLs will
be considered "real" by search engines, and the full path will be sent to the
server when visiting the URL in your browser. For example, a URL may look like
`https://example.com/path/to/page`. The browser will try to load that full URL
directly from the server, not just `https://example.com`. The upside of this is
that these types of URLs are much more friendly for search engines, and can be
indexed (yay!). The downside, however, is that your server, or the place where
you host your site files, has to be able to handle these URLs. Various static
website hosting services allow "rewrite rules" to be configured, such that a
server can be configured to always send back `/index.html` no matter what path
is visited. The value of `location.pathname` will show `/path/to/page`, because
it was actually sent to the server.
TLDR: start with `hash` routing (the default). If you feel adventurous, learn
how to configure a server, then switch to `history` mode for better experience
without the `#` in the URL and SEO optimization.
> **Note** If you use `routerMode: 'history'`, you may want to add an
> [`alias`](#alias) to make your `_sidebar.md` and `_navbar.md` files always be
> loaded no matter which path is being visited.
>
> ```js
> window.$docsify = {
> routerMode: 'history',
> alias: {
> '/.*/_sidebar.md': '/_sidebar.md',
> '/.*/_navbar.md': '/_navbar.md',
> },
> };
> ```
## routes
- Type: `Object`
Define "virtual" routes that can provide content dynamically. A route is a map between the expected path, to either a string or a function. If the mapped value is a string, it is treated as markdown and parsed accordingly. If it is a function, it is expected to return markdown content.
A route function receives up to three parameters:
1. `route` - the path of the route that was requested (e.g. `/bar/baz`)
2. `matched` - the [`RegExpMatchArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match) that was matched by the route (e.g. for `/bar/(.+)`, you get `['/bar/baz', 'baz']`)
3. `next` - this is a callback that you may call when your route function is async
Do note that order matters! Routes are matched the same order you declare them in, which means that in cases where you have overlapping routes, you might want to list the more specific ones first.
```js
window.$docsify = {
routes: {
// Basic match w/ return string
'/foo': '# Custom Markdown',
// RegEx match w/ synchronous function
'/bar/(.*)'(route, matched) {
return '# Custom Markdown';
},
// RegEx match w/ asynchronous function
'/baz/(.*)'(route, matched, next) {
fetch('/api/users?id=12345')
.then(response => {
next('# Custom Markdown');
})
.catch(err => {
// Handle error...
});
},
},
};
```
Other than strings, route functions can return a falsy value (`null` \ `undefined`) to indicate that they ignore the current request:
```js
window.$docsify = {
routes: {
// accepts everything other than dogs (synchronous)
'/pets/(.+)'(route, matched) {
if (matched[0] === 'dogs') {
return null;
} else {
return 'I like all pets but dogs';
}
}
// accepts everything other than cats (asynchronous)
'/pets/(.*)'(route, matched, next) {
if (matched[0] === 'cats') {
next();
} else {
// Async task(s)...
next('I like all pets but cats');
}
}
}
}
```
Finally, if you have a specific path that has a real markdown file (and therefore should not be matched by your route), you can opt it out by returning an explicit `false` value:
```js
window.$docsify = {
routes: {
// if you look up /pets/cats, docsify will skip all routes and look for "pets/cats.md"
'/pets/cats'(route, matched) {
return false;
}
// but any other pet should generate dynamic content right here
'/pets/(.+)'(route, matched) {
const pet = matched[0];
return `your pet is ${pet} (but not a cat)`;
}
}
}
```
## skipLink
- Type: `Boolean|String|Object`
- Default: `'Skip to main content'`
Determines if/how the site's [skip navigation link](https://webaim.org/techniques/skipnav/) will be rendered.
```js
// Render skip link for all routes
window.$docsify = {
skipLink: 'Skip to content',
};
```
```js
// Render localized skip links based on route paths
window.$docsify = {
skipLink: {
'/es/': 'Saltar al contenido principal',
'/de-de/': 'Ga naar de hoofdinhoud',
'/ru-ru/': 'Перейти к основному содержанию',
'/zh-cn/': '跳到主要内容',
},
};
```
```js
// Do not render skip link
window.$docsify = {
skipLink: false,
};
```
```js
// Use default
window.$docsify = {
skipLink: true, // "Skip to main content"
};
```
## subMaxLevel
- Type: `Number`
- Default: `0`
Add table of contents (TOC) in custom sidebar.
```js
window.$docsify = {
subMaxLevel: 2,
};
```
If you have a link to the homepage in the sidebar and want it to be shown as active when accessing the root url, make sure to update your sidebar accordingly:
```markdown
- Sidebar
- [Home](/)
- [Another page](another.md)
```
For more details, see [#1131](https://github.com/docsifyjs/docsify/issues/1131).
## themeColor ⚠️ :id=themecolor
> [!IMPORTANT] Deprecated as of v5. Use the `--theme-color` [theme property](themes#theme-properties) to [customize](themes#customization) your theme color.
- Type: `String`
Customize the theme color.
```js
window.$docsify = {
themeColor: '#3F51B5',
};
```
## topMargin ⚠️ :id=topmargin
> [!IMPORTANT] Deprecated as of v5. Use the `--scroll-padding-top` [theme property](themes#theme-properties) to specify a scroll margin when using a sticky navbar.
- Type: `Number|String`
- Default: `0`
Adds scroll padding to the top of the viewport. This is useful when you have added a sticky or "fixed" element and would like auto scrolling to align with the bottom of your element.
```js
window.$docsify = {
topMargin: 90, // 90, '90px', '2rem', etc.
};
```
## vueComponents
- Type: `Object`
Creates and registers global [Vue](https://vuejs.org/guide/essentials/component-basics.html). Components are specified using the component name as the key with an object containing Vue options as the value. Component `data` is unique for each instance and will not persist as users navigate the site.
```js
window.$docsify = {
vueComponents: {
'button-counter': {
template: `
`,
data() {
return {
count: 0,
};
},
},
},
};
```
```markdown
```
## vueGlobalOptions
- Type: `Object`
Specifies global Vue options for use with Vue content not explicitly mounted with [vueMounts](#mounting-dom-elements), [vueComponents](#components), or a [markdown script](#markdown-script). Changes to global `data` will persist and be reflected anywhere global references are used.
```js
window.$docsify = {
vueGlobalOptions: {
data() {
return {
count: 0,
};
},
},
};
```
```markdown
{{ count }}
```
## vueMounts
- Type: `Object`
Specifies DOM elements to mount as Vue instances and their associated options. Mount elements are specified using a [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) as the key with an object containing Vue options as their value. Docsify will mount the first matching element in the main content area each time a new page is loaded. Mount element `data` is unique for each instance and will not persist as users navigate the site.
```js
window.$docsify = {
vueMounts: {
'#counter': {
data() {
return {
count: 0,
};
},
},
},
};
```
```markdown
{{ count }}
```
================================================
FILE: docs/cover.md
================================================
# Cover
Activate the cover feature by setting `coverpage` to **true**. See [coverpage configuration](configuration#coverpage).
## Basic usage
Set `coverpage` to **true**, and create a `_coverpage.md`:
```js
window.$docsify = {
coverpage: true,
};
```
```markdown

# docsify
> A magical documentation site generator
- Simple and lightweight
- No statically built HTML files
- Multiple themes
[GitHub](https://github.com/docsifyjs/docsify/)
[Get Started](#docsify)
```
## Customization
The cover page can be customized using [theme properties](themes#theme-properties):
```css
:root {
--cover-bg : url('path/to/image.png');
--cover-bg-overlay : rgba(0, 0, 0, 0.5);
--cover-color : #fff;
--cover-title-color: var(--theme-color);
--cover-title-font : 600 var(--font-size-xxxl) var(--font-family);
}
```
Alternatively, a background color or image can be specified in the cover page markdown.
```markdown

```
```markdown

```
## Coverpage as homepage
Normally, the coverpage and the homepage appear at the same time. Of course, you can also separate the coverpage by [`onlyCover`](configuration#onlycover) option.
## Multiple covers
If your docs site is in more than one language, it may be useful to set multiple covers.
For example, your docs structure is like this
```text
.
└── docs
├── README.md
├── guide.md
├── _coverpage.md
└── zh-cn
├── README.md
└── guide.md
└── _coverpage.md
```
Now, you can set
```js
window.$docsify = {
coverpage: ['/', '/zh-cn/'],
};
```
Or a special file name
```js
window.$docsify = {
coverpage: {
'/': 'cover.md',
'/zh-cn/': 'cover.md',
},
};
```
================================================
FILE: docs/custom-navbar.md
================================================
# Custom navbar
## HTML
If you need custom navigation, you can create a HTML-based navigation bar.
> [!IMPORTANT] Note that documentation links begin with `#/`.
```html
```
## Markdown
Alternatively, you can create a custom markdown-based navigation file by setting `loadNavbar` to **true** and creating `_navbar.md`, compare [loadNavbar configuration](configuration#loadnavbar).
```html
```
```markdown
- [En](/)
- [chinese](/zh-cn/)
```
To create drop-down menus:
```markdown
- Translations
- [En](/)
- [chinese](/zh-cn/)
```
> [!IMPORTANT] You need to create a `.nojekyll` in `./docs` to prevent GitHub Pages from ignoring files that begin with an underscore.
`_navbar.md` is loaded from each level directory. If the current directory doesn't have `_navbar.md`, it will fall back to the parent directory. If, for example, the current path is `/guide/quick-start`, the `_navbar.md` will be loaded from `/guide/_navbar.md`.
## Nesting
You can create sub-lists by indenting items that are under a certain parent.
```markdown
- Getting started
- [Quick start](quickstart.md)
- [Writing more pages](more-pages.md)
- [Custom navbar](custom-navbar.md)
- [Cover page](cover.md)
- Configuration
- [Configuration](configuration.md)
- [Themes](themes.md)
- [Using plugins](plugins.md)
- [Markdown configuration](markdown.md)
- [Language highlight](language-highlight.md)
```
renders as

## Combining custom navbars with the emoji plugin
If you use the [emoji plugin](plugins#emoji):
```html
```
you could, for example, use flag emojis in your custom navbar Markdown file:
```markdown
- [:us:, :uk:](/)
- [:cn:](/zh-cn/)
```
================================================
FILE: docs/deploy.md
================================================
# Deploy
Similar to [GitBook](https://www.gitbook.com), you can deploy files to GitHub Pages, GitLab Pages or VPS.
## GitHub Pages
There are three places to populate your docs for your GitHub repository:
- `docs/` folder
- main branch
- gh-pages branch
It is recommended that you save your files to the `./docs` subfolder of the `main` branch of your repository. Then select `main branch /docs folder` as your GitHub Pages source in your repository's settings page.

> [!IMPORTANT] You can also save files in the root directory and select `main branch`.
> You'll need to place a `.nojekyll` file in the deploy location (such as `/docs` or the gh-pages branch)
## GitLab Pages
If you are deploying your master branch, create a `.gitlab-ci.yml` with the following script:
> [!TIP] The `.public` workaround is so `cp` doesn't also copy `public/` to itself in an infinite loop.
```YAML
pages:
stage: deploy
script:
- mkdir .public
- cp -r * .public
- mv .public public
artifacts:
paths:
- public
only:
- master
```
> [!IMPORTANT] You can replace script with `- cp -r docs/. public`, if `./docs` is your Docsify subfolder.
## Firebase Hosting
> [!IMPORTANT] You'll need to install the Firebase CLI using `npm i -g firebase-tools` after signing into the [Firebase Console](https://console.firebase.google.com) using a Google Account.
Using a terminal, determine and navigate to the directory for your Firebase Project. This could be `~/Projects/Docs`, etc. From there, run `firebase init` and choose `Hosting` from the menu (use **space** to select, **arrow keys** to change options and **enter** to confirm). Follow the setup instructions.
Your `firebase.json` file should look similar to this (I changed the deployment directory from `public` to `site`):
```json
{
"hosting": {
"public": "site",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"]
}
}
```
Once finished, build the starting template by running `docsify init ./site` (replacing site with the deployment directory you determined when running `firebase init` - public by default). Add/edit the documentation, then run `firebase deploy` from the root project directory.
## VPS
Use the following nginx config.
```nginx
server {
listen 80;
server_name your.domain.com;
location / {
alias /path/to/dir/of/docs/;
index index.html;
}
}
```
## Netlify
1. Login to your [Netlify](https://www.netlify.com/) account.
2. In the [dashboard](https://app.netlify.com/) page, click **Add New Site**.
3. Select GitHub.
4. Choose the repository where you store your docs, in the **Base Directory** add the subfolder where the files are stored. For example, it should be `docs`.
5. In the **Build Command** area leave it blank.
6. In the **Publish directory** area, if you have added the `docs` in the **Base Directory** you will see the publish directory populated with `docs/`
7. Netlify is smart enough to look for the the `index.html` file inside the `docs/` folder.
### HTML5 router
When using the HTML5 router, you need to set up redirect rules that redirect all requests to your `index.html`. It's pretty simple when you're using Netlify. Just create a file named `_redirects` in the docs directory, add this snippet to the file, and you're all set:
```sh
/* /index.html 200
```
## Vercel
1. Install [Vercel CLI](https://vercel.com/download), `npm i -g vercel`
2. Change directory to your docsify website, for example `cd docs`
3. Deploy with a single command, `vercel`
## AWS Amplify
1. Set the routerMode in the Docsify project `index.html` to _history_ mode.
```html
```
2. Login to your [AWS Console](https://aws.amazon.com).
3. Go to the [AWS Amplify Dashboard](https://aws.amazon.com/amplify).
4. Choose the **Deploy** route to setup your project.
5. When prompted, keep the build settings empty if you're serving your docs within the root directory. If you're serving your docs from a different directory, customise your amplify.yml
```yml
version: 0.1
frontend:
phases:
build:
commands:
- echo "Nothing to build"
artifacts:
baseDirectory: /docs
files:
- '**/*'
cache:
paths: []
```
6. Add the following Redirect rules in their displayed order. Note that the second record is a PNG image where you can change it with any image format you are using.
| Source address | Target address | Type |
| -------------- | -------------- | ------------- |
| /<\*>.md | /<\*>.md | 200 (Rewrite) |
| /<\*>.png | /<\*>.png | 200 (Rewrite) |
| /<\*> | /index.html | 200 (Rewrite) |
## Stormkit
1. Login to your [Stormkit](https://www.stormkit.io) account.
2. Using the user interface, import your docsify project from one of the three supported Git providers (GitHub, GitLab, or Bitbucket).
3. Navigate to the project’s production environment in Stormkit or create a new environment if needed.
4. Verify the build command in your Stormkit configuration. By default, Stormkit CI will run `npm run build` but you can specify a custom build command on this page.
5. Set output folder to `docs`
6. Click the “Deploy Now” button to deploy your site.
Read more in the [Stormkit Documentation](https://stormkit.io/docs).
## Docker
- Create docsify files
You need prepare the initial files instead of making them inside the container.
See the [Quickstart](https://docsify.js.org/#/quickstart) section for instructions on how to create these files manually or using [docsify-cli](https://github.com/docsifyjs/docsify-cli).
```sh
index.html
README.md
```
- Create Dockerfile
```Dockerfile
FROM node:latest
LABEL description="A demo Dockerfile for build Docsify."
WORKDIR /docs
RUN npm install -g docsify-cli@latest
EXPOSE 3000/tcp
ENTRYPOINT docsify serve .
```
The current directory structure should be this:
```sh
index.html
README.md
Dockerfile
```
- Build docker image
```sh
docker build -f Dockerfile -t docsify/demo .
```
- Run docker image
```sh
docker run -itp 3000:3000 --name=docsify -v $(pwd):/docs docsify/demo
```
## Kinsta Static Site Hosting
You can deploy **Docsify** as a Static Site on [Kinsta](https://kinsta.com/static-site-hosting/).
1. Login or create an account to view your [MyKinsta](https://my.kinsta.com/) dashboard.
2. Authorize Kinsta with your Git provider.
3. Select **Static Sites** from the left sidebar and press **Add sites**.
4. Select the repository and branch you want to deploy.
5. During the build settings, Kinsta will automatically try to fill out the **Build command**, **Node version**, and **Publish directory**. If it won't, fill out the following:
- Build command: leave empty
- Node version: leave on default selection or a specific version (e.g. `18.16.0`)
- Publish directory: `docs`
6. Click the **Create site**.
## DeployHQ
[DeployHQ](https://www.deployhq.com/) is a deployment automation platform that deploys your code to SSH/SFTP servers, FTP servers, cloud storage (Amazon S3, Cloudflare R2), and modern hosting platforms (Netlify, Heroku).
> [!IMPORTANT] DeployHQ does not host your site. It automates deploying your Docsify files to your chosen hosting provider or server.
To deploy your Docsify site using DeployHQ:
1. Sign up for a [DeployHQ account](https://www.deployhq.com/) and verify your email.
2. Create your first project by clicking on **Projects** and **New Project**. Connect your Git repository (GitHub, GitLab, Bitbucket, or any private repository). Authorize DeployHQ to access your repository.
3. Add a server and enter your server details:
- Give your server a name
- Select your protocol (SSH/SFTP, FTP, or cloud platform)
- Enter your server hostname, username, and password/SSH key
- Set **Deployment Path** to your web root (e.g., `public_html/`)
4. Since Docsify doesn't require a build step, you can deploy your files directly. If your Docsify files are in a `docs/` folder, configure the **Source Path** in your server settings to `docs/`.
5. Click **Deploy Project**, then select your server and click **Deploy** to start your first deployment.
Your Docsify site will be deployed to your server. You can enable automatic deployments to deploy on every Git push, or schedule deployments for specific times.
For more information on advanced deployment features, see [DeployHQ's documentation](https://www.deployhq.com/support).
================================================
FILE: docs/embed-files.md
================================================
# Embed files
With docsify 4.6 it is now possible to embed any type of file.
You can embed these files as video, audio, iframes, or code blocks, and even Markdown files can even be embedded directly into the document.
For example, here is an embedded Markdown file. You only need to do this:
```markdown
[filename](_media/example.md ':include')
```
Then the content of `example.md` will be displayed directly here:
[filename](_media/example.md ':include')
You can check the original content for [example.md](_media/example.md ':ignore').
Normally, this will be compiled into a link, but in docsify, if you add `:include` it will be embedded. You can use single or double quotation marks around as you like.
External links can be used too - just replace the target. If you want to use a gist URL, see [Embed a gist](#embed-a-gist) section.
## Embedded file type
Currently, file extensions are automatically recognized and embedded in different ways.
These types are supported:
- **iframe** `.html`, `.htm`
- **markdown** `.markdown`, `.md`
- **audio** `.mp3`
- **video** `.mp4`, `.ogg`
- **code** other file extension
Of course, you can force the specified type. For example, a Markdown file can be embedded as a code block by setting `:type=code`.
```markdown
[filename](_media/example.md ':include :type=code')
```
You will get:
[filename](_media/example.md ':include :type=code')
## Markdown with YAML Front Matter
Front Matter, commonly utilized in blogging systems like Jekyll, serves to define metadata for a document. The [front-matter.js](https://www.npmjs.com/package/front-matter) package facilitates the extraction of metadata (front matter) from documents.
When using Markdown, YAML front matter will be stripped from the rendered content. The attributes cannot be used in this case.
```markdown
[filename](_media/example-with-yaml.md ':include')
```
You will get just the content
[filename](_media/example-with-yaml.md ':include')
## Embedded code fragments
Sometimes you don't want to embed a whole file. Maybe because you need just a few lines but you want to compile and test the file in CI.
```markdown
[filename](_media/example.js ':include :type=code :fragment=demo')
```
In your code file you need to surround the fragment between `/// [demo]` lines (before and after the fragment).
Alternatively you can use `### [demo]`. By default, only identifiers are omitted. To omit the entire line containing the identifier in the fragment output, add the `:omitFragmentLine` option. This is useful if your code fragment is e.g. HTML and you want to hide the Docsify fragment identifier from showing in your HTML source. `` in your source file and `:omitFragmentLine` will make the `-->` not show up in your Docsify code fragment section.
Example: In the source file \_media/example.js, `/// [demo]` identifiers have been included:
```markdown
[filename](_media/example.js ':include :type=code')
```
[filename](_media/example.js ':include :type=code')
Adding the `:fragment=demo` results in the following:
```markdown
[filename](_media/example.js ':include :type=code :fragment=demo')
```
[filename](_media/example.js ':include :type=code :fragment=demo')
## Tag attribute
If you embed the file as `iframe`, `audio` and `video`, then you may need to set the attributes of these tags.
> [!TIP] Note, for the `audio` and `video` types, docsify adds the `controls` attribute by default. When you want add more attributes, the `controls` attribute need to be added manually if need be.
```md
[filename](_media/example.mp4 ':include :type=video controls width=100%')
```
```markdown
[cinwell website](https://cinwell.com ':include :type=iframe width=100% height=400px')
```
[cinwell website](https://cinwell.com ':include :type=iframe width=100% height=400px')
Did you see it? You only need to write directly. You can check [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) for these attributes.
## The code block highlight
Embedding any type of source code file, you can specify the highlighted language or automatically identify.
```markdown
[](_media/example.html ':include :type=code text')
```
⬇️
[](_media/example.html ':include :type=code text')
> [!TIP] How to set highlight? You can see [here](language-highlight.md).
## Embed a gist
You can embed a gist as markdown content or as a code block - this is based on the approach at the start of [Embed Files](#embed-files) section, but uses a raw gist URL as the target.
> [!TIP] **No** plugin or app config change is needed here to make this work. In fact, the "Embed" `script` tag that is copied from a gist will _not_ load even if you make plugin or config changes to allow an external script.
### Identify the gist's metadata
Start by viewing a gist on `gist.github.com`. For the purposes of this guide, we use this gist:
- https://gist.github.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15
Identify the following items from the gist:
| Field | Example | Description |
| ------------ | ---------------------------------- | -------------------------------------------------------------------------------------------------- |
| **Username** | `anikethsaha` | The gist's owner. |
| **Gist ID** | `c2bece08f27c4277001f123898d16a7c` | Identifier for the gist. This is fixed for the gist's lifetime. |
| **Filename** | `content.md` | Select a name of a file in the gist. This needed even on a single-file gist for embedding to work. |
You will need those to build the _raw gist URL_ for the target file. This has the following format:
- `https://gist.githubusercontent.com/USERNAME/GIST_ID/raw/FILENAME`
Here are two examples based on the sample gist:
- https://gist.githubusercontent.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15/raw/content.md
- https://gist.githubusercontent.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15/raw/script.js
> [!TIP] Alternatively, you can get a raw URL directly clicking the _Raw_ button on a gist file. But, if you use that approach, just be sure to **remove** the revision number between `raw/` and the filename so that the URL matches the pattern above instead. Otherwise your embedded gist will **not** show the latest content when the gist is updated.
Continue with one of the sections below to embed the gist on a Docsify page.
### Render markdown content from a gist
This is a great way to embed content **seamlessly** in your docs, without sending someone to an external link. This approach is well-suited to reusing a gist of say installation instructions across doc sites of multiple repos. This approach works equally well with a gist owned by your account or by another user.
Here is the format:
```markdown
[LABEL](https://gist.githubusercontent.com/USERNAME/GIST_ID/raw/FILENAME ':include')
```
For example:
```markdown
[gist: content.md](https://gist.githubusercontent.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15/raw/content.md ':include')
```
Which renders as:
[gist: content.md](https://gist.githubusercontent.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15/raw/content.md ':include')
The `LABEL` can be any text you want. It acts as a _fallback_ message if the link is broken - so it is useful to repeat the filename here in case you need to fix a broken link. It also makes an embedded element easy to read at a glance.
### Render a codeblock from a gist
The format is the same as the previous section, but with `:type=code` added to the alt text. As with the [Embedded file type](#embedded-file-type) section, the syntax highlighting will be **inferred** from the extension (e.g. `.js` or `.py`), so you can leave the `type` set as `code`.
Here is the format:
```markdown
[LABEL](https://gist.githubusercontent.com/USERNAME/GIST_ID/raw/FILENAME ':include :type=code')
```
For example:
```markdown
[gist: script.js](https://gist.githubusercontent.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15/raw/script.js ':include :type=code')
```
Which renders as:
[gist: script.js](https://gist.githubusercontent.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15/raw/script.js ':include :type=code')
================================================
FILE: docs/emoji.md
================================================
# Emoji
Below is a complete list of emoji shorthand codes. Docsify can be configured to render emoji using GitHub-style emoji images or native emoji characters using the [`nativeEmoji`](configuration#nativeemoji) configuration option.
================================================
FILE: docs/helpers.md
================================================
# Doc helper
docsify extends Markdown syntax to make your documents more readable.
> Note: For the special code syntax cases, it's better to put them within code backticks to avoid any conflict from configurations or emojis.
## Callouts
Docsify supports [GitHub style](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts) callouts (also known as "admonitions" or "alerts").
> [!CAUTION]
> **Caution** callouts communicate negative potential consequences of an action.
> [!IMPORTANT]
> **Important** callouts communicate information necessary for users to succeed.
> [!NOTE]
> **Note** callouts communicate information that users should take into account.
> [!TIP]
> **Tip** callouts communicate optional information to help a user be more successful.
> [!WARNING]
> **Warning** callouts communicate potential risks user should be aware of.
**Markdown**
```markdown
> [!CAUTION]
> **Caution** callouts communicate negative potential consequences of an action.
> [!IMPORTANT]
> **Important** callouts communicate information necessary for users to succeed.
> [!NOTE]
> **Note** callouts communicate information that users should take into account.
> [!TIP]
> **Tip** callouts communicate optional information to help a user be more successful.
> [!WARNING]
> **Warning** callouts communicate potential risks user should be aware of.
```
### Legacy Style ⚠️
The following Docsify v4 callout syntax has been deprecated and will be removed in a future version.
!> Legacy **Important** callouts are deprecated.
?> Legacy **Tip** callouts are deprecated.
**Markdown**
```markdown
!> Legacy **Important** callouts are deprecated.
?> Legacy **Tip** callouts are deprecated.
```
## Link attributes
### disabled
```markdown
[link](/demo ':disabled')
```
### href
Sometimes we will use some other relative path for the link, and we have to tell docsify that we don't need to compile this link. For example:
```markdown
[link](/demo/)
```
It will be compiled to `link` and will load `/demo/README.md`. Maybe you want to jump to `/demo/index.html`.
Now you can do that
```markdown
[link](/demo/ ':ignore')
```
You will get `link`html. Do not worry, you can still set the title for the link.
```markdown
[link](/demo/ ':ignore title')
link
```
### target
```markdown
[link](/demo ':target=_blank')
[link](/demo2 ':target=_self')
```
## Task lists
```markdown
- [ ] foo
- bar
- [x] baz
- [] bam <~ not working
- [ ] bim
- [ ] lim
```
- [ ] foo
- bar
- [x] baz
- [] bam <~ not working
- [ ] bim
- [ ] lim
## Images
### Class names
```markdown


```
### IDs
```markdown

```
### Sizes
```markdown




```



## Heading IDs
```markdown
### Hello, world! :id=hello-world
```
## Markdown + HTML
You need to insert a space between the html and markdown content.
This is useful for rendering markdown content in the details element.
```markdown
Self-assessment (Click to expand)
- Abc
- Abc
```
Self-assessment (Click to expand)
- Abc
- Abc
Markdown content can also be wrapped in html tags.
```markdown
- listitem
- listitem
- listitem
```
- listitem
- listitem
- listitem
================================================
FILE: docs/index.html
================================================
docsify
================================================
FILE: docs/language-highlight.md
================================================
# Language highlighting
## Prism
Docsify uses [Prism](https://prismjs.com) for syntax highlighting within code blocks. Prism supports the following languages by default (additional [language support](#language-support) also available):
- Markup: HTML, XML, SVG, MathML, SSML, Atom, RSS
- CSS
- C-like
- JavaScript
To enable syntax highlighting, create a markdown codeblock using backticks (` ``` `) with a [language](https://prismjs.com/#supported-languages) specified on the first line (e.g., `html`, `css`, `js`):
````text
```html
This is a paragraph
Docsify
```
````
````text
```css
p {
color: red;
}
```
````
````text
```js
function add(a, b) {
return a + b;
}
```
````
The above markdown will be rendered as:
```html
This is a paragraph
Docsify
```
```css
p {
color: red;
}
```
```js
function add(a, b) {
return a + b;
}
```
## Language support
Support for additional [languages](https://prismjs.com/#supported-languages) is available by loading the Prism [grammar files](https://cdn.jsdelivr.net/npm/prismjs@1/components/):
> [!IMPORTANT] Prism grammar files must be loaded after Docsify.
```html
```
## Theme support
Docsify's official [themes](themes) are compatible with Prism syntax highlighting themes.
> [!IMPORTANT] Prism themes must be loaded after Docsify themes.
```html
```
Themes can be applied in light and/or dark mode
```html
```
The following Docsify [theme properties](themes#theme-properties) will override Prism theme styles by default:
```text
--border-radius
--font-family-mono
--font-size-mono
```
To use the values specified in the Prism theme, set the desired theme property to `unset`:
```html
```
## Dynamic content
Dynamically generated Code blocks can be highlighted using Prism's [`highlightElement()`](https://prismjs.com/docs/Prism.html#.highlightElement) method:
```js
const code = document.createElement('code');
code.innerHTML = "console.log('Hello World!')";
code.setAttribute('class', 'language-javascript');
Prism.highlightElement(code);
```
================================================
FILE: docs/markdown.md
================================================
# Markdown configuration
**docsify** uses [marked](https://github.com/markedjs/marked) as its Markdown parser. You can customize how it renders your Markdown content to HTML by customizing `renderer`:
```js
window.$docsify = {
markdown: {
smartypants: true,
renderer: {
link() {
// ...
},
},
},
};
```
> [!TIP] Configuration Options Reference: [marked documentation](https://marked.js.org/#/USING_ADVANCED.md)
You can completely customize the parsing rules.
```js
window.$docsify = {
markdown(marked, renderer) {
// ...
return marked;
},
};
```
## Supports mermaid
> [!IMPORTANT] Currently, docsify doesn't support the async mermaid render (the latest mermaid version supported is `v9.3.0`).
```js
//
//
let num = 0;
mermaid.initialize({ startOnLoad: false });
window.$docsify = {
markdown: {
renderer: {
code({ text, lang }) {
if (lang === 'mermaid') {
return /* html */ `
`;
}
return this.origin.code.apply(this, arguments);
},
},
},
};
```
================================================
FILE: docs/plugins.md
================================================
# List of Plugins
These are built-in and external plugins for Docsify.
See also how to [Write a Plugin](./write-a-plugin.md).
## Full text search
By default, the hyperlink on the current page is recognized and the content is saved in `IndexedDB`. You can also specify the path to the files.
```html
```
This plugin ignores diacritical marks when performing a full text search (e.g., "cafe" will also match "café").
## Google Analytics
> Google's Universal Analytics service will no longer process new data in standard properties beginning July 1, 2023. Prepare now by setting up and switching over to a Google Analytics 4 property and docsify's gtag.js plugin.
Install the plugin and configure the track id.
```html
```
Configure by `data-ga`.
```html
```
## Google Analytics 4 (GA4)
Install the plugin and configure the track id.
```html
```
## Emoji
Renders a larger collection of emoji shorthand codes. Without this plugin, Docsify is able to render only a limited number of emoji shorthand codes.
> [!IMPORTANT] Deprecated as of v4.13. Docsify no longer requires this plugin for full emoji support.
```html
```
## External Script
If the script on the page is an external one (imports a js file via `src` attribute), you'll need this plugin to make it work.
```html
```
## Zoom image
Medium's image zoom. Based on [medium-zoom](https://github.com/francoischalifour/medium-zoom).
```html
```
Exclude the special image
```markdown

```
## Edit on github
Add `Edit on github` button on every pages. Provided by [@njleonzhang](https://github.com/njleonzhang), see this [document](https://github.com/njleonzhang/docsify-edit-on-github)
## Demo code with instant preview and jsfiddle integration
With this plugin, sample code can be rendered on the page instantly, so that the readers can see the preview immediately.
When readers expand the demo box, the source code and description are shown there. if they click the button `Try in Jsfiddle`,
`jsfiddle.net` will be open with the code of this sample, which allow readers to revise the code and try on their own.
[Vue](https://njleonzhang.github.io/docsify-demo-box-vue/) and [React](https://njleonzhang.github.io/docsify-demo-box-react/) are both supported.
## Copy to Clipboard
Add a simple `Click to copy` button to all preformatted code blocks to effortlessly allow users to copy example code from your docs. Provided by [@jperasmus](https://github.com/jperasmus)
```html
```
See [here](https://github.com/jperasmus/docsify-copy-code/blob/master/README.md) for more details.
## Disqus
Disqus comments. https://disqus.com/
```html
```
## Gitalk
[Gitalk](https://github.com/gitalk/gitalk) is a modern comment component based on GitHub Issue and Preact.
```html
```
## Pagination
Pagination for docsify. By [@imyelo](https://github.com/imyelo)
```html
```
Click [here](https://github.com/imyelo/docsify-pagination#readme) to get more information.
## Tabs
A docsify.js plugin for displaying tabbed content from markdown.
- [Documentation & Demos](https://jhildenbiddle.github.io/docsify-tabs)
Provided by [@jhildenbiddle](https://github.com/jhildenbiddle/docsify-tabs).
## More plugins
See [awesome-docsify](awesome?id=plugins)
================================================
FILE: docs/pwa.md
================================================
# Offline Mode
[Progressive Web Apps](https://developers.google.com/web/progressive-web-apps/) (PWA) are experiences that combine the best of the web with the best of apps. We can enhance our website with service workers to work **offline** or on low-quality networks.
It is also very easy to use.
## Create serviceWorker
Create a `sw.js` file in your project's root directory and copy the following code:
_sw.js_
```js
/* ===========================================================
* docsify sw.js
* ===========================================================
* Copyright 2016 @huxpro
* Licensed under Apache 2.0
* Register service worker.
* ========================================================== */
const RUNTIME = 'docsify';
const HOSTNAME_WHITELIST = [
self.location.hostname,
'fonts.gstatic.com',
'fonts.googleapis.com',
'cdn.jsdelivr.net',
];
// The Util Function to hack URLs of intercepted requests
const getFixedUrl = req => {
const now = Date.now();
const url = new URL(req.url);
// 1. fixed http URL
// Just keep syncing with location.protocol
// fetch(httpURL) belongs to active mixed content.
// And fetch(httpRequest) is not supported yet.
url.protocol = self.location.protocol;
// 2. add query for caching-busting.
// GitHub Pages served with Cache-Control: max-age=600
// max-age on mutable content is error-prone, with SW life of bugs can even extend.
// Until cache mode of Fetch API landed, we have to workaround cache-busting with query string.
// Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190
if (url.hostname === self.location.hostname) {
url.search += (url.search ? '&' : '?') + 'cache-bust=' + now;
}
return url.href;
};
/**
* @Lifecycle Activate
* New one activated when old isnt being used.
*
* waitUntil(): activating ====> activated
*/
self.addEventListener('activate', event => {
event.waitUntil(self.clients.claim());
});
/**
* @Functional Fetch
* All network requests are being intercepted here.
*
* void respondWith(Promise r)
*/
self.addEventListener('fetch', event => {
// Skip some of cross-origin requests, like those for Google Analytics.
if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) {
// Stale-while-revalidate
// similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale
// Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1
const cached = caches.match(event.request);
const fixedUrl = getFixedUrl(event.request);
const fetched = fetch(fixedUrl, { cache: 'no-store' });
const fetchedCopy = fetched.then(resp => resp.clone());
// Call respondWith() with whatever we get first.
// If the fetch fails (e.g disconnected), wait for the cache.
// If there’s nothing in cache, wait for the fetch.
// If neither yields a response, return offline pages.
event.respondWith(
Promise.race([fetched.catch(_ => cached), cached])
.then(resp => resp || fetched)
.catch(_ => {
/* eat any errors */
}),
);
// Update the cache with the version we fetched (only for ok status)
event.waitUntil(
Promise.all([fetchedCopy, caches.open(RUNTIME)])
.then(
([response, cache]) =>
response.ok && cache.put(event.request, response),
)
.catch(_ => {
/* eat any errors */
}),
);
}
});
```
## Register
Now, register it in your `index.html`. It only works on some modern browsers, so we need to check:
_index.html_
```html
```
## Enjoy it
Release your website and start experiencing the magical offline feature.
================================================
FILE: docs/quickstart.md
================================================
# Quick start
It is recommended to install `docsify-cli` globally, which helps initializing and previewing the website locally.
```bash
npm i docsify-cli -g
```
## Initialize
If you want to write the documentation in the `./docs` subdirectory, you can use the `init` command.
```bash
docsify init ./docs
```
## Writing content
After the `init` is complete, you can see the file list in the `./docs` subdirectory.
- `index.html` as the entry file
- `README.md` as the home page
- `.nojekyll` prevents GitHub Pages from ignoring files that begin with an underscore
You can easily update the documentation in `./docs/README.md`, and of course you can add [more pages](adding-pages.md).
## Preview your site
Run the local server with `docsify serve`. You can preview your site in your browser on `http://localhost:3000`.
```bash
docsify serve docs
```
> [!TIP] For more use cases of `docsify-cli`, head over to the [docsify-cli documentation](https://github.com/docsifyjs/docsify-cli).
## Manual initialization
Download or create an `index.html` template using the following markup:
### Specifying docsify versions
> [!TIP] Note that in both of the examples below, docsify URLs will need to be
> manually updated when a new major version of docsify is released (e.g. `v5.x.x`
> => `v6.x.x`). Check the docsify website periodically to see if a new major
> version has been released.
Specifying a major version in the URL (`@5`) will allow your site to receive
non-breaking enhancements (i.e. "minor" updates) and bug fixes (i.e. "patch"
updates) automatically. This is the recommended way to load docsify resources.
```html
```
If you'd like to ensure absolutely no possibility of changes that will break
your site (non-major updates can unintentionally introduce breaking changes
despite that they aim not to), specify the full version after the `@` symbol in
the URL. This is the safest way to ensure your site will look and behave the
same way regardless of any changes made to future versions of docsify.
```html
```
JSDelivr supports [npm-compatible semver ranges](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#dependencies),
so you can also use version syntax such as `@^5.0.0` for the latest v5 release,
`@5.0.x` for the latest v5.0 patch release (f.e. you will receive 5.0.4 but
not 5.1.0), `@5.x` for the latest v5 minor and patch releases (which is
effectively the same as `@5` and `@^5.0.0`), etc.
### Manually preview your site
If you have Python installed on your system, you can easily use it to run a
static server to preview your site instead of using `docsify serve` from
`docsify-cli`.
```python
# Python 2
cd docs && python -m SimpleHTTPServer 3000
```
```python
# Python 3
cd docs && python -m http.server 3000
```
================================================
FILE: docs/themes.md
================================================
# Themes
## Core theme
The Docsify "core" theme contains all of the styles and [theme properties](#theme-properties) needed to render a Docsify site. This theme is designed to serve as a minimalist theme on its own, in combination with [theme add-ons](#theme-add-ons), modified using core [classes](#classes), and as a starting point for [customization](#customization).
```html
```
## Theme add-ons
Theme add-ons are used in combination with the [core theme](#core-theme). Add-ons contain CSS rules that modify [theme properties](#theme-properties) values and/or add custom style declarations. They can often (but not always) be used with other add-ons.
> [!IMPORTANT] Theme add-ons must be loaded after the [core theme](#core-theme).
```html
```
### Core Dark (Add-on)
Dark mode styles for the [core theme](#core-theme). Styles can applied only when an operating system's dark mode is active by specifying a `media` attribute.
```html
```
```html
```
### Vue theme (Add-on)
The popular Docsify v4 theme.
```html
```
## Classes
The [core theme](#core-theme) provides several CSS classes for customizing your Docsify site. These classes should be applied to the `` element within your `index.html` page.
```html
```
### Loading
Display a loading animation while waiting for Docsify to initialize.
```html
```
### Sidebar chevrons
Display expand/collapse icons on page links in the sidebar.
```html
```
```html
```
To prevent chevrons from displaying for specific page links, add a `no-chevron` class as follows:
```md
[My Page](page.md ':class=no-chevron')
```
**Theme properties**
```css
:root {
--sidebar-chevron-collapsed-color: var(--color-mono-3);
--sidebar-chevron-expanded-color : var(--theme-color);
}
```
### Sidebar groups
Add visual distinction between groups of links in the sidebar.
```html
```
```html
```
### Sidebar link clamp
Limit multi-line sidebar links to a single line followed by an ellipses.
```html
```
### Sidebar toggle
Display a "hamburger" icon (three lines) in the sidebar toggle button instead of the default "kebab" icon.
```html
```
```html
```
## Customization
Docsify provides [theme properties](#theme-properties) for simplified customization of frequently modified styles.
1. Add a `
```
Theme properties can also be set on a per-page basis in markdown.
```markdown
# My Heading
Hello, World!
```
2. Set custom [theme properties](#theme-properties) within a `:root` declaration.
```css
:root {
--theme-color: red;
--font-size : 15px;
--line-height: 1.5;
}
```
Custom [theme properties](#theme-properties) can be conditionally applied in light and/or dark mode.
```css
/* Light and dark mode */
:root {
--theme-color: pink;
}
/* Light mode only */
@media (prefers-color-scheme: light) {
:root {
--color-bg : #eee;
--color-text: #444;
}
}
/* Dark mode only */
@media screen and (prefers-color-scheme: dark) {
:root {
--color-bg : #222;
--color-text: #ddd;
}
}
```
3. Custom fonts can be used by adding web font resources and modifying `--font-family` properties as needed:
```css
/* Fonts: Noto Sans, Noto Emoji, Noto Mono */
@import url('https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&family=Noto+Sans+Mono:wght@100..900&family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
:root {
--font-family : 'Noto Sans', sans-serif;
--font-family-emoji: 'Noto Color Emoji', sans-serif;
--font-family-mono : 'Noto Sans Mono', monospace;
}
```
> [!TIP] **Theme authors**: Consider providing instructions for loading your recommended web fonts manually instead of including them in your theme using `@import`. This allows users who prefer a different font to avoid loading your recommended web font(s) unnecessarily.
4. Advanced styling may require custom CSS declarations. This is expected, however custom CSS declarations may break when new versions of Docsify are released. When possible, leverage [theme properties](#theme-properties) instead of custom declarations or lock your [CDN](cdn) URLs to a [specific version](cdn#specific-version) to avoid potential issues when using custom CSS declarations.
```css
.sidebar li.active > a {
border-right: 3px solid var(--theme-color);
}
```
## Theme properties
The following properties are available in all official Docsify themes. Default values for the "Core" theme are shown.
> [!TIP] **Theme and plugin authors**: We encourage you to leverage these custom theme properties and to offer similar customization options in your projects.
### Common
Below are the most commonly modified theme properties. [Advanced](#advanced) theme properties are also available for use but typically do not need to be modified.
[\_vars.css](https://raw.githubusercontent.com/docsifyjs/docsify/refs/heads/develop/src/themes/shared/_vars.css ':include')
### Advanced
Advanced theme properties are also available for use but typically do not need to be modified. Values derived from [common](#common) theme properties but can be set explicitly if preferred.
[\_vars-advanced.css](https://raw.githubusercontent.com/docsifyjs/docsify/refs/heads/develop/src/themes/shared/_vars-advanced.css ':include')
## Community
See [Awesome Docsify](awesome) for additional community themes.
================================================
FILE: docs/ui-kit.md
================================================
# UI Kit
View the markdown source for this page
[ui-kit.md](ui-kit.md ':include :type=code')
## Blockquotes
> Cras aliquet nulla quis metus tincidunt, sed placerat enim cursus. Etiam
> turpis nisl, posuere eu condimentum ut, interdum a risus. Sed non luctus mi.
> Quisque malesuada risus sit amet tortor aliquet, a posuere ex iaculis. Vivamus
> ultrices enim dui, eleifend porttitor elit aliquet sed.
>
> _- Quote Source_
#### Nested
> Level 1
> > Level 2
> > > Level 3
## Buttons
#### Default
#### Basic
Link Button
#### Primary
Link Button
#### Secondary
Link Button
## Callouts
> [!CAUTION]
> **Caution** callout with `inline code`.
> [!IMPORTANT]
> **Important** callout with `inline code`.
> [!NOTE]
> **Note** callout with `inline code`.
> [!TIP]
> **Tip** callout with `inline code`.
> [!WARNING]
> **Warning** callout with `inline code`.
**Multi Line**
> [!NOTE]
> - List item 1
> - List item 2
>
> Text
>
> ```html
>
## Details
Details (click to open)
Suscipit nemo aut ex suscipit voluptatem laboriosam odio velit. Ipsum eveniet labore sequi non optio vel. Ut culpa ad accusantium est aut harum ipsam voluptatum. Velit eum incidunt non sint. Et molestiae veniam natus autem vel assumenda ut numquam esse. Non nisi id qui vero corrupti quos et.
Details (open by default)
Suscipit nemo aut ex suscipit voluptatem laboriosam odio velit. Ipsum eveniet labore sequi non optio vel. Ut culpa ad accusantium est aut harum ipsam voluptatum. Velit eum incidunt non sint. Et molestiae veniam natus autem vel assumenda ut numquam esse. Non nisi id qui vero corrupti quos et.
## Form Elements
### Fieldset
### Input
#### Checkbox
#### Datalist
#### Radio
#### Text
#### Toggles
### Select
### Textarea
## Headings
# Heading 1 {docsify-ignore}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse luctus nulla eu ex varius, a varius elit tincidunt. Aenean arcu magna, gravida id purus a, interdum convallis turpis. Aenean id ipsum eu tortor sollicitudin scelerisque in quis elit.
## Heading 2 {docsify-ignore}
Vestibulum lobortis laoreet nunc vel vulputate. In et augue non lectus pellentesque molestie et ac justo. Sed sed turpis ut diam gravida sagittis nec at neque. Vivamus id tellus est. Nam ac dignissim mi. Vestibulum nec sem convallis, condimentum augue at, commodo diam.
### Heading 3 {docsify-ignore}
Suspendisse sit amet tincidunt nibh, ac interdum velit. Ut orci diam, dignissim at enim sit amet, placerat rutrum magna. Mauris consectetur nibh eget sem feugiat, sit amet congue quam laoreet. Curabitur sed massa metus.
#### Heading 4 {docsify-ignore}
Donec odio orci, facilisis ac vehicula in, vestibulum ut urna. Ut bibendum ullamcorper risus, ac euismod leo maximus sed. In pulvinar sagittis rutrum. Morbi quis cursus diam. Cras ac laoreet nulla, rhoncus sodales dui.
##### Heading 5 {docsify-ignore}
Commodo sit veniam nulla cillum labore ullamco aliquip quis. Consequat nulla fugiat consequat ex duis proident. Adipisicing excepteur tempor exercitation ad. Consectetur voluptate Lorem sint elit exercitation ullamco dolor.
###### Heading 6 {docsify-ignore}
Ipsum ea amet dolore mollit incididunt fugiat nulla laboris est sint voluptate. Ex culpa id amet ipsum amet pariatur ipsum officia sit laborum irure ullamco deserunt. Consequat qui tempor occaecat nostrud proident.
## Horizontal Rule
Text before rule.
---
Text after rule.
## IFrame
[Example](_media/example.html ':include height=200px')
## Images
#### Inline-style

#### Reference-style
![Docsify Logo][logo]
[logo]: /_media/icon.svg 'This is the Docsify logo!'
#### Light / Dark Theme
## Keyboard
#### Default
⌃⌥⌫CtrlAltDel⌃ Control⌥ Alt⌫ Delete
#### Alternate
⌃⌥⌫CtrlAltDel⌃ Control⌥ Alt⌫ Delete
#### Entities
↑
↑
Arrow Up
↓
↓
Arrow Down
←
←
Arrow Left
→
→
Arrow Right
⇪
⇪
Caps Lock
⌘
⌘
Command
⌃
⌃
Control
⌫
⌫
Delete
⌦
⌦
Delete (Forward)
↘
↘
End
⌤
⌤
Enter
⎋
⎋
Escape
↖
↖
Home
⇞
⇞
Page Up
⇟
⇟
Page Down
⌥
⌥
Option, Alt
↵
↵
Return
⇧
⇧
Shift
␣
␣
Space
⇥
⇥
Tab
⇤
⇤
Tab + Shift
## Links
[Inline link](https://google.com)
[Inline link with title](https://google.com 'Google')
[Reference link by name][link1]
[Reference link by number][1]
[Reference link by self]
[link1]: https://google.com
[1]: https://google.com
[Reference link by self]: https://google.com
## Lists
### Ordered List
1. Ordered
1. Ordered
1. Nested
1. Nested (Wrapping): Similique tempora et. Voluptatem consequuntur ut. Rerum minus et sed beatae. Consequatur ut nemo laboriosam quo architecto quia qui. Corrupti aut omnis velit.
1. Ordered (Wrapping): Error minima modi rem sequi facere voluptatem. Est nihil veritatis doloribus et corporis ipsam. Pariatur eos ipsam qui odit labore est voluptatem enim. Veritatis est qui ut pariatur inventore.
### Unordered List
- Unordered
- Unordered
- Nested
- Nested (Wrapping): Quia consectetur sint vel ut excepturi ipsa voluptatum suscipit hic. Ipsa error qui molestiae harum laboriosam. Rerum non amet illo voluptatem odio pariatur. Ut minus enim.
- Unordered (Wrapping): Fugiat qui tempore ratione amet repellendus repudiandae non. Rerum nisi officia enim. Itaque est alias voluptatibus id molestiae accusantium. Cupiditate sequi qui omnis sed facere aliquid quia ut.
### Task List
- [x] Task
- [ ] Task
- [ ] Subtask
- [ ] Subtask
- [x] Subtask
- [ ] Task (Wrapping): Earum consequuntur itaque numquam sunt error omnis ipsum repudiandae. Est assumenda neque eum quia quisquam laborum beatae autem ad. Fuga fugiat perspiciatis harum quia dignissimos molestiae. Officia quo eveniet tempore modi voluptates consequatur. Eum odio adipisci labore.
- [x] Subtask (Wrapping): Vel possimus eaque laborum. Voluptates qui debitis quaerat atque molestiae quia explicabo doloremque. Reprehenderit perspiciatis a aut impedit temporibus aut quasi quia. Incidunt sed recusandae vitae asperiores sit in.
## Output
## Tables
### Alignment
| Left Align | Center Align | Right Align | Non‑Breaking Header |
| ---------- | :----------: | ----------: | ------------------------------ |
| A1 | A2 | A3 | A4 |
| B1 | B2 | B3 | B4 |
| C1 | C2 | C3 | C4 |
### Headerless
| | | | |
| --- | --- | --- | --- |
| A1 | A2 | A3 | A4 |
| B1 | B2 | B3 | B4 |
| C1 | C2 | C3 | C4 |
### Scrolling
| Header |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Dicta in nobis dolor adipisci qui. Accusantium voluptates est dolor laboriosam qui voluptatibus. Veritatis eos aspernatur iusto et dicta quas. Fugit voluptatem dolorum qui quisquam. nihil |
| Aut praesentium officia aut delectus. Quas atque reprehenderit saepe. Et voluptatibus qui dolores rem facere in dignissimos id aut. Debitis excepturi delectus et quos numquam magnam. |
| Sed eum atque at laborum aut et repellendus ullam dolor. Cupiditate saepe voluptatibus odit est pariatur qui. Hic sunt nihil optio enim eum laudantium. Repellendus voluptate. |
## Text Elements
Marked text
Preformatted text
Sample Output
Small Text
This is subscript
This is superscript
Underlined Text
## Text Styles
Body text
**Bold text**
_Italic text_
**_Bold and italic text_**
~~Strikethrough~~
================================================
FILE: docs/v5-upgrade.md
================================================
# Upgrading v4 to v5
The main changes when upgrading a Docsify v4 site to v5 involve updating CDN URLs and theme files. Your configuration settings remain mostly the same, so the upgrade is fairly straightforward.
## Before You Begin
Some older Docsify sites may use non-version-locked URLs like:
```html
```
If your site uses URLs without `@4` or a specific version number, follow the same steps below. You'll need to update both the version specifier and the path structure.
## Step-by-Step Instructions
### 1. Update the Theme CSS
**Replace the theme (v4):**
```html
```
**With this (v5):**
```html
```
**Note:** If you were using a different v4 theme (buble, dark, pure), the v5 core theme replaces these, though Vue and Dark themes are available as add-ons if preferred.
View [Themes](themes.md) for more details.
### 2. Add Optional Body Class (for styling)
**Update your opening body tag:**
```html
```
This adds a chevron indicator to the sidebar. You can omit this if you prefer.
View [Theme Classes](themes.md?id=classes) for more details.
### 3. Update the Main Docsify Script
**Change:**
```html
```
**To:**
```html
```
### 4. Update Plugin URLs
**Search Plugin:**
```html
```
**Zoom Plugin:**
```html
```
**Note:** If you're using additional Docsify plugins (such as emoji, external-script, front-matter, etc.), you'll need to update those URLs as well following the same pattern:
- Change `/lib/plugins/` to `/dist/plugins/`
- Update version from `@4` (or non-versioned) to `@5`
- Example: `//cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.min.js` becomes `//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/emoji.min.js`
## Key Differences Summary
- **CDN Path**: Changed from `/lib/` to `/dist/`
- **Version**: Updated from `@4` to `@5`
- **Themes**: v5 uses a core theme (with optional add-ons available)
- **Plugin Names**: `zoom-image` → `zoom`
## Additional Notes
- Your configuration in `window.$docsify` stays the same
- All your markdown content remains unchanged
- The upgrade is non-breaking for most sites (however, legacy browsers like Internet Explorer 11 are no longer supported)
- To maintain the same visual styling as Docsify v4, the [Vue theme (Add-on)](themes.md?id=vue-theme-add-on) is available
- Custom CSS targeting v4 theme-specific classes or elements may need to be updated for v5
- The v5 core theme can be customized using CSS variables - view [Theme Customization](themes.md?id=customization) for more details
That's it! Your Docsify site should now be running on v5.
================================================
FILE: docs/vue.md
================================================
# Vue compatibility
Docsify allows [Vue.js](https://vuejs.org) content to be added directly to your markdown pages. This can greatly simplify working with data and adding reactivity to your site.
Vue [template syntax](https://vuejs.org/guide/essentials/template-syntax) can be used to add dynamic content to your pages. Vue content becomes more interesting when [data](#data), [computed properties](#computed-properties), [methods](#methods), and [lifecycle hooks](#lifecycle-hooks) are used. These options can be specified as [global options](#global-options) or within DOM [mounts](#mounts) and [components](#components).
## Setup
To get started, add Vue.js to your `index.html` file. Choose the production version for your live site or the development version for helpful console warnings and [Vue.js devtools](https://github.com/vuejs/vue-devtools) support.
```html
```
## Template syntax
Vue [template syntax](https://vuejs.org/guide/essentials/template-syntax) offers several useful features like support for [JavaScript expressions](https://vuejs.org/guide/essentials/template-syntax.html#using-javascript-expressions) and Vue [directives](https://vuejs.org/guide/essentials/template-syntax.html#directives) for loops and conditional rendering.
```markdown
Text for GitHub
Item {{ i }}
2 + 2 = {{ 2 + 2 }}
```
[View output on GitHub](https://github.com/docsifyjs/docsify/blob/develop/docs/vue.md#template-syntax)
## Code Blocks
Docsify ignores Vue template syntax within code blocks by default:
````markdown
```
{{ message }}
```
````
To process Vue template syntax within a code block, wrap the code block in an element with a `v-template` attribute:
````markdown
```
## Global options
Use `vueGlobalOptions` to specify global Vue options for use with Vue content not explicitly mounted with [vueMounts](#mounts), [vueComponents](#components), or a [markdown script](#markdown-script). Changes to global `data` will persist and be reflected anywhere global references are used.
```js
window.$docsify = {
vueGlobalOptions: {
data() {
return {
count: 0,
};
},
},
};
```
```markdown
{{ count }}
```
Notice the behavior when multiple global counters are rendered:
Changes made to one counter affect the both counters. This is because both instances reference the same global `count` value. Now, navigate to a new page and return to this section to see how changes made to global data persist between page loads.
## Mounts
Use `vueMounts` to specify DOM elements to mount as Vue instances and their associated options. Mount elements are specified using a [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) as the key with an object containing Vue options as their value. Docsify will mount the first matching element in the main content area each time a new page is loaded. Mount element `data` is unique for each instance and will not persist as users navigate the site.
```js
window.$docsify = {
vueMounts: {
'#counter': {
data() {
return {
count: 0,
};
},
},
},
};
```
```markdown
{{ count }}
```
## Components
Use `vueComponents` to create and register global [Vue components](https://vuejs.org/guide/essentials/component-basics.html). Components are specified using the component name as the key with an object containing Vue options as the value. Component `data` is unique for each instance and will not persist as users navigate the site.
```js
window.$docsify = {
vueComponents: {
'button-counter': {
template: `
`,
data() {
return {
count: 0,
};
},
},
},
};
```
```markdown
```
## Markdown script
Vue content can mounted using a `
```
## Technical Notes
- Docsify processes Vue content in the following order on each page load:
1. Execute markdown `
```
## Template
Below is a plugin template with placeholders for all available lifecycle hooks.
1. Copy the template
1. Modify the `myPlugin` name as appropriate
1. Add your plugin logic
1. Remove unused lifecycle hooks
1. Save the file as `docsify-plugin-[name].js`
1. Load your plugin using a standard `
================================================
FILE: test/consume-types/package.json
================================================
{
"scripts": {
"typecheck": "tsc --noEmit",
"serve": "five-server . --open=false --ignorePattern=node_modules"
},
"dependencies": {
"docsify": "file:../../"
},
"devDependencies": {
"five-server": "^0.4.5",
"typescript": "^5.9.3"
}
}
================================================
FILE: test/consume-types/prism.js
================================================
// Small ESM wrapper, used in the importmap for 'prismjs', to import PrismJS by
// tricking its CommonJS format to see module.exports.
const __prismCjs = await fetch('/node_modules/prismjs/prism.js').then(res =>
res.text(),
);
// Emulate CommonJS environment
const module = { exports: {} };
// eslint-disable-next-line no-unused-vars
const exports = module.exports;
// Evaluate the CommonJS code with module.exports in scope
eval(__prismCjs);
// Export the Prism object
const _Prism = module.exports;
export default _Prism;
// Also make Prism global because Docsify expects it by importing from
// 'prismjs/components/prism-markup-templating.js' in the compiler.
// @ts-expect-error FIXME get rid of this ugly global dependency hack in Docsify.
window.Prism = _Prism;
// @ts-expect-error
// eslint-disable-next-line no-undef
console.log('Prism loaded:', Prism);
================================================
FILE: test/consume-types/register-sw.js
================================================
// This whole thing is ugly. It is only so that we can fix improper import
// statements in libraries from node_modules. See sw.js for how we re-map the
// import URLs.
// The convoluted code here is so that we will force the app to use a new
// service worker if in dev mode we update the service worker code in sw.js
let reloadQueued = false;
function queueReload() {
if (reloadQueued) {
return;
}
reloadQueued = true;
// Use location.reload() after a so any late controllerchange still settles.
window.location.reload();
}
// Register first.
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/',
type: 'module',
updateViaCache: 'none',
});
// If there is already a waiting worker, wait for it to claim this client.
if (registration.waiting) {
const sw = registration.waiting;
await new Promise(resolve => {
sw.addEventListener('statechange', () => {
// Wait until activated.
if (sw.state === 'activated') {
if (sw === navigator.serviceWorker.controller) {
resolve(void 0);
} else {
// If the new SW activated but is not controlling yet, it changed? Not sure if this can actuall happen.
queueReload();
}
}
});
});
}
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.addEventListener('controllerchange', () => {
queueReload();
});
} else {
// First-ever load: wait for controllerchange
await new Promise(resolve => {
navigator.serviceWorker.addEventListener('controllerchange', resolve, {
once: true,
});
});
}
// Track new installs.
registration.addEventListener('updatefound', () => {
const sw = registration.installing;
if (!sw) {
return;
}
sw.addEventListener('statechange', () => {
// When installed AND there is already a controller, a reload will let new SW control.
if (sw.state === 'installed' && navigator.serviceWorker.controller) {
// If skipWaiting ran inside new SW, controllerchange may fire soon; still queue.
queueReload();
}
});
});
// Force update check after listeners.
// For purposes of testing the example, force update on page load so we
// always test the latest service worker.
await registration.update();
// Wait until there is an active worker (first load).
await navigator.serviceWorker.ready;
if (reloadQueued) {
await new Promise(() => {});
}
console.log(
'Service worker ready and controlling. Continue app bootstrap here.',
);
export {};
================================================
FILE: test/consume-types/sw.js
================================================
// The purpose of this service worker is to help with loading
// node_modules modules when using ES modules in the browser.
//
// Specifically, this service worker helps with non-standard module paths
// that do not include file extensions, such as:
//
// /node_modules/some-lib/foo/bar
// /node_modules/some-lib/foo/bar/
//
// In these cases, the service worker will try to resolve them to actual files
// by appending ".js" or "/index.js" as needed.
//
// This service worker only handles requests under /node_modules/.
// All other requests are passed through unmodified.
export {};
const scope = /** @type {ServiceWorkerGlobalScope} */ (
/** @type {any} */ (self)
);
scope.addEventListener('install', event => {
// Always activate worker immediately, for purposes of testing.
event.waitUntil(scope.skipWaiting());
});
scope.addEventListener('activate', event => {
// Always activate worker immediately, for purposes of testing.
event.waitUntil(scope.clients.claim());
});
scope.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// Don't handle non-node_modules paths
if (!url.pathname.startsWith('/node_modules/')) {
event.respondWith(fetch(url.href));
return;
}
// 6
// Special handling for non-standard module paths in node_modules
const parts = url.pathname.split('/');
const fileName = /** @type {string} */ (parts.pop());
const ext = fileName.includes('.') ? fileName.split('.').pop() : '';
// Handle imports like 'some-lib/foo/bar' without an extension.
if (fileName !== '' && ext === '') {
event.respondWith(
// eslint-disable-next-line no-async-promise-executor
new Promise(async resolve => {
try {
// First try adding .js
const response = await tryJs();
const mimeType = response.headers.get('Content-Type') || '';
if (response.ok && mimeType.includes('javascript')) {
resolve(response);
} else {
throw new Error('Not JS');
}
} catch {
// If that fails, try adding /index.js
resolve(await tryIndexJs());
}
async function tryJs() {
const tryJs = new URL(url);
tryJs.href += '.js';
const response = await fetch(tryJs);
return response;
}
async function tryIndexJs() {
const tryIndexJs = new URL(url);
tryIndexJs.href += '/index.js';
const response = await fetch(tryIndexJs);
return response;
}
}),
);
return;
}
// Handle imports like 'some-lib/foo/bar/' (ending with a slash).
if (fileName === '') {
// adding index.js
const tryIndexJs = new URL(url);
tryIndexJs.href += 'index.js';
event.respondWith(fetch(tryIndexJs));
return;
}
// For all other cases, just fetch normally.
event.respondWith(fetch(url));
});
================================================
FILE: test/consume-types/tsconfig.json
================================================
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"module": "esnext",
"moduleResolution": "node",
"target": "esnext",
"strict": true,
"noEmit": true,
"lib": ["DOM", "ESNext", "WebWorker"],
"skipLibCheck": true,
"skipDefaultLibCheck": true
}
}
================================================
FILE: test/e2e/configuration.test.js
================================================
/* global fail */
import docsifyInit from '../helpers/docsify-init.js';
import { test, expect } from './fixtures/docsify-init-fixture.js';
test.describe('Configuration options', () => {
test.describe('catchPluginErrors', () => {
test('true (handles uncaught errors)', async ({ page }) => {
let consoleMsg, errorMsg;
page.on('console', msg => (consoleMsg = msg.text()));
page.on('pageerror', err => (errorMsg = err.message));
await docsifyInit({
config: {
catchPluginErrors: true,
plugins: [
function (hook, vm) {
hook.init(function () {
fail();
});
hook.beforeEach(markdown => {
return `${markdown}\n\nbeforeEach`;
});
},
],
},
markdown: {
homepage: '# Hello World',
},
// _logHTML: true,
});
const mainElm = page.locator('#main');
expect(errorMsg).toBeUndefined();
expect(consoleMsg).toContain('Docsify plugin error');
await expect(mainElm).toContainText('Hello World');
await expect(mainElm).toContainText('beforeEach');
});
test('false (throws uncaught errors)', async ({ page }) => {
let consoleMsg, errorMsg;
page.on('console', msg => {
const text = msg.text();
if (text.startsWith('DEPRECATION:')) {
return;
} // ignore expected deprecation warnings
consoleMsg = text;
});
page.on('pageerror', err => (errorMsg = err.message));
await docsifyInit({
config: {
catchPluginErrors: false,
plugins: [
function (hook, vm) {
hook.ready(function () {
fail();
});
},
],
},
markdown: {
homepage: '# Hello World',
},
// _logHTML: true,
});
expect(consoleMsg).toBeUndefined();
expect(errorMsg).toContain('fail');
});
});
test.describe('notFoundPage', () => {
test.describe('renders default error content', () => {
test.beforeEach(async ({ page }) => {
await page.route('README.md', async route => {
await route.fulfill({
status: 500,
});
});
});
test('false', async ({ page }) => {
await docsifyInit({
config: {
notFoundPage: false,
},
});
await expect(page.locator('#main')).toContainText('500');
});
test('true with non-404 error', async ({ page }) => {
await docsifyInit({
config: {
notFoundPage: true,
},
routes: {
'_404.md': '',
},
});
await expect(page.locator('#main')).toContainText('500');
});
test('string with non-404 error', async ({ page }) => {
await docsifyInit({
config: {
notFoundPage: '404.md',
},
routes: {
'404.md': '',
},
});
await expect(page.locator('#main')).toContainText('500');
});
});
test('true: renders _404.md page', async ({ page }) => {
const expectText = 'Pass';
await docsifyInit({
config: {
notFoundPage: true,
},
routes: {
'_404.md': expectText,
},
});
await page.evaluate(() => (window.location.hash = '#/fail'));
await expect(page.locator('#main')).toContainText(expectText);
});
test('string: renders specified 404 page', async ({ page }) => {
const expectText = 'Pass';
await docsifyInit({
config: {
notFoundPage: '404.md',
},
routes: {
'404.md': expectText,
},
});
await page.evaluate(() => (window.location.hash = '#/fail'));
await expect(page.locator('#main')).toContainText(expectText);
});
});
});
test.describe('keyBindings', () => {
test('handles toggleSidebar binding (default)', async ({ page }) => {
const docsifyInitConfig = {
markdown: {
homepage: `
# Heading 1
`,
},
};
await docsifyInit(docsifyInitConfig);
const sidebarElm = page.locator('.sidebar');
await expect(sidebarElm).toHaveClass(/show/);
await page.keyboard.press('\\');
await expect(sidebarElm).not.toHaveClass(/show/);
});
test('handles custom binding', async ({ page }) => {
const docsifyInitConfig = {
config: {
keyBindings: {
customBinding: {
bindings: 'z',
callback(e) {
const elm = document.querySelector('main input[type="text"]');
elm.value = 'foo';
},
},
},
},
markdown: {
homepage: `
`,
},
};
const inputElm = page.locator('main input[type="text"]');
await docsifyInit(docsifyInitConfig);
await expect(inputElm).toHaveValue('');
await page.keyboard.press('z');
await expect(inputElm).toHaveValue('foo');
});
test('ignores event when focused on text input elements', async ({
page,
}) => {
const docsifyInitConfig = {
config: {
keyBindings: {
customBinding: {
bindings: 'z',
callback(e) {
document.body.setAttribute('data-foo', '');
},
},
},
},
markdown: {
homepage: `
`,
},
};
const bodyElm = page.locator('body');
const inputElm = page.locator('input[type="text"]');
const selectElm = page.locator('select');
const textareaElm = page.locator('textarea');
await docsifyInit(docsifyInitConfig);
await inputElm.focus();
await expect(inputElm).toHaveValue('');
await page.keyboard.press('z');
await expect(inputElm).toHaveValue('z');
await inputElm.blur();
await textareaElm.focus();
await expect(textareaElm).toHaveValue('');
await page.keyboard.press('z');
await expect(textareaElm).toHaveValue('z');
await textareaElm.blur();
await selectElm.focus();
await page.keyboard.press('z');
await expect(selectElm).toHaveValue('z');
await selectElm.blur();
await expect(bodyElm).not.toHaveAttribute('data-foo');
await page.keyboard.press('z');
await expect(bodyElm).toHaveAttribute('data-foo');
});
});
================================================
FILE: test/e2e/example.test.js
================================================
import docsifyInit from '../helpers/docsify-init.js';
import { test, expect } from './fixtures/docsify-init-fixture.js';
test.describe('Creating a Docsify site (e2e tests in Playwright)', () => {
test('manual docsify site using playwright methods', async ({ page }) => {
// Add docsify target element
await page.setContent('');
// Set docsify configuration
await page.evaluate(() => {
window.$docsify = {
el: '#app',
themeColor: 'red',
};
});
// Inject docsify theme
await page.addStyleTag({ url: '/dist/themes/core.css' });
// Inject docsify.js
await page.addScriptTag({ url: '/dist/docsify.js' });
// Wait for docsify to initialize
await page.locator('#main').waitFor();
// Create handle for JavaScript object in browser
const $docsify = await page.evaluate(() => window.$docsify);
// const $docsify = await page.evaluateHandle(() => window.$docsify);
// Test object property and value
expect($docsify).toHaveProperty('themeColor', 'red');
});
test('Docsify /docs/ site using docsifyInit()', async ({ page }) => {
// Load custom docsify
// (See ./helpers/docsifyInit.js for details)
await docsifyInit({
// _logHTML: true,
});
// Verify docsifyInitConfig.markdown content was rendered
const mainElm = page.locator('#main');
await expect(mainElm).toHaveCount(1);
await expect(mainElm).toContainText(
'A magical documentation site generator',
);
});
test('custom docsify site using docsifyInit()', async ({ page }) => {
const docsifyInitConfig = {
config: {
name: 'Docsify Name',
themeColor: 'red',
},
markdown: {
coverpage: `
# Docsify Test
> Testing a magical documentation site generator
[GitHub](https://github.com/docsifyjs/docsify/)
`,
homepage: `
# Hello World
This is the homepage.
`,
navbar: `
- [docsify.js.org](https://docsify.js.org/#/)
`,
sidebar: `
- [Test Page](test)
`,
},
routes: {
'test.md': `
# Test Page
This is a custom route.
`,
'data-test-scripturls.js': `
document.body.setAttribute('data-test-scripturls', 'pass');
`,
},
script: `
document.body.setAttribute('data-test-script', 'pass');
`,
scriptURLs: [
// docsifyInit() route
'data-test-scripturls.js',
// Server route
'/dist/plugins/search.js',
],
style: `
body {
background: red !important;
}
`,
styleURLs: ['/dist/themes/core.css'],
};
await docsifyInit({
...docsifyInitConfig,
// _logHTML: true,
});
const $docsify = await page.evaluate(() => window.$docsify);
// Verify config options
expect(typeof $docsify).toEqual('object');
expect($docsify).toHaveProperty('themeColor', 'red');
await expect(page.locator('.app-name')).toHaveText('Docsify Name');
// Verify docsifyInitConfig.markdown content was rendered
await expect(page.locator('section.cover h1')).toHaveText('Docsify Test'); // Coverpage
await expect(page.locator('nav.app-nav')).toHaveText('docsify.js.org'); // Navbar
await expect(page.locator('aside.sidebar')).toContainText('Test Page'); // Sidebar
await expect(page.locator('#main')).toContainText('This is the homepage'); // Homepage
// Verify docsifyInitConfig.scriptURLs were added to the DOM
for (const scriptURL of docsifyInitConfig.scriptURLs) {
await expect(page.locator(`script[src$="${scriptURL}"]`)).toHaveCount(1);
}
// Verify docsifyInitConfig.scriptURLs were executed
await expect(page.locator('body[data-test-scripturls]')).toHaveCount(1);
await expect(page.locator('.search input[type="search"]')).toHaveCount(1);
// Verify docsifyInitConfig.script was added to the DOM
expect(
await page.evaluate(
scriptText => {
return [...document.querySelectorAll('script')].some(
elm => elm.textContent.replace(/\s+/g, '') === scriptText,
);
},
docsifyInitConfig.script.replace(/\s+/g, ''),
),
).toBe(true);
// Verify docsifyInitConfig.script was executed
await expect(page.locator('body[data-test-script]')).toHaveCount(1);
// Verify docsifyInitConfig.styleURLs were added to the DOM
for (const styleURL of docsifyInitConfig.styleURLs) {
await expect(
page.locator(`link[rel*="stylesheet"][href$="${styleURL}"]`),
).toHaveCount(1);
}
// Verify docsifyInitConfig.style was added to the DOM
expect(
await page.evaluate(
styleText => {
return [...document.querySelectorAll('style')].some(
elm => elm.textContent.replace(/\s+/g, '') === styleText,
);
},
docsifyInitConfig.style.replace(/\s+/g, ''),
),
).toBe(true);
// Verify docsify navigation and docsifyInitConfig.routes
await page.click('a[href="#/test"]');
expect(page.url()).toMatch(/\/test$/);
await expect(page.locator('#main')).toContainText('This is a custom route');
});
// test.fixme('image snapshots', async ({ page }) => {
// await docsifyInit({
// config: {
// name: 'Docsify Test',
// },
// markdown: {
// homepage: `
// # The Cosmos Awaits
// [Carl Sagan](https://en.wikipedia.org/wiki/Carl_Sagan)
// Cosmic ocean take root and flourish decipherment hundreds of thousands
// dream of the mind's eye courage of our questions. At the edge of forever
// network of wormholes ship of the imagination two ghostly white figures
// in coveralls and helmets are softly dancing are creatures of the cosmos
// the only home we've ever known? How far away emerged into consciousness
// bits of moving fluff gathered by gravity with pretty stories for which
// there's little good evidence vanquish the impossible.
// The ash of stellar alchemy permanence of the stars shores of the cosmic
// ocean billions upon billions Drake Equation finite but unbounded.
// Hundreds of thousands cosmic ocean hearts of the stars Hypatia invent
// the universe hearts of the stars? Realm of the galaxies muse about dream
// of the mind's eye hundreds of thousands the only home we've ever known
// how far away. Extraordinary claims require extraordinary evidence
// citizens of distant epochs invent the universe as a patch of light the
// carbon in our apple pies gathered by gravity.
// Billions upon billions gathered by gravity white dwarf intelligent
// beings vanquish the impossible descended from astronomers. A still more
// glorious dawn awaits cosmic ocean star stuff harvesting star light the
// sky calls to us kindling the energy hidden in matter rich in heavy
// atoms. A mote of dust suspended in a sunbeam across the centuries the
// only home we've ever known bits of moving fluff a very small stage in a
// vast cosmic arena courage of our questions.
// Euclid the only home we've ever known realm of the galaxies trillion
// radio telescope Apollonius of Perga. The carbon in our apple pies invent
// the universe muse about stirred by starlight great turbulent clouds
// emerged into consciousness? Invent the universe vastness is bearable
// only through love a still more glorious dawn awaits descended from
// astronomers as a patch of light the sky calls to us. Great turbulent
// clouds citizens of distant epochs invent the universe two ghostly white
// figures in coveralls and helmets are softly dancing courage of our
// questions rich in heavy atoms and billions upon billions upon billions
// upon billions upon billions upon billions upon billions.
// `,
// },
// styleURLs: [`/dist/themes/core.css`],
// // _logHTML: true,
// });
// // Viewport screenshot
// const viewportShot = await page.screenshot();
// expect(viewportShot).toMatchSnapshot('viewport.png');
// // Element screenshot
// const elmHandle = await page.locator('h1').first();
// const elmShot = await elmHandle.screenshot();
// expect(elmShot).toMatchSnapshot('element.png');
// });
});
================================================
FILE: test/e2e/fixtures/docsify-init-fixture.js
================================================
import { test as _test, expect as _expect } from '@playwright/test';
export const test = _test.extend({
page: async ({ page }, use) => {
global.page = page;
// Navigate to a real URL by default
// Playwright tests are executed on "about:blank" by default, which will
// cause operations that require the window location to be a valid URL to
// fail (e.g. AJAX requests). Navigating to a blank document with a real
// URL solved this problem.
await page.goto('/_blank.html');
await use(page);
},
});
export const expect = _expect;
================================================
FILE: test/e2e/gtag.test.js
================================================
import docsifyInit from '../helpers/docsify-init.js';
import { test, expect } from './fixtures/docsify-init-fixture.js';
const gtagList = [
'AW-YYYYYY', // Google Ads
'DC-ZZZZZZ', // Floodlight
'G-XXXXXX', // Google Analytics 4 (GA4)
'UA-XXXXXX', // Google Universal Analytics (GA3)
];
test.describe('Gtag Plugin Tests', () => {
// page request listened, print collect url
function pageRequestListened(page) {
page.on('request', request => {
if (request.url().indexOf('www.google-analytics.com') !== -1) {
// console.log(request.url());
}
});
page.on('response', response => {
const request = response.request();
// googleads.g.doubleclick.net
// www.google-analytics.com
// www.googletagmanager.com
const reg =
/googleads\.g\.doubleclick\.net|www\.google-analytics\.com|www\.googletagmanager\.com/g;
if (request.url().match(reg)) {
// console.log(request.url(), response.status());
}
});
}
test('single gtag', async ({ page }) => {
pageRequestListened(page);
const docsifyInitConfig = {
config: {
gtag: gtagList[0],
},
scriptURLs: ['/dist/plugins/gtag.js'],
styleURLs: ['/dist/themes/core.css'],
};
await docsifyInit({
...docsifyInitConfig,
});
const $docsify = await page.evaluate(() => window.$docsify);
// Verify config options
expect(typeof $docsify).toEqual('object');
// console.log($docsify.gtag, $docsify.gtag === '');
// Tests
expect($docsify.gtag).not.toEqual('');
});
test('multi gtag', async ({ page }) => {
pageRequestListened(page);
const docsifyInitConfig = {
config: {
gtag: gtagList,
},
scriptURLs: ['/dist/plugins/gtag.js'],
styleURLs: ['/dist/themes/core.css'],
};
await docsifyInit({
...docsifyInitConfig,
});
const $docsify = await page.evaluate(() => window.$docsify);
// Verify config options
expect(typeof $docsify).toEqual('object');
// console.log($docsify.gtag, $docsify.gtag === '');
// Tests
expect($docsify.gtag).not.toEqual('');
});
});
================================================
FILE: test/e2e/index-file.test.js
================================================
import docsifyInit from '../helpers/docsify-init.js';
import { test, expect } from './fixtures/docsify-init-fixture.js';
test.describe('Index file hosting', () => {
const sharedOptions = {
config: {
basePath: '/index.html#/',
},
testURL: '/index.html#/',
};
test('should serve from index file', async ({ page }) => {
await docsifyInit(sharedOptions);
await expect(page.locator('#main')).toContainText(
'A magical documentation site generator',
);
expect(page.url()).toMatch(/index\.html#\/$/);
});
test('should use index file links in sidebar from index file hosting', async ({
page,
}) => {
await docsifyInit(sharedOptions);
await page.click('a[href="#/quickstart"]');
await expect(page.locator('#main')).toContainText('Quick start');
expect(page.url()).toMatch(/index\.html#\/quickstart$/);
});
});
================================================
FILE: test/e2e/plugins.test.js
================================================
import docsifyInit from '../helpers/docsify-init.js';
import { waitForFunction } from '../helpers/wait-for.js';
import { test, expect } from './fixtures/docsify-init-fixture.js';
test.describe('Plugins', () => {
test('Hook order', async ({ page }) => {
const consoleMsgs = [];
const expectedMsgs = [
'init',
'mounted',
'beforeEach-async',
'beforeEach',
'afterEach-async',
'afterEach',
'doneEach',
'ready',
];
page.on('console', msg => {
const text = msg.text();
if (text.startsWith('DEPRECATION:')) {
return;
} // ignore expected deprecation warnings
consoleMsgs.push(text);
});
await docsifyInit({
config: {
plugins: [
function (hook, vm) {
hook.init(() => {
console.log('init');
});
hook.mounted(() => {
console.log('mounted');
});
hook.beforeEach((markdown, next) => {
setTimeout(() => {
console.log('beforeEach-async');
next(markdown);
}, 100);
});
hook.beforeEach(markdown => {
console.log('beforeEach');
return markdown;
});
hook.afterEach((html, next) => {
setTimeout(() => {
console.log('afterEach-async');
next(html);
}, 100);
});
hook.afterEach(html => {
console.log('afterEach');
return html;
});
hook.doneEach(() => {
console.log('doneEach');
});
hook.ready(() => {
console.log('ready');
});
},
],
},
markdown: {
homepage: '# Hello World',
},
// _logHTML: true,
});
expect(consoleMsgs).toEqual(expectedMsgs);
});
test.describe('beforeEach()', () => {
test('return value', async ({ page }) => {
await docsifyInit({
config: {
plugins: [
function (hook, vm) {
hook.beforeEach(markdown => {
return 'beforeEach';
});
},
],
},
// _logHTML: true,
});
await expect(page.locator('#main')).toContainText('beforeEach');
});
test('async return value', async ({ page }) => {
await docsifyInit({
config: {
plugins: [
function (hook, vm) {
hook.beforeEach((markdown, next) => {
setTimeout(() => {
next('beforeEach');
}, 100);
});
},
],
},
markdown: {
homepage: '# Hello World',
},
// _logHTML: true,
});
await expect(page.locator('#main')).toContainText('beforeEach');
});
});
test.describe('afterEach()', () => {
test('return value', async ({ page }) => {
await docsifyInit({
config: {
plugins: [
function (hook, vm) {
hook.afterEach(html => {
return '
"',
);
});
test('unordered with custom start', async function () {
const output = window.marked(stripIndent`
1. first
2. second
text
3. third
`);
expect(output).toMatchInlineSnapshot(
'"
first
second
text
third
"',
);
});
test('nested', async function () {
const output = window.marked(stripIndent`
- 1
- 2
- 2 a
- 2 b
- 3
`);
expect(output).toMatchInlineSnapshot(
'"