fix (frontend): federation settings glitch when the grid has more than one columns
fix: remove the overflow flag fix: remove the overflow style fix: modify the initial value of backed revert change feat: add padding to the pagination property fix: update top calculation fix: wrong implementation fix: wrong implementation in federation page feat: new implementation fix: wrong implementation fix: wrong implementation # Conflicts: # packages/frontend/src/components/MkPagination.vue fix: wrong execution timing fix: execution timing fix: wrong implementation fix: wrong execute timing fix: execution timing fix: execution timing
This commit is contained in:
parent
eb38f08e13
commit
ac0f552a83
|
|
@ -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 * as Misskey from 'misskey-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 MkButton from '@/components/MkButton.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
|
|
@ -95,8 +95,11 @@ const props = withDefaults(defineProps<{
|
|||
pagination: Paging;
|
||||
disableAutoLoad?: boolean;
|
||||
displayLimit?: number;
|
||||
disableObserver?: boolean;
|
||||
tolerance?: number;
|
||||
}>(), {
|
||||
displayLimit: 20,
|
||||
tolerance: TOLERANCE,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
@ -106,7 +109,7 @@ const emit = defineEmits<{
|
|||
let rootEl = $shallowRef<HTMLElement>();
|
||||
|
||||
// 遡り中かどうか
|
||||
let backed = $ref(false);
|
||||
let backed = $ref(true);
|
||||
|
||||
let scrollRemove = $ref<(() => void) | null>(null);
|
||||
|
||||
|
|
@ -151,11 +154,13 @@ const BACKGROUND_PAUSE_WAIT_SEC = 10;
|
|||
|
||||
// 先頭が表示されているかどうかを検出
|
||||
// https://qiita.com/mkataigi/items/0154aefd2223ce23398e
|
||||
let scrollObserver = $ref<IntersectionObserver>();
|
||||
|
||||
watch([() => props.pagination.reversed, $$(scrollableElement)], () => {
|
||||
if (scrollObserver) scrollObserver.disconnect();
|
||||
let scrollObserver = $ref<IntersectionObserver | null>(null);
|
||||
|
||||
const createScrollObserver = () => {
|
||||
if (scrollObserver) {
|
||||
scrollObserver.disconnect();
|
||||
scrollObserver = null;
|
||||
}
|
||||
scrollObserver = new IntersectionObserver(entries => {
|
||||
backed = entries[0].isIntersecting;
|
||||
}, {
|
||||
|
|
@ -163,6 +168,16 @@ watch([() => props.pagination.reversed, $$(scrollableElement)], () => {
|
|||
rootMargin: props.pagination.reversed ? '-100% 0px 100% 0px' : '100% 0px -100% 0px',
|
||||
threshold: 0.01,
|
||||
});
|
||||
};
|
||||
|
||||
let initialScrollCleaner: () => void | undefined;
|
||||
|
||||
watch([() => props.pagination.reversed, $$(scrollableElement)], () => {
|
||||
if (props.disableObserver) {
|
||||
initialScrollCleaner?.();
|
||||
} else {
|
||||
createScrollObserver();
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
watch($$(rootEl), () => {
|
||||
|
|
@ -175,8 +190,7 @@ watch($$(rootEl), () => {
|
|||
watch([$$(backed), $$(contentEl)], () => {
|
||||
if (!backed) {
|
||||
if (!contentEl) return;
|
||||
|
||||
scrollRemove = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl, executeQueue, TOLERANCE);
|
||||
scrollRemove = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl, executeQueue, props.tolerance, false, !props.disableObserver);
|
||||
} else {
|
||||
if (scrollRemove) scrollRemove();
|
||||
scrollRemove = null;
|
||||
|
|
@ -201,9 +215,15 @@ async function init(): Promise<void> {
|
|||
...params,
|
||||
limit: props.pagination.limit ?? 10,
|
||||
}).then(res => {
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
for (let i = 0; i < res.length; i += 3) {
|
||||
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) {
|
||||
|
|
@ -241,9 +261,9 @@ const fetchMore = async (): Promise<void> => {
|
|||
untilId: Array.from(items.value.keys()).at(-1),
|
||||
}),
|
||||
}).then(res => {
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
const item = res[i];
|
||||
if (i === 10) item._shouldInsertAd_ = true;
|
||||
for (let i = 0; i < res.length; i += 10) {
|
||||
if (!res[i]) continue;
|
||||
res[i]._shouldInsertAd_ = true;
|
||||
}
|
||||
|
||||
const reverseConcat = _res => {
|
||||
|
|
@ -388,9 +408,11 @@ const prepend = (item: MisskeyEntity): void => {
|
|||
*/
|
||||
function unshiftItems(newItems: MisskeyEntity[]) {
|
||||
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;
|
||||
offset.value = mapEntries.length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -399,9 +421,11 @@ function unshiftItems(newItems: MisskeyEntity[]) {
|
|||
*/
|
||||
function concatItems(oldItems: MisskeyEntity[]) {
|
||||
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;
|
||||
offset.value = mapEntries.length;
|
||||
}
|
||||
|
||||
function executeQueue() {
|
||||
|
|
@ -447,11 +471,42 @@ function toBottom() {
|
|||
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(() => {
|
||||
inited.then(() => {
|
||||
if (props.pagination.reversed) {
|
||||
nextTick(() => {
|
||||
setTimeout(toBottom, 800);
|
||||
setTimeout(() => {
|
||||
toBottom();
|
||||
setTimeout(() => {
|
||||
createInitialScrollListener();
|
||||
});
|
||||
}, 800);
|
||||
|
||||
// scrollToBottomでmoreFetchingボタンが画面外まで出るまで
|
||||
// more = trueを遅らせる
|
||||
|
|
@ -459,6 +514,10 @@ onMounted(() => {
|
|||
moreFetching.value = false;
|
||||
}, 2000);
|
||||
});
|
||||
} else {
|
||||
nextTick(() => {
|
||||
createInitialScrollListener();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #header><XHeader :actions="headerActions"/></template>
|
||||
<MkSpacer :contentMax="900">
|
||||
<div class="_gaps">
|
||||
<div>
|
||||
<div ref="form">
|
||||
<MkInput v-model="host" :debounce="true" class="">
|
||||
<template #prefix><i class="ti ti-search"></i></template>
|
||||
<template #label>{{ i18n.ts.host }}</template>
|
||||
|
|
@ -42,8 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkSelect>
|
||||
</FormSplit>
|
||||
</div>
|
||||
|
||||
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
|
||||
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination" :tolerance="1" disable-observer>
|
||||
<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}`">
|
||||
<MkInstanceCardMini :instance="instance"/>
|
||||
|
|
@ -70,13 +69,14 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
|||
let host = $ref('');
|
||||
let state = $ref('federating');
|
||||
let sort = $ref('+pubSub');
|
||||
|
||||
const pagination = {
|
||||
endpoint: 'federation/instances' as const,
|
||||
limit: 10,
|
||||
limit: 30,
|
||||
offsetMode: true,
|
||||
params: computed(() => ({
|
||||
sort: sort,
|
||||
host: host !== '' ? host : null,
|
||||
host: host || null,
|
||||
...(
|
||||
state === 'federating' ? { federating: true } :
|
||||
state === 'subscribing' ? { subscribing: true } :
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ export function getScrollPosition(el: HTMLElement | null): number {
|
|||
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();
|
||||
if (once) return null;
|
||||
}
|
||||
|
|
@ -50,11 +50,11 @@ export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, o
|
|||
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);
|
||||
|
||||
// とりあえず評価してみる
|
||||
if (el.isConnected && isBottomVisible(el, tolerance, container)) {
|
||||
if (el.isConnected && isBottomVisible(el, tolerance, container) && immediate) {
|
||||
cb();
|
||||
if (once) return null;
|
||||
}
|
||||
|
|
@ -132,3 +132,43 @@ export function getBodyScrollHeight() {
|
|||
document.body.clientHeight, document.documentElement.clientHeight,
|
||||
);
|
||||
}
|
||||
|
||||
export function onScrollDownOnce(el: HTMLElement, cb: () => unknown): () => void {
|
||||
const containerEl = getScrollContainer(el);
|
||||
const targetEl = containerEl ?? window;
|
||||
|
||||
let 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;
|
||||
|
||||
let 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