feat: article editor core

This commit is contained in:
fsylvain committed 2026-09-27 02:11:38 +02:00
1 parent d493a181e1
commit a4734b2c91
55 files changed
+4186 -496

No files matched your search

+146
View File
@@ -0,0 +1,146 @@
<script setup lang="ts">
import { computed } from 'vue'
import type { Article } from '@/composables/useArticles'
const props = defineProps<{ article: Article }>()
defineEmits<{ 'copy-url': [] }>()
const open = defineModel<boolean>('open', { default: false })
const TITLE_NOTE =
"Titles can't be changed after an article is created. To use a different title, create a new article and delete this one."
const MONTH_NAMES = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
]
function isSameDay(a: Date, b: Date) {
return (
a.getFullYear() === b.getFullYear() &&
a.getMonth() === b.getMonth() &&
a.getDate() === b.getDate()
)
}
function formatDetailDate(iso: string, relative = false) {
const date = new Date(iso)
const hours = date.getHours().toString().padStart(2, '0')
const minutes = date.getMinutes().toString().padStart(2, '0')
if (relative && isSameDay(date, new Date())) {
return `Today, ${hours}:${minutes}`
}
return `${date.getDate()} ${MONTH_NAMES[date.getMonth()]} ${date.getFullYear()}, ${hours}:${minutes}`
}
const createdLabel = computed(() => formatDetailDate(props.article.created_at))
const updatedLabel = computed(() =>
formatDetailDate(props.article.updated_at, true),
)
const publicUrl = computed(() => `/v1/article/${props.article.id}`)
const imagesCount = computed(() => props.article.images.length)
const apiNote = computed(() =>
props.article.is_online
? 'Live. Your website can read this article.'
: 'Returns 404 until the article is online.',
)
</script>
<template>
<div>
<UCard
data-testid="article-details"
class="hidden lg:flex"
:ui="{
root: 'flex-col gap-4 rounded-none bg-transparent shadow-none ring-0 divide-y-0',
header: 'p-0 sm:p-0',
body: 'flex flex-col gap-4 p-0 sm:p-0',
}"
>
<template #header>
<h2 class="text-base font-semibold text-highlighted">
Details
</h2>
</template>
<dl class="grid grid-cols-[auto_1fr] gap-x-3 gap-y-2 text-sm">
<dt class="text-muted">Created</dt>
<dd class="text-highlighted">{{ createdLabel }}</dd>
<dt class="text-muted">Updated</dt>
<dd class="text-highlighted">{{ updatedLabel }}</dd>
<dt class="text-muted">ID</dt>
<dd class="font-mono text-highlighted">{{ article.id }}</dd>
<dt class="text-muted">Images</dt>
<dd class="text-highlighted">{{ imagesCount }}</dd>
</dl>
<div class="flex flex-col gap-1.5 text-sm">
<p class="text-muted">Public API URL</p>
<div
class="flex items-center gap-1.5 bg-elevated px-2 py-1.5 font-mono text-xs"
>
<span class="flex-1">{{ publicUrl }}</span>
<UIcon name="i-lucide-copy" class="size-3.5" />
</div>
<p class="text-xs/[18px] text-muted">{{ apiNote }}</p>
</div>
<div
class="flex flex-col gap-1 border-t border-default pt-3 text-sm"
>
<p class="text-muted">Title</p>
<p class="text-xs/[18px] text-toned">{{ TITLE_NOTE }}</p>
</div>
</UCard>
<USlideover
v-model:open="open"
side="right"
title="Details"
class="lg:hidden"
>
<template #body>
<div class="flex flex-col gap-4 text-sm">
<dl class="grid grid-cols-[auto_1fr] gap-x-3 gap-y-2">
<dt class="text-muted">Created</dt>
<dd class="text-highlighted">{{ createdLabel }}</dd>
<dt class="text-muted">Updated</dt>
<dd class="text-highlighted">{{ updatedLabel }}</dd>
<dt class="text-muted">ID</dt>
<dd class="font-mono text-highlighted">
{{ article.id }}
</dd>
<dt class="text-muted">Images</dt>
<dd class="text-highlighted">{{ imagesCount }}</dd>
</dl>
<div class="flex flex-col gap-1.5">
<p class="text-muted">Public API URL</p>
<div
class="flex items-center gap-1.5 bg-elevated px-2 py-1.5 font-mono text-xs"
>
<span class="flex-1">{{ publicUrl }}</span>
<UIcon name="i-lucide-copy" class="size-3.5" />
</div>
<p class="text-xs/[18px] text-muted">{{ apiNote }}</p>
</div>
<div
class="flex flex-col gap-1 border-t border-default pt-3"
>
<p class="text-muted">Title</p>
<p class="text-xs/[18px] text-toned">
{{ TITLE_NOTE }}
</p>
</div>
</div>
</template>
</USlideover>
</div>
</template>