完成页面列表预览组件
parent
fb8e78e630
commit
6e162edd86
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace Isekai\Widgets;
|
||||
|
||||
use Html;
|
||||
use Title;
|
||||
|
||||
class PreviewPageListWidget {
|
||||
public const CONTAINER_CLASS_NAME = 'isekai-card isekai-preview-page-list-card';
|
||||
|
||||
public static function create($text, $params, \Parser $parser, \PPFrame $frame) {
|
||||
$parser->getOutput()->addModules(['ext.isekai.previewPageList']);
|
||||
|
||||
$loader = $params['loader'] ?? 'unknown';
|
||||
if ($loader === 'unknown' && !empty(trim($text))) {
|
||||
$loader = 'pages';
|
||||
}
|
||||
|
||||
$autoFocus = $params['autofocus'] ?? false;
|
||||
if ($autoFocus === 'false' || $autoFocus === '0') {
|
||||
$autoFocus = false;
|
||||
} else {
|
||||
$autoFocus = true;
|
||||
}
|
||||
|
||||
$lazyLoad = $params['lazyload'] ?? false;
|
||||
if ($lazyLoad === 'false' || $lazyLoad === '0') {
|
||||
$lazyLoad = false;
|
||||
} else {
|
||||
$lazyLoad = true;
|
||||
}
|
||||
|
||||
switch ($loader) {
|
||||
case 'pages':
|
||||
$pageList = explode("\n", trim($text));
|
||||
$pageList = array_map(function ($page) {
|
||||
return trim($page);
|
||||
}, $pageList);
|
||||
|
||||
return Html::openElement('div', [
|
||||
'class' => self::CONTAINER_CLASS_NAME,
|
||||
'data-loader' => $loader,
|
||||
'data-autofocus' => $autoFocus,
|
||||
'data-lazyload' => $lazyLoad,
|
||||
]) . Html::element('script', [
|
||||
'type' => 'application/json',
|
||||
'data-pages' => true,
|
||||
], json_encode($pageList)) . Html::closeElement('div');
|
||||
case 'category':
|
||||
$category = $params['category'] ?? null;
|
||||
if (!$category) {
|
||||
return Html::element('span', [
|
||||
'class' => 'error'
|
||||
], wfMessage('isekai-preview-page-list-error-invalid-category')->parse());
|
||||
}
|
||||
|
||||
$categoryTitle = new Title($category, NS_CATEGORY);
|
||||
$categoryTitleText = $categoryTitle->getPrefixedText();
|
||||
|
||||
return Html::element('div', [
|
||||
'class' => self::CONTAINER_CLASS_NAME,
|
||||
'data-category' => $categoryTitleText,
|
||||
'data-autofocus' => $autoFocus,
|
||||
'data-lazyload' => $lazyLoad,
|
||||
]);
|
||||
}
|
||||
|
||||
return Html::element('span', [
|
||||
'class' => 'error'
|
||||
], wfMessage('isekai-preview-page-list-error-invalid-loader', $loader)->parse());
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
const Vue = require("vue");
|
||||
|
||||
class BasePageListLoader {
|
||||
loadPageList() {
|
||||
return Promise.reject(new Error('Not implemented'));
|
||||
}
|
||||
|
||||
parseHTMLString(txt) {
|
||||
try {
|
||||
let parser = new DOMParser();
|
||||
let xmlDoc = parser.parseFromString(txt, "text/html");
|
||||
return xmlDoc;
|
||||
} catch(e) {
|
||||
console.error(e.message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
loadPageContent(pageInfo) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!pageInfo || !pageInfo.fullTitle) {
|
||||
return reject(new Error('pageInfo.fullTitle is null'));
|
||||
}
|
||||
|
||||
let url = mw.util.getUrl(pageInfo.fullTitle);
|
||||
if (url.indexOf('?') >= 0) {
|
||||
url += '&';
|
||||
} else {
|
||||
url += '?'
|
||||
}
|
||||
url += 'action=render';
|
||||
$.get(url, (str) => {
|
||||
let $dom = $(this.parseHTMLString(str));
|
||||
let $content = $dom.find('.mw-parser-output');
|
||||
if ($content.length > 0) {
|
||||
//删除目录
|
||||
$content.find('.toc').remove();
|
||||
resolve($content.html());
|
||||
}
|
||||
}, 'html');
|
||||
});
|
||||
}
|
||||
|
||||
getPageUrl(pageInfo) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!pageInfo || !pageInfo.fullTitle) {
|
||||
return reject(new Error('pageInfo.fullTitle is null'));
|
||||
}
|
||||
|
||||
let url = mw.util.getUrl(pageInfo.fullTitle);
|
||||
resolve(url);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标题生成页面信息
|
||||
* @param {string} title
|
||||
* @returns
|
||||
*/
|
||||
makePageInfoFromTitle(title) {
|
||||
let pageInfo = {
|
||||
fullTitle: title,
|
||||
title: title,
|
||||
subTitle: null
|
||||
};
|
||||
if (title.indexOf('/') !== 0) {
|
||||
let splitPos = title.lastIndexOf('/');
|
||||
pageInfo.title = title.substring(splitPos + 1);
|
||||
pageInfo.subTitle = title.substring(0, splitPos);
|
||||
}
|
||||
return pageInfo;
|
||||
}
|
||||
}
|
||||
|
||||
class PresetPageListLoader extends BasePageListLoader {
|
||||
/**
|
||||
* 从页面列表加载页面
|
||||
* @param {string[]} pageList
|
||||
*/
|
||||
constructor(pageList) {
|
||||
super();
|
||||
|
||||
this.pageList = pageList.map((pageTitle) => {
|
||||
return this.makePageInfoFromTitle(pageTitle);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载页面列表
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
loadPageList() {
|
||||
return Promise.resolve(this.pageList);
|
||||
}
|
||||
}
|
||||
|
||||
class CategoryPageListLoader extends BasePageListLoader {
|
||||
/**
|
||||
* 从分类加载页面
|
||||
* @param {string} category
|
||||
*/
|
||||
constructor(category) {
|
||||
super();
|
||||
|
||||
this.category = category;
|
||||
this.api = new mw.Api();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载页面列表
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
loadPageList() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.api.get({
|
||||
action: 'query',
|
||||
list: 'categorymembers',
|
||||
cmtitle: this.category,
|
||||
cmtype: page,
|
||||
cmnamespace: 0,
|
||||
cmlimit: 200,
|
||||
cmsort: 'sortkey',
|
||||
}).done((data) => {
|
||||
if(data.query && data.query.categorymembers){
|
||||
let pageList = data.query.categorymembers.map((pageInfo) => {
|
||||
return this.makePageInfoFromTitle(pageInfo.title);
|
||||
});
|
||||
resolve(pageList);
|
||||
} else if(data.error){
|
||||
reject(new Error(data.error.info));
|
||||
} else {
|
||||
reject(new Error(mw.message('isekai-discover-error-cannotload').parse()));
|
||||
}
|
||||
}).fail((data) => {
|
||||
reject(new Error(data.error.info));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let previewPageList = document.querySelectorAll('.isekai-preview-page-list-card');
|
||||
if (previewPageList.length > 0) {
|
||||
const App = require("./PreviewPageList.vue");
|
||||
previewPageList.forEach((previewPageListDom) => {
|
||||
try {
|
||||
let props = {};
|
||||
let dataset = previewPageListDom.dataset;
|
||||
|
||||
props.autoFocus = (dataset.autofocus == '1');
|
||||
props.lazyLoad = (dataset.lazyload == '1');
|
||||
|
||||
switch (dataset.loader) {
|
||||
case 'pages':
|
||||
let pagesJsonEl = document.querySelector('script[type="application/json"][data-pages]');
|
||||
let pageList = [];
|
||||
if (pagesJsonEl) {
|
||||
pageList = JSON.parse(pagesJsonEl.innerHTML);
|
||||
}
|
||||
props.pageListLoader = new PresetPageListLoader(pageList);
|
||||
break;
|
||||
case 'category':
|
||||
let category = dataset.category;
|
||||
props.pageListLoader = new CategoryPageListLoader(category);
|
||||
break;
|
||||
default:
|
||||
console.error('Unknown loader: ' + dataset.loader);
|
||||
}
|
||||
|
||||
Vue.createMwApp(App, props).mount(previewPageListDom);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
@preview-page-list-height: 70vh;
|
||||
|
||||
.isekai-preview-page-list-card {
|
||||
height: @preview-page-list-height;
|
||||
}
|
||||
|
||||
.isekai-preview-page-list-card > .card-header {
|
||||
height: 2.2rem;
|
||||
}
|
||||
|
||||
.isekai-preview-page-list-container {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
|
||||
.loading {
|
||||
width: 100%;
|
||||
height: 99.5%;
|
||||
height: calc(100% - 2px); // fix: overflow because of border
|
||||
margin-top: 1px;
|
||||
display: flex;
|
||||
|
||||
.spinner {
|
||||
margin: auto;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.oo-ui-progressBarWidget {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.isekai-booklet-layout {
|
||||
display: flex;
|
||||
height: @preview-page-list-height;
|
||||
}
|
||||
|
||||
// 页面列表
|
||||
.isekai-booklet-menu {
|
||||
flex: 0 0;
|
||||
width: 280px;
|
||||
flex-basis: 280px;
|
||||
padding-bottom: 0 !important;
|
||||
height: @preview-page-list-height;
|
||||
overflow-y: auto;
|
||||
border-right: 1px solid rgba(0,0,0,.12);
|
||||
}
|
||||
|
||||
.isekai-preview-page-list {
|
||||
width: 100%;
|
||||
|
||||
.isekai-list-item {
|
||||
&.active {
|
||||
box-shadow: none;
|
||||
background-color: #36c;
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 页面预览
|
||||
.isekai-booklet-content {
|
||||
flex: 1 1;
|
||||
width: 0;
|
||||
height: @preview-page-list-height;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.card-header {
|
||||
flex: 0 0;
|
||||
}
|
||||
|
||||
.back-button-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.isekai-preview-container {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
flex: 1 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
||||
// 提示选择页面
|
||||
.isekai-preview-page-list-placeholder {
|
||||
width: 100%;
|
||||
height: @preview-page-list-height;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.isekai-booklet-layout {
|
||||
position: relative;
|
||||
|
||||
&.animating {
|
||||
.isekai-booklet-menu,
|
||||
.isekai-booklet-content {
|
||||
transition: transform 150ms ease-in-out, opacity 150ms ease-in-out;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.isekai-booklet-menu {
|
||||
display: block;
|
||||
z-index: 3;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.isekai-booklet-content {
|
||||
display: flex;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&.focus-menu-transition {
|
||||
.isekai-booklet-menu {
|
||||
transform: scale(100%);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.isekai-booklet-content {
|
||||
transform: scale(80%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.focus-content-transition {
|
||||
.isekai-booklet-menu {
|
||||
transform: scale(120%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.isekai-booklet-content {
|
||||
transform: scale(100%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.isekai-booklet-menu {
|
||||
width: 100%;
|
||||
flex-basis: 100%;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.isekai-booklet-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.focus-content {
|
||||
.isekai-booklet-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.isekai-booklet-content {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.back-button-container {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue