mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-06-14 12:34:56 +02:00
Merge branch 'develop' into pizzax-indexeddb
This commit is contained in:
@@ -55,7 +55,7 @@
|
||||
<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>
|
||||
<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>
|
||||
@@ -68,8 +68,8 @@
|
||||
import { ref } from 'vue';
|
||||
import { hostname } from '@/config';
|
||||
import { byteify, hexify, stringify } from '@/scripts/2fa';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkInfo from '@/components/ui/info.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import MkSwitch from '@/components/form/switch.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkKeyValue from '@/components/key-value.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import * as os from '@/os';
|
||||
import number from '@/filters/number';
|
||||
import bytes from '@/filters/bytes';
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, ref } from 'vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os';
|
||||
import { getAccounts, addAccount as addAccounts, removeAccount as _removeAccount, login, $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
@@ -75,7 +75,7 @@ function removeAccount(account) {
|
||||
}
|
||||
|
||||
function addExistingAccount() {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, {
|
||||
done: res => {
|
||||
addAccounts(res.id, res.i);
|
||||
os.success();
|
||||
@@ -84,7 +84,7 @@ function addExistingAccount() {
|
||||
}
|
||||
|
||||
function createAccount() {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, {
|
||||
done: res => {
|
||||
addAccounts(res.id, res.i);
|
||||
switchAccountWithToken(res.i);
|
||||
@@ -109,7 +109,6 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.accounts,
|
||||
icon: 'fas fa-users',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, ref } from 'vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
@@ -17,7 +17,7 @@ import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
const isDesktop = ref(window.innerWidth >= 1100);
|
||||
|
||||
function generateToken() {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/token-generate-window.vue')), {}, {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), {}, {
|
||||
done: async result => {
|
||||
const { name, permissions } = result;
|
||||
const { token } = await os.api('miauth/gen-token', {
|
||||
@@ -42,6 +42,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: 'API',
|
||||
icon: 'fas fa-key',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import FormPagination from '@/components/ui/pagination.vue';
|
||||
import FormPagination from '@/components/MkPagination.vue';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
@@ -67,7 +67,6 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.installedApps,
|
||||
icon: 'fas fa-plug',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import FormTextarea from '@/components/form/textarea.vue';
|
||||
import FormInfo from '@/components/ui/info.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import * as os from '@/os';
|
||||
import { unisonReload } from '@/scripts/unison-reload';
|
||||
import { i18n } from '@/i18n';
|
||||
@@ -42,6 +42,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.customCss,
|
||||
icon: 'fas fa-code',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<div class="_formRoot">
|
||||
<FormGroup>
|
||||
<template #label>{{ i18n.ts.defaultNavigationBehaviour }}</template>
|
||||
<FormSwitch v-model="navWindow">{{ i18n.ts.openInWindow }}</FormSwitch>
|
||||
</FormGroup>
|
||||
<FormSwitch v-model="navWindow">{{ i18n.ts.defaultNavigationBehaviour }}: {{ i18n.ts.openInWindow }}</FormSwitch>
|
||||
|
||||
<FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ i18n.ts._deck.alwaysShowMainColumn }}</FormSwitch>
|
||||
|
||||
@@ -12,20 +9,6 @@
|
||||
<option value="left">{{ i18n.ts.left }}</option>
|
||||
<option value="center">{{ i18n.ts.center }}</option>
|
||||
</FormRadios>
|
||||
|
||||
<FormRadios v-model="columnHeaderHeight" class="_formBlock">
|
||||
<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>{{ i18n.ts._deck.columnMargin }}</template>
|
||||
<template #suffix>px</template>
|
||||
</FormInput>
|
||||
|
||||
<FormLink class="_formBlock" @click="setProfile">{{ i18n.ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,7 +18,6 @@ import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormRadios from '@/components/form/radios.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import { deckStore } from '@/ui/deck/deck-store';
|
||||
import * as os from '@/os';
|
||||
import { unisonReload } from '@/scripts/unison-reload';
|
||||
@@ -45,30 +27,6 @@ import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
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'));
|
||||
|
||||
watch(navWindow, async () => {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'info',
|
||||
text: i18n.ts.reloadToApplySetting,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
unisonReload();
|
||||
});
|
||||
|
||||
async function setProfile() {
|
||||
const { canceled, result: name } = await os.inputText({
|
||||
title: i18n.ts._deck.profile,
|
||||
allowEmpty: false,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
profile.value = name;
|
||||
unisonReload();
|
||||
}
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
@@ -77,6 +35,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.deck,
|
||||
icon: 'fas fa-columns',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import FormInfo from '@/components/ui/info.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os';
|
||||
import { signout } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
@@ -48,6 +48,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts._accountDelete.accountDelete,
|
||||
icon: 'fas fa-exclamation-triangle',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -28,7 +28,17 @@
|
||||
<template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template>
|
||||
<template #suffixIcon><i class="fas fa-folder-open"></i></template>
|
||||
</FormLink>
|
||||
<FormSwitch v-model="keepOriginalUploading" class="_formBlock">{{ i18n.ts.keepOriginalUploading }}<template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template></FormSwitch>
|
||||
<FormSwitch v-model="keepOriginalUploading" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.keepOriginalUploading }}</template>
|
||||
<template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template>
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model="alwaysMarkNsfw" class="_formBlock" @update:modelValue="saveProfile()">
|
||||
<template #label>{{ i18n.ts.alwaysMarkSensitive }}</template>
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model="autoSensitive" class="_formBlock" @update:modelValue="saveProfile()">
|
||||
<template #label>{{ i18n.ts.enableAutoSensitive }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<template #caption>{{ i18n.ts.enableAutoSensitiveDescription }}</template>
|
||||
</FormSwitch>
|
||||
</FormSection>
|
||||
</div>
|
||||
</template>
|
||||
@@ -39,19 +49,22 @@ 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';
|
||||
import MkKeyValue from '@/components/key-value.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import FormSplit from '@/components/form/split.vue';
|
||||
import * as os from '@/os';
|
||||
import bytes from '@/filters/bytes';
|
||||
import { defaultStore } from '@/store';
|
||||
import MkChart from '@/components/chart.vue';
|
||||
import MkChart from '@/components/MkChart.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { $i } from '@/account';
|
||||
|
||||
const fetching = ref(true);
|
||||
const usage = ref<any>(null);
|
||||
const capacity = ref<any>(null);
|
||||
const uploadFolder = ref<any>(null);
|
||||
let alwaysMarkNsfw = $ref($i.alwaysMarkNsfw);
|
||||
let autoSensitive = $ref($i.autoSensitive);
|
||||
|
||||
const meterStyle = computed(() => {
|
||||
return {
|
||||
@@ -94,6 +107,13 @@ function chooseUploadFolder() {
|
||||
});
|
||||
}
|
||||
|
||||
function saveProfile() {
|
||||
os.api('i/update', {
|
||||
alwaysMarkNsfw: !!alwaysMarkNsfw,
|
||||
autoSensitive: !!autoSensitive,
|
||||
});
|
||||
}
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
const headerTabs = $computed(() => []);
|
||||
@@ -101,7 +121,6 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.drive,
|
||||
icon: 'fas fa-cloud',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
<template>
|
||||
<div class="_formRoot">
|
||||
<FormSection>
|
||||
<template #label>{{ $ts.emailAddress }}</template>
|
||||
<template #label>{{ i18n.ts.emailAddress }}</template>
|
||||
<FormInput v-model="emailAddress" type="email" manual-save>
|
||||
<template #prefix><i class="fas fa-envelope"></i></template>
|
||||
<template v-if="$i.email && !$i.emailVerified" #caption>{{ $ts.verificationEmailSent }}</template>
|
||||
<template v-else-if="emailAddress === $i.email && $i.emailVerified" #caption><i class="fas fa-check" style="color: var(--success);"></i> {{ $ts.emailVerified }}</template>
|
||||
<template v-if="$i.email && !$i.emailVerified" #caption>{{ i18n.ts.verificationEmailSent }}</template>
|
||||
<template v-else-if="emailAddress === $i.email && $i.emailVerified" #caption><i class="fas fa-check" style="color: var(--success);"></i> {{ i18n.ts.emailVerified }}</template>
|
||||
</FormInput>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<FormSwitch :value="$i.receiveAnnouncementEmail" @update:modelValue="onChangeReceiveAnnouncementEmail">
|
||||
{{ $ts.receiveAnnouncementFromInstance }}
|
||||
<FormSwitch :model-value="$i.receiveAnnouncementEmail" @update:modelValue="onChangeReceiveAnnouncementEmail">
|
||||
{{ i18n.ts.receiveAnnouncementFromInstance }}
|
||||
</FormSwitch>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ $ts.emailNotification }}</template>
|
||||
<template #label>{{ i18n.ts.emailNotification }}</template>
|
||||
<FormSwitch v-model="emailNotification_mention" class="_formBlock">
|
||||
{{ $ts._notification._types.mention }}
|
||||
{{ i18n.ts._notification._types.mention }}
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model="emailNotification_reply" class="_formBlock">
|
||||
{{ $ts._notification._types.reply }}
|
||||
{{ i18n.ts._notification._types.reply }}
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model="emailNotification_quote" class="_formBlock">
|
||||
{{ $ts._notification._types.quote }}
|
||||
{{ i18n.ts._notification._types.quote }}
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model="emailNotification_follow" class="_formBlock">
|
||||
{{ $ts._notification._types.follow }}
|
||||
{{ i18n.ts._notification._types.follow }}
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model="emailNotification_receiveFollowRequest" class="_formBlock">
|
||||
{{ $ts._notification._types.receiveFollowRequest }}
|
||||
{{ i18n.ts._notification._types.receiveFollowRequest }}
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model="emailNotification_groupInvited" class="_formBlock">
|
||||
{{ $ts._notification._types.groupInvited }}
|
||||
{{ i18n.ts._notification._types.groupInvited }}
|
||||
</FormSwitch>
|
||||
</FormSection>
|
||||
</div>
|
||||
@@ -107,6 +107,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.email,
|
||||
icon: 'fas fa-envelope',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -56,10 +56,10 @@
|
||||
|
||||
<FormRadios v-model="fontSize" class="_formBlock">
|
||||
<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>
|
||||
<option value="veryLarge"><span style="font-size: 20px;">Aa</span></option>
|
||||
<option :value="null"><span style="font-size: 14px;">Aa</span></option>
|
||||
<option value="1"><span style="font-size: 15px;">Aa</span></option>
|
||||
<option value="2"><span style="font-size: 16px;">Aa</span></option>
|
||||
<option value="3"><span style="font-size: 17px;">Aa</span></option>
|
||||
</FormRadios>
|
||||
</FormSection>
|
||||
|
||||
@@ -81,10 +81,10 @@
|
||||
<option value="force">{{ i18n.ts._nsfw.force }}</option>
|
||||
</FormSelect>
|
||||
|
||||
<FormGroup>
|
||||
<template #label>{{ i18n.ts.defaultNavigationBehaviour }}</template>
|
||||
<FormSwitch v-model="defaultSideView">{{ i18n.ts.openInSideView }}</FormSwitch>
|
||||
</FormGroup>
|
||||
<FormRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing class="_formBlock">
|
||||
<template #label>{{ i18n.ts.numberOfPageCache }}</template>
|
||||
<template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template>
|
||||
</FormRange>
|
||||
|
||||
<FormLink to="/settings/deck" class="_formBlock">{{ i18n.ts.deck }}</FormLink>
|
||||
|
||||
@@ -97,10 +97,10 @@ import { computed, 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';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import FormRange from '@/components/form/range.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import MkLink from '@/components/link.vue';
|
||||
import MkLink from '@/components/MkLink.vue';
|
||||
import { langs } from '@/config';
|
||||
import { defaultStore } from '@/store';
|
||||
import * as os from '@/os';
|
||||
@@ -137,7 +137,7 @@ 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 numberOfPageCache = computed(defaultStore.makeGetterSetter('numberOfPageCache'));
|
||||
const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker'));
|
||||
const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll'));
|
||||
const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu'));
|
||||
@@ -186,6 +186,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.general,
|
||||
icon: 'fas fa-cogs',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,47 +1,79 @@
|
||||
<template>
|
||||
<div class="_formRoot">
|
||||
<FormSection>
|
||||
<template #label>{{ $ts._exportOrImport.allNotes }}</template>
|
||||
<MkButton :class="$style.button" inline @click="exportNotes()"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton>
|
||||
<template #label>{{ i18n.ts._exportOrImport.allNotes }}</template>
|
||||
<FormFolder>
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="fas fa-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportNotes()"><i class="fas fa-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</FormFolder>
|
||||
</FormSection>
|
||||
<FormSection>
|
||||
<template #label>{{ $ts._exportOrImport.followingList }}</template>
|
||||
<FormGroup>
|
||||
<template #label>{{ i18n.ts._exportOrImport.followingList }}</template>
|
||||
<FormFolder class="_formBlock">
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="fas fa-download"></i></template>
|
||||
<FormSwitch v-model="excludeMutingUsers" class="_formBlock">
|
||||
{{ $ts._exportOrImport.excludeMutingUsers }}
|
||||
{{ i18n.ts._exportOrImport.excludeMutingUsers }}
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model="excludeInactiveUsers" class="_formBlock">
|
||||
{{ $ts._exportOrImport.excludeInactiveUsers }}
|
||||
{{ i18n.ts._exportOrImport.excludeInactiveUsers }}
|
||||
</FormSwitch>
|
||||
<MkButton :class="$style.button" inline @click="exportFollowing()"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<MkButton :class="$style.button" inline @click="importFollowing($event)"><i class="fas fa-upload"></i> {{ $ts.import }}</MkButton>
|
||||
</FormGroup>
|
||||
<MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="fas fa-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</FormFolder>
|
||||
<FormFolder class="_formBlock">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="fas fa-upload"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="importFollowing($event)"><i class="fas fa-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</FormFolder>
|
||||
</FormSection>
|
||||
<FormSection>
|
||||
<template #label>{{ $ts._exportOrImport.userLists }}</template>
|
||||
<MkButton :class="$style.button" inline @click="exportUserLists()"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton>
|
||||
<MkButton :class="$style.button" inline @click="importUserLists($event)"><i class="fas fa-upload"></i> {{ $ts.import }}</MkButton>
|
||||
<template #label>{{ i18n.ts._exportOrImport.userLists }}</template>
|
||||
<FormFolder class="_formBlock">
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="fas fa-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportUserLists()"><i class="fas fa-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</FormFolder>
|
||||
<FormFolder class="_formBlock">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="fas fa-upload"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="importUserLists($event)"><i class="fas fa-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</FormFolder>
|
||||
</FormSection>
|
||||
<FormSection>
|
||||
<template #label>{{ $ts._exportOrImport.muteList }}</template>
|
||||
<MkButton :class="$style.button" inline @click="exportMuting()"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton>
|
||||
<MkButton :class="$style.button" inline @click="importMuting($event)"><i class="fas fa-upload"></i> {{ $ts.import }}</MkButton>
|
||||
<template #label>{{ i18n.ts._exportOrImport.muteList }}</template>
|
||||
<FormFolder class="_formBlock">
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="fas fa-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportMuting()"><i class="fas fa-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</FormFolder>
|
||||
<FormFolder class="_formBlock">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="fas fa-upload"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="importMuting($event)"><i class="fas fa-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</FormFolder>
|
||||
</FormSection>
|
||||
<FormSection>
|
||||
<template #label>{{ $ts._exportOrImport.blockingList }}</template>
|
||||
<MkButton :class="$style.button" inline @click="exportBlocking()"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton>
|
||||
<MkButton :class="$style.button" inline @click="importBlocking($event)"><i class="fas fa-upload"></i> {{ $ts.import }}</MkButton>
|
||||
<template #label>{{ i18n.ts._exportOrImport.blockingList }}</template>
|
||||
<FormFolder class="_formBlock">
|
||||
<template #label>{{ i18n.ts.export }}</template>
|
||||
<template #icon><i class="fas fa-download"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="exportBlocking()"><i class="fas fa-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||
</FormFolder>
|
||||
<FormFolder class="_formBlock">
|
||||
<template #label>{{ i18n.ts.import }}</template>
|
||||
<template #icon><i class="fas fa-upload"></i></template>
|
||||
<MkButton primary :class="$style.button" inline @click="importBlocking($event)"><i class="fas fa-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||
</FormFolder>
|
||||
</FormSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import FormFolder from '@/components/form/folder.vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import * as os from '@/os';
|
||||
import { selectFile } from '@/scripts/select-file';
|
||||
@@ -123,7 +155,6 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.importAndExport,
|
||||
icon: 'fas fa-boxes',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
<MkSpacer :content-max="900" :margin-min="20" :margin-max="32">
|
||||
<div ref="el" class="vvcocwet" :class="{ wide: !narrow }">
|
||||
<div class="body">
|
||||
<div v-if="!narrow || initialPage == null" class="nav">
|
||||
<div v-if="!narrow || currentPage?.route.name == 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="initialPage == null"></MkSuperMenu>
|
||||
<MkInfo v-if="emailNotConfigured" warn class="info">{{ i18n.ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
|
||||
<MkSuperMenu :def="menuDef" :grid="currentPage?.route.name == null"></MkSuperMenu>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!(narrow && initialPage == null)" class="main">
|
||||
<div v-if="!(narrow && currentPage?.route.name == null)" class="main">
|
||||
<div class="bkzroven">
|
||||
<component :is="component" :key="initialPage" v-bind="pageProps"/>
|
||||
<RouterView/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -22,26 +22,21 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, defineAsyncComponent, inject, nextTick, onMounted, onUnmounted, provide, ref, watch } from 'vue';
|
||||
import { computed, defineAsyncComponent, inject, nextTick, onActivated, onMounted, onUnmounted, provide, ref, watch } from 'vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import MkInfo from '@/components/ui/info.vue';
|
||||
import MkSuperMenu from '@/components/ui/super-menu.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkSuperMenu from '@/components/MkSuperMenu.vue';
|
||||
import { scroll } from '@/scripts/scroll';
|
||||
import { signout , $i } from '@/account';
|
||||
import { unisonReload } from '@/scripts/unison-reload';
|
||||
import { instance } from '@/instance';
|
||||
import { useRouter } from '@/router';
|
||||
import { definePageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
initialPage?: string;
|
||||
}>(), {
|
||||
});
|
||||
import * as os from '@/os';
|
||||
|
||||
const indexInfo = {
|
||||
title: i18n.ts.settings,
|
||||
icon: 'fas fa-cog',
|
||||
bg: 'var(--bg)',
|
||||
hideHeader: true,
|
||||
};
|
||||
const INFO = ref(indexInfo);
|
||||
@@ -50,12 +45,14 @@ const childInfo = ref(null);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const narrow = ref(false);
|
||||
let narrow = $ref(false);
|
||||
const NARROW_THRESHOLD = 600;
|
||||
|
||||
let currentPage = $computed(() => router.currentRef.value.child);
|
||||
|
||||
const ro = new ResizeObserver((entries, observer) => {
|
||||
if (entries.length === 0) return;
|
||||
narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
|
||||
narrow = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
|
||||
});
|
||||
|
||||
const menuDef = computed(() => [{
|
||||
@@ -64,42 +61,42 @@ const menuDef = computed(() => [{
|
||||
icon: 'fas fa-user',
|
||||
text: i18n.ts.profile,
|
||||
to: '/settings/profile',
|
||||
active: props.initialPage === 'profile',
|
||||
active: currentPage?.route.name === 'profile',
|
||||
}, {
|
||||
icon: 'fas fa-lock-open',
|
||||
text: i18n.ts.privacy,
|
||||
to: '/settings/privacy',
|
||||
active: props.initialPage === 'privacy',
|
||||
active: currentPage?.route.name === 'privacy',
|
||||
}, {
|
||||
icon: 'fas fa-laugh',
|
||||
text: i18n.ts.reaction,
|
||||
to: '/settings/reaction',
|
||||
active: props.initialPage === 'reaction',
|
||||
active: currentPage?.route.name === 'reaction',
|
||||
}, {
|
||||
icon: 'fas fa-cloud',
|
||||
text: i18n.ts.drive,
|
||||
to: '/settings/drive',
|
||||
active: props.initialPage === 'drive',
|
||||
active: currentPage?.route.name === 'drive',
|
||||
}, {
|
||||
icon: 'fas fa-bell',
|
||||
text: i18n.ts.notifications,
|
||||
to: '/settings/notifications',
|
||||
active: props.initialPage === 'notifications',
|
||||
active: currentPage?.route.name === 'notifications',
|
||||
}, {
|
||||
icon: 'fas fa-envelope',
|
||||
text: i18n.ts.email,
|
||||
to: '/settings/email',
|
||||
active: props.initialPage === 'email',
|
||||
active: currentPage?.route.name === 'email',
|
||||
}, {
|
||||
icon: 'fas fa-share-alt',
|
||||
text: i18n.ts.integration,
|
||||
to: '/settings/integration',
|
||||
active: props.initialPage === 'integration',
|
||||
active: currentPage?.route.name === 'integration',
|
||||
}, {
|
||||
icon: 'fas fa-lock',
|
||||
text: i18n.ts.security,
|
||||
to: '/settings/security',
|
||||
active: props.initialPage === 'security',
|
||||
active: currentPage?.route.name === 'security',
|
||||
}],
|
||||
}, {
|
||||
title: i18n.ts.clientSettings,
|
||||
@@ -107,32 +104,32 @@ const menuDef = computed(() => [{
|
||||
icon: 'fas fa-cogs',
|
||||
text: i18n.ts.general,
|
||||
to: '/settings/general',
|
||||
active: props.initialPage === 'general',
|
||||
active: currentPage?.route.name === 'general',
|
||||
}, {
|
||||
icon: 'fas fa-palette',
|
||||
text: i18n.ts.theme,
|
||||
to: '/settings/theme',
|
||||
active: props.initialPage === 'theme',
|
||||
active: currentPage?.route.name === 'theme',
|
||||
}, {
|
||||
icon: 'fas fa-list-ul',
|
||||
text: i18n.ts.menu,
|
||||
to: '/settings/menu',
|
||||
active: props.initialPage === 'menu',
|
||||
icon: 'fas fa-bars',
|
||||
text: i18n.ts.navbar,
|
||||
to: '/settings/navbar',
|
||||
active: currentPage?.route.name === 'navbar',
|
||||
}, {
|
||||
icon: 'fas fa-bars-progress',
|
||||
text: i18n.ts.statusbar,
|
||||
to: '/settings/statusbar',
|
||||
active: currentPage?.route.name === 'statusbar',
|
||||
}, {
|
||||
icon: 'fas fa-music',
|
||||
text: i18n.ts.sounds,
|
||||
to: '/settings/sounds',
|
||||
active: props.initialPage === 'sounds',
|
||||
active: currentPage?.route.name === 'sounds',
|
||||
}, {
|
||||
icon: 'fas fa-plug',
|
||||
text: i18n.ts.plugins,
|
||||
to: '/settings/plugin',
|
||||
active: props.initialPage === 'plugin',
|
||||
}, {
|
||||
icon: 'fas fa-floppy-disk',
|
||||
text: i18n.ts.preferencesRegistryShort,
|
||||
to: '/settings/preferences-registry',
|
||||
active: props.initialPage === 'preferences-registry',
|
||||
active: currentPage?.route.name === 'plugin',
|
||||
}],
|
||||
}, {
|
||||
title: i18n.ts.otherSettings,
|
||||
@@ -140,40 +137,45 @@ const menuDef = computed(() => [{
|
||||
icon: 'fas fa-boxes',
|
||||
text: i18n.ts.importAndExport,
|
||||
to: '/settings/import-export',
|
||||
active: props.initialPage === 'import-export',
|
||||
active: currentPage?.route.name === 'import-export',
|
||||
}, {
|
||||
icon: 'fas fa-volume-mute',
|
||||
text: i18n.ts.instanceMute,
|
||||
to: '/settings/instance-mute',
|
||||
active: props.initialPage === 'instance-mute',
|
||||
active: currentPage?.route.name === 'instance-mute',
|
||||
}, {
|
||||
icon: 'fas fa-ban',
|
||||
text: i18n.ts.muteAndBlock,
|
||||
to: '/settings/mute-block',
|
||||
active: props.initialPage === 'mute-block',
|
||||
active: currentPage?.route.name === 'mute-block',
|
||||
}, {
|
||||
icon: 'fas fa-comment-slash',
|
||||
text: i18n.ts.wordMute,
|
||||
to: '/settings/word-mute',
|
||||
active: props.initialPage === 'word-mute',
|
||||
active: currentPage?.route.name === 'word-mute',
|
||||
}, {
|
||||
icon: 'fas fa-key',
|
||||
text: 'API',
|
||||
to: '/settings/api',
|
||||
active: props.initialPage === 'api',
|
||||
active: currentPage?.route.name === 'api',
|
||||
}, {
|
||||
icon: 'fas fa-bolt',
|
||||
text: 'Webhook',
|
||||
to: '/settings/webhook',
|
||||
active: props.initialPage === 'webhook',
|
||||
active: currentPage?.route.name === 'webhook',
|
||||
}, {
|
||||
icon: 'fas fa-ellipsis-h',
|
||||
text: i18n.ts.other,
|
||||
to: '/settings/other',
|
||||
active: props.initialPage === 'other',
|
||||
active: currentPage?.route.name === 'other',
|
||||
}],
|
||||
}, {
|
||||
items: [{
|
||||
icon: 'fas fa-floppy-disk',
|
||||
text: i18n.ts.preferencesBackups,
|
||||
to: '/settings/preferences-backups',
|
||||
active: currentPage?.route.name === 'preferences-backups',
|
||||
}, {
|
||||
type: 'button',
|
||||
icon: 'fas fa-trash',
|
||||
text: i18n.ts.clearCache,
|
||||
@@ -186,84 +188,36 @@ const menuDef = computed(() => [{
|
||||
type: 'button',
|
||||
icon: 'fas fa-sign-in-alt fa-flip-horizontal',
|
||||
text: i18n.ts.logout,
|
||||
action: () => {
|
||||
action: async () => {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts.logoutConfirm,
|
||||
});
|
||||
if (canceled) return;
|
||||
signout();
|
||||
},
|
||||
danger: true,
|
||||
}],
|
||||
}]);
|
||||
|
||||
const pageProps = ref({});
|
||||
const component = computed(() => {
|
||||
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'));
|
||||
case 'reaction': return defineAsyncComponent(() => import('./reaction.vue'));
|
||||
case 'drive': return defineAsyncComponent(() => import('./drive.vue'));
|
||||
case 'notifications': return defineAsyncComponent(() => import('./notifications.vue'));
|
||||
case 'mute-block': return defineAsyncComponent(() => import('./mute-block.vue'));
|
||||
case 'word-mute': return defineAsyncComponent(() => import('./word-mute.vue'));
|
||||
case 'instance-mute': return defineAsyncComponent(() => import('./instance-mute.vue'));
|
||||
case 'integration': return defineAsyncComponent(() => import('./integration.vue'));
|
||||
case 'security': return defineAsyncComponent(() => import('./security.vue'));
|
||||
case '2fa': return defineAsyncComponent(() => import('./2fa.vue'));
|
||||
case 'api': return defineAsyncComponent(() => import('./api.vue'));
|
||||
case 'webhook': return defineAsyncComponent(() => import('./webhook.vue'));
|
||||
case 'webhook/new': return defineAsyncComponent(() => import('./webhook.new.vue'));
|
||||
case 'webhook/edit': return defineAsyncComponent(() => import('./webhook.edit.vue'));
|
||||
case 'apps': return defineAsyncComponent(() => import('./apps.vue'));
|
||||
case 'other': return defineAsyncComponent(() => import('./other.vue'));
|
||||
case 'general': return defineAsyncComponent(() => import('./general.vue'));
|
||||
case 'email': return defineAsyncComponent(() => import('./email.vue'));
|
||||
case 'theme': return defineAsyncComponent(() => import('./theme.vue'));
|
||||
case 'theme/install': return defineAsyncComponent(() => import('./theme.install.vue'));
|
||||
case 'theme/manage': return defineAsyncComponent(() => import('./theme.manage.vue'));
|
||||
case 'menu': return defineAsyncComponent(() => import('./menu.vue'));
|
||||
case 'sounds': return defineAsyncComponent(() => import('./sounds.vue'));
|
||||
case 'custom-css': return defineAsyncComponent(() => import('./custom-css.vue'));
|
||||
case 'deck': return defineAsyncComponent(() => import('./deck.vue'));
|
||||
case 'plugin': return defineAsyncComponent(() => import('./plugin.vue'));
|
||||
case 'plugin/install': return defineAsyncComponent(() => import('./plugin.install.vue'));
|
||||
case 'preferences-registry': return defineAsyncComponent(() => import('./preferences-registry.vue'));
|
||||
case 'import-export': return defineAsyncComponent(() => import('./import-export.vue'));
|
||||
case 'account-info': return defineAsyncComponent(() => import('./account-info.vue'));
|
||||
case 'delete-account': return defineAsyncComponent(() => import('./delete-account.vue'));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
watch(component, () => {
|
||||
pageProps.value = {};
|
||||
|
||||
nextTick(() => {
|
||||
scroll(el.value, { top: 0 });
|
||||
});
|
||||
}, { immediate: true });
|
||||
|
||||
watch(() => props.initialPage, () => {
|
||||
if (props.initialPage == null && !narrow.value) {
|
||||
router.push('/settings/profile');
|
||||
} else {
|
||||
if (props.initialPage == null) {
|
||||
INFO.value = indexInfo;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
watch(narrow, () => {
|
||||
if (props.initialPage == null && !narrow.value) {
|
||||
router.push('/settings/profile');
|
||||
}
|
||||
watch($$(narrow), () => {
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
ro.observe(el.value);
|
||||
|
||||
narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
|
||||
if (props.initialPage == null && !narrow.value) {
|
||||
router.push('/settings/profile');
|
||||
narrow = el.value.offsetWidth < NARROW_THRESHOLD;
|
||||
|
||||
if (!narrow && currentPage?.route.name == null) {
|
||||
router.replace('/settings/profile');
|
||||
}
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
narrow = el.value.offsetWidth < NARROW_THRESHOLD;
|
||||
|
||||
if (!narrow && currentPage?.route.name == null) {
|
||||
router.replace('/settings/profile');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -286,6 +240,8 @@ const headerActions = $computed(() => []);
|
||||
const headerTabs = $computed(() => []);
|
||||
|
||||
definePageMetadata(INFO);
|
||||
// w 890
|
||||
// h 700
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -323,13 +279,11 @@ definePageMetadata(INFO);
|
||||
width: 34%;
|
||||
padding-right: 32px;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
> .main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { 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 MkInfo from '@/components/MkInfo.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os';
|
||||
import { $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { apiUrl } from '@/config';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { $i } from '@/account';
|
||||
import { instance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
@@ -95,6 +95,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.integration,
|
||||
icon: 'fas fa-share-alt',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="_formRoot">
|
||||
<MkTab v-model="tab" style="margin-bottom: var(--margin);">
|
||||
<option value="mute">{{ $ts.mutedUsers }}</option>
|
||||
<option value="block">{{ $ts.blockedUsers }}</option>
|
||||
<option value="mute">{{ i18n.ts.mutedUsers }}</option>
|
||||
<option value="block">{{ i18n.ts.blockedUsers }}</option>
|
||||
</MkTab>
|
||||
<div v-if="tab === 'mute'">
|
||||
<MkPagination :pagination="mutingPagination" class="muting">
|
||||
<template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template>
|
||||
<template #empty><FormInfo>{{ i18n.ts.noUsers }}</FormInfo></template>
|
||||
<template #default="{items}">
|
||||
<FormLink v-for="mute in items" :key="mute.id" :to="userPage(mute.mutee)">
|
||||
<MkAcct :user="mute.mutee"/>
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div v-if="tab === 'block'">
|
||||
<MkPagination :pagination="blockingPagination" class="blocking">
|
||||
<template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template>
|
||||
<template #empty><FormInfo>{{ i18n.ts.noUsers }}</FormInfo></template>
|
||||
<template #default="{items}">
|
||||
<FormLink v-for="block in items" :key="block.id" :to="userPage(block.blockee)">
|
||||
<MkAcct :user="block.blockee"/>
|
||||
@@ -29,9 +29,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import MkPagination from '@/components/ui/pagination.vue';
|
||||
import MkTab from '@/components/tab.vue';
|
||||
import FormInfo from '@/components/ui/info.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import MkTab from '@/components/MkTab.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import { userPage } from '@/filters/user';
|
||||
import * as os from '@/os';
|
||||
@@ -57,6 +57,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.muteAndBlock,
|
||||
icon: 'fas fa-ban',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="_formRoot">
|
||||
<FormTextarea v-model="items" tall manual-save class="_formBlock">
|
||||
<template #label>{{ i18n.ts.menu }}</template>
|
||||
<template #label>{{ i18n.ts.navbar }}</template>
|
||||
<template #caption><button class="_textButton" @click="addItem">{{ i18n.ts.addItem }}</button></template>
|
||||
</FormTextarea>
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
import { computed, 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';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os';
|
||||
import { menuDef } from '@/menu';
|
||||
import { navbarItemDef } from '@/navbar';
|
||||
import { defaultStore } from '@/store';
|
||||
import { unisonReload } from '@/scripts/unison-reload';
|
||||
import { i18n } from '@/i18n';
|
||||
@@ -45,11 +45,11 @@ async function reloadAsk() {
|
||||
}
|
||||
|
||||
async function addItem() {
|
||||
const menu = Object.keys(menuDef).filter(k => !defaultStore.state.menu.includes(k));
|
||||
const menu = Object.keys(navbarItemDef).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: k, text: i18n.ts[navbarItemDef[k].title],
|
||||
})), {
|
||||
value: '-', text: i18n.ts.divider,
|
||||
}],
|
||||
@@ -81,8 +81,7 @@ const headerActions = $computed(() => []);
|
||||
const headerTabs = $computed(() => []);
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts.menu,
|
||||
title: i18n.ts.navbar,
|
||||
icon: 'fas fa-list-ul',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
@@ -12,7 +12,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import { notificationTypes } from 'misskey-js';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import * as os from '@/os';
|
||||
@@ -34,7 +34,7 @@ async function readAllNotifications() {
|
||||
|
||||
function configure() {
|
||||
const includingTypes = notificationTypes.filter(x => !$i!.mutingNotificationTypes.includes(x));
|
||||
os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSettingWindow.vue')), {
|
||||
includingTypes,
|
||||
showGlobalToggle: false,
|
||||
}, {
|
||||
@@ -56,6 +56,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.notifications,
|
||||
icon: 'fas fa-bell',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
<FormLink to="/settings/account-info" class="_formBlock">{{ i18n.ts.accountInfo }}</FormLink>
|
||||
|
||||
<FormLink to="/registry" class="_formBlock"><template #icon><i class="fas fa-cogs"></i></template>{{ i18n.ts.registry }}</FormLink>
|
||||
|
||||
<FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ i18n.ts.closeAccount }}</FormLink>
|
||||
</div>
|
||||
</template>
|
||||
@@ -41,6 +43,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.other,
|
||||
icon: 'fas fa-ellipsis-h',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -18,8 +18,8 @@ import { AiScript, parse } from '@syuilo/aiscript';
|
||||
import { serialize } from '@syuilo/aiscript/built/serializer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import FormTextarea from '@/components/form/textarea.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormInfo from '@/components/ui/info.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import * as os from '@/os';
|
||||
import { ColdDeviceStorage } from '@/store';
|
||||
import { unisonReload } from '@/scripts/unison-reload';
|
||||
@@ -79,7 +79,7 @@ async function install() {
|
||||
}
|
||||
|
||||
const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/token-generate-window.vue')), {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), {
|
||||
title: i18n.ts.tokenRequested,
|
||||
information: i18n.ts.pluginTokenRequestedDescription,
|
||||
initialName: name,
|
||||
@@ -120,6 +120,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts._plugin.install,
|
||||
icon: 'fas fa-download',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -36,8 +36,8 @@ import { 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';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkKeyValue from '@/components/key-value.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import * as os from '@/os';
|
||||
import { ColdDeviceStorage } from '@/store';
|
||||
import { unisonReload } from '@/scripts/unison-reload';
|
||||
@@ -90,7 +90,6 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.plugins,
|
||||
icon: 'fas fa-plug',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
444
packages/client/src/pages/settings/preferences-backups.vue
Normal file
444
packages/client/src/pages/settings/preferences-backups.vue
Normal file
@@ -0,0 +1,444 @@
|
||||
<template>
|
||||
<div class="_formRoot">
|
||||
<div :class="$style.buttons">
|
||||
<MkButton inline primary @click="saveNew">{{ ts._preferencesBackups.saveNew }}</MkButton>
|
||||
<MkButton inline @click="loadFile">{{ ts._preferencesBackups.loadFile }}</MkButton>
|
||||
</div>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ ts._preferencesBackups.list }}</template>
|
||||
<template v-if="profiles && Object.keys(profiles).length > 0">
|
||||
<div
|
||||
v-for="(profile, id) in profiles"
|
||||
:key="id"
|
||||
class="_formBlock _panel"
|
||||
:class="$style.profile"
|
||||
@click="$event => menu($event, id)"
|
||||
@contextmenu.prevent.stop="$event => menu($event, id)"
|
||||
>
|
||||
<div :class="$style.profileName">{{ profile.name }}</div>
|
||||
<div :class="$style.profileTime">{{ t('_preferencesBackups.createdAt', { date: (new Date(profile.createdAt)).toLocaleDateString(), time: (new Date(profile.createdAt)).toLocaleTimeString() }) }}</div>
|
||||
<div v-if="profile.updatedAt" :class="$style.profileTime">{{ t('_preferencesBackups.updatedAt', { date: (new Date(profile.updatedAt)).toLocaleDateString(), time: (new Date(profile.updatedAt)).toLocaleTimeString() }) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="profiles">
|
||||
<MkInfo>{{ ts._preferencesBackups.noBackups }}</MkInfo>
|
||||
</div>
|
||||
<MkLoading v-else/>
|
||||
</FormSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, useCssModule } from 'vue';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import * as os from '@/os';
|
||||
import { ColdDeviceStorage, defaultStore } from '@/store';
|
||||
import { unisonReload } from '@/scripts/unison-reload';
|
||||
import { stream } from '@/stream';
|
||||
import { $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
import { version, host } from '@/config';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
const { t, ts } = i18n;
|
||||
|
||||
useCssModule();
|
||||
|
||||
const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
|
||||
'menu',
|
||||
'visibility',
|
||||
'localOnly',
|
||||
'statusbars',
|
||||
'widgets',
|
||||
'tl',
|
||||
'overridedDeviceKind',
|
||||
'serverDisconnectedBehavior',
|
||||
'nsfw',
|
||||
'animation',
|
||||
'animatedMfm',
|
||||
'loadRawImages',
|
||||
'imageNewTab',
|
||||
'disableShowingAnimatedImages',
|
||||
'disablePagesScript',
|
||||
'useOsNativeEmojis',
|
||||
'disableDrawer',
|
||||
'useBlurEffectForModal',
|
||||
'useBlurEffect',
|
||||
'showFixedPostForm',
|
||||
'enableInfiniteScroll',
|
||||
'useReactionPickerForContextMenu',
|
||||
'showGapBetweenNotesInTimeline',
|
||||
'instanceTicker',
|
||||
'reactionPickerSize',
|
||||
'reactionPickerWidth',
|
||||
'reactionPickerHeight',
|
||||
'reactionPickerUseDrawerForMobile',
|
||||
'defaultSideView',
|
||||
'menuDisplay',
|
||||
'reportError',
|
||||
'squareAvatars',
|
||||
'numberOfPageCache',
|
||||
'aiChanMode',
|
||||
];
|
||||
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
|
||||
'lightTheme',
|
||||
'darkTheme',
|
||||
'syncDeviceDarkMode',
|
||||
'plugins',
|
||||
'mediaVolume',
|
||||
'sound_masterVolume',
|
||||
'sound_note',
|
||||
'sound_noteMy',
|
||||
'sound_notification',
|
||||
'sound_chat',
|
||||
'sound_chatBg',
|
||||
'sound_antenna',
|
||||
'sound_channel',
|
||||
];
|
||||
|
||||
const scope = ['clientPreferencesProfiles'];
|
||||
|
||||
const profileProps = ['name', 'createdAt', 'updatedAt', 'misskeyVersion', 'settings'];
|
||||
|
||||
type Profile = {
|
||||
name: string;
|
||||
createdAt: string;
|
||||
updatedAt: string | null;
|
||||
misskeyVersion: string;
|
||||
host: string;
|
||||
settings: {
|
||||
hot: Record<keyof typeof defaultStoreSaveKeys, unknown>;
|
||||
cold: Record<keyof typeof coldDeviceStorageSaveKeys, unknown>;
|
||||
fontSize: string | null;
|
||||
useSystemFont: 't' | null;
|
||||
wallpaper: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
const connection = $i && stream.useChannel('main');
|
||||
|
||||
let profiles = $ref<Record<string, Profile> | null>(null);
|
||||
|
||||
os.api('i/registry/get-all', { scope })
|
||||
.then(res => {
|
||||
profiles = res || {};
|
||||
});
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return value != null && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function validate(profile: unknown): void {
|
||||
if (!isObject(profile)) throw new Error('not an object');
|
||||
|
||||
// Check if unnecessary properties exist
|
||||
if (Object.keys(profile).some(key => !profileProps.includes(key))) throw new Error('Unnecessary properties exist');
|
||||
|
||||
if (!profile.name) throw new Error('Missing required prop: name');
|
||||
if (!profile.misskeyVersion) throw new Error('Missing required prop: misskeyVersion');
|
||||
|
||||
// Check if createdAt and updatedAt is Date
|
||||
// https://zenn.dev/lollipop_onl/articles/eoz-judge-js-invalid-date
|
||||
if (!profile.createdAt || Number.isNaN(new Date(profile.createdAt).getTime())) throw new Error('createdAt is falsy or not Date');
|
||||
if (profile.updatedAt) {
|
||||
if (Number.isNaN(new Date(profile.updatedAt).getTime())) {
|
||||
throw new Error('updatedAt is not Date');
|
||||
}
|
||||
} else if (profile.updatedAt !== null) {
|
||||
throw new Error('updatedAt is not null');
|
||||
}
|
||||
|
||||
if (!profile.settings) throw new Error('Missing required prop: settings');
|
||||
if (!isObject(profile.settings)) throw new Error('Invalid prop: settings');
|
||||
}
|
||||
|
||||
function getSettings(): Profile['settings'] {
|
||||
const hot = {} as Record<keyof typeof defaultStoreSaveKeys, unknown>;
|
||||
for (const key of defaultStoreSaveKeys) {
|
||||
hot[key] = defaultStore.state[key];
|
||||
}
|
||||
|
||||
const cold = {} as Record<keyof typeof coldDeviceStorageSaveKeys, unknown>;
|
||||
for (const key of coldDeviceStorageSaveKeys) {
|
||||
cold[key] = ColdDeviceStorage.get(key);
|
||||
}
|
||||
|
||||
return {
|
||||
hot,
|
||||
cold,
|
||||
fontSize: localStorage.getItem('fontSize'),
|
||||
useSystemFont: localStorage.getItem('useSystemFont') as 't' | null,
|
||||
wallpaper: localStorage.getItem('wallpaper'),
|
||||
};
|
||||
}
|
||||
|
||||
async function saveNew(): Promise<void> {
|
||||
if (!profiles) return;
|
||||
|
||||
const { canceled, result: name } = await os.inputText({
|
||||
title: ts._preferencesBackups.inputName,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
if (Object.values(profiles).some(x => x.name === name)) {
|
||||
return os.alert({
|
||||
title: ts._preferencesBackups.cannotSave,
|
||||
text: t('_preferencesBackups.nameAlreadyExists', { name }),
|
||||
});
|
||||
}
|
||||
|
||||
const id = uuid();
|
||||
const profile: Profile = {
|
||||
name,
|
||||
createdAt: (new Date()).toISOString(),
|
||||
updatedAt: null,
|
||||
misskeyVersion: version,
|
||||
host,
|
||||
settings: getSettings(),
|
||||
};
|
||||
await os.apiWithDialog('i/registry/set', { scope, key: id, value: profile });
|
||||
}
|
||||
|
||||
function loadFile(): void {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.multiple = false;
|
||||
input.onchange = async () => {
|
||||
if (!profiles) return;
|
||||
if (!input.files || input.files.length === 0) return;
|
||||
|
||||
const file = input.files[0];
|
||||
|
||||
if (file.type !== 'application/json') {
|
||||
return os.alert({
|
||||
type: 'error',
|
||||
title: ts._preferencesBackups.cannotLoad,
|
||||
text: ts._preferencesBackups.invalidFile,
|
||||
});
|
||||
}
|
||||
|
||||
let profile: Profile;
|
||||
try {
|
||||
profile = JSON.parse(await file.text()) as unknown as Profile;
|
||||
validate(profile);
|
||||
} catch (err) {
|
||||
return os.alert({
|
||||
type: 'error',
|
||||
title: ts._preferencesBackups.cannotLoad,
|
||||
text: err?.message,
|
||||
});
|
||||
}
|
||||
|
||||
const id = uuid();
|
||||
await os.apiWithDialog('i/registry/set', { scope, key: id, value: profile });
|
||||
|
||||
// 一応廃棄
|
||||
(window as any).__misskey_input_ref__ = null;
|
||||
};
|
||||
|
||||
// https://qiita.com/fukasawah/items/b9dc732d95d99551013d
|
||||
// iOS Safari で正常に動かす為のおまじない
|
||||
(window as any).__misskey_input_ref__ = input;
|
||||
|
||||
input.click();
|
||||
}
|
||||
|
||||
async function applyProfile(id: string): Promise<void> {
|
||||
if (!profiles) return;
|
||||
|
||||
const profile = profiles[id];
|
||||
|
||||
const { canceled: cancel1 } = await os.confirm({
|
||||
type: 'warning',
|
||||
title: ts._preferencesBackups.apply,
|
||||
text: t('_preferencesBackups.applyConfirm', { name: profile.name }),
|
||||
});
|
||||
if (cancel1) return;
|
||||
|
||||
// TODO: バージョン or ホストが違ったらさらに警告を表示
|
||||
|
||||
const settings = profile.settings;
|
||||
|
||||
// defaultStore
|
||||
for (const key of defaultStoreSaveKeys) {
|
||||
if (settings.hot[key] !== undefined) {
|
||||
defaultStore.set(key, settings.hot[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// coldDeviceStorage
|
||||
for (const key of coldDeviceStorageSaveKeys) {
|
||||
if (settings.cold[key] !== undefined) {
|
||||
ColdDeviceStorage.set(key, settings.cold[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// fontSize
|
||||
if (settings.fontSize) {
|
||||
localStorage.setItem('fontSize', settings.fontSize);
|
||||
} else {
|
||||
localStorage.removeItem('fontSize');
|
||||
}
|
||||
|
||||
// useSystemFont
|
||||
if (settings.useSystemFont) {
|
||||
localStorage.setItem('useSystemFont', settings.useSystemFont);
|
||||
} else {
|
||||
localStorage.removeItem('useSystemFont');
|
||||
}
|
||||
|
||||
// wallpaper
|
||||
if (settings.wallpaper != null) {
|
||||
localStorage.setItem('wallpaper', settings.wallpaper);
|
||||
} else {
|
||||
localStorage.removeItem('wallpaper');
|
||||
}
|
||||
|
||||
const { canceled: cancel2 } = await os.confirm({
|
||||
type: 'info',
|
||||
text: ts.reloadToApplySetting,
|
||||
});
|
||||
if (cancel2) return;
|
||||
|
||||
unisonReload();
|
||||
}
|
||||
|
||||
async function deleteProfile(id: string): Promise<void> {
|
||||
if (!profiles) return;
|
||||
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'info',
|
||||
title: ts.delete,
|
||||
text: t('deleteAreYouSure', { x: profiles[id].name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
await os.apiWithDialog('i/registry/remove', { scope, key: id });
|
||||
delete profiles[id];
|
||||
}
|
||||
|
||||
async function save(id: string): Promise<void> {
|
||||
if (!profiles) return;
|
||||
|
||||
const { name, createdAt } = profiles[id];
|
||||
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'info',
|
||||
title: ts._preferencesBackups.save,
|
||||
text: t('_preferencesBackups.saveConfirm', { name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
const profile: Profile = {
|
||||
name,
|
||||
createdAt,
|
||||
updatedAt: (new Date()).toISOString(),
|
||||
misskeyVersion: version,
|
||||
host,
|
||||
settings: getSettings(),
|
||||
};
|
||||
await os.apiWithDialog('i/registry/set', { scope, key: id, value: profile });
|
||||
}
|
||||
|
||||
async function rename(id: string): Promise<void> {
|
||||
if (!profiles) return;
|
||||
|
||||
const { canceled: cancel1, result: name } = await os.inputText({
|
||||
title: ts._preferencesBackups.inputName,
|
||||
});
|
||||
if (cancel1 || profiles[id].name === name) return;
|
||||
|
||||
if (Object.values(profiles).some(x => x.name === name)) {
|
||||
return os.alert({
|
||||
title: ts._preferencesBackups.cannotSave,
|
||||
text: t('_preferencesBackups.nameAlreadyExists', { name }),
|
||||
});
|
||||
}
|
||||
|
||||
const registry = Object.assign({}, { ...profiles[id] });
|
||||
|
||||
const { canceled: cancel2 } = await os.confirm({
|
||||
type: 'info',
|
||||
title: ts._preferencesBackups.rename,
|
||||
text: t('_preferencesBackups.renameConfirm', { old: registry.name, new: name }),
|
||||
});
|
||||
if (cancel2) return;
|
||||
|
||||
registry.name = name;
|
||||
await os.apiWithDialog('i/registry/set', { scope, key: id, value: registry });
|
||||
}
|
||||
|
||||
function menu(ev: MouseEvent, profileId: string) {
|
||||
if (!profiles) return;
|
||||
|
||||
return os.popupMenu([{
|
||||
text: ts._preferencesBackups.apply,
|
||||
icon: 'fas fa-circle-down',
|
||||
action: () => applyProfile(profileId),
|
||||
}, {
|
||||
type: 'a',
|
||||
text: ts.download,
|
||||
icon: 'fas fa-download',
|
||||
href: URL.createObjectURL(new Blob([JSON.stringify(profiles[profileId], null, 2)], { type: 'application/json' })),
|
||||
download: `${profiles[profileId].name}.json`,
|
||||
}, null, {
|
||||
text: ts.rename,
|
||||
icon: 'fas fa-i-cursor',
|
||||
action: () => rename(profileId),
|
||||
}, {
|
||||
text: ts._preferencesBackups.save,
|
||||
icon: 'fas fa-floppy-disk',
|
||||
action: () => save(profileId),
|
||||
}, null, {
|
||||
text: ts._preferencesBackups.delete,
|
||||
icon: 'fas fa-trash-can',
|
||||
action: () => deleteProfile(profileId),
|
||||
danger: true,
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// streamingのuser storage updateイベントを監視して更新
|
||||
connection?.on('registryUpdated', ({ scope: recievedScope, key, value }) => {
|
||||
if (!recievedScope || recievedScope.length !== scope.length || recievedScope[0] !== scope[0]) return;
|
||||
if (!profiles) return;
|
||||
|
||||
profiles[key] = value;
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
connection?.off('registryUpdated');
|
||||
});
|
||||
|
||||
definePageMetadata(computed(() => ({
|
||||
title: ts.preferencesBackups,
|
||||
icon: 'fas fa-floppy-disk',
|
||||
bg: 'var(--bg)',
|
||||
})));
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: var(--margin);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.profile {
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
|
||||
&Name {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&Time {
|
||||
font-size: .85em;
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,49 +1,54 @@
|
||||
<template>
|
||||
<div class="_formRoot">
|
||||
<FormSwitch v-model="isLocked" class="_formBlock" @update:modelValue="save()">{{ $ts.makeFollowManuallyApprove }}<template #caption>{{ $ts.lockedAccountInfo }}</template></FormSwitch>
|
||||
<FormSwitch v-if="isLocked" v-model="autoAcceptFollowed" class="_formBlock" @update:modelValue="save()">{{ $ts.autoAcceptFollowed }}</FormSwitch>
|
||||
<FormSwitch v-model="isLocked" class="_formBlock" @update:modelValue="save()">{{ i18n.ts.makeFollowManuallyApprove }}<template #caption>{{ i18n.ts.lockedAccountInfo }}</template></FormSwitch>
|
||||
<FormSwitch v-if="isLocked" v-model="autoAcceptFollowed" class="_formBlock" @update:modelValue="save()">{{ i18n.ts.autoAcceptFollowed }}</FormSwitch>
|
||||
|
||||
<FormSwitch v-model="publicReactions" class="_formBlock" @update:modelValue="save()">
|
||||
{{ $ts.makeReactionsPublic }}
|
||||
<template #caption>{{ $ts.makeReactionsPublicDescription }}</template>
|
||||
{{ i18n.ts.makeReactionsPublic }}
|
||||
<template #caption>{{ i18n.ts.makeReactionsPublicDescription }}</template>
|
||||
</FormSwitch>
|
||||
|
||||
<FormSelect v-model="ffVisibility" class="_formBlock" @update:modelValue="save()">
|
||||
<template #label>{{ $ts.ffVisibility }}</template>
|
||||
<option value="public">{{ $ts._ffVisibility.public }}</option>
|
||||
<option value="followers">{{ $ts._ffVisibility.followers }}</option>
|
||||
<option value="private">{{ $ts._ffVisibility.private }}</option>
|
||||
<template #caption>{{ $ts.ffVisibilityDescription }}</template>
|
||||
<template #label>{{ i18n.ts.ffVisibility }}</template>
|
||||
<option value="public">{{ i18n.ts._ffVisibility.public }}</option>
|
||||
<option value="followers">{{ i18n.ts._ffVisibility.followers }}</option>
|
||||
<option value="private">{{ i18n.ts._ffVisibility.private }}</option>
|
||||
<template #caption>{{ i18n.ts.ffVisibilityDescription }}</template>
|
||||
</FormSelect>
|
||||
|
||||
<FormSwitch v-model="hideOnlineStatus" class="_formBlock" @update:modelValue="save()">
|
||||
{{ $ts.hideOnlineStatus }}
|
||||
<template #caption>{{ $ts.hideOnlineStatusDescription }}</template>
|
||||
{{ i18n.ts.hideOnlineStatus }}
|
||||
<template #caption>{{ i18n.ts.hideOnlineStatusDescription }}</template>
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model="noCrawle" class="_formBlock" @update:modelValue="save()">
|
||||
{{ $ts.noCrawle }}
|
||||
<template #caption>{{ $ts.noCrawleDescription }}</template>
|
||||
{{ i18n.ts.noCrawle }}
|
||||
<template #caption>{{ i18n.ts.noCrawleDescription }}</template>
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model="isExplorable" class="_formBlock" @update:modelValue="save()">
|
||||
{{ $ts.makeExplorable }}
|
||||
<template #caption>{{ $ts.makeExplorableDescription }}</template>
|
||||
{{ i18n.ts.makeExplorable }}
|
||||
<template #caption>{{ i18n.ts.makeExplorableDescription }}</template>
|
||||
</FormSwitch>
|
||||
|
||||
<FormSection>
|
||||
<FormSwitch v-model="rememberNoteVisibility" class="_formBlock" @update:modelValue="save()">{{ $ts.rememberNoteVisibility }}</FormSwitch>
|
||||
<FormGroup v-if="!rememberNoteVisibility" class="_formBlock">
|
||||
<template #label>{{ $ts.defaultNoteVisibility }}</template>
|
||||
<FormSwitch v-model="rememberNoteVisibility" class="_formBlock" @update:modelValue="save()">{{ i18n.ts.rememberNoteVisibility }}</FormSwitch>
|
||||
<FormFolder v-if="!rememberNoteVisibility" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.defaultNoteVisibility }}</template>
|
||||
<template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template>
|
||||
<template v-else-if="defaultNoteVisibility === 'home'" #suffix>{{ i18n.ts._visibility.home }}</template>
|
||||
<template v-else-if="defaultNoteVisibility === 'followers'" #suffix>{{ i18n.ts._visibility.followers }}</template>
|
||||
<template v-else-if="defaultNoteVisibility === 'specified'" #suffix>{{ i18n.ts._visibility.specified }}</template>
|
||||
|
||||
<FormSelect v-model="defaultNoteVisibility" class="_formBlock">
|
||||
<option value="public">{{ $ts._visibility.public }}</option>
|
||||
<option value="home">{{ $ts._visibility.home }}</option>
|
||||
<option value="followers">{{ $ts._visibility.followers }}</option>
|
||||
<option value="specified">{{ $ts._visibility.specified }}</option>
|
||||
<option value="public">{{ i18n.ts._visibility.public }}</option>
|
||||
<option value="home">{{ i18n.ts._visibility.home }}</option>
|
||||
<option value="followers">{{ i18n.ts._visibility.followers }}</option>
|
||||
<option value="specified">{{ i18n.ts._visibility.specified }}</option>
|
||||
</FormSelect>
|
||||
<FormSwitch v-model="defaultNoteLocalOnly" class="_formBlock">{{ $ts._visibility.localOnly }}</FormSwitch>
|
||||
</FormGroup>
|
||||
<FormSwitch v-model="defaultNoteLocalOnly" class="_formBlock">{{ i18n.ts._visibility.localOnly }}</FormSwitch>
|
||||
</FormFolder>
|
||||
</FormSection>
|
||||
|
||||
<FormSwitch v-model="keepCw" class="_formBlock" @update:modelValue="save()">{{ $ts.keepCw }}</FormSwitch>
|
||||
<FormSwitch v-model="keepCw" class="_formBlock" @update:modelValue="save()">{{ i18n.ts.keepCw }}</FormSwitch>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -52,7 +57,7 @@ import { } from 'vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormSelect from '@/components/form/select.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import FormFolder from '@/components/form/folder.vue';
|
||||
import * as os from '@/os';
|
||||
import { defaultStore } from '@/store';
|
||||
import { i18n } from '@/i18n';
|
||||
@@ -91,6 +96,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.privacy,
|
||||
icon: 'fas fa-lock-open',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="_formRoot">
|
||||
<div class="llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
|
||||
<div class="avatar _acrylic">
|
||||
<div class="avatar">
|
||||
<MkAvatar class="avatar" :user="$i" :disable-link="true" @click="changeAvatar"/>
|
||||
<MkButton primary class="avatarEdit" @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
|
||||
<MkButton primary rounded class="avatarEdit" @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
|
||||
</div>
|
||||
<MkButton primary class="bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
|
||||
<MkButton primary rounded class="bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
|
||||
</div>
|
||||
|
||||
<FormInput v-model="profile.name" :max="30" manual-save class="_formBlock">
|
||||
@@ -39,10 +39,10 @@
|
||||
|
||||
<div class="_formRoot">
|
||||
<FormSplit v-for="(record, i) in fields" :min-width="250" class="_formBlock">
|
||||
<FormInput v-model="record.name">
|
||||
<FormInput v-model="record.name" small>
|
||||
<template #label>{{ i18n.ts._profile.metadataLabel }} #{{ i + 1 }}</template>
|
||||
</FormInput>
|
||||
<FormInput v-model="record.value">
|
||||
<FormInput v-model="record.value" small>
|
||||
<template #label>{{ i18n.ts._profile.metadataContent }} #{{ i + 1 }}</template>
|
||||
</FormInput>
|
||||
</FormSplit>
|
||||
@@ -56,14 +56,12 @@
|
||||
<FormSwitch v-model="profile.isCat" class="_formBlock">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></FormSwitch>
|
||||
<FormSwitch v-model="profile.showTimelineReplies" class="_formBlock">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></FormSwitch>
|
||||
<FormSwitch v-model="profile.isBot" class="_formBlock">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></FormSwitch>
|
||||
|
||||
<FormSwitch v-model="profile.alwaysMarkNsfw" class="_formBlock">{{ i18n.ts.alwaysMarkSensitive }}</FormSwitch>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, watch } from 'vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormTextarea from '@/components/form/textarea.vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
@@ -88,7 +86,6 @@ const profile = reactive({
|
||||
isBot: $i.isBot,
|
||||
isCat: $i.isCat,
|
||||
showTimelineReplies: $i.showTimelineReplies,
|
||||
alwaysMarkNsfw: $i.alwaysMarkNsfw,
|
||||
});
|
||||
|
||||
watch(() => profile, () => {
|
||||
@@ -126,7 +123,6 @@ function save() {
|
||||
isBot: !!profile.isBot,
|
||||
isCat: !!profile.isCat,
|
||||
showTimelineReplies: !!profile.showTimelineReplies,
|
||||
alwaysMarkNsfw: !!profile.alwaysMarkNsfw,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -183,7 +179,6 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.profile,
|
||||
icon: 'fas fa-user',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -192,6 +187,7 @@ definePageMetadata({
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: 10px;
|
||||
overflow: clip;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="_formRoot">
|
||||
<FromSlot class="_formBlock">
|
||||
<template #label>{{ $ts.reactionSettingDescription }}</template>
|
||||
<template #label>{{ i18n.ts.reactionSettingDescription }}</template>
|
||||
<div v-panel style="border-radius: 6px;">
|
||||
<XDraggable v-model="reactions" class="zoaiodol" :item-key="item => item" animation="150" delay="100" delay-on-touch-only="true">
|
||||
<template #item="{element}">
|
||||
@@ -14,17 +14,17 @@
|
||||
</template>
|
||||
</XDraggable>
|
||||
</div>
|
||||
<template #caption>{{ $ts.reactionSettingDescription2 }} <button class="_textButton" @click="preview">{{ $ts.preview }}</button></template>
|
||||
<template #caption>{{ i18n.ts.reactionSettingDescription2 }} <button class="_textButton" @click="preview">{{ i18n.ts.preview }}</button></template>
|
||||
</FromSlot>
|
||||
|
||||
<FormRadios v-model="reactionPickerSize" class="_formBlock">
|
||||
<template #label>{{ $ts.size }}</template>
|
||||
<option :value="1">{{ $ts.small }}</option>
|
||||
<option :value="2">{{ $ts.medium }}</option>
|
||||
<option :value="3">{{ $ts.large }}</option>
|
||||
<template #label>{{ i18n.ts.size }}</template>
|
||||
<option :value="1">{{ i18n.ts.small }}</option>
|
||||
<option :value="2">{{ i18n.ts.medium }}</option>
|
||||
<option :value="3">{{ i18n.ts.large }}</option>
|
||||
</FormRadios>
|
||||
<FormRadios v-model="reactionPickerWidth" class="_formBlock">
|
||||
<template #label>{{ $ts.numberOfColumn }}</template>
|
||||
<template #label>{{ i18n.ts.numberOfColumn }}</template>
|
||||
<option :value="1">5</option>
|
||||
<option :value="2">6</option>
|
||||
<option :value="3">7</option>
|
||||
@@ -32,22 +32,22 @@
|
||||
<option :value="5">9</option>
|
||||
</FormRadios>
|
||||
<FormRadios v-model="reactionPickerHeight" class="_formBlock">
|
||||
<template #label>{{ $ts.height }}</template>
|
||||
<option :value="1">{{ $ts.small }}</option>
|
||||
<option :value="2">{{ $ts.medium }}</option>
|
||||
<option :value="3">{{ $ts.large }}</option>
|
||||
<option :value="4">{{ $ts.large }}+</option>
|
||||
<template #label>{{ i18n.ts.height }}</template>
|
||||
<option :value="1">{{ i18n.ts.small }}</option>
|
||||
<option :value="2">{{ i18n.ts.medium }}</option>
|
||||
<option :value="3">{{ i18n.ts.large }}</option>
|
||||
<option :value="4">{{ i18n.ts.large }}+</option>
|
||||
</FormRadios>
|
||||
|
||||
<FormSwitch v-model="reactionPickerUseDrawerForMobile" class="_formBlock">
|
||||
{{ $ts.useDrawerReactionPickerForMobile }}
|
||||
<template #caption>{{ $ts.needReloadToApply }}</template>
|
||||
{{ i18n.ts.useDrawerReactionPickerForMobile }}
|
||||
<template #caption>{{ i18n.ts.needReloadToApply }}</template>
|
||||
</FormSwitch>
|
||||
|
||||
<FormSection>
|
||||
<div style="display: flex; gap: var(--margin); flex-wrap: wrap;">
|
||||
<FormButton inline @click="preview"><i class="fas fa-eye"></i> {{ $ts.preview }}</FormButton>
|
||||
<FormButton inline danger @click="setDefault"><i class="fas fa-undo"></i> {{ $ts.default }}</FormButton>
|
||||
<FormButton inline @click="preview"><i class="fas fa-eye"></i> {{ i18n.ts.preview }}</FormButton>
|
||||
<FormButton inline danger @click="setDefault"><i class="fas fa-undo"></i> {{ i18n.ts.default }}</FormButton>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
@@ -59,15 +59,16 @@ import XDraggable from 'vuedraggable';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormRadios from '@/components/form/radios.vue';
|
||||
import FromSlot from '@/components/form/slot.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import * as os from '@/os';
|
||||
import { defaultStore } from '@/store';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { deepClone } from '@/scripts/clone';
|
||||
|
||||
let reactions = $ref(JSON.parse(JSON.stringify(defaultStore.state.reactions)));
|
||||
let reactions = $ref(deepClone(defaultStore.state.reactions));
|
||||
|
||||
const reactionPickerSize = $computed(defaultStore.makeGetterSetter('reactionPickerSize'));
|
||||
const reactionPickerWidth = $computed(defaultStore.makeGetterSetter('reactionPickerWidth'));
|
||||
@@ -88,7 +89,7 @@ function remove(reaction, ev: MouseEvent) {
|
||||
}
|
||||
|
||||
function preview(ev: MouseEvent) {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
|
||||
asReactionPicker: true,
|
||||
src: ev.currentTarget ?? ev.target,
|
||||
}, {}, 'closed');
|
||||
@@ -101,7 +102,7 @@ async function setDefault() {
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
reactions = JSON.parse(JSON.stringify(defaultStore.def.reactions.default));
|
||||
reactions = deepClone(defaultStore.def.reactions.default);
|
||||
}
|
||||
|
||||
function chooseEmoji(ev: MouseEvent) {
|
||||
@@ -131,7 +132,6 @@ definePageMetadata({
|
||||
icon: 'fas fa-eye',
|
||||
handler: preview,
|
||||
},
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.signinHistory }}</template>
|
||||
<MkPagination :pagination="pagination">
|
||||
<MkPagination :pagination="pagination" disable-auto-load>
|
||||
<template #default="{items}">
|
||||
<div>
|
||||
<div v-for="item in items" :key="item.id" v-panel class="timnmucd">
|
||||
@@ -41,8 +41,8 @@
|
||||
import X2fa from './2fa.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormSlot from '@/components/form/slot.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import MkPagination from '@/components/ui/pagination.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
@@ -104,7 +104,6 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.security,
|
||||
icon: 'fas fa-lock',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import FormRange from '@/components/form/range.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import * as os from '@/os';
|
||||
@@ -90,7 +90,7 @@ async function edit(type) {
|
||||
},
|
||||
volume: {
|
||||
type: 'range',
|
||||
mim: 0,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
textConverter: (v) => `${Math.floor(v * 100)}%`,
|
||||
@@ -131,6 +131,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.sounds,
|
||||
icon: 'fas fa-music',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
140
packages/client/src/pages/settings/statusbar.statusbar.vue
Normal file
140
packages/client/src/pages/settings/statusbar.statusbar.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div class="_formRoot">
|
||||
<FormSelect v-model="statusbar.type" placeholder="Please select" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.type }}</template>
|
||||
<option value="rss">RSS</option>
|
||||
<option value="federation">Federation</option>
|
||||
<option value="userList">User list timeline</option>
|
||||
</FormSelect>
|
||||
|
||||
<MkInput v-model="statusbar.name" manual-save class="_formBlock">
|
||||
<template #label>{{ i18n.ts.label }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkSwitch v-model="statusbar.black" class="_formBlock">
|
||||
<template #label>Black</template>
|
||||
</MkSwitch>
|
||||
|
||||
<FormRadios v-model="statusbar.size" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.size }}</template>
|
||||
<option value="verySmall">{{ i18n.ts.small }}+</option>
|
||||
<option value="small">{{ i18n.ts.small }}</option>
|
||||
<option value="medium">{{ i18n.ts.medium }}</option>
|
||||
<option value="large">{{ i18n.ts.large }}</option>
|
||||
<option value="veryLarge">{{ i18n.ts.large }}+</option>
|
||||
</FormRadios>
|
||||
|
||||
<template v-if="statusbar.type === 'rss'">
|
||||
<MkInput v-model="statusbar.props.url" manual-save class="_formBlock" type="url">
|
||||
<template #label>URL</template>
|
||||
</MkInput>
|
||||
<MkSwitch v-model="statusbar.props.shuffle" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.shuffle }}</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="statusbar.props.refreshIntervalSec" manual-save class="_formBlock" type="number">
|
||||
<template #label>{{ i18n.ts.refreshInterval }}</template>
|
||||
</MkInput>
|
||||
<FormRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.speed }}</template>
|
||||
<template #caption>{{ i18n.ts.fast }} <-> {{ i18n.ts.slow }}</template>
|
||||
</FormRange>
|
||||
<MkSwitch v-model="statusbar.props.marqueeReverse" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.reverse }}</template>
|
||||
</MkSwitch>
|
||||
</template>
|
||||
<template v-else-if="statusbar.type === 'federation'">
|
||||
<MkInput v-model="statusbar.props.refreshIntervalSec" manual-save class="_formBlock" type="number">
|
||||
<template #label>{{ i18n.ts.refreshInterval }}</template>
|
||||
</MkInput>
|
||||
<FormRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.speed }}</template>
|
||||
<template #caption>{{ i18n.ts.fast }} <-> {{ i18n.ts.slow }}</template>
|
||||
</FormRange>
|
||||
<MkSwitch v-model="statusbar.props.marqueeReverse" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.reverse }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="statusbar.props.colored" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.colored }}</template>
|
||||
</MkSwitch>
|
||||
</template>
|
||||
<template v-else-if="statusbar.type === 'userList' && userLists != null">
|
||||
<FormSelect v-model="statusbar.props.userListId" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.userList }}</template>
|
||||
<option v-for="list in userLists" :value="list.id">{{ list.name }}</option>
|
||||
</FormSelect>
|
||||
<MkInput v-model="statusbar.props.refreshIntervalSec" manual-save class="_formBlock" type="number">
|
||||
<template #label>{{ i18n.ts.refreshInterval }}</template>
|
||||
</MkInput>
|
||||
<FormRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.speed }}</template>
|
||||
<template #caption>{{ i18n.ts.fast }} <-> {{ i18n.ts.slow }}</template>
|
||||
</FormRange>
|
||||
<MkSwitch v-model="statusbar.props.marqueeReverse" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.reverse }}</template>
|
||||
</MkSwitch>
|
||||
</template>
|
||||
|
||||
<div style="display: flex; gap: var(--margin); flex-wrap: wrap;">
|
||||
<FormButton danger @click="del">{{ i18n.ts.remove }}</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import FormSelect from '@/components/form/select.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import MkSwitch from '@/components/form/switch.vue';
|
||||
import FormRadios from '@/components/form/radios.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import FormRange from '@/components/form/range.vue';
|
||||
import * as os from '@/os';
|
||||
import { defaultStore } from '@/store';
|
||||
import { i18n } from '@/i18n';
|
||||
import { deepClone } from '@/scripts/clone';
|
||||
|
||||
const props = defineProps<{
|
||||
_id: string;
|
||||
userLists: any[] | null;
|
||||
}>();
|
||||
|
||||
const statusbar = reactive(deepClone(defaultStore.state.statusbars.find(x => x.id === props._id)));
|
||||
|
||||
watch(() => statusbar.type, () => {
|
||||
if (statusbar.type === 'rss') {
|
||||
statusbar.name = 'NEWS';
|
||||
statusbar.props.url = 'http://feeds.afpbb.com/rss/afpbb/afpbbnews';
|
||||
statusbar.props.shuffle = true;
|
||||
statusbar.props.refreshIntervalSec = 120;
|
||||
statusbar.props.display = 'marquee';
|
||||
statusbar.props.marqueeDuration = 100;
|
||||
statusbar.props.marqueeReverse = false;
|
||||
} else if (statusbar.type === 'federation') {
|
||||
statusbar.name = 'FEDERATION';
|
||||
statusbar.props.refreshIntervalSec = 120;
|
||||
statusbar.props.display = 'marquee';
|
||||
statusbar.props.marqueeDuration = 100;
|
||||
statusbar.props.marqueeReverse = false;
|
||||
statusbar.props.colored = false;
|
||||
} else if (statusbar.type === 'userList') {
|
||||
statusbar.name = 'LIST TL';
|
||||
statusbar.props.refreshIntervalSec = 120;
|
||||
statusbar.props.display = 'marquee';
|
||||
statusbar.props.marqueeDuration = 100;
|
||||
statusbar.props.marqueeReverse = false;
|
||||
}
|
||||
});
|
||||
|
||||
watch(statusbar, save);
|
||||
|
||||
async function save() {
|
||||
const i = defaultStore.state.statusbars.findIndex(x => x.id === props._id);
|
||||
const statusbars = deepClone(defaultStore.state.statusbars);
|
||||
statusbars[i] = deepClone(statusbar);
|
||||
defaultStore.set('statusbars', statusbars);
|
||||
}
|
||||
|
||||
function del() {
|
||||
defaultStore.set('statusbars', defaultStore.state.statusbars.filter(x => x.id !== props._id));
|
||||
}
|
||||
</script>
|
||||
54
packages/client/src/pages/settings/statusbar.vue
Normal file
54
packages/client/src/pages/settings/statusbar.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="_formRoot">
|
||||
<FormFolder v-for="x in statusbars" :key="x.id" class="_formBlock">
|
||||
<template #label>{{ x.type ?? i18n.ts.notSet }}</template>
|
||||
<template #suffix>{{ x.name }}</template>
|
||||
<XStatusbar :_id="x.id" :user-lists="userLists"/>
|
||||
</FormFolder>
|
||||
<FormButton primary @click="add">{{ i18n.ts.add }}</FormButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import XStatusbar from './statusbar.statusbar.vue';
|
||||
import FormRadios from '@/components/form/radios.vue';
|
||||
import FormFolder from '@/components/form/folder.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os';
|
||||
import { defaultStore } from '@/store';
|
||||
import { unisonReload } from '@/scripts/unison-reload';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
const statusbars = defaultStore.reactiveState.statusbars;
|
||||
|
||||
let userLists = $ref();
|
||||
|
||||
onMounted(() => {
|
||||
os.api('users/lists/list').then(res => {
|
||||
userLists = res;
|
||||
});
|
||||
});
|
||||
|
||||
async function add() {
|
||||
defaultStore.push('statusbars', {
|
||||
id: uuid(),
|
||||
type: null,
|
||||
black: false,
|
||||
size: 'medium',
|
||||
props: {},
|
||||
});
|
||||
}
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
const headerTabs = $computed(() => []);
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts.statusbar,
|
||||
icon: 'fas fa-list-ul',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
@@ -15,7 +15,7 @@
|
||||
import { } from 'vue';
|
||||
import JSON5 from 'json5';
|
||||
import FormTextarea from '@/components/form/textarea.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import { applyTheme, validateTheme } from '@/scripts/theme';
|
||||
import * as os from '@/os';
|
||||
import { addTheme, getThemes } from '@/theme-store';
|
||||
@@ -76,6 +76,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts._theme.install,
|
||||
icon: 'fas fa-download',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -31,7 +31,7 @@ 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 FormButton from '@/components/MkButton.vue';
|
||||
import { Theme, getBuiltinThemesRef } from '@/scripts/theme';
|
||||
import copyToClipboard from '@/scripts/copy-to-clipboard';
|
||||
import * as os from '@/os';
|
||||
@@ -74,6 +74,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts._theme.manage,
|
||||
icon: 'fas fa-folder-open',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="_formRoot">
|
||||
<div class="_formRoot rsljpzjq">
|
||||
<div v-adaptive-border class="rfqxtzch _panel _formBlock">
|
||||
<div class="toggle">
|
||||
<div class="toggleWrapper">
|
||||
<input id="dn" v-model="darkMode" type="checkbox" class="dn"/>
|
||||
<label for="dn" class="toggle">
|
||||
<span class="before">{{ $ts.light }}</span>
|
||||
<span class="after">{{ $ts.dark }}</span>
|
||||
<span class="before">{{ i18n.ts.light }}</span>
|
||||
<span class="after">{{ i18n.ts.dark }}</span>
|
||||
<span class="toggle__handler">
|
||||
<span class="crater crater--1"></span>
|
||||
<span class="crater crater--2"></span>
|
||||
@@ -22,66 +22,46 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="sync">
|
||||
<FormSwitch v-model="syncDeviceDarkMode">{{ $ts.syncDeviceDarkMode }}</FormSwitch>
|
||||
<FormSwitch v-model="syncDeviceDarkMode">{{ i18n.ts.syncDeviceDarkMode }}</FormSwitch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="darkMode">
|
||||
<FormSelect v-model="darkThemeId" class="_formBlock">
|
||||
<template #label>{{ $ts.themeForDarkMode }}</template>
|
||||
<template #prefix><i class="fas fa-moon"></i></template>
|
||||
<optgroup :label="$ts.darkThemes">
|
||||
<option v-for="x in darkThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
|
||||
</optgroup>
|
||||
<optgroup :label="$ts.lightThemes">
|
||||
<option v-for="x in lightThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
|
||||
</optgroup>
|
||||
</FormSelect>
|
||||
<FormSelect v-model="lightThemeId" class="_formBlock">
|
||||
<template #label>{{ $ts.themeForLightMode }}</template>
|
||||
<div class="selects _formBlock">
|
||||
<FormSelect v-model="lightThemeId" large class="select">
|
||||
<template #label>{{ i18n.ts.themeForLightMode }}</template>
|
||||
<template #prefix><i class="fas fa-sun"></i></template>
|
||||
<optgroup :label="$ts.lightThemes">
|
||||
<option v-for="x in lightThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
|
||||
<option v-if="instanceLightTheme" :key="'instance:' + instanceLightTheme.id" :value="instanceLightTheme.id">{{ instanceLightTheme.name }}</option>
|
||||
<optgroup v-if="installedLightThemes.length > 0" :label="i18n.ts._theme.installedThemes">
|
||||
<option v-for="x in installedLightThemes" :key="'installed:' + x.id" :value="x.id">{{ x.name }}</option>
|
||||
</optgroup>
|
||||
<optgroup :label="$ts.darkThemes">
|
||||
<option v-for="x in darkThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
|
||||
<optgroup :label="i18n.ts._theme.builtinThemes">
|
||||
<option v-for="x in builtinLightThemes" :key="'builtin:' + x.id" :value="x.id">{{ x.name }}</option>
|
||||
</optgroup>
|
||||
</FormSelect>
|
||||
</template>
|
||||
<template v-else>
|
||||
<FormSelect v-model="lightThemeId" class="_formBlock">
|
||||
<template #label>{{ $ts.themeForLightMode }}</template>
|
||||
<template #prefix><i class="fas fa-sun"></i></template>
|
||||
<optgroup :label="$ts.lightThemes">
|
||||
<option v-for="x in lightThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
|
||||
</optgroup>
|
||||
<optgroup :label="$ts.darkThemes">
|
||||
<option v-for="x in darkThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
|
||||
</optgroup>
|
||||
</FormSelect>
|
||||
<FormSelect v-model="darkThemeId" class="_formBlock">
|
||||
<template #label>{{ $ts.themeForDarkMode }}</template>
|
||||
<FormSelect v-model="darkThemeId" large class="select">
|
||||
<template #label>{{ i18n.ts.themeForDarkMode }}</template>
|
||||
<template #prefix><i class="fas fa-moon"></i></template>
|
||||
<optgroup :label="$ts.darkThemes">
|
||||
<option v-for="x in darkThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
|
||||
<option v-if="instanceDarkTheme" :key="'instance:' + instanceDarkTheme.id" :value="instanceDarkTheme.id">{{ instanceDarkTheme.name }}</option>
|
||||
<optgroup v-if="installedDarkThemes.length > 0" :label="i18n.ts._theme.installedThemes">
|
||||
<option v-for="x in installedDarkThemes" :key="'installed:' + x.id" :value="x.id">{{ x.name }}</option>
|
||||
</optgroup>
|
||||
<optgroup :label="$ts.lightThemes">
|
||||
<option v-for="x in lightThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
|
||||
<optgroup :label="i18n.ts._theme.builtinThemes">
|
||||
<option v-for="x in builtinDarkThemes" :key="'builtin:' + x.id" :value="x.id">{{ x.name }}</option>
|
||||
</optgroup>
|
||||
</FormSelect>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<FormSection>
|
||||
<div class="_formLinksGrid">
|
||||
<FormLink to="/settings/theme/manage"><template #icon><i class="fas fa-folder-open"></i></template>{{ $ts._theme.manage }}<template #suffix>{{ themesCount }}</template></FormLink>
|
||||
<FormLink to="https://assets.misskey.io/theme/list" external><template #icon><i class="fas fa-globe"></i></template>{{ $ts._theme.explore }}</FormLink>
|
||||
<FormLink to="/settings/theme/install"><template #icon><i class="fas fa-download"></i></template>{{ $ts._theme.install }}</FormLink>
|
||||
<FormLink to="/theme-editor"><template #icon><i class="fas fa-paint-roller"></i></template>{{ $ts._theme.make }}</FormLink>
|
||||
<FormLink to="/settings/theme/manage"><template #icon><i class="fas fa-folder-open"></i></template>{{ i18n.ts._theme.manage }}<template #suffix>{{ themesCount }}</template></FormLink>
|
||||
<FormLink to="https://assets.misskey.io/theme/list" external><template #icon><i class="fas fa-globe"></i></template>{{ i18n.ts._theme.explore }}</FormLink>
|
||||
<FormLink to="/settings/theme/install"><template #icon><i class="fas fa-download"></i></template>{{ i18n.ts._theme.install }}</FormLink>
|
||||
<FormLink to="/theme-editor"><template #icon><i class="fas fa-paint-roller"></i></template>{{ i18n.ts._theme.make }}</FormLink>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormButton v-if="wallpaper == null" class="_formBlock" @click="setWallpaper">{{ $ts.setWallpaper }}</FormButton>
|
||||
<FormButton v-else class="_formBlock" @click="wallpaper = null">{{ $ts.removeWallpaper }}</FormButton>
|
||||
<FormButton v-if="wallpaper == null" class="_formBlock" @click="setWallpaper">{{ i18n.ts.setWallpaper }}</FormButton>
|
||||
<FormButton v-else class="_formBlock" @click="wallpaper = null">{{ i18n.ts.removeWallpaper }}</FormButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -92,7 +72,7 @@ import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormSelect from '@/components/form/select.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import { getBuiltinThemesRef } from '@/scripts/theme';
|
||||
import { selectFile } from '@/scripts/select-file';
|
||||
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
|
||||
@@ -105,21 +85,25 @@ import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
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 instanceDarkTheme = computed(() => instance.defaultDarkTheme ? JSON5.parse(instance.defaultDarkTheme) : null);
|
||||
const installedDarkThemes = computed(() => installedThemes.value.filter(t => t.base === 'dark' || t.kind === 'dark'));
|
||||
const builtinDarkThemes = computed(() => builtinThemes.value.filter(t => t.base === 'dark' || t.kind === 'dark'));
|
||||
const instanceLightTheme = computed(() => instance.defaultLightTheme ? JSON5.parse(instance.defaultLightTheme) : null);
|
||||
const installedLightThemes = computed(() => installedThemes.value.filter(t => t.base === 'light' || t.kind === 'light'));
|
||||
const builtinLightThemes = computed(() => builtinThemes.value.filter(t => t.base === 'light' || t.kind === 'light'));
|
||||
const themes = computed(() => uniqueBy([ instanceDarkTheme.value, instanceLightTheme.value, ...builtinThemes.value, ...installedThemes.value ].filter(x => x != null), theme => theme.id));
|
||||
|
||||
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 t = themes.value.find(x => x.id === id);
|
||||
if (t) { // テーマエディタでテーマを作成したときなどは、themesに反映されないため undefined になる
|
||||
ColdDeviceStorage.set('darkTheme', t);
|
||||
}
|
||||
},
|
||||
});
|
||||
const lightTheme = ColdDeviceStorage.ref('lightTheme');
|
||||
@@ -128,7 +112,10 @@ const lightThemeId = computed({
|
||||
return lightTheme.value.id;
|
||||
},
|
||||
set(id) {
|
||||
ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id));
|
||||
const t = themes.value.find(x => x.id === id);
|
||||
if (t) { // テーマエディタでテーマを作成したときなどは、themesに反映されないため undefined になる
|
||||
ColdDeviceStorage.set('lightTheme', t);
|
||||
}
|
||||
},
|
||||
});
|
||||
const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
|
||||
@@ -174,7 +161,6 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.theme,
|
||||
icon: 'fas fa-palette',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -200,6 +186,7 @@ definePageMetadata({
|
||||
text-align: left;
|
||||
overflow: clip;
|
||||
padding: 0 100px;
|
||||
vertical-align: bottom;
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
@@ -406,4 +393,17 @@ definePageMetadata({
|
||||
border-top: solid 0.5px var(--divider);
|
||||
}
|
||||
}
|
||||
|
||||
.rsljpzjq {
|
||||
> .selects {
|
||||
display: flex;
|
||||
gap: 1.5em var(--margin);
|
||||
flex-wrap: wrap;
|
||||
|
||||
> .select {
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -38,13 +38,17 @@ import { } from 'vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
const props = defineProps<{
|
||||
webhookId: string;
|
||||
}>();
|
||||
|
||||
const webhook = await os.api('i/webhooks/show', {
|
||||
webhookId: new URLSearchParams(window.location.search).get('id'),
|
||||
webhookId: props.webhookId,
|
||||
});
|
||||
|
||||
let name = $ref(webhook.name);
|
||||
@@ -74,6 +78,7 @@ async function save(): Promise<void> {
|
||||
name,
|
||||
url,
|
||||
secret,
|
||||
webhookId: props.webhookId,
|
||||
on: events,
|
||||
active,
|
||||
});
|
||||
@@ -86,6 +91,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: 'Edit webhook',
|
||||
icon: 'fas fa-bolt',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -36,7 +36,7 @@ import { } from 'vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
@@ -78,6 +78,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: 'Create new webhook',
|
||||
icon: 'fas fa-bolt',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<FormSection>
|
||||
<MkPagination :pagination="pagination">
|
||||
<template #default="{items}">
|
||||
<FormLink v-for="webhook in items" :key="webhook.id" :to="`/settings/webhook/edit?id=${webhook.id}`" class="_formBlock">
|
||||
<FormLink v-for="webhook in items" :key="webhook.id" :to="`/settings/webhook/edit/${webhook.id}`" class="_formBlock">
|
||||
<template #icon>
|
||||
<i v-if="webhook.active === false" class="fas fa-circle-pause"></i>
|
||||
<i v-else-if="webhook.latestStatus === null" class="far fa-circle"></i>
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import MkPagination from '@/components/ui/pagination.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import { userPage } from '@/filters/user';
|
||||
@@ -49,6 +49,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: 'Webhook',
|
||||
icon: 'fas fa-bolt',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { 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';
|
||||
import MkInfo from '@/components/ui/info.vue';
|
||||
import MkTab from '@/components/tab.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkTab from '@/components/MkTab.vue';
|
||||
import * as os from '@/os';
|
||||
import number from '@/filters/number';
|
||||
import { defaultStore } from '@/store';
|
||||
@@ -124,6 +124,5 @@ const headerTabs = $computed(() => []);
|
||||
definePageMetadata({
|
||||
title: i18n.ts.wordMute,
|
||||
icon: 'fas fa-comment-slash',
|
||||
bg: 'var(--bg)',
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user