增加全局组件,更改Tile样式

master
落雨楓 2 years ago
parent 1bab22e474
commit 6b8d9e88cf

@ -160,6 +160,46 @@
"desktop",
"mobile"
]
},
"ext.isekai.baseWidgets": {
"scripts": [
"baseWidgets/ext.isekai.baseWidgets.js"
],
"styles": [
"baseWidgets/ext.isekai.baseWidgets.less"
],
"dependencies": [
"oojs-ui-core",
"oojs-ui-toolbars"
],
"targets": [
"desktop",
"mobile"
],
"messages": [
"isekai-fab-hide-fab-button",
"isekai-fab-hide-fab-button-success"
]
},
"ext.isekai.offcanvasTOC": {
"scripts": [
"offcanvasTOC/ext.isekai.offcanvasTOC.js"
],
"styles": [
"offcanvasTOC/ext.isekai.offcanvasTOC.less"
],
"dependencies": [
"ext.isekai.baseWidgets",
"oojs-ui.styles.icons-layout"
],
"targets": [
"desktop",
"mobile"
],
"messages": [
"isekai-offcanvastoc-menubutton",
"isekai-offcanvastoc-description-item"
]
}
},
"ResourceFileModulePaths": {
@ -180,6 +220,9 @@
"config": {
"IsekaiWidgetInformationTextSeparator": {
"value": ": "
},
"IsekaiGlobalWidgets": {
"value": ["baseWidgets", "offcanvasTOC"]
}
},
"manifest_version": 2

@ -28,5 +28,11 @@
"isekai-font-error-font-not-imported": "未导入字体: \"$1\"。",
"isekai-information-title-base-information": "基本资料",
"isekai-information-error-invalid-type": "提供的信息框类型错误"
"isekai-information-error-invalid-type": "提供的信息框类型错误",
"isekai-fab-hide-fab-button": "隐藏按钮",
"isekai-fab-hide-fab-button-success": "浮动按钮已隐藏,刷新页面可重新显示。",
"isekai-offcanvastoc-menubutton": "目录",
"isekai-offcanvastoc-description-item": "简介"
}

