refactor: extract PublicNoteList shared component
Move the duplicated <ul> + infinite scroll + note row markup into a new PublicNoteList component with a #meta scoped slot. Both list views now delegate rendering to it, supplying only their view-specific author/date markup via the slot.
This commit is contained in:
65
src/components/PublicNoteList.vue
Normal file
65
src/components/PublicNoteList.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { PublicNoteListItem } from "@/modules/note/models/Note"
|
||||||
|
import { slugify } from "@/utils/slugify"
|
||||||
|
import { vInfiniteScroll } from "@vueuse/components"
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
notes: PublicNoteListItem[]
|
||||||
|
canLoadMore: boolean
|
||||||
|
onLoadMore: () => Promise<void>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineSlots<{
|
||||||
|
meta(props: { note: PublicNoteListItem }): unknown
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ul
|
||||||
|
class="list rounded-box shadow-sm"
|
||||||
|
v-infinite-scroll="[onLoadMore, { canLoadMore: () => canLoadMore }]"
|
||||||
|
>
|
||||||
|
<li v-for="note in notes" class="list-row">
|
||||||
|
<div class="list-col">
|
||||||
|
<router-link
|
||||||
|
:to="{
|
||||||
|
name: 'PublicNoteView',
|
||||||
|
params: {
|
||||||
|
did: note.did,
|
||||||
|
rkey: note.rkey,
|
||||||
|
slug: slugify(note.title),
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
class="btn btn-link"
|
||||||
|
>{{ note.title }}</router-link
|
||||||
|
>
|
||||||
|
<div class="text-xs opacity-80 alias">
|
||||||
|
<slot name="meta" :note="note" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.list-col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alias {
|
||||||
|
text-align: right;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import BackButton from "@/components/BackButton.vue"
|
import BackButton from "@/components/BackButton.vue"
|
||||||
|
import PublicNoteList from "@/components/PublicNoteList.vue"
|
||||||
import { usePublicNoteList } from "@/hooks/usePublicNoteList.hook"
|
import { usePublicNoteList } from "@/hooks/usePublicNoteList.hook"
|
||||||
import { getAuthor } from "@/modules/atproto/getAuthor"
|
import { getAuthor } from "@/modules/atproto/getAuthor"
|
||||||
import { slugify } from "@/utils/slugify"
|
|
||||||
import { computedAsync } from "@vueuse/core"
|
import { computedAsync } from "@vueuse/core"
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
import { vInfiniteScroll } from "@vueuse/components"
|
|
||||||
|
|
||||||
const props = defineProps<{ did: string }>()
|
const props = defineProps<{ did: string }>()
|
||||||
const did = computed(() => props.did)
|
const did = computed(() => props.did)
|
||||||
@@ -23,33 +22,17 @@ const author = computedAsync(async () => getAuthor(did.value))
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="isLoading"></div>
|
<div v-if="isLoading"></div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<ul
|
<PublicNoteList
|
||||||
class="list rounded-box shadow-sm"
|
:notes="notes"
|
||||||
v-infinite-scroll="[onLoadMore, { canLoadMore: () => canLoadMore }]"
|
:can-load-more="canLoadMore"
|
||||||
|
:on-load-more="onLoadMore"
|
||||||
>
|
>
|
||||||
<li v-for="note in notes" class="list-row">
|
<template #meta="{ note }">
|
||||||
<div class="list-col">
|
|
||||||
<router-link
|
|
||||||
:to="{
|
|
||||||
name: 'PublicNoteView',
|
|
||||||
params: {
|
|
||||||
did: note.did,
|
|
||||||
rkey: note.rkey,
|
|
||||||
slug: slugify(note.title),
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
class="btn btn-link"
|
|
||||||
>{{ note.title }}</router-link
|
|
||||||
>
|
|
||||||
|
|
||||||
<div class="text-xs opacity-80 alias">
|
|
||||||
<span v-if="note.publishedAt">
|
<span v-if="note.publishedAt">
|
||||||
{{ new Date(note.publishedAt).toLocaleDateString() }}
|
{{ new Date(note.publishedAt).toLocaleDateString() }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</PublicNoteList>
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
@@ -81,27 +64,6 @@ const author = computedAsync(async () => getAuthor(did.value))
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.list-col {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: inline;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alias {
|
|
||||||
text-align: right;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 769px) {
|
@media screen and (min-width: 769px) {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import BackButton from "@/components/BackButton.vue"
|
import BackButton from "@/components/BackButton.vue"
|
||||||
|
import PublicNoteList from "@/components/PublicNoteList.vue"
|
||||||
import { usePublicNoteList } from "@/hooks/usePublicNoteList.hook"
|
import { usePublicNoteList } from "@/hooks/usePublicNoteList.hook"
|
||||||
import { slugify } from "@/utils/slugify"
|
|
||||||
import { vInfiniteScroll } from "@vueuse/components"
|
|
||||||
|
|
||||||
const { notes, isLoading, canLoadMore, onLoadMore, getAuthor } =
|
const { notes, isLoading, canLoadMore, onLoadMore, getAuthor } =
|
||||||
usePublicNoteList()
|
usePublicNoteList()
|
||||||
@@ -16,26 +15,12 @@ const { notes, isLoading, canLoadMore, onLoadMore, getAuthor } =
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="isLoading"></div>
|
<div v-if="isLoading"></div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<ul
|
<PublicNoteList
|
||||||
class="list rounded-box shadow-sm"
|
:notes="notes"
|
||||||
v-infinite-scroll="[onLoadMore, { canLoadMore: () => canLoadMore }]"
|
:can-load-more="canLoadMore"
|
||||||
|
:on-load-more="onLoadMore"
|
||||||
>
|
>
|
||||||
<li v-for="note in notes" class="list-row">
|
<template #meta="{ note }">
|
||||||
<div class="list-col">
|
|
||||||
<router-link
|
|
||||||
:to="{
|
|
||||||
name: 'PublicNoteView',
|
|
||||||
params: {
|
|
||||||
did: note.did,
|
|
||||||
rkey: note.rkey,
|
|
||||||
slug: slugify(note.title),
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
class="btn btn-link"
|
|
||||||
>{{ note.title }}</router-link
|
|
||||||
>
|
|
||||||
|
|
||||||
<div class="text-xs opacity-80 alias">
|
|
||||||
<router-link
|
<router-link
|
||||||
v-if="getAuthor(note.did)"
|
v-if="getAuthor(note.did)"
|
||||||
:to="{
|
:to="{
|
||||||
@@ -49,15 +34,11 @@ const { notes, isLoading, canLoadMore, onLoadMore, getAuthor } =
|
|||||||
|
|
||||||
<template v-if="note.publishedAt">
|
<template v-if="note.publishedAt">
|
||||||
<span> • </span>
|
<span> • </span>
|
||||||
<span>{{
|
<span>{{ new Date(note.publishedAt).toLocaleDateString() }}</span>
|
||||||
new Date(note.publishedAt).toLocaleDateString()
|
|
||||||
}}</span>
|
|
||||||
</template>
|
</template>
|
||||||
<div v-else class="skeleton h-4 w-20"></div>
|
<div v-else class="skeleton h-4 w-20"></div>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</PublicNoteList>
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
@@ -89,27 +70,6 @@ const { notes, isLoading, canLoadMore, onLoadMore, getAuthor } =
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.list-col {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: inline;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alias {
|
|
||||||
text-align: right;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 769px) {
|
@media screen and (min-width: 769px) {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user