diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2022-06-11 19:31:03 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-06-11 19:31:03 +0900 |
| commit | 182a1bf653ecfbcf76e4530b3077c6252b0d4827 (patch) | |
| tree | 45d1472747d4cac017e96616f844292f5785ccdd /packages/client/src/pages | |
| parent | 12.110.1 (diff) | |
| parent | 12.111.0 (diff) | |
| download | misskey-182a1bf653ecfbcf76e4530b3077c6252b0d4827.tar.gz misskey-182a1bf653ecfbcf76e4530b3077c6252b0d4827.tar.bz2 misskey-182a1bf653ecfbcf76e4530b3077c6252b0d4827.zip | |
Merge pull request #8783 from misskey-dev/develop
Release: 12.111.0
Diffstat (limited to 'packages/client/src/pages')
85 files changed, 3017 insertions, 3843 deletions
diff --git a/packages/client/src/pages/about-misskey.vue b/packages/client/src/pages/about-misskey.vue index ff04ed84f2..691bc4f07b 100644 --- a/packages/client/src/pages/about-misskey.vue +++ b/packages/client/src/pages/about-misskey.vue @@ -150,6 +150,7 @@ const patrons = [ 'Weeble', '蝉暮せせせ', 'ThatOneCalculator', + 'pixeldesu', ]; let easterEggReady = false; diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue index 92f93797ce..e1d0361c0b 100644 --- a/packages/client/src/pages/admin/abuses.vue +++ b/packages/client/src/pages/admin/abuses.vue @@ -24,10 +24,10 @@ </div> <!-- TODO <div class="inputs" style="display: flex; padding-top: 1.2em;"> - <MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()"> + <MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false"> <span>{{ $ts.username }}</span> </MkInput> - <MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()" :disabled="pagination.params().origin === 'local'"> + <MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" :disabled="pagination.params().origin === 'local'"> <span>{{ $ts.host }}</span> </MkInput> </div> @@ -41,8 +41,8 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -50,45 +50,35 @@ import MkPagination from '@/components/ui/pagination.vue'; import XAbuseReport from '@/components/abuse-report.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkInput, - MkSelect, - MkPagination, - XAbuseReport, - }, +let reports = $ref<InstanceType<typeof MkPagination>>(); - emits: ['info'], +let state = $ref('unresolved'); +let reporterOrigin = $ref('combined'); +let targetUserOrigin = $ref('combined'); +let searchUsername = $ref(''); +let searchHost = $ref(''); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.abuseReports, - icon: 'fas fa-exclamation-circle', - bg: 'var(--bg)', - }, - searchUsername: '', - searchHost: '', - state: 'unresolved', - reporterOrigin: 'combined', - targetUserOrigin: 'combined', - pagination: { - endpoint: 'admin/abuse-user-reports' as const, - limit: 10, - params: computed(() => ({ - state: this.state, - reporterOrigin: this.reporterOrigin, - targetUserOrigin: this.targetUserOrigin, - })), - }, - } - }, +const pagination = { + endpoint: 'admin/abuse-user-reports' as const, + limit: 10, + params: computed(() => ({ + state, + reporterOrigin, + targetUserOrigin, + })), +}; - methods: { - resolved(reportId) { - this.$refs.reports.removeItem(item => item.id === reportId); - }, +function resolved(reportId) { + reports.removeItem(item => item.id === reportId); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.abuseReports, + icon: 'fas fa-exclamation-circle', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/ads.vue b/packages/client/src/pages/admin/ads.vue index 8f164caa99..b18e08db96 100644 --- a/packages/client/src/pages/admin/ads.vue +++ b/packages/client/src/pages/admin/ads.vue @@ -7,7 +7,7 @@ <template #label>URL</template> </MkInput> <MkInput v-model="ad.imageUrl" class="_formBlock"> - <template #label>{{ $ts.imageUrl }}</template> + <template #label>{{ i18n.ts.imageUrl }}</template> </MkInput> <FormRadios v-model="ad.place" class="_formBlock"> <template #label>Form</template> @@ -17,34 +17,34 @@ </FormRadios> <!-- <div style="margin: 32px 0;"> - {{ $ts.priority }} - <MkRadio v-model="ad.priority" value="high">{{ $ts.high }}</MkRadio> - <MkRadio v-model="ad.priority" value="middle">{{ $ts.middle }}</MkRadio> - <MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio> + {{ i18n.ts.priority }} + <MkRadio v-model="ad.priority" value="high">{{ i18n.ts.high }}</MkRadio> + <MkRadio v-model="ad.priority" value="middle">{{ i18n.ts.middle }}</MkRadio> + <MkRadio v-model="ad.priority" value="low">{{ i18n.ts.low }}</MkRadio> </div> --> <FormSplit> <MkInput v-model="ad.ratio" type="number"> - <template #label>{{ $ts.ratio }}</template> + <template #label>{{ i18n.ts.ratio }}</template> </MkInput> <MkInput v-model="ad.expiresAt" type="date"> - <template #label>{{ $ts.expiration }}</template> + <template #label>{{ i18n.ts.expiration }}</template> </MkInput> </FormSplit> <MkTextarea v-model="ad.memo" class="_formBlock"> - <template #label>{{ $ts.memo }}</template> + <template #label>{{ i18n.ts.memo }}</template> </MkTextarea> <div class="buttons _formBlock"> - <MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> - <MkButton class="button" inline danger @click="remove(ad)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> + <MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton> + <MkButton class="button" inline danger @click="remove(ad)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton> </div> </div> </div> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; @@ -52,81 +52,65 @@ import FormRadios from '@/components/form/radios.vue'; import FormSplit from '@/components/form/split.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkInput, - MkTextarea, - FormRadios, - FormSplit, - }, +let ads: any[] = $ref([]); - emits: ['info'], +os.api('admin/ad/list').then(adsResponse => { + ads = adsResponse; +}); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.ads, - icon: 'fas fa-audio-description', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-plus', - text: this.$ts.add, - handler: this.add, - }], - }, - ads: [], - } - }, +function add() { + ads.unshift({ + id: null, + memo: '', + place: 'square', + priority: 'middle', + ratio: 1, + url: '', + imageUrl: null, + expiresAt: null, + }); +} - created() { - os.api('admin/ad/list').then(ads => { - this.ads = ads; +function remove(ad) { + os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: ad.url }), + }).then(({ canceled }) => { + if (canceled) return; + ads = ads.filter(x => x !== ad); + os.apiWithDialog('admin/ad/delete', { + id: ad.id }); - }, - - methods: { - add() { - this.ads.unshift({ - id: null, - memo: '', - place: 'square', - priority: 'middle', - ratio: 1, - url: '', - imageUrl: null, - expiresAt: null, - }); - }, + }); +} - remove(ad) { - os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: ad.url }), - }).then(({ canceled }) => { - if (canceled) return; - this.ads = this.ads.filter(x => x != ad); - os.apiWithDialog('admin/ad/delete', { - id: ad.id - }); - }); - }, +function save(ad) { + if (ad.id == null) { + os.apiWithDialog('admin/ad/create', { + ...ad, + expiresAt: new Date(ad.expiresAt).getTime() + }); + } else { + os.apiWithDialog('admin/ad/update', { + ...ad, + expiresAt: new Date(ad.expiresAt).getTime() + }); + } +} - save(ad) { - if (ad.id == null) { - os.apiWithDialog('admin/ad/create', { - ...ad, - expiresAt: new Date(ad.expiresAt).getTime() - }); - } else { - os.apiWithDialog('admin/ad/update', { - ...ad, - expiresAt: new Date(ad.expiresAt).getTime() - }); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.ads, + icon: 'fas fa-audio-description', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: i18n.ts.add, + handler: add, + }], } }); </script> diff --git a/packages/client/src/pages/admin/announcements.vue b/packages/client/src/pages/admin/announcements.vue index a0d720bb29..97774975de 100644 --- a/packages/client/src/pages/admin/announcements.vue +++ b/packages/client/src/pages/admin/announcements.vue @@ -3,112 +3,98 @@ <section v-for="announcement in announcements" class="_card _gap announcements"> <div class="_content announcement"> <MkInput v-model="announcement.title"> - <template #label>{{ $ts.title }}</template> + <template #label>{{ i18n.ts.title }}</template> </MkInput> <MkTextarea v-model="announcement.text"> - <template #label>{{ $ts.text }}</template> + <template #label>{{ i18n.ts.text }}</template> </MkTextarea> <MkInput v-model="announcement.imageUrl"> - <template #label>{{ $ts.imageUrl }}</template> + <template #label>{{ i18n.ts.imageUrl }}</template> </MkInput> - <p v-if="announcement.reads">{{ $t('nUsersRead', { n: announcement.reads }) }}</p> + <p v-if="announcement.reads">{{ i18n.t('nUsersRead', { n: announcement.reads }) }}</p> <div class="buttons"> - <MkButton class="button" inline primary @click="save(announcement)"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> - <MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> + <MkButton class="button" inline primary @click="save(announcement)"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton> + <MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton> </div> </div> </section> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkInput, - MkTextarea, - }, +let announcements: any[] = $ref([]); - emits: ['info'], +os.api('admin/announcements/list').then(announcementResponse => { + announcements = announcementResponse; +}); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.announcements, - icon: 'fas fa-broadcast-tower', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-plus', - text: this.$ts.add, - handler: this.add, - }], - }, - announcements: [], - } - }, +function add() { + announcements.unshift({ + id: null, + title: '', + text: '', + imageUrl: null + }); +} - created() { - os.api('admin/announcements/list').then(announcements => { - this.announcements = announcements; - }); - }, +function remove(announcement) { + os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: announcement.title }), + }).then(({ canceled }) => { + if (canceled) return; + announcements = announcements.filter(x => x !== announcement); + os.api('admin/announcements/delete', announcement); + }); +} - methods: { - add() { - this.announcements.unshift({ - id: null, - title: '', - text: '', - imageUrl: null +function save(announcement) { + if (announcement.id == null) { + os.api('admin/announcements/create', announcement).then(() => { + os.alert({ + type: 'success', + text: i18n.ts.saved }); - }, - - remove(announcement) { - os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: announcement.title }), - }).then(({ canceled }) => { - if (canceled) return; - this.announcements = this.announcements.filter(x => x != announcement); - os.api('admin/announcements/delete', announcement); + }).catch(err => { + os.alert({ + type: 'error', + text: err }); - }, + }); + } else { + os.api('admin/announcements/update', announcement).then(() => { + os.alert({ + type: 'success', + text: i18n.ts.saved + }); + }).catch(err => { + os.alert({ + type: 'error', + text: err + }); + }); + } +} - save(announcement) { - if (announcement.id == null) { - os.api('admin/announcements/create', announcement).then(() => { - os.alert({ - type: 'success', - text: this.$ts.saved - }); - }).catch(e => { - os.alert({ - type: 'error', - text: e - }); - }); - } else { - os.api('admin/announcements/update', announcement).then(() => { - os.alert({ - type: 'success', - text: this.$ts.saved - }); - }).catch(e => { - os.alert({ - type: 'error', - text: e - }); - }); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.announcements, + icon: 'fas fa-broadcast-tower', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: i18n.ts.add, + handler: add, + }], } }); </script> diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue index 5e0cdd96a5..30fee5015a 100644 --- a/packages/client/src/pages/admin/bot-protection.vue +++ b/packages/client/src/pages/admin/bot-protection.vue @@ -43,8 +43,8 @@ </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent } from 'vue'; import FormRadios from '@/components/form/radios.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; @@ -54,64 +54,39 @@ import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; -export default defineComponent({ - components: { - FormRadios, - FormInput, - FormButton, - FormSuspense, - FormSlot, - MkCaptcha: defineAsyncComponent(() => import('@/components/captcha.vue')), - }, +const MkCaptcha = defineAsyncComponent(() => import('@/components/captcha.vue')); - emits: ['info'], +let provider = $ref(null); +let hcaptchaSiteKey: string | null = $ref(null); +let hcaptchaSecretKey: string | null = $ref(null); +let recaptchaSiteKey: string | null = $ref(null); +let recaptchaSecretKey: string | null = $ref(null); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.botProtection, - icon: 'fas fa-shield-alt' - }, - provider: null, - enableHcaptcha: false, - hcaptchaSiteKey: null, - hcaptchaSecretKey: null, - enableRecaptcha: false, - recaptchaSiteKey: null, - recaptchaSecretKey: null, - } - }, +const enableHcaptcha = $computed(() => provider === 'hcaptcha'); +const enableRecaptcha = $computed(() => provider === 'recaptcha'); - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.enableHcaptcha = meta.enableHcaptcha; - this.hcaptchaSiteKey = meta.hcaptchaSiteKey; - this.hcaptchaSecretKey = meta.hcaptchaSecretKey; - this.enableRecaptcha = meta.enableRecaptcha; - this.recaptchaSiteKey = meta.recaptchaSiteKey; - this.recaptchaSecretKey = meta.recaptchaSecretKey; +async function init() { + const meta = await os.api('admin/meta'); + enableHcaptcha = meta.enableHcaptcha; + hcaptchaSiteKey = meta.hcaptchaSiteKey; + hcaptchaSecretKey = meta.hcaptchaSecretKey; + enableRecaptcha = meta.enableRecaptcha; + recaptchaSiteKey = meta.recaptchaSiteKey; + recaptchaSecretKey = meta.recaptchaSecretKey; - this.provider = this.enableHcaptcha ? 'hcaptcha' : this.enableRecaptcha ? 'recaptcha' : null; + provider = enableHcaptcha ? 'hcaptcha' : enableRecaptcha ? 'recaptcha' : null; +} - this.$watch(() => this.provider, () => { - this.enableHcaptcha = this.provider === 'hcaptcha'; - this.enableRecaptcha = this.provider === 'recaptcha'; - }); - }, - - save() { - os.apiWithDialog('admin/update-meta', { - enableHcaptcha: this.enableHcaptcha, - hcaptchaSiteKey: this.hcaptchaSiteKey, - hcaptchaSecretKey: this.hcaptchaSecretKey, - enableRecaptcha: this.enableRecaptcha, - recaptchaSiteKey: this.recaptchaSiteKey, - recaptchaSecretKey: this.recaptchaSecretKey, - }).then(() => { - fetchInstance(); - }); - } - } -}); +function save() { + os.apiWithDialog('admin/update-meta', { + enableHcaptcha, + hcaptchaSiteKey, + hcaptchaSecretKey, + enableRecaptcha, + recaptchaSiteKey, + recaptchaSecretKey, + }).then(() => { + fetchInstance(); + }); +} </script> diff --git a/packages/client/src/pages/admin/database.vue b/packages/client/src/pages/admin/database.vue index 3a835eeafa..d3519922b1 100644 --- a/packages/client/src/pages/admin/database.vue +++ b/packages/client/src/pages/admin/database.vue @@ -9,36 +9,23 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSuspense from '@/components/form/suspense.vue'; import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import bytes from '@/filters/bytes'; import number from '@/filters/number'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSuspense, - MkKeyValue, - }, +const databasePromiseFactory = () => os.api('admin/get-table-stats').then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size)); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.database, - icon: 'fas fa-database', - bg: 'var(--bg)', - }, - databasePromiseFactory: () => os.api('admin/get-table-stats', {}).then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size)), - } - }, - - methods: { - bytes, number, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.database, + icon: 'fas fa-database', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/email-settings.vue b/packages/client/src/pages/admin/email-settings.vue index 7df0b6db1c..aa13043193 100644 --- a/packages/client/src/pages/admin/email-settings.vue +++ b/packages/client/src/pages/admin/email-settings.vue @@ -3,37 +3,37 @@ <FormSuspense :p="init"> <div class="_formRoot"> <FormSwitch v-model="enableEmail" class="_formBlock"> - <template #label>{{ $ts.enableEmail }}</template> - <template #caption>{{ $ts.emailConfigInfo }}</template> + <template #label>{{ i18n.ts.enableEmail }}</template> + <template #caption>{{ i18n.ts.emailConfigInfo }}</template> </FormSwitch> <template v-if="enableEmail"> <FormInput v-model="email" type="email" class="_formBlock"> - <template #label>{{ $ts.emailAddress }}</template> + <template #label>{{ i18n.ts.emailAddress }}</template> </FormInput> <FormSection> - <template #label>{{ $ts.smtpConfig }}</template> + <template #label>{{ i18n.ts.smtpConfig }}</template> <FormSplit :min-width="280"> <FormInput v-model="smtpHost" class="_formBlock"> - <template #label>{{ $ts.smtpHost }}</template> + <template #label>{{ i18n.ts.smtpHost }}</template> </FormInput> <FormInput v-model="smtpPort" type="number" class="_formBlock"> - <template #label>{{ $ts.smtpPort }}</template> + <template #label>{{ i18n.ts.smtpPort }}</template> </FormInput> </FormSplit> <FormSplit :min-width="280"> <FormInput v-model="smtpUser" class="_formBlock"> - <template #label>{{ $ts.smtpUser }}</template> + <template #label>{{ i18n.ts.smtpUser }}</template> </FormInput> <FormInput v-model="smtpPass" type="password" class="_formBlock"> - <template #label>{{ $ts.smtpPass }}</template> + <template #label>{{ i18n.ts.smtpPass }}</template> </FormInput> </FormSplit> - <FormInfo class="_formBlock">{{ $ts.emptyToDisableSmtpAuth }}</FormInfo> + <FormInfo class="_formBlock">{{ i18n.ts.emptyToDisableSmtpAuth }}</FormInfo> <FormSwitch v-model="smtpSecure" class="_formBlock"> - <template #label>{{ $ts.smtpSecure }}</template> - <template #caption>{{ $ts.smtpSecureInfo }}</template> + <template #label>{{ i18n.ts.smtpSecure }}</template> + <template #caption>{{ i18n.ts.smtpSecureInfo }}</template> </FormSwitch> </FormSection> </template> @@ -42,8 +42,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormInfo from '@/components/ui/info.vue'; @@ -52,86 +52,71 @@ import FormSplit from '@/components/form/split.vue'; import FormSection from '@/components/form/section.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; -import { fetchInstance } from '@/instance'; +import { fetchInstance, instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormSplit, - FormSection, - FormInfo, - FormSuspense, - }, +let enableEmail: boolean = $ref(false); +let email: any = $ref(null); +let smtpSecure: boolean = $ref(false); +let smtpHost: string = $ref(''); +let smtpPort: number = $ref(0); +let smtpUser: string = $ref(''); +let smtpPass: string = $ref(''); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + enableEmail = meta.enableEmail; + email = meta.email; + smtpSecure = meta.smtpSecure; + smtpHost = meta.smtpHost; + smtpPort = meta.smtpPort; + smtpUser = meta.smtpUser; + smtpPass = meta.smtpPass; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.emailServer, - icon: 'fas fa-envelope', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - text: this.$ts.testEmail, - handler: this.testEmail, - }, { - asFullButton: true, - icon: 'fas fa-check', - text: this.$ts.save, - handler: this.save, - }], - }, - enableEmail: false, - email: null, - smtpSecure: false, - smtpHost: '', - smtpPort: 0, - smtpUser: '', - smtpPass: '', - } - }, +async function testEmail() { + const { canceled, result: destination } = await os.inputText({ + title: i18n.ts.destination, + type: 'email', + placeholder: instance.maintainerEmail + }); + if (canceled) return; + os.apiWithDialog('admin/send-email', { + to: destination, + subject: 'Test email', + text: 'Yo' + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.enableEmail = meta.enableEmail; - this.email = meta.email; - this.smtpSecure = meta.smtpSecure; - this.smtpHost = meta.smtpHost; - this.smtpPort = meta.smtpPort; - this.smtpUser = meta.smtpUser; - this.smtpPass = meta.smtpPass; - }, +function save() { + os.apiWithDialog('admin/update-meta', { + enableEmail, + email, + smtpSecure, + smtpHost, + smtpPort, + smtpUser, + smtpPass, + }).then(() => { + fetchInstance(); + }); +} - async testEmail() { - const { canceled, result: destination } = await os.inputText({ - title: this.$ts.destination, - type: 'email', - placeholder: this.$instance.maintainerEmail - }); - if (canceled) return; - os.apiWithDialog('admin/send-email', { - to: destination, - subject: 'Test email', - text: 'Yo' - }); - }, - - save() { - os.apiWithDialog('admin/update-meta', { - enableEmail: this.enableEmail, - email: this.email, - smtpSecure: this.smtpSecure, - smtpHost: this.smtpHost, - smtpPort: this.smtpPort, - smtpUser: this.smtpUser, - smtpPass: this.smtpPass, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.emailServer, + icon: 'fas fa-envelope', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + text: i18n.ts.testEmail, + handler: testEmail, + }, { + asFullButton: true, + icon: 'fas fa-check', + text: i18n.ts.save, + handler: save, + }], } }); </script> diff --git a/packages/client/src/pages/admin/emoji-edit-dialog.vue b/packages/client/src/pages/admin/emoji-edit-dialog.vue index 2e3903426e..d482fa49e6 100644 --- a/packages/client/src/pages/admin/emoji-edit-dialog.vue +++ b/packages/client/src/pages/admin/emoji-edit-dialog.vue @@ -27,85 +27,71 @@ </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import * as os from '@/os'; import { unique } from '@/scripts/array'; +import { i18n } from '@/i18n'; +import { emojiCategories } from '@/instance'; -export default defineComponent({ - components: { - XModalWindow, - MkButton, - MkInput, - }, +const props = defineProps<{ + emoji: any, +}>(); - props: { - emoji: { - required: true, - } - }, - - emits: ['done', 'closed'], +let dialog = $ref(null); +let name: string = $ref(props.emoji.name); +let category: string = $ref(props.emoji.category); +let aliases: string = $ref(props.emoji.aliases.join(' ')); +let categories: string[] = $ref(emojiCategories); - data() { - return { - name: this.emoji.name, - category: this.emoji.category, - aliases: this.emoji.aliases?.join(' '), - categories: [], - } - }, +const emit = defineEmits<{ + (ev: 'done', v: { deleted?: boolean, updated?: any }): void, + (ev: 'closed'): void +}>(); - created() { - os.api('meta', { detail: false }).then(({ emojis }) => { - this.categories = unique(emojis.map((x: any) => x.category || '').filter((x: string) => x !== '')); - }); - }, +function ok() { + update(); +} - methods: { - ok() { - this.update(); - }, +async function update() { + await os.apiWithDialog('admin/emoji/update', { + id: props.emoji.id, + name, + category, + aliases: aliases.split(' '), + }); - async update() { - await os.apiWithDialog('admin/emoji/update', { - id: this.emoji.id, - name: this.name, - category: this.category, - aliases: this.aliases.split(' '), - }); + emit('done', { + updated: { + id: props.emoji.id, + name, + category, + aliases: aliases.split(' '), + } + }); - this.$emit('done', { - updated: { - name: this.name, - category: this.category, - aliases: this.aliases.split(' '), - } - }); - this.$refs.dialog.close(); - }, + dialog.close(); +} - async del() { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: this.emoji.name }), - }); - if (canceled) return; +async function del() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: name }), + }); + if (canceled) return; - os.api('admin/emoji/delete', { - id: this.emoji.id - }).then(() => { - this.$emit('done', { - deleted: true - }); - this.$refs.dialog.close(); - }); - }, - } -}); + os.api('admin/emoji/delete', { + id: props.emoji.id + }).then(() => { + emit('done', { + deleted: true + }); + dialog.close(); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue index a080ee9c23..8ca5b3d65c 100644 --- a/packages/client/src/pages/admin/emojis.vue +++ b/packages/client/src/pages/admin/emojis.vue @@ -63,7 +63,7 @@ </template> <script lang="ts" setup> -import { computed, defineComponent, ref, toRef } from 'vue'; +import { computed, defineAsyncComponent, defineComponent, ref, toRef } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkPagination from '@/components/ui/pagination.vue'; @@ -130,17 +130,17 @@ const add = async (ev: MouseEvent) => { }; const edit = (emoji) => { - os.popup(import('./emoji-edit-dialog.vue'), { + os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), { emoji: emoji }, { done: result => { if (result.updated) { - emojisPaginationComponent.value.replaceItem(item => item.id === emoji.id, { - ...emoji, + emojisPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({ + ...oldEmoji, ...result.updated - }); + })); } else if (result.deleted) { - emojisPaginationComponent.value.removeItem(item => item.id === emoji.id); + emojisPaginationComponent.value.removeItem((item) => item.id === emoji.id); } }, }, 'closed'); @@ -159,7 +159,7 @@ const remoteMenu = (emoji, ev: MouseEvent) => { }, { text: i18n.ts.import, icon: 'fas fa-plus', - action: () => { im(emoji) } + action: () => { im(emoji); } }], ev.currentTarget ?? ev.target); }; @@ -175,10 +175,10 @@ const menu = (ev: MouseEvent) => { type: 'info', text: i18n.ts.exportRequested, }); - }).catch((e) => { + }).catch((err) => { os.alert({ type: 'error', - text: e.message, + text: err.message, }); }); } @@ -195,10 +195,10 @@ const menu = (ev: MouseEvent) => { type: 'info', text: i18n.ts.importRequested, }); - }).catch((e) => { + }).catch((err) => { os.alert({ type: 'error', - text: e.message, + text: err.message, }); }); } diff --git a/packages/client/src/pages/admin/file-dialog.vue b/packages/client/src/pages/admin/file-dialog.vue index 4c33f62399..0765548aab 100644 --- a/packages/client/src/pages/admin/file-dialog.vue +++ b/packages/client/src/pages/admin/file-dialog.vue @@ -34,74 +34,52 @@ </XModalWindow> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkSwitch from '@/components/form/switch.vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import MkDriveFileThumbnail from '@/components/drive-file-thumbnail.vue'; import bytes from '@/filters/bytes'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkSwitch, - XModalWindow, - MkDriveFileThumbnail, - }, +let file: any = $ref(null); +let info: any = $ref(null); +let isSensitive: boolean = $ref(false); - props: { - fileId: { - required: true, - } - }, - - emits: ['closed'], - - data() { - return { - file: null, - info: null, - isSensitive: false, - }; - }, +const props = defineProps<{ + fileId: string, +}>(); - created() { - this.fetch(); - }, - - methods: { - async fetch() { - this.file = await os.api('drive/files/show', { fileId: this.fileId }); - this.info = await os.api('admin/drive/show-file', { fileId: this.fileId }); - this.isSensitive = this.file.isSensitive; - }, +async function fetch() { + file = await os.api('drive/files/show', { fileId: props.fileId }); + info = await os.api('admin/drive/show-file', { fileId: props.fileId }); + isSensitive = file.isSensitive; +} - showUser() { - os.pageWindow(`/user-info/${this.file.userId}`); - }, +fetch(); - async del() { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: this.file.name }), - }); - if (canceled) return; +function showUser() { + os.pageWindow(`/user-info/${file.userId}`); +} - os.apiWithDialog('drive/files/delete', { - fileId: this.file.id - }); - }, +async function del() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: file.name }), + }); + if (canceled) return; - async toggleIsSensitive(v) { - await os.api('drive/files/update', { fileId: this.fileId, isSensitive: v }); - this.isSensitive = v; - }, + os.apiWithDialog('drive/files/delete', { + fileId: file.id + }); +} - bytes - } -}); +async function toggleIsSensitive(v) { + await os.api('drive/files/update', { fileId: props.fileId, isSensitive: v }); + isSensitive = v; +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue index c62f053092..3cda688698 100644 --- a/packages/client/src/pages/admin/files.vue +++ b/packages/client/src/pages/admin/files.vue @@ -55,7 +55,7 @@ </template> <script lang="ts" setup> -import { computed } from 'vue'; +import { computed, defineAsyncComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -93,7 +93,7 @@ function clear() { } function show(file) { - os.popup(import('./file-dialog.vue'), { + os.popup(defineAsyncComponent(() => import('./file-dialog.vue')), { fileId: file.id }, {}, 'closed'); } diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue index 6b11650f48..9b7fa5678e 100644 --- a/packages/client/src/pages/admin/index.vue +++ b/packages/client/src/pages/admin/index.vue @@ -1,6 +1,6 @@ <template> <div ref="el" class="hiyeyicy" :class="{ wide: !narrow }"> - <div v-if="!narrow || page == null" class="nav"> + <div v-if="!narrow || initialPage == null" class="nav"> <MkHeader :info="header"></MkHeader> <MkSpacer :content-max="700" :margin-min="16"> @@ -12,21 +12,21 @@ <MkInfo v-if="noMaintainerInformation" warn class="info">{{ $ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ $ts.configure }}</MkA></MkInfo> <MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ $ts.configure }}</MkA></MkInfo> - <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu> + <MkSuperMenu :def="menuDef" :grid="initialPage == null"></MkSuperMenu> </div> </MkSpacer> </div> - <div class="main"> + <div v-if="!(narrow && initialPage == null)" class="main"> <MkStickyContainer> <template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template> - <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> + <component :is="component" :ref="el => pageChanged(el)" :key="initialPage" v-bind="pageProps"/> </MkStickyContainer> </div> </div> </template> -<script lang="ts"> -import { computed, defineAsyncComponent, defineComponent, isRef, nextTick, onMounted, reactive, ref, watch } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent, nextTick, onMounted, onUnmounted, provide, watch } from 'vue'; import { i18n } from '@/i18n'; import MkSuperMenu from '@/components/ui/super-menu.vue'; import MkInfo from '@/components/ui/info.vue'; @@ -35,292 +35,277 @@ import { instance } from '@/instance'; import * as symbols from '@/symbols'; import * as os from '@/os'; import { lookupUser } from '@/scripts/lookup-user'; +import { MisskeyNavigator } from '@/scripts/navigate'; -export default defineComponent({ - components: { - MkSuperMenu, - MkInfo, - }, +const isEmpty = (x: string | null) => x == null || x === ''; - provide: { - shouldOmitHeaderTitle: false, - }, +const nav = new MisskeyNavigator(); - props: { - initialPage: { - type: String, - required: false - } - }, +const indexInfo = { + title: i18n.ts.controlPanel, + icon: 'fas fa-cog', + bg: 'var(--bg)', + hideHeader: true, +}; - setup(props, context) { - const indexInfo = { - title: i18n.ts.controlPanel, - icon: 'fas fa-cog', - bg: 'var(--bg)', - hideHeader: true, - }; - const INFO = ref(indexInfo); - const childInfo = ref(null); - const page = ref(props.initialPage); - const narrow = ref(false); - const view = ref(null); - const el = ref(null); - const pageChanged = (page) => { - if (page == null) return; - const viewInfo = page[symbols.PAGE_INFO]; - if (isRef(viewInfo)) { - watch(viewInfo, () => { - childInfo.value = viewInfo.value; - }, { immediate: true }); - } else { - childInfo.value = viewInfo; - } - }; - const pageProps = ref({}); +const props = defineProps<{ + initialPage?: string, +}>(); - const isEmpty = (x: any) => x == null || x == ''; +provide('shouldOmitHeaderTitle', false); - const noMaintainerInformation = ref(false); - const noBotProtection = ref(false); +let INFO = $ref(indexInfo); +let childInfo = $ref(null); +let page = $ref(props.initialPage); +let narrow = $ref(false); +let view = $ref(null); +let el = $ref(null); +let pageProps = $ref({}); +let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail); +let noBotProtection = !instance.enableHcaptcha && !instance.enableRecaptcha; - os.api('meta', { detail: true }).then(meta => { - // TODO: 設定が完了しても残ったままになるので、ストリーミングでmeta更新イベントを受け取ってよしなに更新する - noMaintainerInformation.value = isEmpty(meta.maintainerName) || isEmpty(meta.maintainerEmail); - noBotProtection.value = !meta.enableHcaptcha && !meta.enableRecaptcha; - }); +const NARROW_THRESHOLD = 600; +const ro = new ResizeObserver((entries, observer) => { + if (entries.length === 0) return; + narrow = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD; +}); - const menuDef = computed(() => [{ - title: i18n.ts.quickAction, - items: [{ - type: 'button', - icon: 'fas fa-search', - text: i18n.ts.lookup, - action: lookup, - }, ...(instance.disableRegistration ? [{ - type: 'button', - icon: 'fas fa-user', - text: i18n.ts.invite, - action: invite, - }] : [])], - }, { - title: i18n.ts.administration, - items: [{ - icon: 'fas fa-tachometer-alt', - text: i18n.ts.dashboard, - to: '/admin/overview', - active: page.value === 'overview', - }, { - icon: 'fas fa-users', - text: i18n.ts.users, - to: '/admin/users', - active: page.value === 'users', - }, { - icon: 'fas fa-laugh', - text: i18n.ts.customEmojis, - to: '/admin/emojis', - active: page.value === 'emojis', - }, { - icon: 'fas fa-globe', - text: i18n.ts.federation, - to: '/admin/federation', - active: page.value === 'federation', - }, { - icon: 'fas fa-clipboard-list', - text: i18n.ts.jobQueue, - to: '/admin/queue', - active: page.value === 'queue', - }, { - icon: 'fas fa-cloud', - text: i18n.ts.files, - to: '/admin/files', - active: page.value === 'files', - }, { - icon: 'fas fa-broadcast-tower', - text: i18n.ts.announcements, - to: '/admin/announcements', - active: page.value === 'announcements', - }, { - icon: 'fas fa-audio-description', - text: i18n.ts.ads, - to: '/admin/ads', - active: page.value === 'ads', - }, { - icon: 'fas fa-exclamation-circle', - text: i18n.ts.abuseReports, - to: '/admin/abuses', - active: page.value === 'abuses', - }], - }, { - title: i18n.ts.settings, - items: [{ - icon: 'fas fa-cog', - text: i18n.ts.general, - to: '/admin/settings', - active: page.value === 'settings', - }, { - icon: 'fas fa-envelope', - text: i18n.ts.emailServer, - to: '/admin/email-settings', - active: page.value === 'email-settings', - }, { - icon: 'fas fa-cloud', - text: i18n.ts.objectStorage, - to: '/admin/object-storage', - active: page.value === 'object-storage', - }, { - icon: 'fas fa-lock', - text: i18n.ts.security, - to: '/admin/security', - active: page.value === 'security', - }, { - icon: 'fas fa-globe', - text: i18n.ts.relays, - to: '/admin/relays', - active: page.value === 'relays', - }, { - icon: 'fas fa-share-alt', - text: i18n.ts.integration, - to: '/admin/integrations', - active: page.value === 'integrations', - }, { - icon: 'fas fa-ban', - text: i18n.ts.instanceBlocking, - to: '/admin/instance-block', - active: page.value === 'instance-block', - }, { - icon: 'fas fa-ghost', - text: i18n.ts.proxyAccount, - to: '/admin/proxy-account', - active: page.value === 'proxy-account', - }, { - icon: 'fas fa-cogs', - text: i18n.ts.other, - to: '/admin/other-settings', - active: page.value === 'other-settings', - }], - }, { - title: i18n.ts.info, - items: [{ - icon: 'fas fa-database', - text: i18n.ts.database, - to: '/admin/database', - active: page.value === 'database', - }], - }]); - const component = computed(() => { - if (page.value == null) return null; - switch (page.value) { - case 'overview': return defineAsyncComponent(() => import('./overview.vue')); - case 'users': return defineAsyncComponent(() => import('./users.vue')); - case 'emojis': return defineAsyncComponent(() => import('./emojis.vue')); - case 'federation': return defineAsyncComponent(() => import('../federation.vue')); - case 'queue': return defineAsyncComponent(() => import('./queue.vue')); - case 'files': return defineAsyncComponent(() => import('./files.vue')); - case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); - case 'ads': return defineAsyncComponent(() => import('./ads.vue')); - case 'database': return defineAsyncComponent(() => import('./database.vue')); - case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); - case 'settings': return defineAsyncComponent(() => import('./settings.vue')); - case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue')); - case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue')); - case 'security': return defineAsyncComponent(() => import('./security.vue')); - case 'relays': return defineAsyncComponent(() => import('./relays.vue')); - case 'integrations': return defineAsyncComponent(() => import('./integrations.vue')); - case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue')); - case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue')); - case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue')); - } - }); +const menuDef = $computed(() => [{ + title: i18n.ts.quickAction, + items: [{ + type: 'button', + icon: 'fas fa-search', + text: i18n.ts.lookup, + action: lookup, + }, ...(instance.disableRegistration ? [{ + type: 'button', + icon: 'fas fa-user', + text: i18n.ts.invite, + action: invite, + }] : [])], +}, { + title: i18n.ts.administration, + items: [{ + icon: 'fas fa-tachometer-alt', + text: i18n.ts.dashboard, + to: '/admin/overview', + active: props.initialPage === 'overview', + }, { + icon: 'fas fa-users', + text: i18n.ts.users, + to: '/admin/users', + active: props.initialPage === 'users', + }, { + icon: 'fas fa-laugh', + text: i18n.ts.customEmojis, + to: '/admin/emojis', + active: props.initialPage === 'emojis', + }, { + icon: 'fas fa-globe', + text: i18n.ts.federation, + to: '/admin/federation', + active: props.initialPage === 'federation', + }, { + icon: 'fas fa-clipboard-list', + text: i18n.ts.jobQueue, + to: '/admin/queue', + active: props.initialPage === 'queue', + }, { + icon: 'fas fa-cloud', + text: i18n.ts.files, + to: '/admin/files', + active: props.initialPage === 'files', + }, { + icon: 'fas fa-broadcast-tower', + text: i18n.ts.announcements, + to: '/admin/announcements', + active: props.initialPage === 'announcements', + }, { + icon: 'fas fa-audio-description', + text: i18n.ts.ads, + to: '/admin/ads', + active: props.initialPage === 'ads', + }, { + icon: 'fas fa-exclamation-circle', + text: i18n.ts.abuseReports, + to: '/admin/abuses', + active: props.initialPage === 'abuses', + }], +}, { + title: i18n.ts.settings, + items: [{ + icon: 'fas fa-cog', + text: i18n.ts.general, + to: '/admin/settings', + active: props.initialPage === 'settings', + }, { + icon: 'fas fa-envelope', + text: i18n.ts.emailServer, + to: '/admin/email-settings', + active: props.initialPage === 'email-settings', + }, { + icon: 'fas fa-cloud', + text: i18n.ts.objectStorage, + to: '/admin/object-storage', + active: props.initialPage === 'object-storage', + }, { + icon: 'fas fa-lock', + text: i18n.ts.security, + to: '/admin/security', + active: props.initialPage === 'security', + }, { + icon: 'fas fa-globe', + text: i18n.ts.relays, + to: '/admin/relays', + active: props.initialPage === 'relays', + }, { + icon: 'fas fa-share-alt', + text: i18n.ts.integration, + to: '/admin/integrations', + active: props.initialPage === 'integrations', + }, { + icon: 'fas fa-ban', + text: i18n.ts.instanceBlocking, + to: '/admin/instance-block', + active: props.initialPage === 'instance-block', + }, { + icon: 'fas fa-ghost', + text: i18n.ts.proxyAccount, + to: '/admin/proxy-account', + active: props.initialPage === 'proxy-account', + }, { + icon: 'fas fa-cogs', + text: i18n.ts.other, + to: '/admin/other-settings', + active: props.initialPage === 'other-settings', + }], +}, { + title: i18n.ts.info, + items: [{ + icon: 'fas fa-database', + text: i18n.ts.database, + to: '/admin/database', + active: props.initialPage === 'database', + }], +}]); - watch(component, () => { - pageProps.value = {}; +const component = $computed(() => { + if (props.initialPage == null) return null; + switch (props.initialPage) { + case 'overview': return defineAsyncComponent(() => import('./overview.vue')); + case 'users': return defineAsyncComponent(() => import('./users.vue')); + case 'emojis': return defineAsyncComponent(() => import('./emojis.vue')); + case 'federation': return defineAsyncComponent(() => import('../federation.vue')); + case 'queue': return defineAsyncComponent(() => import('./queue.vue')); + case 'files': return defineAsyncComponent(() => import('./files.vue')); + case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); + case 'ads': return defineAsyncComponent(() => import('./ads.vue')); + case 'database': return defineAsyncComponent(() => import('./database.vue')); + case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); + case 'settings': return defineAsyncComponent(() => import('./settings.vue')); + case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue')); + case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue')); + case 'security': return defineAsyncComponent(() => import('./security.vue')); + case 'relays': return defineAsyncComponent(() => import('./relays.vue')); + case 'integrations': return defineAsyncComponent(() => import('./integrations.vue')); + case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue')); + case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue')); + case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue')); + } +}); - nextTick(() => { - scroll(el.value, { top: 0 }); - }); - }, { immediate: true }); +watch(component, () => { + pageProps = {}; - watch(() => props.initialPage, () => { - if (props.initialPage == null && !narrow.value) { - page.value = 'overview'; - } else { - page.value = props.initialPage; - if (props.initialPage == null) { - INFO.value = indexInfo; - } - } - }); + nextTick(() => { + scroll(el, { top: 0 }); + }); +}, { immediate: true }); - onMounted(() => { - narrow.value = el.value.offsetWidth < 800; - if (!narrow.value) { - page.value = 'overview'; - } - }); +watch(() => props.initialPage, () => { + if (props.initialPage == null && !narrow) { + nav.push('/admin/overview'); + } else { + if (props.initialPage == null) { + INFO = indexInfo; + } + } +}); - const invite = () => { - os.api('admin/invite').then(x => { - os.alert({ - type: 'info', - text: x.code - }); - }).catch(e => { - os.alert({ - type: 'error', - text: e - }); - }); - }; +watch(narrow, () => { + if (props.initialPage == null && !narrow) { + nav.push('/admin/overview'); + } +}); - const lookup = (ev) => { - os.popupMenu([{ - text: i18n.ts.user, - icon: 'fas fa-user', - action: () => { - lookupUser(); - } - }, { - text: i18n.ts.note, - icon: 'fas fa-pencil-alt', - action: () => { - alert('TODO'); - } - }, { - text: i18n.ts.file, - icon: 'fas fa-cloud', - action: () => { - alert('TODO'); - } - }, { - text: i18n.ts.instance, - icon: 'fas fa-globe', - action: () => { - alert('TODO'); - } - }], ev.currentTarget ?? ev.target); - }; +onMounted(() => { + ro.observe(el); + + narrow = el.offsetWidth < NARROW_THRESHOLD; + if (props.initialPage == null && !narrow) { + nav.push('/admin/overview'); + } +}); + +onUnmounted(() => { + ro.disconnect(); +}); + +const pageChanged = (page) => { + if (page == null) { + childInfo = null; + } else { + childInfo = page[symbols.PAGE_INFO]; + } +}; - return { - [symbols.PAGE_INFO]: INFO, - menuDef, - header: { - title: i18n.ts.controlPanel, - }, - noMaintainerInformation, - noBotProtection, - page, - narrow, - view, - el, - pageChanged, - childInfo, - pageProps, - component, - invite, - lookup, - }; - }, +const invite = () => { + os.api('admin/invite').then(x => { + os.alert({ + type: 'info', + text: x.code + }); + }).catch(err => { + os.alert({ + type: 'error', + text: err, + }); + }); +}; + +const lookup = (ev) => { + os.popupMenu([{ + text: i18n.ts.user, + icon: 'fas fa-user', + action: () => { + lookupUser(); + } + }, { + text: i18n.ts.note, + icon: 'fas fa-pencil-alt', + action: () => { + alert('TODO'); + } + }, { + text: i18n.ts.file, + icon: 'fas fa-cloud', + action: () => { + alert('TODO'); + } + }, { + text: i18n.ts.instance, + icon: 'fas fa-globe', + action: () => { + alert('TODO'); + } + }], ev.currentTarget ?? ev.target); +}; + +defineExpose({ + [symbols.PAGE_INFO]: INFO, + header: { + title: i18n.ts.controlPanel, + } }); </script> diff --git a/packages/client/src/pages/admin/instance-block.vue b/packages/client/src/pages/admin/instance-block.vue index 4cb8dc604e..3347846a80 100644 --- a/packages/client/src/pages/admin/instance-block.vue +++ b/packages/client/src/pages/admin/instance-block.vue @@ -2,57 +2,45 @@ <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> <FormTextarea v-model="blockedHosts" class="_formBlock"> - <span>{{ $ts.blockedInstances }}</span> - <template #caption>{{ $ts.blockedInstancesDescription }}</template> + <span>{{ i18n.ts.blockedInstances }}</span> + <template #caption>{{ i18n.ts.blockedInstancesDescription }}</template> </FormTextarea> - <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton> </FormSuspense> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormButton from '@/components/ui/button.vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormButton, - FormTextarea, - FormSuspense, - }, +let blockedHosts: string = $ref(''); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + blockedHosts = meta.blockedHosts.join('\n'); +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.instanceBlocking, - icon: 'fas fa-ban', - bg: 'var(--bg)', - }, - blockedHosts: '', - } - }, +function save() { + os.apiWithDialog('admin/update-meta', { + blockedHosts: blockedHosts.split('\n') || [], + }).then(() => { + fetchInstance(); + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.blockedHosts = meta.blockedHosts.join('\n'); - }, - - save() { - os.apiWithDialog('admin/update-meta', { - blockedHosts: this.blockedHosts.split('\n') || [], - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.instanceBlocking, + icon: 'fas fa-ban', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/integrations.discord.vue b/packages/client/src/pages/admin/integrations.discord.vue index 6b50f1b0a9..9fdc51a6ca 100644 --- a/packages/client/src/pages/admin/integrations.discord.vue +++ b/packages/client/src/pages/admin/integrations.discord.vue @@ -24,57 +24,36 @@ </FormSuspense> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; import FormInfo from '@/components/ui/info.vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; -import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormInfo, - FormButton, - FormSuspense, - }, +let uri: string = $ref(''); +let enableDiscordIntegration: boolean = $ref(false); +let discordClientId: string | null = $ref(null); +let discordClientSecret: string | null = $ref(null); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + uri = meta.uri; + enableDiscordIntegration = meta.enableDiscordIntegration; + discordClientId = meta.discordClientId; + discordClientSecret = meta.discordClientSecret; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: 'Discord', - icon: 'fab fa-discord' - }, - enableDiscordIntegration: false, - discordClientId: null, - discordClientSecret: null, - } - }, - - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.uri = meta.uri; - this.enableDiscordIntegration = meta.enableDiscordIntegration; - this.discordClientId = meta.discordClientId; - this.discordClientSecret = meta.discordClientSecret; - }, - save() { - os.apiWithDialog('admin/update-meta', { - enableDiscordIntegration: this.enableDiscordIntegration, - discordClientId: this.discordClientId, - discordClientSecret: this.discordClientSecret, - }).then(() => { - fetchInstance(); - }); - } - } -}); +function save() { + os.apiWithDialog('admin/update-meta', { + enableDiscordIntegration, + discordClientId, + discordClientSecret, + }).then(() => { + fetchInstance(); + }); +} </script> diff --git a/packages/client/src/pages/admin/integrations.github.vue b/packages/client/src/pages/admin/integrations.github.vue index 67f299e1bc..b10ccb8394 100644 --- a/packages/client/src/pages/admin/integrations.github.vue +++ b/packages/client/src/pages/admin/integrations.github.vue @@ -24,57 +24,36 @@ </FormSuspense> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; import FormInfo from '@/components/ui/info.vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; -import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormInfo, - FormButton, - FormSuspense, - }, +let uri: string = $ref(''); +let enableGithubIntegration: boolean = $ref(false); +let githubClientId: string | null = $ref(null); +let githubClientSecret: string | null = $ref(null); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + uri = meta.uri; + enableGithubIntegration = meta.enableGithubIntegration; + githubClientId = meta.githubClientId; + githubClientSecret = meta.githubClientSecret; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: 'GitHub', - icon: 'fab fa-github' - }, - enableGithubIntegration: false, - githubClientId: null, - githubClientSecret: null, - } - }, - - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.uri = meta.uri; - this.enableGithubIntegration = meta.enableGithubIntegration; - this.githubClientId = meta.githubClientId; - this.githubClientSecret = meta.githubClientSecret; - }, - save() { - os.apiWithDialog('admin/update-meta', { - enableGithubIntegration: this.enableGithubIntegration, - githubClientId: this.githubClientId, - githubClientSecret: this.githubClientSecret, - }).then(() => { - fetchInstance(); - }); - } - } -}); +function save() { + os.apiWithDialog('admin/update-meta', { + enableGithubIntegration, + githubClientId, + githubClientSecret, + }).then(() => { + fetchInstance(); + }); +} </script> diff --git a/packages/client/src/pages/admin/integrations.twitter.vue b/packages/client/src/pages/admin/integrations.twitter.vue index a389c71506..11b5fd86b2 100644 --- a/packages/client/src/pages/admin/integrations.twitter.vue +++ b/packages/client/src/pages/admin/integrations.twitter.vue @@ -24,7 +24,7 @@ </FormSuspense> </template> -<script lang="ts"> +<script lang="ts" setup> import { defineComponent } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; @@ -32,49 +32,28 @@ import FormButton from '@/components/ui/button.vue'; import FormInfo from '@/components/ui/info.vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; -import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormInfo, - FormButton, - FormSuspense, - }, +let uri: string = $ref(''); +let enableTwitterIntegration: boolean = $ref(false); +let twitterConsumerKey: string | null = $ref(null); +let twitterConsumerSecret: string | null = $ref(null); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + uri = meta.uri; + enableTwitterIntegration = meta.enableTwitterIntegration; + twitterConsumerKey = meta.twitterConsumerKey; + twitterConsumerSecret = meta.twitterConsumerSecret; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: 'Twitter', - icon: 'fab fa-twitter' - }, - enableTwitterIntegration: false, - twitterConsumerKey: null, - twitterConsumerSecret: null, - } - }, - - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.uri = meta.uri; - this.enableTwitterIntegration = meta.enableTwitterIntegration; - this.twitterConsumerKey = meta.twitterConsumerKey; - this.twitterConsumerSecret = meta.twitterConsumerSecret; - }, - save() { - os.apiWithDialog('admin/update-meta', { - enableTwitterIntegration: this.enableTwitterIntegration, - twitterConsumerKey: this.twitterConsumerKey, - twitterConsumerSecret: this.twitterConsumerSecret, - }).then(() => { - fetchInstance(); - }); - } - } -}); +function save() { + os.apiWithDialog('admin/update-meta', { + enableTwitterIntegration, + twitterConsumerKey, + twitterConsumerSecret, + }).then(() => { + fetchInstance(); + }); +} </script> diff --git a/packages/client/src/pages/admin/integrations.vue b/packages/client/src/pages/admin/integrations.vue index 4db8a9e0a9..d6061d0e51 100644 --- a/packages/client/src/pages/admin/integrations.vue +++ b/packages/client/src/pages/admin/integrations.vue @@ -4,69 +4,52 @@ <FormFolder class="_formBlock"> <template #icon><i class="fab fa-twitter"></i></template> <template #label>Twitter</template> - <template #suffix>{{ enableTwitterIntegration ? $ts.enabled : $ts.disabled }}</template> + <template #suffix>{{ enableTwitterIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template> <XTwitter/> </FormFolder> - <FormFolder to="/admin/integrations/github" class="_formBlock"> + <FormFolder class="_formBlock"> <template #icon><i class="fab fa-github"></i></template> <template #label>GitHub</template> - <template #suffix>{{ enableGithubIntegration ? $ts.enabled : $ts.disabled }}</template> + <template #suffix>{{ enableGithubIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template> <XGithub/> </FormFolder> - <FormFolder to="/admin/integrations/discord" class="_formBlock"> + <FormFolder class="_formBlock"> <template #icon><i class="fab fa-discord"></i></template> <template #label>Discord</template> - <template #suffix>{{ enableDiscordIntegration ? $ts.enabled : $ts.disabled }}</template> + <template #suffix>{{ enableDiscordIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template> <XDiscord/> </FormFolder> </FormSuspense> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormFolder from '@/components/form/folder.vue'; -import FormSecion from '@/components/form/section.vue'; import FormSuspense from '@/components/form/suspense.vue'; import XTwitter from './integrations.twitter.vue'; import XGithub from './integrations.github.vue'; import XDiscord from './integrations.discord.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; -import { fetchInstance } from '@/instance'; - -export default defineComponent({ - components: { - FormFolder, - FormSecion, - FormSuspense, - XTwitter, - XGithub, - XDiscord, - }, +import { i18n } from '@/i18n'; - emits: ['info'], +let enableTwitterIntegration: boolean = $ref(false); +let enableGithubIntegration: boolean = $ref(false); +let enableDiscordIntegration: boolean = $ref(false); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.integration, - icon: 'fas fa-share-alt', - bg: 'var(--bg)', - }, - enableTwitterIntegration: false, - enableGithubIntegration: false, - enableDiscordIntegration: false, - } - }, +async function init() { + const meta = await os.api('admin/meta'); + enableTwitterIntegration = meta.enableTwitterIntegration; + enableGithubIntegration = meta.enableGithubIntegration; + enableDiscordIntegration = meta.enableDiscordIntegration; +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.enableTwitterIntegration = meta.enableTwitterIntegration; - this.enableGithubIntegration = meta.enableGithubIntegration; - this.enableDiscordIntegration = meta.enableDiscordIntegration; - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.integration, + icon: 'fas fa-share-alt', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/metrics.vue b/packages/client/src/pages/admin/metrics.vue index 1de297fd93..7e5f5bb094 100644 --- a/packages/client/src/pages/admin/metrics.vue +++ b/packages/client/src/pages/admin/metrics.vue @@ -132,7 +132,7 @@ export default defineComponent({ overviewHeight: '1fr', queueHeight: '1fr', paused: false, - } + }; }, computed: { diff --git a/packages/client/src/pages/admin/object-storage.vue b/packages/client/src/pages/admin/object-storage.vue index a1ee0761c8..d109db9c38 100644 --- a/packages/client/src/pages/admin/object-storage.vue +++ b/packages/client/src/pages/admin/object-storage.vue @@ -2,32 +2,32 @@ <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> <div class="_formRoot"> - <FormSwitch v-model="useObjectStorage" class="_formBlock">{{ $ts.useObjectStorage }}</FormSwitch> + <FormSwitch v-model="useObjectStorage" class="_formBlock">{{ i18n.ts.useObjectStorage }}</FormSwitch> <template v-if="useObjectStorage"> <FormInput v-model="objectStorageBaseUrl" class="_formBlock"> - <template #label>{{ $ts.objectStorageBaseUrl }}</template> - <template #caption>{{ $ts.objectStorageBaseUrlDesc }}</template> + <template #label>{{ i18n.ts.objectStorageBaseUrl }}</template> + <template #caption>{{ i18n.ts.objectStorageBaseUrlDesc }}</template> </FormInput> <FormInput v-model="objectStorageBucket" class="_formBlock"> - <template #label>{{ $ts.objectStorageBucket }}</template> - <template #caption>{{ $ts.objectStorageBucketDesc }}</template> + <template #label>{{ i18n.ts.objectStorageBucket }}</template> + <template #caption>{{ i18n.ts.objectStorageBucketDesc }}</template> </FormInput> <FormInput v-model="objectStoragePrefix" class="_formBlock"> - <template #label>{{ $ts.objectStoragePrefix }}</template> - <template #caption>{{ $ts.objectStoragePrefixDesc }}</template> + <template #label>{{ i18n.ts.objectStoragePrefix }}</template> + <template #caption>{{ i18n.ts.objectStoragePrefixDesc }}</template> </FormInput> <FormInput v-model="objectStorageEndpoint" class="_formBlock"> - <template #label>{{ $ts.objectStorageEndpoint }}</template> - <template #caption>{{ $ts.objectStorageEndpointDesc }}</template> + <template #label>{{ i18n.ts.objectStorageEndpoint }}</template> + <template #caption>{{ i18n.ts.objectStorageEndpointDesc }}</template> </FormInput> <FormInput v-model="objectStorageRegion" class="_formBlock"> - <template #label>{{ $ts.objectStorageRegion }}</template> - <template #caption>{{ $ts.objectStorageRegionDesc }}</template> + <template #label>{{ i18n.ts.objectStorageRegion }}</template> + <template #caption>{{ i18n.ts.objectStorageRegionDesc }}</template> </FormInput> <FormSplit :min-width="280"> @@ -43,17 +43,17 @@ </FormSplit> <FormSwitch v-model="objectStorageUseSSL" class="_formBlock"> - <template #label>{{ $ts.objectStorageUseSSL }}</template> - <template #caption>{{ $ts.objectStorageUseSSLDesc }}</template> + <template #label>{{ i18n.ts.objectStorageUseSSL }}</template> + <template #caption>{{ i18n.ts.objectStorageUseSSLDesc }}</template> </FormSwitch> <FormSwitch v-model="objectStorageUseProxy" class="_formBlock"> - <template #label>{{ $ts.objectStorageUseProxy }}</template> - <template #caption>{{ $ts.objectStorageUseProxyDesc }}</template> + <template #label>{{ i18n.ts.objectStorageUseProxy }}</template> + <template #caption>{{ i18n.ts.objectStorageUseProxyDesc }}</template> </FormSwitch> <FormSwitch v-model="objectStorageSetPublicRead" class="_formBlock"> - <template #label>{{ $ts.objectStorageSetPublicRead }}</template> + <template #label>{{ i18n.ts.objectStorageSetPublicRead }}</template> </FormSwitch> <FormSwitch v-model="objectStorageS3ForcePathStyle" class="_formBlock"> @@ -65,8 +65,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormGroup from '@/components/form/group.vue'; @@ -76,84 +76,70 @@ import FormSection from '@/components/form/section.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormGroup, - FormSuspense, - FormSplit, - FormSection, - }, +let useObjectStorage: boolean = $ref(false); +let objectStorageBaseUrl: string | null = $ref(null); +let objectStorageBucket: string | null = $ref(null); +let objectStoragePrefix: string | null = $ref(null); +let objectStorageEndpoint: string | null = $ref(null); +let objectStorageRegion: string | null = $ref(null); +let objectStoragePort: number | null = $ref(null); +let objectStorageAccessKey: string | null = $ref(null); +let objectStorageSecretKey: string | null = $ref(null); +let objectStorageUseSSL: boolean = $ref(false); +let objectStorageUseProxy: boolean = $ref(false); +let objectStorageSetPublicRead: boolean = $ref(false); +let objectStorageS3ForcePathStyle: boolean = $ref(true); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + useObjectStorage = meta.useObjectStorage; + objectStorageBaseUrl = meta.objectStorageBaseUrl; + objectStorageBucket = meta.objectStorageBucket; + objectStoragePrefix = meta.objectStoragePrefix; + objectStorageEndpoint = meta.objectStorageEndpoint; + objectStorageRegion = meta.objectStorageRegion; + objectStoragePort = meta.objectStoragePort; + objectStorageAccessKey = meta.objectStorageAccessKey; + objectStorageSecretKey = meta.objectStorageSecretKey; + objectStorageUseSSL = meta.objectStorageUseSSL; + objectStorageUseProxy = meta.objectStorageUseProxy; + objectStorageSetPublicRead = meta.objectStorageSetPublicRead; + objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.objectStorage, - icon: 'fas fa-cloud', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-check', - text: this.$ts.save, - handler: this.save, - }], - }, - useObjectStorage: false, - objectStorageBaseUrl: null, - objectStorageBucket: null, - objectStoragePrefix: null, - objectStorageEndpoint: null, - objectStorageRegion: null, - objectStoragePort: null, - objectStorageAccessKey: null, - objectStorageSecretKey: null, - objectStorageUseSSL: false, - objectStorageUseProxy: false, - objectStorageSetPublicRead: false, - objectStorageS3ForcePathStyle: true, - } - }, +function save() { + os.apiWithDialog('admin/update-meta', { + useObjectStorage, + objectStorageBaseUrl, + objectStorageBucket, + objectStoragePrefix, + objectStorageEndpoint, + objectStorageRegion, + objectStoragePort, + objectStorageAccessKey, + objectStorageSecretKey, + objectStorageUseSSL, + objectStorageUseProxy, + objectStorageSetPublicRead, + objectStorageS3ForcePathStyle, + }).then(() => { + fetchInstance(); + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.useObjectStorage = meta.useObjectStorage; - this.objectStorageBaseUrl = meta.objectStorageBaseUrl; - this.objectStorageBucket = meta.objectStorageBucket; - this.objectStoragePrefix = meta.objectStoragePrefix; - this.objectStorageEndpoint = meta.objectStorageEndpoint; - this.objectStorageRegion = meta.objectStorageRegion; - this.objectStoragePort = meta.objectStoragePort; - this.objectStorageAccessKey = meta.objectStorageAccessKey; - this.objectStorageSecretKey = meta.objectStorageSecretKey; - this.objectStorageUseSSL = meta.objectStorageUseSSL; - this.objectStorageUseProxy = meta.objectStorageUseProxy; - this.objectStorageSetPublicRead = meta.objectStorageSetPublicRead; - this.objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle; - }, - save() { - os.apiWithDialog('admin/update-meta', { - useObjectStorage: this.useObjectStorage, - objectStorageBaseUrl: this.objectStorageBaseUrl ? this.objectStorageBaseUrl : null, - objectStorageBucket: this.objectStorageBucket ? this.objectStorageBucket : null, - objectStoragePrefix: this.objectStoragePrefix ? this.objectStoragePrefix : null, - objectStorageEndpoint: this.objectStorageEndpoint ? this.objectStorageEndpoint : null, - objectStorageRegion: this.objectStorageRegion ? this.objectStorageRegion : null, - objectStoragePort: this.objectStoragePort ? this.objectStoragePort : null, - objectStorageAccessKey: this.objectStorageAccessKey ? this.objectStorageAccessKey : null, - objectStorageSecretKey: this.objectStorageSecretKey ? this.objectStorageSecretKey : null, - objectStorageUseSSL: this.objectStorageUseSSL, - objectStorageUseProxy: this.objectStorageUseProxy, - objectStorageSetPublicRead: this.objectStorageSetPublicRead, - objectStorageS3ForcePathStyle: this.objectStorageS3ForcePathStyle, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.objectStorage, + icon: 'fas fa-cloud', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: i18n.ts.save, + handler: save, + }], } }); </script> diff --git a/packages/client/src/pages/admin/other-settings.vue b/packages/client/src/pages/admin/other-settings.vue index 99ea6a5f32..552b05f347 100644 --- a/packages/client/src/pages/admin/other-settings.vue +++ b/packages/client/src/pages/admin/other-settings.vue @@ -6,52 +6,35 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormInput from '@/components/form/input.vue'; -import FormSection from '@/components/form/section.vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormSection, - FormSuspense, - }, +async function init() { + await os.api('admin/meta'); +} - emits: ['info'], +function save() { + os.apiWithDialog('admin/update-meta').then(() => { + fetchInstance(); + }); +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.other, - icon: 'fas fa-cogs', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-check', - text: this.$ts.save, - handler: this.save, - }], - }, - } - }, - - methods: { - async init() { - const meta = await os.api('admin/meta'); - }, - save() { - os.apiWithDialog('admin/update-meta', { - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.other, + icon: 'fas fa-cogs', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: i18n.ts.save, + handler: save, + }], } }); </script> diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue index b8ae8ad9e1..cc69424c3b 100644 --- a/packages/client/src/pages/admin/overview.vue +++ b/packages/client/src/pages/admin/overview.vue @@ -5,20 +5,20 @@ <div class="label">Users</div> <div class="value _monospace"> {{ number(stats.originalUsersCount) }} - <MkNumberDiff v-if="usersComparedToThePrevDay != null" v-tooltip="$ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff> + <MkNumberDiff v-if="usersComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff> </div> </div> <div class="number _panel"> <div class="label">Notes</div> <div class="value _monospace"> {{ number(stats.originalNotesCount) }} - <MkNumberDiff v-if="notesComparedToThePrevDay != null" v-tooltip="$ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff> + <MkNumberDiff v-if="notesComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff> </div> </div> </div> <MkContainer :foldable="true" class="charts"> - <template #header><i class="fas fa-chart-bar"></i>{{ $ts.charts }}</template> + <template #header><i class="fas fa-chart-bar"></i>{{ i18n.ts.charts }}</template> <div style="padding: 12px;"> <MkInstanceStats :chart-limit="500" :detailed="true"/> </div> @@ -38,7 +38,7 @@ <!--<XMetrics/>--> <MkFolder style="margin: var(--margin)"> - <template #header><i class="fas fa-info-circle"></i> {{ $ts.info }}</template> + <template #header><i class="fas fa-info-circle"></i> {{ i18n.ts.info }}</template> <div class="cfcdecdf"> <div class="number _panel"> <div class="label">Misskey</div> @@ -65,103 +65,61 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent, markRaw, version as vueVersion } from 'vue'; +<script lang="ts" setup> +import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; import MkInstanceStats from '@/components/instance-stats.vue'; -import MkButton from '@/components/ui/button.vue'; -import MkSelect from '@/components/form/select.vue'; import MkNumberDiff from '@/components/number-diff.vue'; import MkContainer from '@/components/ui/container.vue'; import MkFolder from '@/components/ui/folder.vue'; import MkQueueChart from '@/components/queue-chart.vue'; import { version, url } from '@/config'; -import bytes from '@/filters/bytes'; import number from '@/filters/number'; import XMetrics from './metrics.vue'; import * as os from '@/os'; import { stream } from '@/stream'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkNumberDiff, - MkInstanceStats, - MkContainer, - MkFolder, - MkQueueChart, - XMetrics, - }, +let stats: any = $ref(null); +let serverInfo: any = $ref(null); +let usersComparedToThePrevDay: any = $ref(null); +let notesComparedToThePrevDay: any = $ref(null); +const queueStatsConnection = markRaw(stream.useChannel('queueStats')); - emits: ['info'], +onMounted(async () => { + os.api('stats', {}).then(statsResponse => { + stats = statsResponse; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.dashboard, - icon: 'fas fa-tachometer-alt', - bg: 'var(--bg)', - }, - version, - vueVersion, - url, - stats: null, - meta: null, - serverInfo: null, - usersComparedToThePrevDay: null, - notesComparedToThePrevDay: null, - fetchJobs: () => os.api('admin/queue/deliver-delayed', {}), - fetchModLogs: () => os.api('admin/show-moderation-logs', {}), - queueStatsConnection: markRaw(stream.useChannel('queueStats')), - } - }, - - async mounted() { - os.api('meta', { detail: true }).then(meta => { - this.meta = meta; + os.api('charts/users', { limit: 2, span: 'day' }).then(chart => { + usersComparedToThePrevDay = stats.originalUsersCount - chart.local.total[1]; }); - - os.api('stats', {}).then(stats => { - this.stats = stats; - - os.api('charts/users', { limit: 2, span: 'day' }).then(chart => { - this.usersComparedToThePrevDay = this.stats.originalUsersCount - chart.local.total[1]; - }); - os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => { - this.notesComparedToThePrevDay = this.stats.originalNotesCount - chart.local.total[1]; - }); + os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => { + notesComparedToThePrevDay = stats.originalNotesCount - chart.local.total[1]; }); + }); - os.api('admin/server-info', {}).then(serverInfo => { - this.serverInfo = serverInfo; - }); + os.api('admin/server-info').then(serverInfoResponse => { + serverInfo = serverInfoResponse; + }); - this.$nextTick(() => { - this.queueStatsConnection.send('requestLog', { - id: Math.random().toString().substr(2, 8), - length: 200 - }); + nextTick(() => { + queueStatsConnection.send('requestLog', { + id: Math.random().toString().substr(2, 8), + length: 200 }); - }, - - beforeUnmount() { - this.queueStatsConnection.dispose(); - }, - - methods: { - async showInstanceInfo(q) { - let instance = q; - if (typeof q === 'string') { - instance = await os.api('federation/show-instance', { - host: q - }); - } - // TODO - }, + }); +}); - bytes, +onBeforeUnmount(() => { + queueStatsConnection.dispose(); +}); - number, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.dashboard, + icon: 'fas fa-tachometer-alt', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/proxy-account.vue b/packages/client/src/pages/admin/proxy-account.vue index 00f14a176f..727e20e7e5 100644 --- a/packages/client/src/pages/admin/proxy-account.vue +++ b/packages/client/src/pages/admin/proxy-account.vue @@ -1,19 +1,19 @@ <template> <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <MkInfo class="_formBlock">{{ $ts.proxyAccountDescription }}</MkInfo> + <MkInfo class="_formBlock">{{ i18n.ts.proxyAccountDescription }}</MkInfo> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.proxyAccount }}</template> - <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template> + <template #key>{{ i18n.ts.proxyAccount }}</template> + <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : i18n.ts.none }}</template> </MkKeyValue> - <FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton> + <FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</FormButton> </FormSuspense> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkKeyValue from '@/components/key-value.vue'; import FormButton from '@/components/ui/button.vue'; import MkInfo from '@/components/ui/info.vue'; @@ -21,53 +21,40 @@ import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkKeyValue, - FormButton, - MkInfo, - FormSuspense, - }, +let proxyAccount: any = $ref(null); +let proxyAccountId: any = $ref(null); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.proxyAccount, - icon: 'fas fa-ghost', - bg: 'var(--bg)', - }, - proxyAccount: null, - proxyAccountId: null, - } - }, +async function init() { + const meta = await os.api('admin/meta'); + proxyAccountId = meta.proxyAccountId; + if (proxyAccountId) { + proxyAccount = await os.api('users/show', { userId: proxyAccountId }); + } +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.proxyAccountId = meta.proxyAccountId; - if (this.proxyAccountId) { - this.proxyAccount = await os.api('users/show', { userId: this.proxyAccountId }); - } - }, +function chooseProxyAccount() { + os.selectUser().then(user => { + proxyAccount = user; + proxyAccountId = user.id; + save(); + }); +} - chooseProxyAccount() { - os.selectUser().then(user => { - this.proxyAccount = user; - this.proxyAccountId = user.id; - this.save(); - }); - }, +function save() { + os.apiWithDialog('admin/update-meta', { + proxyAccountId: proxyAccountId, + }).then(() => { + fetchInstance(); + }); +} - save() { - os.apiWithDialog('admin/update-meta', { - proxyAccountId: this.proxyAccountId, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.proxyAccount, + icon: 'fas fa-ghost', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/queue.chart.vue b/packages/client/src/pages/admin/queue.chart.vue index 136fb63bb6..be63830bdd 100644 --- a/packages/client/src/pages/admin/queue.chart.vue +++ b/packages/client/src/pages/admin/queue.chart.vue @@ -26,62 +26,40 @@ </div> </template> -<script lang="ts"> -import { defineComponent, markRaw, onMounted, onUnmounted, ref } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; import number from '@/filters/number'; import MkQueueChart from '@/components/queue-chart.vue'; import * as os from '@/os'; -export default defineComponent({ - components: { - MkQueueChart - }, +const activeSincePrevTick = ref(0); +const active = ref(0); +const waiting = ref(0); +const delayed = ref(0); +const jobs = ref([]); - props: { - domain: { - type: String, - required: true, - }, - connection: { - required: true, - }, - }, +const props = defineProps<{ + domain: string, + connection: any, +}>(); - setup(props) { - const activeSincePrevTick = ref(0); - const active = ref(0); - const waiting = ref(0); - const delayed = ref(0); - const jobs = ref([]); +onMounted(() => { + os.api(props.domain === 'inbox' ? 'admin/queue/inbox-delayed' : props.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(result => { + jobs.value = result; + }); - onMounted(() => { - os.api(props.domain === 'inbox' ? 'admin/queue/inbox-delayed' : props.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(result => { - jobs.value = result; - }); + const onStats = (stats) => { + activeSincePrevTick.value = stats[props.domain].activeSincePrevTick; + active.value = stats[props.domain].active; + waiting.value = stats[props.domain].waiting; + delayed.value = stats[props.domain].delayed; + }; - const onStats = (stats) => { - activeSincePrevTick.value = stats[props.domain].activeSincePrevTick; - active.value = stats[props.domain].active; - waiting.value = stats[props.domain].waiting; - delayed.value = stats[props.domain].delayed; - }; + props.connection.on('stats', onStats); - props.connection.on('stats', onStats); - - onUnmounted(() => { - props.connection.off('stats', onStats); - }); - }); - - return { - jobs, - activeSincePrevTick, - active, - waiting, - delayed, - number, - }; - }, + onUnmounted(() => { + props.connection.off('stats', onStats); + }); }); </script> diff --git a/packages/client/src/pages/admin/queue.vue b/packages/client/src/pages/admin/queue.vue index 35fd618c82..656b18199f 100644 --- a/packages/client/src/pages/admin/queue.vue +++ b/packages/client/src/pages/admin/queue.vue @@ -6,71 +6,60 @@ <XQueue :connection="connection" domain="deliver"> <template #title>Out</template> </XQueue> - <MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</MkButton> + <MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ i18n.ts.clearQueue }}</MkButton> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { markRaw, onMounted, onBeforeUnmount, nextTick } from 'vue'; import MkButton from '@/components/ui/button.vue'; import XQueue from './queue.chart.vue'; import * as os from '@/os'; import { stream } from '@/stream'; import * as symbols from '@/symbols'; import * as config from '@/config'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - XQueue, - }, +const connection = markRaw(stream.useChannel('queueStats')); - emits: ['info'], +function clear() { + os.confirm({ + type: 'warning', + title: i18n.ts.clearQueueConfirmTitle, + text: i18n.ts.clearQueueConfirmText, + }).then(({ canceled }) => { + if (canceled) return; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.jobQueue, - icon: 'fas fa-clipboard-list', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-up-right-from-square', - text: this.$ts.dashboard, - handler: () => { - window.open(config.url + '/queue', '_blank'); - }, - }], - }, - connection: markRaw(stream.useChannel('queueStats')), - } - }, + os.apiWithDialog('admin/queue/clear'); + }); +} - mounted() { - this.$nextTick(() => { - this.connection.send('requestLog', { - id: Math.random().toString().substr(2, 8), - length: 200 - }); +onMounted(() => { + nextTick(() => { + connection.send('requestLog', { + id: Math.random().toString().substr(2, 8), + length: 200 }); - }, - - beforeUnmount() { - this.connection.dispose(); - }, + }); +}); - methods: { - clear() { - os.confirm({ - type: 'warning', - title: this.$ts.clearQueueConfirmTitle, - text: this.$ts.clearQueueConfirmText, - }).then(({ canceled }) => { - if (canceled) return; +onBeforeUnmount(() => { + connection.dispose(); +}); - os.apiWithDialog('admin/queue/clear', {}); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.jobQueue, + icon: 'fas fa-clipboard-list', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-up-right-from-square', + text: i18n.ts.dashboard, + handler: () => { + window.open(config.url + '/queue', '_blank'); + }, + }], } }); </script> diff --git a/packages/client/src/pages/admin/relays.vue b/packages/client/src/pages/admin/relays.vue index bb840db0a2..1a36bb4753 100644 --- a/packages/client/src/pages/admin/relays.vue +++ b/packages/client/src/pages/admin/relays.vue @@ -8,84 +8,71 @@ <i v-else class="fas fa-clock icon requesting"></i> <span>{{ $t(`_relayStatus.${relay.status}`) }}</span> </div> - <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> + <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton> </div> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - }, +let relays: any[] = $ref([]); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.relays, - icon: 'fas fa-globe', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-plus', - text: this.$ts.addRelay, - handler: this.addRelay, - }], - }, - relays: [], - inbox: '', - } - }, +async function addRelay() { + const { canceled, result: inbox } = await os.inputText({ + title: i18n.ts.addRelay, + type: 'url', + placeholder: i18n.ts.inboxUrl + }); + if (canceled) return; + os.api('admin/relays/add', { + inbox + }).then((relay: any) => { + refresh(); + }).catch((err: any) => { + os.alert({ + type: 'error', + text: err.message || err + }); + }); +} - created() { - this.refresh(); - }, +function remove(inbox: string) { + os.api('admin/relays/remove', { + inbox + }).then(() => { + refresh(); + }).catch((err: any) => { + os.alert({ + type: 'error', + text: err.message || err + }); + }); +} - methods: { - async addRelay() { - const { canceled, result: inbox } = await os.inputText({ - title: this.$ts.addRelay, - type: 'url', - placeholder: this.$ts.inboxUrl - }); - if (canceled) return; - os.api('admin/relays/add', { - inbox - }).then((relay: any) => { - this.refresh(); - }).catch((e: any) => { - os.alert({ - type: 'error', - text: e.message || e - }); - }); - }, +function refresh() { + os.api('admin/relays/list').then((relayList: any) => { + relays = relayList; + }); +} - remove(inbox: string) { - os.api('admin/relays/remove', { - inbox - }).then(() => { - this.refresh(); - }).catch((e: any) => { - os.alert({ - type: 'error', - text: e.message || e - }); - }); - }, +refresh(); - refresh() { - os.api('admin/relays/list').then((relays: any) => { - this.relays = relays; - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.relays, + icon: 'fas fa-globe', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: i18n.ts.addRelay, + handler: addRelay, + }], } }); </script> diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue index d1c979b3e0..6b8f70cca5 100644 --- a/packages/client/src/pages/admin/security.vue +++ b/packages/client/src/pages/admin/security.vue @@ -4,10 +4,10 @@ <div class="_formRoot"> <FormFolder class="_formBlock"> <template #icon><i class="fas fa-shield-alt"></i></template> - <template #label>{{ $ts.botProtection }}</template> + <template #label>{{ i18n.ts.botProtection }}</template> <template v-if="enableHcaptcha" #suffix>hCaptcha</template> <template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template> - <template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template> + <template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template> <XBotProtection/> </FormFolder> @@ -21,7 +21,7 @@ <template #label>Summaly Proxy URL</template> </FormInput> - <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton> </div> </FormFolder> </div> @@ -29,8 +29,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormFolder from '@/components/form/folder.vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInfo from '@/components/ui/info.vue'; @@ -42,49 +42,32 @@ import XBotProtection from './bot-protection.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormFolder, - FormSwitch, - FormInfo, - FormSection, - FormSuspense, - FormButton, - FormInput, - XBotProtection, - }, +let summalyProxy: string = $ref(''); +let enableHcaptcha: boolean = $ref(false); +let enableRecaptcha: boolean = $ref(false); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + summalyProxy = meta.summalyProxy; + enableHcaptcha = meta.enableHcaptcha; + enableRecaptcha = meta.enableRecaptcha; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.security, - icon: 'fas fa-lock', - bg: 'var(--bg)', - }, - summalyProxy: '', - enableHcaptcha: false, - enableRecaptcha: false, - } - }, +function save() { + os.apiWithDialog('admin/update-meta', { + summalyProxy, + }).then(() => { + fetchInstance(); + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.summalyProxy = meta.summalyProxy; - this.enableHcaptcha = meta.enableHcaptcha; - this.enableRecaptcha = meta.enableRecaptcha; - }, - - save() { - os.apiWithDialog('admin/update-meta', { - summalyProxy: this.summalyProxy, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.security, + icon: 'fas fa-lock', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue index f2970d0459..6dc30fe50b 100644 --- a/packages/client/src/pages/admin/settings.vue +++ b/packages/client/src/pages/admin/settings.vue @@ -3,104 +3,104 @@ <FormSuspense :p="init"> <div class="_formRoot"> <FormInput v-model="name" class="_formBlock"> - <template #label>{{ $ts.instanceName }}</template> + <template #label>{{ i18n.ts.instanceName }}</template> </FormInput> <FormTextarea v-model="description" class="_formBlock"> - <template #label>{{ $ts.instanceDescription }}</template> + <template #label>{{ i18n.ts.instanceDescription }}</template> </FormTextarea> <FormInput v-model="tosUrl" class="_formBlock"> <template #prefix><i class="fas fa-link"></i></template> - <template #label>{{ $ts.tosUrl }}</template> + <template #label>{{ i18n.ts.tosUrl }}</template> </FormInput> <FormSplit :min-width="300"> <FormInput v-model="maintainerName" class="_formBlock"> - <template #label>{{ $ts.maintainerName }}</template> + <template #label>{{ i18n.ts.maintainerName }}</template> </FormInput> <FormInput v-model="maintainerEmail" type="email" class="_formBlock"> <template #prefix><i class="fas fa-envelope"></i></template> - <template #label>{{ $ts.maintainerEmail }}</template> + <template #label>{{ i18n.ts.maintainerEmail }}</template> </FormInput> </FormSplit> <FormTextarea v-model="pinnedUsers" class="_formBlock"> - <template #label>{{ $ts.pinnedUsers }}</template> - <template #caption>{{ $ts.pinnedUsersDescription }}</template> + <template #label>{{ i18n.ts.pinnedUsers }}</template> + <template #caption>{{ i18n.ts.pinnedUsersDescription }}</template> </FormTextarea> <FormSection> <FormSwitch v-model="enableRegistration" class="_formBlock"> - <template #label>{{ $ts.enableRegistration }}</template> + <template #label>{{ i18n.ts.enableRegistration }}</template> </FormSwitch> <FormSwitch v-model="emailRequiredForSignup" class="_formBlock"> - <template #label>{{ $ts.emailRequiredForSignup }}</template> + <template #label>{{ i18n.ts.emailRequiredForSignup }}</template> </FormSwitch> </FormSection> <FormSection> - <FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ $ts.enableLocalTimeline }}</FormSwitch> - <FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ $ts.enableGlobalTimeline }}</FormSwitch> - <FormInfo class="_formBlock">{{ $ts.disablingTimelinesInfo }}</FormInfo> + <FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ i18n.ts.enableLocalTimeline }}</FormSwitch> + <FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ i18n.ts.enableGlobalTimeline }}</FormSwitch> + <FormInfo class="_formBlock">{{ i18n.ts.disablingTimelinesInfo }}</FormInfo> </FormSection> <FormSection> - <template #label>{{ $ts.theme }}</template> + <template #label>{{ i18n.ts.theme }}</template> <FormInput v-model="iconUrl" class="_formBlock"> <template #prefix><i class="fas fa-link"></i></template> - <template #label>{{ $ts.iconUrl }}</template> + <template #label>{{ i18n.ts.iconUrl }}</template> </FormInput> <FormInput v-model="bannerUrl" class="_formBlock"> <template #prefix><i class="fas fa-link"></i></template> - <template #label>{{ $ts.bannerUrl }}</template> + <template #label>{{ i18n.ts.bannerUrl }}</template> </FormInput> <FormInput v-model="backgroundImageUrl" class="_formBlock"> <template #prefix><i class="fas fa-link"></i></template> - <template #label>{{ $ts.backgroundImageUrl }}</template> + <template #label>{{ i18n.ts.backgroundImageUrl }}</template> </FormInput> <FormInput v-model="themeColor" class="_formBlock"> <template #prefix><i class="fas fa-palette"></i></template> - <template #label>{{ $ts.themeColor }}</template> + <template #label>{{ i18n.ts.themeColor }}</template> <template #caption>#RRGGBB</template> </FormInput> <FormTextarea v-model="defaultLightTheme" class="_formBlock"> - <template #label>{{ $ts.instanceDefaultLightTheme }}</template> - <template #caption>{{ $ts.instanceDefaultThemeDescription }}</template> + <template #label>{{ i18n.ts.instanceDefaultLightTheme }}</template> + <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template> </FormTextarea> <FormTextarea v-model="defaultDarkTheme" class="_formBlock"> - <template #label>{{ $ts.instanceDefaultDarkTheme }}</template> - <template #caption>{{ $ts.instanceDefaultThemeDescription }}</template> + <template #label>{{ i18n.ts.instanceDefaultDarkTheme }}</template> + <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template> </FormTextarea> </FormSection> <FormSection> - <template #label>{{ $ts.files }}</template> + <template #label>{{ i18n.ts.files }}</template> <FormSwitch v-model="cacheRemoteFiles" class="_formBlock"> - <template #label>{{ $ts.cacheRemoteFiles }}</template> - <template #caption>{{ $ts.cacheRemoteFilesDescription }}</template> + <template #label>{{ i18n.ts.cacheRemoteFiles }}</template> + <template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}</template> </FormSwitch> <FormSplit :min-width="280"> <FormInput v-model="localDriveCapacityMb" type="number" class="_formBlock"> - <template #label>{{ $ts.driveCapacityPerLocalAccount }}</template> + <template #label>{{ i18n.ts.driveCapacityPerLocalAccount }}</template> <template #suffix>MB</template> - <template #caption>{{ $ts.inMb }}</template> + <template #caption>{{ i18n.ts.inMb }}</template> </FormInput> <FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" class="_formBlock"> - <template #label>{{ $ts.driveCapacityPerRemoteAccount }}</template> + <template #label>{{ i18n.ts.driveCapacityPerRemoteAccount }}</template> <template #suffix>MB</template> - <template #caption>{{ $ts.inMb }}</template> + <template #caption>{{ i18n.ts.inMb }}</template> </FormInput> </FormSplit> </FormSection> @@ -109,8 +109,8 @@ <template #label>ServiceWorker</template> <FormSwitch v-model="enableServiceWorker" class="_formBlock"> - <template #label>{{ $ts.enableServiceworker }}</template> - <template #caption>{{ $ts.serviceworkerInfo }}</template> + <template #label>{{ i18n.ts.enableServiceworker }}</template> + <template #caption>{{ i18n.ts.serviceworkerInfo }}</template> </FormSwitch> <template v-if="enableServiceWorker"> @@ -142,8 +142,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormTextarea from '@/components/form/textarea.vue'; @@ -154,119 +154,103 @@ import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormSuspense, - FormTextarea, - FormInfo, - FormSection, - FormSplit, - }, +let name: string | null = $ref(null); +let description: string | null = $ref(null); +let tosUrl: string | null = $ref(null); +let maintainerName: string | null = $ref(null); +let maintainerEmail: string | null = $ref(null); +let iconUrl: string | null = $ref(null); +let bannerUrl: string | null = $ref(null); +let backgroundImageUrl: string | null = $ref(null); +let themeColor: any = $ref(null); +let defaultLightTheme: any = $ref(null); +let defaultDarkTheme: any = $ref(null); +let enableLocalTimeline: boolean = $ref(false); +let enableGlobalTimeline: boolean = $ref(false); +let pinnedUsers: string = $ref(''); +let cacheRemoteFiles: boolean = $ref(false); +let localDriveCapacityMb: any = $ref(0); +let remoteDriveCapacityMb: any = $ref(0); +let enableRegistration: boolean = $ref(false); +let emailRequiredForSignup: boolean = $ref(false); +let enableServiceWorker: boolean = $ref(false); +let swPublicKey: any = $ref(null); +let swPrivateKey: any = $ref(null); +let deeplAuthKey: string = $ref(''); +let deeplIsPro: boolean = $ref(false); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + name = meta.name; + description = meta.description; + tosUrl = meta.tosUrl; + iconUrl = meta.iconUrl; + bannerUrl = meta.bannerUrl; + backgroundImageUrl = meta.backgroundImageUrl; + themeColor = meta.themeColor; + defaultLightTheme = meta.defaultLightTheme; + defaultDarkTheme = meta.defaultDarkTheme; + maintainerName = meta.maintainerName; + maintainerEmail = meta.maintainerEmail; + enableLocalTimeline = !meta.disableLocalTimeline; + enableGlobalTimeline = !meta.disableGlobalTimeline; + pinnedUsers = meta.pinnedUsers.join('\n'); + cacheRemoteFiles = meta.cacheRemoteFiles; + localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; + remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; + enableRegistration = !meta.disableRegistration; + emailRequiredForSignup = meta.emailRequiredForSignup; + enableServiceWorker = meta.enableServiceWorker; + swPublicKey = meta.swPublickey; + swPrivateKey = meta.swPrivateKey; + deeplAuthKey = meta.deeplAuthKey; + deeplIsPro = meta.deeplIsPro; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.general, - icon: 'fas fa-cog', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-check', - text: this.$ts.save, - handler: this.save, - }], - }, - name: null, - description: null, - tosUrl: null as string | null, - maintainerName: null, - maintainerEmail: null, - iconUrl: null, - bannerUrl: null, - backgroundImageUrl: null, - themeColor: null, - defaultLightTheme: null, - defaultDarkTheme: null, - enableLocalTimeline: false, - enableGlobalTimeline: false, - pinnedUsers: '', - cacheRemoteFiles: false, - localDriveCapacityMb: 0, - remoteDriveCapacityMb: 0, - enableRegistration: false, - emailRequiredForSignup: false, - enableServiceWorker: false, - swPublicKey: null, - swPrivateKey: null, - deeplAuthKey: '', - deeplIsPro: false, - } - }, +function save() { + os.apiWithDialog('admin/update-meta', { + name, + description, + tosUrl, + iconUrl, + bannerUrl, + backgroundImageUrl, + themeColor: themeColor === '' ? null : themeColor, + defaultLightTheme: defaultLightTheme === '' ? null : defaultLightTheme, + defaultDarkTheme: defaultDarkTheme === '' ? null : defaultDarkTheme, + maintainerName, + maintainerEmail, + disableLocalTimeline: !enableLocalTimeline, + disableGlobalTimeline: !enableGlobalTimeline, + pinnedUsers: pinnedUsers.split('\n'), + cacheRemoteFiles, + localDriveCapacityMb: parseInt(localDriveCapacityMb, 10), + remoteDriveCapacityMb: parseInt(remoteDriveCapacityMb, 10), + disableRegistration: !enableRegistration, + emailRequiredForSignup, + enableServiceWorker, + swPublicKey, + swPrivateKey, + deeplAuthKey, + deeplIsPro, + }).then(() => { + fetchInstance(); + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.name = meta.name; - this.description = meta.description; - this.tosUrl = meta.tosUrl; - this.iconUrl = meta.iconUrl; - this.bannerUrl = meta.bannerUrl; - this.backgroundImageUrl = meta.backgroundImageUrl; - this.themeColor = meta.themeColor; - this.defaultLightTheme = meta.defaultLightTheme; - this.defaultDarkTheme = meta.defaultDarkTheme; - this.maintainerName = meta.maintainerName; - this.maintainerEmail = meta.maintainerEmail; - this.enableLocalTimeline = !meta.disableLocalTimeline; - this.enableGlobalTimeline = !meta.disableGlobalTimeline; - this.pinnedUsers = meta.pinnedUsers.join('\n'); - this.cacheRemoteFiles = meta.cacheRemoteFiles; - this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; - this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; - this.enableRegistration = !meta.disableRegistration; - this.emailRequiredForSignup = meta.emailRequiredForSignup; - this.enableServiceWorker = meta.enableServiceWorker; - this.swPublicKey = meta.swPublickey; - this.swPrivateKey = meta.swPrivateKey; - this.deeplAuthKey = meta.deeplAuthKey; - this.deeplIsPro = meta.deeplIsPro; - }, - - save() { - os.apiWithDialog('admin/update-meta', { - name: this.name, - description: this.description, - tosUrl: this.tosUrl, - iconUrl: this.iconUrl, - bannerUrl: this.bannerUrl, - backgroundImageUrl: this.backgroundImageUrl, - themeColor: this.themeColor === '' ? null : this.themeColor, - defaultLightTheme: this.defaultLightTheme === '' ? null : this.defaultLightTheme, - defaultDarkTheme: this.defaultDarkTheme === '' ? null : this.defaultDarkTheme, - maintainerName: this.maintainerName, - maintainerEmail: this.maintainerEmail, - disableLocalTimeline: !this.enableLocalTimeline, - disableGlobalTimeline: !this.enableGlobalTimeline, - pinnedUsers: this.pinnedUsers.split('\n'), - cacheRemoteFiles: this.cacheRemoteFiles, - localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10), - remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10), - disableRegistration: !this.enableRegistration, - emailRequiredForSignup: this.emailRequiredForSignup, - enableServiceWorker: this.enableServiceWorker, - swPublicKey: this.swPublicKey, - swPrivateKey: this.swPrivateKey, - deeplAuthKey: this.deeplAuthKey, - deeplIsPro: this.deeplIsPro, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.general, + icon: 'fas fa-cog', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: i18n.ts.save, + handler: save, + }], } }); </script> diff --git a/packages/client/src/pages/api-console.vue b/packages/client/src/pages/api-console.vue index 142a3bee2e..88acbcd3a3 100644 --- a/packages/client/src/pages/api-console.vue +++ b/packages/client/src/pages/api-console.vue @@ -25,72 +25,60 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as JSON5 from 'json5'; +<script lang="ts" setup> +import { ref } from 'vue'; +import JSON5 from 'json5'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; import MkSwitch from '@/components/form/switch.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { Endpoints } from 'misskey-js'; -export default defineComponent({ - components: { - MkButton, MkInput, MkTextarea, MkSwitch, - }, +const body = ref('{}'); +const endpoint = ref(''); +const endpoints = ref<any[]>([]); +const sending = ref(false); +const res = ref(''); +const withCredential = ref(true); - data() { - return { - [symbols.PAGE_INFO]: { - title: 'API console', - icon: 'fas fa-terminal' - }, +os.api('endpoints').then(endpointResponse => { + endpoints.value = endpointResponse; +}); - endpoint: '', - body: '{}', - res: null, - sending: false, - endpoints: [], - withCredential: true, +function send() { + sending.value = true; + const requestBody = JSON5.parse(body.value); + os.api(endpoint.value as keyof Endpoints, requestBody, requestBody.i || (withCredential.value ? undefined : null)).then(resp => { + sending.value = false; + res.value = JSON5.stringify(resp, null, 2); + }, err => { + sending.value = false; + res.value = JSON5.stringify(err, null, 2); + }); +} - }; - }, +function onEndpointChange() { + os.api('endpoint', { endpoint: endpoint.value }, withCredential.value ? undefined : null).then(resp => { + const endpointBody = {}; + for (const p of resp.params) { + endpointBody[p.name] = + p.type === 'String' ? '' : + p.type === 'Number' ? 0 : + p.type === 'Boolean' ? false : + p.type === 'Array' ? [] : + p.type === 'Object' ? {} : + null; + } + body.value = JSON5.stringify(endpointBody, null, 2); + }); +} - created() { - os.api('endpoints').then(endpoints => { - this.endpoints = endpoints; - }); +defineExpose({ + [symbols.PAGE_INFO]: { + title: 'API console', + icon: 'fas fa-terminal' }, - - methods: { - send() { - this.sending = true; - const body = JSON5.parse(this.body); - os.api(this.endpoint, body, body.i || (this.withCredential ? undefined : null)).then(res => { - this.sending = false; - this.res = JSON5.stringify(res, null, 2); - }, err => { - this.sending = false; - this.res = JSON5.stringify(err, null, 2); - }); - }, - - onEndpointChange() { - os.api('endpoint', { endpoint: this.endpoint }, this.withCredential ? undefined : null).then(endpoint => { - const body = {}; - for (const p of endpoint.params) { - body[p.name] = - p.type === 'String' ? '' : - p.type === 'Number' ? 0 : - p.type === 'Boolean' ? false : - p.type === 'Array' ? [] : - p.type === 'Object' ? {} : - null; - } - this.body = JSON5.stringify(body, null, 2); - }); - } - } }); </script> diff --git a/packages/client/src/pages/auth.form.vue b/packages/client/src/pages/auth.form.vue index bc719aebd3..5feff0149a 100644 --- a/packages/client/src/pages/auth.form.vue +++ b/packages/client/src/pages/auth.form.vue @@ -32,7 +32,7 @@ export default defineComponent({ computed: { name(): string { const el = document.createElement('div'); - el.textContent = this.app.name + el.textContent = this.app.name; return el.innerHTML; }, app(): any { diff --git a/packages/client/src/pages/channel-editor.vue b/packages/client/src/pages/channel-editor.vue index 3818c7481a..ea3a5dab76 100644 --- a/packages/client/src/pages/channel-editor.vue +++ b/packages/client/src/pages/channel-editor.vue @@ -111,8 +111,8 @@ export default defineComponent({ } }, - setBannerImage(e) { - selectFile(e.currentTarget ?? e.target, null).then(file => { + setBannerImage(evt) { + selectFile(evt.currentTarget ?? evt.target, null).then(file => { this.bannerId = file.id; }); }, diff --git a/packages/client/src/pages/emojis.category.vue b/packages/client/src/pages/emojis.category.vue index 9a317418be..c47870f4d4 100644 --- a/packages/client/src/pages/emojis.category.vue +++ b/packages/client/src/pages/emojis.category.vue @@ -58,7 +58,7 @@ export default defineComponent({ tags: emojiTags, selectedTags: new Set(), searchEmojis: null, - } + }; }, watch: { @@ -79,9 +79,9 @@ export default defineComponent({ } if (this.selectedTags.size === 0) { - this.searchEmojis = this.customEmojis.filter(e => e.name.includes(this.q) || e.aliases.includes(this.q)); + this.searchEmojis = this.customEmojis.filter(emoji => emoji.name.includes(this.q) || emoji.aliases.includes(this.q)); } else { - this.searchEmojis = this.customEmojis.filter(e => (e.name.includes(this.q) || e.aliases.includes(this.q)) && [...this.selectedTags].every(t => e.aliases.includes(t))); + this.searchEmojis = this.customEmojis.filter(emoji => (emoji.name.includes(this.q) || emoji.aliases.includes(this.q)) && [...this.selectedTags].every(t => emoji.aliases.includes(t))); } }, diff --git a/packages/client/src/pages/emojis.vue b/packages/client/src/pages/emojis.vue index 886b5f7119..f44b29df04 100644 --- a/packages/client/src/pages/emojis.vue +++ b/packages/client/src/pages/emojis.vue @@ -25,10 +25,10 @@ function menu(ev) { type: 'info', text: i18n.ts.exportRequested, }); - }).catch((e) => { + }).catch((err) => { os.alert({ type: 'error', - text: e.message, + text: err.message, }); }); } diff --git a/packages/client/src/pages/federation.vue b/packages/client/src/pages/federation.vue index 5add2b5324..447918905b 100644 --- a/packages/client/src/pages/federation.vue +++ b/packages/client/src/pages/federation.vue @@ -127,7 +127,7 @@ function getStatus(instance) { if (instance.isSuspended) return 'suspended'; if (instance.isNotResponding) return 'error'; return 'alive'; -}; +} defineExpose({ [symbols.PAGE_INFO]: { diff --git a/packages/client/src/pages/follow.vue b/packages/client/src/pages/follow.vue index d8a6824dca..e69e0481e0 100644 --- a/packages/client/src/pages/follow.vue +++ b/packages/client/src/pages/follow.vue @@ -20,7 +20,7 @@ export default defineComponent({ uri: acct }); promise.then(res => { - if (res.type == 'User') { + if (res.type === 'User') { this.follow(res.object); } else if (res.type === 'Note') { this.$router.push(`/notes/${res.object.id}`); diff --git a/packages/client/src/pages/gallery/edit.vue b/packages/client/src/pages/gallery/edit.vue index 25ee513186..bc87160c44 100644 --- a/packages/client/src/pages/gallery/edit.vue +++ b/packages/client/src/pages/gallery/edit.vue @@ -71,7 +71,7 @@ export default defineComponent({ description: null, title: null, isSensitive: false, - } + }; }, watch: { @@ -91,8 +91,8 @@ export default defineComponent({ }, methods: { - selectFile(e) { - selectFiles(e.currentTarget ?? e.target, null).then(files => { + selectFile(evt) { + selectFiles(evt.currentTarget ?? evt.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 1755c23286..1ca3443e56 100644 --- a/packages/client/src/pages/gallery/post.vue +++ b/packages/client/src/pages/gallery/post.vue @@ -119,8 +119,8 @@ export default defineComponent({ postId: this.postId }).then(post => { this.post = post; - }).catch(e => { - this.error = e; + }).catch(err => { + this.error = err; }); }, diff --git a/packages/client/src/pages/messaging/index.vue b/packages/client/src/pages/messaging/index.vue index 88a1e07afc..7c1d3e3cbe 100644 --- a/packages/client/src/pages/messaging/index.vue +++ b/packages/client/src/pages/messaging/index.vue @@ -90,14 +90,14 @@ export default defineComponent({ getAcct: Acct.toString, isMe(message) { - return message.userId == this.$i.id; + return message.userId === this.$i.id; }, onMessage(message) { if (message.recipientId) { this.messages = this.messages.filter(m => !( - (m.recipientId == message.recipientId && m.userId == message.userId) || - (m.recipientId == message.userId && m.userId == message.recipientId))); + (m.recipientId === message.recipientId && m.userId === message.userId) || + (m.recipientId === message.userId && m.userId === message.recipientId))); this.messages.unshift(message); } else if (message.groupId) { @@ -108,7 +108,7 @@ export default defineComponent({ onRead(ids) { for (const id of ids) { - const found = this.messages.find(m => m.id == id); + const found = this.messages.find(m => m.id === id); if (found) { if (found.recipientId) { found.isRead = true; @@ -123,11 +123,11 @@ export default defineComponent({ os.popupMenu([{ text: this.$ts.messagingWithUser, icon: 'fas fa-user', - action: () => { this.startUser() } + action: () => { this.startUser(); } }, { text: this.$ts.messagingWithGroup, icon: 'fas fa-users', - action: () => { this.startGroup() } + action: () => { this.startGroup(); } }], ev.currentTarget ?? ev.target); }, diff --git a/packages/client/src/pages/messaging/messaging-room.form.vue b/packages/client/src/pages/messaging/messaging-room.form.vue index 3863c8f82b..8e779c4f39 100644 --- a/packages/client/src/pages/messaging/messaging-room.form.vue +++ b/packages/client/src/pages/messaging/messaging-room.form.vue @@ -31,6 +31,7 @@ import * as os from '@/os'; import { stream } from '@/stream'; import { Autocomplete } from '@/scripts/autocomplete'; import { throttle } from 'throttle-debounce'; +import { uploadFile } from '@/scripts/upload'; export default defineComponent({ props: { @@ -58,7 +59,7 @@ export default defineComponent({ return this.user ? 'user:' + this.user.id : 'group:' + this.group.id; }, canSend(): boolean { - return (this.text != null && this.text != '') || this.file != null; + return (this.text != null && this.text !== '') || this.file != null; }, room(): any { return this.$parent; @@ -87,12 +88,11 @@ export default defineComponent({ } }, methods: { - async onPaste(e: ClipboardEvent) { - const data = e.clipboardData; - const items = data.items; + async onPaste(evt: ClipboardEvent) { + const items = evt.clipboardData.items; - if (items.length == 1) { - if (items[0].kind == 'file') { + if (items.length === 1) { + if (items[0].kind === 'file') { const file = items[0].getAsFile(); const lio = file.name.lastIndexOf('.'); const ext = lio >= 0 ? file.name.slice(lio) : ''; @@ -100,7 +100,7 @@ export default defineComponent({ if (formatted) this.upload(file, formatted); } } else { - if (items[0].kind == 'file') { + if (items[0].kind === 'file') { os.alert({ type: 'error', text: this.$ts.onlyOneFileCanBeAttached @@ -109,23 +109,23 @@ export default defineComponent({ } }, - onDragover(e) { - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + onDragover(evt) { + const isFile = evt.dataTransfer.items[0].kind === 'file'; + const isDriveFile = evt.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; if (isFile || isDriveFile) { - e.preventDefault(); - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + evt.preventDefault(); + evt.dataTransfer.dropEffect = evt.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } }, - onDrop(e): void { + onDrop(evt): void { // ファイルだったら - if (e.dataTransfer.files.length == 1) { - e.preventDefault(); - this.upload(e.dataTransfer.files[0]); + if (evt.dataTransfer.files.length === 1) { + evt.preventDefault(); + this.upload(evt.dataTransfer.files[0]); return; - } else if (e.dataTransfer.files.length > 1) { - e.preventDefault(); + } else if (evt.dataTransfer.files.length > 1) { + evt.preventDefault(); os.alert({ type: 'error', text: this.$ts.onlyOneFileCanBeAttached @@ -134,17 +134,17 @@ export default defineComponent({ } //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + const driveFile = evt.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile !== '') { this.file = JSON.parse(driveFile); - e.preventDefault(); + evt.preventDefault(); } //#endregion }, - onKeydown(e) { + onKeydown(evt) { this.typing(); - if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) { + if ((evt.which === 10 || evt.which === 13) && (evt.ctrlKey || evt.metaKey) && this.canSend) { this.send(); } }, @@ -153,8 +153,8 @@ export default defineComponent({ this.typing(); }, - chooseFile(e) { - selectFile(e.currentTarget ?? e.target, this.$ts.selectFile).then(file => { + chooseFile(evt) { + selectFile(evt.currentTarget ?? evt.target, this.$ts.selectFile).then(file => { this.file = file; }); }, @@ -164,7 +164,7 @@ export default defineComponent({ }, upload(file: File, name?: string) { - os.upload(file, this.$store.state.uploadFolder, name).then(res => { + uploadFile(file, this.$store.state.uploadFolder, name).then(res => { this.file = res; }); }, @@ -192,25 +192,25 @@ export default defineComponent({ }, saveDraft() { - const data = JSON.parse(localStorage.getItem('message_drafts') || '{}'); + const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}'); - data[this.draftKey] = { + drafts[this.draftKey] = { updatedAt: new Date(), data: { text: this.text, file: this.file } - } + }; - localStorage.setItem('message_drafts', JSON.stringify(data)); + localStorage.setItem('message_drafts', JSON.stringify(drafts)); }, deleteDraft() { - const data = JSON.parse(localStorage.getItem('message_drafts') || '{}'); + const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}'); - delete data[this.draftKey]; + delete drafts[this.draftKey]; - localStorage.setItem('message_drafts', JSON.stringify(data)); + localStorage.setItem('message_drafts', JSON.stringify(drafts)); }, async insertEmoji(ev) { diff --git a/packages/client/src/pages/messaging/messaging-room.vue b/packages/client/src/pages/messaging/messaging-room.vue index 2ecc68eb54..fd1962218a 100644 --- a/packages/client/src/pages/messaging/messaging-room.vue +++ b/packages/client/src/pages/messaging/messaging-room.vue @@ -166,23 +166,23 @@ const Component = defineComponent({ }); }, - onDragover(e) { - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + onDragover(evt) { + const isFile = evt.dataTransfer.items[0].kind === 'file'; + const isDriveFile = evt.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; if (isFile || isDriveFile) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + evt.dataTransfer.dropEffect = evt.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } else { - e.dataTransfer.dropEffect = 'none'; + evt.dataTransfer.dropEffect = 'none'; } }, - onDrop(e): void { + onDrop(evt): void { // ファイルだったら - if (e.dataTransfer.files.length == 1) { - this.form.upload(e.dataTransfer.files[0]); + if (evt.dataTransfer.files.length === 1) { + this.form.upload(evt.dataTransfer.files[0]); return; - } else if (e.dataTransfer.files.length > 1) { + } else if (evt.dataTransfer.files.length > 1) { os.alert({ type: 'error', text: this.$ts.onlyOneFileCanBeAttached @@ -191,8 +191,8 @@ const Component = defineComponent({ } //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + const driveFile = evt.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile !== '') { const file = JSON.parse(driveFile); this.form.file = file; } @@ -209,7 +209,7 @@ const Component = defineComponent({ limit: max + 1, untilId: this.existMoreMessages ? this.messages[0].id : undefined }).then(messages => { - if (messages.length == max + 1) { + if (messages.length === max + 1) { this.existMoreMessages = true; messages.pop(); } else { @@ -235,7 +235,7 @@ const Component = defineComponent({ const _isBottom = isBottom(this.$el, 64); this.messages.push(message); - if (message.userId != this.$i.id && !document.hidden) { + if (message.userId !== this.$i.id && !document.hidden) { this.connection.send('read', { id: message.id }); @@ -246,7 +246,7 @@ const Component = defineComponent({ this.$nextTick(() => { this.scrollToBottom(); }); - } else if (message.userId != this.$i.id) { + } else if (message.userId !== this.$i.id) { // Notify this.notifyNewMessage(); } @@ -256,7 +256,7 @@ const Component = defineComponent({ if (this.user) { if (!Array.isArray(x)) x = [x]; for (const id of x) { - if (this.messages.some(x => x.id == id)) { + if (this.messages.some(x => x.id === id)) { const exist = this.messages.map(x => x.id).indexOf(id); this.messages[exist] = { ...this.messages[exist], @@ -266,7 +266,7 @@ const Component = defineComponent({ } } else if (this.group) { for (const id of x.ids) { - if (this.messages.some(x => x.id == id)) { + if (this.messages.some(x => x.id === id)) { const exist = this.messages.map(x => x.id).indexOf(id); this.messages[exist] = { ...this.messages[exist], diff --git a/packages/client/src/pages/mfm-cheat-sheet.vue b/packages/client/src/pages/mfm-cheat-sheet.vue index 83ae5741c3..2c10494ede 100644 --- a/packages/client/src/pages/mfm-cheat-sheet.vue +++ b/packages/client/src/pages/mfm-cheat-sheet.vue @@ -325,23 +325,23 @@ export default defineComponent({ preview_inlineMath: '\\(x= \\frac{-b\' \\pm \\sqrt{(b\')^2-ac}}{a}\\)', preview_quote: `> ${this.$ts._mfm.dummy}`, preview_search: `${this.$ts._mfm.dummy} 検索`, - preview_jelly: `$[jelly 🍮]`, - preview_tada: `$[tada 🍮]`, - preview_jump: `$[jump 🍮]`, - preview_bounce: `$[bounce 🍮]`, - preview_shake: `$[shake 🍮]`, - preview_twitch: `$[twitch 🍮]`, - preview_spin: `$[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]\n$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]\n$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]`, + preview_jelly: `$[jelly 🍮] $[jelly.speed=5s 🍮]`, + preview_tada: `$[tada 🍮] $[tada.speed=5s 🍮]`, + preview_jump: `$[jump 🍮] $[jump.speed=5s 🍮]`, + preview_bounce: `$[bounce 🍮] $[bounce.speed=5s 🍮]`, + preview_shake: `$[shake 🍮] $[shake.speed=5s 🍮]`, + preview_twitch: `$[twitch 🍮] $[twitch.speed=5s 🍮]`, + preview_spin: `$[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]\n$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]\n$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]\n\n$[spin.speed=5s 🍮]`, preview_flip: `$[flip ${this.$ts._mfm.dummy}]\n$[flip.v ${this.$ts._mfm.dummy}]\n$[flip.h,v ${this.$ts._mfm.dummy}]`, preview_font: `$[font.serif ${this.$ts._mfm.dummy}]\n$[font.monospace ${this.$ts._mfm.dummy}]\n$[font.cursive ${this.$ts._mfm.dummy}]\n$[font.fantasy ${this.$ts._mfm.dummy}]`, preview_x2: `$[x2 🍮]`, preview_x3: `$[x3 🍮]`, preview_x4: `$[x4 🍮]`, preview_blur: `$[blur ${this.$ts._mfm.dummy}]`, - preview_rainbow: `$[rainbow 🍮]`, + preview_rainbow: `$[rainbow 🍮] $[rainbow.speed=5s 🍮]`, preview_sparkle: `$[sparkle 🍮]`, preview_rotate: `$[rotate 🍮]`, - } + }; }, }); </script> diff --git a/packages/client/src/pages/miauth.vue b/packages/client/src/pages/miauth.vue index 6e85b784ff..4032d7723e 100644 --- a/packages/client/src/pages/miauth.vue +++ b/packages/client/src/pages/miauth.vue @@ -42,6 +42,7 @@ import MkSignin from '@/components/signin.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import { login } from '@/account'; +import { appendQuery, query } from '@/scripts/url'; export default defineComponent({ components: { @@ -82,7 +83,9 @@ export default defineComponent({ this.state = 'accepted'; if (this.callback) { - location.href = `${this.callback}?session=${this.session}`; + location.href = appendQuery(this.callback, query({ + session: this.session + })); } }, deny() { diff --git a/packages/client/src/pages/my-antennas/edit.vue b/packages/client/src/pages/my-antennas/edit.vue index 04928c81a3..38e56ce35d 100644 --- a/packages/client/src/pages/my-antennas/edit.vue +++ b/packages/client/src/pages/my-antennas/edit.vue @@ -4,49 +4,34 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; +<script lang="ts" setup> +import { watch } from 'vue'; import XAntenna from './editor.vue'; import * as symbols from '@/symbols'; import * as os from '@/os'; +import { MisskeyNavigator } from '@/scripts/navigate'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - XAntenna, - }, +const nav = new MisskeyNavigator(); - props: { - antennaId: { - type: String, - required: true, - } - }, +let antenna: any = $ref(null); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.manageAntennas, - icon: 'fas fa-satellite', - }, - antenna: null, - }; - }, +const props = defineProps<{ + antennaId: string +}>(); - watch: { - antennaId: { - async handler() { - this.antenna = await os.api('antennas/show', { antennaId: this.antennaId }); - }, - immediate: true, - } - }, +function onAntennaUpdated() { + nav.push('/my/antennas'); +} - methods: { - onAntennaUpdated() { - this.$router.push('/my/antennas'); - }, +os.api('antennas/show', { antennaId: props.antennaId }).then((antennaResponse) => { + antenna = antennaResponse; +}); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.manageAntennas, + icon: 'fas fa-satellite', } }); </script> diff --git a/packages/client/src/pages/my-antennas/editor.vue b/packages/client/src/pages/my-antennas/editor.vue index 8c1d6148fe..6f3c4afbfe 100644 --- a/packages/client/src/pages/my-antennas/editor.vue +++ b/packages/client/src/pages/my-antennas/editor.vue @@ -44,135 +44,100 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { watch } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; import MkSelect from '@/components/form/select.vue'; import MkSwitch from '@/components/form/switch.vue'; -import * as Acct from 'misskey-js/built/acct'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, MkInput, MkTextarea, MkSelect, MkSwitch - }, +const props = defineProps<{ + antenna: any +}>(); - props: { - antenna: { - type: Object, - required: true - } - }, +const emit = defineEmits<{ + (ev: 'created'): void, + (ev: 'updated'): void, + (ev: 'deleted'): void, +}>(); - data() { - return { - name: '', - src: '', - userListId: null, - userGroupId: null, - users: '', - keywords: '', - excludeKeywords: '', - caseSensitive: false, - withReplies: false, - withFile: false, - notify: false, - userLists: null, - userGroups: null, - }; - }, +let name: string = $ref(props.antenna.name); +let src: string = $ref(props.antenna.src); +let userListId: any = $ref(props.antenna.userListId); +let userGroupId: any = $ref(props.antenna.userGroupId); +let users: string = $ref(props.antenna.users.join('\n')); +let keywords: string = $ref(props.antenna.keywords.map(x => x.join(' ')).join('\n')); +let excludeKeywords: string = $ref(props.antenna.excludeKeywords.map(x => x.join(' ')).join('\n')); +let caseSensitive: boolean = $ref(props.antenna.caseSensitive); +let withReplies: boolean = $ref(props.antenna.withReplies); +let withFile: boolean = $ref(props.antenna.withFile); +let notify: boolean = $ref(props.antenna.notify); +let userLists: any = $ref(null); +let userGroups: any = $ref(null); - watch: { - async src() { - if (this.src === 'list' && this.userLists === null) { - this.userLists = await os.api('users/lists/list'); - } +watch(() => src, async () => { + if (src === 'list' && userLists === null) { + userLists = await os.api('users/lists/list'); + } - if (this.src === 'group' && this.userGroups === null) { - const groups1 = await os.api('users/groups/owned'); - const groups2 = await os.api('users/groups/joined'); + if (src === 'group' && userGroups === null) { + const groups1 = await os.api('users/groups/owned'); + const groups2 = await os.api('users/groups/joined'); - this.userGroups = [...groups1, ...groups2]; - } - } - }, + userGroups = [...groups1, ...groups2]; + } +}); - created() { - this.name = this.antenna.name; - this.src = this.antenna.src; - this.userListId = this.antenna.userListId; - this.userGroupId = this.antenna.userGroupId; - this.users = this.antenna.users.join('\n'); - this.keywords = this.antenna.keywords.map(x => x.join(' ')).join('\n'); - this.excludeKeywords = this.antenna.excludeKeywords.map(x => x.join(' ')).join('\n'); - this.caseSensitive = this.antenna.caseSensitive; - this.withReplies = this.antenna.withReplies; - this.withFile = this.antenna.withFile; - this.notify = this.antenna.notify; - }, +async function saveAntenna() { + const antennaData = { + name, + src, + userListId, + userGroupId, + withReplies, + withFile, + notify, + caseSensitive, + users: users.trim().split('\n').map(x => x.trim()), + keywords: keywords.trim().split('\n').map(x => x.trim().split(' ')), + excludeKeywords: excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')), + }; - methods: { - async saveAntenna() { - if (this.antenna.id == null) { - await os.apiWithDialog('antennas/create', { - name: this.name, - src: this.src, - userListId: this.userListId, - userGroupId: this.userGroupId, - withReplies: this.withReplies, - withFile: this.withFile, - notify: this.notify, - caseSensitive: this.caseSensitive, - users: this.users.trim().split('\n').map(x => x.trim()), - keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')), - excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')), - }); - this.$emit('created'); - } else { - await os.apiWithDialog('antennas/update', { - antennaId: this.antenna.id, - name: this.name, - src: this.src, - userListId: this.userListId, - userGroupId: this.userGroupId, - withReplies: this.withReplies, - withFile: this.withFile, - notify: this.notify, - caseSensitive: this.caseSensitive, - users: this.users.trim().split('\n').map(x => x.trim()), - keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')), - excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')), - }); - this.$emit('updated'); - } - }, + if (props.antenna.id == null) { + await os.apiWithDialog('antennas/create', antennaData); + emit('created'); + } else { + antennaData['antennaId'] = props.antenna.id; + await os.apiWithDialog('antennas/update', antennaData); + emit('updated'); + } +} - async deleteAntenna() { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: this.antenna.name }), - }); - if (canceled) return; +async function deleteAntenna() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: props.antenna.name }), + }); + if (canceled) return; - await os.api('antennas/delete', { - antennaId: this.antenna.id, - }); + await os.api('antennas/delete', { + antennaId: props.antenna.id, + }); - os.success(); - this.$emit('deleted'); - }, + os.success(); + emit('deleted'); +} - addUser() { - os.selectUser().then(user => { - this.users = this.users.trim(); - this.users += '\n@' + Acct.toString(user); - this.users = this.users.trim(); - }); - } - } -}); +function addUser() { + os.selectUser().then(user => { + users = users.trim(); + users += '\n@' + Acct.toString(user as any); + users = users.trim(); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue index 7138d269a9..a568f64c52 100644 --- a/packages/client/src/pages/my-antennas/index.vue +++ b/packages/client/src/pages/my-antennas/index.vue @@ -1,7 +1,7 @@ <template> <MkSpacer :content-max="700"> <div class="ieepwinx"> - <MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> + <MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="fas fa-plus"></i> {{ i18n.ts.add }}</MkButton> <div class=""> <MkPagination v-slot="{items}" ref="list" :pagination="pagination"> @@ -14,35 +14,24 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkButton from '@/components/ui/button.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkPagination, - MkButton, - }, +const pagination = { + endpoint: 'antennas/list' as const, + limit: 10, +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.manageAntennas, - icon: 'fas fa-satellite', - bg: 'var(--bg)', - action: { - icon: 'fas fa-plus', - handler: this.create - } - }, - pagination: { - endpoint: 'antennas/list' as const, - limit: 10, - }, - }; - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.manageAntennas, + icon: 'fas fa-satellite', + bg: 'var(--bg)' + } }); </script> diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue index 29261ec484..f0a18ecc36 100644 --- a/packages/client/src/pages/note.vue +++ b/packages/client/src/pages/note.vue @@ -108,6 +108,10 @@ export default defineComponent({ }, methods: { fetch() { + this.hasPrev = false; + this.hasNext = false; + this.showPrev = false; + this.showNext = false; this.note = null; os.api('notes/show', { noteId: this.noteId @@ -132,8 +136,8 @@ export default defineComponent({ this.hasPrev = prev.length !== 0; this.hasNext = next.length !== 0; }); - }).catch(e => { - this.error = e; + }).catch(err => { + this.error = err; }); } } diff --git a/packages/client/src/pages/page-editor/page-editor.vue b/packages/client/src/pages/page-editor/page-editor.vue index f302ac4f90..9566592618 100644 --- a/packages/client/src/pages/page-editor/page-editor.vue +++ b/packages/client/src/pages/page-editor/page-editor.vue @@ -114,7 +114,7 @@ export default defineComponent({ readonly: this.readonly, getScriptBlockList: this.getScriptBlockList, getPageBlockList: this.getPageBlockList - } + }; }, props: { diff --git a/packages/client/src/pages/page.vue b/packages/client/src/pages/page.vue index b2c039a269..5bca971438 100644 --- a/packages/client/src/pages/page.vue +++ b/packages/client/src/pages/page.vue @@ -139,8 +139,8 @@ export default defineComponent({ username: this.username, }).then(page => { this.page = page; - }).catch(e => { - this.error = e; + }).catch(err => { + this.error = err; }); }, diff --git a/packages/client/src/pages/reset-password.vue b/packages/client/src/pages/reset-password.vue index 7d008ae75c..b3e2ca8d6f 100644 --- a/packages/client/src/pages/reset-password.vue +++ b/packages/client/src/pages/reset-password.vue @@ -12,7 +12,7 @@ </template> <script lang="ts" setup> -import { onMounted } from 'vue'; +import { defineAsyncComponent, onMounted } from 'vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; @@ -36,7 +36,7 @@ async function save() { onMounted(() => { if (props.token == null) { - os.popup(import('@/components/forgot-password.vue'), {}, {}, 'closed'); + os.popup(defineAsyncComponent(() => import('@/components/forgot-password.vue')), {}, {}, 'closed'); router.push('/'); } }); diff --git a/packages/client/src/pages/scratchpad.vue b/packages/client/src/pages/scratchpad.vue index f871dc48e8..34a41b81a5 100644 --- a/packages/client/src/pages/scratchpad.vue +++ b/packages/client/src/pages/scratchpad.vue @@ -6,20 +6,20 @@ </div> <MkContainer :foldable="true" class="_gap"> - <template #header>{{ $ts.output }}</template> + <template #header>{{ i18n.ts.output }}</template> <div class="bepmlvbi"> <div v-for="log in logs" :key="log.id" class="log" :class="{ print: log.print }">{{ log.text }}</div> </div> </MkContainer> <div class="_gap"> - {{ $ts.scratchpadDescription }} + {{ i18n.ts.scratchpadDescription }} </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref, watch } from 'vue'; import 'prismjs'; import { highlight, languages } from 'prismjs/components/prism-core'; import 'prismjs/components/prism-clike'; @@ -27,103 +27,90 @@ import 'prismjs/components/prism-javascript'; import 'prismjs/themes/prism-okaidia.css'; import { PrismEditor } from 'vue-prism-editor'; import 'vue-prism-editor/dist/prismeditor.min.css'; -import { AiScript, parse, utils, values } from '@syuilo/aiscript'; +import { AiScript, parse, utils } from '@syuilo/aiscript'; import MkContainer from '@/components/ui/container.vue'; import MkButton from '@/components/ui/button.vue'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkContainer, - MkButton, - PrismEditor, - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.scratchpad, - icon: 'fas fa-terminal', - }, - code: '', - logs: [], - } - }, - - watch: { - code() { - localStorage.setItem('scratchpad', this.code); - } - }, +const code = ref(''); +const logs = ref<any[]>([]); - created() { - const saved = localStorage.getItem('scratchpad'); - if (saved) { - this.code = saved; - } - }, +const saved = localStorage.getItem('scratchpad'); +if (saved) { + code.value = saved; +} - methods: { - async run() { - this.logs = []; - const aiscript = new AiScript(createAiScriptEnv({ - storageKey: 'scratchpad', - token: this.$i?.token, - }), { - in: (q) => { - return new Promise(ok => { - os.inputText({ - title: q, - }).then(({ canceled, result: a }) => { - ok(a); - }); - }); - }, - out: (value) => { - this.logs.push({ - id: Math.random(), - text: value.type === 'str' ? value.value : utils.valToString(value), - print: true - }); - }, - log: (type, params) => { - switch (type) { - case 'end': this.logs.push({ - id: Math.random(), - text: utils.valToString(params.val, true), - print: false - }); break; - default: break; - } - } - }); +watch(code, () => { + localStorage.setItem('scratchpad', code.value); +}); - let ast; - try { - ast = parse(this.code); - } catch (e) { - os.alert({ - type: 'error', - text: 'Syntax error :(' - }); - return; - } - try { - await aiscript.exec(ast); - } catch (e) { - os.alert({ - type: 'error', - text: e +async function run() { + logs.value = []; + const aiscript = new AiScript(createAiScriptEnv({ + storageKey: 'scratchpad', + token: $i?.token, + }), { + in: (q) => { + return new Promise(ok => { + os.inputText({ + title: q, + }).then(({ canceled, result: a }) => { + ok(a); }); - } + }); }, - - highlighter(code) { - return highlight(code, languages.js, 'javascript'); + out: (value) => { + logs.value.push({ + id: Math.random(), + text: value.type === 'str' ? value.value : utils.valToString(value), + print: true + }); }, + log: (type, params) => { + switch (type) { + case 'end': logs.value.push({ + id: Math.random(), + text: utils.valToString(params.val, true), + print: false + }); break; + default: break; + } + } + }); + + let ast; + try { + ast = parse(code.value); + } catch (error) { + os.alert({ + type: 'error', + text: 'Syntax error :(' + }); + return; } + try { + await aiscript.exec(ast); + } catch (error: any) { + os.alert({ + type: 'error', + text: error.message + }); + } +} + +function highlighter(code) { + return highlight(code, languages.js, 'javascript'); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.scratchpad, + icon: 'fas fa-terminal', + }, }); </script> diff --git a/packages/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue index 10599d99ff..fb3a7a17f3 100644 --- a/packages/client/src/pages/settings/2fa.vue +++ b/packages/client/src/pages/settings/2fa.vue @@ -1,49 +1,49 @@ <template> <div> - <MkButton v-if="!data && !$i.twoFactorEnabled" @click="register">{{ $ts._2fa.registerDevice }}</MkButton> + <MkButton v-if="!twoFactorData && !$i.twoFactorEnabled" @click="register">{{ i18n.ts._2fa.registerDevice }}</MkButton> <template v-if="$i.twoFactorEnabled"> - <p>{{ $ts._2fa.alreadyRegistered }}</p> - <MkButton @click="unregister">{{ $ts.unregister }}</MkButton> + <p>{{ i18n.ts._2fa.alreadyRegistered }}</p> + <MkButton @click="unregister">{{ i18n.ts.unregister }}</MkButton> <template v-if="supportsCredentials"> <hr class="totp-method-sep"> - <h2 class="heading">{{ $ts.securityKey }}</h2> - <p>{{ $ts._2fa.securityKeyInfo }}</p> + <h2 class="heading">{{ i18n.ts.securityKey }}</h2> + <p>{{ i18n.ts._2fa.securityKeyInfo }}</p> <div class="key-list"> <div v-for="key in $i.securityKeysList" class="key"> <h3>{{ key.name }}</h3> - <div class="last-used">{{ $ts.lastUsed }}<MkTime :time="key.lastUsed"/></div> - <MkButton @click="unregisterKey(key)">{{ $ts.unregister }}</MkButton> + <div class="last-used">{{ i18n.ts.lastUsed }}<MkTime :time="key.lastUsed"/></div> + <MkButton @click="unregisterKey(key)">{{ i18n.ts.unregister }}</MkButton> </div> </div> - <MkSwitch v-if="$i.securityKeysList.length > 0" v-model="usePasswordLessLogin" @update:modelValue="updatePasswordLessLogin">{{ $ts.passwordLessLogin }}</MkSwitch> + <MkSwitch v-if="$i.securityKeysList.length > 0" v-model="usePasswordLessLogin" @update:modelValue="updatePasswordLessLogin">{{ i18n.ts.passwordLessLogin }}</MkSwitch> - <MkInfo v-if="registration && registration.error" warn>{{ $ts.error }} {{ registration.error }}</MkInfo> - <MkButton v-if="!registration || registration.error" @click="addSecurityKey">{{ $ts._2fa.registerKey }}</MkButton> + <MkInfo v-if="registration && registration.error" warn>{{ i18n.ts.error }} {{ registration.error }}</MkInfo> + <MkButton v-if="!registration || registration.error" @click="addSecurityKey">{{ i18n.ts._2fa.registerKey }}</MkButton> <ol v-if="registration && !registration.error"> <li v-if="registration.stage >= 0"> - {{ $ts.tapSecurityKey }} + {{ i18n.ts.tapSecurityKey }} <i v-if="registration.saving && registration.stage == 0" class="fas fa-spinner fa-pulse fa-fw"></i> </li> <li v-if="registration.stage >= 1"> <MkForm :disabled="registration.stage != 1 || registration.saving"> <MkInput v-model="keyName" :max="30"> - <template #label>{{ $ts.securityKeyName }}</template> + <template #label>{{ i18n.ts.securityKeyName }}</template> </MkInput> - <MkButton :disabled="keyName.length == 0" @click="registerKey">{{ $ts.registerSecurityKey }}</MkButton> + <MkButton :disabled="keyName.length == 0" @click="registerKey">{{ i18n.ts.registerSecurityKey }}</MkButton> <i v-if="registration.saving && registration.stage == 1" class="fas fa-spinner fa-pulse fa-fw"></i> </MkForm> </li> </ol> </template> </template> - <div v-if="data && !$i.twoFactorEnabled"> + <div v-if="twoFactorData && !$i.twoFactorEnabled"> <ol style="margin: 0; padding: 0 0 0 1em;"> <li> - <I18n :src="$ts._2fa.step1" tag="span"> + <I18n :src="i18n.ts._2fa.step1" tag="span"> <template #a> <a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a> </template> @@ -52,19 +52,20 @@ </template> </I18n> </li> - <li>{{ $ts._2fa.step2 }}<br><img :src="data.qr"></li> - <li>{{ $ts._2fa.step3 }}<br> - <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false"><template #label>{{ $ts.token }}</template></MkInput> - <MkButton primary @click="submit">{{ $ts.done }}</MkButton> + <li>{{ i18n.ts._2fa.step2 }}<br><img :src="twoFactorData.qr"><p>{{ $ts._2fa.step2Url }}<br>{{ twoFactorData.url }}</p></li> + <li> + {{ i18n.ts._2fa.step3 }}<br> + <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false"><template #label>{{ i18n.ts.token }}</template></MkInput> + <MkButton primary @click="submit">{{ i18n.ts.done }}</MkButton> </li> </ol> - <MkInfo>{{ $ts._2fa.step4 }}</MkInfo> + <MkInfo>{{ i18n.ts._2fa.step4 }}</MkInfo> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; import { hostname } from '@/config'; import { byteify, hexify, stringify } from '@/scripts/2fa'; import MkButton from '@/components/ui/button.vue'; @@ -72,155 +73,144 @@ import MkInfo from '@/components/ui/info.vue'; import MkInput from '@/components/form/input.vue'; import MkSwitch from '@/components/form/switch.vue'; import * as os from '@/os'; -import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, MkInfo, MkInput, MkSwitch - }, +const twoFactorData = ref<any>(null); +const supportsCredentials = ref(!!navigator.credentials); +const usePasswordLessLogin = ref($i!.usePasswordLessLogin); +const registration = ref<any>(null); +const keyName = ref(''); +const token = ref(null); - data() { - return { - data: null, - supportsCredentials: !!navigator.credentials, - usePasswordLessLogin: this.$i.usePasswordLessLogin, - registration: null, - keyName: '', - token: null, - }; - }, +function register() { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.api('i/2fa/register', { + password: password + }).then(data => { + twoFactorData.value = data; + }); + }); +} - methods: { - register() { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/2fa/register', { - password: password - }).then(data => { - this.data = data; - }); - }); - }, +function unregister() { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.api('i/2fa/unregister', { + password: password + }).then(() => { + usePasswordLessLogin.value = false; + updatePasswordLessLogin(); + }).then(() => { + os.success(); + $i!.twoFactorEnabled = false; + }); + }); +} - unregister() { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/2fa/unregister', { - password: password - }).then(() => { - this.usePasswordLessLogin = false; - this.updatePasswordLessLogin(); - }).then(() => { - os.success(); - this.$i.twoFactorEnabled = false; - }); - }); - }, +function submit() { + os.api('i/2fa/done', { + token: token.value + }).then(() => { + os.success(); + $i!.twoFactorEnabled = true; + }).catch(err => { + os.alert({ + type: 'error', + text: err, + }); + }); +} - submit() { - os.api('i/2fa/done', { - token: this.token - }).then(() => { - os.success(); - this.$i.twoFactorEnabled = true; - }).catch(e => { - os.alert({ - type: 'error', - text: e - }); - }); - }, +function registerKey() { + registration.value.saving = true; + os.api('i/2fa/key-done', { + password: registration.value.password, + name: keyName.value, + challengeId: registration.value.challengeId, + // we convert each 16 bits to a string to serialise + clientDataJSON: stringify(registration.value.credential.response.clientDataJSON), + attestationObject: hexify(registration.value.credential.response.attestationObject) + }).then(key => { + registration.value = null; + key.lastUsed = new Date(); + os.success(); + }); +} - registerKey() { - this.registration.saving = true; - os.api('i/2fa/key-done', { - password: this.registration.password, - name: this.keyName, - challengeId: this.registration.challengeId, - // we convert each 16 bits to a string to serialise - clientDataJSON: stringify(this.registration.credential.response.clientDataJSON), - attestationObject: hexify(this.registration.credential.response.attestationObject) - }).then(key => { - this.registration = null; - key.lastUsed = new Date(); - os.success(); - }) - }, +function unregisterKey(key) { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + return os.api('i/2fa/remove-key', { + password, + credentialId: key.id + }).then(() => { + usePasswordLessLogin.value = false; + updatePasswordLessLogin(); + }).then(() => { + os.success(); + }); + }); +} - unregisterKey(key) { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - return os.api('i/2fa/remove-key', { - password, - credentialId: key.id - }).then(() => { - this.usePasswordLessLogin = false; - this.updatePasswordLessLogin(); - }).then(() => { - os.success(); - }); +function addSecurityKey() { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.api('i/2fa/register-key', { + password + }).then(reg => { + registration.value = { + password, + challengeId: reg!.challengeId, + stage: 0, + publicKeyOptions: { + challenge: byteify(reg!.challenge, 'base64'), + rp: { + id: hostname, + name: 'Misskey' + }, + user: { + id: byteify($i!.id, 'ascii'), + name: $i!.username, + displayName: $i!.name, + }, + pubKeyCredParams: [{ alg: -7, type: 'public-key' }], + timeout: 60000, + attestation: 'direct' + }, + saving: true + }; + return navigator.credentials.create({ + publicKey: registration.value.publicKeyOptions }); - }, + }).then(credential => { + registration.value.credential = credential; + registration.value.saving = false; + registration.value.stage = 1; + }).catch(err => { + console.warn('Error while registering?', err); + registration.value.error = err.message; + registration.value.stage = -1; + }); + }); +} - addSecurityKey() { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/2fa/register-key', { - password - }).then(registration => { - this.registration = { - password, - challengeId: registration.challengeId, - stage: 0, - publicKeyOptions: { - challenge: byteify(registration.challenge, 'base64'), - rp: { - id: hostname, - name: 'Misskey' - }, - user: { - id: byteify(this.$i.id, 'ascii'), - name: this.$i.username, - displayName: this.$i.name, - }, - pubKeyCredParams: [{ alg: -7, type: 'public-key' }], - timeout: 60000, - attestation: 'direct' - }, - saving: true - }; - return navigator.credentials.create({ - publicKey: this.registration.publicKeyOptions - }); - }).then(credential => { - this.registration.credential = credential; - this.registration.saving = false; - this.registration.stage = 1; - }).catch(err => { - console.warn('Error while registering?', err); - this.registration.error = err.message; - this.registration.stage = -1; - }); - }); - }, - - updatePasswordLessLogin() { - os.api('i/2fa/password-less', { - value: !!this.usePasswordLessLogin - }); - } - } -}); +async function updatePasswordLessLogin() { + await os.api('i/2fa/password-less', { + value: !!usePasswordLessLogin.value + }); +} </script> diff --git a/packages/client/src/pages/settings/account-info.vue b/packages/client/src/pages/settings/account-info.vue index c98ad056f6..12142b4dc1 100644 --- a/packages/client/src/pages/settings/account-info.vue +++ b/packages/client/src/pages/settings/account-info.vue @@ -7,163 +7,150 @@ <FormSection> <MkKeyValue> - <template #key>{{ $ts.registeredDate }}</template> + <template #key>{{ i18n.ts.registeredDate }}</template> <template #value><MkTime :time="$i.createdAt" mode="detail"/></template> </MkKeyValue> </FormSection> <FormSection v-if="stats"> - <template #label>{{ $ts.statistics }}</template> + <template #label>{{ i18n.ts.statistics }}</template> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.notesCount }}</template> + <template #key>{{ i18n.ts.notesCount }}</template> <template #value>{{ number(stats.notesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.repliesCount }}</template> + <template #key>{{ i18n.ts.repliesCount }}</template> <template #value>{{ number(stats.repliesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.renotesCount }}</template> + <template #key>{{ i18n.ts.renotesCount }}</template> <template #value>{{ number(stats.renotesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.repliedCount }}</template> + <template #key>{{ i18n.ts.repliedCount }}</template> <template #value>{{ number(stats.repliedCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.renotedCount }}</template> + <template #key>{{ i18n.ts.renotedCount }}</template> <template #value>{{ number(stats.renotedCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.pollVotesCount }}</template> + <template #key>{{ i18n.ts.pollVotesCount }}</template> <template #value>{{ number(stats.pollVotesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.pollVotedCount }}</template> + <template #key>{{ i18n.ts.pollVotedCount }}</template> <template #value>{{ number(stats.pollVotedCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.sentReactionsCount }}</template> + <template #key>{{ i18n.ts.sentReactionsCount }}</template> <template #value>{{ number(stats.sentReactionsCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.receivedReactionsCount }}</template> + <template #key>{{ i18n.ts.receivedReactionsCount }}</template> <template #value>{{ number(stats.receivedReactionsCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.noteFavoritesCount }}</template> + <template #key>{{ i18n.ts.noteFavoritesCount }}</template> <template #value>{{ number(stats.noteFavoritesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followingCount }}</template> + <template #key>{{ i18n.ts.followingCount }}</template> <template #value>{{ number(stats.followingCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followingCount }} ({{ $ts.local }})</template> + <template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.local }})</template> <template #value>{{ number(stats.localFollowingCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followingCount }} ({{ $ts.remote }})</template> + <template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.remote }})</template> <template #value>{{ number(stats.remoteFollowingCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followersCount }}</template> + <template #key>{{ i18n.ts.followersCount }}</template> <template #value>{{ number(stats.followersCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followersCount }} ({{ $ts.local }})</template> + <template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.local }})</template> <template #value>{{ number(stats.localFollowersCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followersCount }} ({{ $ts.remote }})</template> + <template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.remote }})</template> <template #value>{{ number(stats.remoteFollowersCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.pageLikesCount }}</template> + <template #key>{{ i18n.ts.pageLikesCount }}</template> <template #value>{{ number(stats.pageLikesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.pageLikedCount }}</template> + <template #key>{{ i18n.ts.pageLikedCount }}</template> <template #value>{{ number(stats.pageLikedCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.driveFilesCount }}</template> + <template #key>{{ i18n.ts.driveFilesCount }}</template> <template #value>{{ number(stats.driveFilesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.driveUsage }}</template> + <template #key>{{ i18n.ts.driveUsage }}</template> <template #value>{{ bytes(stats.driveUsage) }}</template> </MkKeyValue> </FormSection> <FormSection> - <template #label>{{ $ts.other }}</template> + <template #label>{{ i18n.ts.other }}</template> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>emailVerified</template> - <template #value>{{ $i.emailVerified ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.emailVerified ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>twoFactorEnabled</template> - <template #value>{{ $i.twoFactorEnabled ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.twoFactorEnabled ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>securityKeys</template> - <template #value>{{ $i.securityKeys ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.securityKeys ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>usePasswordLessLogin</template> - <template #value>{{ $i.usePasswordLessLogin ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.usePasswordLessLogin ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>isModerator</template> - <template #value>{{ $i.isModerator ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.isModerator ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>isAdmin</template> - <template #value>{{ $i.isAdmin ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.isAdmin ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> </FormSection> </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, onMounted, ref } from 'vue'; import FormSection from '@/components/form/section.vue'; import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import number from '@/filters/number'; import bytes from '@/filters/bytes'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - MkKeyValue, - }, +const stats = ref<any>({}); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.accountInfo, - icon: 'fas fa-info-circle' - }, - stats: null - } - }, - - mounted() { - os.api('users/stats', { - userId: this.$i.id - }).then(stats => { - this.stats = stats; - }); - }, +onMounted(() => { + os.api('users/stats', { + userId: $i!.id + }).then(response => { + stats.value = response; + }); +}); - methods: { - number, - bytes, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.accountInfo, + icon: 'fas fa-info-circle' } }); </script> diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue index a744a031d4..5e75639c55 100644 --- a/packages/client/src/pages/settings/accounts.vue +++ b/packages/client/src/pages/settings/accounts.vue @@ -1,7 +1,7 @@ <template> <div class="_formRoot"> <FormSuspense :p="init"> - <FormButton primary @click="addAccount"><i class="fas fa-plus"></i> {{ $ts.addAccount }}</FormButton> + <FormButton primary @click="addAccount"><i class="fas fa-plus"></i> {{ i18n.ts.addAccount }}</FormButton> <div v-for="account in accounts" :key="account.id" class="_panel _button lcjjdxlm" @click="menu(account, $event)"> <div class="avatar"> @@ -20,90 +20,89 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent, defineExpose, ref } from 'vue'; import FormSuspense from '@/components/form/suspense.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; -import { getAccounts, addAccount, login } from '@/account'; +import { getAccounts, addAccount as addAccounts, login, $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSuspense, - FormButton, - }, +const storedAccounts = ref<any>(null); +const accounts = ref<any>(null); - emits: ['info'], +const init = async () => { + getAccounts().then(accounts => { + storedAccounts.value = accounts.filter(x => x.id !== $i!.id); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.accounts, - icon: 'fas fa-users', - bg: 'var(--bg)', - }, - storedAccounts: getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)), - accounts: null, - init: async () => os.api('users/show', { - userIds: (await this.storedAccounts).map(x => x.id) - }).then(accounts => { - this.accounts = accounts; - }), - }; - }, + console.log(storedAccounts.value); - methods: { - menu(account, ev) { - os.popupMenu([{ - text: this.$ts.switch, - icon: 'fas fa-exchange-alt', - action: () => this.switchAccount(account), - }, { - text: this.$ts.remove, - icon: 'fas fa-trash-alt', - danger: true, - action: () => this.removeAccount(account), - }], ev.currentTarget ?? ev.target); - }, + return os.api('users/show', { + userIds: storedAccounts.value.map(x => x.id) + }); + }).then(response => { + accounts.value = response; + console.log(accounts.value); + }); +}; - addAccount(ev) { - os.popupMenu([{ - text: this.$ts.existingAccount, - action: () => { this.addExistingAccount(); }, - }, { - text: this.$ts.createAccount, - action: () => { this.createAccount(); }, - }], ev.currentTarget ?? ev.target); - }, +function menu(account, ev) { + os.popupMenu([{ + text: i18n.ts.switch, + icon: 'fas fa-exchange-alt', + action: () => switchAccount(account), + }, { + text: i18n.ts.remove, + icon: 'fas fa-trash-alt', + danger: true, + action: () => removeAccount(account), + }], ev.currentTarget ?? ev.target); +} - addExistingAccount() { - os.popup(import('@/components/signin-dialog.vue'), {}, { - done: res => { - addAccount(res.id, res.i); - os.success(); - }, - }, 'closed'); - }, +function addAccount(ev) { + os.popupMenu([{ + text: i18n.ts.existingAccount, + action: () => { addExistingAccount(); }, + }, { + text: i18n.ts.createAccount, + action: () => { createAccount(); }, + }], ev.currentTarget ?? ev.target); +} - createAccount() { - os.popup(import('@/components/signup-dialog.vue'), {}, { - done: res => { - addAccount(res.id, res.i); - this.switchAccountWithToken(res.i); - }, - }, 'closed'); +function addExistingAccount() { + os.popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, { + done: res => { + addAccounts(res.id, res.i); + os.success(); }, + }, 'closed'); +} - async switchAccount(account: any) { - const storedAccounts = await getAccounts(); - const token = storedAccounts.find(x => x.id === account.id).token; - this.switchAccountWithToken(token); +function createAccount() { + os.popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, { + done: res => { + addAccounts(res.id, res.i); + switchAccountWithToken(res.i); }, + }, 'closed'); +} - switchAccountWithToken(token: string) { - login(token); - }, +async function switchAccount(account: any) { + const fetchedAccounts: any[] = await getAccounts(); + const token = fetchedAccounts.find(x => x.id === account.id).token; + switchAccountWithToken(token); +} + +function switchAccountWithToken(token: string) { + login(token); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.accounts, + icon: 'fas fa-users', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/api.vue b/packages/client/src/pages/settings/api.vue index 20ff2a8d96..e6375763f1 100644 --- a/packages/client/src/pages/settings/api.vue +++ b/packages/client/src/pages/settings/api.vue @@ -1,56 +1,45 @@ <template> <div class="_formRoot"> - <FormButton primary class="_formBlock" @click="generateToken">{{ $ts.generateAccessToken }}</FormButton> - <FormLink to="/settings/apps" class="_formBlock">{{ $ts.manageAccessTokens }}</FormLink> + <FormButton primary class="_formBlock" @click="generateToken">{{ i18n.ts.generateAccessToken }}</FormButton> + <FormLink to="/settings/apps" class="_formBlock">{{ i18n.ts.manageAccessTokens }}</FormLink> <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null" class="_formBlock">API console</FormLink> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent, defineExpose, ref } from 'vue'; import FormLink from '@/components/form/link.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormButton, - FormLink, - }, +const isDesktop = ref(window.innerWidth >= 1100); - emits: ['info'], +function generateToken() { + os.popup(defineAsyncComponent(() => import('@/components/token-generate-window.vue')), {}, { + done: async result => { + const { name, permissions } = result; + const { token } = await os.api('miauth/gen-token', { + session: null, + name: name, + permission: permissions, + }); - data() { - return { - [symbols.PAGE_INFO]: { - title: 'API', - icon: 'fas fa-key', - bg: 'var(--bg)', - }, - isDesktop: window.innerWidth >= 1100, - }; - }, - - methods: { - generateToken() { - os.popup(import('@/components/token-generate-window.vue'), {}, { - done: async result => { - const { name, permissions } = result; - const { token } = await os.api('miauth/gen-token', { - session: null, - name: name, - permission: permissions, - }); - - os.alert({ - type: 'success', - title: this.$ts.token, - text: token - }); - }, - }, 'closed'); + os.alert({ + type: 'success', + title: i18n.ts.token, + text: token + }); }, + }, 'closed'); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: 'API', + icon: 'fas fa-key', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/apps.vue b/packages/client/src/pages/settings/apps.vue index 9c0fa8a54d..7b0b5548d5 100644 --- a/packages/client/src/pages/settings/apps.vue +++ b/packages/client/src/pages/settings/apps.vue @@ -4,7 +4,7 @@ <template #empty> <div class="_fullinfo"> <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ $ts.nothing }}</div> + <div>{{ i18n.ts.nothing }}</div> </div> </template> <template v-slot="{items}"> @@ -14,18 +14,18 @@ <div class="name">{{ token.name }}</div> <div class="description">{{ token.description }}</div> <div class="_keyValue"> - <div>{{ $ts.installedDate }}:</div> + <div>{{ i18n.ts.installedDate }}:</div> <div><MkTime :time="token.createdAt"/></div> </div> <div class="_keyValue"> - <div>{{ $ts.lastUsedDate }}:</div> + <div>{{ i18n.ts.lastUsedDate }}:</div> <div><MkTime :time="token.lastUsedAt"/></div> </div> <div class="actions"> <button class="_button" @click="revoke(token)"><i class="fas fa-trash-alt"></i></button> </div> <details> - <summary>{{ $ts.details }}</summary> + <summary>{{ i18n.ts.details }}</summary> <ul> <li v-for="p in token.permission" :key="p">{{ $t(`_permissions.${p}`) }}</li> </ul> @@ -37,42 +37,34 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref } from 'vue'; import FormPagination from '@/components/ui/pagination.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormPagination, - }, +const list = ref<any>(null); - emits: ['info'], +const pagination = { + endpoint: 'i/apps' as const, + limit: 100, + params: { + sort: '+lastUsedAt' + } +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.installedApps, - icon: 'fas fa-plug', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'i/apps' as const, - limit: 100, - params: { - sort: '+lastUsedAt' - } - }, - }; - }, +function revoke(token) { + os.api('i/revoke-token', { tokenId: token.id }).then(() => { + list.value.reload(); + }); +} - methods: { - revoke(token) { - os.api('i/revoke-token', { tokenId: token.id }).then(() => { - this.$refs.list.reload(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.installedApps, + icon: 'fas fa-plug', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/custom-css.vue b/packages/client/src/pages/settings/custom-css.vue index 556ee30c1d..20db077ceb 100644 --- a/packages/client/src/pages/settings/custom-css.vue +++ b/packages/client/src/pages/settings/custom-css.vue @@ -1,6 +1,6 @@ <template> <div class="_formRoot"> - <FormInfo warn class="_formBlock">{{ $ts.customCssWarn }}</FormInfo> + <FormInfo warn class="_formBlock">{{ i18n.ts.customCssWarn }}</FormInfo> <FormTextarea v-model="localCustomCss" manual-save tall class="_monospace _formBlock" style="tab-size: 2;"> <template #label>CSS</template> @@ -8,50 +8,38 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref, watch } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormInfo from '@/components/ui/info.vue'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; -import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormTextarea, - FormInfo, - }, +const localCustomCss = ref(localStorage.getItem('customCss') ?? ''); - emits: ['info'], +async function apply() { + localStorage.setItem('customCss', localCustomCss.value); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.customCss, - icon: 'fas fa-code', - bg: 'var(--bg)', - }, - localCustomCss: localStorage.getItem('customCss') - } - }, + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting, + }); + if (canceled) return; - mounted() { - this.$watch('localCustomCss', this.apply); - }, + unisonReload(); +} - methods: { - async apply() { - localStorage.setItem('customCss', this.localCustomCss); - - const { canceled } = await os.confirm({ - type: 'info', - text: this.$ts.reloadToApplySetting, - }); - if (canceled) return; +watch(localCustomCss, async () => { + await apply(); +}); - unisonReload(); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.customCss, + icon: 'fas fa-code', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/deck.vue b/packages/client/src/pages/settings/deck.vue index 46b90d3d1a..2d868aa0a7 100644 --- a/packages/client/src/pages/settings/deck.vue +++ b/packages/client/src/pages/settings/deck.vue @@ -1,36 +1,36 @@ <template> <div class="_formRoot"> <FormGroup> - <template #label>{{ $ts.defaultNavigationBehaviour }}</template> - <FormSwitch v-model="navWindow">{{ $ts.openInWindow }}</FormSwitch> + <template #label>{{ i18n.ts.defaultNavigationBehaviour }}</template> + <FormSwitch v-model="navWindow">{{ i18n.ts.openInWindow }}</FormSwitch> </FormGroup> - <FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch> + <FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ i18n.ts._deck.alwaysShowMainColumn }}</FormSwitch> <FormRadios v-model="columnAlign" class="_formBlock"> - <template #label>{{ $ts._deck.columnAlign }}</template> - <option value="left">{{ $ts.left }}</option> - <option value="center">{{ $ts.center }}</option> + <template #label>{{ i18n.ts._deck.columnAlign }}</template> + <option value="left">{{ i18n.ts.left }}</option> + <option value="center">{{ i18n.ts.center }}</option> </FormRadios> <FormRadios v-model="columnHeaderHeight" class="_formBlock"> - <template #label>{{ $ts._deck.columnHeaderHeight }}</template> - <option :value="42">{{ $ts.narrow }}</option> - <option :value="45">{{ $ts.medium }}</option> - <option :value="48">{{ $ts.wide }}</option> + <template #label>{{ i18n.ts._deck.columnHeaderHeight }}</template> + <option :value="42">{{ i18n.ts.narrow }}</option> + <option :value="45">{{ i18n.ts.medium }}</option> + <option :value="48">{{ i18n.ts.wide }}</option> </FormRadios> <FormInput v-model="columnMargin" type="number" class="_formBlock"> - <template #label>{{ $ts._deck.columnMargin }}</template> + <template #label>{{ i18n.ts._deck.columnMargin }}</template> <template #suffix>px</template> </FormInput> - <FormLink class="_formBlock" @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink> + <FormLink class="_formBlock" @click="setProfile">{{ i18n.ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, watch } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormLink from '@/components/form/link.vue'; import FormRadios from '@/components/form/radios.vue'; @@ -40,59 +40,41 @@ import { deckStore } from '@/ui/deck/deck-store'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormLink, - FormInput, - FormRadios, - FormGroup, - }, +const navWindow = computed(deckStore.makeGetterSetter('navWindow')); +const alwaysShowMainColumn = computed(deckStore.makeGetterSetter('alwaysShowMainColumn')); +const columnAlign = computed(deckStore.makeGetterSetter('columnAlign')); +const columnMargin = computed(deckStore.makeGetterSetter('columnMargin')); +const columnHeaderHeight = computed(deckStore.makeGetterSetter('columnHeaderHeight')); +const profile = computed(deckStore.makeGetterSetter('profile')); - emits: ['info'], +watch(navWindow, async () => { + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting, + }); + if (canceled) return; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.deck, - icon: 'fas fa-columns', - bg: 'var(--bg)', - }, - } - }, - - computed: { - navWindow: deckStore.makeGetterSetter('navWindow'), - alwaysShowMainColumn: deckStore.makeGetterSetter('alwaysShowMainColumn'), - columnAlign: deckStore.makeGetterSetter('columnAlign'), - columnMargin: deckStore.makeGetterSetter('columnMargin'), - columnHeaderHeight: deckStore.makeGetterSetter('columnHeaderHeight'), - profile: deckStore.makeGetterSetter('profile'), - }, - - watch: { - async navWindow() { - const { canceled } = await os.confirm({ - type: 'info', - text: this.$ts.reloadToApplySetting, - }); - if (canceled) return; + unisonReload(); +}); - unisonReload(); - } - }, +async function setProfile() { + const { canceled, result: name } = await os.inputText({ + title: i18n.ts._deck.profile, + allowEmpty: false + }); + if (canceled) return; + + profile.value = name; + unisonReload(); +} - methods: { - async setProfile() { - const { canceled, result: name } = await os.inputText({ - title: this.$ts._deck.profile, - allowEmpty: false - }); - if (canceled) return; - this.profile = name; - unisonReload(); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.deck, + icon: 'fas fa-columns', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/delete-account.vue b/packages/client/src/pages/settings/delete-account.vue index 7edc81a309..e9f19aaf0b 100644 --- a/packages/client/src/pages/settings/delete-account.vue +++ b/packages/client/src/pages/settings/delete-account.vue @@ -1,64 +1,52 @@ <template> <div class="_formRoot"> - <FormInfo warn class="_formBlock">{{ $ts._accountDelete.mayTakeTime }}</FormInfo> - <FormInfo class="_formBlock">{{ $ts._accountDelete.sendEmail }}</FormInfo> - <FormButton v-if="!$i.isDeleted" danger class="_formBlock" @click="deleteAccount">{{ $ts._accountDelete.requestAccountDelete }}</FormButton> - <FormButton v-else disabled>{{ $ts._accountDelete.inProgress }}</FormButton> + <FormInfo warn class="_formBlock">{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo> + <FormInfo class="_formBlock">{{ i18n.ts._accountDelete.sendEmail }}</FormInfo> + <FormButton v-if="!$i.isDeleted" danger class="_formBlock" @click="deleteAccount">{{ i18n.ts._accountDelete.requestAccountDelete }}</FormButton> + <FormButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</FormButton> </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose } from 'vue'; import FormInfo from '@/components/ui/info.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import { signout } from '@/account'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormButton, - FormInfo, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._accountDelete.accountDelete, - icon: 'fas fa-exclamation-triangle', - bg: 'var(--bg)', - }, - } - }, +async function deleteAccount() { + { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts.deleteAccountConfirm, + }); + if (canceled) return; + } - methods: { - async deleteAccount() { - { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$ts.deleteAccountConfirm, - }); - if (canceled) return; - } + const { canceled, result: password } = await os.inputText({ + title: i18n.ts.password, + type: 'password' + }); + if (canceled) return; - const { canceled, result: password } = await os.inputText({ - title: this.$ts.password, - type: 'password' - }); - if (canceled) return; + await os.apiWithDialog('i/delete-account', { + password: password + }); - await os.apiWithDialog('i/delete-account', { - password: password - }); + await os.alert({ + title: i18n.ts._accountDelete.started, + }); - await os.alert({ - title: this.$ts._accountDelete.started, - }); + await signout(); +} - signout(); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts._accountDelete.accountDelete, + icon: 'fas fa-exclamation-triangle', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/drive.vue b/packages/client/src/pages/settings/drive.vue index 9309eb5ec7..09a2537ed5 100644 --- a/packages/client/src/pages/settings/drive.vue +++ b/packages/client/src/pages/settings/drive.vue @@ -1,41 +1,41 @@ <template> <div class="_formRoot"> <FormSection v-if="!fetching"> - <template #label>{{ $ts.usageAmount }}</template> + <template #label>{{ i18n.ts.usageAmount }}</template> <div class="_formBlock uawsfosz"> <div class="meter"><div :style="meterStyle"></div></div> </div> <FormSplit> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.capacity }}</template> + <template #key>{{ i18n.ts.capacity }}</template> <template #value>{{ bytes(capacity, 1) }}</template> </MkKeyValue> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.inUse }}</template> + <template #key>{{ i18n.ts.inUse }}</template> <template #value>{{ bytes(usage, 1) }}</template> </MkKeyValue> </FormSplit> </FormSection> <FormSection> - <template #label>{{ $ts.statistics }}</template> + <template #label>{{ i18n.ts.statistics }}</template> <MkChart src="per-user-drive" :args="{ user: $i }" span="day" :limit="7 * 5" :bar="true" :stacked="true" :detailed="false" :aspect-ratio="6"/> </FormSection> <FormSection> <FormLink @click="chooseUploadFolder()"> - {{ $ts.uploadFolder }} + {{ i18n.ts.uploadFolder }} <template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template> <template #suffixIcon><i class="fas fa-folder-open"></i></template> </FormLink> - <FormSwitch v-model="keepOriginalUploading" class="_formBlock">{{ $ts.keepOriginalUploading }}<template #caption>{{ $ts.keepOriginalUploadingDescription }}</template></FormSwitch> + <FormSwitch v-model="keepOriginalUploading" class="_formBlock">{{ i18n.ts.keepOriginalUploading }}<template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template></FormSwitch> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as tinycolor from 'tinycolor2'; +<script lang="ts" setup> +import { computed, defineExpose, ref } from 'vue'; +import tinycolor from 'tinycolor2'; import FormLink from '@/components/form/link.vue'; import FormSwitch from '@/components/form/switch.vue'; import FormSection from '@/components/form/section.vue'; @@ -46,80 +46,59 @@ import bytes from '@/filters/bytes'; import * as symbols from '@/symbols'; import { defaultStore } from '@/store'; import MkChart from '@/components/chart.vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormLink, - FormSwitch, - FormSection, - MkKeyValue, - FormSplit, - MkChart, - }, +const fetching = ref(true); +const usage = ref<any>(null); +const capacity = ref<any>(null); +const uploadFolder = ref<any>(null); - emits: ['info'], +const meterStyle = computed(() => { + return { + width: `${usage.value / capacity.value * 100}%`, + background: tinycolor({ + h: 180 - (usage.value / capacity.value * 180), + s: 0.7, + l: 0.5 + }) + }; +}); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.drive, - icon: 'fas fa-cloud', - bg: 'var(--bg)', - }, - fetching: true, - usage: null, - capacity: null, - uploadFolder: null, - } - }, +const keepOriginalUploading = computed(defaultStore.makeGetterSetter('keepOriginalUploading')); - computed: { - meterStyle(): any { - return { - width: `${this.usage / this.capacity * 100}%`, - background: tinycolor({ - h: 180 - (this.usage / this.capacity * 180), - s: 0.7, - l: 0.5 - }) - }; - }, - keepOriginalUploading: defaultStore.makeGetterSetter('keepOriginalUploading'), - }, +os.api('drive').then(info => { + capacity.value = info.capacity; + usage.value = info.usage; + fetching.value = false; +}); - async created() { - os.api('drive').then(info => { - this.capacity = info.capacity; - this.usage = info.usage; - this.fetching = false; - this.$nextTick(() => { - this.renderChart(); - }); - }); +if (defaultStore.state.uploadFolder) { + os.api('drive/folders/show', { + folderId: defaultStore.state.uploadFolder + }).then(response => { + uploadFolder.value = response; + }); +} - if (this.$store.state.uploadFolder) { - this.uploadFolder = await os.api('drive/folders/show', { - folderId: this.$store.state.uploadFolder +function chooseUploadFolder() { + os.selectDriveFolder(false).then(async folder => { + defaultStore.set('uploadFolder', folder ? folder.id : null); + os.success(); + if (defaultStore.state.uploadFolder) { + uploadFolder.value = await os.api('drive/folders/show', { + folderId: defaultStore.state.uploadFolder }); + } else { + uploadFolder.value = null; } - }, - - methods: { - chooseUploadFolder() { - os.selectDriveFolder(false).then(async folder => { - this.$store.set('uploadFolder', folder ? folder.id : null); - os.success(); - if (this.$store.state.uploadFolder) { - this.uploadFolder = await os.api('drive/folders/show', { - folderId: this.$store.state.uploadFolder - }); - } else { - this.uploadFolder = null; - } - }); - }, + }); +} - bytes +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.drive, + icon: 'fas fa-cloud', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/email.vue b/packages/client/src/pages/settings/email.vue index 4697fec9b7..37f14068e2 100644 --- a/packages/client/src/pages/settings/email.vue +++ b/packages/client/src/pages/settings/email.vue @@ -39,8 +39,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent, onMounted, ref, watch } from 'vue'; +<script lang="ts" setup> +import { defineExpose, onMounted, ref, watch } from 'vue'; import FormSection from '@/components/form/section.vue'; import FormInput from '@/components/form/input.vue'; import FormSwitch from '@/components/form/switch.vue'; @@ -49,79 +49,62 @@ import * as symbols from '@/symbols'; import { $i } from '@/account'; import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - FormSwitch, - FormInput, - }, +const emailAddress = ref($i!.email); - emits: ['info'], +const onChangeReceiveAnnouncementEmail = (v) => { + os.api('i/update', { + receiveAnnouncementEmail: v + }); +}; - setup(props, context) { - const emailAddress = ref($i.email); - - const INFO = { - title: i18n.ts.email, - icon: 'fas fa-envelope', - bg: 'var(--bg)', - }; - - const onChangeReceiveAnnouncementEmail = (v) => { - os.api('i/update', { - receiveAnnouncementEmail: v - }); - }; - - const saveEmailAddress = () => { - os.inputText({ - title: i18n.ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.apiWithDialog('i/update-email', { - password: password, - email: emailAddress.value, - }); - }); - }; +const saveEmailAddress = () => { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.apiWithDialog('i/update-email', { + password: password, + email: emailAddress.value, + }); + }); +}; - const emailNotification_mention = ref($i.emailNotificationTypes.includes('mention')); - const emailNotification_reply = ref($i.emailNotificationTypes.includes('reply')); - const emailNotification_quote = ref($i.emailNotificationTypes.includes('quote')); - const emailNotification_follow = ref($i.emailNotificationTypes.includes('follow')); - const emailNotification_receiveFollowRequest = ref($i.emailNotificationTypes.includes('receiveFollowRequest')); - const emailNotification_groupInvited = ref($i.emailNotificationTypes.includes('groupInvited')); +const emailNotification_mention = ref($i!.emailNotificationTypes.includes('mention')); +const emailNotification_reply = ref($i!.emailNotificationTypes.includes('reply')); +const emailNotification_quote = ref($i!.emailNotificationTypes.includes('quote')); +const emailNotification_follow = ref($i!.emailNotificationTypes.includes('follow')); +const emailNotification_receiveFollowRequest = ref($i!.emailNotificationTypes.includes('receiveFollowRequest')); +const emailNotification_groupInvited = ref($i!.emailNotificationTypes.includes('groupInvited')); - const saveNotificationSettings = () => { - os.api('i/update', { - emailNotificationTypes: [ - ...[emailNotification_mention.value ? 'mention' : null], - ...[emailNotification_reply.value ? 'reply' : null], - ...[emailNotification_quote.value ? 'quote' : null], - ...[emailNotification_follow.value ? 'follow' : null], - ...[emailNotification_receiveFollowRequest.value ? 'receiveFollowRequest' : null], - ...[emailNotification_groupInvited.value ? 'groupInvited' : null], - ].filter(x => x != null) - }); - }; +const saveNotificationSettings = () => { + os.api('i/update', { + emailNotificationTypes: [ + ...[emailNotification_mention.value ? 'mention' : null], + ...[emailNotification_reply.value ? 'reply' : null], + ...[emailNotification_quote.value ? 'quote' : null], + ...[emailNotification_follow.value ? 'follow' : null], + ...[emailNotification_receiveFollowRequest.value ? 'receiveFollowRequest' : null], + ...[emailNotification_groupInvited.value ? 'groupInvited' : null], + ].filter(x => x != null) + }); +}; - watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => { - saveNotificationSettings(); - }); +watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => { + saveNotificationSettings(); +}); - onMounted(() => { - watch(emailAddress, () => { - saveEmailAddress(); - }); - }); +onMounted(() => { + watch(emailAddress, () => { + saveEmailAddress(); + }); +}); - return { - [symbols.PAGE_INFO]: INFO, - emailAddress, - onChangeReceiveAnnouncementEmail, - emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited, - }; - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.email, + icon: 'fas fa-envelope', + bg: 'var(--bg)', + } }); </script> diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue index c8f6f58322..64b8cc3106 100644 --- a/packages/client/src/pages/settings/general.vue +++ b/packages/client/src/pages/settings/general.vue @@ -1,10 +1,10 @@ <template> <div class="_formRoot"> <FormSelect v-model="lang" class="_formBlock"> - <template #label>{{ $ts.uiLanguage }}</template> + <template #label>{{ i18n.ts.uiLanguage }}</template> <option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option> <template #caption> - <I18n :src="$ts.i18nInfo" tag="span"> + <I18n :src="i18n.ts.i18nInfo" tag="span"> <template #link> <MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink> </template> @@ -13,48 +13,48 @@ </FormSelect> <FormRadios v-model="overridedDeviceKind" class="_formBlock"> - <template #label>{{ $ts.overridedDeviceKind }}</template> - <option :value="null">{{ $ts.auto }}</option> - <option value="smartphone"><i class="fas fa-mobile-alt"/> {{ $ts.smartphone }}</option> - <option value="tablet"><i class="fas fa-tablet-alt"/> {{ $ts.tablet }}</option> - <option value="desktop"><i class="fas fa-desktop"/> {{ $ts.desktop }}</option> + <template #label>{{ i18n.ts.overridedDeviceKind }}</template> + <option :value="null">{{ i18n.ts.auto }}</option> + <option value="smartphone"><i class="fas fa-mobile-alt"/> {{ i18n.ts.smartphone }}</option> + <option value="tablet"><i class="fas fa-tablet-alt"/> {{ i18n.ts.tablet }}</option> + <option value="desktop"><i class="fas fa-desktop"/> {{ i18n.ts.desktop }}</option> </FormRadios> - <FormSwitch v-model="showFixedPostForm" class="_formBlock">{{ $ts.showFixedPostForm }}</FormSwitch> + <FormSwitch v-model="showFixedPostForm" class="_formBlock">{{ i18n.ts.showFixedPostForm }}</FormSwitch> <FormSection> - <template #label>{{ $ts.behavior }}</template> - <FormSwitch v-model="imageNewTab" class="_formBlock">{{ $ts.openImageInNewTab }}</FormSwitch> - <FormSwitch v-model="enableInfiniteScroll" class="_formBlock">{{ $ts.enableInfiniteScroll }}</FormSwitch> - <FormSwitch v-model="useReactionPickerForContextMenu" class="_formBlock">{{ $ts.useReactionPickerForContextMenu }}</FormSwitch> - <FormSwitch v-model="disablePagesScript" class="_formBlock">{{ $ts.disablePagesScript }}</FormSwitch> + <template #label>{{ i18n.ts.behavior }}</template> + <FormSwitch v-model="imageNewTab" class="_formBlock">{{ i18n.ts.openImageInNewTab }}</FormSwitch> + <FormSwitch v-model="enableInfiniteScroll" class="_formBlock">{{ i18n.ts.enableInfiniteScroll }}</FormSwitch> + <FormSwitch v-model="useReactionPickerForContextMenu" class="_formBlock">{{ i18n.ts.useReactionPickerForContextMenu }}</FormSwitch> + <FormSwitch v-model="disablePagesScript" class="_formBlock">{{ i18n.ts.disablePagesScript }}</FormSwitch> <FormSelect v-model="serverDisconnectedBehavior" class="_formBlock"> - <template #label>{{ $ts.whenServerDisconnected }}</template> - <option value="reload">{{ $ts._serverDisconnectedBehavior.reload }}</option> - <option value="dialog">{{ $ts._serverDisconnectedBehavior.dialog }}</option> - <option value="quiet">{{ $ts._serverDisconnectedBehavior.quiet }}</option> + <template #label>{{ i18n.ts.whenServerDisconnected }}</template> + <option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option> + <option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option> + <option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option> </FormSelect> </FormSection> <FormSection> - <template #label>{{ $ts.appearance }}</template> - <FormSwitch v-model="disableAnimatedMfm" class="_formBlock">{{ $ts.disableAnimatedMfm }}</FormSwitch> - <FormSwitch v-model="reduceAnimation" class="_formBlock">{{ $ts.reduceUiAnimation }}</FormSwitch> - <FormSwitch v-model="useBlurEffect" class="_formBlock">{{ $ts.useBlurEffect }}</FormSwitch> - <FormSwitch v-model="useBlurEffectForModal" class="_formBlock">{{ $ts.useBlurEffectForModal }}</FormSwitch> - <FormSwitch v-model="showGapBetweenNotesInTimeline" class="_formBlock">{{ $ts.showGapBetweenNotesInTimeline }}</FormSwitch> - <FormSwitch v-model="loadRawImages" class="_formBlock">{{ $ts.loadRawImages }}</FormSwitch> - <FormSwitch v-model="disableShowingAnimatedImages" class="_formBlock">{{ $ts.disableShowingAnimatedImages }}</FormSwitch> - <FormSwitch v-model="squareAvatars" class="_formBlock">{{ $ts.squareAvatars }}</FormSwitch> - <FormSwitch v-model="useSystemFont" class="_formBlock">{{ $ts.useSystemFont }}</FormSwitch> - <FormSwitch v-model="useOsNativeEmojis" class="_formBlock">{{ $ts.useOsNativeEmojis }} + <template #label>{{ i18n.ts.appearance }}</template> + <FormSwitch v-model="disableAnimatedMfm" class="_formBlock">{{ i18n.ts.disableAnimatedMfm }}</FormSwitch> + <FormSwitch v-model="reduceAnimation" class="_formBlock">{{ i18n.ts.reduceUiAnimation }}</FormSwitch> + <FormSwitch v-model="useBlurEffect" class="_formBlock">{{ i18n.ts.useBlurEffect }}</FormSwitch> + <FormSwitch v-model="useBlurEffectForModal" class="_formBlock">{{ i18n.ts.useBlurEffectForModal }}</FormSwitch> + <FormSwitch v-model="showGapBetweenNotesInTimeline" class="_formBlock">{{ i18n.ts.showGapBetweenNotesInTimeline }}</FormSwitch> + <FormSwitch v-model="loadRawImages" class="_formBlock">{{ i18n.ts.loadRawImages }}</FormSwitch> + <FormSwitch v-model="disableShowingAnimatedImages" class="_formBlock">{{ i18n.ts.disableShowingAnimatedImages }}</FormSwitch> + <FormSwitch v-model="squareAvatars" class="_formBlock">{{ i18n.ts.squareAvatars }}</FormSwitch> + <FormSwitch v-model="useSystemFont" class="_formBlock">{{ i18n.ts.useSystemFont }}</FormSwitch> + <FormSwitch v-model="useOsNativeEmojis" class="_formBlock">{{ i18n.ts.useOsNativeEmojis }} <div><Mfm :key="useOsNativeEmojis" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div> </FormSwitch> - <FormSwitch v-model="disableDrawer" class="_formBlock">{{ $ts.disableDrawer }}</FormSwitch> + <FormSwitch v-model="disableDrawer" class="_formBlock">{{ i18n.ts.disableDrawer }}</FormSwitch> <FormRadios v-model="fontSize" class="_formBlock"> - <template #label>{{ $ts.fontSize }}</template> + <template #label>{{ i18n.ts.fontSize }}</template> <option value="small"><span style="font-size: 14px;">Aa</span></option> <option :value="null"><span style="font-size: 16px;">Aa</span></option> <option value="large"><span style="font-size: 18px;">Aa</span></option> @@ -63,36 +63,36 @@ </FormSection> <FormSection> - <FormSwitch v-model="aiChanMode">{{ $ts.aiChanMode }}</FormSwitch> + <FormSwitch v-model="aiChanMode">{{ i18n.ts.aiChanMode }}</FormSwitch> </FormSection> <FormSelect v-model="instanceTicker" class="_formBlock"> - <template #label>{{ $ts.instanceTicker }}</template> - <option value="none">{{ $ts._instanceTicker.none }}</option> - <option value="remote">{{ $ts._instanceTicker.remote }}</option> - <option value="always">{{ $ts._instanceTicker.always }}</option> + <template #label>{{ i18n.ts.instanceTicker }}</template> + <option value="none">{{ i18n.ts._instanceTicker.none }}</option> + <option value="remote">{{ i18n.ts._instanceTicker.remote }}</option> + <option value="always">{{ i18n.ts._instanceTicker.always }}</option> </FormSelect> <FormSelect v-model="nsfw" class="_formBlock"> - <template #label>{{ $ts.nsfw }}</template> - <option value="respect">{{ $ts._nsfw.respect }}</option> - <option value="ignore">{{ $ts._nsfw.ignore }}</option> - <option value="force">{{ $ts._nsfw.force }}</option> + <template #label>{{ i18n.ts.nsfw }}</template> + <option value="respect">{{ i18n.ts._nsfw.respect }}</option> + <option value="ignore">{{ i18n.ts._nsfw.ignore }}</option> + <option value="force">{{ i18n.ts._nsfw.force }}</option> </FormSelect> <FormGroup> - <template #label>{{ $ts.defaultNavigationBehaviour }}</template> - <FormSwitch v-model="defaultSideView">{{ $ts.openInSideView }}</FormSwitch> + <template #label>{{ i18n.ts.defaultNavigationBehaviour }}</template> + <FormSwitch v-model="defaultSideView">{{ i18n.ts.openInSideView }}</FormSwitch> </FormGroup> - <FormLink to="/settings/deck" class="_formBlock">{{ $ts.deck }}</FormLink> + <FormLink to="/settings/deck" class="_formBlock">{{ i18n.ts.deck }}</FormLink> - <FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ $ts.customCss }}</FormLink> + <FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ i18n.ts.customCss }}</FormLink> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, ref, watch } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormSelect from '@/components/form/select.vue'; import FormRadios from '@/components/form/radios.vue'; @@ -102,122 +102,87 @@ import FormLink from '@/components/form/link.vue'; import MkLink from '@/components/link.vue'; import { langs } from '@/config'; import { defaultStore } from '@/store'; -import { ColdDeviceStorage } from '@/store'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkLink, - FormSwitch, - FormSelect, - FormRadios, - FormGroup, - FormLink, - FormSection, - }, +const lang = ref(localStorage.getItem('lang')); +const fontSize = ref(localStorage.getItem('fontSize')); +const useSystemFont = ref(localStorage.getItem('useSystemFont') != null); - emits: ['info'], +async function reloadAsk() { + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting, + }); + if (canceled) return; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.general, - icon: 'fas fa-cogs', - bg: 'var(--bg)' - }, - langs, - lang: localStorage.getItem('lang'), - fontSize: localStorage.getItem('fontSize'), - useSystemFont: localStorage.getItem('useSystemFont') != null, - } - }, + unisonReload(); +} - computed: { - overridedDeviceKind: defaultStore.makeGetterSetter('overridedDeviceKind'), - serverDisconnectedBehavior: defaultStore.makeGetterSetter('serverDisconnectedBehavior'), - reduceAnimation: defaultStore.makeGetterSetter('animation', v => !v, v => !v), - useBlurEffectForModal: defaultStore.makeGetterSetter('useBlurEffectForModal'), - useBlurEffect: defaultStore.makeGetterSetter('useBlurEffect'), - showGapBetweenNotesInTimeline: defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline'), - disableAnimatedMfm: defaultStore.makeGetterSetter('animatedMfm', v => !v, v => !v), - useOsNativeEmojis: defaultStore.makeGetterSetter('useOsNativeEmojis'), - disableDrawer: defaultStore.makeGetterSetter('disableDrawer'), - disableShowingAnimatedImages: defaultStore.makeGetterSetter('disableShowingAnimatedImages'), - loadRawImages: defaultStore.makeGetterSetter('loadRawImages'), - imageNewTab: defaultStore.makeGetterSetter('imageNewTab'), - nsfw: defaultStore.makeGetterSetter('nsfw'), - disablePagesScript: defaultStore.makeGetterSetter('disablePagesScript'), - showFixedPostForm: defaultStore.makeGetterSetter('showFixedPostForm'), - defaultSideView: defaultStore.makeGetterSetter('defaultSideView'), - instanceTicker: defaultStore.makeGetterSetter('instanceTicker'), - enableInfiniteScroll: defaultStore.makeGetterSetter('enableInfiniteScroll'), - useReactionPickerForContextMenu: defaultStore.makeGetterSetter('useReactionPickerForContextMenu'), - squareAvatars: defaultStore.makeGetterSetter('squareAvatars'), - aiChanMode: defaultStore.makeGetterSetter('aiChanMode'), - }, +const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind')); +const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior')); +const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v)); +const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal')); +const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect')); +const showGapBetweenNotesInTimeline = computed(defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline')); +const disableAnimatedMfm = computed(defaultStore.makeGetterSetter('animatedMfm', v => !v, v => !v)); +const useOsNativeEmojis = computed(defaultStore.makeGetterSetter('useOsNativeEmojis')); +const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer')); +const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages')); +const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages')); +const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab')); +const nsfw = computed(defaultStore.makeGetterSetter('nsfw')); +const disablePagesScript = computed(defaultStore.makeGetterSetter('disablePagesScript')); +const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm')); +const defaultSideView = computed(defaultStore.makeGetterSetter('defaultSideView')); +const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker')); +const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll')); +const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu')); +const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars')); +const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode')); - watch: { - lang() { - localStorage.setItem('lang', this.lang); - localStorage.removeItem('locale'); - this.reloadAsk(); - }, - - fontSize() { - if (this.fontSize == null) { - localStorage.removeItem('fontSize'); - } else { - localStorage.setItem('fontSize', this.fontSize); - } - this.reloadAsk(); - }, - - useSystemFont() { - if (this.useSystemFont) { - localStorage.setItem('useSystemFont', 't'); - } else { - localStorage.removeItem('useSystemFont'); - } - this.reloadAsk(); - }, - - enableInfiniteScroll() { - this.reloadAsk(); - }, - - squareAvatars() { - this.reloadAsk(); - }, - - aiChanMode() { - this.reloadAsk(); - }, - - showGapBetweenNotesInTimeline() { - this.reloadAsk(); - }, +watch(lang, () => { + localStorage.setItem('lang', lang.value as string); + localStorage.removeItem('locale'); +}); - instanceTicker() { - this.reloadAsk(); - }, +watch(fontSize, () => { + if (fontSize.value == null) { + localStorage.removeItem('fontSize'); + } else { + localStorage.setItem('fontSize', fontSize.value); + } +}); - overridedDeviceKind() { - this.reloadAsk(); - }, - }, +watch(useSystemFont, () => { + if (useSystemFont.value) { + localStorage.setItem('useSystemFont', 't'); + } else { + localStorage.removeItem('useSystemFont'); + } +}); - methods: { - async reloadAsk() { - const { canceled } = await os.confirm({ - type: 'info', - text: this.$ts.reloadToApplySetting, - }); - if (canceled) return; +watch([ + lang, + fontSize, + useSystemFont, + enableInfiniteScroll, + squareAvatars, + aiChanMode, + showGapBetweenNotesInTimeline, + instanceTicker, + overridedDeviceKind +], async () => { + await reloadAsk(); +}); - unisonReload(); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.general, + icon: 'fas fa-cogs', + bg: 'var(--bg)' } }); </script> diff --git a/packages/client/src/pages/settings/import-export.vue b/packages/client/src/pages/settings/import-export.vue index c153b4d28c..127cbcd4c1 100644 --- a/packages/client/src/pages/settings/import-export.vue +++ b/packages/client/src/pages/settings/import-export.vue @@ -37,8 +37,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent, onMounted, ref } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref } from 'vue'; import MkButton from '@/components/ui/button.vue'; import FormSection from '@/components/form/section.vue'; import FormGroup from '@/components/form/group.vue'; @@ -48,108 +48,80 @@ import { selectFile } from '@/scripts/select-file'; import * as symbols from '@/symbols'; import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - FormGroup, - FormSwitch, - MkButton, - }, +const excludeMutingUsers = ref(false); +const excludeInactiveUsers = ref(false); - emits: ['info'], +const onExportSuccess = () => { + os.alert({ + type: 'info', + text: i18n.ts.exportRequested, + }); +}; - setup(props, context) { - const INFO = { - title: i18n.ts.importAndExport, - icon: 'fas fa-boxes', - bg: 'var(--bg)', - }; +const onImportSuccess = () => { + os.alert({ + type: 'info', + text: i18n.ts.importRequested, + }); +}; - const excludeMutingUsers = ref(false); - const excludeInactiveUsers = ref(false); +const onError = (ev) => { + os.alert({ + type: 'error', + text: ev.message, + }); +}; - const onExportSuccess = () => { - os.alert({ - type: 'info', - text: i18n.ts.exportRequested, - }); - }; +const exportNotes = () => { + os.api('i/export-notes', {}).then(onExportSuccess).catch(onError); +}; - const onImportSuccess = () => { - os.alert({ - type: 'info', - text: i18n.ts.importRequested, - }); - }; +const exportFollowing = () => { + os.api('i/export-following', { + excludeMuting: excludeMutingUsers.value, + excludeInactive: excludeInactiveUsers.value, + }) + .then(onExportSuccess).catch(onError); +}; - const onError = (e) => { - os.alert({ - type: 'error', - text: e.message, - }); - }; +const exportBlocking = () => { + os.api('i/export-blocking', {}).then(onExportSuccess).catch(onError); +}; - const exportNotes = () => { - os.api('i/export-notes', {}).then(onExportSuccess).catch(onError); - }; +const exportUserLists = () => { + os.api('i/export-user-lists', {}).then(onExportSuccess).catch(onError); +}; - const exportFollowing = () => { - os.api('i/export-following', { - excludeMuting: excludeMutingUsers.value, - excludeInactive: excludeInactiveUsers.value, - }) - .then(onExportSuccess).catch(onError); - }; +const exportMuting = () => { + os.api('i/export-mute', {}).then(onExportSuccess).catch(onError); +}; - const exportBlocking = () => { - os.api('i/export-blocking', {}).then(onExportSuccess).catch(onError); - }; +const importFollowing = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + os.api('i/import-following', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; - const exportUserLists = () => { - os.api('i/export-user-lists', {}).then(onExportSuccess).catch(onError); - }; +const importUserLists = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + os.api('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; - const exportMuting = () => { - os.api('i/export-mute', {}).then(onExportSuccess).catch(onError); - }; +const importMuting = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + os.api('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; - const importFollowing = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - os.api('i/import-following', { fileId: file.id }).then(onImportSuccess).catch(onError); - }; +const importBlocking = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; - const importUserLists = async (ev) => { - 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); - os.api('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError); - }; - - const importBlocking = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); - }; - - return { - [symbols.PAGE_INFO]: INFO, - excludeMutingUsers, - excludeInactiveUsers, - - exportNotes, - exportFollowing, - exportBlocking, - exportUserLists, - exportMuting, - - importFollowing, - importUserLists, - importMuting, - importBlocking, - }; - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.importAndExport, + icon: 'fas fa-boxes', + bg: 'var(--bg)', + } }); </script> diff --git a/packages/client/src/pages/settings/index.vue b/packages/client/src/pages/settings/index.vue index 44c3be62fe..e6670ea930 100644 --- a/packages/client/src/pages/settings/index.vue +++ b/packages/client/src/pages/settings/index.vue @@ -2,19 +2,22 @@ <MkSpacer :content-max="900" :margin-min="20" :margin-max="32"> <div ref="el" class="vvcocwet" :class="{ wide: !narrow }"> <div class="header"> - <div class="title">{{ $ts.settings }}</div> + <div class="title"> + <MkA v-if="narrow" to="/settings">{{ $ts.settings }}</MkA> + <template v-else>{{ $ts.settings }}</template> + </div> <div v-if="childInfo" class="subtitle">{{ childInfo.title }}</div> </div> <div class="body"> - <div v-if="!narrow || page == null" class="nav"> + <div v-if="!narrow || initialPage == null" class="nav"> <div class="baaadecd"> <MkInfo v-if="emailNotConfigured" warn class="info">{{ $ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ $ts.configure }}</MkA></MkInfo> - <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu> + <MkSuperMenu :def="menuDef" :grid="initialPage == null"></MkSuperMenu> </div> </div> - <div class="main"> + <div v-if="!(narrow && initialPage == null)" class="main"> <div class="bkzroven"> - <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> + <component :is="component" :ref="el => pageChanged(el)" :key="initialPage" v-bind="pageProps"/> </div> </div> </div> @@ -23,7 +26,7 @@ </template> <script setup lang="ts"> -import { computed, defineAsyncComponent, nextTick, onMounted, ref, watch } from 'vue'; +import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'; import { i18n } from '@/i18n'; import MkInfo from '@/components/ui/info.vue'; import MkSuperMenu from '@/components/ui/super-menu.vue'; @@ -33,6 +36,7 @@ import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; import { instance } from '@/instance'; import { $i } from '@/account'; +import { MisskeyNavigator } from '@/scripts/navigate'; const props = defineProps<{ initialPage?: string @@ -45,53 +49,61 @@ const indexInfo = { hideHeader: true, }; const INFO = ref(indexInfo); -const page = ref(props.initialPage); -const narrow = ref(false); -const view = ref(null); const el = ref<HTMLElement | null>(null); const childInfo = ref(null); + +const nav = new MisskeyNavigator(); + +const narrow = ref(false); +const NARROW_THRESHOLD = 600; + +const ro = new ResizeObserver((entries, observer) => { + if (entries.length === 0) return; + narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD; +}); + const menuDef = computed(() => [{ title: i18n.ts.basicSettings, items: [{ icon: 'fas fa-user', text: i18n.ts.profile, to: '/settings/profile', - active: page.value === 'profile', + active: props.initialPage === 'profile', }, { icon: 'fas fa-lock-open', text: i18n.ts.privacy, to: '/settings/privacy', - active: page.value === 'privacy', + active: props.initialPage === 'privacy', }, { icon: 'fas fa-laugh', text: i18n.ts.reaction, to: '/settings/reaction', - active: page.value === 'reaction', + active: props.initialPage === 'reaction', }, { icon: 'fas fa-cloud', text: i18n.ts.drive, to: '/settings/drive', - active: page.value === 'drive', + active: props.initialPage === 'drive', }, { icon: 'fas fa-bell', text: i18n.ts.notifications, to: '/settings/notifications', - active: page.value === 'notifications', + active: props.initialPage === 'notifications', }, { icon: 'fas fa-envelope', text: i18n.ts.email, to: '/settings/email', - active: page.value === 'email', + active: props.initialPage === 'email', }, { icon: 'fas fa-share-alt', text: i18n.ts.integration, to: '/settings/integration', - active: page.value === 'integration', + active: props.initialPage === 'integration', }, { icon: 'fas fa-lock', text: i18n.ts.security, to: '/settings/security', - active: page.value === 'security', + active: props.initialPage === 'security', }], }, { title: i18n.ts.clientSettings, @@ -99,27 +111,27 @@ const menuDef = computed(() => [{ icon: 'fas fa-cogs', text: i18n.ts.general, to: '/settings/general', - active: page.value === 'general', + active: props.initialPage === 'general', }, { icon: 'fas fa-palette', text: i18n.ts.theme, to: '/settings/theme', - active: page.value === 'theme', + active: props.initialPage === 'theme', }, { icon: 'fas fa-list-ul', text: i18n.ts.menu, to: '/settings/menu', - active: page.value === 'menu', + active: props.initialPage === 'menu', }, { icon: 'fas fa-music', text: i18n.ts.sounds, to: '/settings/sounds', - active: page.value === 'sounds', + active: props.initialPage === 'sounds', }, { icon: 'fas fa-plug', text: i18n.ts.plugins, to: '/settings/plugin', - active: page.value === 'plugin', + active: props.initialPage === 'plugin', }], }, { title: i18n.ts.otherSettings, @@ -127,37 +139,37 @@ const menuDef = computed(() => [{ icon: 'fas fa-boxes', text: i18n.ts.importAndExport, to: '/settings/import-export', - active: page.value === 'import-export', + active: props.initialPage === 'import-export', }, { icon: 'fas fa-volume-mute', text: i18n.ts.instanceMute, to: '/settings/instance-mute', - active: page.value === 'instance-mute', + active: props.initialPage === 'instance-mute', }, { icon: 'fas fa-ban', text: i18n.ts.muteAndBlock, to: '/settings/mute-block', - active: page.value === 'mute-block', + active: props.initialPage === 'mute-block', }, { icon: 'fas fa-comment-slash', text: i18n.ts.wordMute, to: '/settings/word-mute', - active: page.value === 'word-mute', + active: props.initialPage === 'word-mute', }, { icon: 'fas fa-key', text: 'API', to: '/settings/api', - active: page.value === 'api', + active: props.initialPage === 'api', }, { icon: 'fas fa-bolt', text: 'Webhook', to: '/settings/webhook', - active: page.value === 'webhook', + active: props.initialPage === 'webhook', }, { icon: 'fas fa-ellipsis-h', text: i18n.ts.other, to: '/settings/other', - active: page.value === 'other', + active: props.initialPage === 'other', }], }, { items: [{ @@ -182,8 +194,8 @@ const menuDef = computed(() => [{ const pageProps = ref({}); const component = computed(() => { - if (page.value == null) return null; - switch (page.value) { + if (props.initialPage == null) return null; + switch (props.initialPage) { case 'accounts': return defineAsyncComponent(() => import('./accounts.vue')); case 'profile': return defineAsyncComponent(() => import('./profile.vue')); case 'privacy': return defineAsyncComponent(() => import('./privacy.vue')); @@ -230,27 +242,41 @@ watch(component, () => { watch(() => props.initialPage, () => { if (props.initialPage == null && !narrow.value) { - page.value = 'profile'; + nav.push('/settings/profile'); } else { - page.value = props.initialPage; if (props.initialPage == null) { INFO.value = indexInfo; } } }); +watch(narrow, () => { + if (props.initialPage == null && !narrow.value) { + nav.push('/settings/profile'); + } +}); + onMounted(() => { - narrow.value = el.value.offsetWidth < 800; - if (!narrow.value) { - page.value = 'profile'; + ro.observe(el.value); + + narrow.value = el.value.offsetWidth < NARROW_THRESHOLD; + if (props.initialPage == null && !narrow.value) { + nav.push('/settings/profile'); } }); +onUnmounted(() => { + ro.disconnect(); +}); + const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified)); const pageChanged = (page) => { - if (page == null) return; - childInfo.value = page[symbols.PAGE_INFO]; + if (page == null) { + childInfo.value = null; + } else { + childInfo.value = page[symbols.PAGE_INFO]; + } }; defineExpose({ @@ -267,6 +293,7 @@ defineExpose({ font-weight: bold; > .title { + display: block; width: 34%; } diff --git a/packages/client/src/pages/settings/instance-mute.vue b/packages/client/src/pages/settings/instance-mute.vue index f84a209b60..bcc2ee85ad 100644 --- a/packages/client/src/pages/settings/instance-mute.vue +++ b/packages/client/src/pages/settings/instance-mute.vue @@ -1,67 +1,51 @@ <template> <div class="_formRoot"> - <MkInfo>{{ $ts._instanceMute.title }}</MkInfo> + <MkInfo>{{ i18n.ts._instanceMute.title }}</MkInfo> <FormTextarea v-model="instanceMutes" class="_formBlock"> - <template #label>{{ $ts._instanceMute.heading }}</template> - <template #caption>{{ $ts._instanceMute.instanceMuteDescription }}<br>{{ $ts._instanceMute.instanceMuteDescription2 }}</template> + <template #label>{{ i18n.ts._instanceMute.heading }}</template> + <template #caption>{{ i18n.ts._instanceMute.instanceMuteDescription }}<br>{{ i18n.ts._instanceMute.instanceMuteDescription2 }}</template> </FormTextarea> - <MkButton primary :disabled="!changed" class="_formBlock" @click="save()"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> + <MkButton primary :disabled="!changed" class="_formBlock" @click="save()"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton> </div> </template> -<script> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref, watch } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import MkInfo from '@/components/ui/info.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - FormTextarea, - MkInfo, - }, +const instanceMutes = ref($i!.mutedInstances.join('\n')); +const changed = ref(false); - emits: ['info'], +async function save() { + let mutes = instanceMutes.value + .trim().split('\n') + .map(el => el.trim()) + .filter(el => el); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.instanceMute, - icon: 'fas fa-volume-mute' - }, - tab: 'soft', - instanceMutes: '', - changed: false, - } - }, + await os.api('i/update', { + mutedInstances: mutes, + }); - watch: { - instanceMutes: { - handler() { - this.changed = true; - }, - deep: true - }, - }, + changed.value = false; - async created() { - this.instanceMutes = this.$i.mutedInstances.join('\n'); - }, + // Refresh filtered list to signal to the user how they've been saved + instanceMutes.value = mutes.join('\n'); +} - methods: { - async save() { - let mutes = this.instanceMutes.trim().split('\n').map(el => el.trim()).filter(el => el); - await os.api('i/update', { - mutedInstances: mutes, - }); - this.changed = false; +watch(instanceMutes, () => { + changed.value = true; +}); - // Refresh filtered list to signal to the user how they've been saved - this.instanceMutes = mutes.join('\n'); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.instanceMute, + icon: 'fas fa-volume-mute' } -}) +}); </script> diff --git a/packages/client/src/pages/settings/integration.vue b/packages/client/src/pages/settings/integration.vue index ca36c91665..75c6200944 100644 --- a/packages/client/src/pages/settings/integration.vue +++ b/packages/client/src/pages/settings/integration.vue @@ -1,133 +1,98 @@ <template> <div class="_formRoot"> - <FormSection v-if="enableTwitterIntegration"> + <FormSection v-if="instance.enableTwitterIntegration"> <template #label><i class="fab fa-twitter"></i> Twitter</template> - <p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> - <MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectTwitter">{{ $ts.connectService }}</MkButton> + <p v-if="integrations.twitter">{{ i18n.ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> + <MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ i18n.ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectTwitter">{{ i18n.ts.connectService }}</MkButton> </FormSection> - <FormSection v-if="enableDiscordIntegration"> + <FormSection v-if="instance.enableDiscordIntegration"> <template #label><i class="fab fa-discord"></i> Discord</template> - <p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> - <MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectDiscord">{{ $ts.connectService }}</MkButton> + <p v-if="integrations.discord">{{ i18n.ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> + <MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ i18n.ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectDiscord">{{ i18n.ts.connectService }}</MkButton> </FormSection> - <FormSection v-if="enableGithubIntegration"> + <FormSection v-if="instance.enableGithubIntegration"> <template #label><i class="fab fa-github"></i> GitHub</template> - <p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> - <MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectGithub">{{ $ts.connectService }}</MkButton> + <p v-if="integrations.github">{{ i18n.ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> + <MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ i18n.ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectGithub">{{ i18n.ts.connectService }}</MkButton> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, onMounted, ref, watch } from 'vue'; import { apiUrl } from '@/config'; import FormSection from '@/components/form/section.vue'; import MkButton from '@/components/ui/button.vue'; -import * as os from '@/os'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - MkButton - }, +const twitterForm = ref<Window | null>(null); +const discordForm = ref<Window | null>(null); +const githubForm = ref<Window | null>(null); - emits: ['info'], +const integrations = computed(() => $i!.integrations); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.integration, - icon: 'fas fa-share-alt', - bg: 'var(--bg)', - }, - apiUrl, - twitterForm: null, - discordForm: null, - githubForm: null, - enableTwitterIntegration: false, - enableDiscordIntegration: false, - enableGithubIntegration: false, - }; - }, +function openWindow(service: string, type: string) { + return window.open(`${apiUrl}/${type}/${service}`, + `${service}_${type}_window`, + 'height=570, width=520' + ); +} - computed: { - integrations() { - return this.$i.integrations; - }, - - meta() { - return this.$instance; - }, - }, +function connectTwitter() { + twitterForm.value = openWindow('twitter', 'connect'); +} - created() { - this.enableTwitterIntegration = this.meta.enableTwitterIntegration; - this.enableDiscordIntegration = this.meta.enableDiscordIntegration; - this.enableGithubIntegration = this.meta.enableGithubIntegration; - }, +function disconnectTwitter() { + openWindow('twitter', 'disconnect'); +} - mounted() { - document.cookie = `igi=${this.$i.token}; path=/;` + - ` max-age=31536000;` + - (document.location.protocol.startsWith('https') ? ' secure' : ''); +function connectDiscord() { + discordForm.value = openWindow('discord', 'connect'); +} - this.$watch('integrations', () => { - if (this.integrations.twitter) { - if (this.twitterForm) this.twitterForm.close(); - } - if (this.integrations.discord) { - if (this.discordForm) this.discordForm.close(); - } - if (this.integrations.github) { - if (this.githubForm) this.githubForm.close(); - } - }, { - deep: true - }); - }, +function disconnectDiscord() { + openWindow('discord', 'disconnect'); +} - methods: { - connectTwitter() { - this.twitterForm = window.open(apiUrl + '/connect/twitter', - 'twitter_connect_window', - 'height=570, width=520'); - }, +function connectGithub() { + githubForm.value = openWindow('github', 'connect'); +} - disconnectTwitter() { - window.open(apiUrl + '/disconnect/twitter', - 'twitter_disconnect_window', - 'height=570, width=520'); - }, +function disconnectGithub() { + openWindow('github', 'disconnect'); +} - connectDiscord() { - this.discordForm = window.open(apiUrl + '/connect/discord', - 'discord_connect_window', - 'height=570, width=520'); - }, +onMounted(() => { + document.cookie = `igi=${$i!.token}; path=/;` + + ` max-age=31536000;` + + (document.location.protocol.startsWith('https') ? ' secure' : ''); - disconnectDiscord() { - window.open(apiUrl + '/disconnect/discord', - 'discord_disconnect_window', - 'height=570, width=520'); - }, - - connectGithub() { - this.githubForm = window.open(apiUrl + '/connect/github', - 'github_connect_window', - 'height=570, width=520'); - }, + watch(integrations, () => { + if (integrations.value.twitter) { + if (twitterForm.value) twitterForm.value.close(); + } + if (integrations.value.discord) { + if (discordForm.value) discordForm.value.close(); + } + if (integrations.value.github) { + if (githubForm.value) githubForm.value.close(); + } + }); +}); - disconnectGithub() { - window.open(apiUrl + '/disconnect/github', - 'github_disconnect_window', - 'height=570, width=520'); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.integration, + icon: 'fas fa-share-alt', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/menu.vue b/packages/client/src/pages/settings/menu.vue index 6e38cd5dfe..2288c3f718 100644 --- a/packages/client/src/pages/settings/menu.vue +++ b/packages/client/src/pages/settings/menu.vue @@ -1,24 +1,24 @@ <template> <div class="_formRoot"> <FormTextarea v-model="items" tall manual-save class="_formBlock"> - <template #label>{{ $ts.menu }}</template> - <template #caption><button class="_textButton" @click="addItem">{{ $ts.addItem }}</button></template> + <template #label>{{ i18n.ts.menu }}</template> + <template #caption><button class="_textButton" @click="addItem">{{ i18n.ts.addItem }}</button></template> </FormTextarea> <FormRadios v-model="menuDisplay" class="_formBlock"> - <template #label>{{ $ts.display }}</template> - <option value="sideFull">{{ $ts._menuDisplay.sideFull }}</option> - <option value="sideIcon">{{ $ts._menuDisplay.sideIcon }}</option> - <option value="top">{{ $ts._menuDisplay.top }}</option> - <!-- <MkRadio v-model="menuDisplay" value="hide" disabled>{{ $ts._menuDisplay.hide }}</MkRadio>--> <!-- TODO: サイドバーを完全に隠せるようにすると、別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 --> + <template #label>{{ i18n.ts.display }}</template> + <option value="sideFull">{{ i18n.ts._menuDisplay.sideFull }}</option> + <option value="sideIcon">{{ i18n.ts._menuDisplay.sideIcon }}</option> + <option value="top">{{ i18n.ts._menuDisplay.top }}</option> + <!-- <MkRadio v-model="menuDisplay" value="hide" disabled>{{ i18n.ts._menuDisplay.hide }}</MkRadio>--> <!-- TODO: サイドバーを完全に隠せるようにすると、別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 --> </FormRadios> - <FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ $ts.default }}</FormButton> + <FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ i18n.ts.default }}</FormButton> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, ref, watch } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormRadios from '@/components/form/radios.vue'; import FormButton from '@/components/ui/button.vue'; @@ -27,81 +27,60 @@ import { menuDef } from '@/menu'; import { defaultStore } from '@/store'; import * as symbols from '@/symbols'; import { unisonReload } from '@/scripts/unison-reload'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormButton, - FormTextarea, - FormRadios, - }, +const items = ref(defaultStore.state.menu.join('\n')); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.menu, - icon: 'fas fa-list-ul', - bg: 'var(--bg)', - }, - menuDef: menuDef, - items: defaultStore.state.menu.join('\n'), - } - }, +const split = computed(() => items.value.trim().split('\n').filter(x => x.trim() !== '')); +const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay')); - computed: { - splited(): string[] { - return this.items.trim().split('\n').filter(x => x.trim() !== ''); - }, +async function reloadAsk() { + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting + }); + if (canceled) return; - menuDisplay: defaultStore.makeGetterSetter('menuDisplay') - }, + unisonReload(); +} - watch: { - menuDisplay() { - this.reloadAsk(); - }, +async function addItem() { + const menu = Object.keys(menuDef).filter(k => !defaultStore.state.menu.includes(k)); + const { canceled, result: item } = await os.select({ + title: i18n.ts.addItem, + items: [...menu.map(k => ({ + value: k, text: i18n.ts[menuDef[k].title] + })), { + value: '-', text: i18n.ts.divider + }] + }); + if (canceled) return; + items.value = [...split.value, item].join('\n'); +} - items() { - this.save(); - }, - }, +async function save() { + defaultStore.set('menu', split.value); + await reloadAsk(); +} - methods: { - async addItem() { - const menu = Object.keys(this.menuDef).filter(k => !this.$store.state.menu.includes(k)); - const { canceled, result: item } = await os.select({ - title: this.$ts.addItem, - items: [...menu.map(k => ({ - value: k, text: this.$ts[this.menuDef[k].title] - })), ...[{ - value: '-', text: this.$ts.divider - }]] - }); - if (canceled) return; - this.items = [...this.splited, item].join('\n'); - }, +function reset() { + defaultStore.reset('menu'); + items.value = defaultStore.state.menu.join('\n'); +} - save() { - this.$store.set('menu', this.splited); - this.reloadAsk(); - }, - - reset() { - this.$store.reset('menu'); - this.items = this.$store.state.menu.join('\n'); - }, +watch(items, async () => { + await save(); +}); - async reloadAsk() { - const { canceled } = await os.confirm({ - type: 'info', - text: this.$ts.reloadToApplySetting, - showCancelButton: true - }); - if (canceled) return; +watch(menuDisplay, async () => { + await reloadAsk(); +}); - unisonReload(); - } - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.menu, + icon: 'fas fa-list-ul', + bg: 'var(--bg)', + } }); </script> diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue index 12171530bb..b8fff95a8d 100644 --- a/packages/client/src/pages/settings/notifications.vue +++ b/packages/client/src/pages/settings/notifications.vue @@ -1,71 +1,59 @@ <template> <div class="_formRoot"> - <FormLink class="_formBlock" @click="configure"><template #icon><i class="fas fa-cog"></i></template>{{ $ts.notificationSetting }}</FormLink> + <FormLink class="_formBlock" @click="configure"><template #icon><i class="fas fa-cog"></i></template>{{ i18n.ts.notificationSetting }}</FormLink> <FormSection> - <FormLink class="_formBlock" @click="readAllNotifications">{{ $ts.markAsReadAllNotifications }}</FormLink> - <FormLink class="_formBlock" @click="readAllUnreadNotes">{{ $ts.markAsReadAllUnreadNotes }}</FormLink> - <FormLink class="_formBlock" @click="readAllMessagingMessages">{{ $ts.markAsReadAllTalkMessages }}</FormLink> + <FormLink class="_formBlock" @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</FormLink> + <FormLink class="_formBlock" @click="readAllUnreadNotes">{{ i18n.ts.markAsReadAllUnreadNotes }}</FormLink> + <FormLink class="_formBlock" @click="readAllMessagingMessages">{{ i18n.ts.markAsReadAllTalkMessages }}</FormLink> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent, defineExpose } from 'vue'; import FormButton from '@/components/ui/button.vue'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import { notificationTypes } from 'misskey-js'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormLink, - FormButton, - FormSection, - }, +async function readAllUnreadNotes() { + await os.api('i/read-all-unread-notes'); +} - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.notifications, - icon: 'fas fa-bell', - bg: 'var(--bg)', - }, - } - }, - - methods: { - readAllUnreadNotes() { - os.api('i/read-all-unread-notes'); - }, +async function readAllMessagingMessages() { + await os.api('i/read-all-messaging-messages'); +} - readAllMessagingMessages() { - os.api('i/read-all-messaging-messages'); - }, +async function readAllNotifications() { + await os.api('notifications/mark-all-as-read'); +} - readAllNotifications() { - os.api('notifications/mark-all-as-read'); - }, +function configure() { + const includingTypes = notificationTypes.filter(x => !$i!.mutingNotificationTypes.includes(x)); + os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), { + includingTypes, + showGlobalToggle: false, + }, { + done: async (res) => { + const { includingTypes: value } = res; + await os.apiWithDialog('i/update', { + mutingNotificationTypes: notificationTypes.filter(x => !value.includes(x)), + }).then(i => { + $i!.mutingNotificationTypes = i.mutingNotificationTypes; + }); + } + }, 'closed'); +} - configure() { - const includingTypes = notificationTypes.filter(x => !this.$i.mutingNotificationTypes.includes(x)); - os.popup(import('@/components/notification-setting-window.vue'), { - includingTypes, - showGlobalToggle: false, - }, { - done: async (res) => { - const { includingTypes: value } = res; - await os.apiWithDialog('i/update', { - mutingNotificationTypes: notificationTypes.filter(x => !value.includes(x)), - }).then(i => { - this.$i.mutingNotificationTypes = i.mutingNotificationTypes; - }); - } - }, 'closed'); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.notifications, + icon: 'fas fa-bell', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/other.vue b/packages/client/src/pages/settings/other.vue index a9903acc7e..82e174a5b4 100644 --- a/packages/client/src/pages/settings/other.vue +++ b/packages/client/src/pages/settings/other.vue @@ -1,66 +1,44 @@ <template> <div class="_formRoot"> - <FormSwitch :value="$i.injectFeaturedNote" class="_formBlock" @update:modelValue="onChangeInjectFeaturedNote"> - {{ $ts.showFeaturedNotesInTimeline }} + <FormSwitch v-model="$i.injectFeaturedNote" class="_formBlock" @update:modelValue="onChangeInjectFeaturedNote"> + {{ i18n.ts.showFeaturedNotesInTimeline }} </FormSwitch> <!-- - <FormSwitch v-model="reportError" class="_formBlock">{{ $ts.sendErrorReports }}<template #caption>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch> + <FormSwitch v-model="reportError" class="_formBlock">{{ i18n.ts.sendErrorReports }}<template #caption>{{ i18n.ts.sendErrorReportsDescription }}</template></FormSwitch> --> - <FormLink to="/settings/account-info" class="_formBlock">{{ $ts.accountInfo }}</FormLink> + <FormLink to="/settings/account-info" class="_formBlock">{{ i18n.ts.accountInfo }}</FormLink> - <FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ $ts.closeAccount }}</FormLink> + <FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ i18n.ts.closeAccount }}</FormLink> </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; -import FormSection from '@/components/form/section.vue'; import FormLink from '@/components/form/link.vue'; import * as os from '@/os'; -import { debug } from '@/config'; import { defaultStore } from '@/store'; -import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - FormSwitch, - FormLink, - }, +const reportError = computed(defaultStore.makeGetterSetter('reportError')); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.other, - icon: 'fas fa-ellipsis-h', - bg: 'var(--bg)', - }, - debug, - } - }, +function onChangeInjectFeaturedNote(v) { + os.api('i/update', { + injectFeaturedNote: v + }).then((i) => { + $i!.injectFeaturedNote = i.injectFeaturedNote; + }); +} - computed: { - reportError: defaultStore.makeGetterSetter('reportError'), - }, - - methods: { - changeDebug(v) { - console.log(v); - localStorage.setItem('debug', v.toString()); - unisonReload(); - }, - - onChangeInjectFeaturedNote(v) { - os.api('i/update', { - injectFeaturedNote: v - }); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.other, + icon: 'fas fa-ellipsis-h', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue index d35d20d17a..96c0abfd99 100644 --- a/packages/client/src/pages/settings/plugin.install.vue +++ b/packages/client/src/pages/settings/plugin.install.vue @@ -1,19 +1,19 @@ <template> <div class="_formRoot"> - <FormInfo warn class="_formBlock">{{ $ts._plugin.installWarn }}</FormInfo> + <FormInfo warn class="_formBlock">{{ i18n.ts._plugin.installWarn }}</FormInfo> <FormTextarea v-model="code" tall class="_formBlock"> - <template #label>{{ $ts.code }}</template> + <template #label>{{ i18n.ts.code }}</template> </FormTextarea> <div class="_formBlock"> - <FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton> + <FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ i18n.ts.install }}</FormButton> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, defineAsyncComponent, nextTick, ref } from 'vue'; import { AiScript, parse } from '@syuilo/aiscript'; import { serialize } from '@syuilo/aiscript/built/serializer'; import { v4 as uuid } from 'uuid'; @@ -23,111 +23,101 @@ import FormInfo from '@/components/ui/info.vue'; import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; import { unisonReload } from '@/scripts/unison-reload'; +import { i18n } from '@/i18n'; import * as symbols from '@/symbols'; -export default defineComponent({ - components: { - FormTextarea, - FormButton, - FormInfo, - }, +const code = ref(null); - emits: ['info'], +function installPlugin({ id, meta, ast, token }) { + ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({ + ...meta, + id, + active: true, + configData: {}, + token: token, + ast: ast + })); +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._plugin.install, - icon: 'fas fa-download', - bg: 'var(--bg)', - }, - code: null, - } - }, +async function install() { + let ast; + try { + ast = parse(code.value); + } catch (err) { + os.alert({ + type: 'error', + text: 'Syntax error :(' + }); + return; + } - methods: { - installPlugin({ id, meta, ast, token }) { - ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({ - ...meta, - id, - active: true, - configData: {}, - token: token, - ast: ast - })); - }, + const meta = AiScript.collectMetadata(ast); + if (meta == null) { + os.alert({ + type: 'error', + text: 'No metadata found :(' + }); + return; + } - async install() { - let ast; - try { - ast = parse(this.code); - } catch (e) { - os.alert({ - type: 'error', - text: 'Syntax error :(' - }); - return; - } - const meta = AiScript.collectMetadata(ast); - if (meta == null) { - os.alert({ - type: 'error', - text: 'No metadata found :(' - }); - return; - } - const data = meta.get(null); - if (data == null) { - os.alert({ - type: 'error', - text: 'No metadata found :(' - }); - return; - } - const { name, version, author, description, permissions, config } = data; - if (name == null || version == null || author == null) { - os.alert({ - type: 'error', - text: 'Required property not found :(' + const metadata = meta.get(null); + if (metadata == null) { + os.alert({ + type: 'error', + text: 'No metadata found :(' + }); + return; + } + + const { name, version, author, description, permissions, config } = metadata; + if (name == null || version == null || author == null) { + os.alert({ + type: 'error', + text: 'Required property not found :(' + }); + return; + } + + const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => { + os.popup(defineAsyncComponent(() => import('@/components/token-generate-window.vue')), { + title: i18n.ts.tokenRequested, + information: i18n.ts.pluginTokenRequestedDescription, + initialName: name, + initialPermissions: permissions + }, { + done: async result => { + const { name, permissions } = result; + const { token } = await os.api('miauth/gen-token', { + session: null, + name: name, + permission: permissions, }); - return; + res(token); } + }, 'closed'); + }); - const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => { - os.popup(import('@/components/token-generate-window.vue'), { - title: this.$ts.tokenRequested, - information: this.$ts.pluginTokenRequestedDescription, - initialName: name, - initialPermissions: permissions - }, { - done: async result => { - const { name, permissions } = result; - const { token } = await os.api('miauth/gen-token', { - session: null, - name: name, - permission: permissions, - }); - - res(token); - } - }, 'closed'); - }); + installPlugin({ + id: uuid(), + meta: { + name, version, author, description, permissions, config + }, + token, + ast: serialize(ast) + }); - this.installPlugin({ - id: uuid(), - meta: { - name, version, author, description, permissions, config - }, - token, - ast: serialize(ast) - }); + os.success(); - os.success(); + nextTick(() => { + unisonReload(); + }); +} - this.$nextTick(() => { - unisonReload(); - }); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts._plugin.install, + icon: 'fas fa-download', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/plugin.vue b/packages/client/src/pages/settings/plugin.vue index 7a3ab9d152..873a022cbc 100644 --- a/packages/client/src/pages/settings/plugin.vue +++ b/packages/client/src/pages/settings/plugin.vue @@ -1,38 +1,38 @@ <template> <div class="_formRoot"> - <FormLink to="/settings/plugin/install"><template #icon><i class="fas fa-download"></i></template>{{ $ts._plugin.install }}</FormLink> + <FormLink to="/settings/plugin/install"><template #icon><i class="fas fa-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink> <FormSection> - <template #label>{{ $ts.manage }}</template> + <template #label>{{ i18n.ts.manage }}</template> <div v-for="plugin in plugins" :key="plugin.id" class="_formBlock _panel" style="padding: 20px;"> <span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span> - <FormSwitch class="_formBlock" :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch> + <FormSwitch class="_formBlock" :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</FormSwitch> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.author }}</template> + <template #key>{{ i18n.ts.author }}</template> <template #value>{{ plugin.author }}</template> </MkKeyValue> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.description }}</template> + <template #key>{{ i18n.ts.description }}</template> <template #value>{{ plugin.description }}</template> </MkKeyValue> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.permission }}</template> + <template #key>{{ i18n.ts.permission }}</template> <template #value>{{ plugin.permission }}</template> </MkKeyValue> <div style="display: flex; gap: var(--margin); flex-wrap: wrap;"> - <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton> - <MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</MkButton> + <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ i18n.ts.settings }}</MkButton> + <MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.uninstall }}</MkButton> </div> </div> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, nextTick, ref } from 'vue'; import FormLink from '@/components/form/link.vue'; import FormSwitch from '@/components/form/switch.vue'; import FormSection from '@/components/form/section.vue'; @@ -41,67 +41,54 @@ import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; import * as symbols from '@/symbols'; +import { unisonReload } from '@/scripts/unison-reload'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormLink, - FormSwitch, - FormSection, - MkButton, - MkKeyValue, - }, +const plugins = ref(ColdDeviceStorage.get('plugins')); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.plugins, - icon: 'fas fa-plug', - bg: 'var(--bg)', - }, - plugins: ColdDeviceStorage.get('plugins'), - } - }, +function uninstall(plugin) { + ColdDeviceStorage.set('plugins', plugins.value.filter(x => x.id !== plugin.id)); + os.success(); + nextTick(() => { + unisonReload(); + }); +} - methods: { - uninstall(plugin) { - ColdDeviceStorage.set('plugins', this.plugins.filter(x => x.id !== plugin.id)); - os.success(); - this.$nextTick(() => { - unisonReload(); - }); - }, +// TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする +async function config(plugin) { + const config = plugin.config; + for (const key in plugin.configData) { + config[key].default = plugin.configData[key]; + } - // TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする - async config(plugin) { - const config = plugin.config; - for (const key in plugin.configData) { - config[key].default = plugin.configData[key]; - } + const { canceled, result } = await os.form(plugin.name, config); + if (canceled) return; - const { canceled, result } = await os.form(plugin.name, config); - if (canceled) return; + const coldPlugins = ColdDeviceStorage.get('plugins'); + coldPlugins.find(p => p.id === plugin.id)!.configData = result; + ColdDeviceStorage.set('plugins', coldPlugins); - const plugins = ColdDeviceStorage.get('plugins'); - plugins.find(p => p.id === plugin.id).configData = result; - ColdDeviceStorage.set('plugins', plugins); + nextTick(() => { + location.reload(); + }); +} - this.$nextTick(() => { - location.reload(); - }); - }, +function changeActive(plugin, active) { + const coldPlugins = ColdDeviceStorage.get('plugins'); + coldPlugins.find(p => p.id === plugin.id)!.active = active; + ColdDeviceStorage.set('plugins', coldPlugins); - changeActive(plugin, active) { - const plugins = ColdDeviceStorage.get('plugins'); - plugins.find(p => p.id === plugin.id).active = active; - ColdDeviceStorage.set('plugins', plugins); + nextTick(() => { + location.reload(); + }); +} - this.$nextTick(() => { - location.reload(); - }); - } - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.plugins, + icon: 'fas fa-plug', + bg: 'var(--bg)', + } }); </script> diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue index e991d725b6..b64dc93cc7 100644 --- a/packages/client/src/pages/settings/profile.vue +++ b/packages/client/src/pages/settings/profile.vue @@ -62,7 +62,7 @@ </template> <script lang="ts" setup> -import { defineComponent, reactive, watch } from 'vue'; +import { reactive, watch } from 'vue'; import MkButton from '@/components/ui/button.vue'; import FormInput from '@/components/form/input.vue'; import FormTextarea from '@/components/form/textarea.vue'; @@ -132,8 +132,21 @@ function save() { function changeAvatar(ev) { selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => { + let originalOrCropped = file; + + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.t('cropImageAsk'), + }); + + if (!canceled) { + originalOrCropped = await os.cropImage(file, { + aspectRatio: 1, + }); + } + const i = await os.apiWithDialog('i/update', { - avatarId: file.id, + avatarId: originalOrCropped.id, }); $i.avatarId = i.avatarId; $i.avatarUrl = i.avatarUrl; @@ -142,8 +155,21 @@ function changeAvatar(ev) { function changeBanner(ev) { selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => { + let originalOrCropped = file; + + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.t('cropImageAsk'), + }); + + if (!canceled) { + originalOrCropped = await os.cropImage(file, { + aspectRatio: 2, + }); + } + const i = await os.apiWithDialog('i/update', { - bannerId: file.id, + bannerId: originalOrCropped.id, }); $i.bannerId = i.bannerId; $i.bannerUrl = i.bannerUrl; diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue index a188ba353d..963ac81dfa 100644 --- a/packages/client/src/pages/settings/reaction.vue +++ b/packages/client/src/pages/settings/reaction.vue @@ -54,7 +54,7 @@ </template> <script lang="ts" setup> -import { watch } from 'vue'; +import { defineAsyncComponent, watch } from 'vue'; import XDraggable from 'vuedraggable'; import FormInput from '@/components/form/input.vue'; import FormRadios from '@/components/form/radios.vue'; @@ -88,7 +88,7 @@ function remove(reaction, ev: MouseEvent) { } function preview(ev: MouseEvent) { - os.popup(import('@/components/emoji-picker-dialog.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), { asReactionPicker: true, src: ev.currentTarget ?? ev.target, }, {}, 'closed'); diff --git a/packages/client/src/pages/settings/security.vue b/packages/client/src/pages/settings/security.vue index 6fb3f1c413..401648790a 100644 --- a/packages/client/src/pages/settings/security.vue +++ b/packages/client/src/pages/settings/security.vue @@ -1,17 +1,17 @@ <template> <div class="_formRoot"> <FormSection> - <template #label>{{ $ts.password }}</template> - <FormButton primary @click="change()">{{ $ts.changePassword }}</FormButton> + <template #label>{{ i18n.ts.password }}</template> + <FormButton primary @click="change()">{{ i18n.ts.changePassword }}</FormButton> </FormSection> <FormSection> - <template #label>{{ $ts.twoStepAuthentication }}</template> + <template #label>{{ i18n.ts.twoStepAuthentication }}</template> <X2fa/> </FormSection> <FormSection> - <template #label>{{ $ts.signinHistory }}</template> + <template #label>{{ i18n.ts.signinHistory }}</template> <MkPagination :pagination="pagination"> <template v-slot="{items}"> <div> @@ -30,15 +30,15 @@ <FormSection> <FormSlot> - <FormButton danger @click="regenerateToken"><i class="fas fa-sync-alt"></i> {{ $ts.regenerateLoginToken }}</FormButton> - <template #caption>{{ $ts.regenerateLoginTokenDescription }}</template> + <FormButton danger @click="regenerateToken"><i class="fas fa-sync-alt"></i> {{ i18n.ts.regenerateLoginToken }}</FormButton> + <template #caption>{{ i18n.ts.regenerateLoginTokenDescription }}</template> </FormSlot> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose } from 'vue'; import FormSection from '@/components/form/section.vue'; import FormSlot from '@/components/form/slot.vue'; import FormButton from '@/components/ui/button.vue'; @@ -46,77 +46,63 @@ import MkPagination from '@/components/ui/pagination.vue'; import X2fa from './2fa.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - FormButton, - MkPagination, - FormSlot, - X2fa, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.security, - icon: 'fas fa-lock', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'i/signin-history' as const, - limit: 5, - }, - } - }, +const pagination = { + endpoint: 'i/signin-history' as const, + limit: 5, +}; - methods: { - async change() { - const { canceled: canceled1, result: currentPassword } = await os.inputText({ - title: this.$ts.currentPassword, - type: 'password' - }); - if (canceled1) return; +async function change() { + const { canceled: canceled1, result: currentPassword } = await os.inputText({ + title: i18n.ts.currentPassword, + type: 'password' + }); + if (canceled1) return; - const { canceled: canceled2, result: newPassword } = await os.inputText({ - title: this.$ts.newPassword, - type: 'password' - }); - if (canceled2) return; + const { canceled: canceled2, result: newPassword } = await os.inputText({ + title: i18n.ts.newPassword, + type: 'password' + }); + if (canceled2) return; - const { canceled: canceled3, result: newPassword2 } = await os.inputText({ - title: this.$ts.newPasswordRetype, - type: 'password' - }); - if (canceled3) return; + const { canceled: canceled3, result: newPassword2 } = await os.inputText({ + title: i18n.ts.newPasswordRetype, + type: 'password' + }); + if (canceled3) return; - if (newPassword !== newPassword2) { - os.alert({ - type: 'error', - text: this.$ts.retypedNotMatch - }); - return; - } - - os.apiWithDialog('i/change-password', { - currentPassword, - newPassword - }); - }, + if (newPassword !== newPassword2) { + os.alert({ + type: 'error', + text: i18n.ts.retypedNotMatch + }); + return; + } + + os.apiWithDialog('i/change-password', { + currentPassword, + newPassword + }); +} + +function regenerateToken() { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.api('i/regenerate_token', { + password: password + }); + }); +} - regenerateToken() { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/regenerate_token', { - password: password - }); - }); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.security, + icon: 'fas fa-lock', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/sounds.vue b/packages/client/src/pages/settings/sounds.vue index 490a1b5514..d01e87c1f8 100644 --- a/packages/client/src/pages/settings/sounds.vue +++ b/packages/client/src/pages/settings/sounds.vue @@ -1,24 +1,24 @@ <template> <div class="_formRoot"> <FormRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :text-converter="(v) => `${Math.floor(v * 100)}%`" class="_formBlock"> - <template #label>{{ $ts.masterVolume }}</template> + <template #label>{{ i18n.ts.masterVolume }}</template> </FormRange> <FormSection> - <template #label>{{ $ts.sounds }}</template> + <template #label>{{ i18n.ts.sounds }}</template> <FormLink v-for="type in Object.keys(sounds)" :key="type" style="margin-bottom: 8px;" @click="edit(type)"> {{ $t('_sfx.' + type) }} - <template #suffix>{{ sounds[type].type || $ts.none }}</template> + <template #suffix>{{ sounds[type].type || i18n.ts.none }}</template> <template #suffixIcon><i class="fas fa-chevron-down"></i></template> </FormLink> </FormSection> - <FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ $ts.default }}</FormButton> + <FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ i18n.ts.default }}</FormButton> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, ref } from 'vue'; import FormRange from '@/components/form/range.vue'; import FormButton from '@/components/ui/button.vue'; import FormLink from '@/components/form/link.vue'; @@ -27,6 +27,28 @@ import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; import { playFile } from '@/scripts/sound'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; + +const masterVolume = computed({ + get: () => { + return ColdDeviceStorage.get('sound_masterVolume'); + }, + set: (value) => { + ColdDeviceStorage.set('sound_masterVolume', value); + } +}); + +const volumeIcon = computed(() => masterVolume.value === 0 ? 'fas fa-volume-mute' : 'fas fa-volume-up'); + +const sounds = ref({ + note: ColdDeviceStorage.get('sound_note'), + noteMy: ColdDeviceStorage.get('sound_noteMy'), + notification: ColdDeviceStorage.get('sound_notification'), + chat: ColdDeviceStorage.get('sound_chat'), + chatBg: ColdDeviceStorage.get('sound_chatBg'), + antenna: ColdDeviceStorage.get('sound_antenna'), + channel: ColdDeviceStorage.get('sound_channel'), +}); const soundsTypes = [ null, @@ -55,94 +77,58 @@ const soundsTypes = [ 'noizenecio/kick_gaba2', ]; -export default defineComponent({ - components: { - FormLink, - FormButton, - FormRange, - FormSection, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.sounds, - icon: 'fas fa-music', - bg: 'var(--bg)', - }, - sounds: {}, - } - }, - - computed: { - masterVolume: { // TODO: (外部)関数にcomputedを使うのはアレなので直す - get() { return ColdDeviceStorage.get('sound_masterVolume'); }, - set(value) { ColdDeviceStorage.set('sound_masterVolume', value); } +async function edit(type) { + const { canceled, result } = await os.form(i18n.t('_sfx.' + type), { + type: { + type: 'enum', + enum: soundsTypes.map(x => ({ + value: x, + label: x == null ? i18n.ts.none : x, + })), + label: i18n.ts.sound, + default: sounds.value[type].type, + }, + volume: { + type: 'range', + mim: 0, + max: 1, + step: 0.05, + textConverter: (v) => `${Math.floor(v * 100)}%`, + label: i18n.ts.volume, + default: sounds.value[type].volume }, - volumeIcon() { - return this.masterVolume === 0 ? 'fas fa-volume-mute' : 'fas fa-volume-up'; + listen: { + type: 'button', + content: i18n.ts.listen, + action: (_, values) => { + playFile(values.type, values.volume); + } } - }, + }); + if (canceled) return; - created() { - this.sounds.note = ColdDeviceStorage.get('sound_note'); - this.sounds.noteMy = ColdDeviceStorage.get('sound_noteMy'); - this.sounds.notification = ColdDeviceStorage.get('sound_notification'); - this.sounds.chat = ColdDeviceStorage.get('sound_chat'); - this.sounds.chatBg = ColdDeviceStorage.get('sound_chatBg'); - this.sounds.antenna = ColdDeviceStorage.get('sound_antenna'); - this.sounds.channel = ColdDeviceStorage.get('sound_channel'); - }, + const v = { + type: result.type, + volume: result.volume, + }; - methods: { - async edit(type) { - const { canceled, result } = await os.form(this.$t('_sfx.' + type), { - type: { - type: 'enum', - enum: soundsTypes.map(x => ({ - value: x, - label: x == null ? this.$ts.none : x, - })), - label: this.$ts.sound, - default: this.sounds[type].type, - }, - volume: { - type: 'range', - mim: 0, - max: 1, - step: 0.05, - textConverter: (v) => `${Math.floor(v * 100)}%`, - label: this.$ts.volume, - default: this.sounds[type].volume - }, - listen: { - type: 'button', - content: this.$ts.listen, - action: (_, values) => { - playFile(values.type, values.volume); - } - } - }); - if (canceled) return; + ColdDeviceStorage.set('sound_' + type, v); + sounds.value[type] = v; +} - const v = { - type: result.type, - volume: result.volume, - }; - - ColdDeviceStorage.set('sound_' + type, v); - this.sounds[type] = v; - }, +function reset() { + for (const sound of Object.keys(sounds.value)) { + const v = ColdDeviceStorage.default['sound_' + sound]; + ColdDeviceStorage.set('sound_' + sound, v); + sounds.value[sound] = v; + } +} - reset() { - for (const sound of Object.keys(this.sounds)) { - const v = ColdDeviceStorage.default['sound_' + sound]; - ColdDeviceStorage.set('sound_' + sound, v); - this.sounds[sound] = v; - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.sounds, + icon: 'fas fa-music', + 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 2d3514342e..25fa6c012b 100644 --- a/packages/client/src/pages/settings/theme.install.vue +++ b/packages/client/src/pages/settings/theme.install.vue @@ -13,7 +13,7 @@ <script lang="ts" setup> import { } from 'vue'; -import * as JSON5 from 'json5'; +import JSON5 from 'json5'; import FormTextarea from '@/components/form/textarea.vue'; import FormButton from '@/components/ui/button.vue'; import { applyTheme, validateTheme } from '@/scripts/theme'; @@ -29,7 +29,7 @@ function parseThemeCode(code: string) { try { theme = JSON5.parse(code); - } catch (e) { + } catch (err) { os.alert({ type: 'error', text: i18n.ts._theme.invalid diff --git a/packages/client/src/pages/settings/theme.manage.vue b/packages/client/src/pages/settings/theme.manage.vue index a1e849b540..94b2d24455 100644 --- a/packages/client/src/pages/settings/theme.manage.vue +++ b/packages/client/src/pages/settings/theme.manage.vue @@ -1,95 +1,77 @@ <template> <div class="_formRoot"> <FormSelect v-model="selectedThemeId" class="_formBlock"> - <template #label>{{ $ts.theme }}</template> - <optgroup :label="$ts._theme.installedThemes"> + <template #label>{{ i18n.ts.theme }}</template> + <optgroup :label="i18n.ts._theme.installedThemes"> <option v-for="x in installedThemes" :key="x.id" :value="x.id">{{ x.name }}</option> </optgroup> - <optgroup :label="$ts._theme.builtinThemes"> + <optgroup :label="i18n.ts._theme.builtinThemes"> <option v-for="x in builtinThemes" :key="x.id" :value="x.id">{{ x.name }}</option> </optgroup> </FormSelect> <template v-if="selectedTheme"> - <FormInput readonly :modelValue="selectedTheme.author" class="_formBlock"> - <template #label>{{ $ts.author }}</template> + <FormInput readonly :model-value="selectedTheme.author" class="_formBlock"> + <template #label>{{ i18n.ts.author }}</template> </FormInput> - <FormTextarea v-if="selectedTheme.desc" readonly :modelValue="selectedTheme.desc" class="_formBlock"> - <template #label>{{ $ts._theme.description }}</template> + <FormTextarea v-if="selectedTheme.desc" readonly :model-value="selectedTheme.desc" class="_formBlock"> + <template #label>{{ i18n.ts._theme.description }}</template> </FormTextarea> - <FormTextarea readonly tall :modelValue="selectedThemeCode" class="_formBlock"> - <template #label>{{ $ts._theme.code }}</template> - <template #caption><button class="_textButton" @click="copyThemeCode()">{{ $ts.copy }}</button></template> + <FormTextarea readonly tall :model-value="selectedThemeCode" class="_formBlock"> + <template #label>{{ i18n.ts._theme.code }}</template> + <template #caption><button class="_textButton" @click="copyThemeCode()">{{ i18n.ts.copy }}</button></template> </FormTextarea> - <FormButton v-if="!builtinThemes.some(t => t.id == selectedTheme.id)" class="_formBlock" danger @click="uninstall()"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</FormButton> + <FormButton v-if="!builtinThemes.some(t => t.id == selectedTheme.id)" class="_formBlock" danger @click="uninstall()"><i class="fas fa-trash-alt"></i> {{ i18n.ts.uninstall }}</FormButton> </template> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as JSON5 from 'json5'; +<script lang="ts" setup> +import { computed, defineExpose, ref } from 'vue'; +import JSON5 from 'json5'; import FormTextarea from '@/components/form/textarea.vue'; import FormSelect from '@/components/form/select.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; -import { Theme, builtinThemes } from '@/scripts/theme'; +import { Theme, getBuiltinThemesRef } from '@/scripts/theme'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; import { getThemes, removeTheme } from '@/theme-store'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormTextarea, - FormSelect, - FormInput, - FormButton, - }, +const installedThemes = ref(getThemes()); +const builtinThemes = getBuiltinThemesRef(); +const selectedThemeId = ref(null); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._theme.manage, - icon: 'fas fa-folder-open', - bg: 'var(--bg)', - }, - installedThemes: getThemes(), - builtinThemes, - selectedThemeId: null, - } - }, +const themes = computed(() => [ ...installedThemes.value, ...builtinThemes.value ]); - computed: { - themes(): Theme[] { - return this.builtinThemes.concat(this.installedThemes); - }, - - selectedTheme() { - if (this.selectedThemeId == null) return null; - return this.themes.find(x => x.id === this.selectedThemeId); - }, +const selectedTheme = computed(() => { + if (selectedThemeId.value == null) return null; + return themes.value.find(x => x.id === selectedThemeId.value); +}); + +const selectedThemeCode = computed(() => { + if (selectedTheme.value == null) return null; + return JSON5.stringify(selectedTheme.value, null, '\t'); +}); - selectedThemeCode() { - if (this.selectedTheme == null) return null; - return JSON5.stringify(this.selectedTheme, null, '\t'); - }, - }, +function copyThemeCode() { + copyToClipboard(selectedThemeCode.value); + os.success(); +} - methods: { - copyThemeCode() { - copyToClipboard(this.selectedThemeCode); - os.success(); - }, +function uninstall() { + removeTheme(selectedTheme.value as Theme); + installedThemes.value = installedThemes.value.filter(t => t.id !== selectedThemeId.value); + selectedThemeId.value = null; + os.success(); +} - uninstall() { - removeTheme(this.selectedTheme); - this.installedThemes = this.installedThemes.filter(t => t.id !== this.selectedThemeId); - this.selectedThemeId = null; - os.success(); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts._theme.manage, + icon: 'fas fa-folder-open', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue index d134a092b6..5e7ffcff4b 100644 --- a/packages/client/src/pages/settings/theme.vue +++ b/packages/client/src/pages/settings/theme.vue @@ -85,116 +85,94 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent, onActivated, onMounted, ref, watch } from 'vue'; -import * as JSON5 from 'json5'; +<script lang="ts" setup> +import { computed, onActivated, ref, watch } from 'vue'; +import JSON5 from 'json5'; import FormSwitch from '@/components/form/switch.vue'; import FormSelect from '@/components/form/select.vue'; -import FormGroup from '@/components/form/group.vue'; import FormSection from '@/components/form/section.vue'; import FormLink from '@/components/form/link.vue'; import FormButton from '@/components/ui/button.vue'; -import { builtinThemes } from '@/scripts/theme'; +import { getBuiltinThemesRef } from '@/scripts/theme'; import { selectFile } from '@/scripts/select-file'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode'; import { ColdDeviceStorage } from '@/store'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; import { instance } from '@/instance'; -import { concat, uniqueBy } from '@/scripts/array'; +import { uniqueBy } from '@/scripts/array'; import { fetchThemes, getThemes } from '@/theme-store'; import * as symbols from '@/symbols'; -export default defineComponent({ - components: { - FormSwitch, - FormSelect, - FormGroup, - FormSection, - FormLink, - FormButton, - }, +const installedThemes = ref(getThemes()); +const builtinThemes = getBuiltinThemesRef(); +const instanceThemes = []; - emits: ['info'], +if (instance.defaultLightTheme != null) instanceThemes.push(JSON5.parse(instance.defaultLightTheme)); +if (instance.defaultDarkTheme != null) instanceThemes.push(JSON5.parse(instance.defaultDarkTheme)); - setup(props, { emit }) { - const INFO = { - title: i18n.ts.theme, - icon: 'fas fa-palette', - bg: 'var(--bg)', - }; +const themes = computed(() => uniqueBy([ ...instanceThemes, ...builtinThemes.value, ...installedThemes.value ], theme => theme.id)); +const darkThemes = computed(() => themes.value.filter(t => t.base === 'dark' || t.kind === 'dark')); +const lightThemes = computed(() => themes.value.filter(t => t.base === 'light' || t.kind === 'light')); +const darkTheme = ColdDeviceStorage.ref('darkTheme'); +const darkThemeId = computed({ + get() { + return darkTheme.value.id; + }, + set(id) { + ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id)); + } +}); +const lightTheme = ColdDeviceStorage.ref('lightTheme'); +const lightThemeId = computed({ + get() { + return lightTheme.value.id; + }, + set(id) { + ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id)); + } +}); +const darkMode = computed(defaultStore.makeGetterSetter('darkMode')); +const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode')); +const wallpaper = ref(localStorage.getItem('wallpaper')); +const themesCount = installedThemes.value.length; - const installedThemes = ref(getThemes()); - const instanceThemes = []; - if (instance.defaultLightTheme != null) instanceThemes.push(JSON5.parse(instance.defaultLightTheme)); - if (instance.defaultDarkTheme != null) instanceThemes.push(JSON5.parse(instance.defaultDarkTheme)); - const themes = computed(() => uniqueBy(instanceThemes.concat(builtinThemes.concat(installedThemes.value)), theme => theme.id)); - const darkThemes = computed(() => themes.value.filter(t => t.base === 'dark' || t.kind === 'dark')); - const lightThemes = computed(() => themes.value.filter(t => t.base === 'light' || t.kind === 'light')); - const darkTheme = ColdDeviceStorage.ref('darkTheme'); - const darkThemeId = computed({ - get() { - return darkTheme.value.id; - }, - set(id) { - ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id)) - } - }); - const lightTheme = ColdDeviceStorage.ref('lightTheme'); - const lightThemeId = computed({ - get() { - return lightTheme.value.id; - }, - set(id) { - ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id)) - } - }); - const darkMode = computed(defaultStore.makeGetterSetter('darkMode')); - const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode')); - const wallpaper = ref(localStorage.getItem('wallpaper')); - const themesCount = installedThemes.value.length; +watch(syncDeviceDarkMode, () => { + if (syncDeviceDarkMode.value) { + defaultStore.set('darkMode', isDeviceDarkmode()); + } +}); - watch(syncDeviceDarkMode, () => { - if (syncDeviceDarkMode.value) { - defaultStore.set('darkMode', isDeviceDarkmode()); - } - }); +watch(wallpaper, () => { + if (wallpaper.value == null) { + localStorage.removeItem('wallpaper'); + } else { + localStorage.setItem('wallpaper', wallpaper.value); + } + location.reload(); +}); - watch(wallpaper, () => { - if (wallpaper.value == null) { - localStorage.removeItem('wallpaper'); - } else { - localStorage.setItem('wallpaper', wallpaper.value); - } - location.reload(); - }); +onActivated(() => { + fetchThemes().then(() => { + installedThemes.value = getThemes(); + }); +}); - onActivated(() => { - fetchThemes().then(() => { - installedThemes.value = getThemes(); - }); - }); +fetchThemes().then(() => { + installedThemes.value = getThemes(); +}); - fetchThemes().then(() => { - installedThemes.value = getThemes(); - }); +function setWallpaper(event) { + selectFile(event.currentTarget ?? event.target, null).then(file => { + wallpaper.value = file.url; + }); +} - return { - [symbols.PAGE_INFO]: INFO, - darkThemes, - lightThemes, - darkThemeId, - lightThemeId, - darkMode, - syncDeviceDarkMode, - themesCount, - wallpaper, - setWallpaper(e) { - selectFile(e.currentTarget ?? e.target, null).then(file => { - wallpaper.value = file.url; - }); - }, - }; +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.theme, + icon: 'fas fa-palette', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/webhook.edit.vue b/packages/client/src/pages/settings/webhook.edit.vue index bb3a25407e..3690526b41 100644 --- a/packages/client/src/pages/settings/webhook.edit.vue +++ b/packages/client/src/pages/settings/webhook.edit.vue @@ -43,6 +43,14 @@ import * as os from '@/os'; import * as symbols from '@/symbols'; import { i18n } from '@/i18n'; +defineExpose({ + [symbols.PAGE_INFO]: { + title: 'Edit webhook', + icon: 'fas fa-bolt', + bg: 'var(--bg)', + }, +}); + const webhook = await os.api('i/webhooks/show', { webhookId: new URLSearchParams(window.location.search).get('id') }); @@ -78,12 +86,4 @@ async function save(): Promise<void> { active, }); } - -defineExpose({ - [symbols.PAGE_INFO]: { - title: 'Edit webhook', - icon: 'fas fa-bolt', - bg: 'var(--bg)', - }, -}); </script> diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue index c11707b6cf..6e1a4b2ccb 100644 --- a/packages/client/src/pages/settings/word-mute.vue +++ b/packages/client/src/pages/settings/word-mute.vue @@ -1,35 +1,35 @@ <template> <div class="_formRoot"> <MkTab v-model="tab" class="_formBlock"> - <option value="soft">{{ $ts._wordMute.soft }}</option> - <option value="hard">{{ $ts._wordMute.hard }}</option> + <option value="soft">{{ i18n.ts._wordMute.soft }}</option> + <option value="hard">{{ i18n.ts._wordMute.hard }}</option> </MkTab> <div class="_formBlock"> <div v-show="tab === 'soft'"> - <MkInfo class="_formBlock">{{ $ts._wordMute.softDescription }}</MkInfo> + <MkInfo class="_formBlock">{{ i18n.ts._wordMute.softDescription }}</MkInfo> <FormTextarea v-model="softMutedWords" class="_formBlock"> - <span>{{ $ts._wordMute.muteWords }}</span> - <template #caption>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template> + <span>{{ i18n.ts._wordMute.muteWords }}</span> + <template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template> </FormTextarea> </div> <div v-show="tab === 'hard'"> - <MkInfo class="_formBlock">{{ $ts._wordMute.hardDescription }} {{ $ts.reflectMayTakeTime }}</MkInfo> + <MkInfo class="_formBlock">{{ i18n.ts._wordMute.hardDescription }} {{ i18n.ts.reflectMayTakeTime }}</MkInfo> <FormTextarea v-model="hardMutedWords" class="_formBlock"> - <span>{{ $ts._wordMute.muteWords }}</span> - <template #caption>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template> + <span>{{ i18n.ts._wordMute.muteWords }}</span> + <template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template> </FormTextarea> <MkKeyValue v-if="hardWordMutedNotesCount != null" class="_formBlock"> - <template #key>{{ $ts._wordMute.mutedNotes }}</template> + <template #key>{{ i18n.ts._wordMute.mutedNotes }}</template> <template #value>{{ number(hardWordMutedNotesCount) }}</template> </MkKeyValue> </div> </div> - <MkButton primary inline :disabled="!changed" @click="save()"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> + <MkButton primary inline :disabled="!changed" @click="save()"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref, watch } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import MkKeyValue from '@/components/key-value.vue'; import MkButton from '@/components/ui/button.vue'; @@ -38,114 +38,90 @@ import MkTab from '@/components/tab.vue'; import * as os from '@/os'; import number from '@/filters/number'; import * as symbols from '@/symbols'; +import { defaultStore } from '@/store'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - FormTextarea, - MkKeyValue, - MkTab, - MkInfo, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.wordMute, - icon: 'fas fa-comment-slash', - bg: 'var(--bg)', - }, - tab: 'soft', - softMutedWords: '', - hardMutedWords: '', - hardWordMutedNotesCount: null, - changed: false, - } - }, +const render = (mutedWords) => mutedWords.map(x => { + if (Array.isArray(x)) { + return x.join(' '); + } else { + return x; + } +}).join('\n'); - watch: { - softMutedWords: { - handler() { - this.changed = true; - }, - deep: true - }, - hardMutedWords: { - handler() { - this.changed = true; - }, - deep: true - }, - }, +const tab = ref('soft'); +const softMutedWords = ref(render(defaultStore.state.mutedWords)); +const hardMutedWords = ref(render($i!.mutedWords)); +const hardWordMutedNotesCount = ref(null); +const changed = ref(false); - async created() { - const render = (mutedWords) => mutedWords.map(x => { - if (Array.isArray(x)) { - return x.join(' '); - } else { - return x; - } - }).join('\n'); +os.api('i/get-word-muted-notes-count', {}).then(response => { + hardWordMutedNotesCount.value = response?.count; +}); - this.softMutedWords = render(this.$store.state.mutedWords); - this.hardMutedWords = render(this.$i.mutedWords); +watch(softMutedWords, () => { + changed.value = true; +}); - this.hardWordMutedNotesCount = (await os.api('i/get-word-muted-notes-count', {})).count; - }, +watch(hardMutedWords, () => { + changed.value = true; +}); - methods: { - async save() { - const parseMutes = (mutes, tab) => { - // split into lines, remove empty lines and unnecessary whitespace - let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line != ''); +async function save() { + const parseMutes = (mutes, tab) => { + // split into lines, remove empty lines and unnecessary whitespace + let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line !== ''); - // check each line if it is a RegExp or not - for (let i = 0; i < lines.length; i++) { - const line = lines[i] - const regexp = line.match(/^\/(.+)\/(.*)$/); - if (regexp) { - // check that the RegExp is valid - try { - new RegExp(regexp[1], regexp[2]); - // note that regex lines will not be split by spaces! - } catch (err) { - // invalid syntax: do not save, do not reset changed flag - os.alert({ - type: 'error', - title: this.$ts.regexpError, - text: this.$t('regexpErrorDescription', { tab, line: i + 1 }) + "\n" + err.toString() - }); - // re-throw error so these invalid settings are not saved - throw err; - } - } else { - lines[i] = line.split(' '); - } + // check each line if it is a RegExp or not + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const regexp = line.match(/^\/(.+)\/(.*)$/); + if (regexp) { + // check that the RegExp is valid + try { + new RegExp(regexp[1], regexp[2]); + // note that regex lines will not be split by spaces! + } catch (err: any) { + // invalid syntax: do not save, do not reset changed flag + os.alert({ + type: 'error', + title: i18n.ts.regexpError, + text: i18n.t('regexpErrorDescription', { tab, line: i + 1 }) + "\n" + err.toString() + }); + // re-throw error so these invalid settings are not saved + throw err; } + } else { + lines[i] = line.split(' '); + } + } - return lines; - }; + return lines; + }; - let softMutes, hardMutes; - try { - softMutes = parseMutes(this.softMutedWords, this.$ts._wordMute.soft); - hardMutes = parseMutes(this.hardMutedWords, this.$ts._wordMute.hard); - } catch (err) { - // already displayed error message in parseMutes - return; - } + let softMutes, hardMutes; + try { + softMutes = parseMutes(softMutedWords.value, i18n.ts._wordMute.soft); + hardMutes = parseMutes(hardMutedWords.value, i18n.ts._wordMute.hard); + } catch (err) { + // already displayed error message in parseMutes + return; + } - this.$store.set('mutedWords', softMutes); - await os.api('i/update', { - mutedWords: hardMutes, - }); + defaultStore.set('mutedWords', softMutes); + await os.api('i/update', { + mutedWords: hardMutes, + }); - this.changed = false; - }, + changed.value = false; +} - number +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.wordMute, + icon: 'fas fa-comment-slash', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/share.vue b/packages/client/src/pages/share.vue index 4d77de5819..1700944f82 100644 --- a/packages/client/src/pages/share.vue +++ b/packages/client/src/pages/share.vue @@ -56,7 +56,7 @@ export default defineComponent({ localOnly: null as boolean | null, files: [] as Misskey.entities.DriveFile[], visibleUsers: [] as Misskey.entities.User[], - } + }; }, async created() { @@ -153,11 +153,11 @@ export default defineComponent({ ); } //#endregion - } catch (e) { + } catch (err) { os.alert({ type: 'error', - title: e.message, - text: e.name + title: err.message, + text: err.name }); } diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue index a53e23c1c5..2a11c07fd2 100644 --- a/packages/client/src/pages/theme-editor.vue +++ b/packages/client/src/pages/theme-editor.vue @@ -67,15 +67,17 @@ <script lang="ts" setup> import { watch } from 'vue'; import { toUnicode } from 'punycode/'; -import * as tinycolor from 'tinycolor2'; -import { v4 as uuid} from 'uuid'; -import * as JSON5 from 'json5'; +import tinycolor from 'tinycolor2'; +import { v4 as uuid } from 'uuid'; +import JSON5 from 'json5'; import FormButton from '@/components/ui/button.vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormFolder from '@/components/form/folder.vue'; -import { Theme, applyTheme, darkTheme, lightTheme } from '@/scripts/theme'; +import { Theme, applyTheme } from '@/scripts/theme'; +import lightTheme from '@/themes/_light.json5'; +import darkTheme from '@/themes/_dark.json5'; import { host } from '@/config'; import * as os from '@/os'; import { ColdDeviceStorage, defaultStore } from '@/store'; @@ -124,11 +126,11 @@ let changed = $ref(false); useLeaveGuard($$(changed)); function showPreview() { - os.pageWindow('preview'); + os.pageWindow('/preview'); } function setBgColor(color: typeof bgColors[number]) { - if (theme.base != color.kind) { + if (theme.base !== color.kind) { const base = color.kind === 'dark' ? darkTheme : lightTheme; for (const prop of Object.keys(base.props)) { if (prop === 'accent') continue; diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue index 79f00c4b44..fe3dbc3cff 100644 --- a/packages/client/src/pages/timeline.vue +++ b/packages/client/src/pages/timeline.vue @@ -20,7 +20,7 @@ <script lang="ts"> export default { name: 'MkTimelinePage', -} +}; </script> <script lang="ts" setup> diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue index 516ab4d440..54e1f13021 100644 --- a/packages/client/src/pages/user-info.vue +++ b/packages/client/src/pages/user-info.vue @@ -54,6 +54,9 @@ <FormButton v-if="user.host != null" class="_formBlock" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton> </FormSection> + <MkObjectView v-if="info && $i.isAdmin" tall :value="info"> + </MkObjectView> + <MkObjectView tall :value="user"> </MkObjectView> </div> @@ -232,10 +235,10 @@ export default defineComponent({ await os.api('admin/delete-all-files-of-a-user', { userId: this.user.id }); os.success(); }; - await process().catch(e => { + await process().catch(err => { os.alert({ type: 'error', - text: e.toString() + text: err.toString(), }); }); await this.refreshUser(); diff --git a/packages/client/src/pages/user/index.vue b/packages/client/src/pages/user/index.vue index 10a86243f9..a024dd28bc 100644 --- a/packages/client/src/pages/user/index.vue +++ b/packages/client/src/pages/user/index.vue @@ -125,7 +125,7 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent, computed } from 'vue'; -import * as age from 's-age'; +import age from 's-age'; import XUserTimeline from './index.timeline.vue'; import XNote from '@/components/note.vue'; import MkFollowButton from '@/components/follow-button.vue'; @@ -141,6 +141,7 @@ import number from '@/filters/number'; import { userPage, acct as getAcct } from '@/filters/user'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { MisskeyNavigator } from '@/scripts/navigate'; export default defineComponent({ components: { @@ -190,33 +191,34 @@ export default defineComponent({ active: this.page === 'index', title: this.$ts.overview, icon: 'fas fa-home', - onClick: () => { this.$router.push('/@' + getAcct(this.user)); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user)); }, }, ...(this.$i && (this.$i.id === this.user.id)) || this.user.publicReactions ? [{ active: this.page === 'reactions', title: this.$ts.reaction, icon: 'fas fa-laugh', - onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/reactions'); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/reactions'); }, }] : [], { active: this.page === 'clips', title: this.$ts.clips, icon: 'fas fa-paperclip', - onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/clips'); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/clips'); }, }, { active: this.page === 'pages', title: this.$ts.pages, icon: 'fas fa-file-alt', - onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/pages'); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/pages'); }, }, { active: this.page === 'gallery', title: this.$ts.gallery, icon: 'fas fa-icons', - onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/gallery'); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/gallery'); }, }], } : null), user: null, error: null, parallaxAnimationId: null, narrow: null, + mkNav: new MisskeyNavigator(), }; }, @@ -258,8 +260,8 @@ export default defineComponent({ this.user = null; os.api('users/show', Acct.parse(this.acct)).then(user => { this.user = user; - }).catch(e => { - this.error = e; + }).catch(err => { + this.error = err; }); }, diff --git a/packages/client/src/pages/welcome.setup.vue b/packages/client/src/pages/welcome.setup.vue index ec23b76e29..1a2f460283 100644 --- a/packages/client/src/pages/welcome.setup.vue +++ b/packages/client/src/pages/welcome.setup.vue @@ -41,7 +41,7 @@ export default defineComponent({ password: '', submitting: false, host, - } + }; }, methods: { diff --git a/packages/client/src/pages/welcome.timeline.vue b/packages/client/src/pages/welcome.timeline.vue index 38a85f67b1..bec9481ffd 100644 --- a/packages/client/src/pages/welcome.timeline.vue +++ b/packages/client/src/pages/welcome.timeline.vue @@ -39,7 +39,7 @@ export default defineComponent({ return { notes: [], isScrolling: false, - } + }; }, created() { |