diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-03-25 08:36:41 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-25 08:36:41 +0900 |
| commit | f54a9542bb2458e5f83bc7bcf25803a1bb76cef8 (patch) | |
| tree | 03a62e0b9508b75e55b71300b93a1bf540f84812 /packages/frontend/src/components | |
| parent | Merge pull request #10388 from misskey-dev/develop (diff) | |
| parent | New Crowdin updates (#10405) (diff) | |
| download | misskey-f54a9542bb2458e5f83bc7bcf25803a1bb76cef8.tar.gz misskey-f54a9542bb2458e5f83bc7bcf25803a1bb76cef8.tar.bz2 misskey-f54a9542bb2458e5f83bc7bcf25803a1bb76cef8.zip | |
Merge pull request #10402 from misskey-dev/develop
Release: 13.10.3
Diffstat (limited to 'packages/frontend/src/components')
| -rw-r--r-- | packages/frontend/src/components/MkNote.vue | 10 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkNoteDetailed.vue | 10 | ||||
| -rw-r--r-- | packages/frontend/src/components/global/MkAvatar.vue | 162 |
3 files changed, 138 insertions, 44 deletions
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index af81051a54..72c6e55df1 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -109,6 +109,9 @@ <button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)"> <i class="ti ti-minus"></i> </button> + <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()"> + <i class="ti ti-paperclip"></i> + </button> <button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="menu()"> <i class="ti ti-dots"></i> </button> @@ -151,7 +154,7 @@ import { reactionPicker } from '@/scripts/reaction-picker'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; import { $i } from '@/account'; import { i18n } from '@/i18n'; -import { getNoteMenu } from '@/scripts/get-note-menu'; +import { getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu'; import { useNoteCapture } from '@/scripts/use-note-capture'; import { deepClone } from '@/scripts/clone'; import { useTooltip } from '@/scripts/use-tooltip'; @@ -192,6 +195,7 @@ const menuButton = shallowRef<HTMLElement>(); const renoteButton = shallowRef<HTMLElement>(); const renoteTime = shallowRef<HTMLElement>(); const reactButton = shallowRef<HTMLElement>(); +const clipButton = shallowRef<HTMLElement>(); let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note); const isMyRenote = $i && ($i.id === note.userId); const showContent = ref(false); @@ -392,6 +396,10 @@ function menu(viaKeyboard = false): void { }).then(focus); } +async function clip() { + os.popupMenu(await getNoteClipMenu({ note: note, isDeleted, currentClipPage }), clipButton.value).then(focus); +} + function showRenoteMenu(viaKeyboard = false): void { if (!isMyRenote) return; os.popupMenu([{ diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index ea72e1b517..715fd3a9a8 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -114,6 +114,9 @@ <button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)"> <i class="ti ti-minus"></i> </button> + <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="button _button" @mousedown="clip()"> + <i class="ti ti-paperclip"></i> + </button> <button ref="menuButton" class="button _button" @mousedown="menu()"> <i class="ti ti-dots"></i> </button> @@ -156,7 +159,7 @@ import { reactionPicker } from '@/scripts/reaction-picker'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; import { $i } from '@/account'; import { i18n } from '@/i18n'; -import { getNoteMenu } from '@/scripts/get-note-menu'; +import { getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu'; import { useNoteCapture } from '@/scripts/use-note-capture'; import { deepClone } from '@/scripts/clone'; import { useTooltip } from '@/scripts/use-tooltip'; @@ -196,6 +199,7 @@ const menuButton = shallowRef<HTMLElement>(); const renoteButton = shallowRef<HTMLElement>(); const renoteTime = shallowRef<HTMLElement>(); const reactButton = shallowRef<HTMLElement>(); +const clipButton = shallowRef<HTMLElement>(); let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note); const isMyRenote = $i && ($i.id === note.userId); const showContent = ref(false); @@ -384,6 +388,10 @@ function menu(viaKeyboard = false): void { }).then(focus); } +async function clip() { + os.popupMenu(await getNoteClipMenu({ note: note, isDeleted }), clipButton.value).then(focus); +} + function showRenoteMenu(viaKeyboard = false): void { if (!isMyRenote) return; os.popupMenu([{ diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue index 7fb830d537..814ab53d27 100644 --- a/packages/frontend/src/components/global/MkAvatar.vue +++ b/packages/frontend/src/components/global/MkAvatar.vue @@ -1,25 +1,26 @@ <template> -<span v-if="!link" v-user-preview="preview ? user.id : undefined" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick"> +<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick"> <img :class="$style.inner" :src="url" decoding="async"/> <MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/> - <template v-if="user.isCat"> - <div :class="$style.earLeft"/> - <div :class="$style.earRight"/> - </template> -</span> -<MkA v-else v-user-preview="preview ? user.id : undefined" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" :to="userPage(user)" :target="target"> - <img :class="$style.inner" :src="url" decoding="async"/> - <MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/> - <template v-if="user.isCat"> - <div :class="$style.earLeft"/> - <div :class="$style.earRight"/> - </template> -</MkA> + <div v-if="user.isCat" :class="[$style.ears, { [$style.mask]: useBlurEffect }]"> + <div :class="$style.earLeft"> + <div v-if="useBlurEffect" :class="$style.layer"> + <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> + </div> + </div> + <div :class="$style.earRight"> + <div v-if="useBlurEffect" :class="$style.layer"> + <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> + </div> + </div> + </div> +</component> </template> <script lang="ts" setup> import { watch } from 'vue'; import * as misskey from 'misskey-js'; +import MkA from './MkA.vue'; import { getStaticImageUrl } from '@/scripts/media-proxy'; import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; import { acct, userPage } from '@/filters/user'; @@ -27,6 +28,7 @@ import MkUserOnlineIndicator from '@/components/MkUserOnlineIndicator.vue'; import { defaultStore } from '@/store'; const squareAvatars = $ref(defaultStore.state.squareAvatars); +const useBlurEffect = $ref(defaultStore.state.useBlurEffect); const props = withDefaults(defineProps<{ user: misskey.entities.User; @@ -45,15 +47,20 @@ const emit = defineEmits<{ (ev: 'click', v: MouseEvent): void; }>(); +const bound = $computed(() => props.link + ? { to: userPage(props.user), target: props.target } + : {}); + const url = $computed(() => defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(props.user.avatarUrl) : props.user.avatarUrl); -function onClick(ev: MouseEvent) { +function onClick(ev: MouseEvent): void { + if (props.link) return; emit('click', ev); } -let color = $ref(); +let color = $ref<string | undefined>(); watch(() => props.user.avatarBlurhash, () => { color = extractAvgColorFromBlurhash(props.user.avatarBlurhash); @@ -120,42 +127,113 @@ watch(() => props.user.avatarBlurhash, () => { } .cat { - > .earLeft, - > .earRight { + > .ears { contain: strict; - display: inline-block; - height: 50%; - width: 50%; - background: currentColor; + position: absolute; + top: -50%; + left: -50%; + width: 100%; + height: 100%; + padding: 50%; - &::before { - contain: strict; - content: ''; - display: block; - width: 60%; - height: 60%; - margin: 20%; - background: #df548f; + &.mask { + -webkit-mask: + url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><filter id="a"><feGaussianBlur in="SourceGraphic" stdDeviation="1"/></filter><circle cx="16" cy="16" r="15" filter="url(%23a)"/></svg>') center / 50% 50%, + linear-gradient(#fff, #fff); + -webkit-mask-composite: destination-out, source-over; + mask: + url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><filter id="a"><feGaussianBlur in="SourceGraphic" stdDeviation="1"/></filter><circle cx="16" cy="16" r="15" filter="url(%23a)"/></svg>') exclude center / 50% 50%, + linear-gradient(#fff, #fff); // polyfill of `image(#fff)` } - } - > .earLeft { - border-radius: 0 75% 75%; - transform: rotate(37.5deg) skew(30deg); - } + > .earLeft, + > .earRight { + contain: strict; + display: inline-block; + height: 50%; + width: 50%; + background: currentColor; - > .earRight { - border-radius: 75% 0 75% 75%; - transform: rotate(-37.5deg) skew(-30deg); - } + &::after { + contain: strict; + content: ''; + display: block; + width: 60%; + height: 60%; + margin: 20%; + background: #df548f; + } + + > .layer { + contain: strict; + position: absolute; + top: 0; + width: 280%; + height: 280%; + + > .plot { + contain: strict; + width: 100%; + height: 100%; + clip-path: path('M0 0H1V1H0z'); + transform: scale(32767); + transform-origin: 0 0; + } + } + } - &:hover { > .earLeft { - animation: earwiggleleft 1s infinite; + transform: rotate(37.5deg) skew(30deg); + + &, &::after { + border-radius: 0 75% 75%; + } + + > .layer { + left: 0; + transform: + skew(-30deg) + rotate(-37.5deg) + translate(-2.82842712475%, /* -2 * sqrt(2) */ + -38.5857864376%); /* 40 - 2 * sqrt(2) */ + + > .plot { + background-position: 20% 10%; /* ~= 37.5deg */ + } + } } > .earRight { - animation: earwiggleright 1s infinite; + transform: rotate(-37.5deg) skew(-30deg); + + &, &::after { + border-radius: 75% 0 75% 75%; + } + + > .layer { + right: 0; + transform: + skew(30deg) + rotate(37.5deg) + translate(2.82842712475%, /* 2 * sqrt(2) */ + -38.5857864376%); /* 40 - 2 * sqrt(2) */ + + > .plot { + background-position: 80% 10%; /* ~= 37.5deg */ + } + } + } + } + + &:hover { + > .ears { + > .earLeft { + animation: earwiggleleft 1s infinite; + } + + > .earRight { + animation: earwiggleright 1s infinite; + } } } } |