2020-01-29 20:37:25 +01:00
|
|
|
|
<template>
|
2023-10-17 03:57:20 +02:00
|
|
|
|
<transition
|
|
|
|
|
:name="defaultStore.state.animation ? 'fade' : ''"
|
|
|
|
|
mode="out-in"
|
|
|
|
|
>
|
2023-04-08 02:01:42 +02:00
|
|
|
|
<MkLoading v-if="fetching" />
|
2023-01-18 23:54:59 +01:00
|
|
|
|
|
2024-04-28 15:24:54 +02:00
|
|
|
|
<MkError v-else-if="error" @retry="reload()" />
|
2023-01-18 23:54:59 +01:00
|
|
|
|
|
|
|
|
|
<div v-else-if="empty" key="_empty_" class="empty">
|
|
|
|
|
<slot name="empty">
|
|
|
|
|
<div class="_fullinfo">
|
2023-04-08 02:01:42 +02:00
|
|
|
|
<img
|
2023-09-26 05:57:14 +02:00
|
|
|
|
src="/static-assets/badges/info.webp"
|
2023-04-08 02:01:42 +02:00
|
|
|
|
class="_ghost"
|
|
|
|
|
alt="Error"
|
|
|
|
|
/>
|
2023-01-18 23:54:59 +01:00
|
|
|
|
<div>{{ i18n.ts.nothing }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</slot>
|
|
|
|
|
</div>
|
2021-04-22 15:29:33 +02:00
|
|
|
|
|
2023-05-29 15:11:42 +02:00
|
|
|
|
<div v-else ref="rootEl" class="list">
|
2023-04-08 02:01:42 +02:00
|
|
|
|
<div
|
|
|
|
|
v-show="pagination.reversed && more"
|
|
|
|
|
key="_more_"
|
|
|
|
|
class="cxiknjgy _gap"
|
|
|
|
|
>
|
|
|
|
|
<MkButton
|
|
|
|
|
v-if="!moreFetching"
|
|
|
|
|
class="button"
|
|
|
|
|
:disabled="moreFetching"
|
|
|
|
|
:style="{ cursor: moreFetching ? 'wait' : 'pointer' }"
|
|
|
|
|
primary
|
|
|
|
|
@click="fetchMoreAhead"
|
|
|
|
|
>
|
2023-01-18 23:54:59 +01:00
|
|
|
|
{{ i18n.ts.loadMore }}
|
|
|
|
|
</MkButton>
|
2023-04-08 02:01:42 +02:00
|
|
|
|
<MkLoading v-else class="loading" />
|
2023-01-18 23:54:59 +01:00
|
|
|
|
</div>
|
2024-04-29 04:56:31 +02:00
|
|
|
|
<slot :items="items" :folded-items="foldedItems"></slot>
|
2023-04-08 02:01:42 +02:00
|
|
|
|
<div
|
|
|
|
|
v-show="!pagination.reversed && more"
|
|
|
|
|
key="_more_"
|
|
|
|
|
class="cxiknjgy _gap"
|
|
|
|
|
>
|
|
|
|
|
<MkButton
|
|
|
|
|
v-if="!moreFetching"
|
|
|
|
|
v-appear="
|
2023-10-17 03:57:20 +02:00
|
|
|
|
defaultStore.state.enableInfiniteScroll &&
|
|
|
|
|
!disableAutoLoad
|
2023-04-08 02:01:42 +02:00
|
|
|
|
? fetchMore
|
|
|
|
|
: null
|
|
|
|
|
"
|
|
|
|
|
class="button"
|
|
|
|
|
:disabled="moreFetching"
|
|
|
|
|
:style="{ cursor: moreFetching ? 'wait' : 'pointer' }"
|
|
|
|
|
primary
|
|
|
|
|
@click="fetchMore"
|
|
|
|
|
>
|
2023-01-18 23:54:59 +01:00
|
|
|
|
{{ i18n.ts.loadMore }}
|
|
|
|
|
</MkButton>
|
2023-04-08 02:01:42 +02:00
|
|
|
|
<MkLoading v-else class="loading" />
|
2021-12-23 08:10:13 +01:00
|
|
|
|
</div>
|
2021-04-22 15:29:33 +02:00
|
|
|
|
</div>
|
2023-01-18 23:54:59 +01:00
|
|
|
|
</transition>
|
2023-04-08 02:01:42 +02:00
|
|
|
|
</template>
|
|
|
|
|
|
2024-04-26 16:39:58 +02:00
|
|
|
|
<script lang="ts" setup generic="E extends PagingKey, Fold extends PagingAble">
|
|
|
|
|
import type { ComponentPublicInstance, ComputedRef, Ref } from "vue";
|
2024-04-20 14:55:47 +02:00
|
|
|
|
import {
|
|
|
|
|
computed,
|
|
|
|
|
isRef,
|
|
|
|
|
onActivated,
|
|
|
|
|
onDeactivated,
|
|
|
|
|
ref,
|
|
|
|
|
unref,
|
|
|
|
|
watch,
|
|
|
|
|
} from "vue";
|
2024-04-03 17:10:49 +02:00
|
|
|
|
import type { Endpoints, TypeUtils } from "firefish-js";
|
2023-04-08 02:01:42 +02:00
|
|
|
|
import * as os from "@/os";
|
2024-04-26 16:39:58 +02:00
|
|
|
|
import { isTopVisible, onScrollTop } from "@/scripts/scroll";
|
2023-04-08 02:01:42 +02:00
|
|
|
|
import MkButton from "@/components/MkButton.vue";
|
|
|
|
|
import { i18n } from "@/i18n";
|
2023-10-17 03:57:20 +02:00
|
|
|
|
import { defaultStore } from "@/store";
|
2023-04-08 02:01:42 +02:00
|
|
|
|
|
2024-04-12 10:37:32 +02:00
|
|
|
|
/**
|
|
|
|
|
* ref type of MkPagination<E>
|
|
|
|
|
* Due to Vue's incomplete type support for generic components,
|
|
|
|
|
* we have to manually maintain this type instead of
|
|
|
|
|
* using `InstanceType<typeof MkPagination>`
|
|
|
|
|
*/
|
|
|
|
|
export type MkPaginationType<
|
|
|
|
|
E extends PagingKey,
|
|
|
|
|
Item = Endpoints[E]["res"][number],
|
|
|
|
|
> = ComponentPublicInstance & {
|
|
|
|
|
items: Item[];
|
|
|
|
|
queue: Item[];
|
|
|
|
|
backed: boolean;
|
|
|
|
|
reload: () => Promise<void>;
|
|
|
|
|
refresh: () => Promise<void>;
|
|
|
|
|
prepend: (item: Item) => Promise<void>;
|
2024-04-26 16:39:58 +02:00
|
|
|
|
append: (...item: Item[]) => Promise<void>;
|
2024-04-12 10:37:32 +02:00
|
|
|
|
removeItem: (finder: (item: Item) => boolean) => boolean;
|
|
|
|
|
updateItem: (id: string, replacer: (old: Item) => Item) => boolean;
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-29 04:56:31 +02:00
|
|
|
|
export interface PagingAble {
|
2024-04-26 16:39:58 +02:00
|
|
|
|
id: string;
|
2024-04-29 04:56:31 +02:00
|
|
|
|
}
|
2024-04-26 16:39:58 +02:00
|
|
|
|
|
2024-04-12 10:37:32 +02:00
|
|
|
|
export type PagingKeyOf<T> = TypeUtils.EndpointsOf<T[]>;
|
2024-04-05 07:34:06 +02:00
|
|
|
|
// biome-ignore lint/suspicious/noExplicitAny: Used Intentionally
|
2024-04-12 10:37:32 +02:00
|
|
|
|
export type PagingKey = PagingKeyOf<any>;
|
2024-04-04 08:38:21 +02:00
|
|
|
|
|
|
|
|
|
export interface Paging<E extends PagingKey = PagingKey> {
|
2023-04-08 02:01:42 +02:00
|
|
|
|
endpoint: E;
|
|
|
|
|
limit: number;
|
2024-04-24 15:33:56 +02:00
|
|
|
|
secondFetchLimit?: number;
|
2024-02-12 16:40:46 +01:00
|
|
|
|
params?: Endpoints[E]["req"] | ComputedRef<Endpoints[E]["req"]>;
|
2023-04-08 02:01:42 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 検索APIのような、ページング不可なエンドポイントを利用する場合
|
|
|
|
|
* (そのようなAPIをこの関数で使うのは若干矛盾してるけど)
|
|
|
|
|
*/
|
|
|
|
|
noPaging?: boolean;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* items 配列の中身を逆順にする(新しい方が最後)
|
|
|
|
|
*/
|
|
|
|
|
reversed?: boolean;
|
|
|
|
|
|
2024-04-20 14:55:47 +02:00
|
|
|
|
/**
|
|
|
|
|
* For not-reversed, not-offsetMode,
|
|
|
|
|
* Sort by id in ascending order
|
|
|
|
|
*/
|
|
|
|
|
ascending?: boolean;
|
|
|
|
|
|
2023-04-08 02:01:42 +02:00
|
|
|
|
offsetMode?: boolean;
|
2023-09-02 01:27:33 +02:00
|
|
|
|
}
|
2023-04-08 02:01:42 +02:00
|
|
|
|
|
2024-04-03 17:10:49 +02:00
|
|
|
|
export type PagingOf<T> = Paging<TypeUtils.EndpointsOf<T[]>>;
|
|
|
|
|
|
2024-04-26 16:39:58 +02:00
|
|
|
|
type Item = Endpoints[E]["res"][number];
|
|
|
|
|
type Param = Endpoints[E]["req"] | Record<string, never>;
|
|
|
|
|
|
2024-04-24 15:33:56 +02:00
|
|
|
|
const SECOND_FETCH_LIMIT_DEFAULT = 30;
|
2024-04-26 16:39:58 +02:00
|
|
|
|
const FIRST_FETCH_LIMIT_DEFAULT = 10;
|
2023-04-08 02:01:42 +02:00
|
|
|
|
|
|
|
|
|
const props = withDefaults(
|
|
|
|
|
defineProps<{
|
2024-04-03 17:10:49 +02:00
|
|
|
|
pagination: Paging<E>;
|
2023-01-20 01:33:14 +01:00
|
|
|
|
disableAutoLoad?: boolean;
|
|
|
|
|
displayLimit?: number;
|
2024-04-26 16:39:58 +02:00
|
|
|
|
folder?: (i: Item[]) => Fold[];
|
2023-04-08 02:01:42 +02:00
|
|
|
|
}>(),
|
|
|
|
|
{
|
2023-01-20 01:33:14 +01:00
|
|
|
|
displayLimit: 30,
|
2023-07-06 03:28:27 +02:00
|
|
|
|
},
|
2023-04-08 02:01:42 +02:00
|
|
|
|
);
|
|
|
|
|
|
2024-04-03 17:10:49 +02:00
|
|
|
|
const slots = defineSlots<{
|
2024-04-26 16:39:58 +02:00
|
|
|
|
default(props: { items: Item[]; foldedItems: Fold[] }): unknown;
|
2024-04-04 08:38:21 +02:00
|
|
|
|
empty(props: Record<string, never>): never;
|
2024-04-03 17:10:49 +02:00
|
|
|
|
}>();
|
|
|
|
|
|
2023-04-08 02:01:42 +02:00
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
(ev: "queue", count: number): void;
|
2024-04-04 08:38:21 +02:00
|
|
|
|
(ev: "status", hasError: boolean): void;
|
2023-04-08 02:01:42 +02:00
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
const rootEl = ref<HTMLElement>();
|
|
|
|
|
const items = ref<Item[]>([]);
|
2024-04-26 16:39:58 +02:00
|
|
|
|
const foldedItems = ref([]) as Ref<Fold[]>;
|
|
|
|
|
|
|
|
|
|
// To improve performance, we do not use vue’s `computed` here
|
|
|
|
|
function calculateItems() {
|
|
|
|
|
function getItems<T>(folder: (ns: Item[]) => T[]) {
|
|
|
|
|
const res = [
|
|
|
|
|
folder(prepended.value.toReversed()),
|
|
|
|
|
...arrItems.value.map((arr) => folder(arr)),
|
|
|
|
|
folder(appended.value),
|
|
|
|
|
].flat(1);
|
|
|
|
|
if (props.pagination.reversed) {
|
|
|
|
|
res.reverse();
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
items.value = getItems((x) => x);
|
|
|
|
|
if (props.folder) foldedItems.value = getItems(props.folder);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-08 02:01:42 +02:00
|
|
|
|
const queue = ref<Item[]>([]);
|
2024-04-26 16:39:58 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The cached elements inserted front by `prepend` function
|
|
|
|
|
*/
|
|
|
|
|
const prepended = ref<Item[]>([]);
|
|
|
|
|
/**
|
|
|
|
|
* The array of "frozen" items
|
|
|
|
|
*/
|
|
|
|
|
const arrItems = ref<Item[][]>([]);
|
|
|
|
|
/**
|
|
|
|
|
* The cached elements inserted back by `append` function
|
|
|
|
|
*/
|
|
|
|
|
const appended = ref<Item[]>([]);
|
|
|
|
|
|
|
|
|
|
const idMap = new Map<string, boolean>();
|
|
|
|
|
|
2023-04-08 02:01:42 +02:00
|
|
|
|
const offset = ref(0);
|
2024-04-26 16:39:58 +02:00
|
|
|
|
|
|
|
|
|
type PagingByParam =
|
|
|
|
|
| {
|
|
|
|
|
offset: number;
|
|
|
|
|
}
|
|
|
|
|
| {
|
|
|
|
|
sinceId: string;
|
|
|
|
|
}
|
|
|
|
|
| {
|
|
|
|
|
untilId: string;
|
|
|
|
|
}
|
|
|
|
|
| Record<string, never>;
|
|
|
|
|
let nextPagingBy: PagingByParam = {};
|
|
|
|
|
|
2023-04-08 02:01:42 +02:00
|
|
|
|
const fetching = ref(true);
|
|
|
|
|
const moreFetching = ref(false);
|
|
|
|
|
const more = ref(false);
|
|
|
|
|
const backed = ref(false); // 遡り中か否か
|
|
|
|
|
const isBackTop = ref(false);
|
|
|
|
|
const empty = computed(() => items.value.length === 0);
|
|
|
|
|
const error = ref(false);
|
|
|
|
|
|
|
|
|
|
const init = async (): Promise<void> => {
|
|
|
|
|
queue.value = [];
|
|
|
|
|
fetching.value = true;
|
2024-04-04 08:57:47 +02:00
|
|
|
|
|
2024-04-26 16:39:58 +02:00
|
|
|
|
await fetch(true);
|
2023-04-08 02:01:42 +02:00
|
|
|
|
};
|
|
|
|
|
|
2024-02-29 14:21:19 +01:00
|
|
|
|
const reload = (): Promise<void> => {
|
2024-04-26 16:39:58 +02:00
|
|
|
|
arrItems.value = [];
|
|
|
|
|
appended.value = [];
|
|
|
|
|
prepended.value = [];
|
|
|
|
|
idMap.clear();
|
2024-04-27 14:25:49 +02:00
|
|
|
|
offset.value = 0;
|
|
|
|
|
nextPagingBy = {};
|
2024-02-29 14:21:19 +01:00
|
|
|
|
return init();
|
2023-04-08 02:01:42 +02:00
|
|
|
|
};
|
|
|
|
|
|
2024-02-29 14:21:19 +01:00
|
|
|
|
const refresh = async (): Promise<void> => {
|
2024-04-20 14:55:47 +02:00
|
|
|
|
const params = props.pagination.params ? unref(props.pagination.params) : {};
|
2023-04-08 02:01:42 +02:00
|
|
|
|
await os
|
|
|
|
|
.api(props.pagination.endpoint, {
|
2023-01-20 01:33:14 +01:00
|
|
|
|
...params,
|
2024-04-26 16:39:58 +02:00
|
|
|
|
limit: (items.value.length || foldedItems.value.length) + 1,
|
2023-01-20 01:33:14 +01:00
|
|
|
|
offset: 0,
|
2023-04-08 02:01:42 +02:00
|
|
|
|
})
|
|
|
|
|
.then(
|
2024-04-04 08:38:21 +02:00
|
|
|
|
(res: Item[]) => {
|
2024-04-26 16:39:58 +02:00
|
|
|
|
appended.value = [];
|
|
|
|
|
prepended.value = [];
|
2022-12-29 11:00:30 +01:00
|
|
|
|
|
2024-04-26 16:39:58 +02:00
|
|
|
|
// appended should be inserted into arrItems to fix the element position
|
|
|
|
|
arrItems.value = [res];
|
|
|
|
|
|
|
|
|
|
calculateItems();
|
2023-04-08 02:01:42 +02:00
|
|
|
|
},
|
2024-04-04 08:38:21 +02:00
|
|
|
|
(_err) => {
|
2023-04-08 02:01:42 +02:00
|
|
|
|
error.value = true;
|
|
|
|
|
fetching.value = false;
|
2023-07-06 03:28:27 +02:00
|
|
|
|
},
|
2023-04-08 02:01:42 +02:00
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-26 16:39:58 +02:00
|
|
|
|
async function fetch(firstFetching?: boolean) {
|
|
|
|
|
let limit: number;
|
|
|
|
|
|
|
|
|
|
if (firstFetching) {
|
|
|
|
|
limit = props.pagination.noPaging
|
|
|
|
|
? props.pagination.limit || FIRST_FETCH_LIMIT_DEFAULT
|
|
|
|
|
: (props.pagination.limit || FIRST_FETCH_LIMIT_DEFAULT) + 1;
|
|
|
|
|
|
|
|
|
|
if (props.pagination.ascending) {
|
|
|
|
|
nextPagingBy = {
|
|
|
|
|
// An initial value smaller than all possible ids must be filled in here.
|
|
|
|
|
sinceId: "0",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (
|
|
|
|
|
!more.value ||
|
|
|
|
|
fetching.value ||
|
|
|
|
|
moreFetching.value ||
|
|
|
|
|
items.value.length === 0
|
|
|
|
|
)
|
|
|
|
|
return;
|
|
|
|
|
moreFetching.value = true;
|
|
|
|
|
backed.value = true;
|
|
|
|
|
|
|
|
|
|
limit =
|
|
|
|
|
(props.pagination.secondFetchLimit ?? SECOND_FETCH_LIMIT_DEFAULT) + 1;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-20 14:55:47 +02:00
|
|
|
|
const params = props.pagination.params ? unref(props.pagination.params) : {};
|
2024-04-26 16:39:58 +02:00
|
|
|
|
|
2023-04-08 02:01:42 +02:00
|
|
|
|
await os
|
|
|
|
|
.api(props.pagination.endpoint, {
|
2023-01-20 01:33:14 +01:00
|
|
|
|
...params,
|
2024-04-26 16:39:58 +02:00
|
|
|
|
limit,
|
|
|
|
|
...nextPagingBy,
|
2023-04-08 02:01:42 +02:00
|
|
|
|
})
|
|
|
|
|
.then(
|
2024-04-04 08:38:21 +02:00
|
|
|
|
(res: Item[]) => {
|
2024-04-26 16:39:58 +02:00
|
|
|
|
if (!props.pagination.reversed)
|
|
|
|
|
for (let i = 0; i < res.length; i++) {
|
|
|
|
|
const item = res[i];
|
|
|
|
|
if (props.pagination.reversed) {
|
|
|
|
|
if (i === res.length - (firstFetching ? 2 : 9))
|
|
|
|
|
item._shouldInsertAd_ = true;
|
|
|
|
|
} else {
|
|
|
|
|
if (i === (firstFetching ? 3 : 10)) item._shouldInsertAd_ = true;
|
|
|
|
|
}
|
2023-04-08 02:01:42 +02:00
|
|
|
|
}
|
2024-04-26 16:39:58 +02:00
|
|
|
|
if (!props.pagination.noPaging && res.length > limit - 1) {
|
2023-04-08 02:01:42 +02:00
|
|
|
|
res.pop();
|
|
|
|
|
more.value = true;
|
2023-01-20 01:33:14 +01:00
|
|
|
|
} else {
|
2023-04-08 02:01:42 +02:00
|
|
|
|
more.value = false;
|
2023-01-20 01:33:14 +01:00
|
|
|
|
}
|
2024-04-26 16:39:58 +02:00
|
|
|
|
|
2023-04-08 02:01:42 +02:00
|
|
|
|
offset.value += res.length;
|
2024-04-26 16:39:58 +02:00
|
|
|
|
error.value = false;
|
|
|
|
|
fetching.value = false;
|
2023-04-08 02:01:42 +02:00
|
|
|
|
moreFetching.value = false;
|
|
|
|
|
|
2024-04-26 16:39:58 +02:00
|
|
|
|
const lastRes = res[res.length - 1];
|
|
|
|
|
|
|
|
|
|
if (props.pagination.offsetMode) {
|
|
|
|
|
nextPagingBy = {
|
2023-04-08 02:01:42 +02:00
|
|
|
|
offset: offset.value,
|
2024-04-26 16:39:58 +02:00
|
|
|
|
};
|
|
|
|
|
} else if (props.pagination.ascending) {
|
|
|
|
|
nextPagingBy = {
|
|
|
|
|
sinceId: lastRes?.id,
|
|
|
|
|
};
|
2023-01-20 01:33:14 +01:00
|
|
|
|
} else {
|
2024-04-26 16:39:58 +02:00
|
|
|
|
nextPagingBy = {
|
|
|
|
|
untilId: lastRes?.id,
|
|
|
|
|
};
|
2022-01-09 13:35:35 +01:00
|
|
|
|
}
|
2024-04-26 16:39:58 +02:00
|
|
|
|
|
|
|
|
|
if (firstFetching && props.folder != null) {
|
|
|
|
|
// In this way, prepended has some initial values for folding
|
|
|
|
|
prepended.value = res.toReversed();
|
|
|
|
|
} else {
|
|
|
|
|
// For ascending and offset modes, append and prepend may cause item duplication
|
|
|
|
|
// so they need to be filtered out.
|
|
|
|
|
if (props.pagination.offsetMode || props.pagination.ascending) {
|
|
|
|
|
for (const item of appended.value) {
|
|
|
|
|
idMap.set(item.id, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// biome-ignore lint/style/noParameterAssign: assign it intentially
|
|
|
|
|
res = res.filter((item) => {
|
|
|
|
|
if (idMap.has(item)) return false;
|
|
|
|
|
idMap.set(item, true);
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// appended should be inserted into arrItems to fix the element position
|
|
|
|
|
arrItems.value.push(appended.value);
|
|
|
|
|
arrItems.value.push(res);
|
|
|
|
|
appended.value = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
calculateItems();
|
2023-04-08 02:01:42 +02:00
|
|
|
|
},
|
2024-04-04 08:38:21 +02:00
|
|
|
|
(_err) => {
|
2024-04-26 16:39:58 +02:00
|
|
|
|
error.value = true;
|
|
|
|
|
fetching.value = false;
|
2023-04-08 02:01:42 +02:00
|
|
|
|
moreFetching.value = false;
|
2023-07-06 03:28:27 +02:00
|
|
|
|
},
|
2023-04-08 02:01:42 +02:00
|
|
|
|
);
|
2024-04-26 16:39:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchMore = async (): Promise<void> => {
|
|
|
|
|
await fetch();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fetchMoreAhead = async (): Promise<void> => {
|
|
|
|
|
await fetch();
|
2023-04-08 02:01:42 +02:00
|
|
|
|
};
|
|
|
|
|
|
2024-04-26 16:39:58 +02:00
|
|
|
|
const prepend = (...item: Item[]): void => {
|
|
|
|
|
// If there are too many prepended, merge them into arrItems
|
|
|
|
|
if (
|
|
|
|
|
prepended.value.length >
|
|
|
|
|
(props.pagination.secondFetchLimit || SECOND_FETCH_LIMIT_DEFAULT)
|
|
|
|
|
) {
|
|
|
|
|
arrItems.value.unshift(prepended.value.toReversed());
|
|
|
|
|
prepended.value = [];
|
|
|
|
|
// We don't need to calculate here because it won't cause any changes in items
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-08 02:01:42 +02:00
|
|
|
|
if (props.pagination.reversed) {
|
2024-04-26 16:39:58 +02:00
|
|
|
|
prepended.value.push(...item);
|
|
|
|
|
calculateItems();
|
2023-04-08 02:01:42 +02:00
|
|
|
|
} else {
|
2024-04-26 16:39:58 +02:00
|
|
|
|
// When displaying for the first time, just do this is OK
|
2023-04-08 02:01:42 +02:00
|
|
|
|
if (!rootEl.value) {
|
2024-04-26 16:39:58 +02:00
|
|
|
|
prepended.value.push(...item);
|
|
|
|
|
calculateItems();
|
2023-04-08 02:01:42 +02:00
|
|
|
|
return;
|
2023-01-20 01:33:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-08 02:01:42 +02:00
|
|
|
|
const isTop =
|
|
|
|
|
isBackTop.value ||
|
2024-03-28 17:26:52 +01:00
|
|
|
|
(document.body.contains(rootEl.value) && isTopVisible(rootEl.value));
|
2023-04-08 02:01:42 +02:00
|
|
|
|
|
|
|
|
|
if (isTop) {
|
2024-04-26 16:39:58 +02:00
|
|
|
|
prepended.value.push(...item);
|
|
|
|
|
calculateItems();
|
2023-04-08 02:01:42 +02:00
|
|
|
|
} else {
|
2024-04-26 16:39:58 +02:00
|
|
|
|
queue.value.push(...item);
|
2023-04-08 02:01:42 +02:00
|
|
|
|
onScrollTop(rootEl.value, () => {
|
2024-04-26 16:39:58 +02:00
|
|
|
|
prepend(...queue.value);
|
2023-04-08 02:01:42 +02:00
|
|
|
|
queue.value = [];
|
|
|
|
|
});
|
2023-01-20 01:33:14 +01:00
|
|
|
|
}
|
2023-04-08 02:01:42 +02:00
|
|
|
|
}
|
|
|
|
|
};
|
2022-01-09 13:35:35 +01:00
|
|
|
|
|
2024-04-26 16:39:58 +02:00
|
|
|
|
const append = (...items: Item[]): void => {
|
|
|
|
|
appended.value.push(...items);
|
|
|
|
|
calculateItems();
|
2023-04-08 02:01:42 +02:00
|
|
|
|
};
|
2022-01-09 13:35:35 +01:00
|
|
|
|
|
2024-04-26 16:39:58 +02:00
|
|
|
|
const _removeItem = (arr: Item[], finder: (item: Item) => boolean): boolean => {
|
|
|
|
|
const i = arr.findIndex(finder);
|
2023-04-08 02:01:42 +02:00
|
|
|
|
if (i === -1) {
|
|
|
|
|
return false;
|
2022-12-29 11:00:30 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 16:39:58 +02:00
|
|
|
|
arr.splice(i, 1);
|
2023-04-08 02:01:42 +02:00
|
|
|
|
return true;
|
|
|
|
|
};
|
2023-01-20 01:33:14 +01:00
|
|
|
|
|
2024-04-26 16:39:58 +02:00
|
|
|
|
const _updateItem = (
|
|
|
|
|
arr: Item[],
|
|
|
|
|
id: Item["id"],
|
|
|
|
|
replacer: (old: Item) => Item,
|
|
|
|
|
): boolean => {
|
|
|
|
|
const i = arr.findIndex((item) => item.id === id);
|
2023-04-08 02:01:42 +02:00
|
|
|
|
if (i === -1) {
|
|
|
|
|
return false;
|
2022-12-29 11:00:30 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 16:39:58 +02:00
|
|
|
|
arr[i] = replacer(arr[i]);
|
2023-04-08 02:01:42 +02:00
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-26 16:39:58 +02:00
|
|
|
|
const removeItem = (finder: (item: Item) => boolean): boolean => {
|
|
|
|
|
const res =
|
|
|
|
|
_removeItem(prepended.value, finder) ||
|
|
|
|
|
_removeItem(appended.value, finder) ||
|
|
|
|
|
arrItems.value.filter((arr) => _removeItem(arr, finder)).length > 0;
|
|
|
|
|
calculateItems();
|
|
|
|
|
return res;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateItem = (id: Item["id"], replacer: (old: Item) => Item): boolean => {
|
|
|
|
|
const res =
|
|
|
|
|
_updateItem(prepended.value, id, replacer) ||
|
|
|
|
|
_updateItem(appended.value, id, replacer) ||
|
|
|
|
|
arrItems.value.filter((arr) => _updateItem(arr, id, replacer)).length > 0;
|
|
|
|
|
calculateItems();
|
|
|
|
|
return res;
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-04 08:38:21 +02:00
|
|
|
|
if (props.pagination.params && isRef<Param>(props.pagination.params)) {
|
2024-04-28 15:24:54 +02:00
|
|
|
|
watch(props.pagination.params, reload, { deep: true });
|
2023-04-08 02:01:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
queue,
|
|
|
|
|
(a, b) => {
|
|
|
|
|
if (a.length === 0 && b.length === 0) return;
|
|
|
|
|
emit("queue", queue.value.length);
|
|
|
|
|
},
|
2023-07-06 03:28:27 +02:00
|
|
|
|
{ deep: true },
|
2023-04-08 02:01:42 +02:00
|
|
|
|
);
|
|
|
|
|
|
2024-02-29 14:21:19 +01:00
|
|
|
|
watch(error, (n, o) => {
|
|
|
|
|
if (n === o) return;
|
|
|
|
|
emit("status", n);
|
|
|
|
|
});
|
|
|
|
|
|
2023-04-08 02:01:42 +02:00
|
|
|
|
init();
|
|
|
|
|
|
|
|
|
|
onActivated(() => {
|
|
|
|
|
isBackTop.value = false;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onDeactivated(() => {
|
|
|
|
|
isBackTop.value = window.scrollY === 0;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
items,
|
|
|
|
|
queue,
|
|
|
|
|
backed,
|
|
|
|
|
reload,
|
|
|
|
|
refresh,
|
|
|
|
|
prepend,
|
|
|
|
|
append,
|
|
|
|
|
removeItem,
|
|
|
|
|
updateItem,
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.fade-enter-active,
|
|
|
|
|
.fade-leave-active {
|
|
|
|
|
transition: opacity 0.125s ease;
|
|
|
|
|
}
|
|
|
|
|
.fade-enter-from,
|
|
|
|
|
.fade-leave-to {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cxiknjgy {
|
|
|
|
|
> .button {
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
margin-right: auto;
|
2020-02-11 18:52:37 +01:00
|
|
|
|
}
|
2023-04-08 02:01:42 +02:00
|
|
|
|
}
|
2023-05-29 15:11:42 +02:00
|
|
|
|
.list > :deep(._button) {
|
|
|
|
|
margin-inline: auto;
|
|
|
|
|
margin-bottom: 16px;
|
2023-05-29 21:08:33 +02:00
|
|
|
|
&:last-of-type:not(:first-child) {
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
}
|
2023-05-29 15:11:42 +02:00
|
|
|
|
}
|
2023-04-08 02:01:42 +02:00
|
|
|
|
</style>
|