@ -2,6 +2,7 @@
namespace Isekai\Widgets;
use MapCacheLRU;
use MediaWiki\MediaWikiServices;
use Parser;
class Widgets {
@ -41,5 +42,20 @@ class Widgets {
"ext.isekai.information.infobox",
"ext.isekai.collapse"
]);
$globalModuleMap = [
'baseWidgets' => 'ext.isekai.baseWidgets',
'offcanvasTOC' => 'ext.isekai.offcanvasTOC',
];
$config = MediaWikiServices::getInstance()->getMainConfig();
$modules = $config->get('IsekaiGlobalWidgets');
$outputModules = [];
foreach ($modules as $module) {
if (isset($globalModuleMap[$module])) {
$outputModules[] = $globalModuleMap[$module];
}
}
$outputPage->addModules($outputModules);
}
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,217 @@
// =============================================================================
// Fab button
// =============================================================================
.isekai-fab-group {
display: flex;
flex-direction: column;
gap: 10px;
position: fixed;
z-index: 50;
right: 18px;
bottom: 8em;
bottom: 15vh;
@media screen and (min-width: 1340px) {
right: 44px;
}
}
.isekai-fab-hidden {
display: none;
}
.isekai-fab-btn {
display: flex;
width: 48px;
height: 48px;
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);
user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
&:hover {
background-color: rgba(255, 255, 255, 0.9);
color: #000;
}
&:active,
&:focus {
outline: none;
}
@media screen and (max-width: 850px) {
display: none;
}
@media print {
display: none;
}
}
// =============================================================================
// Context menu
// =============================================================================
.isekai-contextmenu-cover {
position: fixed;
z-index: 100;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: transparent;
cursor: default;
}
#iseai-contextmenu {
position: fixed;
z-index: 105;
}
// =============================================================================
// Bottom Navbar
// =============================================================================
@isekai-bottom-nav-button-size: 40px;
.isekai-bottom-nav-container {
position: fixed;
z-index: 100;
bottom: -2px; // Fix white line at the bottom
left: 0;
width: 100%;
height: (@isekai-bottom-nav-button-size + 2px);
padding-bottom: 2px;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.12);
background-color: #fff;
box-sizing: border-box;
user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
// Safe area
padding-left: var(--safe-area-inset-left);
padding-right: var(--safe-area-inset-right);
padding-bottom: ~"calc(var(--safe-area-inset-bottom, 0px) + 2px)";
@media print {
display: none;
}
@media screen and (min-width: 851px) {
display: none;
}
}
.isekai-bottom-nav {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
@supports(backdrop-filter: blur(10px)) {
.isekai-bottom-nav-container {
background-color: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(20px) saturate(120%);
}
}
@supports(-webkit-backdrop-filter: blur(10px)) {
.isekai-bottom-nav {
background-color: rgba(255, 255, 255, 0.8);
-webkit-backdrop-filter: blur(20px) saturate(120%);
}
}
.isekai-bottom-nav-placeholder {
display: flex;
width: 100%;
height: @isekai-bottom-nav-button-size;
}
.isekai-bottom-nav-btn {
display: block;
flex-grow: 0;
flex-shrink: 0;
flex-basis: (@isekai-bottom-nav-button-size + 10px);
width: (@isekai-bottom-nav-button-size + 10px);
height: @isekai-bottom-nav-button-size;
line-height: @isekai-bottom-nav-button-size;
text-align: left;
font-size: 18px;
color: #333;
&:hover {
background-color: rgba(0, 0, 0, 0.05);
color: #333;
text-decoration: none;
}
&:active {
background-color: rgba(0, 0, 0, 0.1);
color: #333;
text-decoration: none;
}
}
.isekai-bottom-nav-btn-expand {
flex-grow: 1;
flex-shrink: 1;
flex-basis: auto;
width: 100%;
display: flex;
}
.isekai-bottom-nav-btn-icon {
display: flex;
flex-grow: 0;
flex-shrink: 0;
flex-basis: (@isekai-bottom-nav-button-size + 10px);
width: (@isekai-bottom-nav-button-size + 10px);
height: @isekai-bottom-nav-button-size;
line-height: @isekai-bottom-nav-button-size;
text-align: center;
align-items: center;
justify-content: center;
}
.isekai-bottom-nav-btn-label {
display: inline-block;
flex-grow: 1;
flex-shrink: 1;
flex-basis: auto;
line-height: @isekai-bottom-nav-button-size;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding-right: 10px;
}
.ve-activated {
.isekai-fab-group {
display: none;
}
.isekai-bottom-nav {
display: none;
}
}
.use-isekai-bottom-nav {
&.skin-minerva {
#mw-mf-page-center {
padding-bottom: 40px;
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -98,6 +98,102 @@
}
}
.isekai-list {
margin: 0 !important;
padding: 0 0 0.5rem 0 !important;
list-style: none;
background-color: transparent;
.isekai-list-item {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
gap: 0.25rem;
-webkit-box-sizing: border-box;
box-sizing: border-box;
min-height: 3rem;
padding: 0 1rem;
text-decoration: none;
cursor: pointer;
border-bottom: 1px solid rgba(0,0,0,.12);
&:hover {
background-color: rgba(0,0,0,.08);
}
&:last-of-type {
border-bottom: none;
}
&.active {
box-shadow: inset 4px 0 0 0 #3366cc;
}
}
a {
color: #000;
text-decoration: none;
&:hover {
color: #000;
text-decoration: none;
}
&:visited {
color: #000;
text-decoration: none;
&:hover {
color: #000;
text-decoration: none;
}
}
}
.isekai-list-item-title {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 1.5rem;
.tag {
opacity: 0.6;
font-size: 0.8rem;
padding: 2px 8px;
background-color: rgba(0, 0, 0, 0.1);
}
}
.isekai-list-item-content {
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
padding-top: 0.625rem;
padding-bottom: 0.625rem;
font-weight: 400;
font-size: 1rem;
line-height: 1.25rem;
}
.isekai-list-item-title~.isekai-list-item-text {
margin-top: 0.25rem;
}
.isekai-list-item-text {
font-size: 0.875rem;
opacity: 0.54;
-webkit-line-clamp: 1;
height: 1.25rem;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
}
}
.isekai-well {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06), 0 -1px 4px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(53,72,91,.07);
border-radius: 5px;
@ -107,6 +203,28 @@
}
.skin-citizen-dark, .skin-timeless-dark {
.isekai-list {
a {
color: white;
text-decoration: none;
&:hover {
color: white;
text-decoration: none;
}
&:visited {
color: white;
text-decoration: none;
&:hover {
color: white;
text-decoration: none;
}
}
}
}
.isekai-well {
background-color: #090909;
}

@ -176,7 +176,12 @@ module.exports = {
});
}
onLoaded();
}).catch(console.error);
}).catch((err) => {
console.error(err);
//
recentData.recentThread = [];
onLoaded();
});
} else {
recentData.recentThread = [];
}

