|
|
var headingList = [];
|
|
|
var scrollBehaviorAvaliable = (function() {
|
|
|
// 检测Chrome
|
|
|
var v = navigator.userAgent.match(/Chrome\/(?<version>\S+)/);
|
|
|
if (v && v.groups.version) {
|
|
|
var chromeVersion = parseInt(v.groups.version);
|
|
|
return chromeVersion >= 61;
|
|
|
}
|
|
|
|
|
|
// 检测Firefox
|
|
|
v = navigator.userAgent.match(/Firefox\/(?<version>\S+)/);
|
|
|
if (v && v.groups.version) {
|
|
|
var firefoxVersion = parseInt(v.groups.version);
|
|
|
return firefoxVersion >= 36;
|
|
|
}
|
|
|
|
|
|
// 检测Safari
|
|
|
v = navigator.userAgent.match(/Version\/(?<version>\S+)/);
|
|
|
if (v && v.groups.version) { // Safari
|
|
|
var safariVersion = parseFloat(v.groups.version);
|
|
|
return safariVersion >= 14;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
})();
|
|
|
var isMobileSafari = (function(){
|
|
|
v = navigator.userAgent.match(/Version\/(?<version>\S+) Mobile\/\S+/);
|
|
|
if (v && v.groups.version) { // Safari
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
})();
|
|
|
var menuHidden = false;
|
|
|
|
|
|
var menuItems = [
|
|
|
{
|
|
|
label: mw.msg('isekai-offcanvastoc-hide-menubutton'),
|
|
|
onClick: () => {
|
|
|
$('#iseai-offcanvas-btn').hide();
|
|
|
menuHidden = true;
|
|
|
mw.notify( mw.msg('isekai-offcanvastoc-menubutton-hide-success'));
|
|
|
},
|
|
|
}
|
|
|
];
|
|
|
|
|
|
function getScrollOffset() {
|
|
|
if (mw.config.get('skin') === 'timeless' && window.innerWidth > 850) {
|
|
|
return 60;
|
|
|
} else if (mw.config.get('skin') === 'minerva') {
|
|
|
return 60;
|
|
|
} else {
|
|
|
return 10;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function getAnchorOffset() {
|
|
|
if (mw.config.get('skin') === 'timeless' && window.innerWidth > 850) {
|
|
|
return 70;
|
|
|
} else if (mw.config.get('skin') === 'minerva') {
|
|
|
return 70;
|
|
|
} else {
|
|
|
return 20;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function scrollToAnchor(link) {
|
|
|
var el = document.getElementById(link.replace(/^#/, ''));
|
|
|
if (el) {
|
|
|
var target = $(el);
|
|
|
|
|
|
function doScroll() {
|
|
|
var position = target.offset().top - getScrollOffset();
|
|
|
|
|
|
if (scrollBehaviorAvaliable) {
|
|
|
window.scrollTo({
|
|
|
top: position,
|
|
|
behavior: 'smooth'
|
|
|
});
|
|
|
} else {
|
|
|
$('html, body').animate({
|
|
|
scrollTop: position,
|
|
|
}, 500);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (mw.config.get('skin') === 'minerva') { // 手机端主题,需要检测折叠状态
|
|
|
var collapseBlock = target.parents('.collapsible-block');
|
|
|
if (collapseBlock.length > 0 && !collapseBlock.hasClass('open-block')) {
|
|
|
var h1Elem = collapseBlock.prev('.collapsible-heading');
|
|
|
if (h1Elem.length > 0) {
|
|
|
// 展开目录
|
|
|
h1Elem.click();
|
|
|
var tid = setInterval(function() {
|
|
|
// 检测是否已经展开
|
|
|
if (collapseBlock.hasClass('open-block')) {
|
|
|
doScroll();
|
|
|
clearInterval(tid);
|
|
|
}
|
|
|
}, 100);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
doScroll();
|
|
|
} else {
|
|
|
doScroll();
|
|
|
}
|
|
|
return false;
|
|
|
} else {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function getScrollbarWidth() {
|
|
|
if (window.innerWidth && document.body.clientWidth) {
|
|
|
return window.innerWidth - document.body.clientWidth;
|
|
|
} else {
|
|
|
return 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function updateActive() {
|
|
|
var scrollTop = $(window).scrollTop() + getAnchorOffset();
|
|
|
$('#isekai-offcanvas-toc ul .list-item').removeClass('active');
|
|
|
|
|
|
if (headingList.length > 0) {
|
|
|
var activedId;
|
|
|
for (var i = 0; i < headingList.length; i ++) {
|
|
|
var headItem = headingList[i];
|
|
|
var headPos = headItem.offset().top;
|
|
|
if (i === 0 && scrollTop < headPos) { // 比第一个head位置靠上,则是简介
|
|
|
activedId = 'bodyContent';
|
|
|
break;
|
|
|
} else if (scrollTop < headPos) { // 如果当前滚动条高度低于目前head,则是上一个
|
|
|
activedId = headingList[i - 1].attr('id');
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (!activedId) {
|
|
|
activedId = headingList[headingList.length - 1].attr('id');
|
|
|
}
|
|
|
$('#isekai-offcanvas-toc ul .list-item[data-id="' + activedId + '"]').addClass('active');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function openOffcanvas() {
|
|
|
$('#iseai-offcanvas-btn').show();
|
|
|
menuHidden = false;
|
|
|
|
|
|
let scrollbarWidth = getScrollbarWidth();
|
|
|
$('#isekai-offcanvas-cover').removeClass('hidden');
|
|
|
|
|
|
window.requestAnimationFrame(function() {
|
|
|
$('body').addClass(['toc-offcanvas-show', 'toc-offcanvas-open'])
|
|
|
.css('margin-right', scrollbarWidth);
|
|
|
$('#iseai-offcanvas-btn').css('margin-right', scrollbarWidth);
|
|
|
if (mw.config.get('skin') === 'timeless') {
|
|
|
$('#mw-header-container').css('padding-right', scrollbarWidth);
|
|
|
}
|
|
|
|
|
|
// 滚动到当前项目
|
|
|
let activedItem = $('#isekai-offcanvas-toc ul .list-item.active');
|
|
|
if (activedItem.length > 0) {
|
|
|
let targetY = Math.max(activedItem.eq(0).prop('offsetTop') - 50, 0);
|
|
|
$('#isekai-offcanvas-toc').scrollTop(targetY);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function closeOffcanvas() {
|
|
|
if ($('#iseai-offcanvas-contextmenu').length > 0) {
|
|
|
$('#iseai-offcanvas-contextmenu').remove();
|
|
|
$('#isekai-offcanvas-cover').addClass('hidden');
|
|
|
} else {
|
|
|
$('body').removeClass('toc-offcanvas-open');
|
|
|
setTimeout(function() {
|
|
|
$('body').removeClass('toc-offcanvas-show').css('margin-right', 0);
|
|
|
$('#isekai-offcanvas-cover').addClass('hidden');
|
|
|
$('#iseai-offcanvas-btn').css('margin-right', 0);
|
|
|
if (mw.config.get('skin') === 'timeless') {
|
|
|
$('#mw-header-container').css('padding-right', 0);
|
|
|
}
|
|
|
}, 260);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function openContextMenu(position) {
|
|
|
var menuContainer = document.createElement('div');
|
|
|
menuContainer.id = 'iseai-offcanvas-contextmenu';
|
|
|
menuContainer.className = 'oo-ui-toolGroup-tools oo-ui-popupToolGroup-tools oo-ui-listToolGroup-tools oo-ui-toolGroup-enabled-tools oo-ui-popupToolGroup-active-tools';
|
|
|
menuContainer.style.minWidth = 'unset';
|
|
|
// 设置弹出位置
|
|
|
menuContainer.style.position = 'fixed';
|
|
|
menuContainer.style.zIndex = 105;
|
|
|
menuContainer.style.right = position.x ? ($(window).width() - position.x) + 'px' : null;
|
|
|
menuContainer.style.bottom = position.y ? ($(window).height() - position.y) + 'px' : null;
|
|
|
|
|
|
function hideMenu() {
|
|
|
menuContainer.remove();
|
|
|
$('#isekai-offcanvas-cover').addClass('hidden');
|
|
|
}
|
|
|
|
|
|
menuItems.forEach((menuItem) => {
|
|
|
var menuElem = document.createElement('span');
|
|
|
menuElem.className = 'oo-ui-widget oo-ui-widget-enabled oo-ui-tool';
|
|
|
|
|
|
var menuLink = document.createElement('a');
|
|
|
menuLink.className = 'oo-ui-tool-link';
|
|
|
menuLink.tabIndex = 0;
|
|
|
menuLink.role = 'button';
|
|
|
menuLink.addEventListener('click', function(e) {
|
|
|
e.preventDefault();
|
|
|
menuItem.onClick();
|
|
|
hideMenu();
|
|
|
|
|
|
});
|
|
|
|
|
|
var menuLabel = document.createElement('span');
|
|
|
menuLabel.className = 'oo-ui-tool-title';
|
|
|
menuLabel.innerText = menuItem.label;
|
|
|
|
|
|
menuLink.appendChild(menuLabel);
|
|
|
menuElem.appendChild(menuLink);
|
|
|
menuContainer.appendChild(menuElem);
|
|
|
});
|
|
|
|
|
|
$('#isekai-offcanvas-cover').removeClass('hidden');
|
|
|
document.body.appendChild(menuContainer);
|
|
|
}
|
|
|
|
|
|
$(function() {
|
|
|
// 创建目录dom
|
|
|
$('body').append(`
|
|
|
<div id="isekai-offcanvas-toc" class="toc-offcanvas">
|
|
|
<ul></ul>
|
|
|
</div>
|
|
|
<a role="button" href="#" id="iseai-offcanvas-btn" class="toc-offcanvas-btn" aria-label="Open float table of contents menu"></a>
|
|
|
<div id="isekai-offcanvas-cover" class="toc-offcanvas-cover hidden"></div>
|
|
|
`);
|
|
|
var menuIcon = new OO.ui.IconWidget({ icon: 'menu' });
|
|
|
$('#iseai-offcanvas-btn').append(menuIcon.$element);
|
|
|
|
|
|
// 生成目录
|
|
|
var parserOutput = $('.mw-parser-output');
|
|
|
var headings = parserOutput.find('h1,h2,h3,h4,h5,h6');
|
|
|
|
|
|
var headNum = new Array(6).fill(0);
|
|
|
var menuList = [{
|
|
|
number: false,
|
|
|
text: mw.msg('isekai-offcanvastoc-description-item'),
|
|
|
id: 'bodyContent'
|
|
|
}];
|
|
|
|
|
|
headings.each(function() {
|
|
|
var headline = $(this).find('.mw-headline');
|
|
|
if (headline.length > 0) {
|
|
|
headingList.push(headline);
|
|
|
var text = headline.text();
|
|
|
var headId = headline.prop('id');
|
|
|
var indentNum = parseInt(this.tagName.replace(/^H/, ''));
|
|
|
// 计算折叠
|
|
|
var menuNumStringBuilder = [];
|
|
|
headNum[indentNum - 1] ++;
|
|
|
for (var i = 0; i < indentNum; i ++) {
|
|
|
menuNumStringBuilder.push(headNum[i]);
|
|
|
}
|
|
|
for (var i = indentNum; i < headNum.length; i ++) {
|
|
|
headNum[i] = 0;
|
|
|
}
|
|
|
var menuNum = menuNumStringBuilder.join('.');
|
|
|
menuList.push({
|
|
|
number: menuNum,
|
|
|
text: text,
|
|
|
id: headId
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 生成dom
|
|
|
var tocContainer = $('#isekai-offcanvas-toc ul');
|
|
|
menuList.forEach(function(menuInfo) {
|
|
|
var listItem = document.createElement('a');
|
|
|
listItem.href = '#' + menuInfo.id;
|
|
|
listItem.dataset.id = menuInfo.id;
|
|
|
listItem.classList.add('list-item');
|
|
|
|
|
|
var titleItem = document.createElement('span');
|
|
|
titleItem.classList.add('title');
|
|
|
titleItem.innerText = menuInfo.text;
|
|
|
|
|
|
if (menuInfo.number) {
|
|
|
var numberItem = document.createElement('span');
|
|
|
numberItem.classList.add('number');
|
|
|
numberItem.innerText = menuInfo.number;
|
|
|
listItem.appendChild(numberItem);
|
|
|
}
|
|
|
|
|
|
listItem.appendChild(titleItem);
|
|
|
|
|
|
tocContainer[0].appendChild(listItem);
|
|
|
});
|
|
|
|
|
|
// 事件
|
|
|
$('#isekai-offcanvas-cover').on('click', function() {
|
|
|
closeOffcanvas();
|
|
|
});
|
|
|
|
|
|
$('#iseai-offcanvas-btn').on('click', function(e) {
|
|
|
e.preventDefault();
|
|
|
|
|
|
updateActive();
|
|
|
openOffcanvas();
|
|
|
});
|
|
|
|
|
|
var btnElem = $('#iseai-offcanvas-btn')[0];
|
|
|
btnElem.addEventListener('contextmenu', function(e) {
|
|
|
e.preventDefault();
|
|
|
|
|
|
if (e.clientX) {
|
|
|
openContextMenu({
|
|
|
x: e.clientX,
|
|
|
y: e.clientY
|
|
|
});
|
|
|
} else {
|
|
|
openContextMenu({
|
|
|
x: e.pageX - window.scrollX,
|
|
|
y: e.pageY - window.scrollY
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
|
|
|
if (isMobileSafari) {
|
|
|
// Safari下对长按的特殊处理
|
|
|
var longPressTimer;
|
|
|
|
|
|
btnElem.addEventListener('touchstart', function(e) {
|
|
|
longPressTimer = setTimeout(function() {
|
|
|
e.preventDefault();
|
|
|
openContextMenu({
|
|
|
x: e.pageX - window.scrollX,
|
|
|
y: e.pageY - window.scrollY
|
|
|
});
|
|
|
}, 200);
|
|
|
});
|
|
|
|
|
|
btnElem.addEventListener('touchend', function(e) {
|
|
|
if (longPressTimer) {
|
|
|
clearInterval(longPressTimer);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
$('#isekai-offcanvas-toc ul .list-item').on('click', function(e) {
|
|
|
// 点击链接
|
|
|
e.preventDefault();
|
|
|
var target = $(this).data('id');
|
|
|
if (target && target != '') {
|
|
|
target = '#' + target;
|
|
|
$('#isekai-offcanvas-toc ul .list-item').removeClass('active');
|
|
|
$(this).addClass('active');
|
|
|
scrollToAnchor(target);
|
|
|
if (window.innerWidth < 550) { // 手机端,关闭抽屉
|
|
|
closeOffcanvas();
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
});
|
|
|
|
|
|
$('#content').on('dblclick', function(e) {
|
|
|
if (menuHidden || window._openOffcanvasTocViaDblclick) {
|
|
|
openOffcanvas();
|
|
|
}
|
|
|
});
|
|
|
}); |