267 lines
7.2 KiB
JavaScript
267 lines
7.2 KiB
JavaScript
/**
|
|
* YwxApp 文档中心前台脚本
|
|
*
|
|
* 提供:目录折叠、移动端抽屉、代码复制、右侧锚点滚动高亮、版本切换。
|
|
* 纯原生实现,不依赖任何库。
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
initTreeToggle();
|
|
initMobileDrawer();
|
|
initCodeCopy();
|
|
initTocScrollSpy();
|
|
initVersionSwitch();
|
|
scrollActiveIntoView();
|
|
});
|
|
|
|
/**
|
|
* 侧边目录展开与折叠
|
|
*/
|
|
function initTreeToggle() {
|
|
var tree = document.querySelector('.dc-tree');
|
|
if (!tree) {
|
|
return;
|
|
}
|
|
|
|
tree.addEventListener('click', function (e) {
|
|
var toggle = e.target.closest('.dc-tree-toggle');
|
|
var dirLink = e.target.closest('.dc-tree-link.is-dir');
|
|
var trigger = toggle || dirLink;
|
|
|
|
if (!trigger) {
|
|
return;
|
|
}
|
|
|
|
var li = trigger.closest('li');
|
|
if (li) {
|
|
li.classList.toggle('is-open');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 移动端侧边栏抽屉
|
|
*/
|
|
function initMobileDrawer() {
|
|
var btn = document.getElementById('dcMenuToggle');
|
|
var sidebar = document.getElementById('dcSidebar');
|
|
var mask = document.getElementById('dcMask');
|
|
|
|
if (!btn || !sidebar) {
|
|
return;
|
|
}
|
|
|
|
function close() {
|
|
sidebar.classList.remove('is-open');
|
|
if (mask) {
|
|
mask.classList.remove('is-show');
|
|
}
|
|
}
|
|
|
|
btn.addEventListener('click', function () {
|
|
sidebar.classList.toggle('is-open');
|
|
if (mask) {
|
|
mask.classList.toggle('is-show', sidebar.classList.contains('is-open'));
|
|
}
|
|
});
|
|
|
|
if (mask) {
|
|
mask.addEventListener('click', close);
|
|
}
|
|
|
|
// 点击目录项后自动收起抽屉
|
|
sidebar.addEventListener('click', function (e) {
|
|
if (e.target.closest('a.dc-tree-link') && window.innerWidth <= 860) {
|
|
close();
|
|
}
|
|
});
|
|
|
|
window.addEventListener('resize', function () {
|
|
if (window.innerWidth > 860) {
|
|
close();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 代码块复制按钮
|
|
*/
|
|
function initCodeCopy() {
|
|
var blocks = document.querySelectorAll('.dc-content pre');
|
|
|
|
Array.prototype.forEach.call(blocks, function (pre) {
|
|
var btn = document.createElement('button');
|
|
btn.className = 'dc-copy-btn';
|
|
btn.type = 'button';
|
|
btn.textContent = '复制';
|
|
|
|
btn.addEventListener('click', function () {
|
|
var code = pre.querySelector('code');
|
|
var text = code ? code.innerText : pre.innerText;
|
|
|
|
copyText(text, function (ok) {
|
|
btn.textContent = ok ? '已复制' : '复制失败';
|
|
btn.classList.toggle('is-done', ok);
|
|
setTimeout(function () {
|
|
btn.textContent = '复制';
|
|
btn.classList.remove('is-done');
|
|
}, 1600);
|
|
});
|
|
});
|
|
|
|
pre.appendChild(btn);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 复制文本到剪贴板,兼容不支持 Clipboard API 的环境
|
|
*
|
|
* @param {string} text 待复制文本
|
|
* @param {Function} cb 回调,参数为是否成功
|
|
*/
|
|
function copyText(text, cb) {
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
navigator.clipboard.writeText(text).then(function () {
|
|
cb(true);
|
|
}).catch(function () {
|
|
cb(fallbackCopy(text));
|
|
});
|
|
return;
|
|
}
|
|
cb(fallbackCopy(text));
|
|
}
|
|
|
|
/**
|
|
* 兜底复制方案
|
|
*
|
|
* @param {string} text 待复制文本
|
|
* @returns {boolean}
|
|
*/
|
|
function fallbackCopy(text) {
|
|
var ta = document.createElement('textarea');
|
|
ta.value = text;
|
|
ta.style.position = 'fixed';
|
|
ta.style.opacity = '0';
|
|
document.body.appendChild(ta);
|
|
ta.select();
|
|
|
|
var ok = false;
|
|
try {
|
|
ok = document.execCommand('copy');
|
|
} catch (err) {
|
|
ok = false;
|
|
}
|
|
|
|
document.body.removeChild(ta);
|
|
return ok;
|
|
}
|
|
|
|
/**
|
|
* 右侧锚点滚动高亮
|
|
*/
|
|
function initTocScrollSpy() {
|
|
var toc = document.getElementById('dcToc');
|
|
var content = document.getElementById('dcContent');
|
|
|
|
if (!toc || !content) {
|
|
return;
|
|
}
|
|
|
|
var links = Array.prototype.slice.call(toc.querySelectorAll('a[data-anchor]'));
|
|
if (!links.length) {
|
|
return;
|
|
}
|
|
|
|
var headings = links.map(function (link) {
|
|
return document.getElementById(link.getAttribute('data-anchor'));
|
|
}).filter(Boolean);
|
|
|
|
if (!headings.length) {
|
|
return;
|
|
}
|
|
|
|
var offset = 90;
|
|
var ticking = false;
|
|
|
|
function update() {
|
|
var current = 0;
|
|
|
|
for (var i = 0; i < headings.length; i++) {
|
|
if (headings[i].getBoundingClientRect().top <= offset) {
|
|
current = i;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
links.forEach(function (link, index) {
|
|
link.classList.toggle('is-active', index === current);
|
|
});
|
|
|
|
ticking = false;
|
|
}
|
|
|
|
window.addEventListener('scroll', function () {
|
|
if (!ticking) {
|
|
window.requestAnimationFrame(update);
|
|
ticking = true;
|
|
}
|
|
}, { passive: true });
|
|
|
|
update();
|
|
}
|
|
|
|
/**
|
|
* 版本切换:保持当前文档路径,仅替换版本段
|
|
*/
|
|
function initVersionSwitch() {
|
|
var select = document.getElementById('dcVersionSelect');
|
|
if (!select) {
|
|
return;
|
|
}
|
|
|
|
select.addEventListener('change', function () {
|
|
var opt = select.options[select.selectedIndex];
|
|
var isDefault = opt.getAttribute('data-default') === '1';
|
|
|
|
// 地址形如 /docs/<项目>[/<版本>]/<文档>
|
|
var parts = window.location.pathname.split('/').filter(Boolean);
|
|
if (parts.length < 2) {
|
|
return;
|
|
}
|
|
|
|
var project = parts[1];
|
|
var docName = parts[parts.length - 1];
|
|
|
|
// 只有项目段时说明还未进入具体文档
|
|
if (parts.length === 2) {
|
|
window.location.href = '/docs/' + project;
|
|
return;
|
|
}
|
|
|
|
window.location.href = isDefault
|
|
? '/docs/' + project + '/' + docName
|
|
: '/docs/' + project + '/' + select.value + '/' + docName;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 让当前选中的目录项进入可视区域
|
|
*/
|
|
function scrollActiveIntoView() {
|
|
var active = document.querySelector('.dc-tree-item.is-active');
|
|
var sidebar = document.getElementById('dcSidebar');
|
|
|
|
if (!active || !sidebar) {
|
|
return;
|
|
}
|
|
|
|
var top = active.offsetTop - sidebar.clientHeight / 2;
|
|
if (top > 0) {
|
|
sidebar.scrollTop = top;
|
|
}
|
|
}
|
|
})();
|