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 | 117 +++++++++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 35 deletions(-) diff --git a/src/core/render/index.js b/src/core/render/index.js index 42e62ba96..e21689090 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 - `, - }, - vue3: { - markdown: ` -
+
+

---

{{ 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 5e62363d0..c4ef62942 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

+ +
    +
  • Item {{ i }}
  • +
+
-## Advanced usage +[View markdown on GitHub](https://github.com/docsifyjs/docsify/blob/develop/docs/vue.md#template-syntax) -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 - + } +}; ``` -The HTML & JavaScript above will render the following: - - +```markdown +
+

{{ message }}

+ + {{ count }} + +
+``` -
+

{{ message }}

+ + {{ count }} + +
+ +## Components + +Docsify provides components + +```js +window.$docsify = { + vueComponents: { + 'button-counter': { + template: ` + + `, + data() { + return { + count: 0, + }; + }, + }, + }, +}; +``` - +```markdown + +``` - - {{ counter }} - -
+ + + - +## Markdown script -!> Only the first ` +``` + +```html + + +``` + +!> Only the first ` - + ``` -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

- + + + +

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](#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). -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). +### Data ```js { data() { return { - message: 'Hello, World!', + message: 'Hello, World!' }; - }, - methods: { - hello() { - alert(this.message); - } } } ``` + ```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); + } + }, +} +``` + +

-[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,14 +211,17 @@ 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 }}

+
+ +Notice the behavior when multiple global counters are rendered: + +

{{ count }} @@ -224,11 +229,11 @@ Notice the behavior when multilpe global counters are rendered below: changes ma

-Now, navigate to a new page and return to this section to see how changes made to global data persist. +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 `