summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-03-25 08:36:41 +0900
committerGitHub <noreply@github.com>2023-03-25 08:36:41 +0900
commitf54a9542bb2458e5f83bc7bcf25803a1bb76cef8 (patch)
tree03a62e0b9508b75e55b71300b93a1bf540f84812 /packages/frontend/src/components
parentMerge pull request #10388 from misskey-dev/develop (diff)
parentNew Crowdin updates (#10405) (diff)
downloadmisskey-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.vue10
-rw-r--r--packages/frontend/src/components/MkNoteDetailed.vue10
-rw-r--r--packages/frontend/src/components/global/MkAvatar.vue162
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;
+ }
}
}
}