From 35ad5c559b43f6d70fae054b1812edef9d2b7e4d Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Fri, 9 Oct 2020 23:14:57 -0500 Subject: [PATCH 01/41] Add vueOptions and vueGlobalOptions Fixed #752 --- src/core/render/index.js | 115 +++++++++++++++++++++++++++------------ 1 file changed, 81 insertions(+), 34 deletions(-) diff --git a/src/core/render/index.js b/src/core/render/index.js index 42e62ba9..e2168909 100644 --- a/src/core/render/index.js +++ b/src/core/render/index.js @@ -13,6 +13,8 @@ import { Compiler } from './compiler'; import * as tpl from './tpl'; import { prerenderEmbed } from './embed'; +let vueGlobalData; + function executeScript() { const script = dom .findAll('.markdown-section>script') @@ -41,58 +43,103 @@ function formatUpdated(html, updated, fn) { } 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 = '

404 - Not found

'; } - this._renderTo('.markdown-section', html); + if ('Vue' in window) { + const mountedElms = dom + .findAll('.markdown-section > *') + .filter(elm => isMountedVue(elm)); + + // Store global data() return value as shared data object + if (!vueGlobalData && docsifyConfig.vueGlobalOptions.data) { + vueGlobalData = docsifyConfig.vueGlobalOptions.data(); + } + + // 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 - !this.config.loadSidebar && this._renderSidebar(); + !docsifyConfig.loadSidebar && this._renderSidebar(); // Execute markdown - `, + }, + }, + vueOptions: { + '#vueoptions': { + data: function() { + return { + counter: 0, + msg: 'vueoptions', + }; + }, + }, + }, }, - vue3: { - markdown: ` -
+ markdown: { + homepage: stripIndent` + # {{ i }} + +
+

---

+ + {{ counter }} +
+ +
+

---

+ + {{ counter }} +
+ +
+

---

{{ counter }}
`, }, }; + return config; + } + + // Tests + // --------------------------------------------------------------------------- + describe('Ignores Vue', function() { + test(`content when Vue is not present`, async () => { + const docsifyInitConfig = getSharedConfig(); + + await docsifyInit(docsifyInitConfig); + await page.evaluate(() => { + return 'Vue' in window === false; + }); + await expect(page).toEqualText('h1', '{{ i }}'); + await expect(page).toEqualText('#vueglobaloptions p', '---'); + await expect(page).toEqualText('#vueoptions p', '---'); + await expect(page).toEqualText('#vuescript p', '---'); + }); + + test(`content when vueOptions and vueGlobalOptions are undefined`, async () => { + const docsifyInitConfig = getSharedConfig(); + + docsifyInitConfig.config.vueGlobalOptions = undefined; + docsifyInitConfig.config.vueOptions = undefined; + docsifyInitConfig.scriptURLs = vueURLs[0]; + + await docsifyInit(docsifyInitConfig); + await expect(page).toEqualText('h1', '{{ i }}'); + await expect(page).toEqualText('#vueglobaloptions p', '---'); + await expect(page).toEqualText('#vueoptions p', '---'); + await expect(page).toEqualText('#vuescript p', 'vuescript'); + }); + + test(`content when vueGlobalOptions data is undefined`, async () => { + const docsifyInitConfig = getSharedConfig(); + + docsifyInitConfig.config.vueGlobalOptions.data = undefined; + docsifyInitConfig.scriptURLs = vueURLs[0]; + + await docsifyInit(docsifyInitConfig); + await expect(page).toEqualText('h1', '{{ i }}'); + await expect(page).toEqualText('#vueoptions p', 'vueoptions'); + await expect(page).toEqualText('#vueglobaloptions p', '---'); + await expect(page).toEqualText('#vuescript p', 'vuescript'); + }); + + test(`content when vueOptions data is undefined`, async () => { + const docsifyInitConfig = getSharedConfig(); + + docsifyInitConfig.config.vueOptions['#vueoptions'].data = undefined; + docsifyInitConfig.scriptURLs = vueURLs[0]; + + await docsifyInit(docsifyInitConfig); + await expect(page).toEqualText('h1', '12345'); + await expect(page).toEqualText('#vueoptions p', 'vueglobaloptions'); + await expect(page).toEqualText('#vueglobaloptions p', 'vueglobaloptions'); + await expect(page).toEqualText('#vuescript p', 'vuescript'); + }); + + test(` + diff --git a/docs/vue.md b/docs/vue.md index 5e62363d..c4ef6294 100644 --- a/docs/vue.md +++ b/docs/vue.md @@ -1,137 +1,230 @@ # Vue compatibility -Docsify allows Vue [v2.x](https://vuejs.org) and [v3.x](https://v3.vuejs.org) components to be added directly to you Markdown files. These components can greatly simplify working with data and adding reactivity to your content. +Docsify allows Vue content to be added directly to you Markdown files. This can greatly simplify working with data and adding reactivity to your site. -To get started, load either the production or development version of Vue in your `index.html`: - -#### Vue 2.x +To get started, add Vue [2.x](https://vuejs.org) or [3.x](https://v3.vuejs.org) to your `index.html` file: ```html - + - - + + ``` -#### Vue 3.x +The URLs above will load a production version of Vue which is optimized for performance. Alternatively, development versions of Vue are larger but offer helpful console warnings and [Vue.js devtools](https://github.com/vuejs/vue-devtools) support: ```html - - + + - + ``` -## Basic rendering +## Template syntax -Docsify will automatically render basic Vue content that does not require `data`, `methods`, or other instance features. +Vue [template syntax](https://vuejs.org/v2/guide/syntax.html) can be added directly to your markdown pages. This syntax can be used to generate dynamic content without additional configuration using [JavaScript expressions](https://vuejs.org/v2/guide/syntax.html#Using-JavaScript-Expressions) and Vue [directives](https://vuejs.org/v2/guide/syntax.html#Directives). ```markdown + +

