♻️ Interactive v2 #1
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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" />
|
||||||
|
|
||||||
|
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