feat: sites and authors integration into existing screens (& formated all frontend files kinda accidentally)

This commit is contained in:
Florian Sylvain committed 2026-10-15 05:25:44 +02:00
1 parent 3d81d2c717
commit 6b8990c55e
239 files changed
+9194 -1354

No files matched your search

+59 -6
View File
@@ -4,6 +4,8 @@ import { t } from '@/i18n'
import { computed, ref, watchPostEffect } from 'vue'
import { useRouter } from 'vue-router'
import ArticleStatusBadge from '@/components/ArticleStatusBadge.vue'
import AuthorStack from '@/components/AuthorStack.vue'
import type { Site } from '@/composables/useSites'
import { formatAbsoluteDateFromIso, formatRelativeDate } from '@/lib/relativeDate'
import type { Article } from '@/composables/useArticles'
import { DEFAULT_ARTICLE_SORT, type ArticleSortField } from '@/lib/articleListQuery'
@@ -15,9 +17,10 @@ const props = withDefaults(
skeletonRows?: number
sort?: string
busyIds: Set<number>
sites?: Site[]
now?: Date
}>(),
{ skeletonRows: 5, sort: DEFAULT_ARTICLE_SORT },
{ skeletonRows: 5, sort: DEFAULT_ARTICLE_SORT, sites: () => [] },
)
const emit = defineEmits<{
@@ -30,8 +33,13 @@ const emit = defineEmits<{
const router = useRouter()
const columnWidths =
'[&:nth-child(2)]:w-27.5 [&:nth-child(3)]:w-32.5 [&:nth-child(4)]:w-35 [&:nth-child(5)]:w-11'
const SINGLE_SITE_WIDTHS =
'[&:nth-child(2)]:w-26 [&:nth-child(3)]:w-27.5 [&:nth-child(4)]:w-32.5 [&:nth-child(5)]:w-35 [&:nth-child(6)]:w-11'
const SEVERAL_SITES_WIDTHS =
'[&:nth-child(2)]:w-26 [&:nth-child(3)]:w-32.5 [&:nth-child(4)]:w-27.5 [&:nth-child(5)]:w-32.5 [&:nth-child(6)]:w-35 [&:nth-child(7)]:w-11'
const showSite = computed(() => props.sites.length > 1)
const columnWidths = computed(() => (showSite.value ? SEVERAL_SITES_WIDTHS : SINGLE_SITE_WIDTHS))
const sorting = computed(() => {
const [id, direction] = props.sort.split(':')
@@ -44,7 +52,7 @@ const SORTABLE_COLUMNS = ['title', 'status', 'created_at', 'updated_at']
watchPostEffect(() => {
const [active, direction] = props.sort.split(':')
tableRoot.value?.querySelectorAll('thead th').forEach((header, index) => {
if (SORTABLE_COLUMNS[index] === active) {
if (SORTABLE_COLUMNS.includes(active!) && columns.value[index]?.id === active) {
header.setAttribute('aria-sort', direction === 'desc' ? 'descending' : 'ascending')
} else {
header.removeAttribute('aria-sort')
@@ -70,14 +78,33 @@ const caption = computed(() => {
])
})
const columns = [
const columns = computed(() => [
{
accessorKey: 'title',
id: 'title',
get header() {
return t('Title')
},
enableSorting: false,
},
{
id: 'authors',
get header() {
return t('Authors')
},
enableSorting: false,
},
...(showSite.value
? [
{
id: 'site',
get header() {
return t('Site')
},
enableSorting: false,
},
]
: []),
{
accessorKey: 'is_online',
id: 'status',
@@ -89,6 +116,7 @@ const columns = [
},
{
accessorKey: 'created_at',
id: 'created_at',
get header() {
return t('Created')
},
@@ -97,6 +125,7 @@ const columns = [
},
{
accessorKey: 'updated_at',
id: 'updated_at',
get header() {
return t('Updated')
},
@@ -104,7 +133,15 @@ const columns = [
enableSorting: false,
},
{ id: 'actions', header: '', size: 44 },
]
])
function siteNameOf(article: Article) {
return props.sites.find((site) => site.id === article.site_id)?.name ?? ''
}
function authorNames(article: Article) {
return (article.authors ?? []).map((author) => author.name).join(', ')
}
function sortIcon(field: string) {
const [id, direction] = props.sort.split(':')
@@ -188,6 +225,8 @@ function onSelect(_event: Event, row: { original: Article }) {
class="flex h-11 items-center gap-3 border-t border-muted px-3 first:border-t-0"
>
<USkeleton class="h-3 max-w-[55%] flex-1 rounded-none bg-accented" />
<USkeleton class="h-6 w-15 rounded-none bg-accented" />
<USkeleton v-if="showSite" class="h-3 w-17.5 rounded-none bg-accented" />
<USkeleton class="h-5 w-15 rounded-none bg-accented" />
<USkeleton class="h-3 w-20 rounded-none bg-accented" />
<USkeleton class="h-3 w-20 rounded-none bg-accented" />
@@ -290,6 +329,20 @@ function onSelect(_event: Event, row: { original: Article }) {
</RouterLink>
</template>
<template #authors-cell="{ row }">
<span class="flex min-w-0 items-center" :title="authorNames(row.original)">
<AuthorStack
v-if="row.original.authors?.length"
:authors="row.original.authors"
/>
<span v-else :aria-label="t('No authors')" class="text-dimmed">—</span>
</span>
</template>
<template #site-cell="{ row }">
<span class="block truncate text-toned">{{ siteNameOf(row.original) }}</span>
</template>
<template #status-cell="{ row }">
<ArticleStatusBadge
:online="row.original.is_online"