Skip to content

Commit 14cf646

Browse files
committed
Support vue in sidebar
1 parent c24a7f7 commit 14cf646

File tree

2 files changed

+197
-149
lines changed

2 files changed

+197
-149
lines changed

src/core/render/index.js

Lines changed: 16 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@ import { callHook } from '../init/lifecycle';
77
import { getAndActive, sticky } from '../event/sidebar';
88
import { getPath, isAbsolutePath } from '../router/util';
99
import { isMobile, inBrowser } from '../util/env';
10-
import { isPrimitive, merge } from '../util/core';
10+
import { isPrimitive } from '../util/core';
1111
import { scrollActiveSidebar } from '../event/scroll';
1212
import { Compiler } from './compiler';
1313
import * as tpl from './tpl';
1414
import { prerenderEmbed } from './embed';
15-
16-
let vueGlobalData;
15+
import * as vue from './vue';
1716

1817
function executeScript() {
1918
const script = dom
@@ -45,35 +44,14 @@ function formatUpdated(html, updated, fn) {
4544
function renderMain(html) {
4645
const docsifyConfig = this.config;
4746
const markdownElm = dom.find('.markdown-section');
48-
const vueVersion =
49-
'Vue' in window &&
50-
window.Vue.version &&
51-
Number(window.Vue.version.charAt(0));
52-
53-
const isMountedVue = elm => {
54-
const isVue2 = Boolean(elm.__vue__ && elm.__vue__._isVue);
55-
const isVue3 = Boolean(elm._vnode && elm._vnode.__v_skip);
56-
57-
return isVue2 || isVue3;
58-
};
5947

6048
if (!html) {
6149
html = '<h1>404 - Not found</h1>';
6250
}
6351

52+
// Unmount vue
6453
if ('Vue' in window) {
65-
const mountedElms = dom
66-
.findAll('.markdown-section > *')
67-
.filter(elm => isMountedVue(elm));
68-
69-
// Destroy/unmount existing Vue instances
70-
for (const mountedElm of mountedElms) {
71-
if (vueVersion === 2) {
72-
mountedElm.__vue__.$destroy();
73-
} else if (vueVersion === 3) {
74-
mountedElm.__vue_app__.unmount();
75-
}
76-
}
54+
vue.unmountVueInst(markdownElm, '.markdown-section > *');
7755
}
7856

7957
this._renderTo(markdownElm, html);
@@ -89,130 +67,9 @@ function renderMain(html) {
8967
executeScript();
9068
}
9169

92-
// Handle Vue content not mounted by markdown <script>
70+
// Mount vue
9371
if ('Vue' in window) {
94-
const vueMountData = [];
95-
const vueComponentNames = Object.keys(docsifyConfig.vueComponents || {});
96-
97-
// Register global vueComponents
98-
if (vueVersion === 2 && vueComponentNames.length) {
99-
vueComponentNames.forEach(name => {
100-
const isNotRegistered = !window.Vue.options.components[name];
101-
102-
if (isNotRegistered) {
103-
window.Vue.component(name, docsifyConfig.vueComponents[name]);
104-
}
105-
});
106-
}
107-
108-
// Store global data() return value as shared data object
109-
if (
110-
!vueGlobalData &&
111-
docsifyConfig.vueGlobalOptions &&
112-
typeof docsifyConfig.vueGlobalOptions.data === 'function'
113-
) {
114-
vueGlobalData = docsifyConfig.vueGlobalOptions.data();
115-
}
116-
117-
// vueMounts
118-
vueMountData.push(
119-
...Object.keys(docsifyConfig.vueMounts || {})
120-
.map(cssSelector => [
121-
dom.find(markdownElm, cssSelector),
122-
docsifyConfig.vueMounts[cssSelector],
123-
])
124-
.filter(([elm, vueConfig]) => elm)
125-
);
126-
127-
// Template syntax, vueComponents, vueGlobalOptions
128-
const reHasBraces = /{{2}[^{}]*}{2}/;
129-
// Matches Vue full and shorthand syntax as attributes in HTML tags.
130-
//
131-
// Full syntax examples:
132-
// v-foo, v-foo[bar], v-foo-bar, v-foo:bar-baz.prop
133-
//
134-
// Shorthand syntax examples:
135-
// @foo, @foo.bar, @foo.bar.baz, @[foo], :foo, :[foo]
136-
//
137-
// Markup examples:
138-
// <div v-html>{{ html }}</div>
139-
// <div v-text="msg"></div>
140-
// <div v-bind:text-content.prop="text">
141-
// <button v-on:click="doThis"></button>
142-
// <button v-on:click.once="doThis"></button>
143-
// <button v-on:[event]="doThis"></button>
144-
// <button @click.stop.prevent="doThis">
145-
// <a :href="url">
146-
// <a :[key]="url">
147-
const reHasDirective = /<[^>/]+\s([@:]|v-)[\w-:.[\]]+[=>\s]/;
148-
149-
vueMountData.push(
150-
...dom
151-
.findAll('.markdown-section > *')
152-
// Remove duplicates
153-
.filter(elm => !vueMountData.some(([e, c]) => e === elm))
154-
// Detect Vue content
155-
.filter(elm => {
156-
const isVueMount =
157-
// is a component
158-
elm.tagName.toLowerCase() in (docsifyConfig.vueComponents || {}) ||
159-
// has a component(s)
160-
elm.querySelector(vueComponentNames.join(',') || null) ||
161-
// has curly braces
162-
reHasBraces.test(elm.outerHTML) ||
163-
// has content directive
164-
reHasDirective.test(elm.outerHTML);
165-
166-
return isVueMount;
167-
})
168-
.map(elm => {
169-
// Clone global configuration
170-
const vueConfig = merge({}, docsifyConfig.vueGlobalOptions || {});
171-
172-
// Replace vueGlobalOptions data() return value with shared data object.
173-
// This provides a global store for all Vue instances that receive
174-
// vueGlobalOptions as their configuration.
175-
if (vueGlobalData) {
176-
vueConfig.data = function() {
177-
return vueGlobalData;
178-
};
179-
}
180-
181-
return [elm, vueConfig];
182-
})
183-
);
184-
185-
// Mount
186-
for (const [mountElm, vueConfig] of vueMountData) {
187-
const isVueAttr = 'data-isvue';
188-
const isSkipElm =
189-
// Is an invalid tag
190-
mountElm.matches('pre, script') ||
191-
// Is a mounted instance
192-
isMountedVue(mountElm) ||
193-
// Has mounted instance(s)
194-
mountElm.querySelector(`[${isVueAttr}]`);
195-
196-
if (!isSkipElm) {
197-
mountElm.setAttribute(isVueAttr, '');
198-
199-
if (vueVersion === 2) {
200-
vueConfig.el = undefined;
201-
new window.Vue(vueConfig).$mount(mountElm);
202-
} else if (vueVersion === 3) {
203-
const app = window.Vue.createApp(vueConfig);
204-
205-
// Register global vueComponents
206-
vueComponentNames.forEach(name => {
207-
const config = docsifyConfig.vueComponents[name];
208-
209-
app.component(name, config);
210-
});
211-
212-
app.mount(mountElm);
213-
}
214-
}
215-
}
72+
vue.mountVueInst(docsifyConfig, markdownElm, '.markdown-section > *');
21673
}
21774
}
21875

@@ -246,6 +103,7 @@ export function renderMixin(proto) {
246103

247104
proto._renderSidebar = function(text) {
248105
const { maxLevel, subMaxLevel, loadSidebar, hideSidebar } = this.config;
106+
const sidebarElm = dom.find('.sidebar-nav');
249107

250108
if (hideSidebar) {
251109
// FIXME : better styling solution
@@ -260,6 +118,11 @@ export function renderMixin(proto) {
260118
return null;
261119
}
262120

121+
// Unmount vue
122+
if ('Vue' in window) {
123+
vue.unmountVueInst(sidebarElm, '.sidebar-nav > *');
124+
}
125+
263126
this._renderTo('.sidebar-nav', this.compiler.sidebar(text, maxLevel));
264127
const activeEl = getAndActive(this.router, '.sidebar-nav', true, true);
265128
if (loadSidebar && activeEl) {
@@ -269,6 +132,10 @@ export function renderMixin(proto) {
269132
// Reset toc
270133
this.compiler.subSidebar();
271134
}
135+
// Mount vue
136+
if ('Vue' in window) {
137+
vue.mountVueInst(this.config, sidebarElm, '.sidebar-nav > *');
138+
}
272139

273140
// Bind event
274141
this._bindEventOnRendered(activeEl);

src/core/render/vue.js

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import * as dom from '../util/dom';
2+
import { merge } from '../util/core';
3+
4+
let vueGlobalData;
5+
6+
export function getVueVersion() {
7+
return (
8+
'Vue' in window &&
9+
window.Vue.version &&
10+
Number(window.Vue.version.charAt(0))
11+
);
12+
}
13+
14+
/**
15+
* Get if element is mounted with vue
16+
* @example
17+
* @param {Element} elm The Element that check if mounted with vue
18+
* @return {Boolean} Is mounted with vue
19+
*/
20+
export function isMountedVue(elm) {
21+
const isVue2 = Boolean(elm.__vue__ && elm.__vue__._isVue);
22+
const isVue3 = Boolean(elm._vnode && elm._vnode.__v_skip);
23+
24+
return isVue2 || isVue3;
25+
}
26+
27+
/**
28+
* Unmount existing vue instances
29+
* @param {String|Element} el The root element where to perform the search from
30+
* @param {Element} node The query
31+
* @example
32+
* unmountVueInst('a') => dom.findAll(el, node)
33+
* unmountVueInst(nav, 'a') => dom.findAll(node)
34+
*/
35+
export function unmountVueInst(el, node) {
36+
const vueVersion = getVueVersion();
37+
38+
if ('Vue' in window) {
39+
const mountedElms = (node
40+
? dom.findAll(el, node)
41+
: dom.findAll(node)
42+
).filter(elm => isMountedVue(elm));
43+
44+
// Destroy/unmount existing Vue instances
45+
for (const mountedElm of mountedElms) {
46+
if (vueVersion === 2) {
47+
mountedElm.__vue__.$destroy();
48+
} else if (vueVersion === 3) {
49+
mountedElm.__vue_app__.unmount();
50+
}
51+
}
52+
}
53+
}
54+
55+
/**
56+
* Mount vue instance
57+
*/
58+
export function mountVueInst(docsifyConfig, el, node) {
59+
const vueVersion = getVueVersion();
60+
const vueMountData = [];
61+
const vueComponentNames = Object.keys(docsifyConfig.vueComponents || {});
62+
63+
// Register global vueComponents
64+
if (vueVersion === 2 && vueComponentNames.length) {
65+
vueComponentNames.forEach(name => {
66+
const isNotRegistered = !window.Vue.options.components[name];
67+
68+
if (isNotRegistered) {
69+
window.Vue.component(name, docsifyConfig.vueComponents[name]);
70+
}
71+
});
72+
}
73+
74+
// Store global data() return value as shared data object
75+
if (
76+
!vueGlobalData &&
77+
docsifyConfig.vueGlobalOptions &&
78+
typeof docsifyConfig.vueGlobalOptions.data === 'function'
79+
) {
80+
vueGlobalData = docsifyConfig.vueGlobalOptions.data();
81+
}
82+
83+
// vueMounts
84+
vueMountData.push(
85+
...Object.keys(docsifyConfig.vueMounts || {})
86+
.map(cssSelector => [
87+
dom.find(el, cssSelector),
88+
docsifyConfig.vueMounts[cssSelector],
89+
])
90+
.filter(([elm /* , vueConfig*/]) => elm)
91+
);
92+
93+
// Template syntax, vueComponents, vueGlobalOptions
94+
const reHasBraces = /{{2}[^{}]*}{2}/;
95+
// Matches Vue full and shorthand syntax as attributes in HTML tags.
96+
//
97+
// Full syntax examples:
98+
// v-foo, v-foo[bar], v-foo-bar, v-foo:bar-baz.prop
99+
//
100+
// Shorthand syntax examples:
101+
// @foo, @foo.bar, @foo.bar.baz, @[foo], :foo, :[foo]
102+
//
103+
// Markup examples:
104+
// <div v-html>{{ html }}</div>
105+
// <div v-text="msg"></div>
106+
// <div v-bind:text-content.prop="text">
107+
// <button v-on:click="doThis"></button>
108+
// <button v-on:click.once="doThis"></button>
109+
// <button v-on:[event]="doThis"></button>
110+
// <button @click.stop.prevent="doThis">
111+
// <a :href="url">
112+
// <a :[key]="url">
113+
const reHasDirective = /<[^>/]+\s([@:]|v-)[\w-:.[\]]+[=>\s]/;
114+
115+
vueMountData.push(
116+
...dom
117+
.findAll(node)
118+
// Remove duplicates
119+
.filter(elm => !vueMountData.some(([e]) => e === elm))
120+
// Detect Vue content
121+
.filter(elm => {
122+
const isVueMount =
123+
// is a component
124+
elm.tagName.toLowerCase() in (docsifyConfig.vueComponents || {}) ||
125+
// has a component(s)
126+
elm.querySelector(vueComponentNames.join(',') || null) ||
127+
// has curly braces
128+
reHasBraces.test(elm.outerHTML) ||
129+
// has content directive
130+
reHasDirective.test(elm.outerHTML);
131+
132+
return isVueMount;
133+
})
134+
.map(elm => {
135+
// Clone global configuration
136+
const vueConfig = merge({}, docsifyConfig.vueGlobalOptions || {});
137+
138+
// Replace vueGlobalOptions data() return value with shared data object.
139+
// This provides a global store for all Vue instances that receive
140+
// vueGlobalOptions as their configuration.
141+
if (vueGlobalData) {
142+
vueConfig.data = function() {
143+
return vueGlobalData;
144+
};
145+
}
146+
147+
return [elm, vueConfig];
148+
})
149+
);
150+
151+
// Mount
152+
for (const [mountElm, vueConfig] of vueMountData) {
153+
const isVueAttr = 'data-isvue';
154+
const isSkipElm =
155+
// Is an invalid tag
156+
mountElm.matches('pre, script') ||
157+
// Is a mounted instance
158+
isMountedVue(mountElm) ||
159+
// Has mounted instance(s)
160+
mountElm.querySelector(`[${isVueAttr}]`);
161+
162+
if (!isSkipElm) {
163+
mountElm.setAttribute(isVueAttr, '');
164+
165+
if (vueVersion === 2) {
166+
vueConfig.el = undefined;
167+
new window.Vue(vueConfig).$mount(mountElm);
168+
} else if (vueVersion === 3) {
169+
const app = window.Vue.createApp(vueConfig);
170+
// Register global vueComponents
171+
vueComponentNames.forEach(name => {
172+
const config = docsifyConfig.vueComponents[name];
173+
174+
app.component(name, config);
175+
});
176+
177+
app.mount(mountElm);
178+
}
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)