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.
280 lines
11 KiB
Vue
280 lines
11 KiB
Vue
<script>
|
|
const { ref, watch, computed, onMounted, nextTick } = require('vue');
|
|
|
|
function throttle(fn, delay) {
|
|
let timer = null;
|
|
return function () {
|
|
if (!timer) {
|
|
timer = setTimeout(() => {
|
|
fn.apply(this, arguments);
|
|
timer = null;
|
|
}, delay);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isMobile() {
|
|
return window.innerWidth <= 576;
|
|
}
|
|
|
|
module.exports = {
|
|
compatConfig: {
|
|
MODE: 3
|
|
},
|
|
compilerOptions: {
|
|
whitespace: 'condense'
|
|
},
|
|
props: {
|
|
pageListLoader: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
autoFocus: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
lazyLoad: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
},
|
|
setup(props) {
|
|
const containerRef = ref();
|
|
|
|
let initLoaded = false;
|
|
|
|
const pageListLoading = ref(true);
|
|
const contentLoading = ref(false);
|
|
const pageList = ref([]);
|
|
const selectedPageIdx = ref(null);
|
|
const previewPageContent = ref('');
|
|
const selectedPageUrl = ref('#');
|
|
|
|
const bookletClassList = ref([]);
|
|
|
|
const onSelectPage = (idx) => {
|
|
selectedPageIdx.value = idx;
|
|
}
|
|
|
|
const selectedPageInfo = computed(() => {
|
|
if (selectedPageIdx.value !== null && selectedPageIdx.value < pageList.value.length) {
|
|
return pageList.value[selectedPageIdx.value];
|
|
} else {
|
|
return '';
|
|
}
|
|
});
|
|
|
|
const loadPageList = () => {
|
|
props.pageListLoader.loadPageList().then((loadedPageList) => {
|
|
pageListLoading.value = false;
|
|
pageList.value = loadedPageList;
|
|
initLoaded = true;
|
|
|
|
// 自动选中第一项
|
|
if (!isMobile() && selectedPageIdx.value === null && props.autoFocus && pageList.value.length > 0) {
|
|
selectedPageIdx.value = 0;
|
|
}
|
|
}).catch((err) => {
|
|
console.error(err);
|
|
});
|
|
}
|
|
|
|
const loadPageContent = () => {
|
|
contentLoading.value = true;
|
|
selectedPageUrl.value = '#';
|
|
props.pageListLoader.loadPageContent(selectedPageInfo.value).then((pageContent) => {
|
|
contentLoading.value = false;
|
|
previewPageContent.value = pageContent;
|
|
|
|
props.pageListLoader.getPageUrl(selectedPageInfo.value).then((pageUrl) => {
|
|
selectedPageUrl.value = pageUrl;
|
|
}).catch((err) => {
|
|
console.error(err);
|
|
});
|
|
}).catch((err) => {
|
|
contentLoading.value = false;
|
|
console.error(err);
|
|
});
|
|
}
|
|
|
|
// 处理Lazy Load
|
|
const onWindowScroll = throttle(() => {
|
|
if (initLoaded) {
|
|
window.removeEventListener('scroll', onWindowScroll, { passive: true });
|
|
return;
|
|
}
|
|
|
|
let container = containerRef.value;
|
|
let containerRect = container.getBoundingClientRect();
|
|
let containerTop = containerRect.top;
|
|
let containerBottom = containerRect.bottom;
|
|
|
|
let windowHeight = window.innerHeight;
|
|
|
|
if (containerTop < windowHeight && containerBottom > 0) {
|
|
loadPageList();
|
|
window.removeEventListener('scroll', onWindowScroll, { passive: true });
|
|
}
|
|
}, 100);
|
|
|
|
const onClickBack = () => {
|
|
focusMenuTransition().then(() => {
|
|
selectedPageIdx.value = null;
|
|
});
|
|
}
|
|
|
|
const focusContentTransition = () => {
|
|
return new Promise((resolve, reject) => {
|
|
// 进入动画
|
|
bookletClassList.value = ['animating', 'focus-menu-transition'];
|
|
nextTick(() => {
|
|
setTimeout(() => {
|
|
bookletClassList.value = ['animating', 'focus-content-transition'];
|
|
}, 10);
|
|
|
|
setTimeout(() => {
|
|
bookletClassList.value = ['focus-content'];
|
|
resolve();
|
|
}, 200);
|
|
});
|
|
});
|
|
}
|
|
|
|
const focusMenuTransition = () => {
|
|
return new Promise((resolve, reject) => {
|
|
// 退出动画
|
|
bookletClassList.value = ['animating', 'focus-content-transition'];
|
|
nextTick(() => {
|
|
setTimeout(() => {
|
|
bookletClassList.value = ['animating', 'focus-menu-transition'];
|
|
}, 10);
|
|
|
|
setTimeout(() => {
|
|
bookletClassList.value = ['focus-menu'];
|
|
resolve();
|
|
}, 200);
|
|
});
|
|
});
|
|
}
|
|
|
|
watch(() => selectedPageIdx.value, (newValue, oldValue) => {
|
|
// 选中页面变化时,加载页面内容
|
|
if (newValue !== null) {
|
|
loadPageContent();
|
|
}
|
|
|
|
// 手机端页面动画
|
|
if (oldValue === null && newValue !== null) {
|
|
focusContentTransition();
|
|
}
|
|
});
|
|
|
|
onMounted(() => {
|
|
if (!props.pageListLoader) {
|
|
console.error('pageListLoader is not set');
|
|
return;
|
|
}
|
|
|
|
if (!props.lazyLoad) {
|
|
loadPageList();
|
|
} else {
|
|
window.addEventListener('scroll', onWindowScroll, { passive: true });
|
|
nextTick(() => {
|
|
onWindowScroll(); // init
|
|
});
|
|
}
|
|
});
|
|
|
|
return {
|
|
// global mw object
|
|
mw,
|
|
// states
|
|
containerRef,
|
|
pageListLoading,
|
|
contentLoading,
|
|
pageList,
|
|
selectedPageIdx,
|
|
previewPageContent,
|
|
selectedPageInfo,
|
|
selectedPageUrl,
|
|
bookletAnimateClassList: bookletClassList,
|
|
// methods
|
|
onSelectPage,
|
|
onClickBack
|
|
};
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="isekai-preview-page-list-container" ref="containerRef">
|
|
<div v-if="pageListLoading" class="loading">
|
|
<div class="spinner">
|
|
<div class="oo-ui-widget oo-ui-widget-enabled oo-ui-progressBarWidget-indeterminate oo-ui-progressBarWidget"
|
|
aria-disabled="false" role="progressbar" aria-valuemin="0" aria-valuemax="100">
|
|
<div class="oo-ui-progressBarWidget-bar"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="isekai-booklet-layout" :class="bookletAnimateClassList">
|
|
<div class="isekai-booklet-menu">
|
|
<ul class="isekai-list isekai-preview-page-list isekai-thin-scrollbar">
|
|
<a class="isekai-list-item" v-for="(pageInfo, idx) in pageList" :key="idx" href="javascript:;"
|
|
:class="{ active: idx === selectedPageIdx }" @click.prevent="onSelectPage(idx)">
|
|
<div class="isekai-list-item-content">
|
|
<div class="isekai-list-item-subtitle" v-if="pageInfo.subTitle">{{ pageInfo.subTitle }}</div>
|
|
<div class="isekai-list-item-title">
|
|
<div>{{ pageInfo.title }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="isekai-list-item-icon">
|
|
<span
|
|
class="oo-ui-widget oo-ui-widget-enabled oo-ui-iconElement oo-ui-iconElement-icon oo-ui-icon-next oo-ui-labelElement-invisible oo-ui-iconWidget"
|
|
:class="{ 'oo-ui-image-invert': idx === selectedPageIdx, 'oo-ui-image-progressive': idx !== selectedPageIdx }"
|
|
aria-disabled="false"></span>
|
|
</div>
|
|
</a>
|
|
</ul>
|
|
</div>
|
|
<div class="isekai-booklet-content" v-if="selectedPageIdx === null">
|
|
<div class="isekai-preview-page-list-placeholder">
|
|
{{ mw.msg('isekai-preview-page-list-preview-placeholder') }}
|
|
</div>
|
|
</div>
|
|
<div class="isekai-booklet-content" v-else>
|
|
<div class="card-header">
|
|
<div class="card-header-buttons back-button-container">
|
|
<span class="oo-ui-widget oo-ui-widget-enabled oo-ui-buttonElement oo-ui-buttonElement-frameless oo-ui-iconElement oo-ui-buttonWidget">
|
|
<a class="oo-ui-buttonElement-button" role="button" tabindex="0" href="javascript:;" @click.prevent="onClickBack">
|
|
<span class="oo-ui-iconElement-icon oo-ui-icon-previous"></span>
|
|
<span class="oo-ui-labelElement-label oo-ui-labelElement-invisible">{{ mw.msg('isekai-preview-page-list-back-btn') }}</span>
|
|
<span class="oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator"></span>
|
|
</a>
|
|
</span>
|
|
</div>
|
|
<div class="card-header-text">{{ selectedPageInfo && selectedPageInfo.title }}</div>
|
|
<div class="card-header-buttons">
|
|
<span class="oo-ui-widget oo-ui-widget-enabled oo-ui-buttonElement oo-ui-buttonElement-framed oo-ui-iconElement oo-ui-labelElement oo-ui-flaggedElement-primary oo-ui-flaggedElement-progressive oo-ui-buttonWidget">
|
|
<a class="oo-ui-buttonElement-button" role="button" tabindex="0" :href="selectedPageUrl" target="_blank">
|
|
<span class="oo-ui-iconElement-icon oo-ui-icon-ellipsis oo-ui-image-invert"></span>
|
|
<span class="oo-ui-labelElement-label">{{ mw.msg('isekai-preview-page-list-readmore-btn') }}</span>
|
|
<span class="oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator oo-ui-image-invert"></span>
|
|
</a>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="isekai-preview-container">
|
|
<div v-if="contentLoading" class="loading">
|
|
<div class="spinner">
|
|
<div class="oo-ui-widget oo-ui-widget-enabled oo-ui-progressBarWidget-indeterminate oo-ui-progressBarWidget"
|
|
aria-disabled="false" role="progressbar" aria-valuemin="0" aria-valuemax="100">
|
|
<div class="oo-ui-progressBarWidget-bar"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="isekai-preview-content" v-html="previewPageContent"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template> |