@ -33,119 +33,3 @@
height: @feed-list-height-mobile;
}
}
.isekai-list {
margin: 0 !important;
padding: 0 0 0.5rem 0 !important;
list-style: none;
background-color: transparent;
.isekai-list-item {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
gap: 0.25rem;
-webkit-box-sizing: border-box;
box-sizing: border-box;
min-height: 3rem;
padding: 0 1rem;
text-decoration: none;
cursor: pointer;
border-bottom: 1px solid rgba(0,0,0,.12);
&:hover {
background-color: rgba(0,0,0,.08);
}
&:last-of-type {
border-bottom: none;
}
}
a {
color: #000;
text-decoration: none;
&:hover {
color: #000;
text-decoration: none;
}
&:visited {
color: #000;
text-decoration: none;
&:hover {
color: #000;
text-decoration: none;
}
}
}
.isekai-list-item-title {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 1.5rem;
.tag {
opacity: 0.6;
font-size: 0.8rem;
padding: 2px 8px;
background-color: rgba(0, 0, 0, 0.1);
}
}
.isekai-list-item-content {
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
padding-top: 0.625rem;
padding-bottom: 0.625rem;
font-weight: 400;
font-size: 1rem;
line-height: 1.25rem;
}
.isekai-list-item-title~.isekai-list-item-text {
margin-top: 0.25rem;
}
.isekai-list-item-text {
font-size: 0.875rem;
opacity: 0.54;
-webkit-line-clamp: 1;
height: 1.25rem;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
}
}
.skin-citizen-dark, .skin-timeless-dark {
.isekai-list {
a {
color: white;
text-decoration: none;
&:hover {
color: white;
text-decoration: none;
}
&:visited {
color: white;
text-decoration: none;
&:hover {
color: white;
text-decoration: none;
}
}
}
}
}

@ -0,0 +1,329 @@
var headingList = [];
var menuList = [];
var bottomNavBtn;
var bottomNavBtnLabel;
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;
})();
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;
}
}
var lastActiveId = null;
function updateActive() {
var scrollTop = $(window).scrollTop() + getAnchorOffset();
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');
}
if (activedId !== lastActiveId) {
$('#isekai-offcanvas-toc ul .list-item').removeClass('active');
$('#isekai-offcanvas-toc ul .list-item[data-id="' + activedId + '"]').addClass('active');
var menuItem = menuList.find((item) => {
return item.id === activedId;
});
if (menuItem) {
if (menuItem.number) {
bottomNavBtnLabel.text(menuItem.number + '. ' + menuItem.text);
} else {
bottomNavBtnLabel.text(menuItem.text);
}
}
lastActiveId = activedId;
}
}
}
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);
$('#isekai-fab-container').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');
$('#isekai-fab-container').css('margin-right', 0);
if (mw.config.get('skin') === 'timeless') {
$('#mw-header-container').css('padding-right', 0);
}
}, 260);
}
}
function throttle(fn, delay) {
var timer = null;
return function() {
if (!timer) {
timer = setTimeout(function() {
fn();
timer = null;
}, delay);
}
};
}
$(function() {
if (mw.config.get('wgIsArticle')) {
// 创建目录dom
$('body').append(`
<div id="isekai-offcanvas-toc" class="toc-offcanvas">
<ul></ul>
</div>
<div id="isekai-offcanvas-cover" class="toc-offcanvas-cover hidden"></div>
`);
// 创建按钮
var menuIcon = new OO.ui.IconWidget({ icon: 'menu' });
isekai.fab.addButton({
id: 'offcanvas-toc',
label: mw.msg('isekai-offcanvastoc-menubutton'),
icon: menuIcon.$element[0],
priority: 0,
onClick: function() {
openOffcanvas();
}
});
var bottomMenuIcon = new OO.ui.IconWidget({ icon: 'menu' });
bottomNavBtn = isekai.bottomNav.addButton({
id: 'offcanvas-toc',
label: mw.msg('isekai-offcanvastoc-menubutton'),
icon: bottomMenuIcon.$element[0],
priority: 0,
expand: true,
onClick: function() {
openOffcanvas();
}
});
bottomNavBtnLabel = $(bottomNavBtn).find('.isekai-bottom-nav-btn-label');
bottomNavBtnLabel.text('');
// 生成目录
var parserOutput = $('.mw-parser-output');
var headings = parserOutput.find('h1,h2,h3,h4,h5,h6,heading-6');
var headNum = new Array(6).fill(0);
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/, ''));
this.classList.forEach((className) => {
if (className.indexOf('heading-') === 0) {
indentNum = parseInt(className.replace(/^heading-/, ''));
}
})
// 计算折叠
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();
});
$('#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 (window._openOffcanvasTocViaDblclick) {
openOffcanvas();
}
});
window.addEventListener('scroll', throttle(updateActive, 500), { passive: true });
updateActive();
}
});

