✨ Better details page
This commit is contained in:
parent
f5ebc1748a
commit
7f89b36efd
@ -75,9 +75,11 @@ func (v *PostTypeContext) SortCreatedAt(order string) *PostTypeContext {
|
||||
func (v *PostTypeContext) GetViaAlias(alias string) (models.Feed, error) {
|
||||
var item models.Feed
|
||||
table := viper.GetString("database.prefix") + v.TableName
|
||||
userTable := viper.GetString("database.prefix") + "accounts"
|
||||
if err := v.Tx.
|
||||
Table(table).
|
||||
Select("*, ? as model_type", v.ColumnName).
|
||||
Joins(fmt.Sprintf("INNER JOIN %s AS author ON author_id = author.id", userTable)).
|
||||
Where("alias = ?", alias).
|
||||
First(&item).Error; err != nil {
|
||||
return item, err
|
||||
@ -99,9 +101,11 @@ func (v *PostTypeContext) GetViaAlias(alias string) (models.Feed, error) {
|
||||
func (v *PostTypeContext) Get(id uint, noComments ...bool) (models.Feed, error) {
|
||||
var item models.Feed
|
||||
table := viper.GetString("database.prefix") + v.TableName
|
||||
userTable := viper.GetString("database.prefix") + "accounts"
|
||||
if err := v.Tx.
|
||||
Table(table).
|
||||
Select("*, ? as model_type", v.ColumnName).
|
||||
Joins(fmt.Sprintf("INNER JOIN %s AS author ON author_id = author.id", userTable)).
|
||||
Where("id = ?", id).First(&item).Error; err != nil {
|
||||
return item, err
|
||||
}
|
||||
|
@ -5,14 +5,16 @@
|
||||
<div class="text-sm">{{ props.item?.description }}</div>
|
||||
</section>
|
||||
|
||||
<div v-if="props.brief">
|
||||
<router-link
|
||||
:to="{ name: 'posts.details.articles', params: { alias: props.item?.alias ?? 'not-found' } }"
|
||||
<div v-if="props.brief" class="mt-2">
|
||||
<v-btn
|
||||
append-icon="mdi-arrow-right"
|
||||
class="link underline text-primary font-medium"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
rounded="sm"
|
||||
:to="{ name: 'posts.details.articles', params: { alias: props.item?.alias ?? 'not-found' } }"
|
||||
>
|
||||
Read more...
|
||||
</router-link>
|
||||
Read more
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
@ -22,12 +24,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dompurify from "dompurify";
|
||||
import { parse } from "marked";
|
||||
import dompurify from "dompurify"
|
||||
import { parse } from "marked"
|
||||
|
||||
const props = defineProps<{ item: any, brief?: boolean, contentOnly?: boolean }>();
|
||||
const props = defineProps<{ item: any; brief?: boolean; contentOnly?: boolean }>()
|
||||
|
||||
function parseContent(src: string): string {
|
||||
return dompurify().sanitize(parse(src) as string);
|
||||
return dompurify().sanitize(parse(src) as string)
|
||||
}
|
||||
</script>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<article class="prose prose-comment" v-html="parseContent(props.item.content)" />
|
||||
<article class="prose prose-comment" v-html="parseContent(props.item?.content ?? '')" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -1,15 +1,29 @@
|
||||
<template>
|
||||
<article class="prose prose-moment" v-html="parseContent(props.item.content)" />
|
||||
<div>
|
||||
<article class="prose prose-moment" v-html="parseContent(props.item?.content ?? '')" />
|
||||
|
||||
<div v-if="props.brief" class="my-1">
|
||||
<v-btn
|
||||
append-icon="mdi-arrow-right"
|
||||
variant="tonal"
|
||||
size="x-small"
|
||||
rounded="sm"
|
||||
:to="{ name: 'posts.details.moments', params: { alias: props.item?.alias ?? 'not-found' } }"
|
||||
>
|
||||
More
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dompurify from "dompurify";
|
||||
import { parse } from "marked";
|
||||
import dompurify from "dompurify"
|
||||
import { parse } from "marked"
|
||||
|
||||
const props = defineProps<{ item: any }>();
|
||||
const props = defineProps<{ item: any; brief?: boolean }>()
|
||||
|
||||
function parseContent(src: string): string {
|
||||
return dompurify().sanitize(parse(src) as string);
|
||||
return dompurify().sanitize(parse(src) as string)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -33,7 +33,7 @@
|
||||
@update="updateReactions"
|
||||
/>
|
||||
|
||||
<div class="mt-1 text-xs text-opacity-60 flex gap-2 items-center">
|
||||
<div class="mt-1 text-xs opacity-80 flex gap-2 items-center">
|
||||
<span>Posted at {{ new Date(props.item?.created_at).toLocaleString() }}</span>
|
||||
</div>
|
||||
|
||||
|
@ -70,6 +70,7 @@ import { request } from "@/scripts/request"
|
||||
import { useEditor } from "@/stores/editor"
|
||||
import { getAtk } from "@/stores/userinfo"
|
||||
import { reactive, ref, watch } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import PlannedPublish from "@/components/publish/parts/PlannedPublish.vue"
|
||||
import Media from "@/components/publish/parts/Media.vue"
|
||||
|
||||
@ -91,6 +92,8 @@ const success = ref(false)
|
||||
const loading = ref(false)
|
||||
const uploading = ref(false)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
async function postMoment(evt: SubmitEvent) {
|
||||
const form = evt.target as HTMLFormElement
|
||||
const payload = data.value
|
||||
@ -108,8 +111,10 @@ async function postMoment(evt: SubmitEvent) {
|
||||
})
|
||||
if (res.status === 200) {
|
||||
form.reset()
|
||||
const data = await res.json()
|
||||
success.value = true
|
||||
editor.show.moment = false
|
||||
router.push({ name: "posts.details.moments", params: { alias: data.alias } })
|
||||
} else {
|
||||
error.value = await res.text()
|
||||
}
|
||||
|
@ -14,6 +14,12 @@ const router = createRouter({
|
||||
component: () => import("@/views/explore.vue")
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
path: "/p/moments/:alias",
|
||||
name: "posts.details.moments",
|
||||
component: () => import("@/views/posts/moments.vue")
|
||||
},
|
||||
{
|
||||
path: "/p/articles/:alias",
|
||||
name: "posts.details.articles",
|
||||
|
@ -4,12 +4,24 @@
|
||||
<v-card :loading="loading">
|
||||
<article>
|
||||
<v-card-text>
|
||||
<h1 class="text-lg font-medium">{{ post?.title }}</h1>
|
||||
<p class="text-sm">{{ post?.description }}</p>
|
||||
<div class="px-3">
|
||||
<h1 class="text-lg font-medium">{{ post?.title }}</h1>
|
||||
<p class="text-sm">{{ post?.description }}</p>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-5 mx-[-16px] border-opacity-50" />
|
||||
|
||||
<div class="px-3 text-xs opacity-80 flex gap-1">
|
||||
<span>Written by {{ post?.author?.nick }}</span>
|
||||
<span>·</span>
|
||||
<span>Published at {{ new Date(post?.created_at).toLocaleString() }}</span>
|
||||
</div>
|
||||
|
||||
<v-divider class="mt-5 mx-[-16px] border-opacity-50" />
|
||||
|
||||
<article-content :item="post" content-only />
|
||||
<div class="px-3">
|
||||
<article-content :item="post" content-only />
|
||||
</div>
|
||||
|
||||
<v-divider class="my-5 mx-[-16px] border-opacity-50" />
|
||||
|
||||
|
108
pkg/views/src/views/posts/moments.vue
Normal file
108
pkg/views/src/views/posts/moments.vue
Normal file
@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<v-container class="flex max-md:flex-col gap-3 overflow-auto max-h-[calc(100vh-64px)] no-scrollbar">
|
||||
<div class="content flex-grow-1">
|
||||
<v-card :loading="loading">
|
||||
<article>
|
||||
<v-card-text>
|
||||
<div class="flex gap-2">
|
||||
<v-avatar
|
||||
color="grey-lighten-2"
|
||||
icon="mdi-account-circle"
|
||||
class="rounded-card"
|
||||
:src="post?.author.avatar"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<p class="font-bold">{{ post?.author.nick }}</p>
|
||||
<p class="opacity-80">
|
||||
{{ post?.author.description ? post?.author.description : "No description yet." }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-divider class="mb-5 mt-3.5 mx-[-16px] border-opacity-50" />
|
||||
|
||||
<div class="px-3">
|
||||
<moment-content :item="post" content-only />
|
||||
</div>
|
||||
|
||||
<div class="mt-3 px-2">
|
||||
<post-attachment v-if="post?.attachments" :attachments="post?.attachments" />
|
||||
</div>
|
||||
|
||||
<v-divider class="my-5 mx-[-16px] border-opacity-50" />
|
||||
|
||||
<div class="px-3">
|
||||
<post-reaction
|
||||
model="moments"
|
||||
:item="post"
|
||||
:reactions="post?.reaction_list ?? {}"
|
||||
@update="updateReactions"
|
||||
/>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</article>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<div class="aside sticky top-0 w-full h-fit w-full md:max-w-[380px] md:min-w-[360px]">
|
||||
<v-card title="Comments">
|
||||
<div class="px-[1rem] pb-[0.825rem] mt-[-12px]">
|
||||
<comment-list
|
||||
model="moment"
|
||||
dataset="moments"
|
||||
:item="post"
|
||||
:alias="route.params.alias"
|
||||
v-model:comments="comments"
|
||||
/>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { request } from "@/scripts/request"
|
||||
import { useRoute } from "vue-router"
|
||||
import MomentContent from "@/components/posts/MomentContent.vue"
|
||||
import PostReaction from "@/components/posts/PostReaction.vue"
|
||||
import CommentList from "@/components/comments/CommentList.vue"
|
||||
import PostAttachment from "@/components/posts/PostAttachment.vue"
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const post = ref<any>(null)
|
||||
const comments = ref<any[]>([])
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
async function readPost() {
|
||||
loading.value = true
|
||||
const res = await request(`/api/p/moments/${route.params.alias}`)
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
error.value = null
|
||||
post.value = await res.json()
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
readPost()
|
||||
|
||||
function updateReactions(symbol: string, num: number) {
|
||||
if (post.value.reaction_list.hasOwnProperty(symbol)) {
|
||||
post.value.reaction_list[symbol] += num
|
||||
} else {
|
||||
post.value.reaction_list[symbol] = num
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rounded-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user