Merge pull request 'Add reactions tab to detailed notes view' (#10161) from Freeplay/calckey:notes into develop
Reviewed-on: https://codeberg.org/calckey/calckey/pulls/10161
This commit is contained in:
commit
7a75ba477d
|
@ -55,6 +55,13 @@
|
||||||
</template>
|
</template>
|
||||||
{{ i18n.ts._notification._types.quote }}
|
{{ i18n.ts._notification._types.quote }}
|
||||||
</option>
|
</option>
|
||||||
|
<option value="reactions">
|
||||||
|
<i class="ph-smiley ph-bold ph-lg"></i>
|
||||||
|
<template v-if="reactionsCount > 0">
|
||||||
|
<span class="count">{{ reactionsCount }}</span>
|
||||||
|
</template>
|
||||||
|
{{ i18n.ts.reaction }}
|
||||||
|
</option>
|
||||||
<option value="clips">
|
<option value="clips">
|
||||||
<i class="ph-paperclip ph-bold ph-lg"></i>
|
<i class="ph-paperclip ph-bold ph-lg"></i>
|
||||||
<template v-if="clips?.length > 0">
|
<template v-if="clips?.length > 0">
|
||||||
|
@ -128,6 +135,11 @@
|
||||||
</MkA>
|
</MkA>
|
||||||
</div>
|
</div>
|
||||||
<MkLoading v-else-if="tab === 'clips' && clips.length > 0" />
|
<MkLoading v-else-if="tab === 'clips' && clips.length > 0" />
|
||||||
|
|
||||||
|
<MkReactedUsers
|
||||||
|
v-if="tab === 'reactions' && reactionsCount > 0"
|
||||||
|
:note-id="appearNote.id"
|
||||||
|
></MkReactedUsers>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="_panel muted" @click="muted.muted = false">
|
<div v-else class="_panel muted" @click="muted.muted = false">
|
||||||
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
|
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
|
||||||
|
@ -165,6 +177,7 @@ import XStarButton from "@/components/MkStarButton.vue";
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
||||||
|
import MkReactedUsers from "@/components/MkReactedUsers.vue";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
import { getWordSoftMute } from "@/scripts/check-word-mute";
|
import { getWordSoftMute } from "@/scripts/check-word-mute";
|
||||||
import { userPage } from "@/filters/user";
|
import { userPage } from "@/filters/user";
|
||||||
|
@ -240,6 +253,8 @@ let clips = $ref();
|
||||||
let renotes = $ref();
|
let renotes = $ref();
|
||||||
let isScrolling;
|
let isScrolling;
|
||||||
|
|
||||||
|
const reactionsCount = Object.values(props.note.reactions).reduce((x,y) => x + y, 0);
|
||||||
|
|
||||||
const keymap = {
|
const keymap = {
|
||||||
r: () => reply(true),
|
r: () => reply(true),
|
||||||
"e|a|plus": () => react(true),
|
"e|a|plus": () => react(true),
|
||||||
|
@ -509,14 +524,22 @@ onUnmounted(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> :deep(.chips) {
|
> :deep(.chips), {
|
||||||
padding: 6px 32px 12px;
|
padding-block: 6px 12px;
|
||||||
|
padding-left: 32px;
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
> :deep(.user-card-mini) {
|
> :deep(.user-card-mini),
|
||||||
|
> :deep(.reacted-users > *) {
|
||||||
padding-inline: 32px;
|
padding-inline: 32px;
|
||||||
border-top: 1px solid var(--divider);
|
border-top: 1px solid var(--divider);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
> :deep(.reacted-users > div) {
|
||||||
|
padding-block: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
> .reply {
|
> .reply {
|
||||||
border-top: solid 0.5px var(--divider);
|
border-top: solid 0.5px var(--divider);
|
||||||
|
@ -612,10 +635,13 @@ onUnmounted(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
> .clips,
|
> .clips,
|
||||||
> .chips,
|
> :deep(.user-card-mini),
|
||||||
> :deep(.user-card-mini) {
|
> :deep(.reacted-users > *) {
|
||||||
padding-inline: 16px !important;
|
padding-inline: 16px !important;
|
||||||
}
|
}
|
||||||
|
> .chips {
|
||||||
|
padding-left: 16px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.max-width_300px {
|
&.max-width_300px {
|
||||||
|
|
|
@ -137,7 +137,7 @@
|
||||||
class="reply"
|
class="reply"
|
||||||
:class="{ single: replies.length == 1 }"
|
:class="{ single: replies.length == 1 }"
|
||||||
:conversation="conversation"
|
:conversation="conversation"
|
||||||
:depth="replies.lenght == 1 ? depth : depth + 1"
|
:depth="replies.length == 1 ? depth : depth + 1"
|
||||||
:replyLevel="replyLevel + 1"
|
:replyLevel="replyLevel + 1"
|
||||||
:parentId="appearNote.replyId"
|
:parentId="appearNote.replyId"
|
||||||
:detailedView="detailedView"
|
:detailedView="detailedView"
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="note" class="_gaps reacted-users">
|
||||||
|
<div :class="$style.tabs">
|
||||||
|
<button
|
||||||
|
v-for="reaction in reactions"
|
||||||
|
:key="reaction"
|
||||||
|
:class="[
|
||||||
|
$style.tab,
|
||||||
|
{ [$style.tabActive]: tab === reaction },
|
||||||
|
]"
|
||||||
|
class="_button"
|
||||||
|
@click="tab = reaction"
|
||||||
|
>
|
||||||
|
<MkReactionIcon
|
||||||
|
ref="reactionRef"
|
||||||
|
:reaction="
|
||||||
|
reaction
|
||||||
|
? reaction.replace(
|
||||||
|
/^:(\w+):$/,
|
||||||
|
':$1@.:'
|
||||||
|
)
|
||||||
|
: reaction
|
||||||
|
"
|
||||||
|
:custom-emojis="note.emojis"
|
||||||
|
/>
|
||||||
|
<span style="margin-left: 4px">{{
|
||||||
|
note.reactions[reaction]
|
||||||
|
}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<MkUserCardMini
|
||||||
|
v-for="user in users"
|
||||||
|
:key="user.id"
|
||||||
|
:user="user"
|
||||||
|
:with-chart="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<MkLoading />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, watch } from "vue";
|
||||||
|
import * as misskey from "calckey-js";
|
||||||
|
import MkReactionIcon from "@/components/MkReactionIcon.vue";
|
||||||
|
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
||||||
|
import { i18n } from "@/i18n";
|
||||||
|
import * as os from "@/os";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
noteId: misskey.entities.Note["id"];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
let note = $ref<misskey.entities.Note>();
|
||||||
|
let tab = $ref<string>();
|
||||||
|
let reactions = $ref<string[]>();
|
||||||
|
let users = $ref();
|
||||||
|
|
||||||
|
watch($$(tab), async () => {
|
||||||
|
const res = await os.api("notes/reactions", {
|
||||||
|
noteId: props.noteId,
|
||||||
|
type: tab,
|
||||||
|
limit: 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
users = res.map((x) => x.user);
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
os.api("notes/show", {
|
||||||
|
noteId: props.noteId,
|
||||||
|
}).then((res) => {
|
||||||
|
reactions = Object.keys(res.reactions);
|
||||||
|
tab = reactions[0];
|
||||||
|
note = res;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
padding: 4px 6px;
|
||||||
|
border: solid 1px var(--divider);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabActive {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,128 +0,0 @@
|
||||||
<template>
|
|
||||||
<MkModalWindow
|
|
||||||
ref="dialog"
|
|
||||||
:width="400"
|
|
||||||
:height="450"
|
|
||||||
@close="dialog.close()"
|
|
||||||
@closed="emit('closed')"
|
|
||||||
>
|
|
||||||
<template #header>{{ i18n.ts.reaction }}</template>
|
|
||||||
|
|
||||||
<MkSpacer :margin-min="20" :margin-max="28">
|
|
||||||
<div v-if="note" class="_gaps">
|
|
||||||
<div v-if="reactions.length === 0" class="_fullinfo">
|
|
||||||
<img
|
|
||||||
src="/static-assets/badges/info.png"
|
|
||||||
class="_ghost"
|
|
||||||
alt="Info"
|
|
||||||
/>
|
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<div :class="$style.tabs">
|
|
||||||
<button
|
|
||||||
v-for="reaction in reactions"
|
|
||||||
:key="reaction"
|
|
||||||
:class="[
|
|
||||||
$style.tab,
|
|
||||||
{ [$style.tabActive]: tab === reaction },
|
|
||||||
]"
|
|
||||||
class="_button"
|
|
||||||
@click="tab = reaction"
|
|
||||||
>
|
|
||||||
<MkReactionIcon
|
|
||||||
ref="reactionRef"
|
|
||||||
:reaction="
|
|
||||||
reaction
|
|
||||||
? reaction.replace(
|
|
||||||
/^:(\w+):$/,
|
|
||||||
':$1@.:'
|
|
||||||
)
|
|
||||||
: reaction
|
|
||||||
"
|
|
||||||
:custom-emojis="note.emojis"
|
|
||||||
/>
|
|
||||||
<span style="margin-left: 4px">{{
|
|
||||||
note.reactions[reaction]
|
|
||||||
}}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<MkA
|
|
||||||
v-for="user in users"
|
|
||||||
:key="user.id"
|
|
||||||
:to="userPage(user)"
|
|
||||||
>
|
|
||||||
<MkUserCardMini :user="user" :with-chart="false" />
|
|
||||||
</MkA>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<MkLoading />
|
|
||||||
</div>
|
|
||||||
</MkSpacer>
|
|
||||||
</MkModalWindow>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, watch } from "vue";
|
|
||||||
import * as misskey from "calckey-js";
|
|
||||||
import MkModalWindow from "@/components/MkModalWindow.vue";
|
|
||||||
import MkReactionIcon from "@/components/MkReactionIcon.vue";
|
|
||||||
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
|
||||||
import { userPage } from "@/filters/user";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import * as os from "@/os";
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: "closed"): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
noteId: misskey.entities.Note["id"];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
|
|
||||||
|
|
||||||
let note = $ref<misskey.entities.Note>();
|
|
||||||
let tab = $ref<string>();
|
|
||||||
let reactions = $ref<string[]>();
|
|
||||||
let users = $ref();
|
|
||||||
|
|
||||||
watch($$(tab), async () => {
|
|
||||||
const res = await os.api("notes/reactions", {
|
|
||||||
noteId: props.noteId,
|
|
||||||
type: tab,
|
|
||||||
limit: 30,
|
|
||||||
});
|
|
||||||
|
|
||||||
users = res.map((x) => x.user);
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
os.api("notes/show", {
|
|
||||||
noteId: props.noteId,
|
|
||||||
}).then((res) => {
|
|
||||||
reactions = Object.keys(res.reactions);
|
|
||||||
tab = reactions[0];
|
|
||||||
note = res;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
|
||||||
.tabs {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
padding: 4px 6px;
|
|
||||||
border: solid 1px var(--divider);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabActive {
|
|
||||||
border-color: var(--accent);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -89,6 +89,12 @@ export default defineComponent({
|
||||||
padding: 12px 32px;
|
padding: 12px 32px;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
mask: linear-gradient(to right, black calc(100% - 90px), transparent);
|
||||||
|
-webkit-mask: linear-gradient(to right, black calc(100% - 90px), transparent);
|
||||||
|
padding-right: 90px !important;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
> button {
|
> button {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
@ -102,6 +108,9 @@ export default defineComponent({
|
||||||
> i {
|
> i {
|
||||||
margin-top: -0.1em;
|
margin-top: -0.1em;
|
||||||
}
|
}
|
||||||
|
> .count {
|
||||||
|
margin-right: -.2em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -230,19 +230,6 @@ export function getNoteMenu(props: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showReactions(): void {
|
|
||||||
os.popup(
|
|
||||||
defineAsyncComponent(
|
|
||||||
() => import("@/components/MkReactedUsersDialog.vue"),
|
|
||||||
),
|
|
||||||
{
|
|
||||||
noteId: appearNote.id,
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
"closed",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function translate(): Promise<void> {
|
async function translate(): Promise<void> {
|
||||||
if (props.translation.value != null) return;
|
if (props.translation.value != null) return;
|
||||||
props.translating.value = true;
|
props.translating.value = true;
|
||||||
|
@ -282,11 +269,6 @@ export function getNoteMenu(props: {
|
||||||
action: edit,
|
action: edit,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
{
|
|
||||||
icon: "ph-smiley ph-bold ph-lg",
|
|
||||||
text: i18n.ts.reaction,
|
|
||||||
action: showReactions,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: "ph-clipboard-text ph-bold ph-lg",
|
icon: "ph-clipboard-text ph-bold ph-lg",
|
||||||
text: i18n.ts.copyContent,
|
text: i18n.ts.copyContent,
|
||||||
|
|
Loading…
Reference in New Issue