@ -0,0 +1,113 @@
.toc-offcanvas {
position: fixed;
visibility: hidden;
opacity: 0;
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: opacity 250ms ease-in-out, transform 250ms ease-in-out;
will-change: transform;
overflow-y: auto;
background-color: #eaecf0;
scrollbar-width: thin;
padding-top: env(safe-area-inset-top, 0);
padding-bottom: env(safe-area-inset-bottom, 0);
padding-right: env(safe-area-inset-right, 0);
> ul {
list-style: none;
margin: 0;
padding: 0;
a.list-item {
text-decoration: none;
&:hover,
&:active,
&:focus,
&:visited {
color: #54595d;
outline: none;
}
}
.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;
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;
&.hidden {
display: none;
}
}
body.toc-offcanvas-show {
overflow-y: hidden;
.toc-offcanvas {
visibility: visible;
}
#mw-overscroll-bottom-cover {
display: none;
}
}
body.toc-offcanvas-open {
.toc-offcanvas-cover {
opacity: 0.5;
}
.toc-offcanvas {
transform: translate3d(0, 0, 0);
opacity: 1;
}
}

@ -1,10 +1,13 @@
/*!************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
!*** css ./node_modules/.pnpm/css-loader@6.7.3_webpack@5.82.0/node_modules/css-loader/dist/cjs.js!./node_modules/.pnpm/postcss-loader@7.3.0_postcss@8.4.23_webpack@5.82.0/node_modules/postcss-loader/dist/cjs.js!./node_modules/.pnpm/less-loader@11.1.0_less@4.1.3_webpack@5.82.0/node_modules/less-loader/dist/cjs.js!./src/tile/tile.less ***!
\************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
.tile-small,
.tile-medium,
.tile-wide,
.tile-large,
.tile-app {
display: block;
background-color: #1ba1e2;
background: linear-gradient(95deg, #007ec6, #0080cd, #008dd3);
color: #ffffff;
width: 150px;
height: 150px;
@ -16,6 +19,7 @@
-moz-user-select: none;
user-select: none;
max-width: none!important;
border-radius: 6px;
}
.tile-small {
width: 70px;
@ -37,6 +41,15 @@
width: 44px;
height: 44px;
}
.tile-small,
.tile-medium,
.tile-wide,
.tile-large,
.tile-app {
transition: transform 250ms ease-in-out, box-shadow 250ms ease-in-out;
box-shadow: none;
transform: none;
}
.tile-small .icon,
.tile-medium .icon,
.tile-wide .icon,
@ -110,7 +123,8 @@
.tile-wide:hover,
.tile-large:hover,
.tile-app:hover {
outline: rgba(29, 29, 29, 0.1) solid 4px;
transform: translate3d(0, -4px, 0);
box-shadow: 0 4px 2px 0 rgba(0, 0, 0, 0.25);
}
.tile-small {
/*.branding-bar {

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

@ -0,0 +1,110 @@
export class BottomNavWidget {
constructor() {
this.domCreated = false;
this.btnList = [];
}
initDom() {
if (!this.domCreated) {
let bottomNavContainer = document.querySelector('#isekai-bottom-nav');
if (bottomNavContainer) {
this.bottomNavContainer = bottomNavContainer;
this.bottomNavElem = bottomNavContainer.querySelector('.isekai-bottom-nav');
} else {
bottomNavContainer = document.createElement('nav');
bottomNavContainer.id = 'isekai-bottom-nav';
bottomNavContainer.className = 'isekai-bottom-nav-container';
this.bottomNavContainer = bottomNavContainer;
let bottomNavElem = document.createElement('div');
bottomNavElem.className = 'isekai-bottom-nav';
this.bottomNavElem = bottomNavElem;
let bottomNavPlaceholder = document.createElement('div');
bottomNavPlaceholder.id = 'isekai-bottom-nav-placeholder';
bottomNavPlaceholder.className = 'isekai-bottom-nav-placeholder';
bottomNavContainer.appendChild(bottomNavElem);
document.body.appendChild(bottomNavContainer);
document.body.appendChild(bottomNavPlaceholder);
document.body.classList.add('use-isekai-bottom-nav');
}
}
}
addButton(btnInfo) {
if (!this.domCreated) {
this.initDom();
}
let btnElem = document.createElement('a');
btnElem.role = 'button';
btnElem.href = 'javascript:void(0);';
btnElem.classList.add('isekai-bottom-nav-btn');
btnElem.dataset.id = btnInfo.id;
if (btnInfo.className) {
btnElem.classList.add(...btnInfo.className.split(' '));
}
if (btnInfo.icon) {
let iconElem = document.createElement('span');
iconElem.className = 'isekai-bottom-nav-btn-icon';
if (typeof btnInfo.icon === 'string') {
iconElem.innerHTML = btnInfo.icon;
} else if (typeof btnInfo.icon === 'object') {
iconElem.appendChild(btnInfo.icon);
}
btnElem.appendChild(iconElem);
}
if (btnInfo.label) {
btnElem.title = btnInfo.label;
btnElem.setAttribute('aria-label', btnInfo.label);
}
if (btnInfo.expand) {
btnElem.classList.add('isekai-bottom-nav-btn-expand');
let labelElem = document.createElement('span');
labelElem.className = 'isekai-bottom-nav-btn-label';
labelElem.innerText = btnInfo.label ?? '';
btnElem.appendChild(labelElem);
}
if (btnInfo.onClick) {
btnElem.addEventListener('click', btnInfo.onClick);
}
let newBtnInfo = {...btnInfo, element: btnElem, proiorty: btnInfo.priority ?? 0};
let insertAfter = null;
let insertAfterIndex = 0;
this.btnList.forEach((one, index) => {
if (newBtnInfo.priority > one.priority) {
insertAfter = one.element;
insertAfterIndex = index;
}
});
if (insertAfter) {
this.bottomNavElem.insertAfter(btnElem, insertAfter);
this.btnList = [...this.btnList.slice(0, insertAfterIndex + 1), newBtnInfo, ...this.btnList.slice(insertAfterIndex + 1)];
} else {
this.bottomNavElem.prepend(btnElem);
this.btnList.unshift(newBtnInfo);
}
return btnElem;
}
removeButton(btnInfo) {
let btnId = '';
if (typeof btnInfo === 'string') {
btnId = btnInfo;
} else if (typeof btnInfo === 'object') {
btnId = btnInfo.id;
}
let btnElem = this.bottomNavElem.querySelector(`.isekai-bottom-nav-btn[data-id="${btnId}"]`);
if (btnElem) {
btnElem.remove();
}
}
}

@ -0,0 +1,149 @@
const isMobileSafari = (() => {
let v = navigator.userAgent.match(/Version\/(?<version>\S+) Mobile\/\S+/);
if (v && v.groups.version) { // Safari
return true;
}
return false;
})();
export class ContextMenuWidget {
constructor() {
this.menuItems = [];
this.menuItemsUpdated = true;
this.initDom();
}
initDom() {
let menuContainer = document.createElement('div');
menuContainer.id = 'iseai-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.display = 'none';
this.menuContainer = menuContainer;
let menuCover = document.createElement('div');
menuCover.className = 'isekai-contextmenu-cover';
menuCover.style.display = 'none';
menuCover.addEventListener('click', () => {
this.hide();
});
this.menuCover = menuCover;
document.body.appendChild(menuContainer);
document.body.appendChild(menuCover);
}
setMenuItem(menuItems) {
this.menuItems = menuItems;
this.menuItemsUpdated = true;
}
updateMenuItemDom() {
if (this.menuItemsUpdated) {
this.menuContainer.innerHTML = '';
this.menuItems.sort((a, b) => {
return (b.priority ?? 0) - (a.priority ?? 0);
}).forEach((menuItem) => {
let menuElem = document.createElement('span');
menuElem.className = 'oo-ui-widget oo-ui-widget-enabled oo-ui-tool';
let menuLink = document.createElement('a');
menuLink.className = 'oo-ui-tool-link';
menuLink.tabIndex = 0;
menuLink.role = 'button';
menuLink.addEventListener('click', (e) => {
e.preventDefault();
menuItem.onClick?.();
this.hide();
});
let menuLabel = document.createElement('span');
menuLabel.className = 'oo-ui-tool-title';
menuLabel.innerText = menuItem.label;
menuLink.appendChild(menuLabel);
menuElem.appendChild(menuLink);
this.menuContainer.appendChild(menuElem);
});
this.menuItemsUpdated = false;
}
}
show(...args) {
let x = 0;
let y = 0;
if (args[0] instanceof MouseEvent) {
x = args[0].clientX;
y = args[0].clientY;
} else if (args[0] instanceof Element) {
let rect = args[0].getBoundingClientRect();
x = rect.left;
y = rect.top;
} else if (args.length === 2 && typeof args[0] === 'number' && typeof args[1] === 'number') {
x = args[0];
y = args[1];
}
if (this.menuItemsUpdated) {
this.updateMenuItemDom();
}
this.menuContainer.style.display = 'block';
this.menuCover.style.display = 'block';
let menuWidth = this.menuContainer.clientWidth;
let menuHeight = this.menuContainer.clientHeight;
if (x + menuWidth > window.innerWidth) {
x -= menuWidth;
}
if (y + menuHeight > window.innerHeight) {
y -= menuHeight;
}
this.menuContainer.style.left = x + 'px';
this.menuContainer.style.top = y + 'px';
}
hide() {
this.menuContainer.style.display = 'none';
this.menuCover.style.display = 'none';
}
bindToDom(dom) {
dom.addEventListener('contextmenu', (e) => {
e.preventDefault();
this.show(e);
});
if (isMobileSafari) {
// Safari下对长按的特殊处理
let longPressTimer;
dom.addEventListener('touchstart', (e) => {
document.body.classList.add('isekai-contextmenu-ios-longpress');
longPressTimer = setTimeout(() => {
e.preventDefault();
this.show({
x: e.pageX - window.scrollX,
y: e.pageY - window.scrollY
});
}, 200);
});
dom.addEventListener('touchend', function (e) {
document.body.classList.remove('isekai-contextmenu-ios-longpress');
if (longPressTimer) {
clearInterval(longPressTimer);
}
});
}
}
}

@ -0,0 +1,14 @@
import { registerModule } from '../moduleRegister';
import { ContextMenuWidget } from './contextMenu';
import { FabWidget } from './fab';
import { BottomNavWidget } from './bottomNav';
registerModule('ui.ContextMenuWidget', ContextMenuWidget);
registerModule('ui.FabWidget', FabWidget);
registerModule('ui.BottomNavWidget', BottomNavWidget);
const fabInstance = new FabWidget();
registerModule('fab', fabInstance);
const bottomNavInstance = new BottomNavWidget();
registerModule('bottomNav', bottomNavInstance);

@ -0,0 +1,123 @@
export class FabWidget {
constructor() {
this.domCreated = false;
this.btnList = [];
this.hidden = false;
}
initDom() {
if (!this.domCreated) {
let fabContainer = document.querySelector('#isekai-fab-container');
if (fabContainer) {
this.fabContainer = fabContainer;
} else {
fabContainer = document.createElement('div');
fabContainer.id = 'isekai-fab-container';
fabContainer.className = 'isekai-fab-group';
this.fabContainer = fabContainer;
document.body.appendChild(fabContainer);
// 右键隐藏
let menuItems = [
{
label: mw.msg('isekai-fab-hide-fab-button'),
onClick: () => {
this.hide();
mw.notify(mw.msg('isekai-fab-hide-fab-button-success'));
},
}
];
let contextMenu = new isekai.ui.ContextMenuWidget();
contextMenu.setMenuItem(menuItems);
contextMenu.bindToDom(fabContainer);
this.contextMenu = contextMenu;
}
this.domCreated = true;
}
}
addButton(btnInfo) {
if (!this.domCreated) {
this.initDom();
}
let btnElem = document.createElement('a');
btnElem.role = 'button';
btnElem.href = 'javascript:void(0);';
btnElem.className = 'isekai-fab-btn';
btnElem.dataset.id = btnInfo.id;
if (btnInfo.icon) {
let iconElem = document.createElement('span');
iconElem.className = 'isekai-fab-btn-icon';
if (typeof btnInfo.icon === 'string') {
iconElem.innerHTML = btnInfo.icon;
} else if (typeof btnInfo.icon === 'object') {
iconElem.appendChild(btnInfo.icon);
}
btnElem.appendChild(iconElem);
}
if (btnInfo.label) {
btnElem.title = btnInfo.label;
btnElem.setAttribute('aria-label', btnInfo.label);
}
if (btnInfo.onClick) {
btnElem.addEventListener('click', btnInfo.onClick);
}
let newBtnInfo = {...btnInfo, element: btnElem, proiorty: btnInfo.priority ?? 0};
let insertBefore = null;
let insertBeforeIndex = 0;
this.btnList.forEach((one, index) => {
if (one.priority < newBtnInfo.priority) {
insertBefore = one.element;
insertBeforeIndex = index;
}
});
if (insertBefore) {
this.fabContainer.insertBefore(btnElem, insertBefore);
this.btnList = [...this.btnList.slice(0, insertBeforeIndex), newBtnInfo, ...this.btnList.slice(insertBeforeIndex)];
} else {
this.fabContainer.appendChild(btnElem);
this.btnList.push(newBtnInfo);
}
return btnElem;
}
removeButton(btnInfo) {
let btnId = '';
if (typeof btnInfo === 'string') {
btnId = btnInfo;
} else if (typeof btnInfo === 'object') {
btnId = btnInfo.id;
}
let fabBtn = this.fabContainer.querySelector(`.isekai-fab-btn[data-id="${btnId}"]`);
if (fabBtn) {
fabBtn.remove();
}
}
hide() {
this.fabContainer.classList.add('isekai-fab-hidden');
this.hidden = true;
document.body.addEventListener('contextmenu', this._onContextMenu);
}
show() {
this.fabContainer.classList.remove('isekai-fab-hidden');
this.hidden = false;
}
_onContextMenu = (e) => {
e.preventDefault();
document.body.removeEventListener('contextmenu', this._onContextMenu);
this.show();
}
}

@ -32,7 +32,7 @@
@blue: #00AFF0;
@teal: #00aba9;
@cyan: #1ba1e2;
@cobalt: #0050ef;
@cobalt: #0f6cbd;
@indigo: #6a00ff;
@violet: #aa00ff;
@pink: #dc4fad;

@ -7,7 +7,7 @@
.tile-large,
.tile-app {
display: block;
background-color: @cyan;
background: linear-gradient(95deg, #007ec6, #0080cd, #008dd3);
color: @white;
width: extract(@tileMedium, 1);
height: extract(@tileMedium, 2);
@ -17,6 +17,7 @@
overflow: hidden;
user-select: none;
max-width: none!important;
border-radius: 6px;
}
.tile {
@ -47,6 +48,10 @@
.tile-wide,
.tile-large,
.tile-app {
transition: transform 250ms ease-in-out, box-shadow 250ms ease-in-out;
box-shadow: none;
transform: none;
.icon {
max-width: 33%;
height: 33%;
@ -95,7 +100,9 @@
}
&:hover {
outline: @tileOutlineColor solid 4px;
// outline: @tileOutlineColor solid 4px;
transform: translate3d(0, -4px, 0);
box-shadow: 0 4px 2px 0 rgba(0, 0, 0, 0.25);
}
}

@ -10,6 +10,7 @@ const config = {
'createPage': './src/createPage/ext.isekai.createPage.js',
'discover': './src/discover/ext.isekai.discover.js',
'tile': './src/tile/ext.isekai.tile.js',
'baseWidgets': './src/baseWidgets/ext.isekai.baseWidgets.js'
},
output: {
filename: '[name]/ext.isekai.[name].js',

Loading…
Cancel
Save