2 + 2 = {{ 2 + 2 }}

+ + +

Text for GitHub

+ + ``` -The HTML above will render the following: + +

2 + 2 = {{ 2 + 2 }}

-
    -
  • {{ i }}
  • -
+

Text for GitHub

-## Advanced usage +
    +
  • Item {{ i }}
  • +
+
-Vue components and templates that require `data`, `methods`, computed properties, lifecycle hooks, etc. require manually creating a new `Vue()` instance within a ` ``` -#### Vue 3.x - -```markdown +```html + ``` -The HTML & JavaScript above will render the following: - - - -
-

{{ message }}

- - - - - {{ counter }} - -
- - - !> Only the first ` From 8a56f7241fb5edaf0ab413f065900a73e1b506ef Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Sun, 18 Oct 2020 18:35:24 -0500 Subject: [PATCH 11/41] Fix directive detection --- src/core/render/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/render/index.js b/src/core/render/index.js index bb288f09..8f719338 100644 --- a/src/core/render/index.js +++ b/src/core/render/index.js @@ -127,7 +127,7 @@ function renderMain(html) { // Template syntax, vueComponents, vueGlobalOptions if (docsifyConfig.vueGlobalOptions || vueComponentNames.length) { const reHasBraces = /{{2}[^{}]*}{2}/; - const reHasDirective = /\sv-(bind|cloak|else|else-if|for|html|if|is|model|on|once|pre|show|slot|text)=/; + const reHasDirective = /\sv-(bind:|cloak|else|else-if=|for=|html=|if=|is=|model=|on:|once|pre|show=|slot=|text=)/; vueMountData.push( ...dom From 5a2dde162451f69533c813856c6ce3f7fbca5f32 Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Tue, 20 Oct 2020 17:45:34 -0500 Subject: [PATCH 12/41] Update docs --- docs/configuration.md | 113 +++++++++++++++++++-- docs/index.html | 29 ++++-- docs/vue.md | 226 ++++++++++++++++++++++++++++++------------ 3 files changed, 289 insertions(+), 79 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 7a65553a..54d6720f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -494,15 +494,15 @@ window.$docsify = { ``` ## crossOriginLinks -- type: `Array` -When `routerMode: 'history'`, you may face the 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). +- type: `Array` + +When `routerMode: 'history'`, you may face the 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). + ```js window.$docsify = { - crossOriginLinks:[ - "https://example.com/cross-origin-link", - ], + crossOriginLinks: ['https://example.com/cross-origin-link'], }; ``` @@ -629,3 +629,104 @@ window.$docsify = { topMargin: 90, // default: 0 }; ``` + +## vueComponents + +- type: `Object` + +Registers Vue components using the component name as the key with an object containing Vue options as the value. + +```js +window.$docsify = { + vueComponents: { + 'button-counter': { + template: ` + + `, + data() { + return { + count: 0, + }; + }, + }, + }, +}; +``` + +```markdown + +``` + + + + + +## vueGlobalOptions + +- type: `Object` + +Specifies Vue options to be shared throughout your site. These options will be used when Docsify detects Vue content in the main content area that has not been previously mounted via [vueOptions](#vueoptions), [vueComponents](#vuecomponents), or a markdown ` - + ``` -The URLs above will load a production version of Vue which is optimized for performance. Alternatively, development versions of Vue are larger but offer helpful console warnings and [Vue.js devtools](https://github.com/vuejs/vue-devtools) support: +The URLs above will load a **production** version of Vue which has been optimized for performance. Alternatively, **development** versions of Vue are available that are larger in size but offer helpful console warnings and [Vue.js devtools](https://github.com/vuejs/vue-devtools) support: ```html - + - + ``` ## Template syntax -Vue [template syntax](https://vuejs.org/v2/guide/syntax.html) can be added directly to your markdown pages. This syntax can be used to generate dynamic content without additional configuration using [JavaScript expressions](https://vuejs.org/v2/guide/syntax.html#Using-JavaScript-Expressions) and Vue [directives](https://vuejs.org/v2/guide/syntax.html#Directives). +Vue [template syntax](https://vuejs.org/v2/guide/syntax.html) is used to create dynamic content. With no additinal configuration, this syntax offers several useful features like support for [JavaScript expressions](https://vuejs.org/v2/guide/syntax.html#Using-JavaScript-Expressions) and Vue [directives](https://vuejs.org/v2/guide/syntax.html#Directives) for loops and conditional rendering. ```markdown - -

