upd: add buttons to replies

This commit is contained in:
Mar0xy 2023-10-01 00:51:57 +02:00
parent 7b179d3a92
commit 2ea7e799fe
No known key found for this signature in database
GPG key ID: 56569BBE47D2C828
3 changed files with 206 additions and 3 deletions

View file

@ -110,7 +110,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</button> </button>
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()"> <button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()">
<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i> <i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i>
<i v-else class="ph-plus ph-bold ph-lg"></i> <i v-else class="ph-smiley ph-bold ph-lg"></i>
</button> </button>
<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)"> <button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)">
<i class="ph-minus ph-bold ph-lg"></i> <i class="ph-minus ph-bold ph-lg"></i>

View file

@ -118,7 +118,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</button> </button>
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @mousedown="react()"> <button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @mousedown="react()">
<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i> <i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i>
<i v-else class="ph-plus ph-bold ph-lg"></i> <i v-else class="ph-smiley ph-bold ph-lg"></i>
</button> </button>
<button v-if="appearNote.myReaction != null" ref="reactButton" class="_button" :class="[$style.noteFooterButton, $style.reacted]" @click="undoReact(appearNote)"> <button v-if="appearNote.myReaction != null" ref="reactButton" class="_button" :class="[$style.noteFooterButton, $style.reacted]" @click="undoReact(appearNote)">
<i class="ph-minus ph-bold ph-lg"></i> <i class="ph-minus ph-bold ph-lg"></i>

View file

