Merge remote-tracking branch 'misskey-dev/master' into develop
This commit is contained in:
@ -150,6 +150,7 @@ const patrons = [
|
||||
'Weeble',
|
||||
'蝉暮せせせ',
|
||||
'ThatOneCalculator',
|
||||
'pixeldesu',
|
||||
];
|
||||
|
||||
let easterEggReady = false;
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
});
|
||||
},
|
||||
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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
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>
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
function save(announcement) {
|
||||
if (announcement.id == null) {
|
||||
os.api('admin/announcements/create', announcement).then(() => {
|
||||
os.alert({
|
||||
type: 'success',
|
||||
text: i18n.ts.saved
|
||||
});
|
||||
}).catch(err => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
add() {
|
||||
this.announcements.unshift({
|
||||
id: null,
|
||||
title: '',
|
||||
text: '',
|
||||
imageUrl: null
|
||||
} else {
|
||||
os.api('admin/announcements/update', 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
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
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);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'done', v: { deleted?: boolean, updated?: any }): void,
|
||||
(ev: 'closed'): void
|
||||
}>();
|
||||
|
||||
function ok() {
|
||||
update();
|
||||
}
|
||||
|
||||
async function update() {
|
||||
await os.apiWithDialog('admin/emoji/update', {
|
||||
id: props.emoji.id,
|
||||
name,
|
||||
category,
|
||||
aliases: aliases.split(' '),
|
||||
});
|
||||
|
||||
emit('done', {
|
||||
updated: {
|
||||
id: props.emoji.id,
|
||||
name,
|
||||
category,
|
||||
aliases: aliases.split(' '),
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
emits: ['done', 'closed'],
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
name: this.emoji.name,
|
||||
category: this.emoji.category,
|
||||
aliases: this.emoji.aliases?.join(' '),
|
||||
categories: [],
|
||||
}
|
||||
},
|
||||
async function del() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.t('removeAreYouSure', { x: name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
created() {
|
||||
os.api('meta', { detail: false }).then(({ emojis }) => {
|
||||
this.categories = unique(emojis.map((x: any) => x.category || '').filter((x: string) => x !== ''));
|
||||
os.api('admin/emoji/delete', {
|
||||
id: props.emoji.id
|
||||
}).then(() => {
|
||||
emit('done', {
|
||||
deleted: true
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
ok() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
async update() {
|
||||
await os.apiWithDialog('admin/emoji/update', {
|
||||
id: this.emoji.id,
|
||||
name: this.name,
|
||||
category: this.category,
|
||||
aliases: this.aliases.split(' '),
|
||||
});
|
||||
|
||||
this.$emit('done', {
|
||||
updated: {
|
||||
name: this.name,
|
||||
category: this.category,
|
||||
aliases: this.aliases.split(' '),
|
||||
}
|
||||
});
|
||||
this.$refs.dialog.close();
|
||||
},
|
||||
|
||||
async del() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: this.$t('removeAreYouSure', { x: this.emoji.name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
os.api('admin/emoji/delete', {
|
||||
id: this.emoji.id
|
||||
}).then(() => {
|
||||
this.$emit('done', {
|
||||
deleted: true
|
||||
});
|
||||
this.$refs.dialog.close();
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
dialog.close();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -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,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
},
|
||||
const props = defineProps<{
|
||||
fileId: string,
|
||||
}>();
|
||||
|
||||
emits: ['closed'],
|
||||
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;
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
file: null,
|
||||
info: null,
|
||||
isSensitive: false,
|
||||
};
|
||||
},
|
||||
fetch();
|
||||
|
||||
created() {
|
||||
this.fetch();
|
||||
},
|
||||
function showUser() {
|
||||
os.pageWindow(`/user-info/${file.userId}`);
|
||||
}
|
||||
|
||||
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 del() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.t('removeAreYouSure', { x: file.name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
showUser() {
|
||||
os.pageWindow(`/user-info/${this.file.userId}`);
|
||||
},
|
||||
os.apiWithDialog('drive/files/delete', {
|
||||
fileId: file.id
|
||||
});
|
||||
}
|
||||
|
||||
async del() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: this.$t('removeAreYouSure', { x: this.file.name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
os.apiWithDialog('drive/files/delete', {
|
||||
fileId: this.file.id
|
||||
});
|
||||
},
|
||||
|
||||
async toggleIsSensitive(v) {
|
||||
await os.api('drive/files/update', { fileId: this.fileId, isSensitive: v });
|
||||
this.isSensitive = v;
|
||||
},
|
||||
|
||||
bytes
|
||||
}
|
||||
});
|
||||
async function toggleIsSensitive(v) {
|
||||
await os.api('drive/files/update', { fileId: props.fileId, isSensitive: v });
|
||||
isSensitive = v;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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,298 +35,283 @@ 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,
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
initialPage?: string,
|
||||
}>();
|
||||
|
||||
provide('shouldOmitHeaderTitle', 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;
|
||||
|
||||
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: 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-laugh',
|
||||
text: i18n.ts.emojiGen,
|
||||
to: '/admin/emojigen',
|
||||
active: props.initialPage === 'emojigen',
|
||||
}, {
|
||||
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',
|
||||
}],
|
||||
}]);
|
||||
|
||||
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 'emojigen': return defineAsyncComponent(() => import('./emojigen.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'));
|
||||
}
|
||||
});
|
||||
|
||||
watch(component, () => {
|
||||
pageProps = {};
|
||||
|
||||
nextTick(() => {
|
||||
scroll(el, { top: 0 });
|
||||
});
|
||||
}, { immediate: true });
|
||||
|
||||
watch(() => props.initialPage, () => {
|
||||
if (props.initialPage == null && !narrow) {
|
||||
nav.push('/admin/overview');
|
||||
} else {
|
||||
if (props.initialPage == null) {
|
||||
INFO = indexInfo;
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
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({});
|
||||
watch(narrow, () => {
|
||||
if (props.initialPage == null && !narrow) {
|
||||
nav.push('/admin/overview');
|
||||
}
|
||||
});
|
||||
|
||||
const isEmpty = (x: any) => x == null || x == '';
|
||||
onMounted(() => {
|
||||
ro.observe(el);
|
||||
|
||||
const noMaintainerInformation = ref(false);
|
||||
const noBotProtection = ref(false);
|
||||
narrow = el.offsetWidth < NARROW_THRESHOLD;
|
||||
if (props.initialPage == null && !narrow) {
|
||||
nav.push('/admin/overview');
|
||||
}
|
||||
});
|
||||
|
||||
os.api('meta', { detail: true }).then(meta => {
|
||||
// TODO: 設定が完了しても残ったままになるので、ストリーミングでmeta更新イベントを受け取ってよしなに更新する
|
||||
noMaintainerInformation.value = isEmpty(meta.maintainerName) || isEmpty(meta.maintainerEmail);
|
||||
noBotProtection.value = !meta.enableHcaptcha && !meta.enableRecaptcha;
|
||||
onUnmounted(() => {
|
||||
ro.disconnect();
|
||||
});
|
||||
|
||||
const pageChanged = (page) => {
|
||||
if (page == null) {
|
||||
childInfo = null;
|
||||
} else {
|
||||
childInfo = page[symbols.PAGE_INFO];
|
||||
}
|
||||
};
|
||||
|
||||
const invite = () => {
|
||||
os.api('admin/invite').then(x => {
|
||||
os.alert({
|
||||
type: 'info',
|
||||
text: x.code
|
||||
});
|
||||
|
||||
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-laugh',
|
||||
text: i18n.ts.emojiGen,
|
||||
to: '/admin/emojigen',
|
||||
active: page.value === 'emojigen',
|
||||
},{
|
||||
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 'emojigen': return defineAsyncComponent(() => import('./emojigen.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'));
|
||||
}
|
||||
}).catch(err => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
watch(component, () => {
|
||||
pageProps.value = {};
|
||||
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);
|
||||
};
|
||||
|
||||
nextTick(() => {
|
||||
scroll(el.value, { top: 0 });
|
||||
});
|
||||
}, { immediate: true });
|
||||
|
||||
watch(() => props.initialPage, () => {
|
||||
if (props.initialPage == null && !narrow.value) {
|
||||
page.value = 'overview';
|
||||
} else {
|
||||
page.value = props.initialPage;
|
||||
if (props.initialPage == null) {
|
||||
INFO.value = indexInfo;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
narrow.value = el.value.offsetWidth < 800;
|
||||
if (!narrow.value) {
|
||||
page.value = 'overview';
|
||||
}
|
||||
});
|
||||
|
||||
const invite = () => {
|
||||
os.api('admin/invite').then(x => {
|
||||
os.alert({
|
||||
type: 'info',
|
||||
text: x.code
|
||||
});
|
||||
}).catch(e => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: e
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
return {
|
||||
[symbols.PAGE_INFO]: INFO,
|
||||
menuDef,
|
||||
header: {
|
||||
title: i18n.ts.controlPanel,
|
||||
},
|
||||
noMaintainerInformation,
|
||||
noBotProtection,
|
||||
page,
|
||||
narrow,
|
||||
view,
|
||||
el,
|
||||
pageChanged,
|
||||
childInfo,
|
||||
pageProps,
|
||||
component,
|
||||
invite,
|
||||
lookup,
|
||||
};
|
||||
},
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: INFO,
|
||||
header: {
|
||||
title: i18n.ts.controlPanel,
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormFolder,
|
||||
FormSecion,
|
||||
FormSuspense,
|
||||
XTwitter,
|
||||
XGithub,
|
||||
XDiscord,
|
||||
},
|
||||
let enableTwitterIntegration: boolean = $ref(false);
|
||||
let enableGithubIntegration: boolean = $ref(false);
|
||||
let enableDiscordIntegration: boolean = $ref(false);
|
||||
|
||||
emits: ['info'],
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
enableTwitterIntegration = meta.enableTwitterIntegration;
|
||||
enableGithubIntegration = meta.enableGithubIntegration;
|
||||
enableDiscordIntegration = meta.enableDiscordIntegration;
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.integration,
|
||||
icon: 'fas fa-share-alt',
|
||||
bg: 'var(--bg)',
|
||||
},
|
||||
enableTwitterIntegration: false,
|
||||
enableGithubIntegration: false,
|
||||
enableDiscordIntegration: false,
|
||||
}
|
||||
},
|
||||
|
||||
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>
|
||||
|
@ -132,7 +132,7 @@ export default defineComponent({
|
||||
overviewHeight: '1fr',
|
||||
queueHeight: '1fr',
|
||||
paused: false,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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('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/users', { limit: 2, span: 'day' }).then(chart => {
|
||||
usersComparedToThePrevDay = stats.originalUsersCount - chart.local.total[1];
|
||||
});
|
||||
|
||||
os.api('admin/server-info', {}).then(serverInfo => {
|
||||
this.serverInfo = serverInfo;
|
||||
os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => {
|
||||
notesComparedToThePrevDay = stats.originalNotesCount - chart.local.total[1];
|
||||
});
|
||||
});
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.queueStatsConnection.send('requestLog', {
|
||||
id: Math.random().toString().substr(2, 8),
|
||||
length: 200
|
||||
});
|
||||
os.api('admin/server-info').then(serverInfoResponse => {
|
||||
serverInfo = serverInfoResponse;
|
||||
});
|
||||
|
||||
nextTick(() => {
|
||||
queueStatsConnection.send('requestLog', {
|
||||
id: Math.random().toString().substr(2, 8),
|
||||
length: 200
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
beforeUnmount() {
|
||||
this.queueStatsConnection.dispose();
|
||||
},
|
||||
onBeforeUnmount(() => {
|
||||
queueStatsConnection.dispose();
|
||||
});
|
||||
|
||||
methods: {
|
||||
async showInstanceInfo(q) {
|
||||
let instance = q;
|
||||
if (typeof q === 'string') {
|
||||
instance = await os.api('federation/show-instance', {
|
||||
host: q
|
||||
});
|
||||
}
|
||||
// TODO
|
||||
},
|
||||
|
||||
bytes,
|
||||
|
||||
number,
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.dashboard,
|
||||
icon: 'fas fa-tachometer-alt',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -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'],
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
proxyAccountId = meta.proxyAccountId;
|
||||
if (proxyAccountId) {
|
||||
proxyAccount = await os.api('users/show', { userId: proxyAccountId });
|
||||
}
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.proxyAccount,
|
||||
icon: 'fas fa-ghost',
|
||||
bg: 'var(--bg)',
|
||||
},
|
||||
proxyAccount: null,
|
||||
proxyAccountId: null,
|
||||
}
|
||||
},
|
||||
function chooseProxyAccount() {
|
||||
os.selectUser().then(user => {
|
||||
proxyAccount = user;
|
||||
proxyAccountId = user.id;
|
||||
save();
|
||||
});
|
||||
}
|
||||
|
||||
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 save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
proxyAccountId: proxyAccountId,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
|
||||
chooseProxyAccount() {
|
||||
os.selectUser().then(user => {
|
||||
this.proxyAccount = user;
|
||||
this.proxyAccountId = user.id;
|
||||
this.save();
|
||||
});
|
||||
},
|
||||
|
||||
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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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();
|
||||
},
|
||||
onBeforeUnmount(() => {
|
||||
connection.dispose();
|
||||
});
|
||||
|
||||
methods: {
|
||||
clear() {
|
||||
os.confirm({
|
||||
type: 'warning',
|
||||
title: this.$ts.clearQueueConfirmTitle,
|
||||
text: this.$ts.clearQueueConfirmText,
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
|
||||
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>
|
||||
|
@ -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'],
|
||||
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
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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: '',
|
||||
}
|
||||
},
|
||||
function remove(inbox: string) {
|
||||
os.api('admin/relays/remove', {
|
||||
inbox
|
||||
}).then(() => {
|
||||
refresh();
|
||||
}).catch((err: any) => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err.message || err
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
created() {
|
||||
this.refresh();
|
||||
},
|
||||
function refresh() {
|
||||
os.api('admin/relays/list').then((relayList: any) => {
|
||||
relays = relayList;
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
});
|
||||
},
|
||||
refresh();
|
||||
|
||||
remove(inbox: string) {
|
||||
os.api('admin/relays/remove', {
|
||||
inbox
|
||||
}).then(() => {
|
||||
this.refresh();
|
||||
}).catch((e: any) => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: e.message || e
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
os.api('endpoints').then(endpoints => {
|
||||
this.endpoints = endpoints;
|
||||
});
|
||||
},
|
||||
|
||||
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);
|
||||
});
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: 'API console',
|
||||
icon: 'fas fa-terminal'
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
});
|
||||
},
|
||||
|
@ -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)));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ function getStatus(instance) {
|
||||
if (instance.isSuspended) return 'suspended';
|
||||
if (instance.isNotResponding) return 'error';
|
||||
return 'alive';
|
||||
};
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
|
@ -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}`);
|
||||
|
@ -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);
|
||||
});
|
||||
},
|
||||
|
@ -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;
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -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);
|
||||
},
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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],
|
||||
|
@ -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>
|
||||
|
@ -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() {
|
||||
|
@ -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>
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
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');
|
||||
}
|
||||
},
|
||||
|
||||
async deleteAntenna() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: this.$t('removeAreYouSure', { x: this.antenna.name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
await os.api('antennas/delete', {
|
||||
antennaId: this.antenna.id,
|
||||
});
|
||||
|
||||
os.success();
|
||||
this.$emit('deleted');
|
||||
},
|
||||
|
||||
addUser() {
|
||||
os.selectUser().then(user => {
|
||||
this.users = this.users.trim();
|
||||
this.users += '\n@' + Acct.toString(user);
|
||||
this.users = this.users.trim();
|
||||
});
|
||||
}
|
||||
userGroups = [...groups1, ...groups2];
|
||||
}
|
||||
});
|
||||
|
||||
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(' ')),
|
||||
};
|
||||
|
||||
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 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: props.antenna.id,
|
||||
});
|
||||
|
||||
os.success();
|
||||
emit('deleted');
|
||||
}
|
||||
|
||||
function addUser() {
|
||||
os.selectUser().then(user => {
|
||||
users = users.trim();
|
||||
users += '\n@' + Acct.toString(user as any);
|
||||
users = users.trim();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ export default defineComponent({
|
||||
readonly: this.readonly,
|
||||
getScriptBlockList: this.getScriptBlockList,
|
||||
getPageBlockList: this.getPageBlockList
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
props: {
|
||||
|
@ -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;
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -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('/');
|
||||
}
|
||||
});
|
||||
|
@ -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,
|
||||
},
|
||||
const code = ref('');
|
||||
const logs = ref<any[]>([]);
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.scratchpad,
|
||||
icon: 'fas fa-terminal',
|
||||
},
|
||||
code: '',
|
||||
logs: [],
|
||||
}
|
||||
},
|
||||
const saved = localStorage.getItem('scratchpad');
|
||||
if (saved) {
|
||||
code.value = saved;
|
||||
}
|
||||
|
||||
watch: {
|
||||
code() {
|
||||
localStorage.setItem('scratchpad', this.code);
|
||||
}
|
||||
},
|
||||
watch(code, () => {
|
||||
localStorage.setItem('scratchpad', code.value);
|
||||
});
|
||||
|
||||
created() {
|
||||
const saved = localStorage.getItem('scratchpad');
|
||||
if (saved) {
|
||||
this.code = 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;
|
||||
}
|
||||
}
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
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>
|
||||
|
||||
|
@ -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;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function submit() {
|
||||
os.api('i/2fa/done', {
|
||||
token: token.value
|
||||
}).then(() => {
|
||||
os.success();
|
||||
$i!.twoFactorEnabled = true;
|
||||
}).catch(err => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
submit() {
|
||||
os.api('i/2fa/done', {
|
||||
token: this.token
|
||||
}).then(() => {
|
||||
os.success();
|
||||
this.$i.twoFactorEnabled = true;
|
||||
}).catch(e => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: e
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
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();
|
||||
})
|
||||
},
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
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>
|
||||
|
@ -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
|
||||
}
|
||||
},
|
||||
onMounted(() => {
|
||||
os.api('users/stats', {
|
||||
userId: $i!.id
|
||||
}).then(response => {
|
||||
stats.value = response;
|
||||
});
|
||||
});
|
||||
|
||||
mounted() {
|
||||
os.api('users/stats', {
|
||||
userId: this.$i.id
|
||||
}).then(stats => {
|
||||
this.stats = stats;
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
number,
|
||||
bytes,
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.accountInfo,
|
||||
icon: 'fas fa-info-circle'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function addAccount(ev) {
|
||||
os.popupMenu([{
|
||||
text: i18n.ts.existingAccount,
|
||||
action: () => { addExistingAccount(); },
|
||||
}, {
|
||||
text: i18n.ts.createAccount,
|
||||
action: () => { createAccount(); },
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
function addExistingAccount() {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, {
|
||||
done: res => {
|
||||
addAccounts(res.id, res.i);
|
||||
os.success();
|
||||
},
|
||||
}, 'closed');
|
||||
}
|
||||
|
||||
addAccount(ev) {
|
||||
os.popupMenu([{
|
||||
text: this.$ts.existingAccount,
|
||||
action: () => { this.addExistingAccount(); },
|
||||
}, {
|
||||
text: this.$ts.createAccount,
|
||||
action: () => { this.createAccount(); },
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
function createAccount() {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, {
|
||||
done: res => {
|
||||
addAccounts(res.id, res.i);
|
||||
switchAccountWithToken(res.i);
|
||||
},
|
||||
}, 'closed');
|
||||
}
|
||||
|
||||
addExistingAccount() {
|
||||
os.popup(import('@/components/signin-dialog.vue'), {}, {
|
||||
done: res => {
|
||||
addAccount(res.id, res.i);
|
||||
os.success();
|
||||
},
|
||||
}, 'closed');
|
||||
},
|
||||
async function switchAccount(account: any) {
|
||||
const fetchedAccounts: any[] = await getAccounts();
|
||||
const token = fetchedAccounts.find(x => x.id === account.id).token;
|
||||
switchAccountWithToken(token);
|
||||
}
|
||||
|
||||
createAccount() {
|
||||
os.popup(import('@/components/signup-dialog.vue'), {}, {
|
||||
done: res => {
|
||||
addAccount(res.id, res.i);
|
||||
this.switchAccountWithToken(res.i);
|
||||
},
|
||||
}, 'closed');
|
||||
},
|
||||
function switchAccountWithToken(token: string) {
|
||||
login(token);
|
||||
}
|
||||
|
||||
async switchAccount(account: any) {
|
||||
const storedAccounts = await getAccounts();
|
||||
const token = storedAccounts.find(x => x.id === account.id).token;
|
||||
this.switchAccountWithToken(token);
|
||||
},
|
||||
|
||||
switchAccountWithToken(token: string) {
|
||||
login(token);
|
||||
},
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.accounts,
|
||||
icon: 'fas fa-users',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
watch(localCustomCss, async () => {
|
||||
await apply();
|
||||
});
|
||||
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'info',
|
||||
text: this.$ts.reloadToApplySetting,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
unisonReload();
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.customCss,
|
||||
icon: 'fas fa-code',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -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)',
|
||||
},
|
||||
}
|
||||
},
|
||||
unisonReload();
|
||||
});
|
||||
|
||||
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'),
|
||||
},
|
||||
async function setProfile() {
|
||||
const { canceled, result: name } = await os.inputText({
|
||||
title: i18n.ts._deck.profile,
|
||||
allowEmpty: false
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
profile.value = name;
|
||||
unisonReload();
|
||||
}
|
||||
|
||||
watch: {
|
||||
async navWindow() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'info',
|
||||
text: this.$ts.reloadToApplySetting,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
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>
|
||||
|
@ -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,
|
||||
},
|
||||
async function deleteAccount() {
|
||||
{
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts.deleteAccountConfirm,
|
||||
});
|
||||
if (canceled) return;
|
||||
}
|
||||
|
||||
emits: ['info'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts._accountDelete.accountDelete,
|
||||
icon: 'fas fa-exclamation-triangle',
|
||||
bg: 'var(--bg)',
|
||||
},
|
||||
}
|
||||
},
|
||||
const { canceled, result: password } = await os.inputText({
|
||||
title: i18n.ts.password,
|
||||
type: 'password'
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
methods: {
|
||||
async deleteAccount() {
|
||||
{
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: this.$ts.deleteAccountConfirm,
|
||||
});
|
||||
if (canceled) return;
|
||||
}
|
||||
await os.apiWithDialog('i/delete-account', {
|
||||
password: password
|
||||
});
|
||||
|
||||
const { canceled, result: password } = await os.inputText({
|
||||
title: this.$ts.password,
|
||||
type: 'password'
|
||||
});
|
||||
if (canceled) return;
|
||||
await os.alert({
|
||||
title: i18n.ts._accountDelete.started,
|
||||
});
|
||||
|
||||
await os.apiWithDialog('i/delete-account', {
|
||||
password: password
|
||||
});
|
||||
await signout();
|
||||
}
|
||||
|
||||
await os.alert({
|
||||
title: this.$ts._accountDelete.started,
|
||||
});
|
||||
|
||||
signout();
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts._accountDelete.accountDelete,
|
||||
icon: 'fas fa-exclamation-triangle',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -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'));
|
||||
|
||||
os.api('drive').then(info => {
|
||||
capacity.value = info.capacity;
|
||||
usage.value = info.usage;
|
||||
fetching.value = false;
|
||||
});
|
||||
|
||||
if (defaultStore.state.uploadFolder) {
|
||||
os.api('drive/folders/show', {
|
||||
folderId: defaultStore.state.uploadFolder
|
||||
}).then(response => {
|
||||
uploadFolder.value = response;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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'),
|
||||
},
|
||||
|
||||
async created() {
|
||||
os.api('drive').then(info => {
|
||||
this.capacity = info.capacity;
|
||||
this.usage = info.usage;
|
||||
this.fetching = false;
|
||||
this.$nextTick(() => {
|
||||
this.renderChart();
|
||||
});
|
||||
});
|
||||
|
||||
if (this.$store.state.uploadFolder) {
|
||||
this.uploadFolder = await os.api('drive/folders/show', {
|
||||
folderId: this.$store.state.uploadFolder
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
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>
|
||||
|
@ -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 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)
|
||||
});
|
||||
};
|
||||
|
||||
watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => {
|
||||
saveNotificationSettings();
|
||||
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,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
watch(emailAddress, () => {
|
||||
saveEmailAddress();
|
||||
});
|
||||
});
|
||||
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'));
|
||||
|
||||
return {
|
||||
[symbols.PAGE_INFO]: INFO,
|
||||
emailAddress,
|
||||
onChangeReceiveAnnouncementEmail,
|
||||
emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_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)
|
||||
});
|
||||
};
|
||||
|
||||
watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => {
|
||||
saveNotificationSettings();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
watch(emailAddress, () => {
|
||||
saveEmailAddress();
|
||||
});
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.email,
|
||||
icon: 'fas fa-envelope',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -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();
|
||||
},
|
||||
watch(lang, () => {
|
||||
localStorage.setItem('lang', lang.value as string);
|
||||
localStorage.removeItem('locale');
|
||||
});
|
||||
|
||||
fontSize() {
|
||||
if (this.fontSize == null) {
|
||||
localStorage.removeItem('fontSize');
|
||||
} else {
|
||||
localStorage.setItem('fontSize', this.fontSize);
|
||||
}
|
||||
this.reloadAsk();
|
||||
},
|
||||
watch(fontSize, () => {
|
||||
if (fontSize.value == null) {
|
||||
localStorage.removeItem('fontSize');
|
||||
} else {
|
||||
localStorage.setItem('fontSize', fontSize.value);
|
||||
}
|
||||
});
|
||||
|
||||
useSystemFont() {
|
||||
if (this.useSystemFont) {
|
||||
localStorage.setItem('useSystemFont', 't');
|
||||
} else {
|
||||
localStorage.removeItem('useSystemFont');
|
||||
}
|
||||
this.reloadAsk();
|
||||
},
|
||||
watch(useSystemFont, () => {
|
||||
if (useSystemFont.value) {
|
||||
localStorage.setItem('useSystemFont', 't');
|
||||
} else {
|
||||
localStorage.removeItem('useSystemFont');
|
||||
}
|
||||
});
|
||||
|
||||
enableInfiniteScroll() {
|
||||
this.reloadAsk();
|
||||
},
|
||||
watch([
|
||||
lang,
|
||||
fontSize,
|
||||
useSystemFont,
|
||||
enableInfiniteScroll,
|
||||
squareAvatars,
|
||||
aiChanMode,
|
||||
showGapBetweenNotesInTimeline,
|
||||
instanceTicker,
|
||||
overridedDeviceKind
|
||||
], async () => {
|
||||
await reloadAsk();
|
||||
});
|
||||
|
||||
squareAvatars() {
|
||||
this.reloadAsk();
|
||||
},
|
||||
|
||||
aiChanMode() {
|
||||
this.reloadAsk();
|
||||
},
|
||||
|
||||
showGapBetweenNotesInTimeline() {
|
||||
this.reloadAsk();
|
||||
},
|
||||
|
||||
instanceTicker() {
|
||||
this.reloadAsk();
|
||||
},
|
||||
|
||||
overridedDeviceKind() {
|
||||
this.reloadAsk();
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
async reloadAsk() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'info',
|
||||
text: this.$ts.reloadToApplySetting,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
unisonReload();
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.general,
|
||||
icon: 'fas fa-cogs',
|
||||
bg: 'var(--bg)'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
narrow.value = el.value.offsetWidth < 800;
|
||||
if (!narrow.value) {
|
||||
page.value = 'profile';
|
||||
watch(narrow, () => {
|
||||
if (props.initialPage == null && !narrow.value) {
|
||||
nav.push('/settings/profile');
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
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%;
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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');
|
||||
},
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
connectGithub() {
|
||||
this.githubForm = window.open(apiUrl + '/connect/github',
|
||||
'github_connect_window',
|
||||
'height=570, width=520');
|
||||
},
|
||||
|
||||
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>
|
||||
|
@ -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();
|
||||
},
|
||||
watch(items, async () => {
|
||||
await save();
|
||||
});
|
||||
|
||||
reset() {
|
||||
this.$store.reset('menu');
|
||||
this.items = this.$store.state.menu.join('\n');
|
||||
},
|
||||
watch(menuDisplay, async () => {
|
||||
await reloadAsk();
|
||||
});
|
||||
|
||||
async reloadAsk() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'info',
|
||||
text: this.$ts.reloadToApplySetting,
|
||||
showCancelButton: true
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
unisonReload();
|
||||
}
|
||||
},
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.menu,
|
||||
icon: 'fas fa-list-ul',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -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)',
|
||||
},
|
||||
async function readAllMessagingMessages() {
|
||||
await os.api('i/read-all-messaging-messages');
|
||||
}
|
||||
|
||||
async function readAllNotifications() {
|
||||
await 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');
|
||||
}
|
||||
|
||||
methods: {
|
||||
readAllUnreadNotes() {
|
||||
os.api('i/read-all-unread-notes');
|
||||
},
|
||||
|
||||
readAllMessagingMessages() {
|
||||
os.api('i/read-all-messaging-messages');
|
||||
},
|
||||
|
||||
readAllNotifications() {
|
||||
os.api('notifications/mark-all-as-read');
|
||||
},
|
||||
|
||||
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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
res(token);
|
||||
}
|
||||
}, 'closed');
|
||||
});
|
||||
|
||||
installPlugin({
|
||||
id: uuid(),
|
||||
meta: {
|
||||
name, version, author, description, permissions, config
|
||||
},
|
||||
token,
|
||||
ast: serialize(ast)
|
||||
});
|
||||
|
||||
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 :('
|
||||
});
|
||||
return;
|
||||
}
|
||||
os.success();
|
||||
|
||||
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,
|
||||
});
|
||||
nextTick(() => {
|
||||
unisonReload();
|
||||
});
|
||||
}
|
||||
|
||||
res(token);
|
||||
}
|
||||
}, 'closed');
|
||||
});
|
||||
|
||||
this.installPlugin({
|
||||
id: uuid(),
|
||||
meta: {
|
||||
name, version, author, description, permissions, config
|
||||
},
|
||||
token,
|
||||
ast: serialize(ast)
|
||||
});
|
||||
|
||||
os.success();
|
||||
|
||||
this.$nextTick(() => {
|
||||
unisonReload();
|
||||
});
|
||||
},
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts._plugin.install,
|
||||
icon: 'fas fa-download',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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');
|
||||
|
@ -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,
|
||||
},
|
||||
const pagination = {
|
||||
endpoint: 'i/signin-history' as const,
|
||||
limit: 5,
|
||||
};
|
||||
|
||||
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: i18n.ts.newPassword,
|
||||
type: 'password'
|
||||
});
|
||||
if (canceled2) 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: i18n.ts.retypedNotMatch
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
},
|
||||
os.apiWithDialog('i/change-password', {
|
||||
currentPassword,
|
||||
newPassword
|
||||
});
|
||||
}
|
||||
|
||||
methods: {
|
||||
async change() {
|
||||
const { canceled: canceled1, result: currentPassword } = await os.inputText({
|
||||
title: this.$ts.currentPassword,
|
||||
type: 'password'
|
||||
});
|
||||
if (canceled1) return;
|
||||
function regenerateToken() {
|
||||
os.inputText({
|
||||
title: i18n.ts.password,
|
||||
type: 'password'
|
||||
}).then(({ canceled, result: password }) => {
|
||||
if (canceled) return;
|
||||
os.api('i/regenerate_token', {
|
||||
password: password
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const { canceled: canceled2, result: newPassword } = await os.inputText({
|
||||
title: this.$ts.newPassword,
|
||||
type: 'password'
|
||||
});
|
||||
if (canceled2) return;
|
||||
|
||||
const { canceled: canceled3, result: newPassword2 } = await os.inputText({
|
||||
title: this.$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
|
||||
});
|
||||
},
|
||||
|
||||
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>
|
||||
|
@ -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,
|
||||
},
|
||||
volumeIcon() {
|
||||
return this.masterVolume === 0 ? 'fas fa-volume-mute' : 'fas fa-volume-up';
|
||||
}
|
||||
},
|
||||
|
||||
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');
|
||||
},
|
||||
|
||||
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;
|
||||
|
||||
const v = {
|
||||
type: result.type,
|
||||
volume: result.volume,
|
||||
};
|
||||
|
||||
ColdDeviceStorage.set('sound_' + type, v);
|
||||
this.sounds[type] = v;
|
||||
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
|
||||
},
|
||||
|
||||
reset() {
|
||||
for (const sound of Object.keys(this.sounds)) {
|
||||
const v = ColdDeviceStorage.default['sound_' + sound];
|
||||
ColdDeviceStorage.set('sound_' + sound, v);
|
||||
this.sounds[sound] = v;
|
||||
listen: {
|
||||
type: 'button',
|
||||
content: i18n.ts.listen,
|
||||
action: (_, values) => {
|
||||
playFile(values.type, values.volume);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
const v = {
|
||||
type: result.type,
|
||||
volume: result.volume,
|
||||
};
|
||||
|
||||
ColdDeviceStorage.set('sound_' + type, v);
|
||||
sounds.value[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;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.sounds,
|
||||
icon: 'fas fa-music',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
selectedThemeCode() {
|
||||
if (this.selectedTheme == null) return null;
|
||||
return JSON5.stringify(this.selectedTheme, null, '\t');
|
||||
},
|
||||
},
|
||||
const selectedThemeCode = computed(() => {
|
||||
if (selectedTheme.value == null) return null;
|
||||
return JSON5.stringify(selectedTheme.value, null, '\t');
|
||||
});
|
||||
|
||||
methods: {
|
||||
copyThemeCode() {
|
||||
copyToClipboard(this.selectedThemeCode);
|
||||
os.success();
|
||||
},
|
||||
function copyThemeCode() {
|
||||
copyToClipboard(selectedThemeCode.value);
|
||||
os.success();
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
removeTheme(this.selectedTheme);
|
||||
this.installedThemes = this.installedThemes.filter(t => t.id !== this.selectedThemeId);
|
||||
this.selectedThemeId = null;
|
||||
os.success();
|
||||
},
|
||||
function uninstall() {
|
||||
removeTheme(selectedTheme.value as Theme);
|
||||
installedThemes.value = installedThemes.value.filter(t => t.id !== selectedThemeId.value);
|
||||
selectedThemeId.value = null;
|
||||
os.success();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts._theme.manage,
|
||||
icon: 'fas fa-folder-open',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -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 = [];
|
||||
|
||||
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, ...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;
|
||||
|
||||
emits: ['info'],
|
||||
watch(syncDeviceDarkMode, () => {
|
||||
if (syncDeviceDarkMode.value) {
|
||||
defaultStore.set('darkMode', isDeviceDarkmode());
|
||||
}
|
||||
});
|
||||
|
||||
setup(props, { emit }) {
|
||||
const INFO = {
|
||||
title: i18n.ts.theme,
|
||||
icon: 'fas fa-palette',
|
||||
bg: 'var(--bg)',
|
||||
};
|
||||
watch(wallpaper, () => {
|
||||
if (wallpaper.value == null) {
|
||||
localStorage.removeItem('wallpaper');
|
||||
} else {
|
||||
localStorage.setItem('wallpaper', wallpaper.value);
|
||||
}
|
||||
location.reload();
|
||||
});
|
||||
|
||||
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;
|
||||
onActivated(() => {
|
||||
fetchThemes().then(() => {
|
||||
installedThemes.value = getThemes();
|
||||
});
|
||||
});
|
||||
|
||||
watch(syncDeviceDarkMode, () => {
|
||||
if (syncDeviceDarkMode.value) {
|
||||
defaultStore.set('darkMode', isDeviceDarkmode());
|
||||
}
|
||||
});
|
||||
fetchThemes().then(() => {
|
||||
installedThemes.value = getThemes();
|
||||
});
|
||||
|
||||
watch(wallpaper, () => {
|
||||
if (wallpaper.value == null) {
|
||||
localStorage.removeItem('wallpaper');
|
||||
} else {
|
||||
localStorage.setItem('wallpaper', wallpaper.value);
|
||||
}
|
||||
location.reload();
|
||||
});
|
||||
function setWallpaper(event) {
|
||||
selectFile(event.currentTarget ?? event.target, null).then(file => {
|
||||
wallpaper.value = file.url;
|
||||
});
|
||||
}
|
||||
|
||||
onActivated(() => {
|
||||
fetchThemes().then(() => {
|
||||
installedThemes.value = getThemes();
|
||||
});
|
||||
});
|
||||
|
||||
fetchThemes().then(() => {
|
||||
installedThemes.value = getThemes();
|
||||
});
|
||||
|
||||
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>
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
},
|
||||
const render = (mutedWords) => mutedWords.map(x => {
|
||||
if (Array.isArray(x)) {
|
||||
return x.join(' ');
|
||||
} else {
|
||||
return x;
|
||||
}
|
||||
}).join('\n');
|
||||
|
||||
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 tab = ref('soft');
|
||||
const softMutedWords = ref(render(defaultStore.state.mutedWords));
|
||||
const hardMutedWords = ref(render($i!.mutedWords));
|
||||
const hardWordMutedNotesCount = ref(null);
|
||||
const changed = ref(false);
|
||||
|
||||
watch: {
|
||||
softMutedWords: {
|
||||
handler() {
|
||||
this.changed = true;
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
hardMutedWords: {
|
||||
handler() {
|
||||
this.changed = true;
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
os.api('i/get-word-muted-notes-count', {}).then(response => {
|
||||
hardWordMutedNotesCount.value = response?.count;
|
||||
});
|
||||
|
||||
async created() {
|
||||
const render = (mutedWords) => mutedWords.map(x => {
|
||||
if (Array.isArray(x)) {
|
||||
return x.join(' ');
|
||||
} else {
|
||||
return x;
|
||||
}
|
||||
}).join('\n');
|
||||
watch(softMutedWords, () => {
|
||||
changed.value = true;
|
||||
});
|
||||
|
||||
this.softMutedWords = render(this.$store.state.mutedWords);
|
||||
this.hardMutedWords = render(this.$i.mutedWords);
|
||||
watch(hardMutedWords, () => {
|
||||
changed.value = true;
|
||||
});
|
||||
|
||||
this.hardWordMutedNotesCount = (await os.api('i/get-word-muted-notes-count', {})).count;
|
||||
},
|
||||
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 !== '');
|
||||
|
||||
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 != '');
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
} else {
|
||||
lines[i] = line.split(' ');
|
||||
}
|
||||
}
|
||||
|
||||
this.$store.set('mutedWords', softMutes);
|
||||
await os.api('i/update', {
|
||||
mutedWords: hardMutes,
|
||||
});
|
||||
return lines;
|
||||
};
|
||||
|
||||
this.changed = false;
|
||||
},
|
||||
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;
|
||||
}
|
||||
|
||||
number
|
||||
defaultStore.set('mutedWords', softMutes);
|
||||
await os.api('i/update', {
|
||||
mutedWords: hardMutes,
|
||||
});
|
||||
|
||||
changed.value = false;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.wordMute,
|
||||
icon: 'fas fa-comment-slash',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -20,7 +20,7 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'MkTimelinePage',
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -41,7 +41,7 @@ export default defineComponent({
|
||||
password: '',
|
||||
submitting: false,
|
||||
host,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -39,7 +39,7 @@ export default defineComponent({
|
||||
return {
|
||||
notes: [],
|
||||
isScrolling: false,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
|
Reference in New Issue
Block a user