You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

373 lines
12 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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