Skip to content

[Release] v4.13.0 #1913

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 50 commits into from
Oct 26, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
c1227b2
fix: packages/docsify-server-renderer/package.json & packages/docsify…
snyk-bot Jan 13, 2022
719dcbe
fix: correctly fix missing +1, -1 (#1722)
ChoKaPeek Jan 14, 2022
799c0a4
Update LICENSE
trusktr Jan 17, 2022
e0bead3
Merge pull request #1725 from docsifyjs/update-license
trusktr Jan 23, 2022
47cb36e
mention that SSR is experimental and incomplete, prevent people from …
trusktr Jan 24, 2022
b58941e
Merge pull request #1734 from docsifyjs/mark-ssr-as-experimental
trusktr Jan 26, 2022
c49c39a
refactor: Update test environments and lint configuration (#1736)
jhildenbiddle Jan 31, 2022
f5b5282
chore: bump node-fetch in /packages/docsify-server-renderer (#1738)
dependabot[bot] Feb 1, 2022
18cd18c
docs: update readme (#1740)
sy-records Feb 2, 2022
301b516
fix: Coverpage when content > viewport height (#1744)
jhildenbiddle Feb 3, 2022
8aee074
chore: sync emojis (#1745)
sy-records Feb 4, 2022
fa6df6d
fix: Legacy bugs (styles, site plugin error, and dev server error) (#…
jhildenbiddle Feb 5, 2022
2dc5b12
fix: package.json & package-lock.json to reduce vulnerabilities (#1756)
snyk-bot Feb 24, 2022
a00cfbb
chore: bump follow-redirects from 1.14.7 to 1.14.9 (#1757)
dependabot[bot] Feb 25, 2022
fd883e6
chore: bump prismjs in /packages/docsify-server-renderer (#1760)
dependabot[bot] Mar 3, 2022
35002c9
feat: Native emoji w/ image-based fallbacks and improved parsing (#1746)
jhildenbiddle Mar 6, 2022
ba5ee26
feat: Emoji build (#1766)
jhildenbiddle Mar 8, 2022
9bff31d
chore: Remove dompurify (#1490)
jhildenbiddle Mar 8, 2022
716a48e
chore: update develop branch preview link (#1772)
sy-records Mar 15, 2022
63b2535
feat: Plugin error handling (#1742)
jhildenbiddle Mar 15, 2022
32c6b38
Fix: ready/doneEach order with async afterEach (#1770)
jhildenbiddle Mar 16, 2022
6f4f09b
docs: Update configuration options (#1773)
jhildenbiddle Mar 18, 2022
9e6a3e6
docs: Minor fixes to plugin docs (#1774)
jhildenbiddle Mar 19, 2022
093d4a0
chore: update vercel link (#1775)
sy-records Mar 19, 2022
2d13255
chore: update vercel logo (#1776)
sy-records Mar 23, 2022
bec187f
chore: Update CONTRIBUTING.md (#1782)
bhbarquero-dev Mar 31, 2022
b8b221f
chore: bump minimist from 1.2.5 to 1.2.6 (#1787)
dependabot[bot] Apr 13, 2022
8cbc7c8
Virtual Routes Support (#1799)
illBeRoy May 23, 2022
fa14210
docs: update the name for "Simplified Chinese" (#1811)
awxiaoxian2020 Jun 3, 2022
54cc5f9
fix: cornerExternalLinkTarget config. (#1814)
Koooooo-7 Jun 4, 2022
150236a
Improve README.md sentence (#1817)
shaunbharat Jun 7, 2022
1e46f2b
chore: bump jpeg-js from 0.4.3 to 0.4.4 (#1820)
dependabot[bot] Jun 17, 2022
682bf96
chore: bump parse-url from 6.0.0 to 6.0.2 (#1833)
dependabot[bot] Jul 10, 2022
19e40c2
Docs: Fix plugin insertion order in docs (#1850)
jhildenbiddle Aug 2, 2022
3c9b3d9
fix: Ignore emoji shorthand codes in URIs (#1847)
socsieng Aug 3, 2022
9832805
fix: fix docsify-ignore in seach title. (#1872)
Koooooo-7 Aug 30, 2022
9b74744
fix: fix search with no content. (#1878)
Koooooo-7 Sep 17, 2022
c98fda7
docs: Update GitHub default branch from to 'main' (#1883)
123MwanjeMike Sep 17, 2022
79a6619
chore: upgrade caniuse-lit. (#1879)
Koooooo-7 Sep 20, 2022
91272ca
chore: bump trim-newlines and lerna (#1895)
dependabot[bot] Oct 4, 2022
d27af3d
fix: filter null node first. (#1909)
Koooooo-7 Oct 26, 2022
7db2434
[build]: 4.12.3
Koooooo-7 Oct 26, 2022
8fcd92b
[build] 4.12.4
Koooooo-7 Oct 26, 2022
c044517
chore: add changelog 4.12.4
Koooooo-7 Oct 26, 2022
6ac6f52
update: update dependencies.
Koooooo-7 Oct 26, 2022
73ea28d
fix: fix test.
Koooooo-7 Oct 26, 2022
dbc4b45
fix: upgrade dependencies.
Koooooo-7 Oct 26, 2022
a6701d6
[build] 4.13.0
Koooooo-7 Oct 26, 2022
1d29eb3
chore: add changelog 4.13.0
Koooooo-7 Oct 26, 2022
78aaa37
chore: add changelog v4.13.0
Koooooo-7 Oct 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Virtual Routes Support (#1799)
* add first test

* new VirtualRoutes mixin that handles routes. fetch tries to use VirtualRoutes. default config updated

* cover all basic use cases

* regex matching in routes

* covered all virtual routes tests

* added hack to fix config test on firefox

* removed formatting regex matches into string routes

* added support for "next" function

* added docs

* navigate now supports both hash and history routerModes

* waiting for networkidle in navigateToRoute helper

* promiseless implementation

* remove firefox workaround from catchPluginErrors test, since we no longer use promises

* updated docs

* updated docs for "alias" as well

* minor rephrasing

* removed non-legacy code from exact-match; updated navigateToRoute helper to infer router mode from page

* moved endsWith from router utils to general utils; added startsWith util; refactored makeExactMatcher to use both

* updated docs per feedback

* moved navigateToRoute helper into the virtual-routes test file

* moved navigateToRoute to top of file

* updated docs per pr comments
  • Loading branch information
illBeRoy authored May 23, 2022
commit 8cbc7c8073e134df37600d097e58071909059fbd
9 changes: 8 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,14 @@ module.exports = {
'no-shadow': [
'error',
{
allow: ['Events', 'Fetch', 'Lifecycle', 'Render', 'Router'],
allow: [
'Events',
'Fetch',
'Lifecycle',
'Render',
'Router',
'VirtualRoutes',
],
},
],
'no-unused-vars': ['error', { args: 'none' }],
Expand Down
86 changes: 86 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ The config can also be defined as a function, in which case the first argument i
- 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 = {
Expand Down Expand Up @@ -680,6 +681,91 @@ window.$docsify = {
};
```

## 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/(.*)': function(route, matched) {
return '# Custom Markdown';
},

// RegEx match w/ asynchronous function
'/baz/(.*)': function(route, matched, next) {
// Requires `fetch` polyfill for legacy browsers (https://github.github.io/fetch/)
fetch('/api/users?id=12345')
.then(function(response) {
next('# Custom Markdown');
})
.catch(function(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/(.+)': function(route, matched) {
if (matched[0] === 'dogs') {
return null;
} else {
return 'I like all pets but dogs';
}
}

// accepts everything other than cats (asynchronous)
'/pets/(.*)': function(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': function(route, matched) {
return false;
}

// but any other pet should generate dynamic content right here
'/pets/(.+)': function(route, matched) {
const pet = matched[0];
return `your pet is ${pet} (but not a cat)`;
}
}
}
```

## subMaxLevel

- Type: `Number`
Expand Down
6 changes: 5 additions & 1 deletion src/core/Docsify.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Router } from './router/index.js';
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';
Expand All @@ -11,7 +12,10 @@ import { Lifecycle } from './init/lifecycle';
/** @typedef {new (...args: any[]) => any} Constructor */

// eslint-disable-next-line new-cap
export class Docsify extends Fetch(Events(Render(Router(Lifecycle(Object))))) {
export class Docsify extends Fetch(
// eslint-disable-next-line new-cap
Events(Render(VirtualRoutes(Router(Lifecycle(Object)))))
) {
constructor() {
super();

Expand Down
1 change: 1 addition & 0 deletions src/core/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default function (vm) {
notFoundPage: true,
relativePath: false,
repo: '',
routes: {},
routerMode: 'hash',
subMaxLevel: 0,
themeColor: '',
Expand Down
47 changes: 33 additions & 14 deletions src/core/fetch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,25 +96,44 @@ export function Fetch(Base) {
// Abort last request

const file = this.router.getFile(path);
const req = request(file + qs, true, requestHeaders);

this.isRemoteUrl = isExternal(file);
// Current page is html
this.isHTML = /\.html$/g.test(file);

// Load main content
req.then(
(text, opt) =>
this._renderMain(
text,
opt,
this._loadSideAndNav(path, qs, loadSidebar, cb)
),
_ => {
this._fetchFallbackPage(path, qs, cb) ||
this._fetch404(file, qs, cb);
}
);
// create a handler that should be called if content was fetched successfully
const contentFetched = (text, opt) => {
this._renderMain(
text,
opt,
this._loadSideAndNav(path, qs, loadSidebar, cb)
);
};

// and a handler that is called if content failed to fetch
const contentFailedToFetch = _ => {
this._fetchFallbackPage(path, qs, cb) || this._fetch404(file, qs, cb);
};

// attempt to fetch content from a virtual route, and fallback to fetching the actual file
if (!this.isRemoteUrl) {
this.matchVirtualRoute(path).then(contents => {
if (typeof contents === 'string') {
contentFetched(contents);
} else {
request(file + qs, true, requestHeaders).then(
contentFetched,
contentFailedToFetch
);
}
});
} else {
// if the requested url is not local, just fetch the file
request(file + qs, true, requestHeaders).then(
contentFetched,
contentFailedToFetch
);
}

// Load nav
loadNavbar &&
Expand Down
3 changes: 2 additions & 1 deletion src/core/router/history/hash.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { noop } from '../../util/core';
import { on } from '../../util/dom';
import { parseQuery, cleanPath, replaceSlug, endsWith } from '../util';
import { endsWith } from '../../util/str';
import { parseQuery, cleanPath, replaceSlug } from '../util';
import { History } from './base';

function replaceHash(path) {
Expand Down
4 changes: 0 additions & 4 deletions src/core/router/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,3 @@ export function getPath(...args) {
export const replaceSlug = cached(path => {
return path.replace('#', '?id=');
});

export function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
7 changes: 7 additions & 0 deletions src/core/util/str.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function startsWith(str, prefix) {
return str.indexOf(prefix) === 0;
}

export function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
21 changes: 21 additions & 0 deletions src/core/virtual-routes/exact-match.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { startsWith, endsWith } from '../util/str';

/**
* Adds beginning of input (^) and end of input ($) assertions if needed into a regex string
* @param {string} matcher the string to match
* @returns {string}
*/
export function makeExactMatcher(matcher) {
const matcherWithBeginningOfInput = startsWith(matcher, '^')
? matcher
: `^${matcher}`;

const matcherWithBeginningAndEndOfInput = endsWith(
matcherWithBeginningOfInput,
'$'
)
? matcherWithBeginningOfInput
: `${matcherWithBeginningOfInput}$`;

return matcherWithBeginningAndEndOfInput;
}
93 changes: 93 additions & 0 deletions src/core/virtual-routes/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { makeExactMatcher } from './exact-match';
import { createNextFunction } from './next';

/** @typedef {import('../Docsify').Constructor} Constructor */

/** @typedef {Record<string, string | VirtualRouteHandler>} VirtualRoutesMap */
/** @typedef {(route: string, match: RegExpMatchArray | null) => string | void | Promise<string | void> } VirtualRouteHandler */

/**
* @template {!Constructor} T
* @param {T} Base - The class to extend
*/
export function VirtualRoutes(Base) {
return class VirtualRoutes extends Base {
/**
* Gets the Routes object from the configuration
* @returns {VirtualRoutesMap}
*/
routes() {
return this.config.routes || {};
}

/**
* Attempts to match the given path with a virtual route.
* @param {string} path the path of the route to match
* @returns {Promise<string | null>} resolves to string if route was matched, otherwise null
*/
matchVirtualRoute(path) {
const virtualRoutes = this.routes();
const virtualRoutePaths = Object.keys(virtualRoutes);

let done = () => null;

/**
* This is a tail recursion that iterates over all the available routes.
* It can result in one of two ways:
* 1. Call itself (essentially reviewing the next route)
* 2. Call the "done" callback with the result (either the contents, or "null" if no match was found)
*/
function asyncMatchNextRoute() {
const virtualRoutePath = virtualRoutePaths.shift();
if (!virtualRoutePath) {
return done(null);
}

const matcher = makeExactMatcher(virtualRoutePath);
const matched = path.match(matcher);

if (!matched) {
return asyncMatchNextRoute();
}

const virtualRouteContentOrFn = virtualRoutes[virtualRoutePath];

if (typeof virtualRouteContentOrFn === 'string') {
const contents = virtualRouteContentOrFn;
return done(contents);
}

if (typeof virtualRouteContentOrFn === 'function') {
const fn = virtualRouteContentOrFn;

const [next, onNext] = createNextFunction();
onNext(contents => {
if (typeof contents === 'string') {
return done(contents);
} else if (contents === false) {
return done(null);
} else {
return asyncMatchNextRoute();
}
});

if (fn.length <= 2) {
const returnedValue = fn(path, matched);
return next(returnedValue);
} else {
return fn(path, matched, next);
}
}

return asyncMatchNextRoute();
}

return {
then: function (cb) {
done = cb;
asyncMatchNextRoute();
},
};
}
};
}
21 changes: 21 additions & 0 deletions src/core/virtual-routes/next.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/** @typedef {((value: any) => void) => void} OnNext */
/** @typedef {(value: any) => void} NextFunction */

/**
* Creates a pair of a function and an event emitter.
* When the function is called, the event emitter calls the given callback with the value that was passed to the function.
* @returns {[NextFunction, OnNext]}
*/
export function createNextFunction() {
let storedCb = () => null;

function next(value) {
storedCb(value);
}

function onNext(cb) {
storedCb = cb;
}

return [next, onNext];
}
Loading