upd: add buttons to replies
This commit is contained in:
parent
7b179d3a92
commit
2ea7e799fe
3 changed files with 206 additions and 3 deletions
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue