diff --git a/src/core/event/index.js b/src/core/event/index.js index 4c44100b..1181c9ba 100644 --- a/src/core/event/index.js +++ b/src/core/event/index.js @@ -2,11 +2,6 @@ import { isMobile } from '../util/env' import { body, on } from '../util/dom' import * as sidebar from './sidebar' -export function eventMixin (Docsify) { - Docsify.prototype.$resetEvents = function () { - } -} - export function initEvent (vm) { // Bind toggle button sidebar.btn('button.sidebar-toggle') diff --git a/src/core/event/scroll.js b/src/core/event/scroll.js index 503c6b46..89b9b82c 100644 --- a/src/core/event/scroll.js +++ b/src/core/event/scroll.js @@ -1,10 +1,89 @@ +import { isMobile } from '../util/env' +import * as dom from '../util/dom' + export function scrollActiveSidebar () { + if (isMobile) return + let hoverOver = false + const anchors = dom.findAll('.anchor') + const sidebar = dom.find('.sidebar') + const wrap = dom.find(sidebar, '.sidebar-nav') + const height = sidebar.clientHeight + + const nav = {} + const lis = dom.findAll(sidebar, 'li') + let active = dom.find(sidebar, 'li.active') + + for (let i = 0, len = lis.length; i < len; i += 1) { + const li = lis[i] + const a = li.querySelector('a') + if (!a) continue + let href = a.getAttribute('href') + + if (href !== '/') { + const match = href.match('#([^#]+)$') + if (match && match.length) href = match[0].slice(1) + } + + nav[decodeURIComponent(href)] = li + } + + function highlight () { + const top = dom.body.scrollTop + let last + + for (let i = 0, len = anchors.length; i < len; i += 1) { + const node = anchors[i] + + if (node.offsetTop > top) { + if (!last) last = node + break + } else { + last = node + } + } + if (!last) return + const li = nav[last.getAttribute('data-id')] + + if (!li || li === active) return + if (active) active.classList.remove('active') + + li.classList.add('active') + active = li + + // 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 curOffset = 0 + const cur = active.offsetTop + active.clientHeight + 40 + const isInView = ( + active.offsetTop >= wrap.scrollTop && + cur <= wrap.scrollTop + height + ) + const notThan = cur - curOffset < height + const top = isInView + ? wrap.scrollTop + : notThan + ? curOffset + : cur - height + + sidebar.scrollTop = top + } + } + + dom.off('scroll', highlight) + dom.on('scroll', highlight) + dom.on(sidebar, 'mouseover', () => { hoverOver = true }) + dom.on(sidebar, 'mouseleave', () => { hoverOver = false }) } -export function scrollIntoView () { - +export function scrollIntoView (id) { + const section = dom.find('#' + id) + section && setTimeout(() => section.scrollIntoView(), 0) } -export function scroll2Top () { +const scrollEl = dom.$.scrollingElement || dom.$.documentElement + +export function scroll2Top (offset = 0) { + scrollEl.scrollTop = offset === true ? 0 : Number(offset) } diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js index d5316aeb..25b74387 100644 --- a/src/core/fetch/index.js +++ b/src/core/fetch/index.js @@ -2,6 +2,8 @@ import { get } from './ajax' import { callHook } from '../init/lifecycle' import { getRoot } from '../route/util' import { noop } from '../util/core' +import { scrollIntoView } from '../event/scroll' +import { getAndActive } from '../event/sidebar' export function fetchMixin (Docsify) { let last @@ -57,7 +59,8 @@ export function fetchMixin (Docsify) { export function initFetch (vm) { vm._fetchCover(vm) vm._fetch(result => { - vm.$resetEvents() + scrollIntoView(vm.route.query.id) + getAndActive('nav') callHook(vm, 'doneEach') }) } diff --git a/src/core/index.js b/src/core/index.js index 3ac8b56d..f5b8b4b6 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -2,7 +2,6 @@ import { initMixin } from './init' import { routeMixin } from './route' import { renderMixin } from './render' import { fetchMixin } from './fetch' -import { eventMixin } from './event' import initGlobalAPI from './global-api' function Docsify () { @@ -13,7 +12,6 @@ initMixin(Docsify) routeMixin(Docsify) renderMixin(Docsify) fetchMixin(Docsify) -eventMixin(Docsify) /** * Global API diff --git a/src/event.js b/src/event.js deleted file mode 100644 index 536899c0..00000000 --- a/src/event.js +++ /dev/null @@ -1,159 +0,0 @@ -import { isMobile } from './util' - -/** - * Active sidebar when scroll - * @link https://buble.surge.sh/ - */ -export function scrollActiveSidebar () { - if (isMobile()) return - - let hoveredOverSidebar = false - const anchors = document.querySelectorAll('.anchor') - const sidebar = document.querySelector('.sidebar') - const sidebarContainer = sidebar.querySelector('.sidebar-nav') - const sidebarHeight = sidebar.clientHeight - - const nav = {} - const lis = sidebar.querySelectorAll('li') - let active = sidebar.querySelector('li.active') - - for (let i = 0, len = lis.length; i < len; i += 1) { - const li = lis[i] - const a = li.querySelector('a') - if (!a) continue - let href = a.getAttribute('href') - - if (href !== '/') { - const match = href.match('#([^#]+)$') - if (match && match.length) href = match[0].slice(1) - } - - nav[decodeURIComponent(href)] = li - } - - function highlight () { - const top = document.body.scrollTop - let last - - for (let i = 0, len = anchors.length; i < len; i += 1) { - const node = anchors[i] - - if (node.offsetTop > top) { - if (!last) last = node - break - } else { - last = node - } - } - if (!last) return - const li = nav[last.getAttribute('data-id')] - - if (!li || li === active) return - if (active) active.classList.remove('active') - - li.classList.add('active') - active = li - - // scroll into view - // https://github.com/vuejs/vuejs.org/blob/master/themes/vue/source/js/common.js#L282-L297 - if (!hoveredOverSidebar && !sticky.noSticky) { - const currentPageOffset = 0 - const currentActiveOffset = active.offsetTop + active.clientHeight + 40 - const currentActiveIsInView = ( - active.offsetTop >= sidebarContainer.scrollTop && - currentActiveOffset <= sidebarContainer.scrollTop + sidebarHeight - ) - const linkNotFurtherThanSidebarHeight = currentActiveOffset - currentPageOffset < sidebarHeight - const newScrollTop = currentActiveIsInView - ? sidebarContainer.scrollTop - : linkNotFurtherThanSidebarHeight - ? currentPageOffset - : currentActiveOffset - sidebarHeight - - sidebar.scrollTop = newScrollTop - } - } - - window.removeEventListener('scroll', highlight) - window.addEventListener('scroll', highlight) - sidebar.addEventListener('mouseover', () => { hoveredOverSidebar = true }) - sidebar.addEventListener('mouseleave', () => { hoveredOverSidebar = false }) -} - -export function scrollIntoView () { - const id = window.location.hash.match(/#[^#\/]+$/g) - if (!id || !id.length) return - const section = document.querySelector(decodeURIComponent(id[0])) - - if (section) setTimeout(() => section.scrollIntoView(), 0) - - return section -} - -/** - * Acitve link - */ -export function activeLink (dom, activeParent) { - const host = window.location.href - - dom = typeof dom === 'object' ? dom : document.querySelector(dom) - if (!dom) return - let target - - ;[].slice.call(dom.querySelectorAll('a')) - .sort((a, b) => b.href.length - a.href.length) - .forEach(node => { - if (host.indexOf(node.href) === 0 && !target) { - activeParent - ? node.parentNode.classList.add('active') - : node.classList.add('active') - target = node - } else { - activeParent - ? node.parentNode.classList.remove('active') - : node.classList.remove('active') - } - }) - - return target -} - -/** - * sidebar toggle - */ -export function bindToggle (dom) { - dom = typeof dom === 'object' ? dom : document.querySelector(dom) - if (!dom) return - const body = document.body - - dom.addEventListener('click', () => body.classList.toggle('close')) - - if (isMobile()) { - const sidebar = document.querySelector('.sidebar') - sidebar.addEventListener('click', () => { - body.classList.toggle('close') - setTimeout(() => activeLink(sidebar, true), 0) - }) - } -} - -const scrollingElement = document.scrollingElement || document.documentElement - -export function scroll2Top (offset = 0) { - scrollingElement.scrollTop = offset === true ? 0 : Number(offset) -} - -export function sticky () { - sticky.dom = sticky.dom || document.querySelector('section.cover') - const coverHeight = sticky.dom.getBoundingClientRect().height - - return (function () { - if (window.pageYOffset >= coverHeight || sticky.dom.classList.contains('hidden')) { - document.body.classList.add('sticky') - sticky.noSticky = false - } else { - document.body.classList.remove('sticky') - sticky.noSticky = true - } - })() -}