From 631e9248786f87a5b2f7892fc73a53543744d288 Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Tue, 1 Feb 2022 08:32:07 -0600 Subject: [PATCH 01/16] Add try/catch w/ error message to plugin calls --- src/core/init/lifecycle.js | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/core/init/lifecycle.js b/src/core/init/lifecycle.js index c04fc1a72..cb2d967b4 100644 --- a/src/core/init/lifecycle.js +++ b/src/core/init/lifecycle.js @@ -36,14 +36,29 @@ export function Lifecycle(Base) { if (index >= queue.length) { next(data); } else if (typeof hookFn === 'function') { + const errTitle = `Docsify plugin ${ + hookFn.name ? '"' + hookFn.name + '"' : '' + } error (${hookName})`; + if (hookFn.length === 2) { - hookFn(data, result => { - data = result; - step(index + 1); - }); + try { + hookFn(data, result => { + data = result; + }); + } catch (err) { + console.error(errTitle, err); + } + step(index + 1); } else { - const result = hookFn(data); - data = result === undefined ? data : result; + let result; + + try { + result = hookFn(data); + } catch (err) { + console.error(errTitle, err); + } + + data = result || data; step(index + 1); } } else { From 67c5410b049237887811350f38e3f9bc8a2c78dc Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Wed, 2 Feb 2022 18:00:46 -0600 Subject: [PATCH 02/16] Update lifecycle.js --- src/core/init/lifecycle.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/core/init/lifecycle.js b/src/core/init/lifecycle.js index cb2d967b4..22906494a 100644 --- a/src/core/init/lifecycle.js +++ b/src/core/init/lifecycle.js @@ -36,29 +36,26 @@ export function Lifecycle(Base) { if (index >= queue.length) { next(data); } else if (typeof hookFn === 'function') { - const errTitle = `Docsify plugin ${ - hookFn.name ? '"' + hookFn.name + '"' : '' - } error (${hookName})`; + const errTitle = `Docsify plugin error (${hookName})`; if (hookFn.length === 2) { try { hookFn(data, result => { data = result; + step(index + 1); }); } catch (err) { console.error(errTitle, err); } - step(index + 1); } else { - let result; - try { - result = hookFn(data); + const result = hookFn(data); + + data = result === undefined ? data : data; } catch (err) { console.error(errTitle, err); } - data = result || data; step(index + 1); } } else { From 2a58be69eb75a87b74d0408765e1d222282ac95b Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Wed, 2 Feb 2022 18:45:26 -0600 Subject: [PATCH 03/16] Update lifecycle.js --- src/core/init/lifecycle.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/init/lifecycle.js b/src/core/init/lifecycle.js index 22906494a..895216c23 100644 --- a/src/core/init/lifecycle.js +++ b/src/core/init/lifecycle.js @@ -42,11 +42,12 @@ export function Lifecycle(Base) { try { hookFn(data, result => { data = result; - step(index + 1); }); } catch (err) { console.error(errTitle, err); } + + step(index + 1); } else { try { const result = hookFn(data); From 97b91faaf2470828e9c93129fbccebc1c0ea036e Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Tue, 8 Mar 2022 00:02:37 -0600 Subject: [PATCH 04/16] Add try/catch to plugin pre-hooks --- src/core/Docsify.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/core/Docsify.js b/src/core/Docsify.js index ba6c54b88..3a5577d09 100644 --- a/src/core/Docsify.js +++ b/src/core/Docsify.js @@ -28,9 +28,14 @@ export class Docsify extends Fetch(Events(Render(Router(Lifecycle(Object))))) { } initPlugin() { - [] - .concat(this.config.plugins) - .forEach(fn => isFn(fn) && fn(this._lifecycle, this)); + [].concat(this.config.plugins).forEach(fn => { + try { + isFn(fn) && fn(this._lifecycle, this); + } catch (err) { + const errTitle = 'Docsify plugin error'; + console.error(errTitle, err); + } + }); } } From b808363ea2d2e84cccab4e26ac1c665e4623d17e Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Tue, 8 Mar 2022 00:02:58 -0600 Subject: [PATCH 05/16] Update error message --- src/core/init/lifecycle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/init/lifecycle.js b/src/core/init/lifecycle.js index 895216c23..a7ad16c0f 100644 --- a/src/core/init/lifecycle.js +++ b/src/core/init/lifecycle.js @@ -36,7 +36,7 @@ export function Lifecycle(Base) { if (index >= queue.length) { next(data); } else if (typeof hookFn === 'function') { - const errTitle = `Docsify plugin error (${hookName})`; + const errTitle = 'Docsify plugin error'; if (hookFn.length === 2) { try { From c2bf5b9570d1bd22dab54c6cac1b78339459e133 Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Wed, 9 Mar 2022 22:23:40 -0600 Subject: [PATCH 06/16] Add catchPluginErrors option --- src/core/Docsify.js | 8 ++++++-- src/core/config.js | 1 + src/core/init/lifecycle.js | 13 +++++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/core/Docsify.js b/src/core/Docsify.js index 3a5577d09..ad339311c 100644 --- a/src/core/Docsify.js +++ b/src/core/Docsify.js @@ -32,8 +32,12 @@ export class Docsify extends Fetch(Events(Render(Router(Lifecycle(Object))))) { try { isFn(fn) && fn(this._lifecycle, this); } catch (err) { - const errTitle = 'Docsify plugin error'; - console.error(errTitle, err); + if (this.config.catchPluginErrors) { + const errTitle = 'Docsify plugin error'; + console.error(errTitle, err); + } else { + throw err; + } } }); } diff --git a/src/core/config.js b/src/core/config.js index d76629159..5830656a9 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -37,6 +37,7 @@ export default function (vm) { crossOriginLinks: [], relativePath: false, topMargin: 0, + catchPluginErrors: true, }, typeof window.$docsify === 'function' ? window.$docsify(vm) diff --git a/src/core/init/lifecycle.js b/src/core/init/lifecycle.js index a7ad16c0f..ac4c9c759 100644 --- a/src/core/init/lifecycle.js +++ b/src/core/init/lifecycle.js @@ -29,6 +29,7 @@ export function Lifecycle(Base) { callHook(hookName, data, next = noop) { const queue = this._hooks[hookName]; + const catchPluginErrors = this.config.catchPluginErrors; const step = function (index) { const hookFn = queue[index]; @@ -44,7 +45,11 @@ export function Lifecycle(Base) { data = result; }); } catch (err) { - console.error(errTitle, err); + if (catchPluginErrors) { + console.error(errTitle, err); + } else { + throw err; + } } step(index + 1); @@ -54,7 +59,11 @@ export function Lifecycle(Base) { data = result === undefined ? data : data; } catch (err) { - console.error(errTitle, err); + if (catchPluginErrors) { + console.error(errTitle, err); + } else { + throw err; + } } step(index + 1); From 99c992943a616b0340e0aa4668098489d870c330 Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Thu, 10 Mar 2022 17:05:34 -0600 Subject: [PATCH 07/16] Fix async beforeEach calls --- src/core/init/lifecycle.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/init/lifecycle.js b/src/core/init/lifecycle.js index ac4c9c759..c677baaf1 100644 --- a/src/core/init/lifecycle.js +++ b/src/core/init/lifecycle.js @@ -43,6 +43,7 @@ export function Lifecycle(Base) { try { hookFn(data, result => { data = result; + step(index + 1); }); } catch (err) { if (catchPluginErrors) { @@ -50,23 +51,24 @@ export function Lifecycle(Base) { } else { throw err; } - } - step(index + 1); + step(index + 1); + } } else { try { const result = hookFn(data); data = result === undefined ? data : data; + step(index + 1); } catch (err) { if (catchPluginErrors) { console.error(errTitle, err); } else { throw err; } - } - step(index + 1); + step(index + 1); + } } } else { step(index + 1); From 01aacedf166e26c1f082e2b55e500dacb114050a Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Thu, 10 Mar 2022 22:52:14 -0600 Subject: [PATCH 08/16] Update plugin dev documentation --- docs/write-a-plugin.md | 260 +++++++++++++++++++++++++++++++---------- 1 file changed, 199 insertions(+), 61 deletions(-) diff --git a/docs/write-a-plugin.md b/docs/write-a-plugin.md index 5495a8771..58552d712 100644 --- a/docs/write-a-plugin.md +++ b/docs/write-a-plugin.md @@ -1,85 +1,238 @@ # Write a plugin -A plugin is simply a function that takes `hook` as an argument. The hook supports handling of asynchronous tasks. +A docsify plugin is a function with the ability to execute custom JavaScript code at various stages of Docsify's lifecycle. -## Full configuration +## Setup + +Docsify plugins can be added directly to the `plugins` array. ```js window.$docsify = { plugins: [ - function(hook, vm) { - hook.init(function() { - // Called when the script starts running, only trigger once, no arguments, - }); + function myPlugin1(hook, vm) { + // ... + }, + function myPlugin2(hook, vm) { + // ... + }, + ], +}; +``` - hook.beforeEach(function(content) { - // Invoked each time before parsing the Markdown file. - // ... - return content; - }); +Alternatively, a plugin can be stored in a separate file and "installed" using a standard ` +``` - hook.doneEach(function() { - // Invoked each time after the data is fully loaded, no arguments, - // ... - }); +```js +(function () { + var myPlugin = function (hook, vm) { + // ... + }; + + // Add plugin to docsify's plugin array + $docsify.plugins = [].concat(myPlugin, $docsify.plugins); +})(); +``` - hook.mounted(function() { - // Called after initial completion. Only trigger once, no arguments. - }); +## Template - hook.ready(function() { - // Called after initial completion, no arguments. - }); - } - ] +Below is a plugin template with placeholders for all available lifecycle hooks. + +1. Copy the template +1. Modify the `myPlugin` name as appropriate +1. Add your plugin logic +1. Remove unused lifecycle hooks +1. Save the file as `docsify-plugin-[name].js` +1. Load your plugin using a standard ` From 17f0313b72b09b4472c18dac016a5f2307418fb3 Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Thu, 10 Mar 2022 22:54:36 -0600 Subject: [PATCH 09/16] Added catchPluginErrors option --- docs/configuration.md | 63 ++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 9762b2143..6f0c4e475 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -365,7 +365,7 @@ window.$docsify = { ## autoHeader -- type: `Boolean` +- Type: `Boolean` If `loadSidebar` and `autoHeader` are both enabled, for each link in `_sidebar.md`, prepend a header to the page before converting it to HTML. See [#78](https://github.com/docsifyjs/docsify/issues/78). @@ -378,7 +378,7 @@ window.$docsify = { ## executeScript -- type: `Boolean` +- Type: `Boolean` Execute the script on the page. Only parse the first script tag ([demo](themes)). If Vue is present, it is turned on by default. @@ -400,8 +400,8 @@ Note that if you are running an external script, e.g. an embedded jsfiddle demo, ## nativeEmoji -- type: `Boolean` -- default: `false` +- Type: `Boolean` +- Default: `false` Render emoji shorthand codes using GitHub-style emoji images or platform-native emoji characters. @@ -453,8 +453,8 @@ To render shorthand codes as text, replace `:` characters with the `:` HTM ## noEmoji -- type: `Boolean` -- default: `false` +- Type: `Boolean` +- Default: `false` Disabled emoji parsing and render all emoji shorthand as text. @@ -492,7 +492,7 @@ To disable emoji parsing of individual shorthand codes, replace `:` characters w ## mergeNavbar -- type: `Boolean` +- Type: `Boolean` Navbar will be merged with the sidebar on smaller screens. @@ -504,7 +504,7 @@ window.$docsify = { ## formatUpdated -- type: `String|Function` +- Type: `String|Function` We can display the file update date through **{docsify-updated}** variable. And format it by `formatUpdated`. See https://github.com/lukeed/tinydate#patterns @@ -523,8 +523,8 @@ window.$docsify = { ## externalLinkTarget -- type: `String` -- default: `_blank` +- Type: `String` +- Default: `_blank` Target to open external links inside the markdown. Default `'_blank'` (new window/tab) @@ -536,8 +536,8 @@ window.$docsify = { ## cornerExternalLinkTarget -- type:`String` -- default:`_blank` +- Type:`String` +- Default:`_blank` Target to open external link at the top right corner. Default `'_blank'` (new window/tab) @@ -549,8 +549,8 @@ window.$docsify = { ## externalLinkRel -- type: `String` -- default: `noopener` +- Type: `String` +- Default: `noopener` Default `'noopener'` (no opener) prevents the newly opened external page (when [externalLinkTarget](#externallinktarget) is `'_blank'`) from having the ability to control our page. No `rel` is set when it's not `'_blank'`. See [this post](https://mathiasbynens.github.io/rel-noopener/) for more information about why you may want to use this option. @@ -562,8 +562,8 @@ window.$docsify = { ## routerMode -- type: `String` -- default: `hash` +- Type: `String` +- Default: `hash` ```js window.$docsify = { @@ -573,7 +573,7 @@ window.$docsify = { ## crossOriginLinks -- type: `Array` +- Type: `Array` When `routerMode: 'history'`, you may face cross-origin issues. See [#1379](https://github.com/docsifyjs/docsify/issues/1379). In Markdown content, there is a simple way to solve it: see extends Markdown syntax `Cross-Origin link` in [helpers](helpers.md). @@ -586,7 +586,7 @@ window.$docsify = { ## noCompileLinks -- type: `Array` +- Type: `Array` Sometimes we do not want docsify to handle our links. See [#203](https://github.com/docsifyjs/docsify/issues/203). We can skip compiling of certain links by specifying an array of strings. Each string is converted into to a regular expression (`RegExp`) and the _whole_ href of a link is matched against it. @@ -598,7 +598,7 @@ window.$docsify = { ## onlyCover -- type: `Boolean` +- Type: `Boolean` Only coverpage is loaded when visiting the home page. @@ -610,7 +610,7 @@ window.$docsify = { ## requestHeaders -- type: `Object` +- Type: `Object` Set the request resource headers. @@ -634,7 +634,7 @@ window.$docsify = { ## ext -- type: `String` +- Type: `String` Request file extension. @@ -646,7 +646,7 @@ window.$docsify = { ## fallbackLanguages -- type: `Array` +- Type: `Array` List of languages that will fallback to the default language when a page is requested and it doesn't exist for the given locale. @@ -664,7 +664,7 @@ window.$docsify = { ## notFoundPage -- type: `Boolean` | `String` | `Object` +- Type: `Boolean` | `String` | `Object` Load the `_404.md` file: @@ -697,8 +697,8 @@ window.$docsify = { ## topMargin -- type: `Number` -- default: `0` +- Type: `Number` +- Default: `0` Adds a space on top when scrolling the content page to reach the selected section. This is useful in case you have a _sticky-header_ layout and you want to align anchors to the end of your header. @@ -710,7 +710,7 @@ window.$docsify = { ## vueComponents -- type: `Object` +- Type: `Object` Creates and registers global [Vue components](https://vuejs.org/v2/guide/components.html). Components are specified using the component name as the key with an object containing Vue options as the value. Component `data` is unique for each instance and will not persist as users navigate the site. @@ -743,7 +743,7 @@ window.$docsify = { ## vueGlobalOptions -- type: `Object` +- Type: `Object` Specifies [Vue options](https://vuejs.org/v2/api/#Options-Data) for use with Vue content not explicitly mounted with [vueMounts](#mounting-dom-elements), [vueComponents](#components), or a [markdown script](#markdown-script). Changes to global `data` will persist and be reflected anywhere global references are used. @@ -777,7 +777,7 @@ window.$docsify = { ## vueMounts -- type: `Object` +- Type: `Object` Specifies DOM elements to mount as [Vue instances](https://vuejs.org/v2/guide/instance.html) and their associated options. Mount elements are specified using a [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) as the key with an object containing Vue options as their value. Docsify will mount the first matching element in the main content area each time a new page is loaded. Mount element `data` is unique for each instance and will not persist as users navigate the site. @@ -808,3 +808,10 @@ window.$docsify = { {{ count }} + +## catchPluginErrors + +- Type: `Boolean` +- Default: `true` + +Determines if Docsify should handle uncaught _synchronous_ plugin errors automatically. This can prevent plugin errors from affecting docsify's ability to properly render live site content. From bf72293db8fa52ecd4e621dd44a6030a1ca7313d Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Thu, 10 Mar 2022 23:01:40 -0600 Subject: [PATCH 10/16] Remove unnecesary code block --- docs/write-a-plugin.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/write-a-plugin.md b/docs/write-a-plugin.md index 58552d712..7e3fae080 100644 --- a/docs/write-a-plugin.md +++ b/docs/write-a-plugin.md @@ -94,12 +94,6 @@ Below is a plugin template with placeholders for all available lifecycle hooks. Lifecycle hooks are provided via the `hook` argument passed to the plugin function. -```js -var myPlugin = function (hook, vm) { - // ... -}; -``` - ### init() Invoked one time when docsify script is initialized From c3c534c1e558355a51fcd74ada2e23e618dea3d3 Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Fri, 11 Mar 2022 09:41:32 -0600 Subject: [PATCH 11/16] Fix handling of hook return value --- src/core/init/lifecycle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/init/lifecycle.js b/src/core/init/lifecycle.js index c677baaf1..94a3981fe 100644 --- a/src/core/init/lifecycle.js +++ b/src/core/init/lifecycle.js @@ -58,7 +58,7 @@ export function Lifecycle(Base) { try { const result = hookFn(data); - data = result === undefined ? data : data; + data = result === undefined ? data : result; step(index + 1); } catch (err) { if (catchPluginErrors) { From 7ae61ea4e63fd1f01733f6db697555e446dc6a73 Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Fri, 11 Mar 2022 09:47:32 -0600 Subject: [PATCH 12/16] Update template to handle no config scenario --- docs/write-a-plugin.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/write-a-plugin.md b/docs/write-a-plugin.md index 7e3fae080..73b279e17 100644 --- a/docs/write-a-plugin.md +++ b/docs/write-a-plugin.md @@ -32,7 +32,8 @@ Alternatively, a plugin can be stored in a separate file and "installed" using a }; // Add plugin to docsify's plugin array - $docsify.plugins = [].concat(myPlugin, $docsify.plugins); + $docsify = $docsify || {}; + $docsify.plugins = [].concat(myPlugin, $docsify.plugins || []); })(); ``` @@ -45,7 +46,7 @@ Below is a plugin template with placeholders for all available lifecycle hooks. 1. Add your plugin logic 1. Remove unused lifecycle hooks 1. Save the file as `docsify-plugin-[name].js` -1. Load your plugin using a standard ` -``` - ```js (function () { var myPlugin = function (hook, vm) { @@ -37,6 +33,10 @@ Alternatively, a plugin can be stored in a separate file and "installed" using a })(); ``` +```html + +``` + ## Template Below is a plugin template with placeholders for all available lifecycle hooks. @@ -62,14 +62,14 @@ Below is a plugin template with placeholders for all available lifecycle hooks. }); // Invoked on each page load before new markdown is transformed to HTML. - // See beforeEach() documentation for asynchronous tasks. + // Supports asynchronous tasks (see beforeEach documentation for details). hook.beforeEach(function (markdown) { // ... return markdown; }); // Invoked on each page load after new markdown has been transformed to HTML. - // See afterEach() documentation for asynchronous tasks. + // Supports asynchronous tasks (see afterEach documentation for details). hook.afterEach(function (html) { // ... return html; @@ -131,16 +131,13 @@ For asynchronous tasks, the hook function accepts a `next` callback as a second ```js hook.beforeEach(function (markdown, next) { - // Asynchronous task (example) - setTimeout(function () { - try { - // ... - } catch (err) { - // ... - } finally { - next(markdown); - } - }, 1000); + try { + // Async task(s)... + } catch (err) { + // ... + } finally { + next(markdown); + } }); ``` @@ -159,16 +156,13 @@ For asynchronous tasks, the hook function accepts a `next` callback as a second ```js hook.afterEach(function (html, next) { - // Asynchronous task (example) - setTimeout(function () { - try { - // ... - } catch (err) { - // ... - } finally { - next(markdown); - } - }, 1000); + try { + // Async task(s)... + } catch (err) { + // ... + } finally { + next(markdown); + } }); ```