diff options
| author | tamaina <tamaina@hotmail.co.jp> | 2022-01-28 15:44:31 +0900 |
|---|---|---|
| committer | tamaina <tamaina@hotmail.co.jp> | 2022-01-28 15:44:31 +0900 |
| commit | 3db78e367b392271a35a545dc2dfada93bf217bb (patch) | |
| tree | 3e993f37ae8dc2d0f460858ae68f65321991d127 /packages/client/src | |
| parent | Merge branch 'develop' into pizzax-indexeddb (diff) | |
| parent | round relative time (#8199) (diff) | |
| download | sharkey-3db78e367b392271a35a545dc2dfada93bf217bb.tar.gz sharkey-3db78e367b392271a35a545dc2dfada93bf217bb.tar.bz2 sharkey-3db78e367b392271a35a545dc2dfada93bf217bb.zip | |
Merge branch 'develop' into pizzax-indexeddb
Diffstat (limited to 'packages/client/src')
122 files changed, 1350 insertions, 1642 deletions
diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index 5a935e1dc7..4aeceeccab 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -192,31 +192,31 @@ export async function openAccountMenu(opts: { if (opts.withExtraOperation) { popupMenu([...[{ type: 'link', - text: i18n.locale.profile, + text: i18n.ts.profile, to: `/@${ $i.username }`, avatar: $i, }, null, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, { icon: 'fas fa-plus', - text: i18n.locale.addAccount, + text: i18n.ts.addAccount, action: () => { popupMenu([{ - text: i18n.locale.existingAccount, + text: i18n.ts.existingAccount, action: () => { showSigninDialog(); }, }, { - text: i18n.locale.createAccount, + text: i18n.ts.createAccount, action: () => { createAccount(); }, - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); }, }, { type: 'link', icon: 'fas fa-users', - text: i18n.locale.manageAccounts, + text: i18n.ts.manageAccounts, to: `/settings/accounts`, - }]], ev.currentTarget || ev.target, { + }]], ev.currentTarget ?? ev.target, { align: 'left' }); } else { - popupMenu([...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises], ev.currentTarget || ev.target, { + popupMenu([...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises], ev.currentTarget ?? ev.target, { align: 'left' }); } diff --git a/packages/client/src/components/abuse-report-window.vue b/packages/client/src/components/abuse-report-window.vue index cd04f62bca..f2cb369802 100644 --- a/packages/client/src/components/abuse-report-window.vue +++ b/packages/client/src/components/abuse-report-window.vue @@ -2,7 +2,7 @@ <XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')"> <template #header> <i class="fas fa-exclamation-circle" style="margin-right: 0.5em;"></i> - <I18n :src="i18n.locale.reportAbuseOf" tag="span"> + <I18n :src="i18n.ts.reportAbuseOf" tag="span"> <template #name> <b><MkAcct :user="user"/></b> </template> @@ -11,12 +11,12 @@ <div class="dpvffvvy _monolithic_"> <div class="_section"> <MkTextarea v-model="comment"> - <template #label>{{ i18n.locale.details }}</template> - <template #caption>{{ i18n.locale.fillAbuseReportDescription }}</template> + <template #label>{{ i18n.ts.details }}</template> + <template #caption>{{ i18n.ts.fillAbuseReportDescription }}</template> </MkTextarea> </div> <div class="_section"> - <MkButton primary full :disabled="comment.length === 0" @click="send">{{ i18n.locale.send }}</MkButton> + <MkButton primary full :disabled="comment.length === 0" @click="send">{{ i18n.ts.send }}</MkButton> </div> </div> </XWindow> @@ -50,7 +50,7 @@ function send() { }, undefined).then(res => { os.alert({ type: 'success', - text: i18n.locale.abuseReported + text: i18n.ts.abuseReported }); window.value?.close(); emit('closed'); diff --git a/packages/client/src/components/autocomplete.vue b/packages/client/src/components/autocomplete.vue index 7ba83b7cb1..91a50ffa59 100644 --- a/packages/client/src/components/autocomplete.vue +++ b/packages/client/src/components/autocomplete.vue @@ -8,7 +8,7 @@ </span> <span class="username">@{{ acct(user) }}</span> </li> - <li tabindex="-1" class="choose" @click="chooseUser()" @keydown="onKeydown">{{ i18n.locale.selectUser }}</li> + <li tabindex="-1" class="choose" @click="chooseUser()" @keydown="onKeydown">{{ i18n.ts.selectUser }}</li> </ol> <ol v-else-if="hashtags.length > 0" ref="suggests" class="hashtags"> <li v-for="hashtag in hashtags" tabindex="-1" @click="complete(type, hashtag)" @keydown="onKeydown"> diff --git a/packages/client/src/components/captcha.vue b/packages/client/src/components/captcha.vue index 770804cf44..963ae25f8e 100644 --- a/packages/client/src/components/captcha.vue +++ b/packages/client/src/components/captcha.vue @@ -1,6 +1,6 @@ <template> <div> - <span v-if="!available">{{ i18n.locale.waiting }}<MkEllipsis/></span> + <span v-if="!available">{{ i18n.ts.waiting }}<MkEllipsis/></span> <div ref="captchaEl"></div> </div> </template> @@ -38,7 +38,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'update:modelValue', v: string | null): void; + (ev: 'update:modelValue', v: string | null): void; }>(); const available = ref(false); diff --git a/packages/client/src/components/channel-follow-button.vue b/packages/client/src/components/channel-follow-button.vue index 0ad5384cd5..7bbf5ae663 100644 --- a/packages/client/src/components/channel-follow-button.vue +++ b/packages/client/src/components/channel-follow-button.vue @@ -6,14 +6,14 @@ > <template v-if="!wait"> <template v-if="isFollowing"> - <span v-if="full">{{ i18n.locale.unfollow }}</span><i class="fas fa-minus"></i> + <span v-if="full">{{ i18n.ts.unfollow }}</span><i class="fas fa-minus"></i> </template> <template v-else> - <span v-if="full">{{ i18n.locale.follow }}</span><i class="fas fa-plus"></i> + <span v-if="full">{{ i18n.ts.follow }}</span><i class="fas fa-plus"></i> </template> </template> <template v-else> - <span v-if="full">{{ i18n.locale.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> + <span v-if="full">{{ i18n.ts.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> </template> </button> </template> diff --git a/packages/client/src/components/channel-preview.vue b/packages/client/src/components/channel-preview.vue index 8d135a192f..dd3794a657 100644 --- a/packages/client/src/components/channel-preview.vue +++ b/packages/client/src/components/channel-preview.vue @@ -6,7 +6,7 @@ <div class="status"> <div> <i class="fas fa-users fa-fw"></i> - <I18n :src="i18n.locale._channel.usersCount" tag="span" style="margin-left: 4px;"> + <I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"> <template #n> <b>{{ channel.usersCount }}</b> </template> @@ -14,7 +14,7 @@ </div> <div> <i class="fas fa-pencil-alt fa-fw"></i> - <I18n :src="i18n.locale._channel.notesCount" tag="span" style="margin-left: 4px;"> + <I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;"> <template #n> <b>{{ channel.notesCount }}</b> </template> @@ -27,7 +27,7 @@ </article> <footer> <span v-if="channel.lastNotedAt"> - {{ i18n.locale.updatedAt }}: <MkTime :time="channel.lastNotedAt"/> + {{ i18n.ts.updatedAt }}: <MkTime :time="channel.lastNotedAt"/> </span> </footer> </MkA> diff --git a/packages/client/src/components/chart.vue b/packages/client/src/components/chart.vue index 1959271f5d..d17c0c9f3e 100644 --- a/packages/client/src/components/chart.vue +++ b/packages/client/src/components/chart.vue @@ -143,6 +143,7 @@ export default defineComponent({ } const gridColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; + const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; // フォントカラー Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg'); @@ -255,6 +256,27 @@ export default defineComponent({ }, }, }, + plugins: [{ + id: 'vLine', + beforeDraw(chart, args, options) { + if (chart.tooltip._active && chart.tooltip._active.length) { + const activePoint = chart.tooltip._active[0]; + const ctx = chart.ctx; + const x = activePoint.element.x; + const topY = chart.scales.y.top; + const bottomY = chart.scales.y.bottom; + + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x, bottomY); + ctx.lineTo(x, topY); + ctx.lineWidth = 1; + ctx.strokeStyle = vLineColor; + ctx.stroke(); + ctx.restore(); + } + } + }] }); }; diff --git a/packages/client/src/components/cw-button.vue b/packages/client/src/components/cw-button.vue index ccfd11462a..e7c9aabe4e 100644 --- a/packages/client/src/components/cw-button.vue +++ b/packages/client/src/components/cw-button.vue @@ -1,6 +1,6 @@ <template> <button class="nrvgflfu _button" @click="toggle"> - <b>{{ modelValue ? i18n.locale._cw.hide : i18n.locale._cw.show }}</b> + <b>{{ modelValue ? i18n.ts._cw.hide : i18n.ts._cw.show }}</b> <span v-if="!modelValue">{{ label }}</span> </button> </template> @@ -25,7 +25,7 @@ const label = computed(() => { return concat([ props.note.text ? [i18n.t('_cw.chars', { count: length(props.note.text) })] : [], props.note.files && props.note.files.length !== 0 ? [i18n.t('_cw.files', { count: props.note.files.length }) ] : [], - props.note.poll != null ? [i18n.locale.poll] : [] + props.note.poll != null ? [i18n.ts.poll] : [] ] as string[][]).join(' / '); }); diff --git a/packages/client/src/components/dialog.vue b/packages/client/src/components/dialog.vue index b6b649cde9..3e106a4f0c 100644 --- a/packages/client/src/components/dialog.vue +++ b/packages/client/src/components/dialog.vue @@ -28,8 +28,8 @@ </template> </MkSelect> <div v-if="(showOkButton || showCancelButton) && !actions" class="buttons"> - <MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" @click="ok">{{ (showCancelButton || input || select) ? i18n.locale.ok : i18n.locale.gotIt }}</MkButton> - <MkButton v-if="showCancelButton || input || select" inline @click="cancel">{{ i18n.locale.cancel }}</MkButton> + <MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" @click="ok">{{ (showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt }}</MkButton> + <MkButton v-if="showCancelButton || input || select" inline @click="cancel">{{ i18n.ts.cancel }}</MkButton> </div> <div v-if="actions" class="buttons"> <MkButton v-for="action in actions" :key="action.text" inline :primary="action.primary" @click="() => { action.callback(); close(); }">{{ action.text }}</MkButton> diff --git a/packages/client/src/components/drive-select-dialog.vue b/packages/client/src/components/drive-select-dialog.vue index 6d84511277..f6c59457d1 100644 --- a/packages/client/src/components/drive-select-dialog.vue +++ b/packages/client/src/components/drive-select-dialog.vue @@ -10,7 +10,7 @@ @closed="emit('closed')" > <template #header> - {{ multiple ? ((type === 'file') ? i18n.locale.selectFiles : i18n.locale.selectFolders) : ((type === 'file') ? i18n.locale.selectFile : i18n.locale.selectFolder) }} + {{ multiple ? ((type === 'file') ? i18n.ts.selectFiles : i18n.ts.selectFolders) : ((type === 'file') ? i18n.ts.selectFile : i18n.ts.selectFolder) }} <span v-if="selected.length > 0" style="margin-left: 8px; opacity: 0.5;">({{ number(selected.length) }})</span> </template> <XDrive :multiple="multiple" :select="type" @changeSelection="onChangeSelection" @selected="ok()"/> diff --git a/packages/client/src/components/drive-window.vue b/packages/client/src/components/drive-window.vue index 8b60bf7794..d08c5fb674 100644 --- a/packages/client/src/components/drive-window.vue +++ b/packages/client/src/components/drive-window.vue @@ -6,7 +6,7 @@ @closed="emit('closed')" > <template #header> - {{ i18n.locale.drive }} + {{ i18n.ts.drive }} </template> <XDrive :initial-folder="initialFolder"/> </XWindow> diff --git a/packages/client/src/components/drive.file.vue b/packages/client/src/components/drive.file.vue index fd6a813838..262eae0de1 100644 --- a/packages/client/src/components/drive.file.vue +++ b/packages/client/src/components/drive.file.vue @@ -10,15 +10,15 @@ > <div v-if="$i?.avatarId == file.id" class="label"> <img src="/client-assets/label.svg"/> - <p>{{ i18n.locale.avatar }}</p> + <p>{{ i18n.ts.avatar }}</p> </div> <div v-if="$i?.bannerId == file.id" class="label"> <img src="/client-assets/label.svg"/> - <p>{{ i18n.locale.banner }}</p> + <p>{{ i18n.ts.banner }}</p> </div> <div v-if="file.isSensitive" class="label red"> <img src="/client-assets/label-red.svg"/> - <p>{{ i18n.locale.nsfw }}</p> + <p>{{ i18n.ts.nsfw }}</p> </div> <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/> @@ -61,30 +61,30 @@ const title = computed(() => `${props.file.name}\n${props.file.type} ${bytes(pro function getMenu() { return [{ - text: i18n.locale.rename, + text: i18n.ts.rename, icon: 'fas fa-i-cursor', action: rename }, { - text: props.file.isSensitive ? i18n.locale.unmarkAsSensitive : i18n.locale.markAsSensitive, + text: props.file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, icon: props.file.isSensitive ? 'fas fa-eye' : 'fas fa-eye-slash', action: toggleSensitive }, { - text: i18n.locale.describeFile, + text: i18n.ts.describeFile, icon: 'fas fa-i-cursor', action: describe }, null, { - text: i18n.locale.copyUrl, + text: i18n.ts.copyUrl, icon: 'fas fa-link', action: copyUrl }, { type: 'a', href: props.file.url, target: '_blank', - text: i18n.locale.download, + text: i18n.ts.download, icon: 'fas fa-download', download: props.file.name }, null, { - text: i18n.locale.delete, + text: i18n.ts.delete, icon: 'fas fa-trash-alt', danger: true, action: deleteFile @@ -95,7 +95,7 @@ function onClick(ev: MouseEvent) { if (props.selectMode) { emit('chosen', props.file); } else { - os.popupMenu(getMenu(), (ev.currentTarget || ev.target || undefined) as HTMLElement | undefined); + os.popupMenu(getMenu(), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); } } @@ -120,8 +120,8 @@ function onDragend() { function rename() { os.inputText({ - title: i18n.locale.renameFile, - placeholder: i18n.locale.inputNewFileName, + title: i18n.ts.renameFile, + placeholder: i18n.ts.inputNewFileName, default: props.file.name, }).then(({ canceled, result: name }) => { if (canceled) return; @@ -134,9 +134,9 @@ function rename() { function describe() { os.popup(import('@/components/media-caption.vue'), { - title: i18n.locale.describeFile, + title: i18n.ts.describeFile, input: { - placeholder: i18n.locale.inputNewDescription, + placeholder: i18n.ts.inputNewDescription, default: props.file.comment !== null ? props.file.comment : '', }, image: props.file diff --git a/packages/client/src/components/drive.folder.vue b/packages/client/src/components/drive.folder.vue index 20a6343cfe..57621bf097 100644 --- a/packages/client/src/components/drive.folder.vue +++ b/packages/client/src/components/drive.folder.vue @@ -20,7 +20,7 @@ {{ folder.name }} </p> <p v-if="defaultStore.state.uploadFolder == folder.id" class="upload"> - {{ i18n.locale.uploadFolder }} + {{ i18n.ts.uploadFolder }} </p> <button v-if="selectMode" class="checkbox _button" :class="{ checked: isSelected }" @click.prevent.stop="checkboxClicked"></button> </div> @@ -146,14 +146,14 @@ function onDrop(ev: DragEvent) { switch (err) { case 'detected-circular-definition': os.alert({ - title: i18n.locale.unableToProcess, - text: i18n.locale.circularReferenceFolder + title: i18n.ts.unableToProcess, + text: i18n.ts.circularReferenceFolder }); break; default: os.alert({ type: 'error', - text: i18n.locale.somethingHappened + text: i18n.ts.somethingHappened }); } }); @@ -184,8 +184,8 @@ function go() { function rename() { os.inputText({ - title: i18n.locale.renameFolder, - placeholder: i18n.locale.inputNewFolderName, + title: i18n.ts.renameFolder, + placeholder: i18n.ts.inputNewFolderName, default: props.folder.name }).then(({ canceled, result: name }) => { if (canceled) return; @@ -208,14 +208,14 @@ function deleteFolder() { case 'b0fc8a17-963c-405d-bfbc-859a487295e1': os.alert({ type: 'error', - title: i18n.locale.unableToDelete, - text: i18n.locale.hasChildFilesOrFolders + title: i18n.ts.unableToDelete, + text: i18n.ts.hasChildFilesOrFolders }); break; default: os.alert({ type: 'error', - text: i18n.locale.unableToDelete + text: i18n.ts.unableToDelete }); } }); @@ -227,7 +227,7 @@ function setAsUploadFolder() { function onContextmenu(ev: MouseEvent) { os.contextMenu([{ - text: i18n.locale.openInWindow, + text: i18n.ts.openInWindow, icon: 'fas fa-window-restore', action: () => { os.popup(import('./drive-window.vue'), { @@ -236,11 +236,11 @@ function onContextmenu(ev: MouseEvent) { }, 'closed'); } }, null, { - text: i18n.locale.rename, + text: i18n.ts.rename, icon: 'fas fa-i-cursor', action: rename, }, null, { - text: i18n.locale.delete, + text: i18n.ts.delete, icon: 'fas fa-trash-alt', danger: true, action: deleteFolder, diff --git a/packages/client/src/components/drive.nav-folder.vue b/packages/client/src/components/drive.nav-folder.vue index 7c35c5d3da..67223267c1 100644 --- a/packages/client/src/components/drive.nav-folder.vue +++ b/packages/client/src/components/drive.nav-folder.vue @@ -8,7 +8,7 @@ @drop.stop="onDrop" > <i v-if="folder == null" class="fas fa-cloud"></i> - <span>{{ folder == null ? i18n.locale.drive : folder.name }}</span> + <span>{{ folder == null ? i18n.ts.drive : folder.name }}</span> </div> </template> diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue index e27b0a5fbb..e044c67523 100644 --- a/packages/client/src/components/drive.vue +++ b/packages/client/src/components/drive.vue @@ -54,7 +54,7 @@ /> <!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid --> <div v-for="(n, i) in 16" :key="i" class="padding"></div> - <MkButton v-if="moreFolders" ref="moreFolders">{{ i18n.locale.loadMore }}</MkButton> + <MkButton v-if="moreFolders" ref="moreFolders">{{ i18n.ts.loadMore }}</MkButton> </div> <div v-show="files.length > 0" ref="filesContainer" class="files"> <XFile @@ -71,12 +71,12 @@ /> <!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid --> <div v-for="(n, i) in 16" :key="i" class="padding"></div> - <MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ i18n.locale.loadMore }}</MkButton> + <MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ i18n.ts.loadMore }}</MkButton> </div> <div v-if="files.length == 0 && folders.length == 0 && !fetching" class="empty"> <p v-if="draghover">{{ i18n.t('empty-draghover') }}</p> - <p v-if="!draghover && folder == null"><strong>{{ i18n.locale.emptyDrive }}</strong><br/>{{ i18n.t('empty-drive-description') }}</p> - <p v-if="!draghover && folder != null">{{ i18n.locale.emptyFolder }}</p> + <p v-if="!draghover && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong><br/>{{ i18n.t('empty-drive-description') }}</p> + <p v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</p> </div> </div> <MkLoading v-if="fetching"/> @@ -253,14 +253,14 @@ function onDrop(e: DragEvent): any { switch (err) { case 'detected-circular-definition': os.alert({ - title: i18n.locale.unableToProcess, - text: i18n.locale.circularReferenceFolder + title: i18n.ts.unableToProcess, + text: i18n.ts.circularReferenceFolder }); break; default: os.alert({ type: 'error', - text: i18n.locale.somethingHappened + text: i18n.ts.somethingHappened }); } }); @@ -274,9 +274,9 @@ function selectLocalFile() { function urlUpload() { os.inputText({ - title: i18n.locale.uploadFromUrl, + title: i18n.ts.uploadFromUrl, type: 'url', - placeholder: i18n.locale.uploadFromUrlDescription + placeholder: i18n.ts.uploadFromUrlDescription }).then(({ canceled, result: url }) => { if (canceled || !url) return; os.api('drive/files/upload-from-url', { @@ -285,16 +285,16 @@ function urlUpload() { }); os.alert({ - title: i18n.locale.uploadFromUrlRequested, - text: i18n.locale.uploadFromUrlMayTakeTime + title: i18n.ts.uploadFromUrlRequested, + text: i18n.ts.uploadFromUrlMayTakeTime }); }); } function createFolder() { os.inputText({ - title: i18n.locale.createFolder, - placeholder: i18n.locale.folderName + title: i18n.ts.createFolder, + placeholder: i18n.ts.folderName }).then(({ canceled, result: name }) => { if (canceled) return; os.api('drive/folders/create', { @@ -308,8 +308,8 @@ function createFolder() { function renameFolder(folderToRename: Misskey.entities.DriveFolder) { os.inputText({ - title: i18n.locale.renameFolder, - placeholder: i18n.locale.inputNewFolderName, + title: i18n.ts.renameFolder, + placeholder: i18n.ts.inputNewFolderName, default: folderToRename.name }).then(({ canceled, result: name }) => { if (canceled) return; @@ -334,14 +334,14 @@ function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) { case 'b0fc8a17-963c-405d-bfbc-859a487295e1': os.alert({ type: 'error', - title: i18n.locale.unableToDelete, - text: i18n.locale.hasChildFilesOrFolders + title: i18n.ts.unableToDelete, + text: i18n.ts.hasChildFilesOrFolders }); break; default: os.alert({ type: 'error', - text: i18n.locale.unableToDelete + text: i18n.ts.unableToDelete }); } }); @@ -562,36 +562,36 @@ function fetchMoreFiles() { function getMenu() { return [{ - text: i18n.locale.addFile, + text: i18n.ts.addFile, type: 'label' }, { - text: i18n.locale.upload, + text: i18n.ts.upload, icon: 'fas fa-upload', action: () => { selectLocalFile(); } }, { - text: i18n.locale.fromUrl, + text: i18n.ts.fromUrl, icon: 'fas fa-link', action: () => { urlUpload(); } }, null, { - text: folder.value ? folder.value.name : i18n.locale.drive, + text: folder.value ? folder.value.name : i18n.ts.drive, type: 'label' }, folder.value ? { - text: i18n.locale.renameFolder, + text: i18n.ts.renameFolder, icon: 'fas fa-i-cursor', action: () => { renameFolder(folder.value); } } : undefined, folder.value ? { - text: i18n.locale.deleteFolder, + text: i18n.ts.deleteFolder, icon: 'fas fa-trash-alt', action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); } } : undefined, { - text: i18n.locale.createFolder, + text: i18n.ts.createFolder, icon: 'fas fa-folder-plus', action: () => { createFolder(); } }]; } function showMenu(ev: MouseEvent) { - os.popupMenu(getMenu(), (ev.currentTarget || ev.target || undefined) as HTMLElement | undefined); + os.popupMenu(getMenu(), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); } function onContextmenu(ev: MouseEvent) { diff --git a/packages/client/src/components/emoji-picker-dialog.vue b/packages/client/src/components/emoji-picker-dialog.vue index f06a24636c..2c0b2e9a8b 100644 --- a/packages/client/src/components/emoji-picker-dialog.vue +++ b/packages/client/src/components/emoji-picker-dialog.vue @@ -32,20 +32,20 @@ import MkEmojiPicker from '@/components/emoji-picker.vue'; import { defaultStore } from '@/store'; withDefaults(defineProps<{ - manualShowing?: boolean; + manualShowing?: boolean | null; src?: HTMLElement; showPinned?: boolean; asReactionPicker?: boolean; }>(), { - manualShowing: false, + manualShowing: null, showPinned: true, asReactionPicker: false, }); const emit = defineEmits<{ - (e: 'done', v: any): void; - (e: 'close'): void; - (e: 'closed'): void; + (ev: 'done', v: any): void; + (ev: 'close'): void; + (ev: 'closed'): void; }>(); const modal = ref<InstanceType<typeof MkModal>>(); diff --git a/packages/client/src/components/emoji-picker.vue b/packages/client/src/components/emoji-picker.vue index 96670fa58c..6999ad6517 100644 --- a/packages/client/src/components/emoji-picker.vue +++ b/packages/client/src/components/emoji-picker.vue @@ -1,6 +1,6 @@ <template> <div class="omfetrab" :class="['w' + width, 'h' + height, { big, asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> - <input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.locale.search" @paste.stop="paste" @keyup.enter="done()"> + <input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" @paste.stop="paste" @keyup.enter="done()"> <div ref="emojis" class="emojis"> <section class="result"> <div v-if="searchResultCustom.length > 0"> @@ -43,7 +43,7 @@ </section> <section> - <header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ i18n.locale.recentUsed }}</header> + <header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ i18n.ts.recentUsed }}</header> <div> <button v-for="emoji in recentlyUsedEmojis" :key="emoji" @@ -56,11 +56,11 @@ </section> </div> <div> - <header class="_acrylic">{{ i18n.locale.customEmojis }}</header> - <XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')" @chosen="chosen">{{ category || i18n.locale.other }}</XSection> + <header class="_acrylic">{{ i18n.ts.customEmojis }}</header> + <XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')" @chosen="chosen">{{ category || i18n.ts.other }}</XSection> </div> <div> - <header class="_acrylic">{{ i18n.locale.emoji }}</header> + <header class="_acrylic">{{ i18n.ts.emoji }}</header> <XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection> </div> </div> @@ -280,7 +280,7 @@ function getKey(emoji: string | Misskey.entities.CustomEmoji | UnicodeEmojiDef): } function chosen(emoji: any, ev?: MouseEvent) { - const el = ev && (ev.currentTarget || ev.target) as HTMLElement | null | undefined; + const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined; if (el) { const rect = el.getBoundingClientRect(); const x = rect.left + (el.offsetWidth / 2); diff --git a/packages/client/src/components/follow-button.vue b/packages/client/src/components/follow-button.vue index 345edb6441..93c9e891c1 100644 --- a/packages/client/src/components/follow-button.vue +++ b/packages/client/src/components/follow-button.vue @@ -6,23 +6,23 @@ > <template v-if="!wait"> <template v-if="hasPendingFollowRequestFromYou && user.isLocked"> - <span v-if="full">{{ i18n.locale.followRequestPending }}</span><i class="fas fa-hourglass-half"></i> + <span v-if="full">{{ i18n.ts.followRequestPending }}</span><i class="fas fa-hourglass-half"></i> </template> <template v-else-if="hasPendingFollowRequestFromYou && !user.isLocked"> <!-- つまりリモートフォローの場合。 --> - <span v-if="full">{{ i18n.locale.processing }}</span><i class="fas fa-spinner fa-pulse"></i> + <span v-if="full">{{ i18n.ts.processing }}</span><i class="fas fa-spinner fa-pulse"></i> </template> <template v-else-if="isFollowing"> - <span v-if="full">{{ i18n.locale.unfollow }}</span><i class="fas fa-minus"></i> + <span v-if="full">{{ i18n.ts.unfollow }}</span><i class="fas fa-minus"></i> </template> <template v-else-if="!isFollowing && user.isLocked"> - <span v-if="full">{{ i18n.locale.followRequest }}</span><i class="fas fa-plus"></i> + <span v-if="full">{{ i18n.ts.followRequest }}</span><i class="fas fa-plus"></i> </template> <template v-else-if="!isFollowing && !user.isLocked"> - <span v-if="full">{{ i18n.locale.follow }}</span><i class="fas fa-plus"></i> + <span v-if="full">{{ i18n.ts.follow }}</span><i class="fas fa-plus"></i> </template> </template> <template v-else> - <span v-if="full">{{ i18n.locale.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> + <span v-if="full">{{ i18n.ts.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> </template> </button> </template> diff --git a/packages/client/src/components/forgot-password.vue b/packages/client/src/components/forgot-password.vue index c74e1ac75e..46cbf6bd70 100644 --- a/packages/client/src/components/forgot-password.vue +++ b/packages/client/src/components/forgot-password.vue @@ -5,28 +5,28 @@ @close="dialog.close()" @closed="emit('closed')" > - <template #header>{{ i18n.locale.forgotPassword }}</template> + <template #header>{{ i18n.ts.forgotPassword }}</template> <form v-if="instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit"> <div class="main _formRoot"> <MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required> - <template #label>{{ i18n.locale.username }}</template> + <template #label>{{ i18n.ts.username }}</template> <template #prefix>@</template> </MkInput> <MkInput v-model="email" class="_formBlock" type="email" spellcheck="false" required> - <template #label>{{ i18n.locale.emailAddress }}</template> - <template #caption>{{ i18n.locale._forgotPassword.enterEmail }}</template> + <template #label>{{ i18n.ts.emailAddress }}</template> + <template #caption>{{ i18n.ts._forgotPassword.enterEmail }}</template> </MkInput> - <MkButton class="_formBlock" type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ i18n.locale.send }}</MkButton> + <MkButton class="_formBlock" type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ i18n.ts.send }}</MkButton> </div> <div class="sub"> - <MkA to="/about" class="_link">{{ i18n.locale._forgotPassword.ifNoEmail }}</MkA> + <MkA to="/about" class="_link">{{ i18n.ts._forgotPassword.ifNoEmail }}</MkA> </div> </form> <div v-else class="bafecedb"> - {{ i18n.locale._forgotPassword.contactAdmin }} + {{ i18n.ts._forgotPassword.contactAdmin }} </div> </XModalWindow> </template> diff --git a/packages/client/src/components/form/suspense.vue b/packages/client/src/components/form/suspense.vue index 4d5debe604..2ad55dacae 100644 --- a/packages/client/src/components/form/suspense.vue +++ b/packages/client/src/components/form/suspense.vue @@ -1,5 +1,5 @@ <template> -<transition name="fade" mode="out-in"> +<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="pending"> <MkLoading/> </div> diff --git a/packages/client/src/components/global/a.vue b/packages/client/src/components/global/a.vue index cf7385ca22..b1b6a0cdaf 100644 --- a/packages/client/src/components/global/a.vue +++ b/packages/client/src/components/global/a.vue @@ -43,31 +43,31 @@ function onContextmenu(ev) { text: props.to, }, { icon: 'fas fa-window-maximize', - text: i18n.locale.openInWindow, + text: i18n.ts.openInWindow, action: () => { os.pageWindow(props.to); } }, sideViewHook ? { icon: 'fas fa-columns', - text: i18n.locale.openInSideView, + text: i18n.ts.openInSideView, action: () => { sideViewHook(props.to); } } : undefined, { icon: 'fas fa-expand-alt', - text: i18n.locale.showInPage, + text: i18n.ts.showInPage, action: () => { router.push(props.to); } }, null, { icon: 'fas fa-external-link-alt', - text: i18n.locale.openInNewTab, + text: i18n.ts.openInNewTab, action: () => { window.open(props.to, '_blank'); } }, { icon: 'fas fa-link', - text: i18n.locale.copyLink, + text: i18n.ts.copyLink, action: () => { copyToClipboard(`${url}${props.to}`); } diff --git a/packages/client/src/components/global/header.vue b/packages/client/src/components/global/header.vue index a241ece407..e558614c12 100644 --- a/packages/client/src/components/global/header.vue +++ b/packages/client/src/components/global/header.vue @@ -104,7 +104,7 @@ export default defineComponent({ if (props.info.share) { if (menu.length > 0) menu.push(null); menu.push({ - text: i18n.locale.share, + text: i18n.ts.share, icon: 'fas fa-share-alt', action: share }); @@ -113,7 +113,7 @@ export default defineComponent({ if (menu.length > 0) menu.push(null); menu = menu.concat(props.menu); } - popupMenu(menu, ev.currentTarget || ev.target); + popupMenu(menu, ev.currentTarget ?? ev.target); }; const showTabsPopup = (ev: MouseEvent) => { @@ -126,7 +126,7 @@ export default defineComponent({ icon: tab.icon, action: tab.onClick, })); - popupMenu(menu, ev.currentTarget || ev.target); + popupMenu(menu, ev.currentTarget ?? ev.target); }; const preventDrag = (ev: TouchEvent) => { diff --git a/packages/client/src/components/global/time.vue b/packages/client/src/components/global/time.vue index d2788264c5..5748d9de61 100644 --- a/packages/client/src/components/global/time.vue +++ b/packages/client/src/components/global/time.vue @@ -24,16 +24,16 @@ let now = $ref(new Date()); const relative = $computed(() => { const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/; return ( - ago >= 31536000 ? i18n.t('_ago.yearsAgo', { n: (~~(ago / 31536000)).toString() }) : - ago >= 2592000 ? i18n.t('_ago.monthsAgo', { n: (~~(ago / 2592000)).toString() }) : - ago >= 604800 ? i18n.t('_ago.weeksAgo', { n: (~~(ago / 604800)).toString() }) : - ago >= 86400 ? i18n.t('_ago.daysAgo', { n: (~~(ago / 86400)).toString() }) : - ago >= 3600 ? i18n.t('_ago.hoursAgo', { n: (~~(ago / 3600)).toString() }) : + ago >= 31536000 ? i18n.t('_ago.yearsAgo', { n: Math.round(ago / 31536000).toString() }) : + ago >= 2592000 ? i18n.t('_ago.monthsAgo', { n: Math.round(ago / 2592000).toString() }) : + ago >= 604800 ? i18n.t('_ago.weeksAgo', { n: Math.round(ago / 604800).toString() }) : + ago >= 86400 ? i18n.t('_ago.daysAgo', { n: Math.round(ago / 86400).toString() }) : + ago >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago / 3600).toString() }) : ago >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : ago >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : - ago >= -1 ? i18n.locale._ago.justNow : - ago < -1 ? i18n.locale._ago.future : - i18n.locale._ago.unknown); + ago >= -1 ? i18n.ts._ago.justNow : + ago < -1 ? i18n.ts._ago.future : + i18n.ts._ago.unknown); }); function tick() { diff --git a/packages/client/src/components/media-image.vue b/packages/client/src/components/media-image.vue index 3e2cabae0a..43639f6771 100644 --- a/packages/client/src/components/media-image.vue +++ b/packages/client/src/components/media-image.vue @@ -20,52 +20,32 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { watch } from 'vue'; +import * as misskey from 'misskey-js'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import ImgWithBlurhash from '@/components/img-with-blurhash.vue'; -import * as os from '@/os'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - ImgWithBlurhash - }, - props: { - image: { - type: Object, - required: true - }, - raw: { - default: false - } - }, - data() { - return { - hide: true, - }; - }, - computed: { - url(): any { - let url = this.$store.state.disableShowingAnimatedImages - ? getStaticImageUrl(this.image.thumbnailUrl) - : this.image.thumbnailUrl; +const props = defineProps<{ + image: misskey.entities.DriveFile; + raw?: boolean; +}>(); - if (this.raw || this.$store.state.loadRawImages) { - url = this.image.url; - } +let hide = $ref(true); - return url; - } - }, - created() { - // Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする - this.$watch('image', () => { - this.hide = (this.$store.state.nsfw === 'force') ? true : this.image.isSensitive && (this.$store.state.nsfw !== 'ignore'); - }, { - deep: true, - immediate: true, - }); - }, +const url = (props.raw || defaultStore.state.loadRawImages) + ? props.image.url + : defaultStore.state.disableShowingAnimatedImages + ? getStaticImageUrl(props.image.thumbnailUrl) + : props.image.thumbnailUrl; + +// Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする +watch(() => props.image, () => { + hide = (defaultStore.state.nsfw === 'force') ? true : props.image.isSensitive && (defaultStore.state.nsfw !== 'ignore'); +}, { + deep: true, + immediate: true, }); </script> diff --git a/packages/client/src/components/media-list.vue b/packages/client/src/components/media-list.vue index 2970d06c97..532627edbd 100644 --- a/packages/client/src/components/media-list.vue +++ b/packages/client/src/components/media-list.vue @@ -3,7 +3,7 @@ <XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/> <div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container"> <div ref="gallery" :data-count="mediaList.filter(media => previewable(media)).length"> - <template v-for="media in mediaList"> + <template v-for="media in mediaList.filter(media => previewable(media))"> <XVideo v-if="media.type.startsWith('video')" :key="media.id" :video="media"/> <XImage v-else-if="media.type.startsWith('image')" :key="media.id" class="image" :data-id="media.id" :image="media" :raw="raw"/> </template> @@ -12,8 +12,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent, onMounted, PropType, ref } from 'vue'; +<script lang="ts" setup> +import { onMounted, ref } from 'vue'; import * as misskey from 'misskey-js'; import PhotoSwipeLightbox from 'photoswipe/dist/photoswipe-lightbox.esm.js'; import PhotoSwipe from 'photoswipe/dist/photoswipe.esm.js'; @@ -22,93 +22,83 @@ import XBanner from './media-banner.vue'; import XImage from './media-image.vue'; import XVideo from './media-video.vue'; import * as os from '@/os'; +import { FILE_TYPE_BROWSERSAFE } from '@/const'; import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - XBanner, - XImage, - XVideo, - }, - props: { - mediaList: { - type: Array as PropType<misskey.entities.DriveFile[]>, - required: true, - }, - raw: { - default: false - }, - }, - setup(props) { - const gallery = ref(null); +const props = defineProps<{ + mediaList: misskey.entities.DriveFile[]; + raw?: boolean; +}>(); - onMounted(() => { - const lightbox = new PhotoSwipeLightbox({ - dataSource: props.mediaList.filter(media => media.type.startsWith('image')).map(media => { - const item = { - src: media.url, - w: media.properties.width, - h: media.properties.height, - alt: media.name, - }; - if (media.properties.orientation != null && media.properties.orientation >= 5) { - [item.w, item.h] = [item.h, item.w]; - } - return item; - }), - gallery: gallery.value, - children: '.image', - thumbSelector: '.image', - loop: false, - padding: window.innerWidth > 500 ? { - top: 32, - bottom: 32, - left: 32, - right: 32, - } : { - top: 0, - bottom: 0, - left: 0, - right: 0, - }, - imageClickAction: 'close', - tapAction: 'toggle-controls', - pswpModule: PhotoSwipe, - }); +const gallery = ref(null); +const pswpZIndex = os.claimZIndex('middle'); - lightbox.on('itemData', (e) => { - const { itemData } = e; - - // element is children - const { element } = itemData; +onMounted(() => { + const lightbox = new PhotoSwipeLightbox({ + dataSource: props.mediaList + .filter(media => { + if (media.type === 'image/svg+xml') return true; // svgのwebpublicはpngなのでtrue + return media.type.startsWith('image') && FILE_TYPE_BROWSERSAFE.includes(media.type); + }) + .map(media => { + const item = { + src: media.url, + w: media.properties.width, + h: media.properties.height, + alt: media.name, + }; + if (media.properties.orientation != null && media.properties.orientation >= 5) { + [item.w, item.h] = [item.h, item.w]; + } + return item; + }), + gallery: gallery.value, + children: '.image', + thumbSelector: '.image', + loop: false, + padding: window.innerWidth > 500 ? { + top: 32, + bottom: 32, + left: 32, + right: 32, + } : { + top: 0, + bottom: 0, + left: 0, + right: 0, + }, + imageClickAction: 'close', + tapAction: 'toggle-controls', + pswpModule: PhotoSwipe, + }); - const id = element.dataset.id; - const file = props.mediaList.find(media => media.id === id); + lightbox.on('itemData', (ev) => { + const { itemData } = ev; - itemData.src = file.url; - itemData.w = Number(file.properties.width); - itemData.h = Number(file.properties.height); - if (file.properties.orientation != null && file.properties.orientation >= 5) { - [itemData.w, itemData.h] = [itemData.h, itemData.w]; - } - itemData.msrc = file.thumbnailUrl; - itemData.thumbCropped = true; - }); + // element is children + const { element } = itemData; - lightbox.init(); - }); + const id = element.dataset.id; + const file = props.mediaList.find(media => media.id === id); - const previewable = (file: misskey.entities.DriveFile): boolean => { - return file.type.startsWith('video') || file.type.startsWith('image'); - }; + itemData.src = file.url; + itemData.w = Number(file.properties.width); + itemData.h = Number(file.properties.height); + if (file.properties.orientation != null && file.properties.orientation >= 5) { + [itemData.w, itemData.h] = [itemData.h, itemData.w]; + } + itemData.msrc = file.thumbnailUrl; + itemData.thumbCropped = true; + }); - return { - previewable, - gallery, - pswpZIndex: os.claimZIndex('middle'), - }; - }, + lightbox.init(); }); + +const previewable = (file: misskey.entities.DriveFile): boolean => { + if (file.type === 'image/svg+xml') return true; // svgのwebpublic/thumbnailはpngなのでtrue + // FILE_TYPE_BROWSERSAFEに適合しないものはブラウザで表示するのに不適切 + return (file.type.startsWith('video') || file.type.startsWith('image')) && FILE_TYPE_BROWSERSAFE.includes(file.type); +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue index a3b30f726e..5fc3a0f334 100644 --- a/packages/client/src/components/note-detailed.vue +++ b/packages/client/src/components/note-detailed.vue @@ -250,7 +250,7 @@ function menu(viaKeyboard = false): void { function showRenoteMenu(viaKeyboard = false): void { if (!isMyRenote) return; os.popupMenu([{ - text: i18n.locale.unrenote, + text: i18n.ts.unrenote, icon: 'fas fa-trash-alt', danger: true, action: () => { diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue index fc89c2777b..6c596fb60d 100644 --- a/packages/client/src/components/note.vue +++ b/packages/client/src/components/note.vue @@ -10,13 +10,13 @@ :class="{ renote: isRenote }" > <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> - <div v-if="pinned" class="info"><i class="fas fa-thumbtack"></i> {{ i18n.locale.pinnedNote }}</div> - <div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ i18n.locale.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.locale.hideThisNote }} <i class="fas fa-times"></i></button></div> - <div v-if="appearNote._featuredId_" class="info"><i class="fas fa-bolt"></i> {{ i18n.locale.featured }}</div> + <div v-if="pinned" class="info"><i class="fas fa-thumbtack"></i> {{ i18n.ts.pinnedNote }}</div> + <div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="fas fa-times"></i></button></div> + <div v-if="appearNote._featuredId_" class="info"><i class="fas fa-bolt"></i> {{ i18n.ts.featured }}</div> <div v-if="isRenote" class="renote"> <MkAvatar class="avatar" :user="note.user"/> <i class="fas fa-retweet"></i> - <I18n :src="i18n.locale.renotedBy" tag="span"> + <I18n :src="i18n.ts.renotedBy" tag="span"> <template #user> <MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)"> <MkUserName :user="note.user"/> @@ -48,7 +48,7 @@ </p> <div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed }"> <div class="text"> - <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.locale.private }})</span> + <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> <MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA> <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> <a v-if="appearNote.renote != null" class="rp">RN:</a> @@ -67,7 +67,7 @@ <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/> <div v-if="appearNote.renote" class="renote"><XNoteSimple :note="appearNote.renote"/></div> <button v-if="collapsed" class="fade _button" @click="collapsed = false"> - <span>{{ i18n.locale.showMore }}</span> + <span>{{ i18n.ts.showMore }}</span> </button> </div> <MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="fas fa-satellite-dish"></i> {{ appearNote.channel.name }}</MkA> @@ -94,7 +94,7 @@ </article> </div> <div v-else class="muted" @click="muted = false"> - <I18n :src="i18n.locale.userSaysSomething" tag="small"> + <I18n :src="i18n.ts.userSaysSomething" tag="small"> <template #name> <MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)"> <MkUserName :user="appearNote.user"/> @@ -238,7 +238,7 @@ function menu(viaKeyboard = false): void { function showRenoteMenu(viaKeyboard = false): void { if (!isMyRenote) return; os.popupMenu([{ - text: i18n.locale.unrenote, + text: i18n.ts.unrenote, icon: 'fas fa-trash-alt', danger: true, action: () => { diff --git a/packages/client/src/components/notification-toast.vue b/packages/client/src/components/notification-toast.vue index fbd8467a6e..b2ab1029ad 100644 --- a/packages/client/src/components/notification-toast.vue +++ b/packages/client/src/components/notification-toast.vue @@ -1,6 +1,6 @@ <template> <div class="mk-notification-toast" :style="{ zIndex }"> - <transition name="notification-toast" appear @after-leave="$emit('closed')"> + <transition :name="$store.state.animation ? 'notification-toast' : ''" appear @after-leave="$emit('closed')"> <XNotification v-if="showing" :notification="notification" class="notification _acrylic"/> </transition> </div> diff --git a/packages/client/src/components/page-window.vue b/packages/client/src/components/page-window.vue index ec7451d5aa..7455236bad 100644 --- a/packages/client/src/components/page-window.vue +++ b/packages/client/src/components/page-window.vue @@ -160,7 +160,7 @@ export default defineComponent({ action: () => { copyToClipboard(this.url); } - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); }, back() { diff --git a/packages/client/src/components/poll-editor.vue b/packages/client/src/components/poll-editor.vue index fad0cf1593..6f3f23a2d3 100644 --- a/packages/client/src/components/poll-editor.vue +++ b/packages/client/src/components/poll-editor.vue @@ -3,7 +3,7 @@ <p v-if="choices.length < 2" class="caution"> <i class="fas fa-exclamation-triangle"></i>{{ $ts._poll.noOnlyOneChoice }} </p> - <ul ref="choices"> + <ul> <li v-for="(choice, i) in choices" :key="i"> <MkInput class="input" :model-value="choice" :placeholder="$t('_poll.choiceN', { n: i + 1 })" @update:modelValue="onInput(i, $event)"> </MkInput> @@ -14,8 +14,8 @@ </ul> <MkButton v-if="choices.length < 10" class="add" @click="add">{{ $ts.add }}</MkButton> <MkButton v-else class="add" disabled>{{ $ts._poll.noMore }}</MkButton> + <MkSwitch v-model="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch> <section> - <MkSwitch v-model="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch> <div> <MkSelect v-model="expiration"> <template #label>{{ $ts._poll.expiration }}</template> @@ -31,7 +31,7 @@ <template #label>{{ $ts._poll.deadlineTime }}</template> </MkInput> </section> - <section v-if="expiration === 'after'"> + <section v-else-if="expiration === 'after'"> <MkInput v-model="after" type="number" class="input"> <template #label>{{ $ts._poll.duration }}</template> </MkInput> @@ -47,8 +47,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref, watch } from 'vue'; import { addTime } from '@/scripts/time'; import { formatDateTimeString } from '@/scripts/format-time-string'; import MkInput from './form/input.vue'; @@ -56,131 +56,91 @@ import MkSelect from './form/select.vue'; import MkSwitch from './form/switch.vue'; import MkButton from './ui/button.vue'; -export default defineComponent({ - components: { - MkInput, - MkSelect, - MkSwitch, - MkButton, - }, +const props = defineProps<{ + modelValue: { + expiresAt: string; + expiredAfter: number; + choices: string[]; + multiple: boolean; + }; +}>(); +const emit = defineEmits<{ + (ev: 'update:modelValue', v: { + expiresAt: string; + expiredAfter: number; + choices: string[]; + multiple: boolean; + }): void; +}>(); - props: { - poll: { - type: Object, - required: true - } - }, - - emits: ['updated'], - - data() { - return { - choices: this.poll.choices, - multiple: this.poll.multiple, - expiration: 'infinite', - atDate: formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'), - atTime: '00:00', - after: 0, - unit: 'second', - }; - }, +const choices = ref(props.modelValue.choices); +const multiple = ref(props.modelValue.multiple); +const expiration = ref('infinite'); +const atDate = ref(formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd')); +const atTime = ref('00:00'); +const after = ref(0); +const unit = ref('second'); - watch: { - choices: { - handler() { - this.$emit('updated', this.get()); - }, - deep: true - }, - multiple: { - handler() { - this.$emit('updated', this.get()); - }, - }, - expiration: { - handler() { - this.$emit('updated', this.get()); - }, - }, - atDate: { - handler() { - this.$emit('updated', this.get()); - }, - }, - after: { - handler() { - this.$emit('updated', this.get()); - }, - }, - unit: { - handler() { - this.$emit('updated', this.get()); - }, - }, - }, +if (props.modelValue.expiresAt) { + expiration.value = 'at'; + atDate.value = atTime.value = props.modelValue.expiresAt; +} else if (typeof props.modelValue.expiredAfter === 'number') { + expiration.value = 'after'; + after.value = props.modelValue.expiredAfter / 1000; +} else { + expiration.value = 'infinite'; +} - created() { - const poll = this.poll; - if (poll.expiresAt) { - this.expiration = 'at'; - this.atDate = this.atTime = poll.expiresAt; - } else if (typeof poll.expiredAfter === 'number') { - this.expiration = 'after'; - this.after = poll.expiredAfter / 1000; - } else { - this.expiration = 'infinite'; - } - }, +function onInput(i, value) { + choices.value[i] = value; +} - methods: { - onInput(i, e) { - this.choices[i] = e; - }, +function add() { + choices.value.push(''); + // TODO + // nextTick(() => { + // (this.$refs.choices as any).childNodes[this.choices.length - 1].childNodes[0].focus(); + // }); +} - add() { - this.choices.push(''); - this.$nextTick(() => { - // TODO - //(this.$refs.choices as any).childNodes[this.choices.length - 1].childNodes[0].focus(); - }); - }, +function remove(i) { + choices.value = choices.value.filter((_, _i) => _i != i); +} - remove(i) { - this.choices = this.choices.filter((_, _i) => _i != i); - }, +function get() { + const calcAt = () => { + return new Date(`${atDate.value} ${atTime.value}`).getTime(); + }; - get() { - const at = () => { - return new Date(`${this.atDate} ${this.atTime}`).getTime(); - }; + const calcAfter = () => { + let base = parseInt(after.value); + switch (unit.value) { + case 'day': base *= 24; + case 'hour': base *= 60; + case 'minute': base *= 60; + case 'second': return base *= 1000; + default: return null; + } + }; - const after = () => { - let base = parseInt(this.after); - switch (this.unit) { - case 'day': base *= 24; - case 'hour': base *= 60; - case 'minute': base *= 60; - case 'second': return base *= 1000; - default: return null; - } - }; + return { + choices: choices.value, + multiple: multiple.value, + ...( + expiration.value === 'at' ? { expiresAt: calcAt() } : + expiration.value === 'after' ? { expiredAfter: calcAfter() } : {} + ) + }; +} - return { - choices: this.choices, - multiple: this.multiple, - ...( - this.expiration === 'at' ? { expiresAt: at() } : - this.expiration === 'after' ? { expiredAfter: after() } : {} - ) - }; - }, - } +watch([choices, multiple, expiration, atDate, atTime, after, unit], () => emit('update:modelValue', get()), { + deep: true, }); </script> <style lang="scss" scoped> .zmdxowus { - padding: 8px; + padding: 8px 16px; > .caution { margin: 0 0 8px 0; @@ -216,7 +176,7 @@ export default defineComponent({ } > .add { - margin: 8px 0 0 0; + margin: 8px 0; z-index: 1; } @@ -225,21 +185,27 @@ export default defineComponent({ > div { margin: 0 8px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 12px; &:last-child { flex: 1 0 auto; + > div { + flex-grow: 1; + } + > section { - align-items: center; + // MAGIC: Prevent div above from growing unless wrapped to its own line + flex-grow: 9999; + align-items: end; display: flex; - margin: -32px 0 0; - - > &:first-child { - margin-right: 16px; - } + gap: 4px; > .input { - flex: 1 0 auto; + flex: 1 1 auto; } } } diff --git a/packages/client/src/components/post-form-attaches.vue b/packages/client/src/components/post-form-attaches.vue index 0c8181b481..9dd69a0ee5 100644 --- a/packages/client/src/components/post-form-attaches.vue +++ b/packages/client/src/components/post-form-attaches.vue @@ -127,7 +127,7 @@ export default defineComponent({ text: this.$ts.attachCancel, icon: 'fas fa-times-circle', action: () => { this.detachMedia(file.id) } - }], ev.currentTarget || ev.target).then(() => this.menu = null); + }], ev.currentTarget ?? ev.target).then(() => this.menu = null); } } }); diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue index 0dcec26932..8c5027f8e7 100644 --- a/packages/client/src/components/post-form.vue +++ b/packages/client/src/components/post-form.vue @@ -8,28 +8,28 @@ > <header> <button v-if="!fixed" class="cancel _button" @click="cancel"><i class="fas fa-times"></i></button> - <button v-click-anime v-tooltip="i18n.locale.switchAccount" class="account _button" @click="openAccountMenu"> + <button v-click-anime v-tooltip="i18n.ts.switchAccount" class="account _button" @click="openAccountMenu"> <MkAvatar :user="postAccount ?? $i" class="avatar"/> </button> <div> <span class="text-count" :class="{ over: textLength > maxTextLength }">{{ maxTextLength - textLength }}</span> <span v-if="localOnly" class="local-only"><i class="fas fa-biohazard"></i></span> - <button ref="visibilityButton" v-tooltip="i18n.locale.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> + <button ref="visibilityButton" v-tooltip="i18n.ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> <span v-if="visibility === 'public'"><i class="fas fa-globe"></i></span> <span v-if="visibility === 'home'"><i class="fas fa-home"></i></span> <span v-if="visibility === 'followers'"><i class="fas fa-unlock"></i></span> <span v-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span> </button> - <button v-tooltip="i18n.locale.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="fas fa-file-code"></i></button> + <button v-tooltip="i18n.ts.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="fas fa-file-code"></i></button> <button class="submit _buttonGradate" :disabled="!canPost" data-cy-open-post-form-submit @click="post">{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button> </div> </header> <div class="form" :class="{ fixed }"> <XNoteSimple v-if="reply" class="preview" :note="reply"/> <XNoteSimple v-if="renote" class="preview" :note="renote"/> - <div v-if="quoteId" class="with-quote"><i class="fas fa-quote-left"></i> {{ i18n.locale.quoteAttached }}<button @click="quoteId = null"><i class="fas fa-times"></i></button></div> + <div v-if="quoteId" class="with-quote"><i class="fas fa-quote-left"></i> {{ i18n.ts.quoteAttached }}<button @click="quoteId = null"><i class="fas fa-times"></i></button></div> <div v-if="visibility === 'specified'" class="to-specified"> - <span style="margin-right: 8px;">{{ i18n.locale.recipient }}</span> + <span style="margin-right: 8px;">{{ i18n.ts.recipient }}</span> <div class="visibleUsers"> <span v-for="u in visibleUsers" :key="u.id"> <MkAcct :user="u"/> @@ -38,21 +38,21 @@ <button class="_buttonPrimary" @click="addVisibleUser"><i class="fas fa-plus fa-fw"></i></button> </div> </div> - <MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ i18n.locale.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.locale.add }}</button></MkInfo> - <input v-show="useCw" ref="cwInputEl" v-model="cw" class="cw" :placeholder="i18n.locale.annotation" @keydown="onKeydown"> + <MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo> + <input v-show="useCw" ref="cwInputEl" v-model="cw" class="cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown"> <textarea ref="textareaEl" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> - <input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" class="hashtags" :placeholder="i18n.locale.hashtags" list="hashtags"> + <input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" class="hashtags" :placeholder="i18n.ts.hashtags" list="hashtags"> <XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> - <XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/> + <XPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/> <XNotePreview v-if="showPreview" class="preview" :text="text"/> <footer> - <button v-tooltip="i18n.locale.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button> - <button v-tooltip="i18n.locale.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="fas fa-poll-h"></i></button> - <button v-tooltip="i18n.locale.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="fas fa-eye-slash"></i></button> - <button v-tooltip="i18n.locale.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button> - <button v-tooltip="i18n.locale.hashtags" class="_button" :class="{ active: withHashtags }" @click="withHashtags = !withHashtags"><i class="fas fa-hashtag"></i></button> - <button v-tooltip="i18n.locale.emoji" class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button> - <button v-if="postFormActions.length > 0" v-tooltip="i18n.locale.plugin" class="_button" @click="showActions"><i class="fas fa-plug"></i></button> + <button v-tooltip="i18n.ts.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button> + <button v-tooltip="i18n.ts.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="fas fa-poll-h"></i></button> + <button v-tooltip="i18n.ts.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="fas fa-eye-slash"></i></button> + <button v-tooltip="i18n.ts.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button> + <button v-tooltip="i18n.ts.hashtags" class="_button" :class="{ active: withHashtags }" @click="withHashtags = !withHashtags"><i class="fas fa-hashtag"></i></button> + <button v-tooltip="i18n.ts.emoji" class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button> + <button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" @click="showActions"><i class="fas fa-plug"></i></button> </footer> <datalist id="hashtags"> <option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/> @@ -102,7 +102,7 @@ const props = withDefaults(defineProps<{ initialLocalOnly?: boolean; initialVisibleUsers?: misskey.entities.User[]; initialNote?: misskey.entities.Note; - share?: boolean; + instant?: boolean; fixed?: boolean; autofocus?: boolean; }>(), { @@ -111,9 +111,9 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'posted'): void; - (e: 'cancel'): void; - (e: 'esc'): void; + (ev: 'posted'): void; + (ev: 'cancel'): void; + (ev: 'esc'): void; }>(); const textareaEl = $ref<HTMLTextAreaElement | null>(null); @@ -127,8 +127,8 @@ let files = $ref(props.initialFiles ?? []); let poll = $ref<{ choices: string[]; multiple: boolean; - expiresAt: string; - expiredAfter: string; + expiresAt: string | null; + expiredAfter: string | null; } | null>(null); let useCw = $ref(false); let showPreview = $ref(false); @@ -165,19 +165,19 @@ const draftKey = $computed((): string => { const placeholder = $computed((): string => { if (props.renote) { - return i18n.locale._postForm.quotePlaceholder; + return i18n.ts._postForm.quotePlaceholder; } else if (props.reply) { - return i18n.locale._postForm.replyPlaceholder; + return i18n.ts._postForm.replyPlaceholder; } else if (props.channel) { - return i18n.locale._postForm.channelPlaceholder; + return i18n.ts._postForm.channelPlaceholder; } else { const xs = [ - i18n.locale._postForm._placeholders.a, - i18n.locale._postForm._placeholders.b, - i18n.locale._postForm._placeholders.c, - i18n.locale._postForm._placeholders.d, - i18n.locale._postForm._placeholders.e, - i18n.locale._postForm._placeholders.f + i18n.ts._postForm._placeholders.a, + i18n.ts._postForm._placeholders.b, + i18n.ts._postForm._placeholders.c, + i18n.ts._postForm._placeholders.d, + i18n.ts._postForm._placeholders.e, + i18n.ts._postForm._placeholders.f ]; return xs[Math.floor(Math.random() * xs.length)]; } @@ -185,10 +185,10 @@ const placeholder = $computed((): string => { const submitText = $computed((): string => { return props.renote - ? i18n.locale.quote + ? i18n.ts.quote : props.reply - ? i18n.locale.reply - : i18n.locale.note; + ? i18n.ts.reply + : i18n.ts.note; }); const textLength = $computed((): number => { @@ -342,7 +342,7 @@ function focus() { } function chooseFileFrom(ev) { - selectFiles(ev.currentTarget || ev.target, i18n.locale.attachFile).then(files_ => { + selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(files_ => { for (const file of files_) { files.push(file); } @@ -371,11 +371,6 @@ function upload(file: File, name?: string) { }); } -function onPollUpdate(poll) { - poll = poll; - saveDraft(); -} - function setVisibility() { if (props.channel) { // TODO: information dialog @@ -452,7 +447,7 @@ async function onPaste(e: ClipboardEvent) { os.confirm({ type: 'info', - text: i18n.locale.quoteQuestion, + text: i18n.ts.quoteQuestion, }).then(({ canceled }) => { if (canceled) { insertTextAtCursor(textareaEl, paste); @@ -597,7 +592,7 @@ function insertMention() { } async function insertEmoji(ev: MouseEvent) { - os.openEmojiPicker(ev.currentTarget || ev.target, {}, textareaEl); + os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textareaEl); } function showActions(ev) { @@ -610,7 +605,7 @@ function showActions(ev) { if (key === 'text') { text = value; } }); } - })), ev.currentTarget || ev.target); + })), ev.currentTarget ?? ev.target); } let postAccount = $ref<misskey.entities.UserDetailed | null>(null); @@ -646,7 +641,7 @@ onMounted(() => { nextTick(() => { // 書きかけの投稿を復元 - if (!props.share && !props.mention && !props.specified) { + if (!props.instant && !props.mention && !props.specified) { const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[draftKey]; if (draft) { text = draft.data.text; diff --git a/packages/client/src/components/renote-button.vue b/packages/client/src/components/renote-button.vue index 446686de10..c1c0d285e1 100644 --- a/packages/client/src/components/renote-button.vue +++ b/packages/client/src/components/renote-button.vue @@ -59,7 +59,7 @@ export default defineComponent({ const renote = (viaKeyboard = false) => { pleaseLogin(); os.popupMenu([{ - text: i18n.locale.renote, + text: i18n.ts.renote, icon: 'fas fa-retweet', action: () => { os.api('notes/create', { @@ -67,7 +67,7 @@ export default defineComponent({ }); } }, { - text: i18n.locale.quote, + text: i18n.ts.quote, icon: 'fas fa-quote-right', action: () => { os.post({ diff --git a/packages/client/src/components/sample.vue b/packages/client/src/components/sample.vue index 03ad6a9838..65249ff7e9 100644 --- a/packages/client/src/components/sample.vue +++ b/packages/client/src/components/sample.vue @@ -109,7 +109,7 @@ export default defineComponent({ text: 'Delete some bananas', danger: true, action: () => {}, - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); }, } }); diff --git a/packages/client/src/components/toast.vue b/packages/client/src/components/toast.vue index 031aa45633..c114379716 100644 --- a/packages/client/src/components/toast.vue +++ b/packages/client/src/components/toast.vue @@ -1,6 +1,6 @@ <template> <div class="mk-toast"> - <transition name="toast" appear @after-leave="emit('closed')"> + <transition :name="$store.state.animation ? 'toast' : ''" appear @after-leave="emit('closed')"> <div v-if="showing" class="body _acrylic" :style="{ zIndex }"> <div class="message"> {{ message }} diff --git a/packages/client/src/components/ui/container.vue b/packages/client/src/components/ui/container.vue index fcd9f32290..7c595d8116 100644 --- a/packages/client/src/components/ui/container.vue +++ b/packages/client/src/components/ui/container.vue @@ -10,7 +10,7 @@ </button> </div> </header> - <transition name="container-toggle" + <transition :name="$store.state.animation ? 'container-toggle' : ''" @enter="enter" @after-enter="afterEnter" @leave="leave" diff --git a/packages/client/src/components/ui/folder.vue b/packages/client/src/components/ui/folder.vue index 9795b1d81a..fe1602b2bb 100644 --- a/packages/client/src/components/ui/folder.vue +++ b/packages/client/src/components/ui/folder.vue @@ -8,7 +8,7 @@ <template v-else><i class="fas fa-angle-down"></i></template> </button> </header> - <transition name="folder-toggle" + <transition :name="$store.state.animation ? 'folder-toggle' : ''" @enter="enter" @after-enter="afterEnter" @leave="leave" diff --git a/packages/client/src/components/ui/modal.vue b/packages/client/src/components/ui/modal.vue index c691c8c6d0..3c3bb5c226 100644 --- a/packages/client/src/components/ui/modal.vue +++ b/packages/client/src/components/ui/modal.vue @@ -1,5 +1,5 @@ <template> -<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="$emit('closed')" @enter="$emit('opening')" @after-enter="childRendered"> +<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="childRendered"> <div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }"> <div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div> <div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick"> @@ -9,8 +9,8 @@ </transition> </template> -<script lang="ts"> -import { defineComponent, nextTick, onMounted, computed, PropType, ref, watch } from 'vue'; +<script lang="ts" setup> +import { nextTick, onMounted, computed, ref, watch, provide } from 'vue'; import * as os from '@/os'; import { isTouchUsing } from '@/scripts/touch'; import { defaultStore } from '@/store'; @@ -25,234 +25,206 @@ function getFixedContainer(el: Element | null): Element | null { } } -export default defineComponent({ - provide: { - modal: true - }, +type ModalTypes = 'popup' | 'dialog' | 'dialog:top' | 'drawer'; - props: { - manualShowing: { - type: Boolean, - required: false, - default: null, - }, - srcCenter: { - type: Boolean, - required: false - }, - src: { - type: Object as PropType<HTMLElement>, - required: false, - default: null, - }, - preferType: { - required: false, - type: String, - default: 'auto', - }, - zPriority: { - type: String as PropType<'low' | 'middle' | 'high'>, - required: false, - default: 'low', - }, - noOverlap: { - type: Boolean, - required: false, - default: true, - }, - transparentBg: { - type: Boolean, - required: false, - default: false, - }, - }, +const props = withDefaults(defineProps<{ + manualShowing?: boolean | null; + srcCenter?: boolean; + src?: HTMLElement; + preferType?: ModalTypes | 'auto'; + zPriority?: 'low' | 'middle' | 'high'; + noOverlap?: boolean; + transparentBg?: boolean; +}>(), { + manualShowing: null, + src: null, + preferType: 'auto', + zPriority: 'low', + noOverlap: true, + transparentBg: false, +}); - emits: ['opening', 'click', 'esc', 'close', 'closed'], +const emit = defineEmits<{ + (ev: 'opening'): void; + (ev: 'click'): void; + (ev: 'esc'): void; + (ev: 'close'): void; + (ev: 'closed'): void; +}>(); - setup(props, context) { - const maxHeight = ref<number>(); - const fixed = ref(false); - const transformOrigin = ref('center'); - const showing = ref(true); - const content = ref<HTMLElement>(); - const zIndex = os.claimZIndex(props.zPriority); - const type = computed(() => { - if (props.preferType === 'auto') { - if (!defaultStore.state.disableDrawer && isTouchUsing && window.innerWidth < 500 && window.innerHeight < 1000) { - return 'drawer'; - } else { - return props.src != null ? 'popup' : 'dialog'; - } - } else { - return props.preferType; - } - }); - - let contentClicking = false; +provide('modal', true); - const close = () => { - // eslint-disable-next-line vue/no-mutating-props - if (props.src) props.src.style.pointerEvents = 'auto'; - showing.value = false; - context.emit('close'); - }; +const maxHeight = ref<number>(); +const fixed = ref(false); +const transformOrigin = ref('center'); +const showing = ref(true); +const content = ref<HTMLElement>(); +const zIndex = os.claimZIndex(props.zPriority); +const type = computed(() => { + if (props.preferType === 'auto') { + if (!defaultStore.state.disableDrawer && isTouchUsing && window.innerWidth < 500 && window.innerHeight < 1000) { + return 'drawer'; + } else { + return props.src != null ? 'popup' : 'dialog'; + } + } else { + return props.preferType!; + } +}); - const onBgClick = () => { - if (contentClicking) return; - context.emit('click'); - }; +let contentClicking = false; - if (type.value === 'drawer') { - maxHeight.value = window.innerHeight / 2; - } +const close = () => { + // eslint-disable-next-line vue/no-mutating-props + if (props.src) props.src.style.pointerEvents = 'auto'; + showing.value = false; + emit('close'); +}; - const keymap = { - 'esc': () => context.emit('esc'), - }; +const onBgClick = () => { + if (contentClicking) return; + emit('click'); +}; - const MARGIN = 16; +if (type.value === 'drawer') { + maxHeight.value = window.innerHeight / 2; +} - const align = () => { - if (props.src == null) return; - if (type.value === 'drawer') return; +const keymap = { + 'esc': () => emit('esc'), +}; - const popover = content.value!; +const MARGIN = 16; - if (popover == null) return; +const align = () => { + if (props.src == null) return; + if (type.value === 'drawer') return; - const rect = props.src.getBoundingClientRect(); - - const width = popover.offsetWidth; - const height = popover.offsetHeight; + const popover = content.value!; - let left; - let top; + if (popover == null) return; - if (props.srcCenter) { - const x = rect.left + (fixed.value ? 0 : window.pageXOffset) + (props.src.offsetWidth / 2); - const y = rect.top + (fixed.value ? 0 : window.pageYOffset) + (props.src.offsetHeight / 2); - left = (x - (width / 2)); - top = (y - (height / 2)); - } else { - const x = rect.left + (fixed.value ? 0 : window.pageXOffset) + (props.src.offsetWidth / 2); - const y = rect.top + (fixed.value ? 0 : window.pageYOffset) + props.src.offsetHeight; - left = (x - (width / 2)); - top = y; - } + const rect = props.src.getBoundingClientRect(); + + const width = popover.offsetWidth; + const height = popover.offsetHeight; - if (fixed.value) { - // 画面から横にはみ出る場合 - if (left + width > window.innerWidth) { - left = window.innerWidth - width; - } + let left; + let top; - // 画面から縦にはみ出る場合 - if (top + height > (window.innerHeight - MARGIN)) { - if (props.noOverlap) { - const underSpace = (window.innerHeight - MARGIN) - top; - const upperSpace = (rect.top - MARGIN); - if (underSpace >= (upperSpace / 3)) { - maxHeight.value = underSpace; - } else { - maxHeight.value = upperSpace; - top = (upperSpace + MARGIN) - height; - } - } else { - top = (window.innerHeight - MARGIN) - height; - } + if (props.srcCenter) { + const x = rect.left + (fixed.value ? 0 : window.pageXOffset) + (props.src.offsetWidth / 2); + const y = rect.top + (fixed.value ? 0 : window.pageYOffset) + (props.src.offsetHeight / 2); + left = (x - (width / 2)); + top = (y - (height / 2)); + } else { + const x = rect.left + (fixed.value ? 0 : window.pageXOffset) + (props.src.offsetWidth / 2); + const y = rect.top + (fixed.value ? 0 : window.pageYOffset) + props.src.offsetHeight; + left = (x - (width / 2)); + top = y; + } + + if (fixed.value) { + // 画面から横にはみ出る場合 + if (left + width > window.innerWidth) { + left = window.innerWidth - width; + } + + // 画面から縦にはみ出る場合 + if (top + height > (window.innerHeight - MARGIN)) { + if (props.noOverlap) { + const underSpace = (window.innerHeight - MARGIN) - top; + const upperSpace = (rect.top - MARGIN); + if (underSpace >= (upperSpace / 3)) { + maxHeight.value = underSpace; + } else { + maxHeight.value = upperSpace; + top = (upperSpace + MARGIN) - height; } } else { - // 画面から横にはみ出る場合 - if (left + width - window.pageXOffset > window.innerWidth) { - left = window.innerWidth - width + window.pageXOffset - 1; - } + top = (window.innerHeight - MARGIN) - height; + } + } + } else { + // 画面から横にはみ出る場合 + if (left + width - window.pageXOffset > window.innerWidth) { + left = window.innerWidth - width + window.pageXOffset - 1; + } - // 画面から縦にはみ出る場合 - if (top + height - window.pageYOffset > (window.innerHeight - MARGIN)) { - if (props.noOverlap) { - const underSpace = (window.innerHeight - MARGIN) - (top - window.pageYOffset); - const upperSpace = (rect.top - MARGIN); - if (underSpace >= (upperSpace / 3)) { - maxHeight.value = underSpace; - } else { - maxHeight.value = upperSpace; - top = window.pageYOffset + ((upperSpace + MARGIN) - height); - } - } else { - top = (window.innerHeight - MARGIN) - height + window.pageYOffset - 1; - } + // 画面から縦にはみ出る場合 + if (top + height - window.pageYOffset > (window.innerHeight - MARGIN)) { + if (props.noOverlap) { + const underSpace = (window.innerHeight - MARGIN) - (top - window.pageYOffset); + const upperSpace = (rect.top - MARGIN); + if (underSpace >= (upperSpace / 3)) { + maxHeight.value = underSpace; + } else { + maxHeight.value = upperSpace; + top = window.pageYOffset + ((upperSpace + MARGIN) - height); } + } else { + top = (window.innerHeight - MARGIN) - height + window.pageYOffset - 1; } + } + } - if (top < 0) { - top = MARGIN; - } + if (top < 0) { + top = MARGIN; + } - if (left < 0) { - left = 0; - } + if (left < 0) { + left = 0; + } - if (top > rect.top + (fixed.value ? 0 : window.pageYOffset)) { - transformOrigin.value = 'center top'; - } else if ((top + height) <= rect.top + (fixed.value ? 0 : window.pageYOffset)) { - transformOrigin.value = 'center bottom'; - } else { - transformOrigin.value = 'center'; - } + if (top > rect.top + (fixed.value ? 0 : window.pageYOffset)) { + transformOrigin.value = 'center top'; + } else if ((top + height) <= rect.top + (fixed.value ? 0 : window.pageYOffset)) { + transformOrigin.value = 'center bottom'; + } else { + transformOrigin.value = 'center'; + } - popover.style.left = left + 'px'; - popover.style.top = top + 'px'; - }; + popover.style.left = left + 'px'; + popover.style.top = top + 'px'; +}; - const childRendered = () => { - // モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する - const el = content.value!.children[0]; - el.addEventListener('mousedown', e => { - contentClicking = true; - window.addEventListener('mouseup', e => { - // click イベントより先に mouseup イベントが発生するかもしれないのでちょっと待つ - window.setTimeout(() => { - contentClicking = false; - }, 100); - }, { passive: true, once: true }); - }, { passive: true }); - }; +const childRendered = () => { + // モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する + const el = content.value!.children[0]; + el.addEventListener('mousedown', ev => { + contentClicking = true; + window.addEventListener('mouseup', ev => { + // click イベントより先に mouseup イベントが発生するかもしれないのでちょっと待つ + window.setTimeout(() => { + contentClicking = false; + }, 100); + }, { passive: true, once: true }); + }, { passive: true }); +}; - onMounted(() => { - watch(() => props.src, async () => { - if (props.src) { - // eslint-disable-next-line vue/no-mutating-props - props.src.style.pointerEvents = 'none'; - } - fixed.value = (type.value === 'drawer') || (getFixedContainer(props.src) != null); +onMounted(() => { + watch(() => props.src, async () => { + if (props.src) { + // eslint-disable-next-line vue/no-mutating-props + props.src.style.pointerEvents = 'none'; + } + fixed.value = (type.value === 'drawer') || (getFixedContainer(props.src) != null); - await nextTick() - - align(); - }, { immediate: true, }); + await nextTick() + + align(); + }, { immediate: true, }); - nextTick(() => { - const popover = content.value; - new ResizeObserver((entries, observer) => { - align(); - }).observe(popover!); - }); - }); + nextTick(() => { + const popover = content.value; + new ResizeObserver((entries, observer) => { + align(); + }).observe(popover!); + }); +}); - return { - showing, - type, - fixed, - content, - transformOrigin, - maxHeight, - close, - zIndex, - keymap, - onBgClick, - childRendered, - }; - }, +defineExpose({ + close, }); </script> diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue index 9c18fc5ce5..13f3215671 100644 --- a/packages/client/src/components/ui/pagination.vue +++ b/packages/client/src/components/ui/pagination.vue @@ -1,5 +1,5 @@ <template> -<transition name="fade" mode="out-in"> +<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <MkLoading v-if="fetching"/> <MkError v-else-if="error" @retry="init()"/> diff --git a/packages/client/src/components/ui/tooltip.vue b/packages/client/src/components/ui/tooltip.vue index 2e48ab623e..394b068352 100644 --- a/packages/client/src/components/ui/tooltip.vue +++ b/packages/client/src/components/ui/tooltip.vue @@ -1,5 +1,5 @@ <template> -<transition name="tooltip" appear @after-leave="$emit('closed')"> +<transition :name="$store.state.animation ? 'tooltip' : ''" appear @after-leave="$emit('closed')"> <div v-show="showing" ref="el" class="buebdbiu _acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }"> <slot>{{ text }}</slot> </div> diff --git a/packages/client/src/components/url-preview-popup.vue b/packages/client/src/components/url-preview-popup.vue index c345bafcf9..5f3717ab91 100644 --- a/packages/client/src/components/url-preview-popup.vue +++ b/packages/client/src/components/url-preview-popup.vue @@ -1,6 +1,6 @@ <template> <div class="fgmtyycl" :style="{ zIndex, top: top + 'px', left: left + 'px' }"> - <transition name="zoom" @after-leave="$emit('closed')"> + <transition :name="$store.state.animation ? 'zoom' : ''" @after-leave="$emit('closed')"> <MkUrlPreview v-if="showing" class="_popup _shadow" :url="url"/> </transition> </div> diff --git a/packages/client/src/components/url-preview.vue b/packages/client/src/components/url-preview.vue index bf3b358797..6c57957617 100644 --- a/packages/client/src/components/url-preview.vue +++ b/packages/client/src/components/url-preview.vue @@ -7,7 +7,7 @@ <iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${$store.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"></iframe> </div> <div v-else v-size="{ max: [400, 350] }" class="mk-url-preview"> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <component :is="self ? 'MkA' : 'a'" v-if="!fetching" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url"> <div v-if="thumbnail" class="thumbnail" :style="`background-image: url('${thumbnail}')`"> <button v-if="!playerEnabled && player.url" class="_button" :title="$ts.enablePlayer" @click.prevent="playerEnabled = true"><i class="fas fa-play-circle"></i></button> diff --git a/packages/client/src/components/user-online-indicator.vue b/packages/client/src/components/user-online-indicator.vue index a87b0aeff5..a4f6f80383 100644 --- a/packages/client/src/components/user-online-indicator.vue +++ b/packages/client/src/components/user-online-indicator.vue @@ -13,10 +13,10 @@ const props = defineProps<{ const text = $computed(() => { switch (props.user.onlineStatus) { - case 'online': return i18n.locale.online; - case 'active': return i18n.locale.active; - case 'offline': return i18n.locale.offline; - case 'unknown': return i18n.locale.unknown; + case 'online': return i18n.ts.online; + case 'active': return i18n.ts.active; + case 'offline': return i18n.ts.offline; + case 'unknown': return i18n.ts.unknown; } }); </script> diff --git a/packages/client/src/components/user-preview.vue b/packages/client/src/components/user-preview.vue index f85a32fbe7..51c5330564 100644 --- a/packages/client/src/components/user-preview.vue +++ b/packages/client/src/components/user-preview.vue @@ -1,5 +1,5 @@ <template> -<transition name="popup" appear @after-leave="$emit('closed')"> +<transition :name="$store.state.animation ? 'popup' : ''" appear @after-leave="$emit('closed')"> <div v-if="showing" class="fxxzrfni _popup _shadow" :style="{ zIndex, top: top + 'px', left: left + 'px' }" @mouseover="() => { $emit('mouseover'); }" @mouseleave="() => { $emit('mouseleave'); }"> <div v-if="fetched" class="info"> <div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> diff --git a/packages/client/src/const.ts b/packages/client/src/const.ts new file mode 100644 index 0000000000..505cf2748e --- /dev/null +++ b/packages/client/src/const.ts @@ -0,0 +1,44 @@ +// ブラウザで直接表示することを許可するファイルの種類のリスト +// ここに含まれないものは application/octet-stream としてレスポンスされる +// SVGはXSSを生むので許可しない +export const FILE_TYPE_BROWSERSAFE = [ + // Images + 'image/png', + 'image/gif', + 'image/jpeg', + 'image/webp', + 'image/apng', + 'image/bmp', + 'image/tiff', + 'image/x-icon', + + // OggS + 'audio/opus', + 'video/ogg', + 'audio/ogg', + 'application/ogg', + + // ISO/IEC base media file format + 'video/quicktime', + 'video/mp4', + 'audio/mp4', + 'video/x-m4v', + 'audio/x-m4a', + 'video/3gpp', + 'video/3gpp2', + + 'video/mpeg', + 'audio/mpeg', + + 'video/webm', + 'audio/webm', + + 'audio/aac', + 'audio/x-flac', + 'audio/vnd.wave', +]; +/* +https://github.com/sindresorhus/file-type/blob/main/supported.js +https://github.com/sindresorhus/file-type/blob/main/core.js +https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers +*/ diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index bdd07b63a7..9b670b4300 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -188,7 +188,7 @@ app.config.globalProperties = { $store: defaultStore, $instance: instance, $t: i18n.t, - $ts: i18n.locale, + $ts: i18n.ts, }; app.use(router); @@ -305,8 +305,8 @@ stream.on('_disconnected_', async () => { reloadDialogShowing = true; const { canceled } = await confirm({ type: 'warning', - title: i18n.locale.disconnectedFromServer, - text: i18n.locale.reloadConfirm, + title: i18n.ts.disconnectedFromServer, + text: i18n.ts.reloadConfirm, }); reloadDialogShowing = false; if (!canceled) { @@ -330,7 +330,7 @@ if ($i) { if ($i.isDeleted) { alert({ type: 'warning', - text: i18n.locale.accountDeletionInProgress, + text: i18n.ts.accountDeletionInProgress, }); } diff --git a/packages/client/src/menu.ts b/packages/client/src/menu.ts index 184779f21f..ebc7898101 100644 --- a/packages/client/src/menu.ts +++ b/packages/client/src/menu.ts @@ -73,12 +73,12 @@ export const menuDef = reactive({ })), null, { type: 'link', to: '/my/lists', - text: i18n.locale.manageLists, + text: i18n.ts.manageLists, icon: 'fas fa-cog', }]; items.value = _items; }); - os.popupMenu(items, ev.currentTarget || ev.target); + os.popupMenu(items, ev.currentTarget ?? ev.target); }, }, groups: { @@ -104,12 +104,12 @@ export const menuDef = reactive({ })), null, { type: 'link', to: '/my/antennas', - text: i18n.locale.manageAntennas, + text: i18n.ts.manageAntennas, icon: 'fas fa-cog', }]; items.value = _items; }); - os.popupMenu(items, ev.currentTarget || ev.target); + os.popupMenu(items, ev.currentTarget ?? ev.target); }, }, mentions: { @@ -173,34 +173,34 @@ export const menuDef = reactive({ icon: 'fas fa-columns', action: (ev) => { os.popupMenu([{ - text: i18n.locale.default, + text: i18n.ts.default, active: ui === 'default' || ui === null, action: () => { localStorage.setItem('ui', 'default'); unisonReload(); } }, { - text: i18n.locale.deck, + text: i18n.ts.deck, active: ui === 'deck', action: () => { localStorage.setItem('ui', 'deck'); unisonReload(); } }, { - text: i18n.locale.classic, + text: i18n.ts.classic, active: ui === 'classic', action: () => { localStorage.setItem('ui', 'classic'); unisonReload(); } }, /*{ - text: i18n.locale.desktop + ' (β)', + text: i18n.ts.desktop + ' (β)', active: ui === 'desktop', action: () => { localStorage.setItem('ui', 'desktop'); unisonReload(); } - }*/], ev.currentTarget || ev.target); + }*/], ev.currentTarget ?? ev.target); }, }, }); diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index 378523e1bc..f3be5c68fb 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -403,7 +403,7 @@ export async function selectDriveFolder(multiple: boolean) { }); } -export async function pickEmoji(src?: HTMLElement, opts) { +export async function pickEmoji(src: HTMLElement | null, opts) { return new Promise((resolve, reject) => { popup(import('@/components/emoji-picker-dialog.vue'), { src, @@ -570,7 +570,7 @@ export function upload(file: File, folder?: any, name?: string): Promise<Misskey const xhr = new XMLHttpRequest(); xhr.open('POST', apiUrl + '/drive/files/create', true); xhr.onload = (ev) => { - if (ev.target == null || ev.target.response == null) { + if (xhr.status !== 200 || ev.target == null || ev.target.response == null) { // TODO: 消すのではなくて再送できるようにしたい uploads.value = uploads.value.filter(x => x.id != id); diff --git a/packages/client/src/pages/_error_.vue b/packages/client/src/pages/_error_.vue index 7540995707..4cfe2e255c 100644 --- a/packages/client/src/pages/_error_.vue +++ b/packages/client/src/pages/_error_.vue @@ -3,15 +3,15 @@ <transition :name="$store.state.animation ? 'zoom' : ''" appear> <div v-show="loaded" class="mjndxjch"> <img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> - <p><b><i class="fas fa-exclamation-triangle"></i> {{ i18n.locale.pageLoadError }}</b></p> - <p v-if="meta && (version === meta.version)">{{ i18n.locale.pageLoadErrorDescription }}</p> - <p v-else-if="serverIsDead">{{ i18n.locale.serverIsDead }}</p> + <p><b><i class="fas fa-exclamation-triangle"></i> {{ i18n.ts.pageLoadError }}</b></p> + <p v-if="meta && (version === meta.version)">{{ i18n.ts.pageLoadErrorDescription }}</p> + <p v-else-if="serverIsDead">{{ i18n.ts.serverIsDead }}</p> <template v-else> - <p>{{ i18n.locale.newVersionOfClientAvailable }}</p> - <p>{{ i18n.locale.youShouldUpgradeClient }}</p> - <MkButton class="button primary" @click="reload">{{ i18n.locale.reload }}</MkButton> + <p>{{ i18n.ts.newVersionOfClientAvailable }}</p> + <p>{{ i18n.ts.youShouldUpgradeClient }}</p> + <MkButton class="button primary" @click="reload">{{ i18n.ts.reload }}</MkButton> </template> - <p><MkA to="/docs/general/troubleshooting" class="_link">{{ i18n.locale.troubleshooting }}</MkA></p> + <p><MkA to="/docs/general/troubleshooting" class="_link">{{ i18n.ts.troubleshooting }}</MkA></p> <p v-if="error" class="error">ERROR: {{ error }}</p> </div> </transition> @@ -54,7 +54,7 @@ function reload() { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.error, + title: i18n.ts.error, icon: 'fas fa-exclamation-triangle', }, }); diff --git a/packages/client/src/pages/about-misskey.vue b/packages/client/src/pages/about-misskey.vue index 8119f33051..0ffb6b9e1d 100644 --- a/packages/client/src/pages/about-misskey.vue +++ b/packages/client/src/pages/about-misskey.vue @@ -10,7 +10,7 @@ <span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"><MkEmoji class="emoji" :emoji="emoji.emoji" :custom-emojis="$instance.emojis" :is-reaction="false" :normal="true" :no-style="true"/></span> </div> <div class="_formBlock" style="text-align: center;"> - {{ i18n.locale._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ i18n.locale.learnMore }}</a> + {{ i18n.ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ i18n.ts.learnMore }}</a> </div> <div class="_formBlock" style="text-align: center;"> <MkButton primary rounded inline @click="iLoveMisskey">I <Mfm text="$[jelly ❤]"/> #Misskey</MkButton> @@ -19,23 +19,23 @@ <div class="_formLinks"> <FormLink to="https://github.com/misskey-dev/misskey" external> <template #icon><i class="fas fa-code"></i></template> - {{ i18n.locale._aboutMisskey.source }} + {{ i18n.ts._aboutMisskey.source }} <template #suffix>GitHub</template> </FormLink> <FormLink to="https://crowdin.com/project/misskey" external> <template #icon><i class="fas fa-language"></i></template> - {{ i18n.locale._aboutMisskey.translation }} + {{ i18n.ts._aboutMisskey.translation }} <template #suffix>Crowdin</template> </FormLink> <FormLink to="https://www.patreon.com/syuilo" external> <template #icon><i class="fas fa-hand-holding-medical"></i></template> - {{ i18n.locale._aboutMisskey.donate }} + {{ i18n.ts._aboutMisskey.donate }} <template #suffix>Patreon</template> </FormLink> </div> </FormSection> <FormSection> - <template #label>{{ i18n.locale._aboutMisskey.contributors }}</template> + <template #label>{{ i18n.ts._aboutMisskey.contributors }}</template> <div class="_formLinks"> <FormLink to="https://github.com/syuilo" external>@syuilo</FormLink> <FormLink to="https://github.com/AyaMorisawa" external>@AyaMorisawa</FormLink> @@ -47,12 +47,12 @@ <FormLink to="https://github.com/u1-liquid" external>@u1-liquid</FormLink> <FormLink to="https://github.com/marihachi" external>@marihachi</FormLink> </div> - <template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ i18n.locale._aboutMisskey.allContributors }}</MkLink></template> + <template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ i18n.ts._aboutMisskey.allContributors }}</MkLink></template> </FormSection> <FormSection> - <template #label><Mfm text="$[jelly ❤]"/> {{ i18n.locale._aboutMisskey.patrons }}</template> + <template #label><Mfm text="$[jelly ❤]"/> {{ i18n.ts._aboutMisskey.patrons }}</template> <div v-for="patron in patrons" :key="patron">{{ patron }}</div> - <template #caption>{{ i18n.locale._aboutMisskey.morePatrons }}</template> + <template #caption>{{ i18n.ts._aboutMisskey.morePatrons }}</template> </FormSection> </div> </MkSpacer> @@ -182,6 +182,7 @@ function gravity() { function iLoveMisskey() { os.post({ initialText: 'I $[jelly ❤] #Misskey', + instant: true, }); } @@ -193,7 +194,7 @@ onBeforeUnmount(() => { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.aboutMisskey, + title: i18n.ts.aboutMisskey, icon: null, bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/about.vue b/packages/client/src/pages/about.vue index a5984c548d..d5bab4baf8 100644 --- a/packages/client/src/pages/about.vue +++ b/packages/client/src/pages/about.vue @@ -90,7 +90,7 @@ const initStats = () => os.api('stats', { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.instanceInfo, + title: i18n.ts.instanceInfo, icon: 'fas fa-info-circle', bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue index 5b1dfe565a..a080ee9c23 100644 --- a/packages/client/src/pages/admin/emojis.vue +++ b/packages/client/src/pages/admin/emojis.vue @@ -118,7 +118,7 @@ const toggleSelect = (emoji) => { }; const add = async (ev: MouseEvent) => { - const files = await selectFiles(ev.currentTarget || ev.target, null); + const files = await selectFiles(ev.currentTarget ?? ev.target, null); const promise = Promise.all(files.map(file => os.api('admin/emoji/add', { fileId: file.id, @@ -157,23 +157,23 @@ const remoteMenu = (emoji, ev: MouseEvent) => { type: 'label', text: ':' + emoji.name + ':', }, { - text: i18n.locale.import, + text: i18n.ts.import, icon: 'fas fa-plus', action: () => { im(emoji) } - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); }; const menu = (ev: MouseEvent) => { os.popupMenu([{ icon: 'fas fa-download', - text: i18n.locale.export, + text: i18n.ts.export, action: async () => { os.api('export-custom-emojis', { }) .then(() => { os.alert({ type: 'info', - text: i18n.locale.exportRequested, + text: i18n.ts.exportRequested, }); }).catch((e) => { os.alert({ @@ -184,16 +184,16 @@ const menu = (ev: MouseEvent) => { } }, { icon: 'fas fa-upload', - text: i18n.locale.import, + text: i18n.ts.import, action: async () => { - const file = await selectFile(ev.currentTarget || ev.target); + const file = await selectFile(ev.currentTarget ?? ev.target); os.api('admin/emoji/import-zip', { fileId: file.id, }) .then(() => { os.alert({ type: 'info', - text: i18n.locale.importRequested, + text: i18n.ts.importRequested, }); }).catch((e) => { os.alert({ @@ -202,7 +202,7 @@ const menu = (ev: MouseEvent) => { }); }); } - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); }; const setCategoryBulk = async () => { @@ -256,7 +256,7 @@ const setTagBulk = async () => { const delBulk = async () => { const { canceled } = await os.confirm({ type: 'warning', - text: i18n.locale.deleteConfirm, + text: i18n.ts.deleteConfirm, }); if (canceled) return; await os.apiWithDialog('admin/emoji/delete-bulk', { @@ -267,13 +267,13 @@ const delBulk = async () => { defineExpose({ [symbols.PAGE_INFO]: computed(() => ({ - title: i18n.locale.customEmojis, + title: i18n.ts.customEmojis, icon: 'fas fa-laugh', bg: 'var(--bg)', actions: [{ asFullButton: true, icon: 'fas fa-plus', - text: i18n.locale.addEmoji, + text: i18n.ts.addEmoji, handler: add, }, { icon: 'fas fa-ellipsis-h', @@ -281,11 +281,11 @@ defineExpose({ }], tabs: [{ active: tab.value === 'local', - title: i18n.locale.local, + title: i18n.ts.local, onClick: () => { tab.value = 'local'; }, }, { active: tab.value === 'remote', - title: i18n.locale.remote, + title: i18n.ts.remote, onClick: () => { tab.value = 'remote'; }, },] })), diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue index 350e7defc6..6b11650f48 100644 --- a/packages/client/src/pages/admin/index.vue +++ b/packages/client/src/pages/admin/index.vue @@ -55,7 +55,7 @@ export default defineComponent({ setup(props, context) { const indexInfo = { - title: i18n.locale.controlPanel, + title: i18n.ts.controlPanel, icon: 'fas fa-cog', bg: 'var(--bg)', hideHeader: true, @@ -91,119 +91,119 @@ export default defineComponent({ }); const menuDef = computed(() => [{ - title: i18n.locale.quickAction, + title: i18n.ts.quickAction, items: [{ type: 'button', icon: 'fas fa-search', - text: i18n.locale.lookup, + text: i18n.ts.lookup, action: lookup, }, ...(instance.disableRegistration ? [{ type: 'button', icon: 'fas fa-user', - text: i18n.locale.invite, + text: i18n.ts.invite, action: invite, }] : [])], }, { - title: i18n.locale.administration, + title: i18n.ts.administration, items: [{ icon: 'fas fa-tachometer-alt', - text: i18n.locale.dashboard, + text: i18n.ts.dashboard, to: '/admin/overview', active: page.value === 'overview', }, { icon: 'fas fa-users', - text: i18n.locale.users, + text: i18n.ts.users, to: '/admin/users', active: page.value === 'users', }, { icon: 'fas fa-laugh', - text: i18n.locale.customEmojis, + text: i18n.ts.customEmojis, to: '/admin/emojis', active: page.value === 'emojis', }, { icon: 'fas fa-globe', - text: i18n.locale.federation, + text: i18n.ts.federation, to: '/admin/federation', active: page.value === 'federation', }, { icon: 'fas fa-clipboard-list', - text: i18n.locale.jobQueue, + text: i18n.ts.jobQueue, to: '/admin/queue', active: page.value === 'queue', }, { icon: 'fas fa-cloud', - text: i18n.locale.files, + text: i18n.ts.files, to: '/admin/files', active: page.value === 'files', }, { icon: 'fas fa-broadcast-tower', - text: i18n.locale.announcements, + text: i18n.ts.announcements, to: '/admin/announcements', active: page.value === 'announcements', }, { icon: 'fas fa-audio-description', - text: i18n.locale.ads, + text: i18n.ts.ads, to: '/admin/ads', active: page.value === 'ads', }, { icon: 'fas fa-exclamation-circle', - text: i18n.locale.abuseReports, + text: i18n.ts.abuseReports, to: '/admin/abuses', active: page.value === 'abuses', }], }, { - title: i18n.locale.settings, + title: i18n.ts.settings, items: [{ icon: 'fas fa-cog', - text: i18n.locale.general, + text: i18n.ts.general, to: '/admin/settings', active: page.value === 'settings', }, { icon: 'fas fa-envelope', - text: i18n.locale.emailServer, + text: i18n.ts.emailServer, to: '/admin/email-settings', active: page.value === 'email-settings', }, { icon: 'fas fa-cloud', - text: i18n.locale.objectStorage, + text: i18n.ts.objectStorage, to: '/admin/object-storage', active: page.value === 'object-storage', }, { icon: 'fas fa-lock', - text: i18n.locale.security, + text: i18n.ts.security, to: '/admin/security', active: page.value === 'security', }, { icon: 'fas fa-globe', - text: i18n.locale.relays, + text: i18n.ts.relays, to: '/admin/relays', active: page.value === 'relays', }, { icon: 'fas fa-share-alt', - text: i18n.locale.integration, + text: i18n.ts.integration, to: '/admin/integrations', active: page.value === 'integrations', }, { icon: 'fas fa-ban', - text: i18n.locale.instanceBlocking, + text: i18n.ts.instanceBlocking, to: '/admin/instance-block', active: page.value === 'instance-block', }, { icon: 'fas fa-ghost', - text: i18n.locale.proxyAccount, + text: i18n.ts.proxyAccount, to: '/admin/proxy-account', active: page.value === 'proxy-account', }, { icon: 'fas fa-cogs', - text: i18n.locale.other, + text: i18n.ts.other, to: '/admin/other-settings', active: page.value === 'other-settings', }], }, { - title: i18n.locale.info, + title: i18n.ts.info, items: [{ icon: 'fas fa-database', - text: i18n.locale.database, + text: i18n.ts.database, to: '/admin/database', active: page.value === 'database', }], @@ -275,37 +275,37 @@ export default defineComponent({ const lookup = (ev) => { os.popupMenu([{ - text: i18n.locale.user, + text: i18n.ts.user, icon: 'fas fa-user', action: () => { lookupUser(); } }, { - text: i18n.locale.note, + text: i18n.ts.note, icon: 'fas fa-pencil-alt', action: () => { alert('TODO'); } }, { - text: i18n.locale.file, + text: i18n.ts.file, icon: 'fas fa-cloud', action: () => { alert('TODO'); } }, { - text: i18n.locale.instance, + text: i18n.ts.instance, icon: 'fas fa-globe', action: () => { alert('TODO'); } - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); }; return { [symbols.PAGE_INFO]: INFO, menuDef, header: { - title: i18n.locale.controlPanel, + title: i18n.ts.controlPanel, }, noMaintainerInformation, noBotProtection, diff --git a/packages/client/src/pages/channel-editor.vue b/packages/client/src/pages/channel-editor.vue index 58c644be62..3818c7481a 100644 --- a/packages/client/src/pages/channel-editor.vue +++ b/packages/client/src/pages/channel-editor.vue @@ -112,7 +112,7 @@ export default defineComponent({ }, setBannerImage(e) { - selectFile(e.currentTarget || e.target, null).then(file => { + selectFile(e.currentTarget ?? e.target, null).then(file => { this.bannerId = file.id; }); }, diff --git a/packages/client/src/pages/clip.vue b/packages/client/src/pages/clip.vue index 6b49221d32..c999f1bfc9 100644 --- a/packages/client/src/pages/clip.vue +++ b/packages/client/src/pages/clip.vue @@ -127,7 +127,7 @@ export default defineComponent({ clipId: this.clip.id, }); } - } : undefined], ev.currentTarget || ev.target); + } : undefined], ev.currentTarget ?? ev.target); } } }); diff --git a/packages/client/src/pages/drive.vue b/packages/client/src/pages/drive.vue index 1e17bea0cc..68777bb083 100644 --- a/packages/client/src/pages/drive.vue +++ b/packages/client/src/pages/drive.vue @@ -15,7 +15,7 @@ let folder = $ref(null); defineExpose({ [symbols.PAGE_INFO]: computed(() => ({ - title: folder ? folder.name : i18n.locale.drive, + title: folder ? folder.name : i18n.ts.drive, icon: 'fas fa-cloud', bg: 'var(--bg)', hideHeader: true, diff --git a/packages/client/src/pages/emojis.emoji.vue b/packages/client/src/pages/emojis.emoji.vue index 83539ce7a3..b2801694db 100644 --- a/packages/client/src/pages/emojis.emoji.vue +++ b/packages/client/src/pages/emojis.emoji.vue @@ -23,13 +23,13 @@ function menu(ev) { type: 'label', text: ':' + props.emoji.name + ':', }, { - text: i18n.locale.copy, + text: i18n.ts.copy, icon: 'fas fa-copy', action: () => { copyToClipboard(`:${props.emoji.name}:`); os.success(); } - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); } </script> diff --git a/packages/client/src/pages/emojis.vue b/packages/client/src/pages/emojis.vue index 6577f5abd9..886b5f7119 100644 --- a/packages/client/src/pages/emojis.vue +++ b/packages/client/src/pages/emojis.vue @@ -16,14 +16,14 @@ const tab = ref('category'); function menu(ev) { os.popupMenu([{ icon: 'fas fa-download', - text: i18n.locale.export, + text: i18n.ts.export, action: async () => { os.api('export-custom-emojis', { }) .then(() => { os.alert({ type: 'info', - text: i18n.locale.exportRequested, + text: i18n.ts.exportRequested, }); }).catch((e) => { os.alert({ @@ -32,12 +32,12 @@ function menu(ev) { }); }); } - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); } defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.customEmojis, + title: i18n.ts.customEmojis, icon: 'fas fa-laugh', bg: 'var(--bg)', actions: [{ diff --git a/packages/client/src/pages/favorites.vue b/packages/client/src/pages/favorites.vue index 8965b30d60..b4f6ff35bc 100644 --- a/packages/client/src/pages/favorites.vue +++ b/packages/client/src/pages/favorites.vue @@ -34,7 +34,7 @@ const pagingComponent = ref<InstanceType<typeof MkPagination>>(); defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.favorites, + title: i18n.ts.favorites, icon: 'fas fa-star', bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/featured.vue b/packages/client/src/pages/featured.vue index 725c70f0f7..14fe0cb740 100644 --- a/packages/client/src/pages/featured.vue +++ b/packages/client/src/pages/featured.vue @@ -17,7 +17,7 @@ const pagination = { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.featured, + title: i18n.ts.featured, icon: 'fas fa-fire-alt', bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/federation.vue b/packages/client/src/pages/federation.vue index 6a4a28b6b4..a4ae901f2a 100644 --- a/packages/client/src/pages/federation.vue +++ b/packages/client/src/pages/federation.vue @@ -135,7 +135,7 @@ function getStatus(instance) { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.federation, + title: i18n.ts.federation, icon: 'fas fa-globe', bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/follow-requests.vue b/packages/client/src/pages/follow-requests.vue index 764daa0d3e..6adc1a404b 100644 --- a/packages/client/src/pages/follow-requests.vue +++ b/packages/client/src/pages/follow-requests.vue @@ -60,7 +60,7 @@ function reject(user) { defineExpose({ [symbols.PAGE_INFO]: computed(() => ({ - title: i18n.locale.followRequests, + title: i18n.ts.followRequests, icon: 'fas fa-user-clock', bg: 'var(--bg)', })), diff --git a/packages/client/src/pages/gallery/edit.vue b/packages/client/src/pages/gallery/edit.vue index e3fa1a0fcd..25ee513186 100644 --- a/packages/client/src/pages/gallery/edit.vue +++ b/packages/client/src/pages/gallery/edit.vue @@ -92,7 +92,7 @@ export default defineComponent({ methods: { selectFile(e) { - selectFiles(e.currentTarget || e.target, null).then(files => { + selectFiles(e.currentTarget ?? e.target, null).then(files => { this.files = this.files.concat(files); }); }, diff --git a/packages/client/src/pages/gallery/post.vue b/packages/client/src/pages/gallery/post.vue index fff2b6a74e..1755c23286 100644 --- a/packages/client/src/pages/gallery/post.vue +++ b/packages/client/src/pages/gallery/post.vue @@ -1,6 +1,6 @@ <template> <div class="_root"> - <transition name="fade" mode="out-in"> + <transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="post" class="rkxwuolj"> <div class="files"> <div v-for="file in post.files" :key="file.id" class="file"> diff --git a/packages/client/src/pages/mentions.vue b/packages/client/src/pages/mentions.vue index bda56fc729..9b57c956bf 100644 --- a/packages/client/src/pages/mentions.vue +++ b/packages/client/src/pages/mentions.vue @@ -16,7 +16,7 @@ const pagination = { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.mentions, + title: i18n.ts.mentions, icon: 'fas fa-at', bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/messages.vue b/packages/client/src/pages/messages.vue index 8efdc55586..9c5fb9b341 100644 --- a/packages/client/src/pages/messages.vue +++ b/packages/client/src/pages/messages.vue @@ -12,14 +12,14 @@ import { i18n } from '@/i18n'; const pagination = { endpoint: 'notes/mentions' as const, limit: 10, - params: () => ({ + params: { visibility: 'specified' - }), + }, }; defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.directNotes, + title: i18n.ts.directNotes, icon: 'fas fa-envelope', bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/messaging/index.vue b/packages/client/src/pages/messaging/index.vue index 554ebc4b6b..88a1e07afc 100644 --- a/packages/client/src/pages/messaging/index.vue +++ b/packages/client/src/pages/messaging/index.vue @@ -128,7 +128,7 @@ export default defineComponent({ text: this.$ts.messagingWithGroup, icon: 'fas fa-users', action: () => { this.startGroup() } - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); }, async startUser() { diff --git a/packages/client/src/pages/messaging/messaging-room.form.vue b/packages/client/src/pages/messaging/messaging-room.form.vue index f2a90fbfba..3863c8f82b 100644 --- a/packages/client/src/pages/messaging/messaging-room.form.vue +++ b/packages/client/src/pages/messaging/messaging-room.form.vue @@ -7,7 +7,7 @@ ref="text" v-model="text" :placeholder="$ts.inputMessageHere" - @keypress="onKeypress" + @keydown="onKeydown" @compositionupdate="onCompositionUpdate" @paste="onPaste" ></textarea> @@ -24,7 +24,7 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent } from 'vue'; import insertTextAtCursor from 'insert-text-at-cursor'; -import * as autosize from 'autosize'; +import autosize from 'autosize'; import { formatTimeString } from '@/scripts/format-time-string'; import { selectFile } from '@/scripts/select-file'; import * as os from '@/os'; @@ -76,7 +76,8 @@ export default defineComponent({ autosize(this.$refs.text); // TODO: detach when unmount - new Autocomplete(this.$refs.text, this, { model: 'text' }); + // TODO + //new Autocomplete(this.$refs.text, this, { model: 'text' }); // 書きかけの投稿を復元 const draft = JSON.parse(localStorage.getItem('message_drafts') || '{}')[this.draftKey]; @@ -141,7 +142,7 @@ export default defineComponent({ //#endregion }, - onKeypress(e) { + onKeydown(e) { this.typing(); if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) { this.send(); @@ -153,7 +154,7 @@ export default defineComponent({ }, chooseFile(e) { - selectFile(e.currentTarget || e.target, this.$ts.selectFile).then(file => { + selectFile(e.currentTarget ?? e.target, this.$ts.selectFile).then(file => { this.file = file; }); }, @@ -213,7 +214,7 @@ export default defineComponent({ }, async insertEmoji(ev) { - os.openEmojiPicker(ev.currentTarget || ev.target, {}, this.$refs.text); + os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, this.$refs.text); } } }); diff --git a/packages/client/src/pages/messaging/messaging-room.vue b/packages/client/src/pages/messaging/messaging-room.vue index 9a34551ddd..2ecc68eb54 100644 --- a/packages/client/src/pages/messaging/messaging-room.vue +++ b/packages/client/src/pages/messaging/messaging-room.vue @@ -11,7 +11,7 @@ <button v-show="existMoreMessages" ref="loadMore" class="more _button" :class="{ fetching: fetchingMoreMessages }" :disabled="fetchingMoreMessages" @click="fetchMoreMessages"> <template v-if="fetchingMoreMessages"><i class="fas fa-spinner fa-pulse fa-fw"></i></template>{{ fetchingMoreMessages ? $ts.loading : $ts.loadMore }} </button> - <XList v-slot="{ item: message }" class="messages" :items="messages" direction="up" reversed> + <XList v-if="messages.length > 0" v-slot="{ item: message }" class="messages" :items="messages" direction="up" reversed> <XMessage :key="message.id" :message="message" :is-group="group != null"/> </XList> </div> @@ -24,7 +24,7 @@ </I18n> <MkEllipsis/> </div> - <transition name="fade"> + <transition :name="$store.state.animation ? 'fade' : ''"> <div v-show="showIndicator" class="new-message"> <button class="_buttonPrimary" @click="onIndicatorClick"><i class="fas fa-arrow-circle-down"></i>{{ $ts.newMessageExists }}</button> </div> @@ -335,7 +335,7 @@ const Component = defineComponent({ popout(path); this.$router.back(); }, - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); } } }); diff --git a/packages/client/src/pages/my-antennas/create.vue b/packages/client/src/pages/my-antennas/create.vue index 427c9935c3..a08bece731 100644 --- a/packages/client/src/pages/my-antennas/create.vue +++ b/packages/client/src/pages/my-antennas/create.vue @@ -31,7 +31,7 @@ function onAntennaCreated() { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.manageAntennas, + title: i18n.ts.manageAntennas, icon: 'fas fa-satellite', bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/my-clips/index.vue b/packages/client/src/pages/my-clips/index.vue index 97b563f6f8..e287357a42 100644 --- a/packages/client/src/pages/my-clips/index.vue +++ b/packages/client/src/pages/my-clips/index.vue @@ -19,7 +19,7 @@ import MkPagination from '@/components/ui/pagination.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; -import i18n from '@/components/global/i18n'; +import { i18n } from '@/i18n'; const pagination = { endpoint: 'clips/list' as const, @@ -29,20 +29,20 @@ const pagination = { const pagingComponent = $ref<InstanceType<typeof MkPagination>>(); async function create() { - const { canceled, result } = await os.form(i18n.locale.createNewClip, { + const { canceled, result } = await os.form(i18n.ts.createNewClip, { name: { type: 'string', - label: i18n.locale.name, + label: i18n.ts.name, }, description: { type: 'string', required: false, multiline: true, - label: i18n.locale.description, + label: i18n.ts.description, }, isPublic: { type: 'boolean', - label: i18n.locale.public, + label: i18n.ts.public, default: false, }, }); @@ -63,7 +63,7 @@ function onClipDeleted() { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.clip, + title: i18n.ts.clip, icon: 'fas fa-paperclip', bg: 'var(--bg)', action: { diff --git a/packages/client/src/pages/my-groups/group.vue b/packages/client/src/pages/my-groups/group.vue index c307f037a6..92c0483af9 100644 --- a/packages/client/src/pages/my-groups/group.vue +++ b/packages/client/src/pages/my-groups/group.vue @@ -1,6 +1,6 @@ <template> <div class="mk-group-page"> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <div v-if="group" class="_section"> <div class="_content" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> <MkButton inline @click="invite()">{{ $ts.invite }}</MkButton> @@ -11,7 +11,7 @@ </div> </transition> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <div v-if="group" class="_section members _gap"> <div class="_title">{{ $ts.members }}</div> <div class="_content"> diff --git a/packages/client/src/pages/my-lists/index.vue b/packages/client/src/pages/my-lists/index.vue index e6fcba1b34..9ed9e2960e 100644 --- a/packages/client/src/pages/my-lists/index.vue +++ b/packages/client/src/pages/my-lists/index.vue @@ -31,7 +31,7 @@ const pagination = { async function create() { const { canceled, result: name } = await os.inputText({ - title: i18n.locale.enterListName, + title: i18n.ts.enterListName, }); if (canceled) return; await os.apiWithDialog('users/lists/create', { name: name }); @@ -40,7 +40,7 @@ async function create() { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.manageLists, + title: i18n.ts.manageLists, icon: 'fas fa-list-ul', bg: 'var(--bg)', action: { diff --git a/packages/client/src/pages/my-lists/list.vue b/packages/client/src/pages/my-lists/list.vue index a25522f933..bc24f58431 100644 --- a/packages/client/src/pages/my-lists/list.vue +++ b/packages/client/src/pages/my-lists/list.vue @@ -1,7 +1,7 @@ <template> <MkSpacer :content-max="700"> <div class="mk-list-page"> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <div v-if="list" class="_section"> <div class="_content"> <MkButton inline @click="addUser()">{{ $ts.addUser }}</MkButton> @@ -11,7 +11,7 @@ </div> </transition> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <div v-if="list" class="_section members _gap"> <div class="_title">{{ $ts.members }}</div> <div class="_content"> diff --git a/packages/client/src/pages/not-found.vue b/packages/client/src/pages/not-found.vue index 914fdb9297..cdeb54b88b 100644 --- a/packages/client/src/pages/not-found.vue +++ b/packages/client/src/pages/not-found.vue @@ -13,7 +13,7 @@ import { i18n } from '@/i18n'; defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.notFound, + title: i18n.ts.notFound, icon: 'fas fa-exclamation-triangle', bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue index 72ac85ee90..efeea345dc 100644 --- a/packages/client/src/pages/note.vue +++ b/packages/client/src/pages/note.vue @@ -1,7 +1,7 @@ <template> <MkSpacer :content-max="800"> <div class="fcuexfpr"> - <transition name="fade" mode="out-in"> + <transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="note" class="note"> <div v-if="showNext" class="_gap"> <XNotes class="_content" :pagination="next" :no-gap="true"/> diff --git a/packages/client/src/pages/notifications.vue b/packages/client/src/pages/notifications.vue index 090e80f99a..36e423e534 100644 --- a/packages/client/src/pages/notifications.vue +++ b/packages/client/src/pages/notifications.vue @@ -27,26 +27,26 @@ function setFilter(ev) { })); const items = includeTypes != null ? [{ icon: 'fas fa-times', - text: i18n.locale.clear, + text: i18n.ts.clear, action: () => { includeTypes = null; } }, null, ...typeItems] : typeItems; - os.popupMenu(items, ev.currentTarget || ev.target); + os.popupMenu(items, ev.currentTarget ?? ev.target); } defineExpose({ [symbols.PAGE_INFO]: computed(() => ({ - title: i18n.locale.notifications, + title: i18n.ts.notifications, icon: 'fas fa-bell', bg: 'var(--bg)', actions: [{ - text: i18n.locale.filter, + text: i18n.ts.filter, icon: 'fas fa-filter', highlighted: includeTypes != null, handler: setFilter, }, { - text: i18n.locale.markAllAsRead, + text: i18n.ts.markAllAsRead, icon: 'fas fa-check', handler: () => { os.apiWithDialog('notifications/mark-all-as-read'); @@ -54,11 +54,11 @@ defineExpose({ }], tabs: [{ active: tab === 'all', - title: i18n.locale.all, + title: i18n.ts.all, onClick: () => { tab = 'all'; }, }, { active: tab === 'unread', - title: i18n.locale.unread, + title: i18n.ts.unread, onClick: () => { tab = 'unread'; }, },] })), diff --git a/packages/client/src/pages/page-editor/page-editor.vue b/packages/client/src/pages/page-editor/page-editor.vue index fe207555f8..f302ac4f90 100644 --- a/packages/client/src/pages/page-editor/page-editor.vue +++ b/packages/client/src/pages/page-editor/page-editor.vue @@ -448,7 +448,7 @@ export default defineComponent({ }, setEyeCatchingImage(e) { - selectFile(e.currentTarget || e.target, null).then(file => { + selectFile(e.currentTarget ?? e.target, null).then(file => { this.eyeCatchingImageId = file.id; }); }, diff --git a/packages/client/src/pages/page.vue b/packages/client/src/pages/page.vue index 429d1ddea2..b2c039a269 100644 --- a/packages/client/src/pages/page.vue +++ b/packages/client/src/pages/page.vue @@ -1,6 +1,6 @@ <template> <MkSpacer :content-max="700"> - <transition name="fade" mode="out-in"> + <transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="page" :key="page.id" v-size="{ max: [450] }" class="xcukqgmh"> <div class="_block main"> <!-- diff --git a/packages/client/src/pages/preview.vue b/packages/client/src/pages/preview.vue index 8eb4549516..4accac4192 100644 --- a/packages/client/src/pages/preview.vue +++ b/packages/client/src/pages/preview.vue @@ -12,7 +12,7 @@ import { i18n } from '@/i18n'; defineExpose({ [symbols.PAGE_INFO]: computed(() => ({ - title: i18n.locale.preview, + title: i18n.ts.preview, icon: 'fas fa-eye', bg: 'var(--bg)', })), diff --git a/packages/client/src/pages/reset-password.vue b/packages/client/src/pages/reset-password.vue index 8ef73858f6..7d008ae75c 100644 --- a/packages/client/src/pages/reset-password.vue +++ b/packages/client/src/pages/reset-password.vue @@ -3,10 +3,10 @@ <div class="_formRoot"> <FormInput v-model="password" type="password" class="_formBlock"> <template #prefix><i class="fas fa-lock"></i></template> - <template #label>{{ i18n.locale.newPassword }}</template> + <template #label>{{ i18n.ts.newPassword }}</template> </FormInput> - <FormButton primary class="_formBlock" @click="save">{{ i18n.locale.save }}</FormButton> + <FormButton primary class="_formBlock" @click="save">{{ i18n.ts.save }}</FormButton> </div> </MkSpacer> </template> @@ -43,7 +43,7 @@ onMounted(() => { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.resetPassword, + title: i18n.ts.resetPassword, icon: 'fas fa-lock', bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue index c795ede8ac..a744a031d4 100644 --- a/packages/client/src/pages/settings/accounts.vue +++ b/packages/client/src/pages/settings/accounts.vue @@ -64,7 +64,7 @@ export default defineComponent({ icon: 'fas fa-trash-alt', danger: true, action: () => this.removeAccount(account), - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); }, addAccount(ev) { @@ -74,7 +74,7 @@ export default defineComponent({ }, { text: this.$ts.createAccount, action: () => { this.createAccount(); }, - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); }, addExistingAccount() { diff --git a/packages/client/src/pages/settings/email.vue b/packages/client/src/pages/settings/email.vue index 54557f8773..4697fec9b7 100644 --- a/packages/client/src/pages/settings/email.vue +++ b/packages/client/src/pages/settings/email.vue @@ -62,7 +62,7 @@ export default defineComponent({ const emailAddress = ref($i.email); const INFO = { - title: i18n.locale.email, + title: i18n.ts.email, icon: 'fas fa-envelope', bg: 'var(--bg)', }; @@ -75,7 +75,7 @@ export default defineComponent({ const saveEmailAddress = () => { os.inputText({ - title: i18n.locale.password, + title: i18n.ts.password, type: 'password' }).then(({ canceled, result: password }) => { if (canceled) return; diff --git a/packages/client/src/pages/settings/import-export.vue b/packages/client/src/pages/settings/import-export.vue index 21031c559e..c153b4d28c 100644 --- a/packages/client/src/pages/settings/import-export.vue +++ b/packages/client/src/pages/settings/import-export.vue @@ -60,7 +60,7 @@ export default defineComponent({ setup(props, context) { const INFO = { - title: i18n.locale.importAndExport, + title: i18n.ts.importAndExport, icon: 'fas fa-boxes', bg: 'var(--bg)', }; @@ -71,14 +71,14 @@ export default defineComponent({ const onExportSuccess = () => { os.alert({ type: 'info', - text: i18n.locale.exportRequested, + text: i18n.ts.exportRequested, }); }; const onImportSuccess = () => { os.alert({ type: 'info', - text: i18n.locale.importRequested, + text: i18n.ts.importRequested, }); }; @@ -114,22 +114,22 @@ export default defineComponent({ }; const importFollowing = async (ev) => { - const file = await selectFile(ev.currentTarget || ev.target); + const file = await selectFile(ev.currentTarget ?? ev.target); os.api('i/import-following', { fileId: file.id }).then(onImportSuccess).catch(onError); }; const importUserLists = async (ev) => { - const file = await selectFile(ev.currentTarget || ev.target); + const file = await selectFile(ev.currentTarget ?? ev.target); os.api('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError); }; const importMuting = async (ev) => { - const file = await selectFile(ev.currentTarget || ev.target); + const file = await selectFile(ev.currentTarget ?? ev.target); os.api('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError); }; const importBlocking = async (ev) => { - const file = await selectFile(ev.currentTarget || ev.target); + const file = await selectFile(ev.currentTarget ?? ev.target); os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); }; diff --git a/packages/client/src/pages/settings/index.vue b/packages/client/src/pages/settings/index.vue index 66c8b147bb..ac8414ddbc 100644 --- a/packages/client/src/pages/settings/index.vue +++ b/packages/client/src/pages/settings/index.vue @@ -49,7 +49,7 @@ export default defineComponent({ setup(props, context) { const indexInfo = { - title: i18n.locale.settings, + title: i18n.ts.settings, icon: 'fas fa-cog', bg: 'var(--bg)', hideHeader: true, @@ -61,96 +61,96 @@ export default defineComponent({ const el = ref(null); const childInfo = ref(null); const menuDef = computed(() => [{ - title: i18n.locale.basicSettings, + title: i18n.ts.basicSettings, items: [{ icon: 'fas fa-user', - text: i18n.locale.profile, + text: i18n.ts.profile, to: '/settings/profile', active: page.value === 'profile', }, { icon: 'fas fa-lock-open', - text: i18n.locale.privacy, + text: i18n.ts.privacy, to: '/settings/privacy', active: page.value === 'privacy', }, { icon: 'fas fa-laugh', - text: i18n.locale.reaction, + text: i18n.ts.reaction, to: '/settings/reaction', active: page.value === 'reaction', }, { icon: 'fas fa-cloud', - text: i18n.locale.drive, + text: i18n.ts.drive, to: '/settings/drive', active: page.value === 'drive', }, { icon: 'fas fa-bell', - text: i18n.locale.notifications, + text: i18n.ts.notifications, to: '/settings/notifications', active: page.value === 'notifications', }, { icon: 'fas fa-envelope', - text: i18n.locale.email, + text: i18n.ts.email, to: '/settings/email', active: page.value === 'email', }, { icon: 'fas fa-share-alt', - text: i18n.locale.integration, + text: i18n.ts.integration, to: '/settings/integration', active: page.value === 'integration', }, { icon: 'fas fa-lock', - text: i18n.locale.security, + text: i18n.ts.security, to: '/settings/security', active: page.value === 'security', }], }, { - title: i18n.locale.clientSettings, + title: i18n.ts.clientSettings, items: [{ icon: 'fas fa-cogs', - text: i18n.locale.general, + text: i18n.ts.general, to: '/settings/general', active: page.value === 'general', }, { icon: 'fas fa-palette', - text: i18n.locale.theme, + text: i18n.ts.theme, to: '/settings/theme', active: page.value === 'theme', }, { icon: 'fas fa-list-ul', - text: i18n.locale.menu, + text: i18n.ts.menu, to: '/settings/menu', active: page.value === 'menu', }, { icon: 'fas fa-music', - text: i18n.locale.sounds, + text: i18n.ts.sounds, to: '/settings/sounds', active: page.value === 'sounds', }, { icon: 'fas fa-plug', - text: i18n.locale.plugins, + text: i18n.ts.plugins, to: '/settings/plugin', active: page.value === 'plugin', }], }, { - title: i18n.locale.otherSettings, + title: i18n.ts.otherSettings, items: [{ icon: 'fas fa-boxes', - text: i18n.locale.importAndExport, + text: i18n.ts.importAndExport, to: '/settings/import-export', active: page.value === 'import-export', }, { icon: 'fas fa-volume-mute', - text: i18n.locale.instanceMute, + text: i18n.ts.instanceMute, to: '/settings/instance-mute', active: page.value === 'instance-mute', }, { icon: 'fas fa-ban', - text: i18n.locale.muteAndBlock, + text: i18n.ts.muteAndBlock, to: '/settings/mute-block', active: page.value === 'mute-block', }, { icon: 'fas fa-comment-slash', - text: i18n.locale.wordMute, + text: i18n.ts.wordMute, to: '/settings/word-mute', active: page.value === 'word-mute', }, { @@ -160,7 +160,7 @@ export default defineComponent({ active: page.value === 'api', }, { icon: 'fas fa-ellipsis-h', - text: i18n.locale.other, + text: i18n.ts.other, to: '/settings/other', active: page.value === 'other', }], @@ -168,7 +168,7 @@ export default defineComponent({ items: [{ type: 'button', icon: 'fas fa-trash', - text: i18n.locale.clearCache, + text: i18n.ts.clearCache, action: () => { localStorage.removeItem('locale'); localStorage.removeItem('theme'); @@ -177,7 +177,7 @@ export default defineComponent({ }, { type: 'button', icon: 'fas fa-sign-in-alt fa-flip-horizontal', - text: i18n.locale.logout, + text: i18n.ts.logout, action: () => { signout(); }, diff --git a/packages/client/src/pages/settings/mute-block.vue b/packages/client/src/pages/settings/mute-block.vue index f4f9ebf8dd..28d11809e3 100644 --- a/packages/client/src/pages/settings/mute-block.vue +++ b/packages/client/src/pages/settings/mute-block.vue @@ -52,7 +52,7 @@ const blockingPagination = { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.muteAndBlock, + title: i18n.ts.muteAndBlock, icon: 'fas fa-ban', bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/settings/privacy.vue b/packages/client/src/pages/settings/privacy.vue index dd13ba4bd0..cfae7e9ca8 100644 --- a/packages/client/src/pages/settings/privacy.vue +++ b/packages/client/src/pages/settings/privacy.vue @@ -86,7 +86,7 @@ function save() { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.privacy, + title: i18n.ts.privacy, icon: 'fas fa-lock-open', bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue index f875146a2c..66b654d87f 100644 --- a/packages/client/src/pages/settings/profile.vue +++ b/packages/client/src/pages/settings/profile.vue @@ -3,45 +3,45 @@ <div class="llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }"> <div class="avatar _acrylic"> <MkAvatar class="avatar" :user="$i" :disable-link="true" @click="changeAvatar"/> - <MkButton primary class="avatarEdit" @click="changeAvatar">{{ i18n.locale._profile.changeAvatar }}</MkButton> + <MkButton primary class="avatarEdit" @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton> </div> - <MkButton primary class="bannerEdit" @click="changeBanner">{{ i18n.locale._profile.changeBanner }}</MkButton> + <MkButton primary class="bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton> </div> <FormInput v-model="profile.name" :max="30" manual-save class="_formBlock"> - <template #label>{{ i18n.locale._profile.name }}</template> + <template #label>{{ i18n.ts._profile.name }}</template> </FormInput> <FormTextarea v-model="profile.description" :max="500" tall manual-save class="_formBlock"> - <template #label>{{ i18n.locale._profile.description }}</template> - <template #caption>{{ i18n.locale._profile.youCanIncludeHashtags }}</template> + <template #label>{{ i18n.ts._profile.description }}</template> + <template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template> </FormTextarea> <FormInput v-model="profile.location" manual-save class="_formBlock"> - <template #label>{{ i18n.locale.location }}</template> + <template #label>{{ i18n.ts.location }}</template> <template #prefix><i class="fas fa-map-marker-alt"></i></template> </FormInput> <FormInput v-model="profile.birthday" type="date" manual-save class="_formBlock"> - <template #label>{{ i18n.locale.birthday }}</template> + <template #label>{{ i18n.ts.birthday }}</template> <template #prefix><i class="fas fa-birthday-cake"></i></template> </FormInput> <FormSelect v-model="profile.lang" class="_formBlock"> - <template #label>{{ i18n.locale.language }}</template> + <template #label>{{ i18n.ts.language }}</template> <option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option> </FormSelect> <FormSlot> - <MkButton @click="editMetadata">{{ i18n.locale._profile.metadataEdit }}</MkButton> - <template #caption>{{ i18n.locale._profile.metadataDescription }}</template> + <MkButton @click="editMetadata">{{ i18n.ts._profile.metadataEdit }}</MkButton> + <template #caption>{{ i18n.ts._profile.metadataDescription }}</template> </FormSlot> - <FormSwitch v-model="profile.isCat" class="_formBlock">{{ i18n.locale.flagAsCat }}<template #caption>{{ i18n.locale.flagAsCatDescription }}</template></FormSwitch> + <FormSwitch v-model="profile.isCat" class="_formBlock">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></FormSwitch> - <FormSwitch v-model="profile.isBot" class="_formBlock">{{ i18n.locale.flagAsBot }}<template #caption>{{ i18n.locale.flagAsBotDescription }}</template></FormSwitch> + <FormSwitch v-model="profile.isBot" class="_formBlock">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></FormSwitch> - <FormSwitch v-model="profile.alwaysMarkNsfw" class="_formBlock">{{ i18n.locale.alwaysMarkSensitive }}</FormSwitch> + <FormSwitch v-model="profile.alwaysMarkNsfw" class="_formBlock">{{ i18n.ts.alwaysMarkSensitive }}</FormSwitch> </div> </template> @@ -102,7 +102,7 @@ function save() { } function changeAvatar(ev) { - selectFile(ev.currentTarget || ev.target, i18n.locale.avatar).then(async (file) => { + selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => { const i = await os.apiWithDialog('i/update', { avatarId: file.id, }); @@ -112,7 +112,7 @@ function changeAvatar(ev) { } function changeBanner(ev) { - selectFile(ev.currentTarget || ev.target, i18n.locale.banner).then(async (file) => { + selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => { const i = await os.apiWithDialog('i/update', { bannerId: file.id, }); @@ -122,45 +122,45 @@ function changeBanner(ev) { } async function editMetadata() { - const { canceled, result } = await os.form(i18n.locale._profile.metadata, { + const { canceled, result } = await os.form(i18n.ts._profile.metadata, { fieldName0: { type: 'string', - label: i18n.locale._profile.metadataLabel + ' 1', + label: i18n.ts._profile.metadataLabel + ' 1', default: additionalFields.fieldName0, }, fieldValue0: { type: 'string', - label: i18n.locale._profile.metadataContent + ' 1', + label: i18n.ts._profile.metadataContent + ' 1', default: additionalFields.fieldValue0, }, fieldName1: { type: 'string', - label: i18n.locale._profile.metadataLabel + ' 2', + label: i18n.ts._profile.metadataLabel + ' 2', default: additionalFields.fieldName1, }, fieldValue1: { type: 'string', - label: i18n.locale._profile.metadataContent + ' 2', + label: i18n.ts._profile.metadataContent + ' 2', default: additionalFields.fieldValue1, }, fieldName2: { type: 'string', - label: i18n.locale._profile.metadataLabel + ' 3', + label: i18n.ts._profile.metadataLabel + ' 3', default: additionalFields.fieldName2, }, fieldValue2: { type: 'string', - label: i18n.locale._profile.metadataContent + ' 3', + label: i18n.ts._profile.metadataContent + ' 3', default: additionalFields.fieldValue2, }, fieldName3: { type: 'string', - label: i18n.locale._profile.metadataLabel + ' 4', + label: i18n.ts._profile.metadataLabel + ' 4', default: additionalFields.fieldName3, }, fieldValue3: { type: 'string', - label: i18n.locale._profile.metadataContent + ' 4', + label: i18n.ts._profile.metadataContent + ' 4', default: additionalFields.fieldValue3, }, }); @@ -196,7 +196,7 @@ async function editMetadata() { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.profile, + title: i18n.ts.profile, icon: 'fas fa-user', bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue index e5b1189947..ae3e1a1187 100644 --- a/packages/client/src/pages/settings/reaction.vue +++ b/packages/client/src/pages/settings/reaction.vue @@ -44,8 +44,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { watch } from 'vue'; import XDraggable from 'vuedraggable'; import FormInput from '@/components/form/input.vue'; import FormRadios from '@/components/form/radios.vue'; @@ -56,91 +56,70 @@ import FormSwitch from '@/components/form/switch.vue'; import * as os from '@/os'; import { defaultStore } from '@/store'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormInput, - FormButton, - FromSlot, - FormRadios, - FormSection, - FormSwitch, - XDraggable, - }, +let reactions = $ref(JSON.parse(JSON.stringify(defaultStore.state.reactions))); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.reaction, - icon: 'fas fa-laugh', - action: { - icon: 'fas fa-eye', - handler: this.preview - }, - bg: 'var(--bg)', - }, - reactions: JSON.parse(JSON.stringify(this.$store.state.reactions)), - } - }, +const reactionPickerWidth = $computed(defaultStore.makeGetterSetter('reactionPickerWidth')); +const reactionPickerHeight = $computed(defaultStore.makeGetterSetter('reactionPickerHeight')); +const reactionPickerUseDrawerForMobile = $computed(defaultStore.makeGetterSetter('reactionPickerUseDrawerForMobile')); - computed: { - reactionPickerWidth: defaultStore.makeGetterSetter('reactionPickerWidth'), - reactionPickerHeight: defaultStore.makeGetterSetter('reactionPickerHeight'), - reactionPickerUseDrawerForMobile: defaultStore.makeGetterSetter('reactionPickerUseDrawerForMobile'), - }, +function save() { + defaultStore.set('reactions', reactions); +} - watch: { - reactions: { - handler() { - this.save(); - }, - deep: true +function remove(reaction, ev: MouseEvent) { + os.popupMenu([{ + text: i18n.ts.remove, + action: () => { + reactions = reactions.filter(x => x !== reaction); } - }, + }], ev.currentTarget ?? ev.target); +} - methods: { - save() { - this.$store.set('reactions', this.reactions); - }, +function preview(ev: MouseEvent) { + os.popup(import('@/components/emoji-picker-dialog.vue'), { + asReactionPicker: true, + src: ev.currentTarget ?? ev.target, + }, {}, 'closed'); +} - remove(reaction, ev) { - os.popupMenu([{ - text: this.$ts.remove, - action: () => { - this.reactions = this.reactions.filter(x => x !== reaction) - } - }], ev.currentTarget || ev.target); - }, +async function setDefault() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts.resetAreYouSure, + }); + if (canceled) return; - preview(ev) { - os.popup(import('@/components/emoji-picker-dialog.vue'), { - asReactionPicker: true, - src: ev.currentTarget || ev.target, - }, {}, 'closed'); - }, + reactions = JSON.parse(JSON.stringify(defaultStore.def.reactions.default)); +} - async setDefault() { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$ts.resetAreYouSure, - }); - if (canceled) return; +function chooseEmoji(ev: MouseEvent) { + os.pickEmoji(ev.currentTarget ?? ev.target, { + showPinned: false + }).then(emoji => { + if (!reactions.includes(emoji)) { + reactions.push(emoji); + } + }); +} - this.reactions = JSON.parse(JSON.stringify(this.$store.def.reactions.default)); - }, +watch($$(reactions), () => { + save(); +}, { + deep: true, +}); - chooseEmoji(ev) { - os.pickEmoji(ev.currentTarget || ev.target, { - showPinned: false - }).then(emoji => { - if (!this.reactions.includes(emoji)) { - this.reactions.push(emoji); - } - }); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.reaction, + icon: 'fas fa-laugh', + action: { + icon: 'fas fa-eye', + handler: preview, + }, + bg: 'var(--bg)', + }, }); </script> diff --git a/packages/client/src/pages/settings/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue index e2a3f042b9..2d3514342e 100644 --- a/packages/client/src/pages/settings/theme.install.vue +++ b/packages/client/src/pages/settings/theme.install.vue @@ -1,12 +1,12 @@ <template> <div class="_formRoot"> <FormTextarea v-model="installThemeCode" class="_formBlock"> - <template #label>{{ i18n.locale._theme.code }}</template> + <template #label>{{ i18n.ts._theme.code }}</template> </FormTextarea> <div class="_formBlock" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> - <FormButton :disabled="installThemeCode == null" inline @click="() => preview(installThemeCode)"><i class="fas fa-eye"></i> {{ i18n.locale.preview }}</FormButton> - <FormButton :disabled="installThemeCode == null" primary inline @click="() => install(installThemeCode)"><i class="fas fa-check"></i> {{ i18n.locale.install }}</FormButton> + <FormButton :disabled="installThemeCode == null" inline @click="() => preview(installThemeCode)"><i class="fas fa-eye"></i> {{ i18n.ts.preview }}</FormButton> + <FormButton :disabled="installThemeCode == null" primary inline @click="() => install(installThemeCode)"><i class="fas fa-check"></i> {{ i18n.ts.install }}</FormButton> </div> </div> </template> @@ -32,21 +32,21 @@ function parseThemeCode(code: string) { } catch (e) { os.alert({ type: 'error', - text: i18n.locale._theme.invalid + text: i18n.ts._theme.invalid }); return false; } if (!validateTheme(theme)) { os.alert({ type: 'error', - text: i18n.locale._theme.invalid + text: i18n.ts._theme.invalid }); return false; } if (getThemes().some(t => t.id === theme.id)) { os.alert({ type: 'info', - text: i18n.locale._theme.alreadyInstalled + text: i18n.ts._theme.alreadyInstalled }); return false; } @@ -71,7 +71,7 @@ async function install(code: string): Promise<void> { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale._theme.install, + title: i18n.ts._theme.install, icon: 'fas fa-download', bg: 'var(--bg)', }, diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue index 658e36ec05..3e4ec1b2af 100644 --- a/packages/client/src/pages/settings/theme.vue +++ b/packages/client/src/pages/settings/theme.vue @@ -116,7 +116,7 @@ export default defineComponent({ setup(props, { emit }) { const INFO = { - title: i18n.locale.theme, + title: i18n.ts.theme, icon: 'fas fa-palette', bg: 'var(--bg)', }; @@ -184,7 +184,7 @@ export default defineComponent({ themesCount, wallpaper, setWallpaper(e) { - selectFile(e.currentTarget || e.target, null).then(file => { + selectFile(e.currentTarget ?? e.target, null).then(file => { wallpaper.value = file.url; }); }, diff --git a/packages/client/src/pages/share.vue b/packages/client/src/pages/share.vue index 5df6256fb2..4d77de5819 100644 --- a/packages/client/src/pages/share.vue +++ b/packages/client/src/pages/share.vue @@ -5,7 +5,7 @@ <XPostForm v-if="state === 'writing'" fixed - :share="true" + :instant="true" :initial-text="initialText" :initial-visibility="visibility" :initial-files="files" diff --git a/packages/client/src/pages/signup-complete.vue b/packages/client/src/pages/signup-complete.vue index a10af1a4cc..344c9195f7 100644 --- a/packages/client/src/pages/signup-complete.vue +++ b/packages/client/src/pages/signup-complete.vue @@ -1,6 +1,6 @@ <template> <div> - {{ i18n.locale.processing }} + {{ i18n.ts.processing }} </div> </template> @@ -18,7 +18,7 @@ const props = defineProps<{ onMounted(async () => { await os.alert({ type: 'info', - text: i18n.t('clickToFinishEmailVerification', { ok: i18n.locale.gotIt }), + text: i18n.t('clickToFinishEmailVerification', { ok: i18n.ts.gotIt }), }); const res = await os.apiWithDialog('signup-pending', { code: props.code, @@ -28,7 +28,7 @@ onMounted(async () => { defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.signup, + title: i18n.ts.signup, icon: 'fas fa-user', }, }); diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue index 80b8c7806c..a53e23c1c5 100644 --- a/packages/client/src/pages/theme-editor.vue +++ b/packages/client/src/pages/theme-editor.vue @@ -2,7 +2,7 @@ <MkSpacer :content-max="800" :margin-min="16" :margin-max="32"> <div class="cwepdizn _formRoot"> <FormFolder :default-open="true" class="_formBlock"> - <template #label>{{ i18n.locale.backgroundColor }}</template> + <template #label>{{ i18n.ts.backgroundColor }}</template> <div class="cwepdizn-colors"> <div class="row"> <button v-for="color in bgColors.filter(x => x.kind === 'light')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)"> @@ -18,7 +18,7 @@ </FormFolder> <FormFolder :default-open="true" class="_formBlock"> - <template #label>{{ i18n.locale.accentColor }}</template> + <template #label>{{ i18n.ts.accentColor }}</template> <div class="cwepdizn-colors"> <div class="row"> <button v-for="color in accentColors" :key="color" class="color rounded _button" :class="{ active: theme.props.accent === color }" @click="setAccentColor(color)"> @@ -29,7 +29,7 @@ </FormFolder> <FormFolder :default-open="true" class="_formBlock"> - <template #label>{{ i18n.locale.textColor }}</template> + <template #label>{{ i18n.ts.textColor }}</template> <div class="cwepdizn-colors"> <div class="row"> <button v-for="color in fgColors" :key="color" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }" @click="setFgColor(color)"> @@ -41,22 +41,22 @@ <FormFolder :default-open="false" class="_formBlock"> <template #icon><i class="fas fa-code"></i></template> - <template #label>{{ i18n.locale.editCode }}</template> + <template #label>{{ i18n.ts.editCode }}</template> <div class="_formRoot"> <FormTextarea v-model="themeCode" tall class="_formBlock"> - <template #label>{{ i18n.locale._theme.code }}</template> + <template #label>{{ i18n.ts._theme.code }}</template> </FormTextarea> - <FormButton primary class="_formBlock" @click="applyThemeCode">{{ i18n.locale.apply }}</FormButton> + <FormButton primary class="_formBlock" @click="applyThemeCode">{{ i18n.ts.apply }}</FormButton> </div> </FormFolder> <FormFolder :default-open="false" class="_formBlock"> - <template #label>{{ i18n.locale.addDescription }}</template> + <template #label>{{ i18n.ts.addDescription }}</template> <div class="_formRoot"> <FormTextarea v-model="description"> - <template #label>{{ i18n.locale._theme.description }}</template> + <template #label>{{ i18n.ts._theme.description }}</template> </FormTextarea> </div> </FormFolder> @@ -167,7 +167,7 @@ function applyThemeCode() { } catch (err) { os.alert({ type: 'error', - text: i18n.locale._theme.invalid, + text: i18n.ts._theme.invalid, }); return; } @@ -177,7 +177,7 @@ function applyThemeCode() { async function saveAs() { const { canceled, result: name } = await os.inputText({ - title: i18n.locale.name, + title: i18n.ts.name, allowEmpty: false, }); if (canceled) return; @@ -204,18 +204,18 @@ watch($$(theme), apply, { deep: true }); defineExpose({ [symbols.PAGE_INFO]: { - title: i18n.locale.themeEditor, + title: i18n.ts.themeEditor, icon: 'fas fa-palette', bg: 'var(--bg)', actions: [{ asFullButton: true, icon: 'fas fa-eye', - text: i18n.locale.preview, + text: i18n.ts.preview, handler: showPreview, }, { asFullButton: true, icon: 'fas fa-check', - text: i18n.locale.saveAs, + text: i18n.ts.saveAs, handler: saveAs, }], }, diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue index ecd1ae6257..b2266d22c3 100644 --- a/packages/client/src/pages/timeline.vue +++ b/packages/client/src/pages/timeline.vue @@ -17,6 +17,12 @@ </MkSpacer> </template> +<script lang="ts"> +export default { + name: 'MkTimelinePage', +} +</script> + <script lang="ts" setup> import { defineAsyncComponent, computed, watch } from 'vue'; import XTimeline from '@/components/timeline.vue'; @@ -58,7 +64,7 @@ async function chooseList(ev: MouseEvent): Promise<void> { text: list.name, to: `/timeline/list/${list.id}`, })); - os.popupMenu(items, ev.currentTarget || ev.target); + os.popupMenu(items, ev.currentTarget ?? ev.target); } async function chooseAntenna(ev: MouseEvent): Promise<void> { @@ -69,7 +75,7 @@ async function chooseAntenna(ev: MouseEvent): Promise<void> { indicate: antenna.hasUnreadNote, to: `/timeline/antenna/${antenna.id}`, })); - os.popupMenu(items, ev.currentTarget || ev.target); + os.popupMenu(items, ev.currentTarget ?? ev.target); } async function chooseChannel(ev: MouseEvent): Promise<void> { @@ -80,7 +86,7 @@ async function chooseChannel(ev: MouseEvent): Promise<void> { indicate: channel.hasUnreadNote, to: `/channels/${channel.id}`, })); - os.popupMenu(items, ev.currentTarget || ev.target); + os.popupMenu(items, ev.currentTarget ?? ev.target); } function saveSrc(): void { @@ -91,7 +97,7 @@ function saveSrc(): void { async function timetravel(): Promise<void> { const { canceled, result: date } = await os.inputDate({ - title: i18n.locale.date, + title: i18n.ts.date, }); if (canceled) return; @@ -104,47 +110,47 @@ function focus(): void { defineExpose({ [symbols.PAGE_INFO]: computed(() => ({ - title: i18n.locale.timeline, + title: i18n.ts.timeline, icon: src === 'local' ? 'fas fa-comments' : src === 'social' ? 'fas fa-share-alt' : src === 'global' ? 'fas fa-globe' : 'fas fa-home', bg: 'var(--bg)', actions: [{ icon: 'fas fa-list-ul', - text: i18n.locale.lists, + text: i18n.ts.lists, handler: chooseList, }, { icon: 'fas fa-satellite', - text: i18n.locale.antennas, + text: i18n.ts.antennas, handler: chooseAntenna, }, { icon: 'fas fa-satellite-dish', - text: i18n.locale.channel, + text: i18n.ts.channel, handler: chooseChannel, }, { icon: 'fas fa-calendar-alt', - text: i18n.locale.jumpToSpecifiedDate, + text: i18n.ts.jumpToSpecifiedDate, handler: timetravel, }], tabs: [{ active: src === 'home', - title: i18n.locale._timelines.home, + title: i18n.ts._timelines.home, icon: 'fas fa-home', iconOnly: true, onClick: () => { src = 'home'; saveSrc(); }, }, ...(isLocalTimelineAvailable ? [{ active: src === 'local', - title: i18n.locale._timelines.local, + title: i18n.ts._timelines.local, icon: 'fas fa-comments', iconOnly: true, onClick: () => { src = 'local'; saveSrc(); }, }, { active: src === 'social', - title: i18n.locale._timelines.social, + title: i18n.ts._timelines.social, icon: 'fas fa-share-alt', iconOnly: true, onClick: () => { src = 'social'; saveSrc(); }, }] : []), ...(isGlobalTimelineAvailable ? [{ active: src === 'global', - title: i18n.locale._timelines.global, + title: i18n.ts._timelines.global, icon: 'fas fa-globe', iconOnly: true, onClick: () => { src = 'global'; saveSrc(); }, diff --git a/packages/client/src/pages/user/index.vue b/packages/client/src/pages/user/index.vue index 0b96368587..10a86243f9 100644 --- a/packages/client/src/pages/user/index.vue +++ b/packages/client/src/pages/user/index.vue @@ -1,196 +1,125 @@ <template> <div> -<transition name="fade" mode="out-in"> - <div v-if="user && narrow === false" class="ftskorzw wide"> - <MkRemoteCaution v-if="user.host != null" :href="user.url"/> + <transition name="fade" mode="out-in"> + <MkSpacer v-if="user" :content-max="narrow ? 800 : 1100"> + <div v-size="{ max: [500] }" class="ftskorzw" :class="{ wide: !narrow }"> + <div class="main"> + <!-- TODO --> + <!-- <div class="punished" v-if="user.isSuspended"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSuspended }}</div> --> + <!-- <div class="punished" v-if="user.isSilenced"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSilenced }}</div> --> - <div class="banner-container" :style="style"> - <div ref="banner" class="banner" :style="style"></div> - </div> - <div class="contents"> - <div class="side _forceContainerFull_"> - <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/> - <div class="name"> - <MkUserName :user="user" :nowrap="false" class="name"/> - <MkAcct :user="user" :detail="true" class="acct"/> - </div> - <div v-if="$i && $i.id != user.id && user.isFollowed" class="followed"><span>{{ $ts.followsYou }}</span></div> - <div class="status"> - <MkA :to="userPage(user)" :class="{ active: page === 'index' }"> - <b>{{ number(user.notesCount) }}</b> - <span>{{ $ts.notes }}</span> - </MkA> - <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> - <b>{{ number(user.followingCount) }}</b> - <span>{{ $ts.following }}</span> - </MkA> - <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> - <b>{{ number(user.followersCount) }}</b> - <span>{{ $ts.followers }}</span> - </MkA> - </div> - <div class="description"> - <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> - <p v-else class="empty">{{ $ts.noAccountDescription }}</p> - </div> - <div class="fields system"> - <dl v-if="user.location" class="field"> - <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> - <dd class="value">{{ user.location }}</dd> - </dl> - <dl v-if="user.birthday" class="field"> - <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt> - <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> - </dl> - <dl class="field"> - <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt> - <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd> - </dl> - </div> - <div v-if="user.fields.length > 0" class="fields"> - <dl v-for="(field, i) in user.fields" :key="i" class="field"> - <dt class="name"> - <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> - </dt> - <dd class="value"> - <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> - </dd> - </dl> - </div> - <XActivity :key="user.id" :user="user" class="_gap"/> - <XPhotos :key="user.id" :user="user" class="_gap"/> - </div> - <div class="main"> - <div class="actions"> - <button class="menu _button" @click="menu"><i class="fas fa-ellipsis-h"></i></button> - <MkFollowButton v-if="!$i || $i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/> - </div> - <template v-if="page === 'index'"> - <div v-if="user.pinnedNotes.length > 0" class="_gap"> - <XNote v-for="note in user.pinnedNotes" :key="note.id" class="note _gap" :note="note" :pinned="true" @update:note="pinnedNoteUpdated(note, $event)"/> - </div> - <div class="_gap"> - <XUserTimeline :user="user"/> - </div> - </template> - <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_gap"/> - <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_gap"/> - <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> - <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> - </div> - </div> - </div> - <MkSpacer v-else-if="user && narrow === true" :content-max="800"> - <div v-size="{ max: [500] }" class="ftskorzw narrow"> - <!-- TODO --> - <!-- <div class="punished" v-if="user.isSuspended"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSuspended }}</div> --> - <!-- <div class="punished" v-if="user.isSilenced"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSilenced }}</div> --> + <div class="profile"> + <MkRemoteCaution v-if="user.host != null" :href="user.url" class="warn"/> - <div class="profile"> - <MkRemoteCaution v-if="user.host != null" :href="user.url" class="warn"/> - - <div :key="user.id" class="_block main"> - <div class="banner-container" :style="style"> - <div ref="banner" class="banner" :style="style"></div> - <div class="fade"></div> - <div class="title"> - <MkUserName class="name" :user="user" :nowrap="true"/> - <div class="bottom"> - <span class="username"><MkAcct :user="user" :detail="true" /></span> - <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> - <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> - <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> - <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> + <div :key="user.id" class="_block main"> + <div class="banner-container" :style="style"> + <div ref="banner" class="banner" :style="style"></div> + <div class="fade"></div> + <div class="title"> + <MkUserName class="name" :user="user" :nowrap="true"/> + <div class="bottom"> + <span class="username"><MkAcct :user="user" :detail="true" /></span> + <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> + <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> + <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> + <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> + </div> + </div> + <span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span> + <div v-if="$i" class="actions"> + <button class="menu _button" @click="menu"><i class="fas fa-ellipsis-h"></i></button> + <MkFollowButton v-if="$i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/> + </div> + </div> + <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/> + <div class="title"> + <MkUserName :user="user" :nowrap="false" class="name"/> + <div class="bottom"> + <span class="username"><MkAcct :user="user" :detail="true" /></span> + <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> + <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> + <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> + <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> + </div> + </div> + <div class="description"> + <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> + <p v-else class="empty">{{ $ts.noAccountDescription }}</p> + </div> + <div class="fields system"> + <dl v-if="user.location" class="field"> + <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> + <dd class="value">{{ user.location }}</dd> + </dl> + <dl v-if="user.birthday" class="field"> + <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt> + <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> + </dl> + <dl class="field"> + <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt> + <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd> + </dl> + </div> + <div v-if="user.fields.length > 0" class="fields"> + <dl v-for="(field, i) in user.fields" :key="i" class="field"> + <dt class="name"> + <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> + </dt> + <dd class="value"> + <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> + </dd> + </dl> + </div> + <div class="status"> + <MkA v-click-anime :to="userPage(user)" :class="{ active: page === 'index' }"> + <b>{{ number(user.notesCount) }}</b> + <span>{{ $ts.notes }}</span> + </MkA> + <MkA v-click-anime :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> + <b>{{ number(user.followingCount) }}</b> + <span>{{ $ts.following }}</span> + </MkA> + <MkA v-click-anime :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> + <b>{{ number(user.followersCount) }}</b> + <span>{{ $ts.followers }}</span> + </MkA> </div> - </div> - <span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span> - <div v-if="$i" class="actions"> - <button class="menu _button" @click="menu"><i class="fas fa-ellipsis-h"></i></button> - <MkFollowButton v-if="$i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/> - </div> - </div> - <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/> - <div class="title"> - <MkUserName :user="user" :nowrap="false" class="name"/> - <div class="bottom"> - <span class="username"><MkAcct :user="user" :detail="true" /></span> - <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> - <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> - <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> - <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> </div> </div> - <div class="description"> - <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> - <p v-else class="empty">{{ $ts.noAccountDescription }}</p> - </div> - <div class="fields system"> - <dl v-if="user.location" class="field"> - <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> - <dd class="value">{{ user.location }}</dd> - </dl> - <dl v-if="user.birthday" class="field"> - <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt> - <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> - </dl> - <dl class="field"> - <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt> - <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd> - </dl> - </div> - <div v-if="user.fields.length > 0" class="fields"> - <dl v-for="(field, i) in user.fields" :key="i" class="field"> - <dt class="name"> - <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> - </dt> - <dd class="value"> - <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> - </dd> - </dl> - </div> - <div class="status"> - <MkA v-click-anime :to="userPage(user)" :class="{ active: page === 'index' }"> - <b>{{ number(user.notesCount) }}</b> - <span>{{ $ts.notes }}</span> - </MkA> - <MkA v-click-anime :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> - <b>{{ number(user.followingCount) }}</b> - <span>{{ $ts.following }}</span> - </MkA> - <MkA v-click-anime :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> - <b>{{ number(user.followersCount) }}</b> - <span>{{ $ts.followers }}</span> - </MkA> - </div> - </div> - </div> - <div class="contents"> - <template v-if="page === 'index'"> - <div> - <div v-if="user.pinnedNotes.length > 0" class="_gap"> - <XNote v-for="note in user.pinnedNotes" :key="note.id" class="note _block" :note="note" :pinned="true" @update:note="pinnedNoteUpdated(note, $event)"/> - </div> - <MkInfo v-else-if="$i && $i.id === user.id">{{ $ts.userPagePinTip }}</MkInfo> - <XPhotos :key="user.id" :user="user"/> - <XActivity :key="user.id" :user="user" style="margin-top: var(--margin);"/> - </div> - <div> - <XUserTimeline :user="user"/> + <div class="contents"> + <template v-if="page === 'index'"> + <div> + <div v-if="user.pinnedNotes.length > 0" class="_gap"> + <XNote v-for="note in user.pinnedNotes" :key="note.id" class="note _block" :note="note" :pinned="true"/> + </div> + <MkInfo v-else-if="$i && $i.id === user.id">{{ $ts.userPagePinTip }}</MkInfo> + <template v-if="narrow"> + <XPhotos :key="user.id" :user="user"/> + <XActivity :key="user.id" :user="user" style="margin-top: var(--margin);"/> + </template> + </div> + <div> + <XUserTimeline :user="user"/> + </div> + </template> + <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _gap"/> + <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/> + <XReactions v-else-if="page === 'reactions'" :user="user" class="_gap"/> + <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> + <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> + <XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/> </div> - </template> - <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _gap"/> - <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/> - <XReactions v-else-if="page === 'reactions'" :user="user" class="_gap"/> - <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> - <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> - <XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/> + </div> + <div v-if="!narrow" class="sub"> + <XPhotos :key="user.id" :user="user"/> + <XActivity :key="user.id" :user="user" style="margin-top: var(--margin);"/> + </div> </div> - </div> - </MkSpacer> - <MkError v-else-if="error" @retry="fetch()"/> - <MkLoading v-else/> -</transition> + </MkSpacer> + <MkError v-else-if="error" @retry="fetch()"/> + <MkLoading v-else/> + </transition> </div> </template> @@ -314,7 +243,7 @@ export default defineComponent({ mounted() { window.requestAnimationFrame(this.parallaxLoop); - this.narrow = true//this.$el.clientWidth < 1000; + this.narrow = this.$el.clientWidth < 1000; }, beforeUnmount() { @@ -335,7 +264,7 @@ export default defineComponent({ }, menu(ev) { - os.popupMenu(getUserMenu(this.user), ev.currentTarget || ev.target); + os.popupMenu(getUserMenu(this.user), ev.currentTarget ?? ev.target); }, parallaxLoop() { @@ -356,11 +285,6 @@ export default defineComponent({ banner.style.backgroundPosition = `center calc(50% - ${pos}px)`; }, - pinnedNoteUpdated(oldValue, newValue) { - const i = this.user.pinnedNotes.findIndex(n => n === oldValue); - this.user.pinnedNotes[i] = newValue; - }, - number, userPage @@ -378,447 +302,289 @@ export default defineComponent({ opacity: 0; } -.ftskorzw.wide { +.ftskorzw { - > .banner-container { - position: relative; - height: 300px; - overflow: hidden; - background-size: cover; - background-position: center; + > .main { - > .banner { - height: 100%; - background-color: #4c5e6d; - background-size: cover; - background-position: center; - box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset; - will-change: background-position; + > .punished { + font-size: 0.8em; + padding: 16px; } - } - - > .contents { - display: flex; - padding: 16px; - - > .side { - width: 360px; - > .avatar { - display: block; - width: 180px; - height: 180px; - margin: -130px auto 0 auto; - } - - > .name { - padding: 16px 0px 20px 0; - text-align: center; - - > .name { - display: block; - font-size: 1.75em; - font-weight: bold; - } - } - - > .followed { - text-align: center; - - > span { - display: inline-block; - font-size: 80%; - padding: 8px 12px; - margin-bottom: 20px; - border: solid 0.5px var(--divider); - border-radius: 999px; - } - } + > .profile { - > .status { - display: flex; - padding: 20px 16px; - border-top: solid 0.5px var(--divider); - font-size: 90%; + > .main { + position: relative; + overflow: hidden; - > a { - flex: 1; - text-align: center; + > .banner-container { + position: relative; + height: 250px; + overflow: hidden; + background-size: cover; + background-position: center; - &.active { - color: var(--accent); + > .banner { + height: 100%; + background-color: #4c5e6d; + background-size: cover; + background-position: center; + box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset; + will-change: background-position; } - &:hover { - text-decoration: none; + > .fade { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 78px; + background: linear-gradient(transparent, rgba(#000, 0.7)); } - > b { - display: block; - line-height: 16px; + > .followed { + position: absolute; + top: 12px; + left: 12px; + padding: 4px 8px; + color: #fff; + background: rgba(0, 0, 0, 0.7); + font-size: 0.7em; + border-radius: 6px; } - > span { - font-size: 75%; - } - } - } + > .actions { + position: absolute; + top: 12px; + right: 12px; + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); + background: rgba(0, 0, 0, 0.2); + padding: 8px; + border-radius: 24px; - > .description { - padding: 20px 16px; - border-top: solid 0.5px var(--divider); - font-size: 90%; - } + > .menu { + vertical-align: bottom; + height: 31px; + width: 31px; + color: #fff; + text-shadow: 0 0 8px #000; + font-size: 16px; + } - > .fields { - padding: 20px 16px; - border-top: solid 0.5px var(--divider); - font-size: 90%; + > .koudoku { + margin-left: 4px; + vertical-align: bottom; + } + } - > .field { - display: flex; - padding: 0; - margin: 0; - align-items: center; + > .title { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + padding: 0 0 8px 154px; + box-sizing: border-box; + color: #fff; - &:not(:last-child) { - margin-bottom: 8px; - } + > .name { + display: block; + margin: 0; + line-height: 32px; + font-weight: bold; + font-size: 1.8em; + text-shadow: 0 0 8px #000; + } - > .name { - width: 30%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - font-weight: bold; - } + > .bottom { + > * { + display: inline-block; + margin-right: 16px; + line-height: 20px; + opacity: 0.8; - > .value { - width: 70%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - margin: 0; + &.username { + font-weight: bold; + } + } + } } } - } - } - > .main { - flex: 1; - margin-left: var(--margin); - min-width: 0; - - > .nav { - display: flex; - align-items: center; - margin-top: var(--margin); - //font-size: 120%; - font-weight: bold; - - > .link { - display: inline-block; - padding: 15px 24px 12px 24px; + > .title { + display: none; text-align: center; - border-bottom: solid 3px transparent; - - &:hover { - text-decoration: none; - } - - &.active { - color: var(--accent); - border-bottom-color: var(--accent); - } + padding: 50px 8px 16px 8px; + font-weight: bold; + border-bottom: solid 0.5px var(--divider); - &:not(.active):hover { - color: var(--fgHighlighted); + > .bottom { + > * { + display: inline-block; + margin-right: 8px; + opacity: 0.8; + } } + } - > .icon { - margin-right: 6px; - } + > .avatar { + display: block; + position: absolute; + top: 170px; + left: 16px; + z-index: 2; + width: 120px; + height: 120px; + box-shadow: 1px 1px 3px rgba(#000, 0.2); } - > .actions { - display: flex; - align-items: center; - margin-left: auto; + > .description { + padding: 24px 24px 24px 154px; + font-size: 0.95em; - > .menu { - padding: 12px 16px; + > .empty { + margin: 0; + opacity: 0.5; } } - } - } - } -} - -.ftskorzw.narrow { - box-sizing: border-box; - overflow: clip; - background: var(--bg); - - > .punished { - font-size: 0.8em; - padding: 16px; - } - - > .profile { - - > .main { - position: relative; - overflow: hidden; - > .banner-container { - position: relative; - height: 250px; - overflow: hidden; - background-size: cover; - background-position: center; - - > .banner { - height: 100%; - background-color: #4c5e6d; - background-size: cover; - background-position: center; - box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset; - will-change: background-position; - } + > .fields { + padding: 24px; + font-size: 0.9em; + border-top: solid 0.5px var(--divider); - > .fade { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 78px; - background: linear-gradient(transparent, rgba(#000, 0.7)); - } + > .field { + display: flex; + padding: 0; + margin: 0; + align-items: center; - > .followed { - position: absolute; - top: 12px; - left: 12px; - padding: 4px 8px; - color: #fff; - background: rgba(0, 0, 0, 0.7); - font-size: 0.7em; - border-radius: 6px; - } + &:not(:last-child) { + margin-bottom: 8px; + } - > .actions { - position: absolute; - top: 12px; - right: 12px; - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); - background: rgba(0, 0, 0, 0.2); - padding: 8px; - border-radius: 24px; + > .name { + width: 30%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-weight: bold; + text-align: center; + } - > .menu { - vertical-align: bottom; - height: 31px; - width: 31px; - color: #fff; - text-shadow: 0 0 8px #000; - font-size: 16px; + > .value { + width: 70%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin: 0; + } } - > .koudoku { - margin-left: 4px; - vertical-align: bottom; + &.system > .field > .name { } } - > .title { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - padding: 0 0 8px 154px; - box-sizing: border-box; - color: #fff; + > .status { + display: flex; + padding: 24px; + border-top: solid 0.5px var(--divider); - > .name { - display: block; - margin: 0; - line-height: 32px; - font-weight: bold; - font-size: 1.8em; - text-shadow: 0 0 8px #000; - } + > a { + flex: 1; + text-align: center; - > .bottom { - > * { - display: inline-block; - margin-right: 16px; - line-height: 20px; - opacity: 0.8; + &.active { + color: var(--accent); + } - &.username { - font-weight: bold; - } + &:hover { + text-decoration: none; } - } - } - } - > .title { - display: none; - text-align: center; - padding: 50px 8px 16px 8px; - font-weight: bold; - border-bottom: solid 0.5px var(--divider); + > b { + display: block; + line-height: 16px; + } - > .bottom { - > * { - display: inline-block; - margin-right: 8px; - opacity: 0.8; + > span { + font-size: 70%; + } } } } + } - > .avatar { - display: block; - position: absolute; - top: 170px; - left: 16px; - z-index: 2; - width: 120px; - height: 120px; - box-shadow: 1px 1px 3px rgba(#000, 0.2); - } - - > .description { - padding: 24px 24px 24px 154px; - font-size: 0.95em; - - > .empty { - margin: 0; - opacity: 0.5; - } + > .contents { + > .content { + margin-bottom: var(--margin); } + } + } - > .fields { - padding: 24px; - font-size: 0.9em; - border-top: solid 0.5px var(--divider); - - > .field { - display: flex; - padding: 0; - margin: 0; - align-items: center; + &.max-width_500px { + > .main { + > .profile > .main { + > .banner-container { + height: 140px; - &:not(:last-child) { - margin-bottom: 8px; + > .fade { + display: none; } - > .name { - width: 30%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - font-weight: bold; - text-align: center; - } - - > .value { - width: 70%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - margin: 0; + > .title { + display: none; } } - &.system > .field > .name { + > .title { + display: block; } - } - > .status { - display: flex; - padding: 24px; - border-top: solid 0.5px var(--divider); + > .avatar { + top: 90px; + left: 0; + right: 0; + width: 92px; + height: 92px; + margin: auto; + } - > a { - flex: 1; + > .description { + padding: 16px; text-align: center; - - &.active { - color: var(--accent); - } - - &:hover { - text-decoration: none; - } - - > b { - display: block; - line-height: 16px; - } - - > span { - font-size: 70%; - } } - } - } - } - > .contents { - > .content { - margin-bottom: var(--margin); - } - } - - &.max-width_500px { - > .profile > .main { - > .banner-container { - height: 140px; - - > .fade { - display: none; + > .fields { + padding: 16px; } - > .title { - display: none; + > .status { + padding: 16px; } } - > .title { - display: block; - } - - > .avatar { - top: 90px; - left: 0; - right: 0; - width: 92px; - height: 92px; - margin: auto; - } - - > .description { - padding: 16px; - text-align: center; + > .contents { + > .nav { + font-size: 80%; + } } + } + } - > .fields { - padding: 16px; - } + &.wide { + display: flex; + width: 100%; - > .status { - padding: 16px; - } + > .main { + width: 100%; + min-width: 0; } - > .contents { - > .nav { - font-size: 80%; - } + > .sub { + max-width: 350px; + min-width: 350px; + margin-left: var(--margin); } } } diff --git a/packages/client/src/pages/welcome.entrance.a.vue b/packages/client/src/pages/welcome.entrance.a.vue index efdc038b7e..47e1f12342 100644 --- a/packages/client/src/pages/welcome.entrance.a.vue +++ b/packages/client/src/pages/welcome.entrance.a.vue @@ -135,7 +135,7 @@ export default defineComponent({ action: () => { window.open(`https://misskey-hub.net/help.md`, '_blank'); } - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); }, number diff --git a/packages/client/src/pages/welcome.entrance.b.vue b/packages/client/src/pages/welcome.entrance.b.vue index 93344dc9a8..053087fda0 100644 --- a/packages/client/src/pages/welcome.entrance.b.vue +++ b/packages/client/src/pages/welcome.entrance.b.vue @@ -119,7 +119,7 @@ export default defineComponent({ action: () => { window.open(`https://misskey-hub.net/help.md`, '_blank'); } - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); }, number diff --git a/packages/client/src/pages/welcome.entrance.c.vue b/packages/client/src/pages/welcome.entrance.c.vue index 36b61647a6..6bf487e16e 100644 --- a/packages/client/src/pages/welcome.entrance.c.vue +++ b/packages/client/src/pages/welcome.entrance.c.vue @@ -139,7 +139,7 @@ export default defineComponent({ action: () => { window.open(`https://misskey-hub.net/help.md`, '_blank'); } - }], ev.currentTarget || ev.target); + }], ev.currentTarget ?? ev.target); }, number diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts index 3634f39632..b19656d3cc 100644 --- a/packages/client/src/scripts/get-note-menu.ts +++ b/packages/client/src/scripts/get-note-menu.ts @@ -27,7 +27,7 @@ export function getNoteMenu(props: { function del(): void { os.confirm({ type: 'warning', - text: i18n.locale.noteDeleteConfirm, + text: i18n.ts.noteDeleteConfirm, }).then(({ canceled }) => { if (canceled) return; @@ -40,7 +40,7 @@ export function getNoteMenu(props: { function delEdit(): void { os.confirm({ type: 'warning', - text: i18n.locale.deleteAndEditConfirm, + text: i18n.ts.deleteAndEditConfirm, }).then(({ canceled }) => { if (canceled) return; @@ -87,7 +87,7 @@ export function getNoteMenu(props: { if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { os.alert({ type: 'error', - text: i18n.locale.pinLimitExceeded + text: i18n.ts.pinLimitExceeded }); } }); @@ -97,22 +97,22 @@ export function getNoteMenu(props: { const clips = await os.api('clips/list'); os.popupMenu([{ icon: 'fas fa-plus', - text: i18n.locale.createNew, + text: i18n.ts.createNew, action: async () => { - const { canceled, result } = await os.form(i18n.locale.createNewClip, { + const { canceled, result } = await os.form(i18n.ts.createNewClip, { name: { type: 'string', - label: i18n.locale.name + label: i18n.ts.name }, description: { type: 'string', required: false, multiline: true, - label: i18n.locale.description + label: i18n.ts.description }, isPublic: { type: 'boolean', - label: i18n.locale.public, + label: i18n.ts.public, default: false } }); @@ -133,7 +133,7 @@ export function getNoteMenu(props: { async function promote(): Promise<void> { const { canceled, result: days } = await os.inputNumber({ - title: i18n.locale.numberOfDays, + title: i18n.ts.numberOfDays, }); if (canceled) return; @@ -171,69 +171,69 @@ export function getNoteMenu(props: { menu = [{ icon: 'fas fa-copy', - text: i18n.locale.copyContent, + text: i18n.ts.copyContent, action: copyContent }, { icon: 'fas fa-link', - text: i18n.locale.copyLink, + text: i18n.ts.copyLink, action: copyLink }, (appearNote.url || appearNote.uri) ? { icon: 'fas fa-external-link-square-alt', - text: i18n.locale.showOnRemote, + text: i18n.ts.showOnRemote, action: () => { window.open(appearNote.url || appearNote.uri, '_blank'); } } : undefined, { icon: 'fas fa-share-alt', - text: i18n.locale.share, + text: i18n.ts.share, action: share }, instance.translatorAvailable ? { icon: 'fas fa-language', - text: i18n.locale.translate, + text: i18n.ts.translate, action: translate } : undefined, null, statePromise.then(state => state.isFavorited ? { icon: 'fas fa-star', - text: i18n.locale.unfavorite, + text: i18n.ts.unfavorite, action: () => toggleFavorite(false) } : { icon: 'fas fa-star', - text: i18n.locale.favorite, + text: i18n.ts.favorite, action: () => toggleFavorite(true) }), { icon: 'fas fa-paperclip', - text: i18n.locale.clip, + text: i18n.ts.clip, action: () => clip() }, (appearNote.userId != $i.id) ? statePromise.then(state => state.isWatching ? { icon: 'fas fa-eye-slash', - text: i18n.locale.unwatch, + text: i18n.ts.unwatch, action: () => toggleWatch(false) } : { icon: 'fas fa-eye', - text: i18n.locale.watch, + text: i18n.ts.watch, action: () => toggleWatch(true) }) : undefined, statePromise.then(state => state.isMutedThread ? { icon: 'fas fa-comment-slash', - text: i18n.locale.unmuteThread, + text: i18n.ts.unmuteThread, action: () => toggleThreadMute(false) } : { icon: 'fas fa-comment-slash', - text: i18n.locale.muteThread, + text: i18n.ts.muteThread, action: () => toggleThreadMute(true) }), appearNote.userId == $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? { icon: 'fas fa-thumbtack', - text: i18n.locale.unpin, + text: i18n.ts.unpin, action: () => togglePin(false) } : { icon: 'fas fa-thumbtack', - text: i18n.locale.pin, + text: i18n.ts.pin, action: () => togglePin(true) } : undefined, /* @@ -241,7 +241,7 @@ export function getNoteMenu(props: { null, { icon: 'fas fa-bullhorn', - text: i18n.locale.promote, + text: i18n.ts.promote, action: promote }] : [] @@ -250,7 +250,7 @@ export function getNoteMenu(props: { null, { icon: 'fas fa-exclamation-circle', - text: i18n.locale.reportAbuse, + text: i18n.ts.reportAbuse, action: () => { const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`; os.popup(import('@/components/abuse-report-window.vue'), { @@ -265,12 +265,12 @@ export function getNoteMenu(props: { null, appearNote.userId == $i.id ? { icon: 'fas fa-edit', - text: i18n.locale.deleteAndEdit, + text: i18n.ts.deleteAndEdit, action: delEdit } : undefined, { icon: 'fas fa-trash-alt', - text: i18n.locale.delete, + text: i18n.ts.delete, danger: true, action: del }] @@ -280,15 +280,15 @@ export function getNoteMenu(props: { } else { menu = [{ icon: 'fas fa-copy', - text: i18n.locale.copyContent, + text: i18n.ts.copyContent, action: copyContent }, { icon: 'fas fa-link', - text: i18n.locale.copyLink, + text: i18n.ts.copyLink, action: copyLink }, (appearNote.url || appearNote.uri) ? { icon: 'fas fa-external-link-square-alt', - text: i18n.locale.showOnRemote, + text: i18n.ts.showOnRemote, action: () => { window.open(appearNote.url || appearNote.uri, '_blank'); } diff --git a/packages/client/src/scripts/get-note-summary.ts b/packages/client/src/scripts/get-note-summary.ts index bd394279cb..54b8d109d6 100644 --- a/packages/client/src/scripts/get-note-summary.ts +++ b/packages/client/src/scripts/get-note-summary.ts @@ -7,11 +7,11 @@ import { i18n } from '@/i18n'; */ export const getNoteSummary = (note: misskey.entities.Note): string => { if (note.deletedAt) { - return `(${i18n.locale.deletedNote})`; + return `(${i18n.ts.deletedNote})`; } if (note.isHidden) { - return `(${i18n.locale.invisibleNote})`; + return `(${i18n.ts.invisibleNote})`; } let summary = ''; @@ -30,7 +30,7 @@ export const getNoteSummary = (note: misskey.entities.Note): string => { // 投票が添付されているとき if (note.poll) { - summary += ` (${i18n.locale.poll})`; + summary += ` (${i18n.ts.poll})`; } // 返信のとき diff --git a/packages/client/src/scripts/get-user-menu.ts b/packages/client/src/scripts/get-user-menu.ts index 7b910a0083..6d1f25a942 100644 --- a/packages/client/src/scripts/get-user-menu.ts +++ b/packages/client/src/scripts/get-user-menu.ts @@ -11,12 +11,12 @@ export function getUserMenu(user) { const meId = $i ? $i.id : null; async function pushList() { - const t = i18n.locale.selectList; // なぜか後で参照すると null になるので最初にメモリに確保しておく + const t = i18n.ts.selectList; // なぜか後で参照すると null になるので最初にメモリに確保しておく const lists = await os.api('users/lists/list'); if (lists.length === 0) { os.alert({ type: 'error', - text: i18n.locale.youHaveNoLists + text: i18n.ts.youHaveNoLists }); return; } @@ -38,12 +38,12 @@ export function getUserMenu(user) { if (groups.length === 0) { os.alert({ type: 'error', - text: i18n.locale.youHaveNoGroups + text: i18n.ts.youHaveNoGroups }); return; } const { canceled, result: groupId } = await os.select({ - title: i18n.locale.group, + title: i18n.ts.group, items: groups.map(group => ({ value: group.id, text: group.name })) @@ -64,7 +64,7 @@ export function getUserMenu(user) { } async function toggleBlock() { - if (!await getConfirmed(user.isBlocking ? i18n.locale.unblockConfirm : i18n.locale.blockConfirm)) return; + if (!await getConfirmed(user.isBlocking ? i18n.ts.unblockConfirm : i18n.ts.blockConfirm)) return; os.apiWithDialog(user.isBlocking ? 'blocking/delete' : 'blocking/create', { userId: user.id @@ -119,70 +119,70 @@ export function getUserMenu(user) { let menu = [{ icon: 'fas fa-at', - text: i18n.locale.copyUsername, + text: i18n.ts.copyUsername, action: () => { copyToClipboard(`@${user.username}@${user.host || host}`); } }, { icon: 'fas fa-info-circle', - text: i18n.locale.info, + text: i18n.ts.info, action: () => { os.pageWindow(`/user-info/${user.id}`); } }, { icon: 'fas fa-envelope', - text: i18n.locale.sendMessage, + text: i18n.ts.sendMessage, action: () => { os.post({ specified: user }); } }, meId != user.id ? { type: 'link', icon: 'fas fa-comments', - text: i18n.locale.startMessaging, + text: i18n.ts.startMessaging, to: '/my/messaging/' + Acct.toString(user), } : undefined, null, { icon: 'fas fa-list-ul', - text: i18n.locale.addToList, + text: i18n.ts.addToList, action: pushList }, meId != user.id ? { icon: 'fas fa-users', - text: i18n.locale.inviteToGroup, + text: i18n.ts.inviteToGroup, action: inviteGroup } : undefined] as any; if ($i && meId != user.id) { menu = menu.concat([null, { icon: user.isMuted ? 'fas fa-eye' : 'fas fa-eye-slash', - text: user.isMuted ? i18n.locale.unmute : i18n.locale.mute, + text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute, action: toggleMute }, { icon: 'fas fa-ban', - text: user.isBlocking ? i18n.locale.unblock : i18n.locale.block, + text: user.isBlocking ? i18n.ts.unblock : i18n.ts.block, action: toggleBlock }]); if (user.isFollowed) { menu = menu.concat([{ icon: 'fas fa-unlink', - text: i18n.locale.breakFollow, + text: i18n.ts.breakFollow, action: invalidateFollow }]); } menu = menu.concat([null, { icon: 'fas fa-exclamation-circle', - text: i18n.locale.reportAbuse, + text: i18n.ts.reportAbuse, action: reportAbuse }]); if (iAmModerator) { menu = menu.concat([null, { icon: 'fas fa-microphone-slash', - text: user.isSilenced ? i18n.locale.unsilence : i18n.locale.silence, + text: user.isSilenced ? i18n.ts.unsilence : i18n.ts.silence, action: toggleSilence }, { icon: 'fas fa-snowflake', - text: user.isSuspended ? i18n.locale.unsuspend : i18n.locale.suspend, + text: user.isSuspended ? i18n.ts.unsuspend : i18n.ts.suspend, action: toggleSuspend }]); } @@ -191,7 +191,7 @@ export function getUserMenu(user) { if ($i && meId === user.id) { menu = menu.concat([null, { icon: 'fas fa-pencil-alt', - text: i18n.locale.editProfile, + text: i18n.ts.editProfile, action: () => { router.push('/settings/profile'); } diff --git a/packages/client/src/scripts/i18n.ts b/packages/client/src/scripts/i18n.ts index 4fa398763a..3fe88e5514 100644 --- a/packages/client/src/scripts/i18n.ts +++ b/packages/client/src/scripts/i18n.ts @@ -1,8 +1,8 @@ export class I18n<T extends Record<string, any>> { - public locale: T; + public ts: T; constructor(locale: T) { - this.locale = locale; + this.ts = locale; //#region BIND this.t = this.t.bind(this); @@ -11,9 +11,9 @@ export class I18n<T extends Record<string, any>> { // string にしているのは、ドット区切りでのパス指定を許可するため // なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも - public t(key: string, args?: Record<string, any>): string { + public t(key: string, args?: Record<string, string>): string { try { - let str = key.split('.').reduce((o, i) => o[i], this.locale) as string; + let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string; if (args) { for (const [k, v] of Object.entries(args)) { @@ -21,7 +21,7 @@ export class I18n<T extends Record<string, any>> { } } return str; - } catch (e) { + } catch (err) { console.warn(`missing localization '${key}'`); return key; } diff --git a/packages/client/src/scripts/lookup-user.ts b/packages/client/src/scripts/lookup-user.ts index 64874f86f6..8de5c84ce8 100644 --- a/packages/client/src/scripts/lookup-user.ts +++ b/packages/client/src/scripts/lookup-user.ts @@ -4,7 +4,7 @@ import * as os from '@/os'; export async function lookupUser() { const { canceled, result } = await os.inputText({ - title: i18n.locale.usernameOrUserId, + title: i18n.ts.usernameOrUserId, }); if (canceled) return; @@ -19,7 +19,7 @@ export async function lookupUser() { if (_notFound) { os.alert({ type: 'error', - text: i18n.locale.noSuchUser + text: i18n.ts.noSuchUser }); } else { _notFound = true; diff --git a/packages/client/src/scripts/please-login.ts b/packages/client/src/scripts/please-login.ts index fe3919e4c7..aeaafa124b 100644 --- a/packages/client/src/scripts/please-login.ts +++ b/packages/client/src/scripts/please-login.ts @@ -6,7 +6,7 @@ export function pleaseLogin() { if ($i) return; alert({ - title: i18n.locale.signinRequired, + title: i18n.ts.signinRequired, text: null }); diff --git a/packages/client/src/scripts/popout.ts b/packages/client/src/scripts/popout.ts index 51b8d72868..b8286a2a76 100644 --- a/packages/client/src/scripts/popout.ts +++ b/packages/client/src/scripts/popout.ts @@ -1,8 +1,8 @@ import * as config from '@/config'; export function popout(path: string, w?: HTMLElement) { - let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + path; - url += '?zen'; // TODO: ちゃんとURLパースしてクエリ付ける + let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + "/" + path; + url += '?zen'; if (w) { const position = w.getBoundingClientRect(); const width = parseInt(getComputedStyle(w, '').width, 10); diff --git a/packages/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts index a070b1121c..0aedee9c98 100644 --- a/packages/client/src/scripts/search.ts +++ b/packages/client/src/scripts/search.ts @@ -4,7 +4,7 @@ import { router } from '@/router'; export async function search() { const { canceled, result: query } = await os.inputText({ - title: i18n.locale.search, + title: i18n.ts.search, }); if (canceled || query == null || query === '') return; @@ -46,7 +46,7 @@ export async function search() { uri: q }); - os.promiseDialog(promise, null, null, i18n.locale.fetchingAsApObject); + os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject); const res = await promise; diff --git a/packages/client/src/scripts/select-file.ts b/packages/client/src/scripts/select-file.ts index 6bb3f8bf8a..56e0b564f3 100644 --- a/packages/client/src/scripts/select-file.ts +++ b/packages/client/src/scripts/select-file.ts @@ -41,9 +41,9 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv const chooseFileFromUrl = () => { os.inputText({ - title: i18n.locale.uploadFromUrl, + title: i18n.ts.uploadFromUrl, type: 'url', - placeholder: i18n.locale.uploadFromUrlDescription + placeholder: i18n.ts.uploadFromUrlDescription }).then(({ canceled, result: url }) => { if (canceled) return; @@ -64,8 +64,8 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv }); os.alert({ - title: i18n.locale.uploadFromUrlRequested, - text: i18n.locale.uploadFromUrlMayTakeTime + title: i18n.ts.uploadFromUrlRequested, + text: i18n.ts.uploadFromUrlMayTakeTime }); }); }; @@ -74,15 +74,15 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv text: label, type: 'label' } : undefined, { - text: i18n.locale.upload, + text: i18n.ts.upload, icon: 'fas fa-upload', action: chooseFileFromPc }, { - text: i18n.locale.fromDrive, + text: i18n.ts.fromDrive, icon: 'fas fa-cloud', action: chooseFileFromDrive }, { - text: i18n.locale.fromUrl, + text: i18n.ts.fromUrl, icon: 'fas fa-link', action: chooseFileFromUrl }], src); diff --git a/packages/client/src/scripts/show-suspended-dialog.ts b/packages/client/src/scripts/show-suspended-dialog.ts index dcbb66933c..acfbc60e92 100644 --- a/packages/client/src/scripts/show-suspended-dialog.ts +++ b/packages/client/src/scripts/show-suspended-dialog.ts @@ -4,7 +4,7 @@ import { i18n } from '@/i18n'; export function showSuspendedDialog() { return os.alert({ type: 'error', - title: i18n.locale.yourAccountSuspendedTitle, - text: i18n.locale.yourAccountSuspendedDescription + title: i18n.ts.yourAccountSuspendedTitle, + text: i18n.ts.yourAccountSuspendedDescription }); } diff --git a/packages/client/src/scripts/use-leave-guard.ts b/packages/client/src/scripts/use-leave-guard.ts index 3984256251..33eea6b522 100644 --- a/packages/client/src/scripts/use-leave-guard.ts +++ b/packages/client/src/scripts/use-leave-guard.ts @@ -12,7 +12,7 @@ export function useLeaveGuard(enabled: Ref<boolean>) { const { canceled } = await os.confirm({ type: 'warning', - text: i18n.locale.leaveConfirm, + text: i18n.ts.leaveConfirm, }); return canceled; @@ -23,7 +23,7 @@ export function useLeaveGuard(enabled: Ref<boolean>) { const { canceled } = await os.confirm({ type: 'warning', - text: i18n.locale.leaveConfirm, + text: i18n.ts.leaveConfirm, }); return !canceled; diff --git a/packages/client/src/scripts/use-note-capture.ts b/packages/client/src/scripts/use-note-capture.ts index bb00e464e3..b7cf99d5e1 100644 --- a/packages/client/src/scripts/use-note-capture.ts +++ b/packages/client/src/scripts/use-note-capture.ts @@ -19,51 +19,41 @@ export function useNoteCapture(props: { case 'reacted': { const reaction = body.reaction; - const updated = JSON.parse(JSON.stringify(appearNote.value)); - if (body.emoji) { const emojis = appearNote.value.emojis || []; if (!emojis.includes(body.emoji)) { - updated.emojis = [...emojis, body.emoji]; + appearNote.value.emojis = [...emojis, body.emoji]; } } // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる const currentCount = (appearNote.value.reactions || {})[reaction] || 0; - updated.reactions[reaction] = currentCount + 1; + appearNote.value.reactions[reaction] = currentCount + 1; if ($i && (body.userId === $i.id)) { - updated.myReaction = reaction; + appearNote.value.myReaction = reaction; } - - appearNote.value = updated; break; } case 'unreacted': { const reaction = body.reaction; - const updated = JSON.parse(JSON.stringify(appearNote.value)); - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる const currentCount = (appearNote.value.reactions || {})[reaction] || 0; - updated.reactions[reaction] = Math.max(0, currentCount - 1); + appearNote.value.reactions[reaction] = Math.max(0, currentCount - 1); if ($i && (body.userId === $i.id)) { - updated.myReaction = null; + appearNote.value.myReaction = null; } - - appearNote.value = updated; break; } case 'pollVoted': { const choice = body.choice; - const updated = JSON.parse(JSON.stringify(appearNote.value)); - const choices = [...appearNote.value.poll.choices]; choices[choice] = { ...choices[choice], @@ -73,16 +63,12 @@ export function useNoteCapture(props: { } : {}) }; - updated.poll.choices = choices; - - appearNote.value = updated; + appearNote.value.poll.choices = choices; break; } case 'deleted': { - const updated = JSON.parse(JSON.stringify(appearNote.value)); - updated.value = true; - appearNote.value = updated; + appearNote.value.deletedAt = new Date(); break; } } diff --git a/packages/client/src/ui/classic.vue b/packages/client/src/ui/classic.vue index 1603ea6399..c61cbc433e 100644 --- a/packages/client/src/ui/classic.vue +++ b/packages/client/src/ui/classic.vue @@ -16,7 +16,7 @@ <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <router-view v-slot="{ Component }"> <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> - <keep-alive :include="['timeline']"> + <keep-alive :include="['MkTimelinePage']"> <component :is="Component" :ref="changePage"/> </keep-alive> </transition> @@ -30,7 +30,7 @@ </div> </div> - <transition name="tray-back"> + <transition :name="$store.state.animation ? 'tray-back' : ''"> <div v-if="widgetsShowing" class="tray-back _modalBg" @click="widgetsShowing = false" @@ -38,7 +38,7 @@ ></div> </transition> - <transition name="tray"> + <transition :name="$store.state.animation ? 'tray' : ''"> <XWidgets v-if="widgetsShowing" class="tray"/> </transition> diff --git a/packages/client/src/ui/deck.vue b/packages/client/src/ui/deck.vue index 73dc83180f..9accc34a88 100644 --- a/packages/client/src/ui/deck.vue +++ b/packages/client/src/ui/deck.vue @@ -29,7 +29,7 @@ <button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button> </div> - <transition name="menu-back"> + <transition :name="$store.state.animation ? 'menu-back' : ''"> <div v-if="drawerMenuShowing" class="menu-back _modalBg" @click="drawerMenuShowing = false" @@ -37,7 +37,7 @@ ></div> </transition> - <transition name="menu"> + <transition :name="$store.state.animation ? 'menu' : ''"> <XDrawerMenu v-if="drawerMenuShowing" class="menu"/> </transition> @@ -104,7 +104,7 @@ export default defineComponent({ ]; const { canceled, result: column } = await os.select({ - title: i18n.locale._deck.addColumn, + title: i18n.ts._deck.addColumn, items: columns.map(column => ({ value: column, text: i18n.t('_deck._columns.' + column) })) @@ -121,7 +121,7 @@ export default defineComponent({ const onContextmenu = (ev) => { os.contextMenu([{ - text: i18n.locale._deck.addColumn, + text: i18n.ts._deck.addColumn, icon: null, action: addColumn }], ev); diff --git a/packages/client/src/ui/deck/deck-store.ts b/packages/client/src/ui/deck/deck-store.ts index 6b6b02f3f9..66db5e83ed 100644 --- a/packages/client/src/ui/deck/deck-store.ts +++ b/packages/client/src/ui/deck/deck-store.ts @@ -77,12 +77,12 @@ export const loadDeck = async () => { deckStore.set('columns', [{ id: 'a', type: 'main', - name: i18n.locale._deck._columns.main, + name: i18n.ts._deck._columns.main, width: 350, }, { id: 'b', type: 'notifications', - name: i18n.locale._deck._columns.notifications, + name: i18n.ts._deck._columns.notifications, width: 330, }]); deckStore.set('layout', [['a'], ['b']]); diff --git a/packages/client/src/ui/deck/main-column.vue b/packages/client/src/ui/deck/main-column.vue index b77d6f35cc..cb045e9a46 100644 --- a/packages/client/src/ui/deck/main-column.vue +++ b/packages/client/src/ui/deck/main-column.vue @@ -11,7 +11,7 @@ <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <router-view v-slot="{ Component }"> <transition> - <keep-alive :include="['timeline']"> + <keep-alive :include="['MkTimelinePage']"> <component :is="Component" :ref="changePage" @contextmenu.stop="onContextmenu"/> </keep-alive> </transition> diff --git a/packages/client/src/ui/universal.vue b/packages/client/src/ui/universal.vue index 9fc2177ee0..8fe9dcffaf 100644 --- a/packages/client/src/ui/universal.vue +++ b/packages/client/src/ui/universal.vue @@ -9,7 +9,7 @@ <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <router-view v-slot="{ Component }"> <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> - <keep-alive :include="['timeline']"> + <keep-alive :include="['MkTimelinePage']"> <component :is="Component" :ref="changePage"/> </keep-alive> </transition> @@ -36,7 +36,7 @@ <button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button> </div> - <transition name="menuDrawer-back"> + <transition :name="$store.state.animation ? 'menuDrawer-back' : ''"> <div v-if="drawerMenuShowing" class="menuDrawer-back _modalBg" @click="drawerMenuShowing = false" @@ -44,11 +44,11 @@ ></div> </transition> - <transition name="menuDrawer"> + <transition :name="$store.state.animation ? 'menuDrawer' : ''"> <XDrawerMenu v-if="drawerMenuShowing" class="menuDrawer"/> </transition> - <transition name="widgetsDrawer-back"> + <transition :name="$store.state.animation ? 'widgetsDrawer-back' : ''"> <div v-if="widgetsShowing" class="widgetsDrawer-back _modalBg" @click="widgetsShowing = false" @@ -56,7 +56,7 @@ ></div> </transition> - <transition name="widgetsDrawer"> + <transition :name="$store.state.animation ? 'widgetsDrawer' : ''"> <XWidgets v-if="widgetsShowing" class="widgetsDrawer"/> </transition> @@ -171,13 +171,13 @@ export default defineComponent({ text: path, }, { icon: 'fas fa-columns', - text: i18n.locale.openInSideView, + text: i18n.ts.openInSideView, action: () => { this.$refs.side.navigate(path); } }, { icon: 'fas fa-window-maximize', - text: i18n.locale.openInWindow, + text: i18n.ts.openInWindow, action: () => { os.pageWindow(path); } diff --git a/packages/client/src/ui/visitor/b.vue b/packages/client/src/ui/visitor/b.vue index f6ad13d9a0..c9c0a1f72e 100644 --- a/packages/client/src/ui/visitor/b.vue +++ b/packages/client/src/ui/visitor/b.vue @@ -25,7 +25,7 @@ </div> </div> - <transition name="tray-back"> + <transition :name="$store.state.animation ? 'tray-back' : ''"> <div v-if="showMenu" class="menu-back _modalBg" @click="showMenu = false" @@ -33,7 +33,7 @@ ></div> </transition> - <transition name="tray"> + <transition :name="$store.state.animation ? 'tray' : ''"> <div v-if="showMenu" class="menu"> <MkA to="/" class="link" active-class="active"><i class="fas fa-home icon"></i>{{ $ts.home }}</MkA> <MkA to="/explore" class="link" active-class="active"><i class="fas fa-hashtag icon"></i>{{ $ts.explore }}</MkA> diff --git a/packages/client/src/ui/zen.vue b/packages/client/src/ui/zen.vue index 7c72232cfd..a7234f729b 100644 --- a/packages/client/src/ui/zen.vue +++ b/packages/client/src/ui/zen.vue @@ -8,7 +8,7 @@ <div class="content"> <router-view v-slot="{ Component }"> <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> - <keep-alive :include="['timeline']"> + <keep-alive :include="['MkTimelinePage']"> <component :is="Component" :ref="changePage"/> </keep-alive> </transition> diff --git a/packages/client/src/widgets/calendar.vue b/packages/client/src/widgets/calendar.vue index b0e3edcb12..c6a69b3fb8 100644 --- a/packages/client/src/widgets/calendar.vue +++ b/packages/client/src/widgets/calendar.vue @@ -79,13 +79,13 @@ const tick = () => { month.value = nm + 1; day.value = nd; weekDay.value = [ - i18n.locale._weekday.sunday, - i18n.locale._weekday.monday, - i18n.locale._weekday.tuesday, - i18n.locale._weekday.wednesday, - i18n.locale._weekday.thursday, - i18n.locale._weekday.friday, - i18n.locale._weekday.saturday + i18n.ts._weekday.sunday, + i18n.ts._weekday.monday, + i18n.ts._weekday.tuesday, + i18n.ts._weekday.wednesday, + i18n.ts._weekday.thursday, + i18n.ts._weekday.friday, + i18n.ts._weekday.saturday ][now.getDay()]; const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime(); diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue index ed7350188e..4c43117e48 100644 --- a/packages/client/src/widgets/federation.vue +++ b/packages/client/src/widgets/federation.vue @@ -4,7 +4,7 @@ <div class="wbrkwalb"> <MkLoading v-if="fetching"/> - <transition-group v-else tag="div" name="chart" class="instances"> + <transition-group v-else tag="div" :name="$store.state.animation ? 'chart' : ''" class="instances"> <div v-for="(instance, i) in instances" :key="instance.id" class="instance"> <img v-if="instance.iconUrl" :src="instance.iconUrl" alt=""/> <div class="body"> diff --git a/packages/client/src/widgets/timeline.vue b/packages/client/src/widgets/timeline.vue index fa700cc8ee..34e3b20e36 100644 --- a/packages/client/src/widgets/timeline.vue +++ b/packages/client/src/widgets/timeline.vue @@ -101,22 +101,22 @@ const choose = async (ev) => { } })); os.popupMenu([{ - text: i18n.locale._timelines.home, + text: i18n.ts._timelines.home, icon: 'fas fa-home', action: () => { setSrc('home') } }, { - text: i18n.locale._timelines.local, + text: i18n.ts._timelines.local, icon: 'fas fa-comments', action: () => { setSrc('local') } }, { - text: i18n.locale._timelines.social, + text: i18n.ts._timelines.social, icon: 'fas fa-share-alt', action: () => { setSrc('social') } }, { - text: i18n.locale._timelines.global, + text: i18n.ts._timelines.global, icon: 'fas fa-globe', action: () => { setSrc('global') } - }, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget || ev.target).then(() => { + }, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget ?? ev.target).then(() => { menuOpened.value = false; }); }; diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue index 5768a8d5d1..a34710eae7 100644 --- a/packages/client/src/widgets/trends.vue +++ b/packages/client/src/widgets/trends.vue @@ -4,7 +4,7 @@ <div class="wbrkwala"> <MkLoading v-if="fetching"/> - <transition-group v-else tag="div" name="chart" class="tags"> + <transition-group v-else tag="div" :name="$store.state.animation ? 'chart' : ''" class="tags"> <div v-for="stat in stats" :key="stat.tag"> <div class="tag"> <MkA class="a" :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</MkA> @@ -52,8 +52,8 @@ const stats = ref([]); const fetching = ref(true); const fetch = () => { - os.api('hashtags/trend').then(stats => { - stats.value = stats; + os.api('hashtags/trend').then(res => { + stats.value = res; fetching.value = false; }); }; |