Ensure code format (#2138)

* chore: add missing Vue support for Vercel builds

* refactor: move some functions and module-level state into classes as private methods and properties to start to encapsulate Docsify

Also some small tweaks:

- move initGlobalAPI out of Docsify.js to start to encapsulate Docsify
- move ajax to utils folder
- fix some type definitions and improve content in some JSDoc comments
- use concise class field syntax
- consolidate duplicate docsify-ignore comment removal code

This handles a task in [Simplify and modernize Docsify](https://github.com/docsifyjs/docsify/issues/2104), as well as works towards [Encapsulating Docsify](https://github.com/docsifyjs/docsify/issues/2135).

* chore: add prettier code format check to our lint script, and add a prettier script for manually formatting the whole code base

* chore: update issue/pr templates

* chore: apply our format to the whole code base


---------

Co-authored-by: Koy <koy@ko8e24.top>
Co-authored-by: i544693 <369491420@qq.com>
This commit is contained in:
Joe Pea 2023-08-09 02:53:30 -07:00 committed by GitHub
parent 6464cf533d
commit 7bbf13d9bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1241 additions and 1176 deletions

View File

@ -1,44 +1,53 @@
---
name: Bug report
about: Create a report to help us improve
about: Create a report to help us fix an issue
---
<!-- Please don't delete this template or we'll close your issue -->
<!-- Please use English language -->
<!-- Before creating an issue please make sure you are using the latest version of Docsify. -->
<!-- Please ask questions on StackOverflow: https://stackoverflow.com/questions/ask?tags=docsify -->
<!-- Please ask questions on Discord: https://discord.gg/3NwKFyR -->
<!--
Please don't delete this template or we'll close your issue.
Please write in English.
Before creating an issue please make sure you are using the latest version of Docsify.
Please ask questions on StackOverflow (👉 https://stackoverflow.com/questions/ask?tags=docsify)
or in our Discord chat server (👉 https://discord.gg/docsify).
-->
## Bug Report
#### Steps to reproduce
<!-- List the steps needed to reproduce here. -->
#### Current behaviour
#### What is current behaviour
#### What is the expected behaviour
<!-- Describe the current behavior here. -->
#### Expected behaviour
<!-- Describe the expected behavior here. -->
#### Other relevant information
<!-- (Update "[ ]" to "[x]" to check a box) -->
- [ ] Bug does still occur when all/other plugins are disabled?
- Docsify version:
- Your OS:
- Node.js version:
- npm/yarn version:
- Browser version:
- Docsify version:
- Docsify plugins:
<!-- (Change "[ ]" to "[x]" to check a box.) -->
<!-- Love docsify? Please consider supporting our collective:
👉 https://opencollective.com/docsify/donate -->
- [ ] Bug still occurs when all/other plugins are disabled?
#### Please create a reproducible sandbox
- Docsify plugins (if the bug happens when plugins enabled, please try to isolate the issue):
<!-- Please provide the following information if relevant to the issue.
- Your OS:
- Node.js version:
- npm/yarn version:
- Browser version:
-->
<!-- Love docsify and want to help us advance Docsify or to spend time supporting your case? Please consider supporting our collective:
👉 https://opencollective.com/docsify/donate -->
#### Please create a reproducible sandbox
[![Edit 307qqv236](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/307qqv236)

View File

@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Discord - the community chat
url: https://discord.gg/3NwKFyR
about: Join Discord community and chat about Docsify
url: https://discord.gg/docsify
about: Join the Discord community and chat about Docsify

View File

@ -1,25 +1,32 @@
---
name: Feature request
about: Suggest an idea for this project
---
<!-- Please don't delete this template or we'll close your issue -->
<!-- Please use English language -->
<!-- Before creating an issue please make sure you are using the latest version of Docsify. -->
<!-- Please ask questions on StackOverflow 👉 https://stackoverflow.com/questions/ask?tags=docsify -->
<!--
Please don't delete this template or we'll close your issue.
Please write in English.
Before creating an issue please make sure you are using the latest version of Docsify.
Please ask questions on StackOverflow (👉 https://stackoverflow.com/questions/ask?tags=docsify)
or in our Discord chat server (👉 https://discord.gg/docsify).
-->
## Feature request
#### What problem does this feature solve?
#### Problem or desire
<!-- Describe what problem this feature will solve. -->
#### Proposal
#### What does the proposed API look like?
<!-- What does the proposed feature look like? -->
#### Implementation
<!--
How should the feature be implemented in your opinion?
#### How should this be implemented in your opinion?
#### Are you willing to work on this yourself?
We have limited free time. Please consider contributing, or donating to our
OpenCollective (👉 https://opencollective.com/docsify/donate), to help get this
feature added and to advance Docsify.
-->

View File

@ -1,31 +1,25 @@
<!--
PULL REQUEST TEMPLATE
---
Please use English language
Please don't delete this template
---
Update "[ ]" to "[x]" to check a box in any list below.
---
To avoid wasting your time, it's best to open a **feature request issue** first and wait for approval before working on it.
Please write in English.
Please follow the template, all sections are required.
Consider opening a feature request first to get your change idea approved.
-->
## **Summary**
## Summary
<!--
THIS IS REQUIRED! Please describe what the change does and why it should be merged.
Describe what the change does and why it should be merged.
Provide **before/after** screenshots for any UI changes.
-->
<!--
If changing the UI in any way, please provide the a **before/after** screenshot:
-->
## Related issue, if any:
## **What kind of change does this PR introduce?**
<!-- Paste issue's link or number hashtag here. -->
## What kind of change does this PR introduce?
<!--
Copy/paste one of the following options:
-->
Copy/paste any of the relevant following options:
<!--
Bugfix
Feature
Code style update
@ -34,29 +28,27 @@
Build-related changes
Repo settings
Other
If you choose Other, describe it.
-->
<!--
If you chose Other, please describe.
-->
## For any code change,
## **For any code change,**
<!-- (Change "[ ]" to "[x]" to check a box.) -->
- [ ] Related documentation has been updated if needed
- [ ] Related tests have been updated or tests have been added
- [ ] Related documentation has been updated, if needed
- [ ] Related tests have been added or updated, if needed
## **Does this PR introduce a breaking change?** (check one)
## Does this PR introduce a breaking change?
- [ ] Yes
- [ ] No
<!-- (pick one) -->
If yes, please describe the impact and migration path for existing applications:
Yes
No
## **Related issue, if any:**
<!-- If yes, describe the impact and migration path for existing applications. -->
<!-- Paste issue's link or number hashtag here. -->
## **Tested in the following browsers:**
## Tested in the following browsers:
- [ ] Chrome
- [ ] Firefox

View File

@ -6,12 +6,12 @@
version: 2
updates:
- package-ecosystem: npm # See documentation for possible values
directory: "/" # Location of package manifests
directory: '/' # Location of package manifests
open-pull-requests-limit: 10
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
interval: 'weekly'
- package-ecosystem: 'github-actions'
directory: '/'
open-pull-requests-limit: 10
schedule:
interval: monthly

View File

@ -7,4 +7,4 @@ ports:
onOpen: ignore
vscode:
extensions:
- sysoev.language-stylus@1.11.0:xX39oruAJ5UQzTNVRdbBaQ==
- sysoev.language-stylus@1.11.0:xX39oruAJ5UQzTNVRdbBaQ==

6
.prettierignore Normal file
View File

@ -0,0 +1,6 @@
CHANGELOG.md
HISTORY.md
lib/
themes/
_playwright-*/
emoji-data.*

View File

@ -1,3 +1,4 @@
<!-- Historical history file (do not edit). -->
<a name="3.7.3"></a>
## [3.7.3](https://github.com/QingWei-Li/docsify/compare/v3.7.2...v3.7.3) (2017-05-22)

View File

@ -27,7 +27,7 @@
</a>
</p>
Docsify turns one or more Markdown files into a Website, with no build process required.
Docsify turns one or more Markdown files into a Website, with no build process required.
## Features
@ -57,14 +57,6 @@ A large collection of showcase projects are included in [awesome-docsify](https:
- [Awesome docsify](https://github.com/docsifyjs/awesome-docsify)
- [Community chat](https://discord.gg/3NwKFyR)
## Similar Projects
| Project | Description |
| ------------------------------------------------ | ---------------------------------------- |
| [Docusaurus](https://docusaurus.io) | Docusaurus makes it easy to maintain Open Source documentation websites |
| [MkDocs](https://www.mkdocs.org) | Project documentation with Markdown |
| [VitePress](https://vitepress.dev/) | Vite & Vue Powered Static Site Generator |
## Contributing
See [CONTRIBUTING.md](./CONTRIBUTING.md).

View File

@ -6,7 +6,7 @@ If you believe you have found a security vulnerability in docsify, please report
**Please do not report security vulnerabilities through our public GitHub issues.**
Send email via :email: maintainers@docsifyjs.org to us.
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:

View File

@ -32,83 +32,89 @@ async function build(opts) {
nodeResolve(),
replace({
__VERSION__: version,
})
}),
],
onwarn(message) {
if (message.code === 'UNRESOLVED_IMPORT') {
throw new Error(
`Could not resolve module ` +
message.source +
`. Try running 'npm install' or using rollup's 'external' option if this is an external dependency. ` +
`Module ${message.source} is imported in ${message.importer}`
)
message.source +
`. Try running 'npm install' or using rollup's 'external' option if this is an external dependency. ` +
`Module ${message.source} is imported in ${message.importer}`
);
}
}
},
})
.then(bundle => {
const dest = 'lib/' + (opts.output || opts.input)
const dest = 'lib/' + (opts.output || opts.input);
console.log(dest)
console.log(dest);
return bundle.write({
format: 'iife',
output: opts.globalName ? {name: opts.globalName} : {},
output: opts.globalName ? { name: opts.globalName } : {},
file: dest,
strict: false
})
})
strict: false,
});
});
}
async function buildCore() {
const promises = []
const promises = [];
promises.push(build({
input: 'src/core/index.js',
output: 'docsify.js',
}))
promises.push(
build({
input: 'src/core/index.js',
output: 'docsify.js',
})
);
if (isProd) {
promises.push(build({
input: 'src/core/index.js',
output: 'docsify.min.js',
plugins: [uglify()]
}))
promises.push(
build({
input: 'src/core/index.js',
output: 'docsify.min.js',
plugins: [uglify()],
})
);
}
await Promise.all(promises)
await Promise.all(promises);
}
async function buildAllPlugin() {
const plugins = [
{name: 'search', input: 'search/index.js'},
{name: 'ga', input: 'ga.js'},
{name: 'gtag', input: 'gtag.js'},
{name: 'matomo', input: 'matomo.js'},
{name: 'emoji', input: 'emoji.js'},
{name: 'external-script', input: 'external-script.js'},
{name: 'front-matter', input: 'front-matter/index.js'},
{name: 'zoom-image', input: 'zoom-image.js'},
{name: 'disqus', input: 'disqus.js'},
{name: 'gitalk', input: 'gitalk.js'}
]
{ name: 'search', input: 'search/index.js' },
{ name: 'ga', input: 'ga.js' },
{ name: 'gtag', input: 'gtag.js' },
{ name: 'matomo', input: 'matomo.js' },
{ name: 'emoji', input: 'emoji.js' },
{ name: 'external-script', input: 'external-script.js' },
{ name: 'front-matter', input: 'front-matter/index.js' },
{ name: 'zoom-image', input: 'zoom-image.js' },
{ name: 'disqus', input: 'disqus.js' },
{ name: 'gitalk', input: 'gitalk.js' },
];
const promises = plugins.map(item => {
return build({
input: 'src/plugins/' + item.input,
output: 'plugins/' + item.name + '.js'
})
})
output: 'plugins/' + item.name + '.js',
});
});
if (isProd) {
plugins.forEach(item => {
promises.push(build({
input: 'src/plugins/' + item.input,
output: 'plugins/' + item.name + '.min.js',
plugins: [uglify()]
}))
})
promises.push(
build({
input: 'src/plugins/' + item.input,
output: 'plugins/' + item.name + '.min.js',
plugins: [uglify()],
})
);
});
}
await Promise.all(promises)
await Promise.all(promises);
}
async function main() {
@ -118,41 +124,37 @@ async function main() {
atomic: true,
awaitWriteFinish: {
stabilityThreshold: 1000,
pollInterval: 100
}
pollInterval: 100,
},
})
.on('change', p => {
console.log('[watch] ', p)
const dirs = p.split(path.sep)
console.log('[watch] ', p);
const dirs = p.split(path.sep);
if (dirs[1] === 'core') {
buildCore()
buildCore();
} else if (dirs[2]) {
const name = path.basename(dirs[2], '.js')
const name = path.basename(dirs[2], '.js');
const input = `src/plugins/${name}${
/\.js/.test(dirs[2]) ? '' : '/index'
}.js`
}.js`;
build({
input,
output: 'plugins/' + name + '.js'
})
output: 'plugins/' + name + '.js',
});
}
})
.on('ready', () => {
console.log('[start]')
buildCore()
buildAllPlugin()
})
console.log('[start]');
buildCore();
buildAllPlugin();
});
} else {
await Promise.all([
buildCore(),
buildAllPlugin()
])
await Promise.all([buildCore(), buildAllPlugin()]);
}
}
main().catch((e) => {
console.error(e)
process.exit(1)
})
main().catch(e => {
console.error(e);
process.exit(1);
});

View File

@ -1,47 +1,48 @@
import fs from 'fs'
import path from 'path'
import {spawn} from 'child_process'
import fs from 'fs';
import path from 'path';
import { spawn } from 'child_process';
const relative = path => new URL(path, import.meta.url);
const args = process.argv.slice(2)
const args = process.argv.slice(2);
fs.readdir(relative('../src/themes'), (err, files) => {
if (err) {
console.error('err', err)
process.exit(1)
}
files.map(async (file) => {
if (/\.styl/g.test(file)) {
const stylusBin = ['node_modules', 'stylus', 'bin', 'stylus'].join(path.sep)
let cmdargs = [
stylusBin,
`src/themes/${file}`,
'-u',
'autoprefixer-stylus'
]
cmdargs = [...cmdargs, ...args]
if (err) {
console.error('err', err);
process.exit(1);
}
files.map(async file => {
if (/\.styl/g.test(file)) {
const stylusBin = ['node_modules', 'stylus', 'bin', 'stylus'].join(
path.sep
);
let cmdargs = [
stylusBin,
`src/themes/${file}`,
'-u',
'autoprefixer-stylus',
];
cmdargs = [...cmdargs, ...args];
const stylusCMD = spawn('node', cmdargs, { shell: true })
const stylusCMD = spawn('node', cmdargs, { shell: true });
stylusCMD.stdout.on('data', (data) => {
console.log(`[Stylus Build ] stdout: ${data}`);
});
stylusCMD.stdout.on('data', data => {
console.log(`[Stylus Build ] stdout: ${data}`);
});
stylusCMD.stderr.on('data', (data) => {
console.error(`[Stylus Build ] stderr: ${data}`);
});
stylusCMD.stderr.on('data', data => {
console.error(`[Stylus Build ] stderr: ${data}`);
});
stylusCMD.on('close', (code) => {
const message = `[Stylus Build ] child process exited with code ${code}`
stylusCMD.on('close', code => {
const message = `[Stylus Build ] child process exited with code ${code}`;
if (code !== 0) {
console.error(message);
process.exit(code)
}
console.log(message);
});
} else {
return
if (code !== 0) {
console.error(message);
process.exit(code);
}
})
})
console.log(message);
});
} else {
return;
}
});
});

View File

@ -1,15 +1,18 @@
import cssnano from 'cssnano';
import path from 'path'
import fs from 'fs'
import path from 'path';
import fs from 'fs';
const files = fs.readdirSync(path.resolve('lib/themes'))
const files = fs.readdirSync(path.resolve('lib/themes'));
files.forEach(file => {
file = path.resolve('lib/themes', file)
cssnano.process(fs.readFileSync(file)).then(result => {
fs.writeFileSync(file, result.css)
}).catch(e => {
console.error(e)
process.exit(1)
})
})
file = path.resolve('lib/themes', file);
cssnano
.process(fs.readFileSync(file))
.then(result => {
fs.writeFileSync(file, result.css);
})
.catch(e => {
console.error(e);
process.exit(1);
});
});

View File

@ -1 +1 @@
<h1>To infinity and Beyond!</h1>
<h1>To infinity and Beyond!</h1>

View File

@ -6,7 +6,7 @@ Recommended: [jsDelivr](//cdn.jsdelivr.net), which will reflect the latest versi
```html
<!-- load css -->
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/themes/vue.css">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/themes/vue.css" />
<!-- load script -->
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.js"></script>
@ -18,7 +18,10 @@ Alternatively, use [compressed files](#compressed-file).
```html
<!-- load css -->
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4.10.2/themes/vue.css">
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify@4.10.2/themes/vue.css"
/>
<!-- load script -->
<script src="//cdn.jsdelivr.net/npm/docsify@4.10.2/lib/docsify.js"></script>
@ -28,7 +31,10 @@ Alternatively, use [compressed files](#compressed-file).
```html
<!-- load css -->
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/lib/themes/vue.css">
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify/lib/themes/vue.css"
/>
<!-- load script -->
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
@ -36,7 +42,10 @@ Alternatively, use [compressed files](#compressed-file).
```html
<!-- load css -->
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4.10.2/lib/themes/vue.css">
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify@4.10.2/lib/themes/vue.css"
/>
<!-- load script -->
<script src="//cdn.jsdelivr.net/npm/docsify@4.10.2/lib/docsify.min.js"></script>

View File

@ -11,8 +11,8 @@ Set `coverpage` to **true**, and create a `_coverpage.md`:
<script>
window.$docsify = {
coverpage: true
}
coverpage: true,
};
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
```
@ -81,7 +81,7 @@ Now, you can set
```js
window.$docsify = {
coverpage: ['/', '/zh-cn/']
coverpage: ['/', '/zh-cn/'],
};
```
@ -91,7 +91,7 @@ Or a special file name
window.$docsify = {
coverpage: {
'/': 'cover.md',
'/zh-cn/': 'cover.md'
}
'/zh-cn/': 'cover.md',
},
};
```

View File

@ -27,8 +27,8 @@ Alternatively, you can create a custom markdown-based navigation file by setting
<script>
window.$docsify = {
loadNavbar: true
}
loadNavbar: true,
};
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
```
@ -36,8 +36,8 @@ Alternatively, you can create a custom markdown-based navigation file by setting
```markdown
<!-- _navbar.md -->
* [En](/)
* [chinese](/zh-cn/)
- [En](/)
- [chinese](/zh-cn/)
```
!> You need to create a `.nojekyll` in `./docs` to prevent GitHub Pages from ignoring files that begin with an underscore.
@ -51,19 +51,19 @@ You can create sub-lists by indenting items that are under a certain parent.
```markdown
<!-- _navbar.md -->
* Getting started
- Getting started
* [Quick start](quickstart.md)
* [Writing more pages](more-pages.md)
* [Custom navbar](custom-navbar.md)
* [Cover page](cover.md)
- [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)
- Configuration
- [Configuration](configuration.md)
- [Themes](themes.md)
- [Using plugins](plugins.md)
- [Markdown configuration](markdown.md)
- [Language highlight](language-highlight.md)
```
renders as
@ -80,7 +80,7 @@ If you use the [emoji plugin](plugins#emoji):
<script>
window.$docsify = {
// ...
}
};
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.min.js"></script>
@ -91,6 +91,6 @@ you could, for example, use flag emojis in your custom navbar Markdown file:
```markdown
<!-- _navbar.md -->
* [:us:, :uk:](/)
* [:cn:](/zh-cn/)
- [:us:, :uk:](/)
- [:cn:](/zh-cn/)
```

View File

@ -96,14 +96,14 @@ When using the HTML5 router, you need to set up redirect rules that redirect all
## AWS Amplify
1. Set the routerMode in the Docsify project `index.html` to *history* mode.
1. Set the routerMode in the Docsify project `index.html` to _history_ mode.
```html
<script>
window.$docsify = {
loadSidebar: true,
routerMode: 'history'
}
window.$docsify = {
loadSidebar: true,
routerMode: 'history',
};
</script>
```
@ -125,17 +125,15 @@ frontend:
- '**/*'
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) |
| -------------- | -------------- | ------------- |
| /<\*>.md | /<\*>.md | 200 (Rewrite) |
| /<\*>.png | /<\*>.png | 200 (Rewrite) |
| /<\*> | /index.html | 200 (Rewrite) |
## Docker
@ -144,10 +142,10 @@ frontend:
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
```
```sh
index.html
README.md
```
- Create Dockerfile
@ -180,4 +178,3 @@ frontend:
```sh
docker run -itp 3000:3000 --name=docsify -v $(pwd):/docs docsify/demo
```

View File

@ -26,11 +26,11 @@ Currently, file extensions are automatically recognized and embedded in differen
These types are supported:
* **iframe** `.html`, `.htm`
* **markdown** `.markdown`, `.md`
* **audio** `.mp3`
* **video** `.mp4`, `.ogg`
* **code** other file extension
- **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`.
@ -74,6 +74,7 @@ Example:
If you embed the file as `iframe`, `audio` and `video`, then you may need to set the attributes of these tags.
?> 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%')
```
@ -114,11 +115,11 @@ Start by viewing a gist on `gist.github.com`. For the purposes of this guide, we
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.
| 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:

View File

@ -2,10 +2,10 @@
Docsify uses [Prism](https://prismjs.com) to highlight code blocks in your pages. Prism supports the following languages by default:
* Markup - `markup`, `html`, `xml`, `svg`, `mathml`, `ssml`, `atom`, `rss`
* CSS - `css`
* C-like - `clike`
* JavaScript - `javascript`, `js`
- Markup - `markup`, `html`, `xml`, `svg`, `mathml`, `ssml`, `atom`, `rss`
- CSS - `css`
- C-like - `clike`
- JavaScript - `javascript`, `js`
Support for [additional languages](https://prismjs.com/#supported-languages) is available by loading the language-specific [grammar files](https://cdn.jsdelivr.net/npm/prismjs@1/components/) via CDN:
@ -29,7 +29,7 @@ echo "hello"
```
```php
function getAdder(int $x): int
function getAdder(int $x): int
{
return 123;
}
@ -48,18 +48,19 @@ echo "hello"
```
```php
function getAdder(int $x): int
function getAdder(int $x): int
{
return 123;
}
```
## Highlighting Dynamic Content
Code blocks [dynamically created from javascript](https://docsify.js.org/#/configuration?id=executescript) can be highlighted using the method `Prism.highlightElement` like so:
```javascript
const code = document.createElement("code");
const code = document.createElement('code');
code.innerHTML = "console.log('Hello World!')";
code.setAttribute("class", "lang-javascript");
code.setAttribute('class', 'lang-javascript');
Prism.highlightElement(code);
```

View File

@ -9,10 +9,10 @@ window.$docsify = {
renderer: {
link() {
// ...
}
}
}
}
},
},
},
};
```
?> Configuration Options Reference: [marked documentation](https://marked.js.org/#/USING_ADVANCED.md)
@ -24,9 +24,9 @@ window.$docsify = {
markdown(marked, renderer) {
// ...
return marked
}
}
return marked;
},
};
```
## Supports mermaid
@ -43,14 +43,17 @@ window.$docsify = {
markdown: {
renderer: {
code(code, lang) {
if (lang === "mermaid") {
if (lang === 'mermaid') {
return /* html */ `
<div class="mermaid">${mermaid.render('mermaid-svg-' + num++, code)}</div>
<div class="mermaid">${mermaid.render(
'mermaid-svg-' + num++,
code
)}</div>
`;
}
return this.origin.code.apply(this, arguments);
}
}
}
}
},
},
},
};
```

View File

@ -34,8 +34,8 @@ First, you need to set `loadSidebar` to **true**. Details are available in the [
<script>
window.$docsify = {
loadSidebar: true
}
loadSidebar: true,
};
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
```
@ -45,8 +45,8 @@ Create the `_sidebar.md`:
```markdown
<!-- docs/_sidebar.md -->
* [Home](/)
* [Guide](guide.md)
- [Home](/)
- [Guide](guide.md)
```
You need to create a `.nojekyll` in `./docs` to prevent GitHub Pages from ignoring files that begin with an underscore.
@ -76,9 +76,9 @@ You can specify `alias` to avoid unnecessary fallback.
window.$docsify = {
loadSidebar: true,
alias: {
'/.*/_sidebar.md': '/_sidebar.md'
}
}
'/.*/_sidebar.md': '/_sidebar.md',
},
};
</script>
```
@ -90,8 +90,9 @@ A page's `title` tag is generated from the _selected_ sidebar item name. For bet
```markdown
<!-- docs/_sidebar.md -->
* [Home](/)
* [Guide](guide.md "The greatest guide in the world")
- [Home](/)
- [Guide](guide.md 'The greatest guide in the world')
```
## Table of Contents
@ -106,8 +107,8 @@ A custom sidebar can also automatically generate a table of contents by setting
<script>
window.$docsify = {
loadSidebar: true,
subMaxLevel: 2
}
subMaxLevel: 2,
};
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
```

