From 8c0e5d5ebdafa56ea754881b1db236107e8ad4b6 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Sun, 15 Feb 2026 08:36:05 +0100 Subject: [PATCH] feat: add infinite scroll pagination --- package.json | 3 ++- pnpm-lock.yaml | 40 +++++++++++++++++++++++++++ src/views/PublicNoteListView.vue | 46 ++++++++++++++++++++++---------- 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 5ea33a1..9dbb98a 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@tanstack/vue-query": "^5.92.9", "@toycode/markdown-it-class": "^1.2.4", "@vscode/markdown-it-katex": "^1.1.2", + "@vueuse/components": "^14.2.1", "@vueuse/core": "^13.6.0", "@vueuse/router": "^13.6.0", "date-fns": "^4.1.0", @@ -69,7 +70,7 @@ "autoprefixer": "^10.4.24", "daisyui": "^5.5.18", "dotenv": "^17.2.3", - "eslint": "^.57.1", + "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier-vue": "^5.0.0", "eslint-plugin-simple-import-sort": "^12.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a16de9..7e5036b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@vscode/markdown-it-katex': specifier: ^1.1.2 version: 1.1.2 + '@vueuse/components': + specifier: ^14.2.1 + version: 14.2.1(vue@3.5.18(typescript@5.9.3)) '@vueuse/core': specifier: ^13.6.0 version: 13.6.0(vue@3.5.18(typescript@5.9.3)) @@ -2072,14 +2075,27 @@ packages: '@vue/shared@3.5.28': resolution: {integrity: sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==} + '@vueuse/components@14.2.1': + resolution: {integrity: sha512-wB0SvwJ22mNm1hWCMI1wTWz4x55nDTugT5RIg/KCwlWc1vITWL6ry5VTU3SQzsMD2XcazJK8Be1siIsrBb/Vcw==} + peerDependencies: + vue: ^3.5.0 + '@vueuse/core@13.6.0': resolution: {integrity: sha512-DJbD5fV86muVmBgS9QQPddVX7d9hWYswzlf4bIyUD2dj8GC46R1uNClZhVAmsdVts4xb2jwp1PbpuiA50Qee1A==} peerDependencies: vue: ^3.5.0 + '@vueuse/core@14.2.1': + resolution: {integrity: sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==} + peerDependencies: + vue: ^3.5.0 + '@vueuse/metadata@13.6.0': resolution: {integrity: sha512-rnIH7JvU7NjrpexTsl2Iwv0V0yAx9cw7+clymjKuLSXG0QMcLD0LDgdNmXic+qL0SGvgSVPEpM9IDO/wqo1vkQ==} + '@vueuse/metadata@14.2.1': + resolution: {integrity: sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==} + '@vueuse/router@13.6.0': resolution: {integrity: sha512-iXRwR4K7nz4PReW0QudhnM9NtYGvN4KrskFgF9G7NouM43big3bpSNRRocJKFWK7iu97ww5y82B3QA2zz3S/vw==} peerDependencies: @@ -2091,6 +2107,11 @@ packages: peerDependencies: vue: ^3.5.0 + '@vueuse/shared@14.2.1': + resolution: {integrity: sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==} + peerDependencies: + vue: ^3.5.0 + abab@1.0.4: resolution: {integrity: sha512-I+Wi+qiE2kUXyrRhNsWv6XsjUTBJjSoVSctKNBfLG5zG/Xe7Rjbxf13+vqYHNTwHaFU+FtSlVxOCTiMEVtPv0A==} deprecated: Use your platform's native atob() and btoa() methods instead @@ -8099,6 +8120,12 @@ snapshots: '@vue/shared@3.5.28': {} + '@vueuse/components@14.2.1(vue@3.5.18(typescript@5.9.3))': + dependencies: + '@vueuse/core': 14.2.1(vue@3.5.18(typescript@5.9.3)) + '@vueuse/shared': 14.2.1(vue@3.5.18(typescript@5.9.3)) + vue: 3.5.18(typescript@5.9.3) + '@vueuse/core@13.6.0(vue@3.5.18(typescript@5.9.3))': dependencies: '@types/web-bluetooth': 0.0.21 @@ -8106,8 +8133,17 @@ snapshots: '@vueuse/shared': 13.6.0(vue@3.5.18(typescript@5.9.3)) vue: 3.5.18(typescript@5.9.3) + '@vueuse/core@14.2.1(vue@3.5.18(typescript@5.9.3))': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 14.2.1 + '@vueuse/shared': 14.2.1(vue@3.5.18(typescript@5.9.3)) + vue: 3.5.18(typescript@5.9.3) + '@vueuse/metadata@13.6.0': {} + '@vueuse/metadata@14.2.1': {} + '@vueuse/router@13.6.0(vue-router@4.5.1(vue@3.5.18(typescript@5.9.3)))(vue@3.5.18(typescript@5.9.3))': dependencies: '@vueuse/shared': 13.6.0(vue@3.5.18(typescript@5.9.3)) @@ -8118,6 +8154,10 @@ snapshots: dependencies: vue: 3.5.18(typescript@5.9.3) + '@vueuse/shared@14.2.1(vue@3.5.18(typescript@5.9.3))': + dependencies: + vue: 3.5.18(typescript@5.9.3) + abab@1.0.4: {} acorn-globals@3.1.0: diff --git a/src/views/PublicNoteListView.vue b/src/views/PublicNoteListView.vue index e02c80c..ef1bc79 100644 --- a/src/views/PublicNoteListView.vue +++ b/src/views/PublicNoteListView.vue @@ -2,24 +2,39 @@ import BackButton from "@/components/BackButton.vue" import { Author, getAka } from "@/modules/atproto/getAka" import { PublicNoteListItem } from "@/modules/note/models/Note" -import { computedAsync, useAsyncState } from "@vueuse/core" +import { computedAsync } from "@vueuse/core" +import { computed, ref } from "vue" +import { vInfiniteScroll } from "@vueuse/components" -const { state, isLoading } = useAsyncState<{ - notes: PublicNoteListItem[] -}>( - async () => { - const response = await fetch("https://api.litenote.li212.fr/notes") - return response.json() - }, - { notes: [] }, -) +const isLoading = ref(false) +const notes = ref([]) +const cursor = ref(null) +const canLoadMore = computed(() => cursor.value !== undefined) + +const onLoadMore = async () => { + isLoading.value = true + const noteAPI = new URL("/notes", "https://api.litenote.li212.fr") + + if (cursor.value) { + noteAPI.searchParams.set("cursor", cursor.value) + } + + const response = await fetch(noteAPI) + + const data: { notes: PublicNoteListItem[]; cursor: string | undefined } = + await response.json() + + notes.value.push(...data.notes) + cursor.value = data.cursor + isLoading.value = false +} const aka = computedAsync>(async () => { - if (state.value.notes.length === 0) { + if (notes.value.length === 0) { return new Map() } - return getAka(new Set(state.value.notes.map((n) => n.did))) + return getAka(new Set(notes.value.map((n) => n.did))) }, new Map()) const getAlias = (did: string) => @@ -31,8 +46,11 @@ const getAlias = (did: string) =>

Remanso notes

-
    -
  • +
      +