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

View file

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

3
locales/index.d.ts vendored
View file

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

View file

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

View file

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

View file

@ -277,6 +277,7 @@ function onContextmenu(ev: MouseEvent) {
text: i18n.ts.copyFolderId, text: i18n.ts.copyFolderId,
action: () => { action: () => {
copyToClipboard(props.folder.id); 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 { openingWindowsCount } from '@/os.js';
import { claimAchievement } from '@/scripts/achievements.js'; import { claimAchievement } from '@/scripts/achievements.js';
import { getScrollContainer } from '@/scripts/scroll.js'; import { getScrollContainer } from '@/scripts/scroll.js';
import * as os from "@/os.js";
const props = defineProps<{ const props = defineProps<{
initialPath: string; initialPath: string;
@ -120,6 +121,7 @@ const contextmenu = computed(() => ([{
text: i18n.ts.copyLink, text: i18n.ts.copyLink,
action: () => { action: () => {
copyToClipboard(url + router.getCurrentPath()); copyToClipboard(url + router.getCurrentPath());
os.toast(i18n.ts.copiedLink, 'copied');
}, },
}])); }]));

View file

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

View file

@ -828,10 +828,10 @@ async function post(ev?: MouseEvent) {
clear(); clear();
} }
nextTick(() => { nextTick(() => {
if (props.reply) os.noteToast(i18n.ts.replied, 'reply'); if (props.reply) os.toast(i18n.ts.replied, 'reply');
else if (props.renote) os.noteToast(i18n.ts.quoted, 'quote'); else if (props.renote) os.toast(i18n.ts.quoted, 'quote');
else if (props.updateMode) os.noteToast(i18n.ts.noteEdited, 'edited'); else if (props.updateMode) os.toast(i18n.ts.noteEdited, 'edited');
else os.noteToast(i18n.ts.posted, 'posted'); else os.toast(i18n.ts.posted, 'posted');
deleteDraft(); deleteDraft();
emit('posted'); 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 v-if="showing" class="_acrylic" :class="[$style.root, { [$style.reduceBlurEffect]: !defaultStore.state.useBlurEffect }]" :style="{ zIndex }">
<div style="padding: 16px 24px;"> <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 }} {{ message }}
</div> </div>
</div> </div>
@ -28,6 +29,7 @@ import { defaultStore } from '@/store.js';
defineProps<{ defineProps<{
message: string; message: string;
icon?: string;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{

View file

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

View file

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

View file

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

View file

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

View file

@ -131,6 +131,7 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
text: i18n.ts.copyFileId, text: i18n.ts.copyFileId,
action: () => { action: () => {
copyToClipboard(file.id); 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, text,
action: (): void => { action: (): void => {
copyToClipboard(`${url}/notes/${note.id}`); copyToClipboard(`${url}/notes/${note.id}`);
os.success(); os.toast(i18n.ts.copiedLink, 'copied');
}, },
}; };
} }
@ -241,12 +241,12 @@ export function getNoteMenu(props: {
function copyContent(): void { function copyContent(): void {
copyToClipboard(appearNote.text); copyToClipboard(appearNote.text);
os.success(); os.toast(i18n.ts.copiedContent, 'copied');
} }
function copyLink(): void { function copyLink(): void {
copyToClipboard(`${url}/notes/${appearNote.id}`); copyToClipboard(`${url}/notes/${appearNote.id}`);
os.success(); os.toast(i18n.ts.copiedLink, 'copied');
} }
function togglePin(pin: boolean): void { function togglePin(pin: boolean): void {
@ -521,6 +521,7 @@ export function getNoteMenu(props: {
text: i18n.ts.copyNoteId, text: i18n.ts.copyNoteId,
action: () => { action: () => {
copyToClipboard(appearNote.id); copyToClipboard(appearNote.id);
os.toast(i18n.ts.copied, 'copied');
}, },
}]); }]);
} }
@ -629,7 +630,7 @@ export function getRenoteMenu(props: {
renoteId: appearNote.id, renoteId: appearNote.id,
channelId: appearNote.channelId, channelId: appearNote.channelId,
}).then(() => { }).then(() => {
os.noteToast(i18n.ts.renoted, 'renote'); os.toast(i18n.ts.renoted, 'renote');
}); });
} }
}, },
@ -685,7 +686,7 @@ export function getRenoteMenu(props: {
visibility, visibility,
renoteId: appearNote.id, renoteId: appearNote.id,
}).then(() => { }).then(() => {
os.noteToast(i18n.ts.renoted, 'renote'); os.toast(i18n.ts.renoted, 'renote');
}); });
} }
}, },
@ -723,7 +724,7 @@ export function getRenoteMenu(props: {
visibility: 'public', visibility: 'public',
renoteId: appearNote.id, renoteId: appearNote.id,
}).then(() => { }).then(() => {
os.noteToast(i18n.ts.renoted, 'renote'); os.toast(i18n.ts.renoted, 'renote');
}); });
}, },
}); });
@ -741,7 +742,7 @@ export function getRenoteMenu(props: {
visibility: 'home', visibility: 'home',
renoteId: appearNote.id, renoteId: appearNote.id,
}).then(() => { }).then(() => {
os.noteToast(i18n.ts.renoted, 'renote'); os.toast(i18n.ts.renoted, 'renote');
}); });
}, },
}); });
@ -758,7 +759,7 @@ export function getRenoteMenu(props: {
visibility: 'followers', visibility: 'followers',
renoteId: appearNote.id, renoteId: appearNote.id,
}).then(() => { }).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, renoteId: appearNote.id,
channelId: appearNote.channelId, channelId: appearNote.channelId,
}).then(() => { }).then(() => {
os.noteToast(i18n.ts.renoted, 'renote'); os.toast(i18n.ts.renoted, 'renote');
}); });
} }
} }
@ -841,7 +842,7 @@ export async function getRenoteOnly(props: {
visibility, visibility,
renoteId: appearNote.id, renoteId: appearNote.id,
}).then(() => { }).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, text: i18n.ts.copyUsername,
action: () => { action: () => {
copyToClipboard(`@${user.username}@${user.host ?? host}`); copyToClipboard(`@${user.username}@${user.host ?? host}`);
os.toast(i18n.ts.copied, 'copied');
}, },
}, ...(iAmModerator ? [{ }, ...(iAmModerator ? [{
icon: 'ti ti-user-exclamation', icon: 'ti ti-user-exclamation',
@ -196,6 +197,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
text: i18n.ts.copyRSS, text: i18n.ts.copyRSS,
action: () => { action: () => {
copyToClipboard(`${user.host ?? host}/@${user.username}.atom`); copyToClipboard(`${user.host ?? host}/@${user.username}.atom`);
os.toast(i18n.ts.copied, 'copied');
}, },
}, { }, {
icon: 'ti ti-share', icon: 'ti ti-share',
@ -203,6 +205,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
action: () => { action: () => {
const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`; const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
copyToClipboard(`${url}/${canonical}`); copyToClipboard(`${url}/${canonical}`);
os.toast(i18n.ts.copiedLink, 'copied');
}, },
}, { }, {
icon: 'ti ti-mail', icon: 'ti ti-mail',
@ -391,6 +394,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
text: i18n.ts.copyUserId, text: i18n.ts.copyUserId,
action: () => { action: () => {
copyToClipboard(user.id); copyToClipboard(user.id);
os.toast(i18n.ts.copied, 'copied');
}, },
}]); }]);
} }