View File

@ -8,7 +8,7 @@ It is also very easy to use.
Create a `sw.js` file in your project's root directory and copy the following code:
*sw.js*
_sw.js_
```js
/* ===========================================================
@ -19,24 +19,24 @@ Create a `sw.js` file in your project's root directory and copy the following co
* Register service worker.
* ========================================================== */
const RUNTIME = 'docsify'
const RUNTIME = 'docsify';
const HOSTNAME_WHITELIST = [
self.location.hostname,
'fonts.gstatic.com',
'fonts.googleapis.com',
'cdn.jsdelivr.net'
]
'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)
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
url.protocol = self.location.protocol;
// 2. add query for caching-busting.
// Github Pages served with Cache-Control: max-age=600
@ -44,10 +44,10 @@ const getFixedUrl = (req) => {
// 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
url.search += (url.search ? '&' : '?') + 'cache-bust=' + now;
}
return url.href
}
return url.href;
};
/**
* @Lifecycle Activate
@ -56,8 +56,8 @@ const getFixedUrl = (req) => {
* waitUntil(): activating ====> activated
*/
self.addEventListener('activate', event => {
event.waitUntil(self.clients.claim())
})
event.waitUntil(self.clients.claim());
});
/**
* @Functional Fetch
@ -71,10 +71,10 @@ self.addEventListener('fetch', event => {
// 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())
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.
@ -83,29 +83,36 @@ self.addEventListener('fetch', event => {
event.respondWith(
Promise.race([fetched.catch(_ => cached), cached])
.then(resp => resp || fetched)
.catch(_ => { /* eat any errors */ })
)
.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 */ })
)
.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*
_index.html_
```html
<script>
if (typeof navigator.serviceWorker !== 'undefined') {
navigator.serviceWorker.register('sw.js')
navigator.serviceWorker.register('sw.js');
}
</script>
```

View File

@ -8,7 +8,12 @@
<link rel="stylesheet" href="/themes/dark.css" title="dark" disabled />
<link rel="stylesheet" href="/themes/buble.css" title="buble" disabled />
<link rel="stylesheet" href="/themes/pure.css" title="pure" disabled />
<link rel="stylesheet" href="/themes/dolphin.css" title="dolphin" disabled />
<link
rel="stylesheet"
href="/themes/dolphin.css"
title="dolphin"
disabled
/>
<style>
nav.app-nav li ul {
min-width: 100px;

View File

@ -41,8 +41,9 @@
"docker:test:integration": "npm run docker:cli -- test:integration",
"docker:test:unit": "npm run docker:cli -- test:unit",
"docker:test": "npm run docker:cli -- test",
"prettier": "prettier . --write",
"lint:fix": "eslint . --fix",
"lint": "eslint .",
"lint": "prettier . --check && eslint .",
"postinstall": "opencollective-postinstall",
"prepare": "npm run build",
"pub:next": "cross-env RELEASE_TAG=next sh build/release.sh",

View File

@ -3,7 +3,6 @@ import { Render } from './render/index.js';
import { Fetch } from './fetch/index.js';
import { Events } from './event/index.js';
import { VirtualRoutes } from './virtual-routes/index.js';
import initGlobalAPI from './global-api.js';
import config from './config.js';
import { isFn } from './util/core.js';
@ -16,11 +15,11 @@ export class Docsify extends Fetch(
// eslint-disable-next-line new-cap
Events(Render(VirtualRoutes(Router(Lifecycle(Object)))))
) {
config = config(this);
constructor() {
super();
this.config = config(this);
this.initLifecycle(); // Init hooks
this.initPlugin(); // Install plugins
this.callHook('init');
@ -46,8 +45,3 @@ export class Docsify extends Fetch(
});
}
}
/**
* Global API
*/
initGlobalAPI();

View File

@ -3,7 +3,7 @@ import { hyphenate, isPrimitive } from './util/core.js';
const currentScript = document.currentScript;
/** @param {import('./Docsify').Docsify} vm */
/** @param {import('./Docsify.js').Docsify} vm */
export default function (vm) {
const config = Object.assign(
{

View File

@ -1,9 +1,11 @@
import Tweezer from 'tweezer.js';
import { isMobile } from '../util/env.js';
import { body, on } from '../util/dom.js';
import * as sidebar from './sidebar.js';
import { scrollIntoView, scroll2Top } from './scroll.js';
import * as dom from '../util/dom.js';
import { removeParams } from '../router/util.js';
import config from '../config.js';
/** @typedef {import('../Docsify').Constructor} Constructor */
/** @typedef {import('../Docsify.js').Constructor} Constructor */
/**
* @template {!Constructor} T
@ -18,29 +20,300 @@ export function Events(Base) {
if (source !== 'history') {
// Scroll to ID if specified
if (this.route.query.id) {
scrollIntoView(this.route.path, this.route.query.id);
this.#scrollIntoView(this.route.path, this.route.query.id);
}
// Scroll to top if a link was clicked and auto2top is enabled
if (source === 'navigate') {
auto2top && scroll2Top(auto2top);
auto2top && this.#scroll2Top(auto2top);
}
}
if (this.config.loadNavbar) {
sidebar.getAndActive(this.router, 'nav');
this.__getAndActive(this.router, 'nav');
}
}
initEvent() {
// Bind toggle button
sidebar.btn('button.sidebar-toggle', this.router);
sidebar.collapse('.sidebar', this.router);
this.#btn('button.sidebar-toggle', this.router);
this.#collapse('.sidebar', this.router);
// Bind sticky effect
if (this.config.coverpage) {
!isMobile && on('scroll', sidebar.sticky);
!isMobile && on('scroll', this.__sticky);
} else {
body.classList.add('sticky');
}
}
/** @readonly */
#nav = {};
#hoverOver = false;
#scroller = null;
#enableScrollEvent = true;
#coverHeight = 0;
#scrollTo(el, offset = 0) {
if (this.#scroller) {
this.#scroller.stop();
}
this.#enableScrollEvent = false;
this.#scroller = new Tweezer({
start: window.pageYOffset,
end:
Math.round(el.getBoundingClientRect().top) +
window.pageYOffset -
offset,
duration: 500,
})
.on('tick', v => window.scrollTo(0, v))
.on('done', () => {
this.#enableScrollEvent = true;
this.#scroller = null;
})
.begin();
}
#highlight(path) {
if (!this.#enableScrollEvent) {
return;
}
const sidebar = dom.getNode('.sidebar');
const anchors = dom.findAll('.anchor');
const wrap = dom.find(sidebar, '.sidebar-nav');
let active = dom.find(sidebar, 'li.active');
const doc = document.documentElement;
const top =
((doc && doc.scrollTop) || document.body.scrollTop) - this.#coverHeight;
let last;
for (const node of anchors) {
if (node.offsetTop > top) {
if (!last) {
last = node;
}
break;
} else {
last = node;
}
}
if (!last) {
return;
}
const li = this.#nav[this.#getNavKey(path, last.getAttribute('data-id'))];
if (!li || li === active) {
return;
}
active && active.classList.remove('active');
li.classList.add('active');
active = li;
// Scroll into view
// https://github.com/vuejs/vuejs.org/blob/master/themes/vue/source/js/common.js#L282-L297
if (!this.#hoverOver && dom.body.classList.contains('sticky')) {
const height = sidebar.clientHeight;
const curOffset = 0;
const cur = active.offsetTop + active.clientHeight + 40;
const isInView =
active.offsetTop >= wrap.scrollTop && cur <= wrap.scrollTop + height;
const notThan = cur - curOffset < height;
sidebar.scrollTop = isInView
? wrap.scrollTop
: notThan
? curOffset
: cur - height;
}
}
#getNavKey(path, id) {
return `${decodeURIComponent(path)}?id=${decodeURIComponent(id)}`;
}
__scrollActiveSidebar(router) {
const cover = dom.find('.cover.show');
this.#coverHeight = cover ? cover.offsetHeight : 0;
const sidebar = dom.getNode('.sidebar');
let lis = [];
if (sidebar !== null && sidebar !== undefined) {
lis = dom.findAll(sidebar, 'li');
}
for (const li of lis) {
const a = li.querySelector('a');
if (!a) {
continue;
}
let href = a.getAttribute('href');
if (href !== '/') {
const {
query: { id },
path,
} = router.parse(href);
if (id) {
href = this.#getNavKey(path, id);
}
}
if (href) {
this.#nav[decodeURIComponent(href)] = li;
}
}
if (isMobile) {
return;
}
const path = removeParams(router.getCurrentPath());
dom.off('scroll', () => this.#highlight(path));
dom.on('scroll', () => this.#highlight(path));
dom.on(sidebar, 'mouseover', () => {
this.#hoverOver = true;
});
dom.on(sidebar, 'mouseleave', () => {
this.#hoverOver = false;
});
}
#scrollIntoView(path, id) {
if (!id) {
return;
}
const topMargin = config().topMargin;
// Use [id='1234'] instead of #id to handle special cases such as reserved characters and pure number id
// https://stackoverflow.com/questions/37270787/uncaught-syntaxerror-failed-to-execute-queryselector-on-document
const section = dom.find("[id='" + id + "']");
section && this.#scrollTo(section, topMargin);
const li = this.#nav[this.#getNavKey(path, id)];
const sidebar = dom.getNode('.sidebar');
const active = dom.find(sidebar, 'li.active');
active && active.classList.remove('active');
li && li.classList.add('active');
}
#scrollEl = dom.$.scrollingElement || dom.$.documentElement;
#scroll2Top(offset = 0) {
this.#scrollEl.scrollTop = offset === true ? 0 : Number(offset);
}
/** @readonly */
#title = dom.$.title;
/**
* Toggle button
* @param {Element} el Button to be toggled
* @void
*/
#btn(el) {
const toggle = _ => dom.body.classList.toggle('close');
el = dom.getNode(el);
if (el === null || el === undefined) {
return;
}
dom.on(el, 'click', e => {
e.stopPropagation();
toggle();
});
isMobile &&
dom.on(
dom.body,
'click',
_ => dom.body.classList.contains('close') && toggle()
);
}
#collapse(el) {
el = dom.getNode(el);
if (el === null || el === undefined) {
return;
}
dom.on(el, 'click', ({ target }) => {
if (
target.nodeName === 'A' &&
target.nextSibling &&
target.nextSibling.classList &&
target.nextSibling.classList.contains('app-sub-sidebar')
) {
dom.toggleClass(target.parentNode, 'collapse');
}
});
}
__sticky = () => {
const cover = dom.getNode('section.cover');
if (!cover) {
return;
}
const coverHeight = cover.getBoundingClientRect().height;
if (
window.pageYOffset >= coverHeight ||
cover.classList.contains('hidden')
) {
dom.toggleClass(dom.body, 'add', 'sticky');
} else {
dom.toggleClass(dom.body, 'remove', 'sticky');
}
};
/**
* Get and active link
* @param {Object} router Router
* @param {String|Element} el Target element
* @param {Boolean} isParent Active parent
* @param {Boolean} autoTitle Automatically set title
* @return {Element} Active element
*/
__getAndActive(router, el, isParent, autoTitle) {
el = dom.getNode(el);
let links = [];
if (el !== null && el !== undefined) {
links = dom.findAll(el, 'a');
}
const hash = decodeURI(router.toURL(router.getCurrentPath()));
let target;
links
.sort((a, b) => b.href.length - a.href.length)
.forEach(a => {
const href = decodeURI(a.getAttribute('href'));
const node = isParent ? a.parentNode : a;
a.title = a.title || a.innerText;
if (hash.indexOf(href) === 0 && !target) {
target = a;
dom.toggleClass(node, 'add', 'active');
} else {
dom.toggleClass(node, 'remove', 'active');
}
});
if (autoTitle) {
dom.$.title = target
? target.title || `${target.innerText} - ${this.#title}`
: this.#title;
}
return target;
}
};
}

