♻️ Interactive v2 #1

Merged
LittleSheep merged 30 commits from refactor/v2 into master 2024-03-16 08:22:25 +00:00
9 changed files with 172 additions and 21 deletions
Showing only changes of commit 7f89b36efd - Show all commits

View File

@ -75,9 +75,11 @@ func (v *PostTypeContext) SortCreatedAt(order string) *PostTypeContext {
func (v *PostTypeContext) GetViaAlias(alias string) (models.Feed, error) { func (v *PostTypeContext) GetViaAlias(alias string) (models.Feed, error) {
var item models.Feed var item models.Feed
table := viper.GetString("database.prefix") + v.TableName table := viper.GetString("database.prefix") + v.TableName
userTable := viper.GetString("database.prefix") + "accounts"
if err := v.Tx. if err := v.Tx.
Table(table). Table(table).
Select("*, ? as model_type", v.ColumnName). Select("*, ? as model_type", v.ColumnName).
Joins(fmt.Sprintf("INNER JOIN %s AS author ON author_id = author.id", userTable)).
Where("alias = ?", alias). Where("alias = ?", alias).
First(&item).Error; err != nil { First(&item).Error; err != nil {
return item, err 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) { func (v *PostTypeContext) Get(id uint, noComments ...bool) (models.Feed, error) {
var item models.Feed var item models.Feed
table := viper.GetString("database.prefix") + v.TableName table := viper.GetString("database.prefix") + v.TableName
userTable := viper.GetString("database.prefix") + "accounts"
if err := v.Tx. if err := v.Tx.
Table(table). Table(table).
Select("*, ? as model_type", v.ColumnName). 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 { Where("id = ?", id).First(&item).Error; err != nil {
return item, err return item, err
} }

View File

@ -5,14 +5,16 @@
<div class="text-sm">{{ props.item?.description }}</div> <div class="text-sm">{{ props.item?.description }}</div>
</section> </section>
<div v-if="props.brief"> <div v-if="props.brief" class="mt-2">
<router-link <v-btn
:to="{ name: 'posts.details.articles', params: { alias: props.item?.alias ?? 'not-found' } }"
append-icon="mdi-arrow-right" 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... Read more
</router-link> </v-btn>
</div> </div>
<div v-else> <div v-else>
@ -22,12 +24,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import dompurify from "dompurify"; import dompurify from "dompurify"
import { parse } from "marked"; 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 { function parseContent(src: string): string {
return dompurify().sanitize(parse(src) as string); return dompurify().sanitize(parse(src) as string)
} }
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<article class="prose prose-comment" v-html="parseContent(props.item.content)" /> <article class="prose prose-comment" v-html="parseContent(props.item?.content ?? '')" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -1,15 +1,29 @@
<template> <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> </template>
<script setup lang="ts"> <script setup lang="ts">
import dompurify from "dompurify"; import dompurify from "dompurify"
import { parse } from "marked"; import { parse } from "marked"
const props = defineProps<{ item: any }>(); const props = defineProps<{ item: any; brief?: boolean }>()
function parseContent(src: string): string { function parseContent(src: string): string {
return dompurify().sanitize(parse(src) as string); return dompurify().sanitize(parse(src) as string)
} }
</script> </script>

View File

@ -33,7 +33,7 @@
@update="updateReactions" @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> <span>Posted at {{ new Date(props.item?.created_at).toLocaleString() }}</span>
</div> </div>

View File

@ -70,6 +70,7 @@ import { request } from "@/scripts/request"
import { useEditor } from "@/stores/editor" import { useEditor } from "@/stores/editor"
import { getAtk } from "@/stores/userinfo" import { getAtk } from "@/stores/userinfo"
import { reactive, ref, watch } from "vue" import { reactive, ref, watch } from "vue"
import { useRouter } from "vue-router"
import PlannedPublish from "@/components/publish/parts/PlannedPublish.vue" import PlannedPublish from "@/components/publish/parts/PlannedPublish.vue"
import Media from "@/components/publish/parts/Media.vue" import Media from "@/components/publish/parts/Media.vue"
@ -91,6 +92,8 @@ const success = ref(false)
const loading = ref(false) const loading = ref(false)
const uploading = ref(false) const uploading = ref(false)
const router = useRouter()
async function postMoment(evt: SubmitEvent) { async function postMoment(evt: SubmitEvent) {
const form = evt.target as HTMLFormElement const form = evt.target as HTMLFormElement
const payload = data.value const payload = data.value
@ -108,8 +111,10 @@ async function postMoment(evt: SubmitEvent) {
}) })
if (res.status === 200) { if (res.status === 200) {
form.reset() form.reset()
const data = await res.json()
success.value = true success.value = true
editor.show.moment = false editor.show.moment = false
router.push({ name: "posts.details.moments", params: { alias: data.alias } })
} else { } else {
error.value = await res.text() error.value = await res.text()
} }

View File

@ -14,6 +14,12 @@ const router = createRouter({
component: () => import("@/views/explore.vue") component: () => import("@/views/explore.vue")
}, },
{
path: "/p/moments/:alias",
name: "posts.details.moments",
component: () => import("@/views/posts/moments.vue")
},
{ {
path: "/p/articles/:alias", path: "/p/articles/:alias",
name: "posts.details.articles", name: "posts.details.articles",

View File

@ -4,12 +4,24 @@
<v-card :loading="loading"> <v-card :loading="loading">
<article> <article>
<v-card-text> <v-card-text>
<h1 class="text-lg font-medium">{{ post?.title }}</h1> <div class="px-3">
<p class="text-sm">{{ post?.description }}</p> <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" /> <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" /> <v-divider class="my-5 mx-[-16px] border-opacity-50" />

View 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>