@ -19,6 +19,36 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSubNoteContent :class="$style.text" :note="note"/> <MkSubNoteContent :class="$style.text" :note="note"/>
</div> </div>
</div> </div>
<footer>
<MkReactionsViewer ref="reactionsViewer" :note="note"/>
<button class="_button" :class="$style.noteFooterButton" @click="reply()">
<i class="ph-arrow-u-up-left ph-bold pg-lg"></i>
<p v-if="note.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ note.repliesCount }}</p>
</button>
<button
v-if="canRenote"
ref="renoteButton"
class="_button"
:class="$style.noteFooterButton"
@mousedown="renote()"
>
<i class="ph-repeat ph-bold ph-lg"></i>
<p v-if="note.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ note.renoteCount }}</p>
</button>
<button v-else class="_button" :class="$style.noteFooterButton" disabled>
<i class="ph-prohibit ph-bold ph-lg"></i>
</button>
<button v-if="note.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @mousedown="react()">
<i v-if="note.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i>
<i v-else class="ph-smiley ph-bold ph-lg"></i>
</button>
<button v-if="note.myReaction != null" ref="reactButton" class="_button" :class="[$style.noteFooterButton, $style.reacted]" @click="undoReact(note)">
<i class="ph-minus ph-bold ph-lg"></i>
</button>
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown="menu()">
<i class="ph-dots-three ph-bold ph-lg"></i>
</button>
</footer>
</div> </div>
</div> </div>
<template v-if="depth < 5"> <template v-if="depth < 5">
@ -40,9 +70,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { computed, ref, shallowRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkNoteHeader from '@/components/MkNoteHeader.vue'; import MkNoteHeader from '@/components/MkNoteHeader.vue';
import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
import MkSubNoteContent from '@/components/MkSubNoteContent.vue'; import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
import MkCwButton from '@/components/MkCwButton.vue'; import MkCwButton from '@/components/MkCwButton.vue';
import { notePage } from '@/filters/note.js'; import { notePage } from '@/filters/note.js';
@ -52,6 +83,14 @@ import { $i } from '@/account.js';
import { userPage } from "@/filters/user"; import { userPage } from "@/filters/user";
import { checkWordMute } from "@/scripts/check-word-mute"; import { checkWordMute } from "@/scripts/check-word-mute";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { pleaseLogin } from '@/scripts/please-login.js';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { reactionPicker } from '@/scripts/reaction-picker.js';
import { claimAchievement } from '@/scripts/achievements.js';
import type { MenuItem } from '@/types/menu.js';
import { getNoteMenu } from '@/scripts/get-note-menu.js';
const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id);
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
@ -63,11 +102,150 @@ const props = withDefaults(defineProps<{
depth: 1, depth: 1,
}); });
function focus() {
el.value.focus();
}
const muted = ref(checkWordMute(props.note, $i, defaultStore.state.mutedWords)); const muted = ref(checkWordMute(props.note, $i, defaultStore.state.mutedWords));
const translation = ref(null);
const translating = ref(false);
const isDeleted = ref(false);
const reactButton = shallowRef<HTMLElement>();
const renoteButton = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>();
function reply(viaKeyboard = false): void {
pleaseLogin();
showMovedDialog();
os.post({
reply: props.note,
channel: props.note.channel,
animation: !viaKeyboard,
}, () => {
focus();
});
}
function react(viaKeyboard = false): void {
pleaseLogin();
showMovedDialog();
if (props.note.reactionAcceptance === 'likeOnly') {
os.api('notes/reactions/create', {
noteId: props.note.id,
reaction: '❤️',
});
const el = reactButton.value as HTMLElement | null | undefined;
if (el) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
os.popup(MkRippleEffect, { x, y }, {}, 'end');
}
} else {
blur();
reactionPicker.show(reactButton.value, reaction => {
os.api('notes/reactions/create', {
noteId: props.note.id,
reaction: reaction,
});
if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) {
claimAchievement('reactWithoutRead');
}
}, () => {
focus();
});
}
}
function undoReact(note): void {
const oldReaction = note.myReaction;
if (!oldReaction) return;
os.api('notes/reactions/delete', {
noteId: note.id,
});
}
let showContent = $ref(false); let showContent = $ref(false);
let replies: Misskey.entities.Note[] = $ref([]); let replies: Misskey.entities.Note[] = $ref([]);
function renote(viaKeyboard = false) {
pleaseLogin();
showMovedDialog();
let items = [] as MenuItem[];
if (props.note.channel) {
items = items.concat([{
text: i18n.ts.inChannelRenote,
icon: 'ph-repeat ph-bold ph-lg',
action: () => {
const el = renoteButton.value as HTMLElement | null | undefined;
if (el) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
os.popup(MkRippleEffect, { x, y }, {}, 'end');
}
os.api('notes/create', {
renoteId: props.note.id,
channelId: props.note.channelId,
}).then(() => {
os.toast(i18n.ts.renoted);
});
},
}, {
text: i18n.ts.inChannelQuote,
icon: 'ph-quotes ph-bold ph-lg',
action: () => {
os.post({
renote: props.note,
channel: props.note.channel,
});
},
}, null]);
}
items = items.concat([{
text: i18n.ts.renote,
icon: 'ph-repeat ph-bold ph-lg',
action: () => {
const el = renoteButton.value as HTMLElement | null | undefined;
if (el) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
os.popup(MkRippleEffect, { x, y }, {}, 'end');
}
os.api('notes/create', {
renoteId: props.note.id,
}).then(() => {
os.toast(i18n.ts.renoted);
});
},
}, {
text: i18n.ts.quote,
icon: 'ph-quotes ph-bold ph-lg',
action: () => {
os.post({
renote: props.note,
});
},
}]);
os.popupMenu(items, renoteButton.value, {
viaKeyboard,
});
}
function menu(viaKeyboard = false): void {
const { menu, cleanup } = getNoteMenu({ note: props.note, translating, translation, menuButton, isDeleted });
os.popupMenu(menu, menuButton.value, {
viaKeyboard,
}).then(focus).finally(cleanup);
}
if (props.detail) { if (props.detail) {
os.api('notes/children', { os.api('notes/children', {
noteId: props.note.id, noteId: props.note.id,
@ -122,6 +300,31 @@ if (props.detail) {
margin-bottom: 2px; margin-bottom: 2px;
} }
.noteFooterButton {
margin: 0;
padding: 8px;
padding-top: 10px;
opacity: 0.7;
&:not(:last-child) {
margin-right: 14px;
}
&:hover {
color: var(--fgHighlighted);
}
}
.noteFooterButtonCount {
display: inline;
margin: 0 0 0 8px;
opacity: 0.7;
&.reacted {
color: var(--accent);
}
}
.cw { .cw {
cursor: default; cursor: default;
display: block; display: block;