2 + 2 = {{ 2 + 2 }}

- - +

Text for GitHub

- +
  • Item {{ i }}
+ + +

2 + 2 = {{ 2 + 2 }}

``` -

2 + 2 = {{ 2 + 2 }}

-

Text for GitHub

  • Item {{ i }}
+ +

2 + 2 = {{ 2 + 2 }}

-[View markdown on GitHub](https://github.com/docsifyjs/docsify/blob/develop/docs/vue.md#template-syntax) +[View output on GitHub](https://github.com/docsifyjs/docsify/blob/develop/docs/vue.md#template-syntax) -Vue content becomes more interesting when data, methods, lifecycle hooks, and computed properties are used. These options can be specified as [global options](#global-options), [instance options](#instance-options), or within [components](#components). +Vue content becomes more interesting when [data](#data), [computed properties](#computed-properties), [methods](#methods), and [lifecycle hooks](#lifecycle-hooks) are used. These options can be specified as [global options](#global-options), [instance options](#instance-options), or within [components](#components). + +### Data ```js { data() { return { - message: 'Hello, World!', + message: 'Hello, World!' + }; + } +} +``` + + +```markdown + +{{ message }} + + +

+ + +

Text for GitHub

+``` + + + + +{{ message }} + +

+

Text for GitHub

+
+ +[View output on GitHub](https://github.com/docsifyjs/docsify/blob/develop/docs/vue.md#data) + +### Computed properties + +```js +{ + computed: { + timeOfDay() { + const date = new Date(); + const hours = date.getHours(); + + if (hours < 12) { + return 'morning'; + } + else if (hours < 18) { + return 'afternoon'; + } + else { + return 'evening' + } + } + }, +} +``` + +```markdown +Good {{ timeOfDay }}! +``` + + + +Good {{ timeOfDay }}! + + + +### Methods + +```js +{ + data() { + return { + message: 'Hello, World!' }; }, methods: { hello() { alert(this.message); } - } + }, } ``` -```markdown - -

