Merge 65f47c5cf9 into 828749be64
This commit is contained in:
commit
e9e4a4476b
|
|
@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue';
|
import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js';
|
import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible, onScrollDownOnce, onScrollUpOnce } from '@/scripts/scroll.js';
|
||||||
import { useDocumentVisibility } from '@/scripts/use-document-visibility.js';
|
import { useDocumentVisibility } from '@/scripts/use-document-visibility.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
|
|
@ -96,8 +96,11 @@ const props = withDefaults(defineProps<{
|
||||||
pagination: Paging;
|
pagination: Paging;
|
||||||
disableAutoLoad?: boolean;
|
disableAutoLoad?: boolean;
|
||||||
displayLimit?: number;
|
displayLimit?: number;
|
||||||
|
disableObserver?: boolean;
|
||||||
|
tolerance?: number;
|
||||||
}>(), {
|
}>(), {
|
||||||
displayLimit: 20,
|
displayLimit: 20,
|
||||||
|
tolerance: TOLERANCE,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|
@ -108,7 +111,7 @@ const emit = defineEmits<{
|
||||||
let rootEl = $shallowRef<HTMLElement>();
|
let rootEl = $shallowRef<HTMLElement>();
|
||||||
|
|
||||||
// 遡り中かどうか
|
// 遡り中かどうか
|
||||||
let backed = $ref(false);
|
let backed = $ref(true);
|
||||||
|
|
||||||
let scrollRemove = $ref<(() => void) | null>(null);
|
let scrollRemove = $ref<(() => void) | null>(null);
|
||||||
|
|
||||||
|
|
@ -153,11 +156,13 @@ const BACKGROUND_PAUSE_WAIT_SEC = 10;
|
||||||
|
|
||||||
// 先頭が表示されているかどうかを検出
|
// 先頭が表示されているかどうかを検出
|
||||||
// https://qiita.com/mkataigi/items/0154aefd2223ce23398e
|
// https://qiita.com/mkataigi/items/0154aefd2223ce23398e
|
||||||
let scrollObserver = $ref<IntersectionObserver>();
|
let scrollObserver = $ref<IntersectionObserver | null>(null);
|
||||||
|
|
||||||
watch([() => props.pagination.reversed, $$(scrollableElement)], () => {
|
|
||||||
if (scrollObserver) scrollObserver.disconnect();
|
|
||||||
|
|
||||||
|
const createScrollObserver = () => {
|
||||||
|
if (scrollObserver) {
|
||||||
|
scrollObserver.disconnect();
|
||||||
|
scrollObserver = null;
|
||||||
|
}
|
||||||
scrollObserver = new IntersectionObserver(entries => {
|
scrollObserver = new IntersectionObserver(entries => {
|
||||||
backed = entries[0].isIntersecting;
|
backed = entries[0].isIntersecting;
|
||||||
}, {
|
}, {
|
||||||
|
|
@ -165,6 +170,16 @@ watch([() => props.pagination.reversed, $$(scrollableElement)], () => {
|
||||||
rootMargin: props.pagination.reversed ? '-100% 0px 100% 0px' : '100% 0px -100% 0px',
|
rootMargin: props.pagination.reversed ? '-100% 0px 100% 0px' : '100% 0px -100% 0px',
|
||||||
threshold: 0.01,
|
threshold: 0.01,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let initialScrollCleaner: () => void | undefined;
|
||||||
|
|
||||||
|
watch([() => props.pagination.reversed, $$(scrollableElement)], () => {
|
||||||
|
if (props.disableObserver) {
|
||||||
|
initialScrollCleaner?.();
|
||||||
|
} else {
|
||||||
|
createScrollObserver();
|
||||||
|
}
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
watch($$(rootEl), () => {
|
watch($$(rootEl), () => {
|
||||||
|
|
@ -177,8 +192,7 @@ watch($$(rootEl), () => {
|
||||||
watch([$$(backed), $$(contentEl)], () => {
|
watch([$$(backed), $$(contentEl)], () => {
|
||||||
if (!backed) {
|
if (!backed) {
|
||||||
if (!contentEl) return;
|
if (!contentEl) return;
|
||||||
|
scrollRemove = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl, executeQueue, props.tolerance, false, !props.disableObserver);
|
||||||
scrollRemove = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl, executeQueue, TOLERANCE);
|
|
||||||
} else {
|
} else {
|
||||||
if (scrollRemove) scrollRemove();
|
if (scrollRemove) scrollRemove();
|
||||||
scrollRemove = null;
|
scrollRemove = null;
|
||||||
|
|
@ -208,9 +222,15 @@ async function init(): Promise<void> {
|
||||||
...params,
|
...params,
|
||||||
limit: props.pagination.limit ?? 10,
|
limit: props.pagination.limit ?? 10,
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
for (let i = 0; i < res.length; i++) {
|
for (let i = 0; i < res.length; i += 3) {
|
||||||
const item = res[i];
|
const item = res[i];
|
||||||
if (i === 3) item._shouldInsertAd_ = true;
|
if (!item) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i === 3) {
|
||||||
|
item._shouldInsertAd_ = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.length === 0 || props.pagination.noPaging) {
|
if (res.length === 0 || props.pagination.noPaging) {
|
||||||
|
|
@ -248,9 +268,9 @@ const fetchMore = async (): Promise<void> => {
|
||||||
untilId: Array.from(items.value.keys()).at(-1),
|
untilId: Array.from(items.value.keys()).at(-1),
|
||||||
}),
|
}),
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
for (let i = 0; i < res.length; i++) {
|
for (let i = 0; i < res.length; i += 10) {
|
||||||
const item = res[i];
|
if (!res[i]) continue;
|
||||||
if (i === 10) item._shouldInsertAd_ = true;
|
res[i]._shouldInsertAd_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reverseConcat = _res => {
|
const reverseConcat = _res => {
|
||||||
|
|
@ -395,9 +415,11 @@ const prepend = (item: MisskeyEntity): void => {
|
||||||
*/
|
*/
|
||||||
function unshiftItems(newItems: MisskeyEntity[]) {
|
function unshiftItems(newItems: MisskeyEntity[]) {
|
||||||
const length = newItems.length + items.value.size;
|
const length = newItems.length + items.value.size;
|
||||||
items.value = new Map([...arrayToEntries(newItems), ...items.value].slice(0, props.displayLimit));
|
const mapEntries = [...arrayToEntries(newItems), ...items.value].slice(0, props.displayLimit);
|
||||||
|
items.value = new Map(mapEntries);
|
||||||
|
|
||||||
if (length >= props.displayLimit) more.value = true;
|
if (length >= props.displayLimit) more.value = true;
|
||||||
|
offset.value = mapEntries.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -406,9 +428,11 @@ function unshiftItems(newItems: MisskeyEntity[]) {
|
||||||
*/
|
*/
|
||||||
function concatItems(oldItems: MisskeyEntity[]) {
|
function concatItems(oldItems: MisskeyEntity[]) {
|
||||||
const length = oldItems.length + items.value.size;
|
const length = oldItems.length + items.value.size;
|
||||||
items.value = new Map([...items.value, ...arrayToEntries(oldItems)].slice(0, props.displayLimit));
|
const mapEntries = [...items.value, ...arrayToEntries(oldItems)].slice(0, props.displayLimit);
|
||||||
|
items.value = new Map(mapEntries);
|
||||||
|
|
||||||
if (length >= props.displayLimit) more.value = true;
|
if (length >= props.displayLimit) more.value = true;
|
||||||
|
offset.value = mapEntries.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeQueue() {
|
function executeQueue() {
|
||||||
|
|
@ -454,11 +478,42 @@ function toBottom() {
|
||||||
scrollToBottom(contentEl!);
|
scrollToBottom(contentEl!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createInitialScrollListener = () => {
|
||||||
|
if (!props.disableObserver || !contentEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
initialScrollCleaner = (props.pagination.reversed ? onScrollUpOnce : onScrollDownOnce)(contentEl, () => {
|
||||||
|
backed = false;
|
||||||
|
nextTick(() => {
|
||||||
|
const cleaner = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl, () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
backed = true;
|
||||||
|
createInitialScrollListener();
|
||||||
|
});
|
||||||
|
}, props.tolerance, true, false);
|
||||||
|
if (cleaner) initialScrollCleaner = cleaner;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch($$(contentEl), () => {
|
||||||
|
if (props.disableObserver) {
|
||||||
|
createInitialScrollListener();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
inited.then(() => {
|
inited.then(() => {
|
||||||
if (props.pagination.reversed) {
|
if (props.pagination.reversed) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setTimeout(toBottom, 800);
|
setTimeout(() => {
|
||||||
|
toBottom();
|
||||||
|
setTimeout(() => {
|
||||||
|
createInitialScrollListener();
|
||||||
|
});
|
||||||
|
}, 800);
|
||||||
|
|
||||||
// scrollToBottomでmoreFetchingボタンが画面外まで出るまで
|
// scrollToBottomでmoreFetchingボタンが画面外まで出るまで
|
||||||
// more = trueを遅らせる
|
// more = trueを遅らせる
|
||||||
|
|
@ -466,6 +521,10 @@ onMounted(() => {
|
||||||
moreFetching.value = false;
|
moreFetching.value = false;
|
||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
nextTick(() => {
|
||||||
|
createInitialScrollListener();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
</FormSplit>
|
</FormSplit>
|
||||||
</div>
|
</div>
|
||||||
|
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination" :tolerance="1" disableObserver>
|
||||||
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
|
|
||||||
<div :class="$style.instances">
|
<div :class="$style.instances">
|
||||||
<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.instance" :to="`/instance-info/${instance.host}`">
|
<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.instance" :to="`/instance-info/${instance.host}`">
|
||||||
<MkInstanceCardMini :instance="instance"/>
|
<MkInstanceCardMini :instance="instance"/>
|
||||||
|
|
@ -70,13 +69,14 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
let host = $ref('');
|
let host = $ref('');
|
||||||
let state = $ref('federating');
|
let state = $ref('federating');
|
||||||
let sort = $ref('+pubSub');
|
let sort = $ref('+pubSub');
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
endpoint: 'federation/instances' as const,
|
endpoint: 'federation/instances' as const,
|
||||||
limit: 10,
|
limit: 30,
|
||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
params: computed(() => ({
|
params: computed(() => ({
|
||||||
sort: sort,
|
sort: sort,
|
||||||
host: host !== '' ? host : null,
|
host: host || null,
|
||||||
...(
|
...(
|
||||||
state === 'federating' ? { federating: true } :
|
state === 'federating' ? { federating: true } :
|
||||||
state === 'subscribing' ? { subscribing: true } :
|
state === 'subscribing' ? { subscribing: true } :
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ export function getScrollPosition(el: HTMLElement | null): number {
|
||||||
return container == null ? window.scrollY : container.scrollTop;
|
return container == null ? window.scrollY : container.scrollTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) {
|
export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false, immediate = true) {
|
||||||
// とりあえず評価してみる
|
// とりあえず評価してみる
|
||||||
if (el.isConnected && isTopVisible(el)) {
|
if (el.isConnected && isTopVisible(el) && immediate) {
|
||||||
cb();
|
cb();
|
||||||
if (once) return null;
|
if (once) return null;
|
||||||
}
|
}
|
||||||
|
|
@ -51,11 +51,11 @@ export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, o
|
||||||
return removeListener;
|
return removeListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) {
|
export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false, immediate = true) {
|
||||||
const container = getScrollContainer(el);
|
const container = getScrollContainer(el);
|
||||||
|
|
||||||
// とりあえず評価してみる
|
// とりあえず評価してみる
|
||||||
if (el.isConnected && isBottomVisible(el, tolerance, container)) {
|
if (el.isConnected && isBottomVisible(el, tolerance, container) && immediate) {
|
||||||
cb();
|
cb();
|
||||||
if (once) return null;
|
if (once) return null;
|
||||||
}
|
}
|
||||||
|
|
@ -134,3 +134,43 @@ export function getBodyScrollHeight() {
|
||||||
document.body.clientHeight, document.documentElement.clientHeight,
|
document.body.clientHeight, document.documentElement.clientHeight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function onScrollDownOnce(el: HTMLElement, cb: () => unknown): () => void {
|
||||||
|
const containerEl = getScrollContainer(el);
|
||||||
|
const targetEl = containerEl ?? window;
|
||||||
|
|
||||||
|
const initialScrollTop = containerEl?.scrollTop ?? 0;
|
||||||
|
|
||||||
|
const listener = () => {
|
||||||
|
if ((containerEl?.scrollTop ?? 0) > initialScrollTop) {
|
||||||
|
cb();
|
||||||
|
targetEl.removeEventListener('scroll', listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
targetEl.addEventListener('scroll', listener, { passive: true });
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
targetEl.removeEventListener('scroll', listener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onScrollUpOnce(el: HTMLElement, cb: () => unknown): () => void {
|
||||||
|
const containerEl = getScrollContainer(el);
|
||||||
|
const targetEl = containerEl ?? window;
|
||||||
|
|
||||||
|
const initialScrollTop = containerEl?.scrollTop ?? 0;
|
||||||
|
|
||||||
|
const listener = () => {
|
||||||
|
if ((containerEl?.scrollTop ?? 0) < initialScrollTop) {
|
||||||
|
cb();
|
||||||
|
targetEl.removeEventListener('scroll', listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
targetEl.addEventListener('scroll', listener, { passive: true });
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
targetEl.removeEventListener('scroll', listener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue