enhance(frontend): 링크 또는 내용을 복사할 때 토스트 알림 표시

This commit is contained in:
NoriDev 2023-12-26 19:20:15 +09:00
parent 485de41483
commit 1f50926912
18 changed files with 46 additions and 107 deletions

View file

@ -63,6 +63,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2023xx](CHANGE
- Enhance: 액세스 토큰 개선
- 토큰 생성 시, 토큰을 복사할 필요없이 '확인' 버튼을 누르면 자동으로 클립보드에 토큰이 복사됨
- 토큰 삭제 시, 삭제 전 대화 상자가 표시됨
- Enhance: 링크 또는 내용을 복사할 때 토스트 알림 표시
- Fix: '모달 배경색 제거' 옵션이 이모지 피커에 반영되지 않음
- Fix: 열람 주의로 설정된 노트의 리액션이 '더 보기'를 눌러야 표시됨
- Fix: 채널 이름이 긴 경우 게시 양식 표시가 깨지는 문제 (misskey-dev/misskey#12524)

View file

@ -1,5 +1,8 @@
---
_lang_: "English"
copiedLink: "링크를 복사했어요!"
copiedContent: "내용을 복사했어요!"
copied: "복사했어요!"
welcome: "Welcome!"
cherrypickMigrated: "The migration to CherryPick is complete!"
cherrypickMigratedCacheClearTitle: "The cache must be cleared."

3
locales/index.d.ts vendored
View file

@ -3,6 +3,9 @@
// Do not edit this file directly.
export interface Locale {
"_lang_": string;
"copiedLink": string;
"copiedContent": string;
"copied": string;
"welcome": string;
"cherrypickMigrated": string;
"cherrypickMigratedCacheClearTitle": string;

View file

@ -1,5 +1,8 @@
_lang_: "日本語"
copiedLink: "링크를 복사했어요!"
copiedContent: "내용을 복사했어요!"
copied: "복사했어요!"
welcome: "ようこそ!"
cherrypickMigrated: "CherryPickへの移行が完了しました"
cherrypickMigratedCacheClearTitle: "キャッシュクリアのご案内"

View file

@ -1,5 +1,8 @@
---
_lang_: "한국어"
copiedLink: "링크를 복사했어요!"
copiedContent: "내용을 복사했어요!"
copied: "복사했어요!"
welcome: "환영합니다!"
cherrypickMigrated: "CherryPick으로 마이그레이션이 완료되었어요!"
cherrypickMigratedCacheClearTitle: "캐시 삭제 안내"

View file

@ -277,6 +277,7 @@ function onContextmenu(ev: MouseEvent) {
text: i18n.ts.copyFolderId,
action: () => {
copyToClipboard(props.folder.id);
os.toast(i18n.ts.copied, 'copied');
},
}]);
}

View file

@ -1,81 +0,0 @@
<!--
SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div>
<Transition
:enterActiveClass="defaultStore.state.animation ? $style.transition_toast_enterActive : ''"
:leaveActiveClass="defaultStore.state.animation ? $style.transition_toast_leaveActive : ''"
:enterFromClass="defaultStore.state.animation ? $style.transition_toast_enterFrom : ''"
:leaveToClass="defaultStore.state.animation ? $style.transition_toast_leaveTo : ''"
appear @afterLeave="emit('closed')"
>
<div v-if="showing" class="_acrylic" :class="[$style.root, { [$style.reduceBlurEffect]: !defaultStore.state.useBlurEffect }]" :style="{ zIndex }">
<div style="padding: 16px 24px;">
<i :class="icon === 'posted' ? 'ti-check' : icon === 'reply' ? 'ti-arrow-back-up' : icon === 'renote' ? 'ti-repeat' : icon === 'quote' ? 'ti-quote' : icon === 'edited' ? 'ti ti-pencil' : 'ti-check'" class="ti"></i>
{{ message }}
</div>
</div>
</Transition>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import * as os from '@/os.js';
import { defaultStore } from '@/store.js';
defineProps<{
message: string;
icon: string;
}>();
const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const zIndex = os.claimZIndex('high');
const showing = ref(true);
onMounted(() => {
window.setTimeout(() => {
showing.value = false;
}, 4000);
});
</script>
<style lang="scss" module>
.transition_toast_enterActive,
.transition_toast_leaveActive {
transition: opacity 0.3s, transform 0.3s !important;
}
.transition_toast_enterFrom,
.transition_toast_leaveTo {
opacity: 0;
transform: translateY(-100%);
}
.root {
position: fixed;
left: 0;
right: 0;
top: 50px;
margin: 16px auto 0;
min-width: 300px;
max-width: calc(100% - 32px);
width: min-content;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
border-radius: 8px;
overflow: clip;
text-align: center;
pointer-events: none;
@media (max-width: 500px) {
&.reduceBlurEffect {
background: var(--panel);
}
}
}
</style>