View File

@ -1,163 +0,0 @@
import Tweezer from 'tweezer.js';
import { isMobile } from '../util/env.js';
import * as dom from '../util/dom.js';
import { removeParams } from '../router/util.js';
import config from '../config.js';
const nav = {};
let hoverOver = false;
let scroller = null;
let enableScrollEvent = true;
let coverHeight = 0;
function scrollTo(el, offset = 0) {
if (scroller) {
scroller.stop();
}
enableScrollEvent = false;
scroller = new Tweezer({
start: window.pageYOffset,
end:
Math.round(el.getBoundingClientRect().top) + window.pageYOffset - offset,
duration: 500,
})
.on('tick', v => window.scrollTo(0, v))
.on('done', () => {
enableScrollEvent = true;
scroller = null;
})
.begin();
}
function highlight(path) {
if (!enableScrollEvent) {
return;
}
const sidebar = dom.getNode('.sidebar');
const anchors = dom.findAll('.anchor');
const wrap = dom.find(sidebar, '.sidebar-nav');
let active = dom.find(sidebar, 'li.active');
const doc = document.documentElement;
const top = ((doc && doc.scrollTop) || document.body.scrollTop) - coverHeight;
let last;
for (const node of anchors) {
if (node.offsetTop > top) {
if (!last) {
last = node;
}
break;
} else {
last = node;
}
}
if (!last) {
return;
}
const li = nav[getNavKey(path, last.getAttribute('data-id'))];
if (!li || li === active) {
return;
}
active && active.classList.remove('active');
li.classList.add('active');
active = li;
// Scroll into view
// https://github.com/vuejs/vuejs.org/blob/master/themes/vue/source/js/common.js#L282-L297
if (!hoverOver && dom.body.classList.contains('sticky')) {
const height = sidebar.clientHeight;
const curOffset = 0;
const cur = active.offsetTop + active.clientHeight + 40;
const isInView =
active.offsetTop >= wrap.scrollTop && cur <= wrap.scrollTop + height;
const notThan = cur - curOffset < height;
sidebar.scrollTop = isInView
? wrap.scrollTop
: notThan
? curOffset
: cur - height;
}
}
function getNavKey(path, id) {
return `${decodeURIComponent(path)}?id=${decodeURIComponent(id)}`;
}
export function scrollActiveSidebar(router) {
const cover = dom.find('.cover.show');
coverHeight = cover ? cover.offsetHeight : 0;
const sidebar = dom.getNode('.sidebar');
let lis = [];
if (sidebar !== null && sidebar !== undefined) {
lis = dom.findAll(sidebar, 'li');
}
for (const li of lis) {
const a = li.querySelector('a');
if (!a) {
continue;
}
let href = a.getAttribute('href');
if (href !== '/') {
const {
query: { id },
path,
} = router.parse(href);
if (id) {
href = getNavKey(path, id);
}
}
if (href) {
nav[decodeURIComponent(href)] = li;
}
}
if (isMobile) {
return;
}
const path = removeParams(router.getCurrentPath());
dom.off('scroll', () => highlight(path));
dom.on('scroll', () => highlight(path));
dom.on(sidebar, 'mouseover', () => {
hoverOver = true;
});
dom.on(sidebar, 'mouseleave', () => {
hoverOver = false;
});
}
export function scrollIntoView(path, id) {
if (!id) {
return;
}
const topMargin = config().topMargin;
// Use [id='1234'] instead of #id to handle special cases such as reserved characters and pure number id
// https://stackoverflow.com/questions/37270787/uncaught-syntaxerror-failed-to-execute-queryselector-on-document
const section = dom.find("[id='" + id + "']");
section && scrollTo(section, topMargin);
const li = nav[getNavKey(path, id)];
const sidebar = dom.getNode('.sidebar');
const active = dom.find(sidebar, 'li.active');
active && active.classList.remove('active');
li && li.classList.add('active');
}
const scrollEl = dom.$.scrollingElement || dom.$.documentElement;
export function scroll2Top(offset = 0) {
scrollEl.scrollTop = offset === true ? 0 : Number(offset);
}

