feat: translate button on note footer

This commit is contained in:
무라쿠모 2024-06-20 01:04:53 +09:00
parent 544f00ecf3
commit d6bc8c3cfc
No known key found for this signature in database
GPG key ID: 139D6573F92DA9F7
5 changed files with 42 additions and 5 deletions

View file

@ -128,6 +128,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()"> <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()">
<i class="ti ti-paperclip"></i> <i class="ti ti-paperclip"></i>
</button> </button>
<button v-if="defaultStore.state.showTranslateButtonInNoteFooter" ref="translateButton" :class="$style.footerButton" class="_button" @mousedown="translate()">
<i class="ti ti-language-hiragana"></i>
</button>
<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="showMenu()"> <button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="showMenu()">
<i class="ti ti-dots"></i> <i class="ti ti-dots"></i>
</button> </button>
@ -193,6 +196,7 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import { shouldCollapsed } from '@/scripts/collapsed.js'; import { shouldCollapsed } from '@/scripts/collapsed.js';
import { isEnabledUrlPreview } from '@/instance.js'; import { isEnabledUrlPreview } from '@/instance.js';
import {miLocalStorage} from "@/local-storage.js";
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
@ -250,6 +254,7 @@ const renoteButton = shallowRef<HTMLElement>();
const renoteTime = shallowRef<HTMLElement>(); const renoteTime = shallowRef<HTMLElement>();
const reactButton = shallowRef<HTMLElement>(); const reactButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>(); const clipButton = shallowRef<HTMLElement>();
const translateButton = shallowRef<HTMLElement>();
const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value); const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
const isMyRenote = $i && ($i.id === note.value.userId); const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false); const showContent = ref(false);
@ -481,7 +486,7 @@ function showMenu(viaKeyboard = false): void {
}).then(focus).finally(cleanup); }).then(focus).finally(cleanup);
} }
async function clip() { async function clip(): Promise<void> {
if (props.mock) { if (props.mock) {
return; return;
} }
@ -489,6 +494,17 @@ async function clip() {
os.popupMenu(await getNoteClipMenu({ note: note.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus); os.popupMenu(await getNoteClipMenu({ note: note.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
} }
async function translate(): Promise<void> {
if (translation.value != null) return;
translating.value = true;
const res = await misskeyApi('notes/translate', {
noteId: appearNote.value.id,
targetLang: miLocalStorage.getItem('lang') ?? navigator.language,
});
translating.value = false;
translation.value = res;
}
function showRenoteMenu(viaKeyboard = false): void { function showRenoteMenu(viaKeyboard = false): void {
if (props.mock) { if (props.mock) {
return; return;

View file

@ -136,6 +136,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown="clip()"> <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown="clip()">
<i class="ti ti-paperclip"></i> <i class="ti ti-paperclip"></i>
</button> </button>
<button v-if="defaultStore.state.showTranslateButtonInNoteFooter" ref="translateButton" :class="$style.footerButton" class="_button" @mousedown="translate()">
<i class="ti ti-language-hiragana"></i>
</button>
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown="showMenu()"> <button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown="showMenu()">
<i class="ti ti-dots"></i> <i class="ti ti-dots"></i>
</button> </button>
@ -233,6 +236,7 @@ import MkPagination, { type Paging } from '@/components/MkPagination.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { isEnabledUrlPreview } from '@/instance.js'; import { isEnabledUrlPreview } from '@/instance.js';
import {miLocalStorage} from "@/local-storage.js";
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
@ -478,10 +482,21 @@ function showMenu(viaKeyboard = false): void {
}).then(focus).finally(cleanup); }).then(focus).finally(cleanup);
} }
async function clip() { async function clip(): Promise<void> {
os.popupMenu(await getNoteClipMenu({ note: note.value, isDeleted }), clipButton.value).then(focus); os.popupMenu(await getNoteClipMenu({ note: note.value, isDeleted }), clipButton.value).then(focus);
} }
async function translate(): Promise<void> {
if (translation.value != null) return;
translating.value = true;
const res = await misskeyApi('notes/translate', {
noteId: appearNote.value.id,
targetLang: miLocalStorage.getItem('lang') ?? navigator.language,
});
translating.value = false;
translation.value = res;
}
function showRenoteMenu(viaKeyboard = false): void { function showRenoteMenu(viaKeyboard = false): void {
if (!isMyRenote) return; if (!isMyRenote) return;
pleaseLogin(); pleaseLogin();

View file

@ -52,6 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_s"> <div class="_gaps_s">
<MkSwitch v-model="showNoteActionsOnlyHover">{{ i18n.ts.showNoteActionsOnlyHover }}</MkSwitch> <MkSwitch v-model="showNoteActionsOnlyHover">{{ i18n.ts.showNoteActionsOnlyHover }}</MkSwitch>
<MkSwitch v-model="showClipButtonInNoteFooter">{{ i18n.ts.showClipButtonInNoteFooter }}</MkSwitch> <MkSwitch v-model="showClipButtonInNoteFooter">{{ i18n.ts.showClipButtonInNoteFooter }}</MkSwitch>
<MkSwitch v-model="showTranslateButtonInNoteFooter">{{ i18n.ts.showTranslateButtonInNoteFooter }}</MkSwitch>
<MkSwitch v-model="collapseRenotes">{{ i18n.ts.collapseRenotes }}</MkSwitch> <MkSwitch v-model="collapseRenotes">{{ i18n.ts.collapseRenotes }}</MkSwitch>
<MkSwitch v-model="hideMutedNotes">{{ i18n.ts._wordMute.hideMutedNotes }}</MkSwitch> <MkSwitch v-model="hideMutedNotes">{{ i18n.ts._wordMute.hideMutedNotes }}</MkSwitch>
<MkSwitch v-model="advancedMfm">{{ i18n.ts.enableAdvancedMfm }}</MkSwitch> <MkSwitch v-model="advancedMfm">{{ i18n.ts.enableAdvancedMfm }}</MkSwitch>
@ -278,6 +279,7 @@ const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDev
const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior')); const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNoteActionsOnlyHover')); const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNoteActionsOnlyHover'));
const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showClipButtonInNoteFooter')); const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showClipButtonInNoteFooter'));
const showTranslateButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showTranslateButtonInNoteFooter'));
const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize')); const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize'));
const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthOfReaction')); const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthOfReaction'));
const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes')); const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));

