feat: article editor core
This commit is contained in:
1 parent
d493a181e1
commit
a4734b2c91
55 files changed
+4186
-496
No files matched your search
@@ -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>
|
||||
Reference in new issue
Block a user