Init project

main
落雨楓 3 years ago
commit 45b571cf29

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Hyperzlib
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,48 @@
{
"name": "Isekai Offcanvas Toc",
"namemsg": "isekai-offcanvastoc-name",
"author": "Hyperzlib",
"version": "1.0.0",
"url": "https://www.isekai.cn",
"descriptionmsg": "isekai-offcanvastoc-desc",
"license-name": "MIT",
"type": "other",
"requires": {
},
"MessagesDirs": {
"IsekaiOffcanvasToc": [
"i18n"
]
},
"AutoloadNamespaces": {
"Isekai\\OffcanvasToc\\": "includes"
},
"Hooks": {
"BeforePageDisplay": [
"Isekai\\OffcanvasToc\\Hooks::onLoad"
]
},
"ResourceModules": {
"ext.isekai.offcanvas-toc": {
"scripts": ["ext.isekai.offcanvas-toc.js"],
"styles": ["ext.isekai.offcanvas-toc.less"],
"dependencies": [
"oojs-ui-core",
"oojs-ui-windows"
],
"targets": [
"desktop",
"mobile"
],
"messages": [
]
}
},
"ResourceFileModulePaths": {
"localBasePath": "modules",
"remoteExtPath": "IsekaiOffcanvasToc/modules"
},
"manifest_version": 1
}

@ -0,0 +1,4 @@
{
"isekai-offcanvastoc-name": "Isekai Offcanvas TOC",
"isekai-offcanvastoc-desc": "Show Offcanvas TOC on wiki"
}

@ -0,0 +1,4 @@
{
"isekai-offcanvastoc-name": "异世界百科 悬浮目录",
"isekai-offcanvastoc-desc": "在页面上显示悬浮目录"
}

@ -0,0 +1,26 @@
<?php
namespace Isekai\OffcanvasToc;
use Html;
class Hooks {
public static function onLoad(\OutputPage $outputPage) {
$outputPage->enableOOUI();
$outputPage->addModules('ext.isekai.offcanvas-toc');
$outputPage->addModules('oojs-ui.styles.icons-layout');
$outputPage->addHTML(Html::openElement('div', [
'id' => 'isekai-offcanvas-toc',
'class' => 'toc-offcanvas'
]) . Html::element('ul') . Html::closeElement('div'));
$outputPage->addHTML(Html::openElement('button', [
'id' => 'iseai-offcanvas-btn',
'class' => 'toc-offcanvas-btn'
]) . new \OOUI\IconWidget([
'icon' => 'menu'
]) . Html::closeElement('button'));
$outputPage->addElement('div', ['id' => 'isekai-offcanvas-cover', 'class' => 'toc-offcanvas-cover']);
}
}

@ -0,0 +1,198 @@
var headingList = [];
function getScrollOffset() {
if (mw.config.get('skin') === 'timeless' && $(window).width() > 850) {
return 60;
} else {
return 0;
}
}
function getAnchorOffset() {
if (mw.config.get('skin') === 'timeless' && $(window).width() > 850) {
return 70;
} else {
return 0;
}
}
function scrollToAnchor(link) {
var target = $(link.replace(/\./g, '\\.'));
if (target.length > 0) {
function doScroll() {
var position = target.offset().top - getScrollOffset();
$('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();
console.log('scroll top', scrollTop);
$('#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.length - 1].attr('id');
}
$('#isekai-offcanvas-toc ul .list-item[data-id="' + activedId + '"]').addClass('active');
}
}
function openOffcanvas() {
let scrollbarWidth = getScrollbarWidth();
$('body').addClass(['toc-offcanvas-show', 'toc-offcanvas-open'])
.css('margin-right', scrollbarWidth);
$('#iseai-offcanvas-btn').css('margin-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() {
$('body').removeClass('toc-offcanvas-open');
setTimeout(function() {
$('body').removeClass('toc-offcanvas-show').css('margin-right', 0);
$('#iseai-offcanvas-btn').css('margin-right', 0);
}, 260);
}
// 生成目录
$(function() {
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: '简介',
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() {
updateActive();
openOffcanvas();
});
$('#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).width() < 550) { // 手机端,关闭抽屉
closeOffcanvas();
}
}
return false;
});
});

@ -0,0 +1,141 @@
.toc-offcanvas {
position: fixed;
visibility: hidden;
top: 0;
right: 0;
z-index: 102;
margin: 0;
height: 100vh;
min-width: 275px;
max-width: 80%;
box-shadow: 1px 0 8px 0 rgba(0, 0, 0, 0.35);
transform: translate3d(100%, 0, 0);
transition: transform 250ms linear;
will-change: transform;
overflow-y: auto;
background-color: #eaecf0;
scrollbar-width: thin;
> ul {
list-style: none;
margin: 0;
padding: 0;
a.list-item {
text-decoration: none;
&:hover,
&:active,
&:focus,
&:visited {
color: #54595d;
}
}
.list-item {
display: block;
color: #54595d;
background-color: #fff;
border-top: 1px solid #eaecf0;
max-width: 100%;
padding: 12px 10px 12px 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.title {
margin-inline-start: 10px;
}
&:active {
background-color: #ccc;
}
&:first-of-type {
border-top: none;
margin-top: 8px;
}
&:last-of-type {
margin-bottom: 8px;
}
&.active {
box-shadow: inset 4px 0 0 0 #3366cc;
}
}
}
}
.toc-offcanvas-cover {
position: fixed;
visibility: hidden;
pointer-events: none;
top: 0;
left: 0;
right: 0;
opacity: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
z-index: 101;
transition: opacity 250ms linear;
will-change: opacity;
}
.toc-offcanvas-btn {
position: fixed;
z-index: 50;
right: 18px;
bottom: 6em;
bottom: 20vh;
display: flex;
width: 50px;
height: 50px;
padding: 0;
border: 1px #eee solid;
outline: none;
align-items: center;
justify-content: center;
text-align: center;
border-radius: 50%;
text-shadow: none;
background-color: rgba(255, 255, 255, 0.95);
color: #333;
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.12);
&:hover {
background-color: rgba(255, 255, 255, 0.9);
color: #000;
}
@media screen and (min-width: 768px) {
right: 24px;
}
@media screen and (min-width: 1340px) {
right: 44px;
}
}
body.toc-offcanvas-show {
overflow: hidden;
.toc-offcanvas-cover {
visibility: visible;
pointer-events: auto;
}
.toc-offcanvas {
visibility: visible;
}
}
body.toc-offcanvas-open {
.toc-offcanvas-cover {
opacity: 0.5;
}
.toc-offcanvas {
transform: translate3d(0, 0, 0);
}
}
Loading…
Cancel
Save