View file

@ -43,6 +43,7 @@ import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.j
import { openingWindowsCount } from '@/os.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { getScrollContainer } from '@/scripts/scroll.js';
import * as os from "@/os.js";
const props = defineProps<{
initialPath: string;
@ -120,6 +121,7 @@ const contextmenu = computed(() => ([{
text: i18n.ts.copyLink,
action: () => {
copyToClipboard(url + router.getCurrentPath());
os.toast(i18n.ts.copiedLink, 'copied');
},
}]));

View file

@ -835,10 +835,10 @@ async function post(ev?: MouseEvent) {
clear();
}
nextTick(() => {
if (props.reply) os.noteToast(i18n.ts.replied, 'reply');
else if (props.renote) os.noteToast(i18n.ts.quoted, 'quote');
else if (props.updateMode) os.noteToast(i18n.ts.noteEdited, 'edited');
else os.noteToast(i18n.ts.posted, 'posted');
if (props.reply) os.toast(i18n.ts.replied, 'reply');
else if (props.renote) os.toast(i18n.ts.quoted, 'quote');
else if (props.updateMode) os.toast(i18n.ts.noteEdited, 'edited');
else os.toast(i18n.ts.posted, 'posted');
deleteDraft();
emit('posted');

View file

@ -828,10 +828,10 @@ async function post(ev?: MouseEvent) {
clear();
}
nextTick(() => {
if (props.reply) os.noteToast(i18n.ts.replied, 'reply');
else if (props.renote) os.noteToast(i18n.ts.quoted, 'quote');
else if (props.updateMode) os.noteToast(i18n.ts.noteEdited, 'edited');
else os.noteToast(i18n.ts.posted, 'posted');
if (props.reply) os.toast(i18n.ts.replied, 'reply');
else if (props.renote) os.toast(i18n.ts.quoted, 'quote');
else if (props.updateMode) os.toast(i18n.ts.noteEdited, 'edited');
else os.toast(i18n.ts.posted, 'posted');
deleteDraft();
emit('posted');

View file

@ -14,6 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<div v-if="showing" class="_acrylic" :class="[$style.root, { [$style.reduceBlurEffect]: !defaultStore.state.useBlurEffect }]" :style="{ zIndex }">
<div style="padding: 16px 24px;">
<i v-if="icon" :class="icon === 'posted' ? 'ti-check' : icon === 'reply' ? 'ti-arrow-back-up' : icon === 'renote' ? 'ti-repeat' : icon === 'quote' ? 'ti-quote' : icon === 'edited' ? 'ti ti-pencil' : icon === 'copied' ? 'ti-copy' : 'ti-check'" class="ti"></i>
{{ message }}
</div>
</div>
@ -28,6 +29,7 @@ import { defaultStore } from '@/store.js';
defineProps<{
message: string;
icon?: string;
}>();
const emit = defineEmits<{

View file

@ -68,6 +68,7 @@ function onContextmenu(ev) {
text: i18n.ts.copyLink,
action: () => {
copyToClipboard(`${url}${props.to}`);
os.toast(i18n.ts.copiedLink, 'copied');
},
}], ev);
}

View file

@ -92,6 +92,7 @@ function onClick(ev: MouseEvent) {
icon: 'ti ti-copy',
action: () => {
copyToClipboard(`:${props.name}:`);
os.toast(i18n.ts.copied, 'copied');
},
}] : []), ...(props.host && $i && ($i.isAdmin || $i.policies.canManageCustomEmojis) ? [{
text: i18n.ts.import,

View file

@ -16,7 +16,6 @@ import MkPostFormDialog from '@/components/MkPostFormDialog.vue';
import MkWaitingDialog from '@/components/MkWaitingDialog.vue';
import MkPageWindow from '@/components/MkPageWindow.vue';
import MkToast from '@/components/MkToast.vue';
import MkNoteToast from '@/components/MkNoteToast.vue';
import MkWelcomeToast from '@/components/MkWelcomeToast.vue';
import MkDialog from '@/components/MkDialog.vue';
import MkPasswordDialog from '@/components/MkPasswordDialog.vue';
@ -179,15 +178,9 @@ export function pageWindow(path: string) {
}, {}, 'closed');
}
export function toast(message: string) {
export function toast(message: string, icon?: string) {
popup(MkToast, {
message,
}, {}, 'closed');
}
export function noteToast(message: string, icon: string) {
popup(MkNoteToast, {
message,
icon,
}, {}, 'closed');
}

View file

@ -38,6 +38,7 @@ function generateToken() {
text: token,
}).then(() => {
copyToClipboard(token);
os.toast(i18n.ts.copied, 'copied');
});
},
}, 'closed');

