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); +}