View file

@ -37,7 +37,7 @@ export async function getNoteClipMenu(props: {
const isRenote = ( const isRenote = (
props.note.renote != null && props.note.renote != null &&
props.note.text == null && props.note.text == null &&
props.note.fileIds.length === 0 && props.note.fileIds?.length === 0 &&
props.note.poll == null props.note.poll == null
); );
@ -165,7 +165,7 @@ export function getNoteMenu(props: {
const isRenote = ( const isRenote = (
props.note.renote != null && props.note.renote != null &&
props.note.text == null && props.note.text == null &&
props.note.fileIds.length === 0 && props.note.fileIds?.length === 0 &&
props.note.poll == null props.note.poll == null
); );
@ -321,7 +321,7 @@ export function getNoteMenu(props: {
text: i18n.ts.share, text: i18n.ts.share,
action: share, action: share,
}] : []), }] : []),
$i && $i.policies.canUseTranslator && instance.translatorAvailable ? { $i && $i.policies.canUseTranslator && instance.translatorAvailable && !defaultStore.state.showTranslateButtonInNoteFooter? {
icon: 'ti ti-language-hiragana', icon: 'ti ti-language-hiragana',
text: i18n.ts.translate, text: i18n.ts.translate,
action: translate, action: translate,

View file

@ -366,6 +366,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: false, default: false,
}, },
showTranslateButtonInNoteFooter: {
where: 'device',
default: false,
},
reactionsDisplaySize: { reactionsDisplaySize: {
where: 'device', where: 'device',
default: 'medium' as 'small' | 'medium' | 'large', default: 'medium' as 'small' | 'medium' | 'large',