View File

@ -1,106 +0,0 @@
/* eslint-disable no-unused-vars */
import { isMobile } from '../util/env.js';
import * as dom from '../util/dom.js';
const title = dom.$.title;
/**
* Toggle button
* @param {Element} el Button to be toggled
* @void
*/
export function btn(el) {
const toggle = _ => dom.body.classList.toggle('close');
el = dom.getNode(el);
if (el === null || el === undefined) {
return;
}
dom.on(el, 'click', e => {
e.stopPropagation();
toggle();
});
isMobile &&
dom.on(
dom.body,
'click',
_ => dom.body.classList.contains('close') && toggle()
);
}
export function collapse(el) {
el = dom.getNode(el);
if (el === null || el === undefined) {
return;
}
dom.on(el, 'click', ({ target }) => {
if (
target.nodeName === 'A' &&
target.nextSibling &&
target.nextSibling.classList &&
target.nextSibling.classList.contains('app-sub-sidebar')
) {
dom.toggleClass(target.parentNode, 'collapse');
}
});
}
export function sticky() {
const cover = dom.getNode('section.cover');
if (!cover) {
return;
}
const coverHeight = cover.getBoundingClientRect().height;
if (window.pageYOffset >= coverHeight || cover.classList.contains('hidden')) {
dom.toggleClass(dom.body, 'add', 'sticky');
} else {
dom.toggleClass(dom.body, 'remove', 'sticky');
}
}
/**
* Get and active link
* @param {Object} router Router
* @param {String|Element} el Target element
* @param {Boolean} isParent Active parent
* @param {Boolean} autoTitle Automatically set title
* @return {Element} Active element
*/
export function getAndActive(router, el, isParent, autoTitle) {
el = dom.getNode(el);
let links = [];
if (el !== null && el !== undefined) {
links = dom.findAll(el, 'a');
}
const hash = decodeURI(router.toURL(router.getCurrentPath()));
let target;
links
.sort((a, b) => b.href.length - a.href.length)
.forEach(a => {
const href = decodeURI(a.getAttribute('href'));
const node = isParent ? a.parentNode : a;
a.title = a.title || a.innerText;
if (hash.indexOf(href) === 0 && !target) {
target = a;
dom.toggleClass(node, 'add', 'active');
} else {
dom.toggleClass(node, 'remove', 'active');
}
});
if (autoTitle) {
dom.$.title = target
? target.title || `${target.innerText} - ${title}`
: title;
}
return target;
}

View File

