diff options
| author | dakkar <dakkar@thenautilus.net> | 2024-03-07 16:09:36 +0000 |
|---|---|---|
| committer | dakkar <dakkar@thenautilus.net> | 2024-03-07 16:09:36 +0000 |
| commit | dfff4d20731c9d356c28db6238a9e438618ff2e7 (patch) | |
| tree | 732119af4c809639356e71cd73f79eb41497d7dc /packages/frontend/src/components | |
| parent | merge: check prohibited words when creating notes (!461) (diff) | |
| parent | fix(frontend): 周年の実績が閏年を考慮するように (#13525) (diff) | |
| download | sharkey-dfff4d20731c9d356c28db6238a9e438618ff2e7.tar.gz sharkey-dfff4d20731c9d356c28db6238a9e438618ff2e7.tar.bz2 sharkey-dfff4d20731c9d356c28db6238a9e438618ff2e7.zip | |
Merge remote-tracking branch 'misskey/develop' into future
Diffstat (limited to 'packages/frontend/src/components')
10 files changed, 106 insertions, 40 deletions
diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue index 6351f5cfbe..23539427e3 100644 --- a/packages/frontend/src/components/MkMediaAudio.vue +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -69,7 +69,7 @@ import * as os from '@/os.js'; import bytes from '@/filters/bytes.js'; import { hms } from '@/filters/hms.js'; import MkMediaRange from '@/components/MkMediaRange.vue'; -import { iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/account.js'; const props = defineProps<{ audio: Misskey.entities.DriveFile; @@ -99,8 +99,6 @@ function showMenu(ev: MouseEvent) { if (iAmModerator) { menu.push({ - type: 'divider', - }, { text: props.audio.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, icon: props.audio.isSensitive ? 'ph-eye ph-bold ph-lg' : 'ph-eye-slash ph-bold ph-lg', danger: true, @@ -108,6 +106,17 @@ function showMenu(ev: MouseEvent) { }); } + if ($i?.id === props.audio.userId) { + menu.push({ + type: 'divider', + }, { + type: 'link' as const, + text: i18n.ts._fileViewer.title, + icon: 'ti ti-info-circle', + to: `/my/drive/file/${props.audio.id}`, + }); + } + menuShowing.value = true; os.popupMenu(menu, ev.currentTarget ?? ev.target, { align: 'right', diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 3f9cff8b71..af9d7d824c 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -60,7 +60,7 @@ import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; -import { iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/account.js'; const props = withDefaults(defineProps<{ image: Misskey.entities.DriveFile; @@ -115,6 +115,13 @@ function showMenu(ev: MouseEvent) { action: () => { os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true }); }, + }] : []), ...($i?.id === props.image.userId ? [{ + type: 'divider' as const, + }, { + type: 'link' as const, + text: i18n.ts._fileViewer.title, + icon: 'ti ti-info-circle', + to: `/my/drive/file/${props.image.id}`, }] : [])], ev.currentTarget ?? ev.target); } diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 7c14ade130..c64d87e8d6 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -97,7 +97,7 @@ import * as os from '@/os.js'; import { isFullscreenNotSupported } from '@/scripts/device-kind.js'; import hasAudio from '@/scripts/media-has-audio.js'; import MkMediaRange from '@/components/MkMediaRange.vue'; -import { iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/account.js'; const props = defineProps<{ video: Misskey.entities.DriveFile; @@ -125,8 +125,6 @@ function showMenu(ev: MouseEvent) { if (iAmModerator) { menu.push({ - type: 'divider', - }, { text: props.video.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, icon: props.video.isSensitive ? 'ph-eye ph-bold ph-lg' : 'ph-eye-slash ph-bold ph-lg', danger: true, @@ -134,6 +132,17 @@ function showMenu(ev: MouseEvent) { }); } + if ($i?.id === props.video.userId) { + menu.push({ + type: 'divider', + }, { + type: 'link' as const, + text: i18n.ts._fileViewer.title, + icon: 'ti ti-info-circle', + to: `/my/drive/file/${props.video.id}`, + }); + } + menuShowing.value = true; os.popupMenu(menu, ev.currentTarget ?? ev.target, { align: 'right', diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 9a667c3118..430f0269b8 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -97,7 +97,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ph-television ph-bold ph-lg"></i> {{ appearNote.channel.name }}</MkA> </div> - <MkReactionsViewer :note="appearNote" :maxNumber="16" @click.stop @mockUpdateMyReaction="emitUpdReaction"> + <MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" :note="appearNote" :maxNumber="16" @click.stop @mockUpdateMyReaction="emitUpdReaction"> <template #more> <div :class="$style.reactionOmitted">{{ i18n.ts.more }}</div> </template> @@ -105,7 +105,7 @@ SPDX-License-Identifier: AGPL-3.0-only <footer :class="$style.footer"> <button :class="$style.footerButton" class="_button" @click.stop @click="reply()"> <i class="ph-arrow-u-up-left ph-bold ph-lg"></i> - <p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ appearNote.repliesCount }}</p> + <p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.repliesCount) }}</p> </button> <button v-if="canRenote" @@ -117,7 +117,7 @@ SPDX-License-Identifier: AGPL-3.0-only @mousedown="renoted ? undoRenote(appearNote) : boostVisibility()" > <i class="ph-rocket-launch ph-bold ph-lg"></i> - <p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ appearNote.renoteCount }}</p> + <p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.renoteCount) }}</p> </button> <button v-else :class="$style.footerButton" class="_button" disabled> <i class="ph-prohibit ph-bold ph-lg"></i> @@ -135,12 +135,12 @@ SPDX-License-Identifier: AGPL-3.0-only <button v-if="appearNote.myReaction == null && appearNote.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.footerButton" class="_button" @click.stop @click="like()"> <i class="ph-heart ph-bold ph-lg"></i> </button> - <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> + <button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()"> + <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ph-heart ph-bold ph-lg" style="color: var(--eventReactionHeart);"></i> + <i v-else-if="appearNote.myReaction != null" class="ph-minus ph-bold ph-lg" style="color: var(--accent);"></i> + <i v-else-if="appearNote.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="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click.stop @click="undoReact(appearNote)"> - <i class="ph-minus ph-bold ph-lg"></i> + <p v-if="appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p> </button> <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()"> <i class="ph-paperclip ph-bold ph-lg"></i> @@ -195,6 +195,7 @@ import { pleaseLogin } from '@/scripts/please-login.js'; import { focusPrev, focusNext } from '@/scripts/focus.js'; import { checkWordMute } from '@/scripts/check-word-mute.js'; import { userPage } from '@/filters/user.js'; +import number from '@/filters/number.js'; import * as os from '@/os.js'; import * as sound from '@/scripts/sound.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; @@ -626,6 +627,14 @@ function undoRenote(note) : void { } } +function toggleReact() { + if (appearNote.value.myReaction == null) { + react(); + } else { + undoReact(appearNote.value); + } +} + function onContextmenu(ev: MouseEvent): void { if (props.mock) { return; diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 3d15f69f73..a901010776 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -113,10 +113,10 @@ SPDX-License-Identifier: AGPL-3.0-only <MkTime :time="appearNote.createdAt" mode="detail" colored/> </MkA> </div> - <MkReactionsViewer ref="reactionsViewer" :note="appearNote"/> + <MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" ref="reactionsViewer" :note="appearNote"/> <button class="_button" :class="$style.noteFooterButton" @click="reply()"> <i class="ph-arrow-u-up-left ph-bold ph-lg"></i> - <p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.repliesCount }}</p> + <p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.repliesCount) }}</p> </button> <button v-if="canRenote" @@ -127,7 +127,7 @@ SPDX-License-Identifier: AGPL-3.0-only @mousedown="renoted ? undoRenote() : boostVisibility()" > <i class="ph-rocket-launch ph-bold ph-lg"></i> - <p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.renoteCount }}</p> + <p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.renoteCount) }}</p> </button> <button v-else class="_button" :class="$style.noteFooterButton" disabled> <i class="ph-prohibit ph-bold ph-lg"></i> @@ -144,12 +144,12 @@ SPDX-License-Identifier: AGPL-3.0-only <button v-if="appearNote.myReaction == null && appearNote.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.noteFooterButton" class="_button" @mousedown="like()"> <i class="ph-heart ph-bold ph-lg"></i> </button> - <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> + <button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()"> + <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ph-heart ph-bold ph-lg" style="color: var(--eventReactionHeart);"></i> + <i v-else-if="appearNote.myReaction != null" class="ph-minus ph-bold ph-lg" style="color: var(--accent);"></i> + <i v-else-if="appearNote.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="appearNote.myReaction != null" ref="reactButton" class="_button" :class="[$style.noteFooterButton, $style.reacted]" @click="undoReact(appearNote)"> - <i class="ph-minus ph-bold ph-lg"></i> + <p v-if="appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p> </button> <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown="clip()"> <i class="ph-paperclip ph-bold ph-lg"></i> @@ -236,6 +236,7 @@ import { pleaseLogin } from '@/scripts/please-login.js'; import { checkWordMute } from '@/scripts/check-word-mute.js'; import { userPage } from '@/filters/user.js'; import { notePage } from '@/filters/note.js'; +import number from '@/filters/number.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import * as sound from '@/scripts/sound.js'; @@ -594,11 +595,11 @@ function like(): void { } } -function undoReact(note): void { - const oldReaction = note.myReaction; +function undoReact(targetNote: Misskey.entities.Note): void { + const oldReaction = targetNote.myReaction; if (!oldReaction) return; misskeyApi('notes/reactions/delete', { - noteId: note.id, + noteId: targetNote.id, }); } @@ -619,6 +620,14 @@ function undoRenote() : void { } } +function toggleReact() { + if (appearNote.value.myReaction == null) { + react(); + } else { + undoReact(appearNote.value); + } +} + function onContextmenu(ev: MouseEvent): void { const isLink = (el: HTMLElement): boolean => { if (el.tagName === 'A') return true; diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index 562cc38bf3..656e6f6635 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.head"> <MkAvatar v-if="['pollEnded', 'note', 'edited'].includes(notification.type) && notification.note" :class="$style.icon" :user="notification.note.user" link preview/> <MkAvatar v-else-if="['roleAssigned', 'achievementEarned'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/> + <div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ph-smiley ph-bold ph-lg" style="line-height: 1;"></i></div> <div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ph-smiley ph-bold ph-lg" style="line-height: 1;"></i></div> <div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ph-rocket-launch ph-bold ph-lg" style="line-height: 1;"></i></div> <img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/> @@ -60,6 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span> <span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span> <MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA> + <span v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'">{{ i18n.tsx._notification.likedBySomeUsers({ n: notification.reactions.length }) }}</span> <span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: notification.reactions.length }) }}</span> <span v-else-if="notification.type === 'renote:grouped'">{{ i18n.tsx._notification.renotedBySomeUsers({ n: notification.users.length }) }}</span> <span v-else-if="notification.type === 'app'">{{ notification.header }}</span> @@ -211,6 +213,7 @@ const rejectFollowRequest = () => { } .icon_reactionGroup, +.icon_reactionGroupHeart, .icon_renoteGroup { display: grid; align-items: center; @@ -223,11 +226,15 @@ const rejectFollowRequest = () => { } .icon_reactionGroup { - background: #e99a0b; + background: var(--eventReaction); +} + +.icon_reactionGroupHeart { + background: var(--eventReactionHeart); } .icon_renoteGroup { - background: #36d298; + background: var(--eventRenote); } .icon_app { @@ -256,49 +263,49 @@ const rejectFollowRequest = () => { .t_follow, .t_followRequestAccepted, .t_receiveFollowRequest { padding: 3px; - background: #36aed2; + background: var(--eventFollow); pointer-events: none; } .t_renote { padding: 3px; - background: #36d298; + background: var(--eventRenote); pointer-events: none; } .t_quote { padding: 3px; - background: #36d298; + background: var(--eventRenote); pointer-events: none; } .t_reply { padding: 3px; - background: #007aff; + background: var(--eventReply); pointer-events: none; } .t_mention { padding: 3px; - background: #88a6b7; + background: var(--eventOther); pointer-events: none; } .t_pollEnded { padding: 3px; - background: #88a6b7; + background: var(--eventOther); pointer-events: none; } .t_achievementEarned { padding: 3px; - background: #cb9a11; + background: var(--eventAchievement); pointer-events: none; } .t_roleAssigned { padding: 3px; - background: #88a6b7; + background: var(--eventOther); pointer-events: none; } diff --git a/packages/frontend/src/components/MkTutorialDialog.Note.vue b/packages/frontend/src/components/MkTutorialDialog.Note.vue index 5544434b5f..57216058fd 100644 --- a/packages/frontend/src/components/MkTutorialDialog.Note.vue +++ b/packages/frontend/src/components/MkTutorialDialog.Note.vue @@ -63,6 +63,7 @@ const exampleNote = reactive<Misskey.entities.Note>({ reactionAcceptance: null, renoteCount: 0, repliesCount: 1, + reactionCount: 0, reactions: {}, reactionEmojis: {}, fileIds: [], diff --git a/packages/frontend/src/components/MkTutorialDialog.PostNote.vue b/packages/frontend/src/components/MkTutorialDialog.PostNote.vue index 1771559a9b..b0561d4bae 100644 --- a/packages/frontend/src/components/MkTutorialDialog.PostNote.vue +++ b/packages/frontend/src/components/MkTutorialDialog.PostNote.vue @@ -68,6 +68,7 @@ const exampleCWNote = reactive<Misskey.entities.Note>({ reactionAcceptance: null, renoteCount: 0, repliesCount: 1, + reactionCount: 0, reactions: {}, reactionEmojis: {}, fileIds: [], diff --git a/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue b/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue index 4b4e8ea8f8..f155ad7bcb 100644 --- a/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue +++ b/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue @@ -58,6 +58,7 @@ const exampleNote = reactive<Misskey.entities.Note>({ reactionAcceptance: null, renoteCount: 0, repliesCount: 1, + reactionCount: 0, reactions: {}, reactionEmojis: {}, fileIds: ['0000000002'], diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue index f13a161ae8..c01211443d 100644 --- a/packages/frontend/src/components/global/MkAd.vue +++ b/packages/frontend/src/components/global/MkAd.vue @@ -14,10 +14,20 @@ SPDX-License-Identifier: AGPL-3.0-only [$style.form_vertical]: chosen.place === 'vertical', }]" > - <a :href="chosen.url" target="_blank" :class="$style.link"> + <component + :is="self ? 'MkA' : 'a'" + :class="$style.link" + v-bind="self ? { + to: chosen.url.substring(local.length), + } : { + href: chosen.url, + rel: 'nofollow noopener', + target: '_blank', + }" + > <img :src="chosen.imageUrl" :class="$style.img"> <button class="_button" :class="$style.i" @click.prevent.stop="toggleMenu"><i :class="$style.iIcon" class="ph-info ph-bold ph-lg"></i></button> - </a> + </component> </div> <div v-else :class="$style.menu"> <div :class="$style.menuContainer"> @@ -32,10 +42,10 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { ref } from 'vue'; +import { ref, computed } from 'vue'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; -import { host } from '@/config.js'; +import { url as local, host } from '@/config.js'; import MkButton from '@/components/MkButton.vue'; import { defaultStore } from '@/store.js'; import * as os from '@/os.js'; @@ -96,6 +106,9 @@ const choseAd = (): Ad | null => { }; const chosen = ref(choseAd()); + +const self = computed(() => chosen.value?.url.startsWith(local)); + const shouldHide = ref(!defaultStore.state.forceShowAds && $i && $i.policies.canHideAds && (props.specify == null)); function reduceFrequency(): void { |