diff --git a/.github/semantic.yml b/.github/semantic.yml
new file mode 100644
index 000000000..e7f5bb59b
--- /dev/null
+++ b/.github/semantic.yml
@@ -0,0 +1,4 @@
+titleAndCommits: true
+allowMergeCommits: true
+allowRevertCommits: true
+anyCommit: true
diff --git a/build/build.js b/build/build.js
index 6fa2b123b..99e2f0c4a 100644
--- a/build/build.js
+++ b/build/build.js
@@ -59,6 +59,7 @@ const buildAllPlugin = function () {
{name: 'emoji', input: 'emoji.js'},
{name: 'external-script', input: 'external-script.js'},
{name: 'front-matter', input: 'front-matter/index.js'},
+ {name: 'sideplus', input: 'sideplus/index.js'},
{name: 'zoom-image', input: 'zoom-image.js'},
{name: 'disqus', input: 'disqus.js'},
{name: 'gitalk', input: 'gitalk.js'}
diff --git a/docs/plugins.md b/docs/plugins.md
index 2eb47ce57..15de43aa9 100644
--- a/docs/plugins.md
+++ b/docs/plugins.md
@@ -192,6 +192,21 @@ A docsify.js plugin for displaying tabbed content from markdown.
Provided by [@jhildenbiddle](https://github.com/jhildenbiddle/docsify-tabs).
+## sideplus
+
+Using this plugin, you can expand/collapse sidebar items by click the arrow icon.
+
+```
+
+
+```
+
## More plugins
See [awesome-docsify](awesome?id=plugins)
diff --git a/src/core/event/scroll.js b/src/core/event/scroll.js
index 946589996..e39335722 100644
--- a/src/core/event/scroll.js
+++ b/src/core/event/scroll.js
@@ -1,21 +1,26 @@
-import Tweezer from 'tweezer.js';
import { isMobile } from '../util/env';
import * as dom from '../util/dom';
import { removeParams } from '../router/util';
import config from '../config';
+import Tweezer from 'tweezer.js';
const nav = {};
let hoverOver = false;
let scroller = null;
-let enableScrollEvent = true;
let coverHeight = 0;
+const scrollEvent = {
+ plugin: false,
+ enable: true,
+};
+export { scrollEvent };
+
function scrollTo(el, offset = 0) {
if (scroller) {
scroller.stop();
}
- enableScrollEvent = false;
+ scrollEvent.enable = false;
scroller = new Tweezer({
start: window.pageYOffset,
end: el.getBoundingClientRect().top + window.pageYOffset - offset,
@@ -23,20 +28,19 @@ function scrollTo(el, offset = 0) {
})
.on('tick', v => window.scrollTo(0, v))
.on('done', () => {
- enableScrollEvent = true;
+ scrollEvent.enable = true;
scroller = null;
})
.begin();
}
function highlight(path) {
- if (!enableScrollEvent) {
+ if (!scrollEvent.enable) {
return;
}
const sidebar = dom.getNode('.sidebar');
const anchors = dom.findAll('.anchor');
- const wrap = dom.find(sidebar, '.sidebar-nav');
let active = dom.find(sidebar, 'li.active');
const doc = document.documentElement;
const top = ((doc && doc.scrollTop) || document.body.scrollTop) - coverHeight;
@@ -70,12 +74,20 @@ function highlight(path) {
li.classList.add('active');
active = li;
+ if (!scrollEvent.plugin) {
+ scrollSidebarIntoView(active);
+ }
+}
+
+export function scrollSidebarIntoView(active) {
+ const sidebar = dom.getNode('.sidebar');
+ const wrap = dom.find(sidebar, '.sidebar-nav');
// Scroll into view
// https://github.com/vuejs/vuejs.org/blob/master/themes/vue/source/js/common.js#L282-L297
if (!hoverOver && dom.body.classList.contains('sticky')) {
const height = sidebar.clientHeight;
const curOffset = 0;
- const cur = active.offsetTop + active.clientHeight + 40;
+ const cur = active.offsetTop + active.clientHeight + height / 2;
const isInView =
active.offsetTop >= wrap.scrollTop && cur <= wrap.scrollTop + height;
const notThan = cur - curOffset < height;
diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js
index 4eaf51215..0638c321a 100644
--- a/src/core/render/compiler.js
+++ b/src/core/render/compiler.js
@@ -1,4 +1,3 @@
-import marked from 'marked';
import { isAbsolutePath, getPath, getParentPath } from '../router/util';
import { isFn, merge, cached, isPrimitive } from '../util/core';
import { tree as treeTpl } from './tpl';
@@ -12,6 +11,7 @@ import { paragraphCompiler } from './compiler/paragraph';
import { taskListCompiler } from './compiler/taskList';
import { taskListItemCompiler } from './compiler/taskListItem';
import { linkCompiler } from './compiler/link';
+import marked from 'marked';
const cachedLinks = {};
@@ -278,7 +278,7 @@ export class Compiler {
}
const tree = this.cacheTree[currentPath] || genTree(toc, level);
- html = treeTpl(tree, '
');
+ html = treeTpl(tree);
this.cacheTree[currentPath] = tree;
}
diff --git a/src/core/render/tpl.js b/src/core/render/tpl.js
index 85efa436c..c672e010c 100644
--- a/src/core/render/tpl.js
+++ b/src/core/render/tpl.js
@@ -93,10 +93,12 @@ export function tree(toc, tpl = '') {
let innerHTML = '';
toc.forEach(node => {
- innerHTML += `${node.title}`;
+ innerHTML += ``;
+ innerHTML += `${node.title}`;
if (node.children) {
innerHTML += tree(node.children, tpl);
}
+ innerHTML += ``;
});
return tpl.replace('{inner}', innerHTML);
}
diff --git a/src/plugins/sideplus/index.js b/src/plugins/sideplus/index.js
new file mode 100644
index 000000000..436ac720b
--- /dev/null
+++ b/src/plugins/sideplus/index.js
@@ -0,0 +1,19 @@
+/* eslint-disable no-unused-vars */
+/* eslint-disable no-console */
+import { style } from './style';
+import { rebuild, collapse } from './sidebar';
+import { highlight } from './scroll';
+
+const install = function(hook, vm) {
+ hook.doneEach(function() {
+ rebuild();
+ highlight();
+ });
+
+ hook.mounted(function() {
+ style();
+ collapse();
+ });
+};
+
+$docsify.plugins = [].concat(install, $docsify.plugins);
diff --git a/src/plugins/sideplus/scroll.js b/src/plugins/sideplus/scroll.js
new file mode 100644
index 000000000..9d5322c37
--- /dev/null
+++ b/src/plugins/sideplus/scroll.js
@@ -0,0 +1,51 @@
+import { scrollSidebarIntoView, scrollEvent } from '../../core/event/scroll';
+
+export function highlight() {
+ const dom = Docsify.dom;
+ scrollEvent.plugin = true;
+
+ dom.off('scroll', () => highlightParent());
+ dom.on('scroll', () => highlightParent());
+}
+
+function highlightParent() {
+ if (!scrollEvent.enable) {
+ return;
+ }
+
+ const dom = Docsify.dom;
+
+ const sidebar = dom.getNode('.sidebar');
+ let active = dom.find(sidebar, 'li.active');
+ let parents = dom.findAll(sidebar, 'li.parent');
+
+ parents.forEach(node => node.classList.remove('parent'));
+ active = findParents(active);
+
+ scrollSidebarIntoView(active);
+}
+
+function findParents(active) {
+ if (!active) {
+ return active;
+ }
+
+ let top = active;
+ let node = active.parentNode;
+
+ while (node) {
+ if (node.classList.contains('app-sub-sidebar')) {
+ node = node.parentNode;
+ continue;
+ } else if (node.classList.contains('has-children')) {
+ node.classList.add('parent');
+ if (node.classList.contains('collapse')) {
+ top = node;
+ }
+ node = node.parentNode;
+ continue;
+ } else {
+ return top;
+ }
+ }
+}
diff --git a/src/plugins/sideplus/sidebar.js b/src/plugins/sideplus/sidebar.js
new file mode 100644
index 000000000..1cccf03e2
--- /dev/null
+++ b/src/plugins/sideplus/sidebar.js
@@ -0,0 +1,41 @@
+export function rebuild() {
+ const dom = Docsify.dom;
+
+ const sidebar = dom.getNode('.sidebar');
+ if (sidebar === null || sidebar === undefined) {
+ return;
+ }
+
+ const list = dom.findAll(sidebar, '.app-sub-sidebar li');
+
+ list.forEach(node => {
+ node.prepend(document.createElement('span'));
+ if (
+ node.lastChild &&
+ node.lastChild.classList &&
+ node.lastChild.classList.contains('app-sub-sidebar')
+ ) {
+ node.classList.add('has-children');
+ }
+ });
+}
+
+export function collapse() {
+ const dom = Docsify.dom;
+
+ const sidebar = dom.getNode('.sidebar');
+ if (sidebar === null || sidebar === undefined) {
+ return;
+ }
+
+ dom.on(sidebar, 'click', ({ target }) => {
+ if (
+ target.nodeName === 'SPAN' &&
+ target.nextSibling &&
+ target.nextSibling.classList &&
+ target.nextSibling.classList.contains('section-link')
+ ) {
+ dom.toggleClass(target.parentNode, 'collapse');
+ }
+ });
+}
diff --git a/src/plugins/sideplus/style.js b/src/plugins/sideplus/style.js
new file mode 100644
index 000000000..128107b16
--- /dev/null
+++ b/src/plugins/sideplus/style.js
@@ -0,0 +1,41 @@
+export function style() {
+ const code = `
+ .app-sub-sidebar li:before {
+ content: '';
+ padding: 0;
+ }
+ .app-sub-sidebar li > span:after {
+ content: '\\00A0';
+ font-size: 12px;
+ line-height: 1em;
+ padding: 0 12px 0 0;
+ float: left;
+ }
+ .app-sub-sidebar li.has-children > span:after {
+ -webkit-writing-mode: vertical-lr;
+ -ms-writing-mode: tb-lr;
+ writing-mode: vertical-lr;
+ content: '\\276F';
+ font-size: 12px;
+ line-height: 1em;
+ padding: 12px 3px 0 0;
+ float: left;
+ }
+ .app-sub-sidebar li.has-children.collapse > span:after {
+ -webkit-writing-mode: horizontal-tb;
+ -ms-writing-mode: lr-tb;
+ writing-mode: horizontal-tb;
+ content: '\\276F';
+ font-size: 12px;
+ line-height: 1em;
+ padding: 9px 7px 0 2px;
+ float: left;
+ }
+ .sidebar li.collapse.parent > a {
+ border-right: 2px solid;
+ color: var(--theme-color, #42b983);
+ font-weight: 600;
+ }`;
+
+ Docsify.dom.style(code);
+}