@ -1,70 +1,70 @@
/* eslint-disable no-unused-vars */
import { getParentPath, stringifyQuery } from '../router/util.js';
import { noop, isExternal } from '../util/core.js';
import { getAndActive } from '../event/sidebar.js';
import { get } from './ajax.js';
import { get } from '../util/ajax.js';
function loadNested(path, qs, file, next, vm, first) {
path = first ? path : path.replace(/\/$/, '');
path = getParentPath(path);
if (!path) {
return;
}
get(
vm.router.getFile(path + file) + qs,
false,
vm.config.requestHeaders
).then(next, _error => loadNested(path, qs, file, next, vm));
}
/** @typedef {import('../Docsify').Constructor} Constructor */
/** @typedef {import('../Docsify.js').Constructor} Constructor */
/**
* @template {!Constructor} T
* @param {T} Base - The class to extend
*/
export function Fetch(Base) {
let last;
return class Fetch extends Base {
#loadNested(path, qs, file, next, vm, first) {
path = first ? path : path.replace(/\/$/, '');
path = getParentPath(path);
const abort = () => last && last.abort && last.abort();
const request = (url, requestHeaders) => {
abort();
last = get(url, true, requestHeaders);
return last;
};
if (!path) {
return;
}
const get404Path = (path, config) => {
const { notFoundPage, ext } = config;
const defaultPath = '_404' + (ext || '.md');
let key;
let path404;
switch (typeof notFoundPage) {
case 'boolean':
path404 = defaultPath;
break;
case 'string':
path404 = notFoundPage;
break;
case 'object':
key = Object.keys(notFoundPage)
.sort((a, b) => b.length - a.length)
.filter(k => path.match(new RegExp('^' + k)))[0];
path404 = (key && notFoundPage[key]) || defaultPath;
break;
default:
break;
get(
vm.router.getFile(path + file) + qs,
false,
vm.config.requestHeaders
).then(next, _error => this.#loadNested(path, qs, file, next, vm));
}
return path404;
};
#last;
#abort = () => this.#last && this.#last.abort && this.#last.abort();
#request = (url, requestHeaders) => {
this.#abort();
this.#last = get(url, true, requestHeaders);
return this.#last;
};
#get404Path = (path, config) => {
const { notFoundPage, ext } = config;
const defaultPath = '_404' + (ext || '.md');
let key;
let path404;
switch (typeof notFoundPage) {
case 'boolean':
path404 = defaultPath;
break;
case 'string':
path404 = notFoundPage;
break;
case 'object':
key = Object.keys(notFoundPage)
.sort((a, b) => b.length - a.length)
.filter(k => path.match(new RegExp('^' + k)))[0];
path404 = (key && notFoundPage[key]) || defaultPath;
break;
default:
break;
}
return path404;
};
return class Fetch extends Base {
_loadSideAndNav(path, qs, loadSidebar, cb) {
return () => {
if (!loadSidebar) {
@ -77,7 +77,7 @@ export function Fetch(Base) {
};
// Load sidebar
loadNested(path, qs, loadSidebar, fn, this, true);
this.#loadNested(path, qs, loadSidebar, fn, this, true);
};
}
@ -121,7 +121,7 @@ export function Fetch(Base) {
if (typeof contents === 'string') {
contentFetched(contents);
} else {
request(file + qs, requestHeaders).then(
this.#request(file + qs, requestHeaders).then(
contentFetched,
contentFailedToFetch
);
@ -129,7 +129,7 @@ export function Fetch(Base) {
});
} else {
// if the requested url is not local, just fetch the file
request(file + qs, requestHeaders).then(
this.#request(file + qs, requestHeaders).then(
contentFetched,
contentFailedToFetch
);
@ -137,7 +137,7 @@ export function Fetch(Base) {
// Load nav
loadNavbar &&
loadNested(
this.#loadNested(
path,
qs,
loadNavbar,
@ -216,7 +216,7 @@ export function Fetch(Base) {
const newPath = this.router.getFile(
path.replace(new RegExp(`^/${local}`), '')
);
const req = request(newPath + qs, requestHeaders);
const req = this.#request(newPath + qs, requestHeaders);
req.then(
(text, opt) =>
@ -244,9 +244,9 @@ export function Fetch(Base) {
const fnLoadSideAndNav = this._loadSideAndNav(path, qs, loadSidebar, cb);
if (notFoundPage) {
const path404 = get404Path(path, this.config);
const path404 = this.#get404Path(path, this.config);
request(this.router.getFile(path404), requestHeaders).then(
this.#request(this.router.getFile(path404), requestHeaders).then(
(text, opt) => this._renderMain(text, opt, fnLoadSideAndNav),
_error => this._renderMain(null, {}, fnLoadSideAndNav)
);
@ -262,7 +262,12 @@ export function Fetch(Base) {
// Server-Side Rendering
if (this.rendered) {
const activeEl = getAndActive(this.router, '.sidebar-nav', true, true);
const activeEl = this.__getAndActive(
this.router,
'.sidebar-nav',
true,
true
);
if (loadSidebar && activeEl) {
activeEl.parentNode.innerHTML += window.__SUB_SIDEBAR__;
}

View File

@ -4,13 +4,13 @@ import * as util from './util/index.js';
import * as dom from './util/dom.js';
import { Compiler } from './render/compiler.js';
import { slugify } from './render/slugify.js';
import { get } from './fetch/ajax.js';
import { get } from './util/ajax.js';
// TODO This is deprecated, kept for backwards compatibility. Remove in next
// TODO This is deprecated, kept for backwards compatibility. Remove in a
// major release. We'll tell people to get everything from the DOCSIFY global
// when using the global build, but we'll highly recommend for them to import
// from the ESM build (f.e. lib/docsify.esm.js and lib/docsify.min.esm.js).
export default function () {
export default function initGlobalAPI() {
window.Docsify = {
util,
dom,

View File

@ -1,8 +1,17 @@
import { documentReady } from './util/dom.js';
import { Docsify } from './Docsify.js';
import initGlobalAPI from './global-api.js';
// TODO This global API and auto-running Docsify will be deprecated, and removed
// in a major release. Instead we'll tell users to use `new Docsify()` to create
// and manage their instance(s).
/**
* Global API
*/
initGlobalAPI();
/**
* Run Docsify
*/
// eslint-disable-next-line no-unused-vars
documentReady(_ => new Docsify());
documentReady(() => new Docsify());

View File

@ -1,6 +1,6 @@
import { noop } from '../util/core.js';
/** @typedef {import('../Docsify').Constructor} Constructor */
/** @typedef {import('../Docsify.js').Constructor} Constructor */
/**
* @template {!Constructor} T
@ -8,6 +8,9 @@ import { noop } from '../util/core.js';
*/
export function Lifecycle(Base) {
return class Lifecycle extends Base {
_hooks = {};
_lifecycle = {};
initLifecycle() {
const hooks = [
'init',
@ -18,9 +21,6 @@ export function Lifecycle(Base) {
'ready',
];
this._hooks = {};
this._lifecycle = {};
hooks.forEach(hook => {
const arr = (this._hooks[hook] = []);
this._lifecycle[hook] = fn => arr.push(fn);

View File

@ -8,7 +8,7 @@ import { emojify } from './emojify.js';
import {
getAndRemoveConfig,
removeAtag,
getAndRemoveDocisfyIgnorConfig,
getAndRemoveDocisfyIgnoreConfig,
} from './utils.js';
import { imageCompiler } from './compiler/image.js';
import { highlightCodeCompiler } from './compiler/code.js';
@ -214,7 +214,7 @@ export class Compiler {
const nextToc = { level, title: str };
const { content, ignoreAllSubs, ignoreSubHeading } =
getAndRemoveDocisfyIgnorConfig(str);
getAndRemoveDocisfyIgnoreConfig(str);
str = content.trim();
nextToc.title = removeAtag(str);

View File

@ -1,7 +1,7 @@
import {
getAndRemoveConfig,
removeAtag,
getAndRemoveDocisfyIgnorConfig,
getAndRemoveDocisfyIgnoreConfig,
} from '../utils.js';
import { slugify } from './slugify.js';
@ -11,7 +11,7 @@ export const headingCompiler = ({ renderer, router, _self }) =>
const nextToc = { level, title: str };
const { content, ignoreAllSubs, ignoreSubHeading } =
getAndRemoveDocisfyIgnorConfig(str);
getAndRemoveDocisfyIgnoreConfig(str);
str = content.trim();
nextToc.title = removeAtag(str);

View File

@ -1,5 +1,5 @@
import stripIndent from 'strip-indent';
import { get } from '../fetch/ajax.js';
import { get } from '../util/ajax.js';
const cached = {};

View File

@ -1,241 +1,14 @@
/* eslint-disable no-unused-vars */
import tinydate from 'tinydate';
import * as dom from '../util/dom.js';
import { getAndActive, sticky } from '../event/sidebar.js';
import { getPath, isAbsolutePath } from '../router/util.js';
import { isMobile, inBrowser } from '../util/env.js';
import { isPrimitive } from '../util/core.js';
import { scrollActiveSidebar } from '../event/scroll.js';
import { Compiler } from './compiler.js';
import * as tpl from './tpl.js';
import { prerenderEmbed } from './embed.js';
let vueGlobalData;
function executeScript() {
const script = dom
.findAll('.markdown-section>script')
.filter(s => !/template/.test(s.type))[0];
if (!script) {
return false;
}
const code = script.innerText.trim();
if (!code) {
return false;
}
new Function(code)();
}
function formatUpdated(html, updated, fn) {
updated =
typeof fn === 'function'
? fn(updated)
: typeof fn === 'string'
? tinydate(fn)(new Date(updated))
: updated;
return html.replace(/{docsify-updated}/g, updated);
}
function renderMain(html) {
const docsifyConfig = this.config;
const markdownElm = dom.find('.markdown-section');
const vueVersion =
'Vue' in window &&
window.Vue.version &&
Number(window.Vue.version.charAt(0));
const isMountedVue = elm => {
const isVue2 = Boolean(elm.__vue__ && elm.__vue__._isVue);
const isVue3 = Boolean(elm._vnode && elm._vnode.__v_skip);
return isVue2 || isVue3;
};
if (!html) {
html = /* html */ `<h1>404 - Not found</h1>`;
}
if ('Vue' in window) {
const mountedElms = dom
.findAll('.markdown-section > *')
.filter(elm => isMountedVue(elm));
// Destroy/unmount existing Vue instances
for (const mountedElm of mountedElms) {
if (vueVersion === 2) {
mountedElm.__vue__.$destroy();
} else if (vueVersion === 3) {
mountedElm.__vue_app__.unmount();
}
}
}
this._renderTo(markdownElm, html);
// Render sidebar with the TOC
!docsifyConfig.loadSidebar && this._renderSidebar();
// Execute markdown <script>
if (
docsifyConfig.executeScript ||
('Vue' in window && docsifyConfig.executeScript !== false)
) {
executeScript();
}
// Handle Vue content not mounted by markdown <script>
if ('Vue' in window) {
const vueMountData = [];
const vueComponentNames = Object.keys(docsifyConfig.vueComponents || {});
// Register global vueComponents
if (vueVersion === 2 && vueComponentNames.length) {
vueComponentNames.forEach(name => {
const isNotRegistered = !window.Vue.options.components[name];
if (isNotRegistered) {
window.Vue.component(name, docsifyConfig.vueComponents[name]);
}
});
}
// Store global data() return value as shared data object
if (
!vueGlobalData &&
docsifyConfig.vueGlobalOptions &&
typeof docsifyConfig.vueGlobalOptions.data === 'function'
) {
vueGlobalData = docsifyConfig.vueGlobalOptions.data();
}
// vueMounts
vueMountData.push(
...Object.keys(docsifyConfig.vueMounts || {})
.map(cssSelector => [
dom.find(markdownElm, cssSelector),
docsifyConfig.vueMounts[cssSelector],
])
.filter(([elm, vueConfig]) => elm)
);
// Template syntax, vueComponents, vueGlobalOptions
if (docsifyConfig.vueGlobalOptions || vueComponentNames.length) {
const reHasBraces = /{{2}[^{}]*}{2}/;
// Matches Vue full and shorthand syntax as attributes in HTML tags.
//
// Full syntax examples:
// v-foo, v-foo[bar], v-foo-bar, v-foo:bar-baz.prop
//
// Shorthand syntax examples:
// @foo, @foo.bar, @foo.bar.baz, @[foo], :foo, :[foo]
//
// Markup examples:
// <div v-html>{{ html }}</div>
// <div v-text="msg"></div>
// <div v-bind:text-content.prop="text">
// <button v-on:click="doThis"></button>
// <button v-on:click.once="doThis"></button>
// <button v-on:[event]="doThis"></button>
// <button @click.stop.prevent="doThis">
// <a :href="url">
// <a :[key]="url">
const reHasDirective = /<[^>/]+\s([@:]|v-)[\w-:.[\]]+[=>\s]/;
vueMountData.push(
...dom
.findAll('.markdown-section > *')
// Remove duplicates
.filter(elm => !vueMountData.some(([e, c]) => e === elm))
// Detect Vue content
.filter(elm => {
const isVueMount =
// is a component
elm.tagName.toLowerCase() in
(docsifyConfig.vueComponents || {}) ||
// has a component(s)
elm.querySelector(vueComponentNames.join(',') || null) ||
// has curly braces
reHasBraces.test(elm.outerHTML) ||
// has content directive
reHasDirective.test(elm.outerHTML);
return isVueMount;
})
.map(elm => {
// Clone global configuration
const vueConfig = {
...docsifyConfig.vueGlobalOptions,
};
// Replace vueGlobalOptions data() return value with shared data object.
// This provides a global store for all Vue instances that receive
// vueGlobalOptions as their configuration.
if (vueGlobalData) {
vueConfig.data = () => vueGlobalData;
}
return [elm, vueConfig];
})
);
}
// Mount
for (const [mountElm, vueConfig] of vueMountData) {
const isVueAttr = 'data-isvue';
const isSkipElm =
// Is an invalid tag
mountElm.matches('pre, script') ||
// Is a mounted instance
isMountedVue(mountElm) ||
// Has mounted instance(s)
mountElm.querySelector(`[${isVueAttr}]`);
if (!isSkipElm) {
mountElm.setAttribute(isVueAttr, '');
if (vueVersion === 2) {
vueConfig.el = undefined;
new window.Vue(vueConfig).$mount(mountElm);
} else if (vueVersion === 3) {
const app = window.Vue.createApp(vueConfig);
// Register global vueComponents
vueComponentNames.forEach(name => {
const config = docsifyConfig.vueComponents[name];
app.component(name, config);
});
app.mount(mountElm);
}
}
}
}
}
function renderNameLink(vm) {
const el = dom.getNode('.app-name-link');
const nameLink = vm.config.nameLink;
const path = vm.route.path;
if (!el) {
return;
}
if (isPrimitive(vm.config.nameLink)) {
el.setAttribute('href', nameLink);
} else if (typeof nameLink === 'object') {
const match = Object.keys(nameLink).filter(
key => path.indexOf(key) > -1
)[0];
el.setAttribute('href', nameLink[match]);
}
}
/** @typedef {import('../Docsify').Constructor} Constructor */
/** @typedef {import('../Docsify.js').Constructor} Constructor */
/**
* @template {!Constructor} T
@ -243,6 +16,232 @@ function renderNameLink(vm) {
*/
export function Render(Base) {
return class Render extends Base {
#vueGlobalData;
#executeScript() {
const script = dom
.findAll('.markdown-section>script')
.filter(s => !/template/.test(s.type))[0];
if (!script) {
return false;
}
const code = script.innerText.trim();
if (!code) {
return false;
}
new Function(code)();
}
#formatUpdated(html, updated, fn) {
updated =
typeof fn === 'function'
? fn(updated)
: typeof fn === 'string'
? tinydate(fn)(new Date(updated))
: updated;
return html.replace(/{docsify-updated}/g, updated);
}
#renderMain(html) {
const docsifyConfig = this.config;
const markdownElm = dom.find('.markdown-section');
const vueVersion =
'Vue' in window &&
window.Vue.version &&
Number(window.Vue.version.charAt(0));
const isMountedVue = elm => {
const isVue2 = Boolean(elm.__vue__ && elm.__vue__._isVue);
const isVue3 = Boolean(elm._vnode && elm._vnode.__v_skip);
return isVue2 || isVue3;
};
if (!html) {
html = /* html */ `<h1>404 - Not found</h1>`;
}
if ('Vue' in window) {
const mountedElms = dom
.findAll('.markdown-section > *')
.filter(elm => isMountedVue(elm));
// Destroy/unmount existing Vue instances
for (const mountedElm of mountedElms) {
if (vueVersion === 2) {
mountedElm.__vue__.$destroy();
} else if (vueVersion === 3) {
mountedElm.__vue_app__.unmount();
}
}
}
this._renderTo(markdownElm, html);
// Render sidebar with the TOC
!docsifyConfig.loadSidebar && this._renderSidebar();
// Execute markdown <script>
if (
docsifyConfig.executeScript ||
('Vue' in window && docsifyConfig.executeScript !== false)
) {
this.#executeScript();
}
// Handle Vue content not mounted by markdown <script>
if ('Vue' in window) {
const vueMountData = [];
const vueComponentNames = Object.keys(
docsifyConfig.vueComponents || {}
);
// Register global vueComponents
if (vueVersion === 2 && vueComponentNames.length) {
vueComponentNames.forEach(name => {
const isNotRegistered = !window.Vue.options.components[name];
if (isNotRegistered) {
window.Vue.component(name, docsifyConfig.vueComponents[name]);
}
});
}
// Store global data() return value as shared data object
if (
!this.#vueGlobalData &&
docsifyConfig.vueGlobalOptions &&
typeof docsifyConfig.vueGlobalOptions.data === 'function'
) {
this.#vueGlobalData = docsifyConfig.vueGlobalOptions.data();
}
// vueMounts
vueMountData.push(
...Object.keys(docsifyConfig.vueMounts || {})
.map(cssSelector => [
dom.find(markdownElm, cssSelector),
docsifyConfig.vueMounts[cssSelector],
])
.filter(([elm, vueConfig]) => elm)
);
// Template syntax, vueComponents, vueGlobalOptions
if (docsifyConfig.vueGlobalOptions || vueComponentNames.length) {
const reHasBraces = /{{2}[^{}]*}{2}/;
// Matches Vue full and shorthand syntax as attributes in HTML tags.
//
// Full syntax examples:
// v-foo, v-foo[bar], v-foo-bar, v-foo:bar-baz.prop
//
// Shorthand syntax examples:
// @foo, @foo.bar, @foo.bar.baz, @[foo], :foo, :[foo]
//
// Markup examples:
// <div v-html>{{ html }}</div>
// <div v-text="msg"></div>
// <div v-bind:text-content.prop="text">
// <button v-on:click="doThis"></button>
// <button v-on:click.once="doThis"></button>
// <button v-on:[event]="doThis"></button>
// <button @click.stop.prevent="doThis">
// <a :href="url">
// <a :[key]="url">
const reHasDirective = /<[^>/]+\s([@:]|v-)[\w-:.[\]]+[=>\s]/;
vueMountData.push(
...dom
.findAll('.markdown-section > *')
// Remove duplicates
.filter(elm => !vueMountData.some(([e, c]) => e === elm))
// Detect Vue content
.filter(elm => {
const isVueMount =
// is a component
elm.tagName.toLowerCase() in
(docsifyConfig.vueComponents || {}) ||
// has a component(s)
elm.querySelector(vueComponentNames.join(',') || null) ||
// has curly braces
reHasBraces.test(elm.outerHTML) ||
// has content directive
reHasDirective.test(elm.outerHTML);
return isVueMount;
})
.map(elm => {
// Clone global configuration
const vueConfig = {
...docsifyConfig.vueGlobalOptions,
};
// Replace vueGlobalOptions data() return value with shared data object.
// This provides a global store for all Vue instances that receive
// vueGlobalOptions as their configuration.
if (this.#vueGlobalData) {
vueConfig.data = () => this.#vueGlobalData;
}
return [elm, vueConfig];
})
);
}
// Mount
for (const [mountElm, vueConfig] of vueMountData) {
const isVueAttr = 'data-isvue';
const isSkipElm =
// Is an invalid tag
mountElm.matches('pre, script') ||
// Is a mounted instance
isMountedVue(mountElm) ||
// Has mounted instance(s)
mountElm.querySelector(`[${isVueAttr}]`);
if (!isSkipElm) {
mountElm.setAttribute(isVueAttr, '');
if (vueVersion === 2) {
vueConfig.el = undefined;
new window.Vue(vueConfig).$mount(mountElm);
} else if (vueVersion === 3) {
const app = window.Vue.createApp(vueConfig);
// Register global vueComponents
vueComponentNames.forEach(name => {
const config = docsifyConfig.vueComponents[name];
app.component(name, config);
});
app.mount(mountElm);
}
}
}
}
}
#renderNameLink(vm) {
const el = dom.getNode('.app-name-link');
const nameLink = vm.config.nameLink;
const path = vm.route.path;
if (!el) {
return;
}
if (isPrimitive(vm.config.nameLink)) {
el.setAttribute('href', nameLink);
} else if (typeof nameLink === 'object') {
const match = Object.keys(nameLink).filter(
key => path.indexOf(key) > -1
)[0];
el.setAttribute('href', nameLink[match]);
}
}
_renderTo(el, content, replace) {
const node = dom.getNode(el);
if (node) {
@ -269,7 +268,12 @@ export function Render(Base) {
}
this._renderTo('.sidebar-nav', this.compiler.sidebar(text, maxLevel));
const activeEl = getAndActive(this.router, '.sidebar-nav', true, true);
const activeEl = this.__getAndActive(
this.router,
'.sidebar-nav',
true,
true
);
if (loadSidebar && activeEl) {
activeEl.parentNode.innerHTML +=
this.compiler.subSidebar(subMaxLevel) || '';
@ -285,7 +289,7 @@ export function Render(Base) {
_bindEventOnRendered(activeEl) {
const { autoHeader } = this.config;
scrollActiveSidebar(this.router);
this.__scrollActiveSidebar(this.router);
if (autoHeader && activeEl) {
const main = dom.getNode('#main');
@ -301,20 +305,20 @@ export function Render(Base) {
_renderNav(text) {
text && this._renderTo('nav', this.compiler.compile(text));
if (this.config.loadNavbar) {
getAndActive(this.router, 'nav');
this.__getAndActive(this.router, 'nav');
}
}
_renderMain(text, opt = {}, next) {
if (!text) {
return renderMain.call(this, text);
return this.#renderMain(text);
}
this.callHook('beforeEach', text, result => {
let html;
const callback = () => {
if (opt.updatedAt) {
html = formatUpdated(
html = this.#formatUpdated(
html,
opt.updatedAt,
this.config.formatUpdated
@ -322,7 +326,7 @@ export function Render(Base) {
}
this.callHook('afterEach', html, hookData => {
renderMain.call(this, hookData);
this.#renderMain(hookData);
next();
});
};
@ -386,12 +390,12 @@ export function Render(Base) {
}
this._renderTo('.cover-main', html);
sticky();
this.__sticky();
}
_updateRender() {
// Render name link
renderNameLink(this);
this.#renderNameLink(this);
}
initRender() {

View File

@ -16,7 +16,7 @@
*
* @param {string} str The string to parse.
*
* @return {object} The original string and parsed object, { str, config }.
* @return {{str: string, config: object}} The original string formatted, and parsed object, { str, config }.
*/
export function getAndRemoveConfig(str = '') {
const config = {};
@ -43,7 +43,7 @@ export function getAndRemoveConfig(str = '') {
* Remove the <a> tag from sidebar when the header with link, details see issue 1069
* @param {string} str The string to deal with.
*
* @return {string} str The string after delete the <a> element.
* @return {string} The string after delete the <a> element.
*/
export function removeAtag(str = '') {
return str.replace(/(<\/?a.*?>)/gi, '');
@ -51,11 +51,11 @@ export function removeAtag(str = '') {
/**
* Remove the docsifyIgnore configs and return the str
* @param {string} str The string to deal with.
* @param {string} content The string to deal with.
*
* @return {string} str The string after delete the docsifyIgnore configs.
* @return {{content: string, ignoreAllSubs: boolean, ignoreSubHeading: boolean}} The string after delete the docsifyIgnore configs, and whether to ignore some or all.
*/
export function getAndRemoveDocisfyIgnorConfig(content = '') {
export function getAndRemoveDocisfyIgnoreConfig(content = '') {
let ignoreAllSubs, ignoreSubHeading;
if (/<!-- {docsify-ignore} -->/g.test(content)) {
content = content.replace('<!-- {docsify-ignore} -->', '');

View File

@ -8,32 +8,37 @@ import {
} from '../util.js';
import { noop } from '../../util/core.js';
const cached = {};
function getAlias(path, alias, last) {
const match = Object.keys(alias).filter(key => {
const re = cached[key] || (cached[key] = new RegExp(`^${key}$`));
return re.test(path) && path !== last;
})[0];
return match
? getAlias(path.replace(cached[match], alias[match]), alias, path)
: path;
}
function getFileName(path, ext) {
return new RegExp(`\\.(${ext.replace(/^\./, '')}|html)$`, 'g').test(path)
? path
: /\/$/g.test(path)
? `${path}README${ext}`
: `${path}${ext}`;
}
export class History {
#cached = {};
constructor(config) {
this.config = config;
}
#getAlias(path, alias, last) {
const match = Object.keys(alias).filter(key => {
const re =
this.#cached[key] || (this.#cached[key] = new RegExp(`^${key}$`));
return re.test(path) && path !== last;
})[0];
return match
? this.#getAlias(
path.replace(this.#cached[match], alias[match]),
alias,
path
)
: path;
}
#getFileName(path, ext) {
return new RegExp(`\\.(${ext.replace(/^\./, '')}|html)$`, 'g').test(path)
? path
: /\/$/g.test(path)
? `${path}README${ext}`
: `${path}${ext}`;
}
getBasePath() {
return this.config.basePath;
}
@ -43,8 +48,8 @@ export class History {
const base = this.getBasePath();
const ext = typeof config.ext === 'string' ? config.ext : '.md';
path = config.alias ? getAlias(path, config.alias) : path;
path = getFileName(path, ext);
path = config.alias ? this.#getAlias(path, config.alias) : path;
path = this.#getFileName(path, ext);
path = path === `/README${ext}` ? config.homepage || path : path;
path = isAbsolutePath(path) ? path : getPath(base, path);

View File

@ -7,11 +7,9 @@ function replaceHash(path) {
const i = location.href.indexOf('#');
location.replace(location.href.slice(0, i >= 0 ? i : 0) + '#' + path);
}
export class HashHistory extends History {
constructor(config) {
super(config);
this.mode = 'hash';
}
mode = 'hash';
getBasePath() {
const path = window.location.pathname || '';

View File

@ -4,10 +4,7 @@ import { parseQuery, getPath } from '../util.js';
import { History } from './base.js';
export class HTML5History extends History {
constructor(config) {
super(config);
this.mode = 'history';
}
mode = 'history';
getCurrentPath() {
const base = this.getBasePath();

View File

@ -12,7 +12,7 @@ import { HTML5History } from './history/html5.js';
/** @type {Route} */
let lastRoute = {};
/** @typedef {import('../Docsify').Constructor} Constructor */
/** @typedef {import('../Docsify.js').Constructor} Constructor */
/**
* @template {!Constructor} T
@ -20,12 +20,7 @@ let lastRoute = {};
*/
export function Router(Base) {
return class Router extends Base {
/** @param {any[]} args */
constructor(...args) {
super(...args);
this.route = {};
}
route = {};
updateRender() {
this.router.normalize();

View File

@ -1,7 +1,7 @@
// @ts-check
/* eslint-disable no-unused-vars */
import progressbar from '../render/progressbar.js';
import { noop } from '../util/core.js';
import { noop } from './core.js';
/** @typedef {{updatedAt: string}} CacheOpt */

View File

@ -1,8 +0,0 @@
export function removeDocsifyIgnoreTag(str) {
return str
.replace(/<!-- {docsify-ignore} -->/, '')
.replace(/{docsify-ignore}/, '')
.replace(/<!-- {docsify-ignore-all} -->/, '')
.replace(/{docsify-ignore-all}/, '')
.trim();
}

View File

@ -1,12 +1,30 @@
import { makeExactMatcher } from './exact-match.js';
import { createNextFunction } from './next.js';
/** @typedef {import('../Docsify').Constructor} Constructor */
/** @typedef {import('../Docsify.js').Constructor} Constructor */
/** @typedef {Record<string, string | VirtualRouteHandler>} VirtualRoutesMap */
/** @typedef {(route: string, match: RegExpMatchArray | null) => string | void | Promise<string | void> } VirtualRouteHandler */
/**
* Allows users/plugins to introduce dynamically created content into their docsify
* websites. https://github.com/docsifyjs/docsify/issues/1737
*
* For instance:
*
* ```js
* window.$docsify = {
* routes: {
* '/items/(.+)': function (route, matched) {
* return `
* # Item Page: ${matched[1]}
* This is an item
* `;
* }
* }
* }
* ```
*
* @template {!Constructor} T
* @param {T} Base - The class to extend
*/

View File

@ -1,4 +1,5 @@
/** @typedef {((value: any) => void) => void} OnNext */
/** @typedef {(value: any) => void} CB */
/** @typedef {(cb: CB) => void} OnNext */
/** @typedef {(value: any) => void} NextFunction */
/**
@ -7,6 +8,7 @@
* @returns {[NextFunction, OnNext]}
*/
export function createNextFunction() {
/** @type {CB} */
let storedCb = () => null;
function next(value) {

View File

@ -2,9 +2,9 @@
* Fork https://github.com/egoist/docute/blob/master/src/utils/front-matter.js
*/
/* eslint-disable */
import parser from './yaml.js'
import parser from './yaml.js';
const optionalByteOrderMark = '\\ufeff?'
const optionalByteOrderMark = '\\ufeff?';
const pattern =
'^(' +
optionalByteOrderMark +
@ -13,37 +13,37 @@ const pattern =
'(?:\\2|\\.\\.\\.)' +
'$' +
'' +
'(?:\\n)?)'
'(?:\\n)?)';
// NOTE: If this pattern uses the 'g' flag the `regex` variable definition will
// need to be moved down into the functions that use it.
const regex = new RegExp(pattern, 'm')
const regex = new RegExp(pattern, 'm');
function extractor(string) {
string = string || ''
string = string || '';
const lines = string.split(/(\r?\n)/)
const lines = string.split(/(\r?\n)/);
if (lines[0] && /= yaml =|---/.test(lines[0])) {
return parse(string)
return parse(string);
} else {
return { attributes: {}, body: string }
return { attributes: {}, body: string };
}
}
function parse(string) {
const match = regex.exec(string)
const match = regex.exec(string);
if (!match) {
return {
attributes: {},
body: string
}
body: string,
};
}
const yaml = match[match.length - 1].replace(/^\s+|\s+$/g, '')
const attributes = parser(yaml) || {}
const body = string.replace(match[0], '')
const yaml = match[match.length - 1].replace(/^\s+|\s+$/g, '');
const attributes = parser(yaml) || {};
const body = string.replace(match[0], '');
return { attributes: attributes, body: body, frontmatter: yaml }
return { attributes: attributes, body: body, frontmatter: yaml };
}
export default extractor
export default extractor;

View File

@ -27,7 +27,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/**
* @name YAML
* @namespace
*/
*/
var errors = [],
reference_blocks = [],
@ -46,14 +46,16 @@ var errors = [],
key: new RegExp('([a-z0-9_-][ a-z0-9_-]+):( .+)?', 'i'),
item: new RegExp('^-\\s+'),
trim: new RegExp('^\\s+|\\s+$'),
comment: new RegExp('([^\\\'\\"#]+([\\\'\\"][^\\\'\\"]*[\\\'\\"])*)*(#.*)?')
}
comment: new RegExp(
'([^\\\'\\"#]+([\\\'\\"][^\\\'\\"]*[\\\'\\"])*)*(#.*)?'
),
};
/**
* @class A block of lines of a given level.
* @param {int} lvl The block's level.
* @private
*/
* @class A block of lines of a given level.
* @param {int} lvl The block's level.
* @private
*/
function Block(lvl) {
return {
/* The block's parent */
@ -67,339 +69,339 @@ function Block(lvl) {
/* Blocks with greater level */
children: [],
/* Add a block to the children collection */
addChild: function(obj) {
this.children.push(obj)
obj.parent = this
++this.length
}
}
addChild: function (obj) {
this.children.push(obj);
obj.parent = this;
++this.length;
},
};
}
function parser(str) {
var regLevel = regex['regLevel']
var invalidLine = regex['invalidLine']
var lines = str.split('\n')
var m
var regLevel = regex['regLevel'];
var invalidLine = regex['invalidLine'];
var lines = str.split('\n');
var m;
var level = 0,
curLevel = 0
curLevel = 0;
var blocks = []
var blocks = [];
var result = new Block(-1)
var currentBlock = new Block(0)
result.addChild(currentBlock)
var levels = []
var line = ''
var result = new Block(-1);
var currentBlock = new Block(0);
result.addChild(currentBlock);
var levels = [];
var line = '';
blocks.push(currentBlock)
levels.push(level)
blocks.push(currentBlock);
levels.push(level);
for (var i = 0, len = lines.length; i < len; ++i) {
line = lines[i]
line = lines[i];
if (line.match(invalidLine)) {
continue
continue;
}
if ((m = regLevel.exec(line))) {
level = m[1].length
} else level = 0
level = m[1].length;
} else level = 0;
if (level > curLevel) {
var oldBlock = currentBlock
currentBlock = new Block(level)
oldBlock.addChild(currentBlock)
blocks.push(currentBlock)
levels.push(level)
var oldBlock = currentBlock;
currentBlock = new Block(level);
oldBlock.addChild(currentBlock);
blocks.push(currentBlock);
levels.push(level);
} else if (level < curLevel) {
var added = false
var added = false;
var k = levels.length - 1
var k = levels.length - 1;
for (; k >= 0; --k) {
if (levels[k] == level) {
currentBlock = new Block(level)
blocks.push(currentBlock)
levels.push(level)
if (blocks[k].parent != null) blocks[k].parent.addChild(currentBlock)
added = true
break
currentBlock = new Block(level);
blocks.push(currentBlock);
levels.push(level);
if (blocks[k].parent != null) blocks[k].parent.addChild(currentBlock);
added = true;
break;
}
}
if (!added) {
errors.push('Error: Invalid indentation at line ' + i + ': ' + line)
return
errors.push('Error: Invalid indentation at line ' + i + ': ' + line);
return;
}
}
currentBlock.lines.push(line.replace(regex['trim'], ''))
curLevel = level
currentBlock.lines.push(line.replace(regex['trim'], ''));
curLevel = level;
}
return result
return result;
}
function processValue(val) {
val = val.replace(regex['trim'], '')
var m = null
val = val.replace(regex['trim'], '');
var m = null;
if (val == 'true') {
return true
return true;
} else if (val == 'false') {
return false
return false;
} else if (val == '.NaN') {
return Number.NaN
return Number.NaN;
} else if (val == 'null') {
return null
return null;
} else if (val == '.inf') {
return Number.POSITIVE_INFINITY
return Number.POSITIVE_INFINITY;
} else if (val == '-.inf') {
return Number.NEGATIVE_INFINITY
return Number.NEGATIVE_INFINITY;
} else if ((m = val.match(regex['dashesString']))) {
return m[1]
return m[1];
} else if ((m = val.match(regex['quotesString']))) {
return m[1]
return m[1];
} else if ((m = val.match(regex['float']))) {
return parseFloat(m[0])
return parseFloat(m[0]);
} else if ((m = val.match(regex['integer']))) {
return parseInt(m[0])
return parseInt(m[0]);
} else if (!isNaN((m = Date.parse(val)))) {
return new Date(m)
return new Date(m);
} else if ((m = val.match(regex['single_key_value']))) {
var res = {}
res[m[1]] = processValue(m[2])
return res
var res = {};
res[m[1]] = processValue(m[2]);
return res;
} else if ((m = val.match(regex['array']))) {
var count = 0,
c = ' '
var res = []
var content = ''
var str = false
c = ' ';
var res = [];
var content = '';
var str = false;
for (var j = 0, lenJ = m[1].length; j < lenJ; ++j) {
c = m[1][j]
c = m[1][j];
if (c == "'" || c == '"') {
if (str === false) {
str = c
content += c
continue
str = c;
content += c;
continue;
} else if ((c == "'" && str == "'") || (c == '"' && str == '"')) {
str = false
content += c
continue
str = false;
content += c;
continue;
}
} else if (str === false && (c == '[' || c == '{')) {
++count
++count;
} else if (str === false && (c == ']' || c == '}')) {
--count
--count;
} else if (str === false && count == 0 && c == ',') {
res.push(processValue(content))
content = ''
continue
res.push(processValue(content));
content = '';
continue;
}
content += c
content += c;
}
if (content.length > 0) res.push(processValue(content))
return res
if (content.length > 0) res.push(processValue(content));
return res;
} else if ((m = val.match(regex['map']))) {
var count = 0,
c = ' '
var res = []
var content = ''
var str = false
c = ' ';
var res = [];
var content = '';
var str = false;
for (var j = 0, lenJ = m[1].length; j < lenJ; ++j) {
c = m[1][j]
c = m[1][j];
if (c == "'" || c == '"') {
if (str === false) {
str = c
content += c
continue
str = c;
content += c;
continue;
} else if ((c == "'" && str == "'") || (c == '"' && str == '"')) {
str = false
content += c
continue
str = false;
content += c;
continue;
}
} else if (str === false && (c == '[' || c == '{')) {
++count
++count;
} else if (str === false && (c == ']' || c == '}')) {
--count
--count;
} else if (str === false && count == 0 && c == ',') {
res.push(content)
content = ''
continue
res.push(content);
content = '';
continue;
}
content += c
content += c;
}
if (content.length > 0) res.push(content)
if (content.length > 0) res.push(content);
var newRes = {}
var newRes = {};
for (var j = 0, lenJ = res.length; j < lenJ; ++j) {
if ((m = res[j].match(regex['key_value']))) {
newRes[m[1]] = processValue(m[2])
newRes[m[1]] = processValue(m[2]);
}
}
return newRes
} else return val
return newRes;
} else return val;
}
function processFoldedBlock(block) {
var lines = block.lines
var children = block.children
var str = lines.join(' ')
var chunks = [str]
var lines = block.lines;
var children = block.children;
var str = lines.join(' ');
var chunks = [str];
for (var i = 0, len = children.length; i < len; ++i) {
chunks.push(processFoldedBlock(children[i]))
chunks.push(processFoldedBlock(children[i]));
}
return chunks.join('\n')
return chunks.join('\n');
}
function processLiteralBlock(block) {
var lines = block.lines
var children = block.children
var str = lines.join('\n')
var lines = block.lines;
var children = block.children;
var str = lines.join('\n');
for (var i = 0, len = children.length; i < len; ++i) {
str += processLiteralBlock(children[i])
str += processLiteralBlock(children[i]);
}
return str
return str;
}
function processBlock(blocks) {
var m = null
var res = {}
var lines = null
var children = null
var currentObj = null
var m = null;
var res = {};
var lines = null;
var children = null;
var currentObj = null;
var level = -1
var level = -1;
var processedBlocks = []
var processedBlocks = [];
var isMap = true
var isMap = true;
for (var j = 0, lenJ = blocks.length; j < lenJ; ++j) {
if (level != -1 && level != blocks[j].level) continue
if (level != -1 && level != blocks[j].level) continue;
processedBlocks.push(j)
processedBlocks.push(j);
level = blocks[j].level
lines = blocks[j].lines
children = blocks[j].children
currentObj = null
level = blocks[j].level;
lines = blocks[j].lines;
children = blocks[j].children;
currentObj = null;
for (var i = 0, len = lines.length; i < len; ++i) {
var line = lines[i]
var line = lines[i];
if ((m = line.match(regex['key']))) {
var key = m[1]
var key = m[1];
if (key[0] == '-') {
key = key.replace(regex['item'], '')
key = key.replace(regex['item'], '');
if (isMap) {
isMap = false
isMap = false;
if (typeof res.length === 'undefined') {
res = []
res = [];
}
}
if (currentObj != null) res.push(currentObj)
currentObj = {}
isMap = true
if (currentObj != null) res.push(currentObj);
currentObj = {};
isMap = true;
}
if (typeof m[2] != 'undefined') {
var value = m[2].replace(regex['trim'], '')
var value = m[2].replace(regex['trim'], '');
if (value[0] == '&') {
var nb = processBlock(children)
if (currentObj != null) currentObj[key] = nb
else res[key] = nb
reference_blocks[value.substr(1)] = nb
var nb = processBlock(children);
if (currentObj != null) currentObj[key] = nb;
else res[key] = nb;
reference_blocks[value.substr(1)] = nb;
} else if (value[0] == '|') {
if (currentObj != null)
currentObj[key] = processLiteralBlock(children.shift())
else res[key] = processLiteralBlock(children.shift())
currentObj[key] = processLiteralBlock(children.shift());
else res[key] = processLiteralBlock(children.shift());
} else if (value[0] == '*') {
var v = value.substr(1)
var no = {}
var v = value.substr(1);
var no = {};
if (typeof reference_blocks[v] == 'undefined') {
errors.push("Reference '" + v + "' not found!")
errors.push("Reference '" + v + "' not found!");
} else {
for (var k in reference_blocks[v]) {
no[k] = reference_blocks[v][k]
no[k] = reference_blocks[v][k];
}
if (currentObj != null) currentObj[key] = no
else res[key] = no
if (currentObj != null) currentObj[key] = no;
else res[key] = no;
}
} else if (value[0] == '>') {
if (currentObj != null)
currentObj[key] = processFoldedBlock(children.shift())
else res[key] = processFoldedBlock(children.shift())
currentObj[key] = processFoldedBlock(children.shift());
else res[key] = processFoldedBlock(children.shift());
} else {
if (currentObj != null) currentObj[key] = processValue(value)
else res[key] = processValue(value)
if (currentObj != null) currentObj[key] = processValue(value);
else res[key] = processValue(value);
}
} else {
if (currentObj != null) currentObj[key] = processBlock(children)
else res[key] = processBlock(children)
if (currentObj != null) currentObj[key] = processBlock(children);
else res[key] = processBlock(children);
}
} else if (line.match(/^-\s*$/)) {
if (isMap) {
isMap = false
isMap = false;
if (typeof res.length === 'undefined') {
res = []
res = [];
}
}
if (currentObj != null) res.push(currentObj)
currentObj = {}
isMap = true
continue
if (currentObj != null) res.push(currentObj);
currentObj = {};
isMap = true;
continue;
} else if ((m = line.match(/^-\s*(.*)/))) {
if (currentObj != null) currentObj.push(processValue(m[1]))
if (currentObj != null) currentObj.push(processValue(m[1]));
else {
if (isMap) {
isMap = false
isMap = false;
if (typeof res.length === 'undefined') {
res = []
res = [];
}
}
res.push(processValue(m[1]))
res.push(processValue(m[1]));
}
continue
continue;
}
}
if (currentObj != null) {
if (isMap) {
isMap = false
isMap = false;
if (typeof res.length === 'undefined') {
res = []
res = [];
}
}
res.push(currentObj)
res.push(currentObj);
}
}
for (var j = processedBlocks.length - 1; j >= 0; --j) {
blocks.splice.call(blocks, processedBlocks[j], 1)
blocks.splice.call(blocks, processedBlocks[j], 1);
}
return res
return res;
}
function semanticAnalysis(blocks) {
var res = processBlock(blocks.children)
return res
var res = processBlock(blocks.children);
return res;
}
function preProcess(src) {
var m
var lines = src.split('\n')
var m;
var lines = src.split('\n');
var r = regex['comment']
var r = regex['comment'];
for (var i in lines) {
if ((m = lines[i].match(r))) {
@ -412,24 +414,24 @@ function preProcess(src) {
lines[i] = "";
*/
if (typeof m[3] !== 'undefined') {
lines[i] = m[0].substr(0, m[0].length - m[3].length)
lines[i] = m[0].substr(0, m[0].length - m[3].length);
}
}
}
return lines.join('\n')
return lines.join('\n');
}
function load(str) {
errors = []
reference_blocks = []
processing_time = new Date().getTime()
var pre = preProcess(str)
var doc = parser(pre)
var res = semanticAnalysis(doc)
processing_time = new Date().getTime() - processing_time
errors = [];
reference_blocks = [];
processing_time = new Date().getTime();
var pre = preProcess(str);
var doc = parser(pre);
var res = semanticAnalysis(doc);
processing_time = new Date().getTime() - processing_time;
return res
return res;
}
export default load
export default load;

View File

@ -1,6 +1,8 @@
/* eslint-disable no-unused-vars */
import { getAndRemoveConfig } from '../../core/render/utils.js';
import { removeDocsifyIgnoreTag } from '../../core/util/str.js';
import {
getAndRemoveConfig,
getAndRemoveDocisfyIgnoreConfig,
} from '../../core/render/utils.js';
let INDEXS = {};
@ -88,7 +90,7 @@ export function genIndex(path, content = '', router, depth) {
if (token.type === 'heading' && token.depth <= depth) {
const { str, config } = getAndRemoveConfig(token.text);
const text = removeDocsifyIgnoreTag(token.text);
const text = getAndRemoveDocisfyIgnoreConfig(token.text).content;
if (config.id) {
slug = router.toURL(path, { id: slugify(config.id) });
@ -97,7 +99,7 @@ export function genIndex(path, content = '', router, depth) {
}
if (str) {
title = removeDocsifyIgnoreTag(str);
title = getAndRemoveDocisfyIgnoreConfig(str).content;
}
index[slug] = { slug, title: title, body: '' };

View File

@ -1,7 +1,7 @@
import {
removeAtag,
getAndRemoveConfig,
getAndRemoveDocisfyIgnorConfig,
getAndRemoveDocisfyIgnoreConfig,
} from '../../src/core/render/utils.js';
import { tree } from '../../src/core/render/tpl.js';
import { slugify } from '../../src/core/render/slugify.js';
@ -24,7 +24,7 @@ describe('core/render/utils', () => {
describe('getAndRemoveDocisfyIgnorConfig()', () => {
test('getAndRemoveDocisfyIgnorConfig from <!-- {docsify-ignore} -->', () => {
const { content, ignoreAllSubs, ignoreSubHeading } =
getAndRemoveDocisfyIgnorConfig(
getAndRemoveDocisfyIgnoreConfig(
'My Ignore Title<!-- {docsify-ignore} -->'
);
expect(content).toBe('My Ignore Title');
@ -34,7 +34,7 @@ describe('core/render/utils', () => {
test('getAndRemoveDocisfyIgnorConfig from <!-- {docsify-ignore-all} -->', () => {
const { content, ignoreAllSubs, ignoreSubHeading } =
getAndRemoveDocisfyIgnorConfig(
getAndRemoveDocisfyIgnoreConfig(
'My Ignore Title<!-- {docsify-ignore-all} -->'
);
expect(content).toBe('My Ignore Title');
@ -44,7 +44,7 @@ describe('core/render/utils', () => {
test('getAndRemoveDocisfyIgnorConfig from {docsify-ignore}', () => {
const { content, ignoreAllSubs, ignoreSubHeading } =
getAndRemoveDocisfyIgnorConfig('My Ignore Title{docsify-ignore}');
getAndRemoveDocisfyIgnoreConfig('My Ignore Title{docsify-ignore}');
expect(content).toBe('My Ignore Title');
expect(ignoreSubHeading).toBeTruthy();
expect(ignoreAllSubs === undefined).toBeTruthy();
@ -52,7 +52,7 @@ describe('core/render/utils', () => {
test('getAndRemoveDocisfyIgnorConfig from {docsify-ignore-all}', () => {
const { content, ignoreAllSubs, ignoreSubHeading } =
getAndRemoveDocisfyIgnorConfig('My Ignore Title{docsify-ignore-all}');
getAndRemoveDocisfyIgnoreConfig('My Ignore Title{docsify-ignore-all}');
expect(content).toBe('My Ignore Title');
expect(ignoreAllSubs).toBeTruthy();
expect(ignoreSubHeading === undefined).toBeTruthy();