Browse Source

fix(posts): detail links instead of id jump; friendly published time on public post

Made-with: Cursor
main
npmrun 10 hours ago
parent
commit
c5953cffd2
  1. 13
      app/pages/@[publicSlug]/posts/[postSlug].vue
  2. 50
      app/pages/me/posts/[id].vue
  3. 42
      app/pages/me/posts/index.vue

13
app/pages/@[publicSlug]/posts/[postSlug].vue

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { unwrapApiBody, type ApiResponse } from '../../../utils/http/factory' import { unwrapApiBody, type ApiResponse } from '../../../utils/http/factory'
import { renderSafeMarkdown } from '../../../utils/render-markdown' import { renderSafeMarkdown } from '../../../utils/render-markdown'
import { formatOccurredOnDisplay, occurredOnToIsoAttr } from '../../../utils/timeline-datetime'
definePageMeta({ definePageMeta({
layout: 'public', layout: 'public',
@ -31,6 +32,14 @@ const { data, pending, error } = await useAsyncData(
const renderedBody = computed(() => (data.value ? renderSafeMarkdown(data.value.bodyMarkdown) : '')) const renderedBody = computed(() => (data.value ? renderSafeMarkdown(data.value.bodyMarkdown) : ''))
const publishedAtLabel = computed(() =>
data.value?.publishedAt != null ? formatOccurredOnDisplay(data.value.publishedAt) : '',
)
const publishedAtIso = computed(() =>
data.value?.publishedAt != null ? occurredOnToIsoAttr(data.value.publishedAt) : '',
)
watchEffect(() => { watchEffect(() => {
if (data.value?.title) { if (data.value?.title) {
useHead({ title: data.value.title }) useHead({ title: data.value.title })
@ -55,8 +64,8 @@ watchEffect(() => {
class="max-h-64 w-full max-w-2xl rounded-lg object-cover border border-default" class="max-h-64 w-full max-w-2xl rounded-lg object-cover border border-default"
> >
</div> </div>
<p v-if="data.publishedAt" class="text-sm text-muted"> <p v-if="publishedAtLabel" class="text-sm tabular-nums text-muted">
{{ data.publishedAt }} <time :datetime="publishedAtIso">{{ publishedAtLabel }}</time>
</p> </p>
<h1 class="text-2xl font-semibold"> <h1 class="text-2xl font-semibold">
{{ data.title }} {{ data.title }}

50
app/pages/me/posts/[id].vue

@ -21,8 +21,6 @@ const state = reactive({
const loading = ref(true) const loading = ref(true)
const saving = ref(false) const saving = ref(false)
const postsNav = ref<PostRow[]>([]) const postsNav = ref<PostRow[]>([])
const jumpIdRaw = ref('')
const toast = useToast()
const currentNumericId = computed(() => Number.parseInt(id.value, 10)) const currentNumericId = computed(() => Number.parseInt(id.value, 10))
@ -52,6 +50,18 @@ const publicPostHref = computed(() => {
return `/@${ps}/posts/${encodeURIComponent(state.slug)}` return `/@${ps}/posts/${encodeURIComponent(state.slug)}`
}) })
/** 公开文跳站点详情,否则进编辑页 */
function navTargetHref(p: PostRow | null) {
if (!p) {
return ''
}
const ps = user.value?.publicSlug
if (p.visibility === 'public' && ps) {
return `/@${ps}/posts/${encodeURIComponent(p.slug)}`
}
return `/me/posts/${p.id}`
}
async function load() { async function load() {
loading.value = true loading.value = true
try { try {
@ -79,18 +89,6 @@ async function loadPostNav() {
} }
} }
function goJumpId() {
const n = Number.parseInt(jumpIdRaw.value.trim(), 10)
if (!Number.isFinite(n) || n < 1) {
toast.add({ title: '请输入有效的文章 ID', color: 'error' })
return
}
if (n === currentNumericId.value) {
return
}
void navigateTo(`/me/posts/${n}`)
}
onMounted(() => { onMounted(() => {
void refreshAuth(true) void refreshAuth(true)
void loadPostNav() void loadPostNav()
@ -147,13 +145,11 @@ const shareUrl = computed(() => {
<UButton <UButton
v-if="publicPostHref" v-if="publicPostHref"
:to="publicPostHref" :to="publicPostHref"
target="_blank"
rel="noopener noreferrer"
variant="soft" variant="soft"
color="neutral" color="neutral"
size="sm" size="sm"
> >
公开页 详情
</UButton> </UButton>
<UButton to="/me/posts" variant="ghost" color="neutral" size="sm"> <UButton to="/me/posts" variant="ghost" color="neutral" size="sm">
返回列表 返回列表
@ -165,7 +161,7 @@ const shareUrl = computed(() => {
<div class="flex flex-wrap items-center gap-2"> <div class="flex flex-wrap items-center gap-2">
<UButton <UButton
v-if="newerPost" v-if="newerPost"
:to="`/me/posts/${newerPost.id}`" :to="navTargetHref(newerPost)"
variant="soft" variant="soft"
color="neutral" color="neutral"
size="sm" size="sm"
@ -185,7 +181,7 @@ const shareUrl = computed(() => {
</UButton> </UButton>
<UButton <UButton
v-if="olderPost" v-if="olderPost"
:to="`/me/posts/${olderPost.id}`" :to="navTargetHref(olderPost)"
variant="soft" variant="soft"
color="neutral" color="neutral"
size="sm" size="sm"
@ -203,23 +199,9 @@ const shareUrl = computed(() => {
> >
较旧 较旧
</UButton> </UButton>
<div class="flex flex-1 flex-wrap items-center gap-2 min-w-[12rem] justify-end">
<UInput
v-model="jumpIdRaw"
type="text"
size="sm"
placeholder="文章 ID"
class="w-28"
autocomplete="off"
@keydown.enter.prevent="goJumpId"
/>
<UButton size="sm" color="neutral" @click="goJumpId">
跳转
</UButton>
</div>
</div> </div>
<p v-if="newerPost || olderPost" class="text-xs text-muted mt-2"> <p v-if="newerPost || olderPost" class="text-xs text-muted mt-2">
顺序与列表一致最新在上<code class="text-xs">/me/posts</code> 顺序与列表一致最新在上公开文章打开站点详情其余进入对应编辑页
</p> </p>
</UCard> </UCard>

42
app/pages/me/posts/index.vue

@ -8,8 +8,6 @@ type Row = { id: number; title: string; slug: string; visibility: string }
const posts = ref<Row[]>([]) const posts = ref<Row[]>([])
const loading = ref(true) const loading = ref(true)
const jumpIdRaw = ref('')
const toast = useToast()
const { user, refresh: refreshAuth } = useAuthSession() const { user, refresh: refreshAuth } = useAuthSession()
async function load() { async function load() {
@ -27,22 +25,13 @@ onMounted(() => {
void load() void load()
}) })
function publicPostHref(slug: string) { function postDetailHref(slug: string, visibility: string) {
const ps = user.value?.publicSlug const ps = user.value?.publicSlug
if (!ps) { if (!ps || visibility !== 'public') {
return '' return ''
} }
return `/@${ps}/posts/${encodeURIComponent(slug)}` return `/@${ps}/posts/${encodeURIComponent(slug)}`
} }
function goEditById() {
const n = Number.parseInt(jumpIdRaw.value.trim(), 10)
if (!Number.isFinite(n) || n < 1) {
toast.add({ title: '请输入有效的文章 ID(正整数)', color: 'error' })
return
}
void navigateTo(`/me/posts/${n}`)
}
</script> </script>
<template> <template>
@ -56,23 +45,6 @@ function goEditById() {
</UButton> </UButton>
</div> </div>
<UCard :ui="{ body: 'p-4 sm:p-5' }">
<div class="flex flex-wrap items-end gap-3">
<UFormField label="按 ID 打开编辑" class="min-w-[200px] flex-1">
<UInput
v-model="jumpIdRaw"
placeholder="例如 1"
type="text"
autocomplete="off"
@keydown.enter.prevent="goEditById"
/>
</UFormField>
<UButton color="neutral" @click="goEditById">
跳转
</UButton>
</div>
</UCard>
<div v-if="loading" class="text-muted"> <div v-if="loading" class="text-muted">
加载中 加载中
</div> </div>
@ -88,20 +60,18 @@ function goEditById() {
{{ p.title }} {{ p.title }}
</div> </div>
<div class="text-xs text-muted"> <div class="text-xs text-muted">
id {{ p.id }} · /{{ p.slug }} · {{ p.visibility }} /{{ p.slug }} · {{ p.visibility }}
</div> </div>
</div> </div>
<div class="flex flex-wrap gap-1 justify-end"> <div class="flex flex-wrap gap-1 justify-end">
<UButton <UButton
v-if="user?.publicSlug && p.visibility === 'public'" v-if="postDetailHref(p.slug, p.visibility)"
:to="publicPostHref(p.slug)" :to="postDetailHref(p.slug, p.visibility)"
target="_blank"
rel="noopener noreferrer"
size="xs" size="xs"
variant="soft" variant="soft"
color="neutral" color="neutral"
> >
公开页 详情
</UButton> </UButton>
<UButton :to="`/me/posts/${p.id}`" size="xs" variant="ghost"> <UButton :to="`/me/posts/${p.id}`" size="xs" variant="ghost">
编辑 编辑

Loading…
Cancel
Save