View file

@ -131,6 +131,7 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
text: i18n.ts.copyFileId,
action: () => {
copyToClipboard(file.id);
os.toast(i18n.ts.copied, 'copied');
},
}]);
}

View file

@ -117,7 +117,7 @@ export function getCopyNoteLinkMenu(note: Misskey.entities.Note, text: string):
text,
action: (): void => {
copyToClipboard(`${url}/notes/${note.id}`);
os.success();
os.toast(i18n.ts.copiedLink, 'copied');
},
};
}
@ -241,12 +241,12 @@ export function getNoteMenu(props: {
function copyContent(): void {
copyToClipboard(appearNote.text);
os.success();
os.toast(i18n.ts.copiedContent, 'copied');
}
function copyLink(): void {
copyToClipboard(`${url}/notes/${appearNote.id}`);
os.success();
os.toast(i18n.ts.copiedLink, 'copied');
}
function togglePin(pin: boolean): void {
@ -521,6 +521,7 @@ export function getNoteMenu(props: {
text: i18n.ts.copyNoteId,
action: () => {
copyToClipboard(appearNote.id);
os.toast(i18n.ts.copied, 'copied');
},
}]);
}
@ -629,7 +630,7 @@ export function getRenoteMenu(props: {
renoteId: appearNote.id,
channelId: appearNote.channelId,
}).then(() => {
os.noteToast(i18n.ts.renoted, 'renote');
os.toast(i18n.ts.renoted, 'renote');
});
}
},
@ -685,7 +686,7 @@ export function getRenoteMenu(props: {
visibility,
renoteId: appearNote.id,
}).then(() => {
os.noteToast(i18n.ts.renoted, 'renote');
os.toast(i18n.ts.renoted, 'renote');
});
}
},
@ -723,7 +724,7 @@ export function getRenoteMenu(props: {
visibility: 'public',
renoteId: appearNote.id,
}).then(() => {
os.noteToast(i18n.ts.renoted, 'renote');
os.toast(i18n.ts.renoted, 'renote');
});
},
});
@ -741,7 +742,7 @@ export function getRenoteMenu(props: {
visibility: 'home',
renoteId: appearNote.id,
}).then(() => {
os.noteToast(i18n.ts.renoted, 'renote');
os.toast(i18n.ts.renoted, 'renote');
});
},
});
@ -758,7 +759,7 @@ export function getRenoteMenu(props: {
visibility: 'followers',
renoteId: appearNote.id,
}).then(() => {
os.noteToast(i18n.ts.renoted, 'renote');
os.toast(i18n.ts.renoted, 'renote');
});
},
});
@ -812,7 +813,7 @@ export async function getRenoteOnly(props: {
renoteId: appearNote.id,
channelId: appearNote.channelId,
}).then(() => {
os.noteToast(i18n.ts.renoted, 'renote');
os.toast(i18n.ts.renoted, 'renote');
});
}
}
@ -841,7 +842,7 @@ export async function getRenoteOnly(props: {
visibility,
renoteId: appearNote.id,
}).then(() => {
os.noteToast(i18n.ts.renoted, 'renote');
os.toast(i18n.ts.renoted, 'renote');
});
}
}

View file

@ -184,6 +184,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
text: i18n.ts.copyUsername,
action: () => {
copyToClipboard(`@${user.username}@${user.host ?? host}`);
os.toast(i18n.ts.copied, 'copied');
},
}, ...(iAmModerator ? [{
icon: 'ti ti-user-exclamation',
@ -196,6 +197,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
text: i18n.ts.copyRSS,
action: () => {
copyToClipboard(`${user.host ?? host}/@${user.username}.atom`);
os.toast(i18n.ts.copied, 'copied');
},
}, {
icon: 'ti ti-share',
@ -203,6 +205,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
action: () => {
const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
copyToClipboard(`${url}/${canonical}`);
os.toast(i18n.ts.copiedLink, 'copied');
},
}, {
icon: 'ti ti-mail',
@ -391,6 +394,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
text: i18n.ts.copyUserId,
action: () => {
copyToClipboard(user.id);
os.toast(i18n.ts.copied, 'copied');
},
}]);
}