feat: sites and authors integration into existing screens (& formated all frontend files kinda accidentally)
This commit is contained in:
1 parent
3d81d2c717
commit
6b8990c55e
239 files changed
+9194
-1354
No files matched your search
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user