✨ Reactions
This commit is contained in:
parent
932bdf1e5a
commit
1e366af3b8
@ -14,9 +14,19 @@
|
||||
<div class="flex-grow-1">
|
||||
<div class="font-bold">{{ props.item?.author.nick }}</div>
|
||||
|
||||
<div v-if="props.item?.modal_type === 'article'" class="text-xs text-grey-darken-4 mb-2">Published an article</div>
|
||||
<div v-if="props.item?.model_type === 'article'" class="text-xs text-grey-darken-4 mb-2">Published an
|
||||
article
|
||||
</div>
|
||||
|
||||
<component :is="renderer[props.item?.model_type]" v-bind="props" />
|
||||
|
||||
<post-reaction
|
||||
size="small"
|
||||
:item="props.item"
|
||||
:model="props.item?.model_type ? props.item?.model_type + 's' : 'articles'"
|
||||
:reactions="props.item?.reaction_list ?? {}"
|
||||
@update="updateReactions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -27,13 +37,25 @@
|
||||
import type { Component } from "vue";
|
||||
import ArticleContent from "@/components/posts/ArticleContent.vue";
|
||||
import MomentContent from "@/components/posts/MomentContent.vue";
|
||||
import PostReaction from "@/components/posts/PostReaction.vue";
|
||||
|
||||
const props = defineProps<{ item: any, brief?: boolean, loading?: boolean }>();
|
||||
const emits = defineEmits(["update:item"]);
|
||||
|
||||
const renderer: { [id: string]: Component } = {
|
||||
article: ArticleContent,
|
||||
moment: MomentContent
|
||||
};
|
||||
|
||||
function updateReactions(symbol: string, num: number) {
|
||||
const item = JSON.parse(JSON.stringify(props.item));
|
||||
if (item.reaction_list.hasOwnProperty(symbol)) {
|
||||
item.reaction_list[symbol] += num;
|
||||
} else {
|
||||
item.reaction_list[symbol] = num;
|
||||
}
|
||||
emits("update:item", item);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -5,9 +5,9 @@
|
||||
</div>
|
||||
|
||||
<v-infinite-scroll :items="props.posts" :onLoad="props.loader">
|
||||
<template v-for="item in props.posts" :key="item">
|
||||
<template v-for="(item, idx) in props.posts" :key="item">
|
||||
<div class="mb-3 px-1">
|
||||
<post-item :item="item" brief />
|
||||
<post-item brief :item="item" @update:item="val => updateItem(idx, val)" />
|
||||
</div>
|
||||
</template>
|
||||
</v-infinite-scroll>
|
||||
@ -18,4 +18,11 @@
|
||||
import PostItem from "@/components/posts/PostItem.vue";
|
||||
|
||||
const props = defineProps<{ loading: boolean, posts: any[], loader: (opts: any) => Promise<any> }>();
|
||||
const emits = defineEmits(["update:posts"]);
|
||||
|
||||
function updateItem(idx: number, data: any) {
|
||||
const posts = JSON.parse(JSON.stringify(props.posts));
|
||||
posts[idx] = data;
|
||||
emits("update:posts", posts);
|
||||
}
|
||||
</script>
|
||||
|
81
pkg/views/src/components/posts/PostReaction.vue
Normal file
81
pkg/views/src/components/posts/PostReaction.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="flex gap-[8px] my-[8px]">
|
||||
<v-chip
|
||||
v-for="[k, v] in Object.entries(props.reactions)"
|
||||
:color="pickColor()"
|
||||
:size="props.size"
|
||||
@click="reactPost(k, emojis[k].attitude)"
|
||||
>
|
||||
<div class="ms-2">{{ v }}</div>
|
||||
<template #prepend>{{ emojis[k].icon }}</template>
|
||||
</v-chip>
|
||||
|
||||
<v-menu v-if="!props.readonly" location="bottom center">
|
||||
<template v-slot:activator="{ props: binding }">
|
||||
<v-chip v-bind="binding" :size="props.size" prepend-icon="mdi-emoticon-plus">
|
||||
React
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<v-list density="compact" lines="one">
|
||||
<v-list-item v-for="[k, v] in Object.entries(emojis)" @click="reactPost(k, v.attitude)">
|
||||
<v-list-item-title class="font-mono">:{{ k }}:</v-list-item-title>
|
||||
<template #prepend>
|
||||
<div class="me-3">{{ v.icon }}</div>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<v-snackbar v-model="status.added" :timeout="3000">Your react has been added into post.</v-snackbar>
|
||||
<v-snackbar v-model="status.removed" :timeout="3000">Your react has been removed from post.</v-snackbar>
|
||||
|
||||
<v-snackbar v-model="error" :timeout="5000">Something went wrong... {{ error }}</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { request } from "@/scripts/request";
|
||||
import { getAtk } from "@/stores/userinfo";
|
||||
import { reactive, ref } from "vue";
|
||||
|
||||
const emits = defineEmits(["update"]);
|
||||
const props = defineProps<{
|
||||
size?: string,
|
||||
readonly?: boolean,
|
||||
model: string,
|
||||
item: any,
|
||||
reactions: { [id: string]: number }
|
||||
}>();
|
||||
|
||||
const emojis: { [id: string]: { icon: string, attitude: number } } = {
|
||||
thumb_up: { icon: "👍", attitude: 1 },
|
||||
clap: { icon: "👏", attitude: 1 }
|
||||
};
|
||||
|
||||
function pickColor(): string {
|
||||
const colors = ["blue", "green", "purple"];
|
||||
const randomIndex = Math.floor(Math.random() * colors.length);
|
||||
return colors[randomIndex];
|
||||
}
|
||||
|
||||
const status = reactive({ added: false, removed: false });
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
async function reactPost(symbol: string, attitude: number) {
|
||||
const res = await request(`/api/${props.model}/${props.item?.id}/react`, {
|
||||
method: "POST",
|
||||
headers: { "Authorization": `Bearer ${getAtk()}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ symbol, attitude })
|
||||
});
|
||||
if (res.status === 201) {
|
||||
status.added = true;
|
||||
emits("update", symbol, 1);
|
||||
} else if (res.status === 204) {
|
||||
status.removed = true;
|
||||
emits("update", symbol, -1);
|
||||
} else {
|
||||
error.value = await res.text();
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<v-container class="flex max-md:flex-col gap-3 overflow-auto max-h-[calc(100vh-64px)] no-scrollbar">
|
||||
<div class="timeline flex-grow-1 mt-[-16px]">
|
||||
<post-list :loading="loading" :posts="posts" :loader="readMore" />
|
||||
<post-list v-model:posts="posts" :loading="loading" :loader="readMore" />
|
||||
</div>
|
||||
|
||||
<div class="aside sticky top-0 w-full h-fit md:min-w-[280px] md:max-w-[320px] max-md:order-first">
|
||||
|
@ -23,8 +23,14 @@
|
||||
</v-card>
|
||||
|
||||
<v-card title="Reactions" class="mt-3">
|
||||
<v-list density="compact">
|
||||
</v-list>
|
||||
<div class="px-[1rem] pb-[0.825rem] mt-[-12px]">
|
||||
<post-reaction
|
||||
:item="post"
|
||||
:model="route.params.postType"
|
||||
:reactions="post?.reaction_list ?? {}"
|
||||
@update="updateReactions"
|
||||
/>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-container>
|
||||
@ -35,6 +41,7 @@ import { ref } from "vue";
|
||||
import { request } from "@/scripts/request";
|
||||
import { useRoute } from "vue-router";
|
||||
import ArticleContent from "@/components/posts/ArticleContent.vue";
|
||||
import PostReaction from "@/components/posts/PostReaction.vue";
|
||||
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
@ -55,4 +62,12 @@ async function readPost() {
|
||||
}
|
||||
|
||||
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>
|
Loading…
Reference in New Issue
Block a user