- - -

Text for GitHub

- - -

- -

-``` - -

-

Text for GitHub

-[View markdown on GitHub](https://github.com/docsifyjs/docsify/blob/develop/docs/vue.md#options) +### Lifecycle Hooks + +```js +{ + data() { + return { + images: null, + }; + }, + created() { + fetch('https://api.domain.com/') + .then(response => response.json()) + .then(data => (this.images = data)) + .catch(err => console.log(err)); + } +} + +// API response: +// [ +// { title: 'Image 1', url: 'https://domain.com/1.jpg' }, +// { title: 'Image 2', url: 'https://domain.com/2.jpg' }, +// { title: 'Image 3', url: 'https://domain.com/3.jpg' }, +// ]; +``` + +```markdown +
+
+ +
{{ image.title }}
+
+
+``` + + +
+
+ +
{{ image.title }}
+
+
+
## Global options -Use `vueGlobalOptions` to share data, methods, lifecycle hooks, and computed properties throughout your site. These options will be available to Vue content not explicitly mounted via [instance options](#instance-options), [components](#components), or a markdown ` ``` @@ -222,9 +311,18 @@ TBD ``` -!> Only the first ` - - + + ``` -The URLs above will load a **production** version of Vue which has been optimized for performance. Alternatively, **development** versions of Vue are available that are larger in size but offer helpful console warnings and [Vue.js devtools](https://github.com/vuejs/vue-devtools) support: +#### Vue 3.x ```html - - + + - + ``` @@ -187,7 +189,7 @@ Good {{ timeOfDay }}! ## Global options -Use `vueGlobalOptions` to share Vue options throughout your site. These options will be used when Docsify detects Vue content in the main content area that has not been previously mounted via [instance options](#instance-options), [components](#components), or a [markdown script](#markdown-script). Global `data()` is shared and changes will persist as users navigate the site. +Use `vueGlobalOptions` to share Vue options throughout your site. These options will be used when Docsify detects Vue content in the main content area that has not been previously mounted via [instance options](#instance-options), [components](#components), or a [markdown script](#markdown-script). ```js window.$docsify = { @@ -209,26 +211,29 @@ window.$docsify = {

``` -Notice the behavior when multilpe global counters are rendered below: changes made to one counter affect the other because both instances reference the global `count` value. -

{{ count }}

-

- - {{ count }} - -

-Now, navigate to a new page and return to this section to see how changes made to global data persist. +Notice the behavior when multiple global counters are rendered: + + +

+ + {{ count }} + +

+
+ +Changes made to one counter affect the both counters. This is because both instances reference the same global `count` value. Now, navigate to a new page and return to this section to see how changes made to global data persist between page loads. ## Instance options -Use `vueOptions` to specify Vue mount elements 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 (`#main, .markdown-section`) each time a new page is loaded. Instance data is not shared and changes will not persist as users navigate the site. +Use `vueOptions` to specify Vue mount elements 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 (`#main, .markdown-section`) each time a new page is loaded. ```js window.$docsify = { @@ -295,7 +300,7 @@ window.$docsify = { Vue content can mounted using a `