Frontend: Removed the update check
This commit is contained in:
parent
39f9ecd8d3
commit
40c471ff56
|
@ -2034,10 +2034,6 @@ export type Endpoints = {
|
||||||
req: NoParams;
|
req: NoParams;
|
||||||
res: ServerInfo;
|
res: ServerInfo;
|
||||||
};
|
};
|
||||||
"latest-version": {
|
|
||||||
req: NoParams;
|
|
||||||
res: TODO;
|
|
||||||
};
|
|
||||||
"sw/register": {
|
"sw/register": {
|
||||||
req: TODO;
|
req: TODO;
|
||||||
res: TODO;
|
res: TODO;
|
||||||
|
|
|
@ -1698,10 +1698,6 @@ export declare type Endpoints = {
|
||||||
req: NoParams;
|
req: NoParams;
|
||||||
res: ServerInfo;
|
res: ServerInfo;
|
||||||
};
|
};
|
||||||
"latest-version": {
|
|
||||||
req: NoParams;
|
|
||||||
res: TODO;
|
|
||||||
};
|
|
||||||
"sw/register": {
|
"sw/register": {
|
||||||
req: TODO;
|
req: TODO;
|
||||||
res: TODO;
|
res: TODO;
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {
|
||||||
App,
|
App,
|
||||||
AuthSession,
|
AuthSession,
|
||||||
Blocking,
|
Blocking,
|
||||||
Channel,
|
|
||||||
Clip,
|
Clip,
|
||||||
DateString,
|
DateString,
|
||||||
DetailedInstanceMetadata,
|
DetailedInstanceMetadata,
|
||||||
|
@ -17,24 +16,23 @@ import {
|
||||||
FollowRequest,
|
FollowRequest,
|
||||||
GalleryPost,
|
GalleryPost,
|
||||||
Instance,
|
Instance,
|
||||||
InstanceMetadata,
|
|
||||||
LiteInstanceMetadata,
|
LiteInstanceMetadata,
|
||||||
MeDetailed,
|
MeDetailed,
|
||||||
|
MessagingMessage,
|
||||||
Note,
|
Note,
|
||||||
NoteFavorite,
|
NoteFavorite,
|
||||||
|
NoteReaction,
|
||||||
|
Notification,
|
||||||
OriginType,
|
OriginType,
|
||||||
Page,
|
Page,
|
||||||
ServerInfo,
|
ServerInfo,
|
||||||
|
Signin,
|
||||||
Stats,
|
Stats,
|
||||||
User,
|
User,
|
||||||
UserDetailed,
|
UserDetailed,
|
||||||
UserGroup,
|
UserGroup,
|
||||||
UserList,
|
UserList,
|
||||||
UserSorting,
|
UserSorting,
|
||||||
Notification,
|
|
||||||
NoteReaction,
|
|
||||||
Signin,
|
|
||||||
MessagingMessage,
|
|
||||||
} from "./entities";
|
} from "./entities";
|
||||||
|
|
||||||
type TODO = Record<string, any> | null;
|
type TODO = Record<string, any> | null;
|
||||||
|
@ -758,10 +756,7 @@ export type Endpoints = {
|
||||||
$cases: [
|
$cases: [
|
||||||
[{ detail: true }, DetailedInstanceMetadata],
|
[{ detail: true }, DetailedInstanceMetadata],
|
||||||
[{ detail: false }, LiteInstanceMetadata],
|
[{ detail: false }, LiteInstanceMetadata],
|
||||||
[
|
[{ detail: boolean }, LiteInstanceMetadata | DetailedInstanceMetadata]
|
||||||
{ detail: boolean },
|
|
||||||
LiteInstanceMetadata | DetailedInstanceMetadata,
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
$default: LiteInstanceMetadata;
|
$default: LiteInstanceMetadata;
|
||||||
};
|
};
|
||||||
|
@ -977,9 +972,6 @@ export type Endpoints = {
|
||||||
// server-info
|
// server-info
|
||||||
"server-info": { req: NoParams; res: ServerInfo };
|
"server-info": { req: NoParams; res: ServerInfo };
|
||||||
|
|
||||||
// ck specific
|
|
||||||
"latest-version": { req: NoParams; res: TODO };
|
|
||||||
|
|
||||||
// sw
|
// sw
|
||||||
"sw/register": { req: TODO; res: TODO };
|
"sw/register": { req: TODO; res: TODO };
|
||||||
|
|
||||||
|
|
|
@ -1,93 +1,70 @@
|
||||||
<template>
|
<template>
|
||||||
<div ref="el" class="hiyeyicy" :class="{ wide: !narrow }">
|
<div ref="el" class="hiyeyicy" :class="{ wide: !narrow }">
|
||||||
<div v-if="!narrow || currentPage?.route.name == null" class="nav">
|
<div v-if="!narrow || currentPage?.route.name == null" class="nav">
|
||||||
<MkSpacer :content-max="700" :margin-min="16">
|
<MkSpacer :content-max="700" :margin-min="16">
|
||||||
<div class="lxpfedzu">
|
<div class="lxpfedzu">
|
||||||
<div class="banner">
|
<div class="banner">
|
||||||
<img
|
<img
|
||||||
:src="$instance.iconUrl || '/favicon.ico'"
|
:src="$instance.iconUrl || '/favicon.ico'"
|
||||||
alt=""
|
alt=""
|
||||||
class="icon"
|
class="icon"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkInfo
|
<MkInfo
|
||||||
v-if="thereIsUnresolvedAbuseReport"
|
v-if="thereIsUnresolvedAbuseReport"
|
||||||
warn
|
warn
|
||||||
class="info"
|
class="info"
|
||||||
>{{ i18n.ts.thereIsUnresolvedAbuseReportWarning }}
|
>{{ i18n.ts.thereIsUnresolvedAbuseReportWarning }}
|
||||||
<MkA to="/admin/abuses" class="_link">{{
|
<MkA to="/admin/abuses" class="_link">{{
|
||||||
i18n.ts.check
|
i18n.ts.check
|
||||||
}}</MkA></MkInfo
|
}}</MkA></MkInfo
|
||||||
>
|
>
|
||||||
<MkInfo v-if="noMaintainerInformation" warn class="info"
|
<MkInfo v-if="noMaintainerInformation" warn class="info"
|
||||||
>{{ i18n.ts.noMaintainerInformationWarning }}
|
>{{ i18n.ts.noMaintainerInformationWarning }}
|
||||||
<MkA to="/admin/settings" class="_link">{{
|
<MkA to="/admin/settings" class="_link">{{
|
||||||
i18n.ts.configure
|
i18n.ts.configure
|
||||||
}}</MkA></MkInfo
|
}}</MkA></MkInfo
|
||||||
>
|
>
|
||||||
<MkInfo v-if="noBotProtection" warn class="info"
|
<MkInfo v-if="noBotProtection" warn class="info"
|
||||||
>{{ i18n.ts.noBotProtectionWarning }}
|
>{{ i18n.ts.noBotProtectionWarning }}
|
||||||
<MkA to="/admin/security" class="_link">{{
|
<MkA to="/admin/security" class="_link">{{
|
||||||
i18n.ts.configure
|
i18n.ts.configure
|
||||||
}}</MkA></MkInfo
|
}}</MkA></MkInfo
|
||||||
>
|
>
|
||||||
<MkInfo v-if="noEmailServer" warn class="info"
|
<MkInfo v-if="noEmailServer" warn class="info"
|
||||||
>{{ i18n.ts.noEmailServerWarning }}
|
>{{ i18n.ts.noEmailServerWarning }}
|
||||||
<MkA to="/admin/email-settings" class="_link">{{
|
<MkA to="/admin/email-settings" class="_link">{{
|
||||||
i18n.ts.configure
|
i18n.ts.configure
|
||||||
}}</MkA></MkInfo
|
}}</MkA></MkInfo
|
||||||
>
|
>
|
||||||
<MkInfo v-if="updateAvailable" warn class="info"
|
|
||||||
>{{ i18n.ts.updateAvailable }}
|
|
||||||
<a
|
|
||||||
href="https://codeberg.org/calckey/calckey/releases"
|
|
||||||
target="_bank"
|
|
||||||
class="_link"
|
|
||||||
>{{ i18n.ts.check }}</a
|
|
||||||
></MkInfo
|
|
||||||
>
|
|
||||||
|
|
||||||
<MkSuperMenu :def="menuDef" :grid="narrow"></MkSuperMenu>
|
<MkSuperMenu :def="menuDef" :grid="narrow"></MkSuperMenu>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!(narrow && currentPage?.route.name == null)" class="main">
|
<div v-if="!(narrow && currentPage?.route.name == null)" class="main">
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { onActivated, onMounted, onUnmounted, provide, ref, watch } from "vue";
|
||||||
defineAsyncComponent,
|
|
||||||
inject,
|
|
||||||
nextTick,
|
|
||||||
onMounted,
|
|
||||||
onUnmounted,
|
|
||||||
onActivated,
|
|
||||||
provide,
|
|
||||||
watch,
|
|
||||||
ref,
|
|
||||||
} from "vue";
|
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import MkSuperMenu from "@/components/MkSuperMenu.vue";
|
import MkSuperMenu from "@/components/MkSuperMenu.vue";
|
||||||
import MkInfo from "@/components/MkInfo.vue";
|
import MkInfo from "@/components/MkInfo.vue";
|
||||||
import { scroll } from "@/scripts/scroll";
|
|
||||||
import { instance } from "@/instance";
|
import { instance } from "@/instance";
|
||||||
import { version } from "@/config";
|
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { lookupUser } from "@/scripts/lookup-user";
|
import { lookupUser } from "@/scripts/lookup-user";
|
||||||
import { lookupFile } from "@/scripts/lookup-file";
|
import { lookupFile } from "@/scripts/lookup-file";
|
||||||
import { lookupInstance } from "@/scripts/lookup-instance";
|
import { lookupInstance } from "@/scripts/lookup-instance";
|
||||||
import { indexPosts } from "@/scripts/index-posts";
|
import { indexPosts } from "@/scripts/index-posts";
|
||||||
import { defaultStore } from "@/store";
|
|
||||||
import { useRouter } from "@/router";
|
import { useRouter } from "@/router";
|
||||||
import {
|
import {
|
||||||
definePageMetadata,
|
definePageMetadata,
|
||||||
provideMetadataReceiver,
|
provideMetadataReceiver,
|
||||||
setPageMetadata,
|
|
||||||
} from "@/scripts/page-metadata";
|
} from "@/scripts/page-metadata";
|
||||||
|
|
||||||
const isEmpty = (x: string | null) => x == null || x === "";
|
const isEmpty = (x: string | null) => x == null || x === "";
|
||||||
|
@ -95,9 +72,9 @@ const el = ref<HTMLElement | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const indexInfo = {
|
const indexInfo = {
|
||||||
title: i18n.ts.controlPanel,
|
title: i18n.ts.controlPanel,
|
||||||
icon: "ph-gear-six ph-bold ph-lg",
|
icon: "ph-gear-six ph-bold ph-lg",
|
||||||
hideHeader: true,
|
hideHeader: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
provide("shouldOmitHeaderTitle", false);
|
provide("shouldOmitHeaderTitle", false);
|
||||||
|
@ -108,326 +85,312 @@ let narrow = $ref(false);
|
||||||
let view = $ref(null);
|
let view = $ref(null);
|
||||||
let pageProps = $ref({});
|
let pageProps = $ref({});
|
||||||
let noMaintainerInformation =
|
let noMaintainerInformation =
|
||||||
isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail);
|
isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail);
|
||||||
let noBotProtection =
|
let noBotProtection =
|
||||||
!instance.disableRegistration &&
|
!instance.disableRegistration &&
|
||||||
!instance.enableHcaptcha &&
|
!instance.enableHcaptcha &&
|
||||||
!instance.enableRecaptcha;
|
!instance.enableRecaptcha;
|
||||||
let noEmailServer = !instance.enableEmail;
|
let noEmailServer = !instance.enableEmail;
|
||||||
let thereIsUnresolvedAbuseReport = $ref(false);
|
let thereIsUnresolvedAbuseReport = $ref(false);
|
||||||
let updateAvailable = $ref(false);
|
|
||||||
let currentPage = $computed(() => router.currentRef.value.child);
|
let currentPage = $computed(() => router.currentRef.value.child);
|
||||||
|
|
||||||
os.api("admin/abuse-user-reports", {
|
os.api("admin/abuse-user-reports", {
|
||||||
state: "unresolved",
|
state: "unresolved",
|
||||||
limit: 1,
|
limit: 1,
|
||||||
}).then((reports) => {
|
}).then((reports) => {
|
||||||
if (reports?.length > 0) thereIsUnresolvedAbuseReport = true;
|
if (reports?.length > 0) thereIsUnresolvedAbuseReport = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (defaultStore.state.showAdminUpdates) {
|
|
||||||
os.api("latest-version").then((res) => {
|
|
||||||
const cleanRes = parseInt(res?.tag_name.replace(/[^0-9]/g, ""));
|
|
||||||
const cleanVersion = parseInt(version.replace(/[^0-9]/g, ""));
|
|
||||||
if (cleanRes > cleanVersion) {
|
|
||||||
updateAvailable = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const NARROW_THRESHOLD = 600;
|
const NARROW_THRESHOLD = 600;
|
||||||
const ro = new ResizeObserver((entries, observer) => {
|
const ro = new ResizeObserver((entries, observer) => {
|
||||||
if (entries.length === 0) return;
|
if (entries.length === 0) return;
|
||||||
narrow = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
|
narrow = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
|
||||||
});
|
});
|
||||||
|
|
||||||
const menuDef = $computed(() => [
|
const menuDef = $computed(() => [
|
||||||
{
|
{
|
||||||
title: i18n.ts.quickAction,
|
title: i18n.ts.quickAction,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
icon: "ph-magnifying-glass ph-bold ph-lg",
|
icon: "ph-magnifying-glass ph-bold ph-lg",
|
||||||
text: i18n.ts.lookup,
|
text: i18n.ts.lookup,
|
||||||
action: lookup,
|
action: lookup,
|
||||||
},
|
},
|
||||||
...(instance.disableRegistration
|
...(instance.disableRegistration
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
icon: "ph-user-plus ph-bold ph-lg",
|
icon: "ph-user-plus ph-bold ph-lg",
|
||||||
text: i18n.ts.invite,
|
text: i18n.ts.invite,
|
||||||
action: invite,
|
action: invite,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...($i.isAdmin
|
...($i.isAdmin
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
icon: "ph-list-magnifying-glass ph-bold ph-lg",
|
icon: "ph-list-magnifying-glass ph-bold ph-lg",
|
||||||
text: i18n.ts.indexPosts,
|
text: i18n.ts.indexPosts,
|
||||||
action: indexPosts,
|
action: indexPosts,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.ts.administration,
|
title: i18n.ts.administration,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
icon: "ph-gauge ph-bold ph-lg",
|
icon: "ph-gauge ph-bold ph-lg",
|
||||||
text: i18n.ts.dashboard,
|
text: i18n.ts.dashboard,
|
||||||
to: "/admin/overview",
|
to: "/admin/overview",
|
||||||
active: currentPage?.route.name === "overview",
|
active: currentPage?.route.name === "overview",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-users ph-bold ph-lg",
|
icon: "ph-users ph-bold ph-lg",
|
||||||
text: i18n.ts.users,
|
text: i18n.ts.users,
|
||||||
to: "/admin/users",
|
to: "/admin/users",
|
||||||
active: currentPage?.route.name === "users",
|
active: currentPage?.route.name === "users",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-smiley ph-bold ph-lg",
|
icon: "ph-smiley ph-bold ph-lg",
|
||||||
text: i18n.ts.customEmojis,
|
text: i18n.ts.customEmojis,
|
||||||
to: "/admin/emojis",
|
to: "/admin/emojis",
|
||||||
active: currentPage?.route.name === "emojis",
|
active: currentPage?.route.name === "emojis",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-planet ph-bold ph-lg",
|
icon: "ph-planet ph-bold ph-lg",
|
||||||
text: i18n.ts.federation,
|
text: i18n.ts.federation,
|
||||||
to: "/admin/federation",
|
to: "/admin/federation",
|
||||||
active: currentPage?.route.name === "federation",
|
active: currentPage?.route.name === "federation",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-queue ph-bold ph-lg",
|
icon: "ph-queue ph-bold ph-lg",
|
||||||
text: i18n.ts.jobQueue,
|
text: i18n.ts.jobQueue,
|
||||||
to: "/admin/queue",
|
to: "/admin/queue",
|
||||||
active: currentPage?.route.name === "queue",
|
active: currentPage?.route.name === "queue",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-cloud ph-bold ph-lg",
|
icon: "ph-cloud ph-bold ph-lg",
|
||||||
text: i18n.ts.files,
|
text: i18n.ts.files,
|
||||||
to: "/admin/files",
|
to: "/admin/files",
|
||||||
active: currentPage?.route.name === "files",
|
active: currentPage?.route.name === "files",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-megaphone-simple ph-bold ph-lg",
|
icon: "ph-megaphone-simple ph-bold ph-lg",
|
||||||
text: i18n.ts.announcements,
|
text: i18n.ts.announcements,
|
||||||
to: "/admin/announcements",
|
to: "/admin/announcements",
|
||||||
active: currentPage?.route.name === "announcements",
|
active: currentPage?.route.name === "announcements",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-money ph-bold ph-lg",
|
icon: "ph-money ph-bold ph-lg",
|
||||||
text: i18n.ts.ads,
|
text: i18n.ts.ads,
|
||||||
to: "/admin/ads",
|
to: "/admin/ads",
|
||||||
active: currentPage?.route.name === "ads",
|
active: currentPage?.route.name === "ads",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-warning-circle ph-bold ph-lg",
|
icon: "ph-warning-circle ph-bold ph-lg",
|
||||||
text: i18n.ts.abuseReports,
|
text: i18n.ts.abuseReports,
|
||||||
to: "/admin/abuses",
|
to: "/admin/abuses",
|
||||||
active: currentPage?.route.name === "abuses",
|
active: currentPage?.route.name === "abuses",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
...($i?.isAdmin
|
...($i?.isAdmin
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
title: i18n.ts.settings,
|
title: i18n.ts.settings,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
icon: "ph-gear-six ph-bold ph-lg",
|
icon: "ph-gear-six ph-bold ph-lg",
|
||||||
text: i18n.ts.general,
|
text: i18n.ts.general,
|
||||||
to: "/admin/settings",
|
to: "/admin/settings",
|
||||||
active: currentPage?.route.name === "settings",
|
active: currentPage?.route.name === "settings",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-envelope-simple-open ph-bold ph-lg",
|
icon: "ph-envelope-simple-open ph-bold ph-lg",
|
||||||
text: i18n.ts.emailServer,
|
text: i18n.ts.emailServer,
|
||||||
to: "/admin/email-settings",
|
to: "/admin/email-settings",
|
||||||
active:
|
active: currentPage?.route.name === "email-settings",
|
||||||
currentPage?.route.name === "email-settings",
|
},
|
||||||
},
|
{
|
||||||
{
|
icon: "ph-cloud ph-bold ph-lg",
|
||||||
icon: "ph-cloud ph-bold ph-lg",
|
text: i18n.ts.objectStorage,
|
||||||
text: i18n.ts.objectStorage,
|
to: "/admin/object-storage",
|
||||||
to: "/admin/object-storage",
|
active: currentPage?.route.name === "object-storage",
|
||||||
active:
|
},
|
||||||
currentPage?.route.name === "object-storage",
|
{
|
||||||
},
|
icon: "ph-lock ph-bold ph-lg",
|
||||||
{
|
text: i18n.ts.security,
|
||||||
icon: "ph-lock ph-bold ph-lg",
|
to: "/admin/security",
|
||||||
text: i18n.ts.security,
|
active: currentPage?.route.name === "security",
|
||||||
to: "/admin/security",
|
},
|
||||||
active: currentPage?.route.name === "security",
|
{
|
||||||
},
|
icon: "ph-flow-arrow ph-bold ph-lg",
|
||||||
{
|
text: i18n.ts.relays,
|
||||||
icon: "ph-flow-arrow ph-bold ph-lg",
|
to: "/admin/relays",
|
||||||
text: i18n.ts.relays,
|
active: currentPage?.route.name === "relays",
|
||||||
to: "/admin/relays",
|
},
|
||||||
active: currentPage?.route.name === "relays",
|
{
|
||||||
},
|
icon: "ph-plug ph-bold ph-lg",
|
||||||
{
|
text: i18n.ts.integration,
|
||||||
icon: "ph-plug ph-bold ph-lg",
|
to: "/admin/integrations",
|
||||||
text: i18n.ts.integration,
|
active: currentPage?.route.name === "integrations",
|
||||||
to: "/admin/integrations",
|
},
|
||||||
active: currentPage?.route.name === "integrations",
|
{
|
||||||
},
|
icon: "ph-prohibit ph-bold ph-lg",
|
||||||
{
|
text: i18n.ts.instanceBlocking,
|
||||||
icon: "ph-prohibit ph-bold ph-lg",
|
to: "/admin/instance-block",
|
||||||
text: i18n.ts.instanceBlocking,
|
active: currentPage?.route.name === "instance-block",
|
||||||
to: "/admin/instance-block",
|
},
|
||||||
active:
|
{
|
||||||
currentPage?.route.name === "instance-block",
|
icon: "ph-hash ph-bold ph-lg",
|
||||||
},
|
text: i18n.ts.hiddenTags,
|
||||||
{
|
to: "/admin/hashtags",
|
||||||
icon: "ph-hash ph-bold ph-lg",
|
active: currentPage?.route.name === "hashtags",
|
||||||
text: i18n.ts.hiddenTags,
|
},
|
||||||
to: "/admin/hashtags",
|
{
|
||||||
active: currentPage?.route.name === "hashtags",
|
icon: "ph-ghost ph-bold ph-lg",
|
||||||
},
|
text: i18n.ts.proxyAccount,
|
||||||
{
|
to: "/admin/proxy-account",
|
||||||
icon: "ph-ghost ph-bold ph-lg",
|
active: currentPage?.route.name === "proxy-account",
|
||||||
text: i18n.ts.proxyAccount,
|
},
|
||||||
to: "/admin/proxy-account",
|
{
|
||||||
active: currentPage?.route.name === "proxy-account",
|
icon: "ph-database ph-bold ph-lg",
|
||||||
},
|
text: i18n.ts.database,
|
||||||
{
|
to: "/admin/database",
|
||||||
icon: "ph-database ph-bold ph-lg",
|
active: currentPage?.route.name === "database",
|
||||||
text: i18n.ts.database,
|
},
|
||||||
to: "/admin/database",
|
{
|
||||||
active: currentPage?.route.name === "database",
|
icon: "ph-flask ph-bold ph-lg",
|
||||||
},
|
text: i18n.ts._experiments.title,
|
||||||
{
|
to: "/admin/experiments",
|
||||||
icon: "ph-flask ph-bold ph-lg",
|
active: currentPage?.route.name === "experiments",
|
||||||
text: i18n.ts._experiments.title,
|
},
|
||||||
to: "/admin/experiments",
|
],
|
||||||
active: currentPage?.route.name === "experiments",
|
},
|
||||||
},
|
]
|
||||||
],
|
: []),
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
watch(narrow, () => {
|
watch(narrow, () => {
|
||||||
if (currentPage?.route.name == null && !narrow) {
|
if (currentPage?.route.name == null && !narrow) {
|
||||||
router.push("/admin/overview");
|
router.push("/admin/overview");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
ro.observe(el.value);
|
ro.observe(el.value);
|
||||||
|
|
||||||
narrow = el.value.offsetWidth < NARROW_THRESHOLD;
|
narrow = el.value.offsetWidth < NARROW_THRESHOLD;
|
||||||
if (currentPage?.route.name == null && !narrow) {
|
if (currentPage?.route.name == null && !narrow) {
|
||||||
router.push("/admin/overview");
|
router.push("/admin/overview");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
narrow = el.value.offsetWidth < NARROW_THRESHOLD;
|
narrow = el.value.offsetWidth < NARROW_THRESHOLD;
|
||||||
|
|
||||||
if (!narrow && currentPage?.route.name == null) {
|
if (!narrow && currentPage?.route.name == null) {
|
||||||
router.replace("/admin/overview");
|
router.replace("/admin/overview");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
ro.disconnect();
|
ro.disconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(router.currentRef, (to) => {
|
watch(router.currentRef, (to) => {
|
||||||
if (to.route.path === "/admin" && to.child?.route.name == null && !narrow) {
|
if (to.route.path === "/admin" && to.child?.route.name == null && !narrow) {
|
||||||
router.replace("/admin/overview");
|
router.replace("/admin/overview");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
provideMetadataReceiver((info) => {
|
provideMetadataReceiver((info) => {
|
||||||
if (info == null) {
|
if (info == null) {
|
||||||
childInfo = null;
|
childInfo = null;
|
||||||
} else {
|
} else {
|
||||||
childInfo = info;
|
childInfo = info;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const invite = () => {
|
const invite = () => {
|
||||||
os.api("admin/invite")
|
os.api("admin/invite")
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: "info",
|
type: "info",
|
||||||
text: x.code,
|
text: x.code,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: "error",
|
type: "error",
|
||||||
text: err,
|
text: err,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
async function lookupNote() {
|
async function lookupNote() {
|
||||||
const { canceled, result: q } = await os.inputText({
|
const { canceled, result: q } = await os.inputText({
|
||||||
title: i18n.ts.noteId,
|
title: i18n.ts.noteId,
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
os.api(
|
os.api(
|
||||||
"notes/show",
|
"notes/show",
|
||||||
q.startsWith("http://") || q.startsWith("https://")
|
q.startsWith("http://") || q.startsWith("https://")
|
||||||
? { url: q.trim() }
|
? { url: q.trim() }
|
||||||
: { noteId: q.trim() }
|
: { noteId: q.trim() }
|
||||||
)
|
)
|
||||||
.then((note) => {
|
.then((note) => {
|
||||||
os.pageWindow(`/notes/${note.id}`);
|
os.pageWindow(`/notes/${note.id}`);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.code === "NO_SUCH_NOTE") {
|
if (err.code === "NO_SUCH_NOTE") {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: "error",
|
type: "error",
|
||||||
text: i18n.ts.notFound,
|
text: i18n.ts.notFound,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const lookup = (ev) => {
|
const lookup = (ev) => {
|
||||||
os.popupMenu(
|
os.popupMenu(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
text: i18n.ts.user,
|
text: i18n.ts.user,
|
||||||
icon: "ph-user ph-bold ph-lg",
|
icon: "ph-user ph-bold ph-lg",
|
||||||
action: () => {
|
action: () => {
|
||||||
lookupUser();
|
lookupUser();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.ts.note,
|
text: i18n.ts.note,
|
||||||
icon: "ph-pencil ph-bold ph-lg",
|
icon: "ph-pencil ph-bold ph-lg",
|
||||||
action: () => {
|
action: () => {
|
||||||
lookupNote();
|
lookupNote();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.ts.file,
|
text: i18n.ts.file,
|
||||||
icon: "ph-cloud ph-bold ph-lg",
|
icon: "ph-cloud ph-bold ph-lg",
|
||||||
action: () => {
|
action: () => {
|
||||||
lookupFile();
|
lookupFile();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.ts.instance,
|
text: i18n.ts.instance,
|
||||||
icon: "ph-planet ph-bold ph-lg",
|
icon: "ph-planet ph-bold ph-lg",
|
||||||
action: () => {
|
action: () => {
|
||||||
lookupInstance();
|
lookupInstance();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
ev.currentTarget ?? ev.target
|
ev.currentTarget ?? ev.target
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
const headerActions = $computed(() => []);
|
||||||
|
@ -437,51 +400,51 @@ const headerTabs = $computed(() => []);
|
||||||
definePageMetadata(INFO);
|
definePageMetadata(INFO);
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
header: {
|
header: {
|
||||||
title: i18n.ts.controlPanel,
|
title: i18n.ts.controlPanel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.hiyeyicy {
|
.hiyeyicy {
|
||||||
&.wide {
|
&.wide {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
> .nav {
|
> .nav {
|
||||||
width: 32%;
|
width: 32%;
|
||||||
max-width: 280px;
|
max-width: 280px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-right: solid 0.5px var(--divider);
|
border-right: solid 0.5px var(--divider);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .main {
|
> .main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .nav {
|
> .nav {
|
||||||
.lxpfedzu {
|
.lxpfedzu {
|
||||||
> .info {
|
> .info {
|
||||||
margin: 16px 0;
|
margin: 16px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .banner {
|
> .banner {
|
||||||
margin: 16px;
|
margin: 16px;
|
||||||
|
|
||||||
> .icon {
|
> .icon {
|
||||||
display: block;
|
display: block;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,136 +1,136 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="_formRoot">
|
<div class="_formRoot">
|
||||||
<FormSelect v-model="lang" class="_formBlock">
|
<FormSelect v-model="lang" class="_formBlock">
|
||||||
<template #label>{{ i18n.ts.uiLanguage }}</template>
|
<template #label>{{ i18n.ts.uiLanguage }}</template>
|
||||||
<option v-for="x in langs" :key="x[0]" :value="x[0]">
|
<option v-for="x in langs" :key="x[0]" :value="x[0]">
|
||||||
{{ x[1] }}
|
{{ x[1] }}
|
||||||
</option>
|
</option>
|
||||||
<template #caption>
|
<template #caption>
|
||||||
<I18n :src="i18n.ts.i18nInfo" tag="span">
|
<I18n :src="i18n.ts.i18nInfo" tag="span">
|
||||||
<template #link>
|
<template #link>
|
||||||
<MkLink url="https://hosted.weblate.org/engage/calckey/"
|
<MkLink url="https://hosted.weblate.org/engage/calckey/"
|
||||||
>Weblate</MkLink
|
>Weblate</MkLink
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</I18n>
|
</I18n>
|
||||||
</template>
|
</template>
|
||||||
</FormSelect>
|
</FormSelect>
|
||||||
|
|
||||||
<FormRadios v-model="overridedDeviceKind" class="_formBlock">
|
<FormRadios v-model="overridedDeviceKind" class="_formBlock">
|
||||||
<template #label>{{ i18n.ts.overridedDeviceKind }}</template>
|
<template #label>{{ i18n.ts.overridedDeviceKind }}</template>
|
||||||
<option :value="null">{{ i18n.ts.auto }}</option>
|
<option :value="null">{{ i18n.ts.auto }}</option>
|
||||||
<option value="smartphone">
|
<option value="smartphone">
|
||||||
<i class="ph-device-mobile ph-bold ph-lg" />
|
<i class="ph-device-mobile ph-bold ph-lg" />
|
||||||
{{ i18n.ts.smartphone }}
|
{{ i18n.ts.smartphone }}
|
||||||
</option>
|
</option>
|
||||||
<option value="tablet">
|
<option value="tablet">
|
||||||
<i class="ph-device-tablet ph-bold ph-lg" />
|
<i class="ph-device-tablet ph-bold ph-lg" />
|
||||||
{{ i18n.ts.tablet }}
|
{{ i18n.ts.tablet }}
|
||||||
</option>
|
</option>
|
||||||
<option value="desktop">
|
<option value="desktop">
|
||||||
<i class="ph-desktop ph-bold ph-lg" /> {{ i18n.ts.desktop }}
|
<i class="ph-desktop ph-bold ph-lg" /> {{ i18n.ts.desktop }}
|
||||||
</option>
|
</option>
|
||||||
</FormRadios>
|
</FormRadios>
|
||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>{{ i18n.ts.behavior }}</template>
|
<template #label>{{ i18n.ts.behavior }}</template>
|
||||||
<FormSwitch v-model="imageNewTab" class="_formBlock">{{
|
<FormSwitch v-model="imageNewTab" class="_formBlock">{{
|
||||||
i18n.ts.openImageInNewTab
|
i18n.ts.openImageInNewTab
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch v-model="enableInfiniteScroll" class="_formBlock">{{
|
<FormSwitch v-model="enableInfiniteScroll" class="_formBlock">{{
|
||||||
i18n.ts.enableInfiniteScroll
|
i18n.ts.enableInfiniteScroll
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch
|
<FormSwitch
|
||||||
v-model="useReactionPickerForContextMenu"
|
v-model="useReactionPickerForContextMenu"
|
||||||
class="_formBlock"
|
class="_formBlock"
|
||||||
>{{ i18n.ts.useReactionPickerForContextMenu }}</FormSwitch
|
>{{ i18n.ts.useReactionPickerForContextMenu }}</FormSwitch
|
||||||
>
|
>
|
||||||
<FormSwitch v-model="swipeOnDesktop" class="_formBlock">{{
|
<FormSwitch v-model="swipeOnDesktop" class="_formBlock">{{
|
||||||
i18n.ts.swipeOnDesktop
|
i18n.ts.swipeOnDesktop
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch v-model="enterSendsMessage" class="_formBlock">{{
|
<FormSwitch v-model="enterSendsMessage" class="_formBlock">{{
|
||||||
i18n.ts.enterSendsMessage
|
i18n.ts.enterSendsMessage
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch v-model="disablePagesScript" class="_formBlock">{{
|
<FormSwitch v-model="disablePagesScript" class="_formBlock">{{
|
||||||
i18n.ts.disablePagesScript
|
i18n.ts.disablePagesScript
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch v-model="showTimelineReplies" class="_formBlock"
|
<FormSwitch v-model="showTimelineReplies" class="_formBlock"
|
||||||
>{{ i18n.ts.flagShowTimelineReplies
|
>{{ i18n.ts.flagShowTimelineReplies
|
||||||
}}<template #caption
|
}}<template #caption
|
||||||
>{{ i18n.ts.flagShowTimelineRepliesDescription }}
|
>{{ i18n.ts.flagShowTimelineRepliesDescription }}
|
||||||
{{ i18n.ts.reflectMayTakeTime }}</template
|
{{ i18n.ts.reflectMayTakeTime }}</template
|
||||||
></FormSwitch
|
></FormSwitch
|
||||||
>
|
>
|
||||||
|
|
||||||
<FormSelect v-model="serverDisconnectedBehavior" class="_formBlock">
|
<FormSelect v-model="serverDisconnectedBehavior" class="_formBlock">
|
||||||
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
|
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
|
||||||
<option value="reload">
|
<option value="reload">
|
||||||
{{ i18n.ts._serverDisconnectedBehavior.reload }}
|
{{ i18n.ts._serverDisconnectedBehavior.reload }}
|
||||||
</option>
|
</option>
|
||||||
<option value="dialog">
|
<option value="dialog">
|
||||||
{{ i18n.ts._serverDisconnectedBehavior.dialog }}
|
{{ i18n.ts._serverDisconnectedBehavior.dialog }}
|
||||||
</option>
|
</option>
|
||||||
<option value="quiet">
|
<option value="quiet">
|
||||||
{{ i18n.ts._serverDisconnectedBehavior.quiet }}
|
{{ i18n.ts._serverDisconnectedBehavior.quiet }}
|
||||||
</option>
|
</option>
|
||||||
<option value="nothing">
|
<option value="nothing">
|
||||||
{{ i18n.ts._serverDisconnectedBehavior.nothing }}
|
{{ i18n.ts._serverDisconnectedBehavior.nothing }}
|
||||||
</option>
|
</option>
|
||||||
</FormSelect>
|
</FormSelect>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>{{ i18n.ts.accessibility }}</template>
|
<template #label>{{ i18n.ts.accessibility }}</template>
|
||||||
<FormSwitch v-model="expandOnNoteClick" class="_formBlock"
|
<FormSwitch v-model="expandOnNoteClick" class="_formBlock"
|
||||||
>{{ i18n.ts.expandOnNoteClick
|
>{{ i18n.ts.expandOnNoteClick
|
||||||
}}<template #caption>{{
|
}}<template #caption>{{
|
||||||
i18n.ts.expandOnNoteClickDesc
|
i18n.ts.expandOnNoteClickDesc
|
||||||
}}</template>
|
}}</template>
|
||||||
</FormSwitch>
|
</FormSwitch>
|
||||||
<FormSwitch v-model="advancedMfm" class="_formBlock">
|
<FormSwitch v-model="advancedMfm" class="_formBlock">
|
||||||
{{ i18n.ts._mfm.advanced
|
{{ i18n.ts._mfm.advanced
|
||||||
}}<template #caption>{{
|
}}<template #caption>{{
|
||||||
i18n.ts._mfm.advancedDescription
|
i18n.ts._mfm.advancedDescription
|
||||||
}}</template>
|
}}</template>
|
||||||
</FormSwitch>
|
</FormSwitch>
|
||||||
<FormSwitch v-model="autoplayMfm" class="_formBlock">
|
<FormSwitch v-model="autoplayMfm" class="_formBlock">
|
||||||
{{ i18n.ts._mfm.alwaysPlay }}
|
{{ i18n.ts._mfm.alwaysPlay }}
|
||||||
<template #caption>
|
<template #caption>
|
||||||
<i
|
<i
|
||||||
class="ph-warning ph-bold ph-lg"
|
class="ph-warning ph-bold ph-lg"
|
||||||
style="color: var(--warn)"
|
style="color: var(--warn)"
|
||||||
></i>
|
></i>
|
||||||
{{ i18n.ts._mfm.warn }}
|
{{ i18n.ts._mfm.warn }}
|
||||||
</template>
|
</template>
|
||||||
</FormSwitch>
|
</FormSwitch>
|
||||||
<FormSwitch v-model="reduceAnimation" class="_formBlock">{{
|
<FormSwitch v-model="reduceAnimation" class="_formBlock">{{
|
||||||
i18n.ts.reduceUiAnimation
|
i18n.ts.reduceUiAnimation
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch
|
<FormSwitch
|
||||||
v-model="disableShowingAnimatedImages"
|
v-model="disableShowingAnimatedImages"
|
||||||
class="_formBlock"
|
class="_formBlock"
|
||||||
>{{ i18n.ts.disableShowingAnimatedImages }}</FormSwitch
|
>{{ i18n.ts.disableShowingAnimatedImages }}</FormSwitch
|
||||||
>
|
>
|
||||||
<FormRadios v-model="fontSize" class="_formBlock">
|
<FormRadios v-model="fontSize" class="_formBlock">
|
||||||
<template #label>{{ i18n.ts.fontSize }}</template>
|
<template #label>{{ i18n.ts.fontSize }}</template>
|
||||||
<option :value="null">
|
<option :value="null">
|
||||||
<span style="font-size: 14px">14</span>
|
<span style="font-size: 14px">14</span>
|
||||||
</option>
|
</option>
|
||||||
<option value="15">
|
<option value="15">
|
||||||
<span style="font-size: 15px">15</span>
|
<span style="font-size: 15px">15</span>
|
||||||
</option>
|
</option>
|
||||||
<option value="16">
|
<option value="16">
|
||||||
<span style="font-size: 16px">16</span>
|
<span style="font-size: 16px">16</span>
|
||||||
</option>
|
</option>
|
||||||
<option value="17">
|
<option value="17">
|
||||||
<span style="font-size: 17px">17</span>
|
<span style="font-size: 17px">17</span>
|
||||||
</option>
|
</option>
|
||||||
<option value="18">
|
<option value="18">
|
||||||
<span style="font-size: 18px">18</span>
|
<span style="font-size: 18px">18</span>
|
||||||
</option>
|
</option>
|
||||||
</FormRadios>
|
</FormRadios>
|
||||||
|
|
||||||
<!-- <FormRange
|
<!-- <FormRange
|
||||||
v-model="fontSize"
|
v-model="fontSize"
|
||||||
:min="12"
|
:min="12"
|
||||||
:max="18"
|
:max="18"
|
||||||
|
@ -142,104 +142,97 @@
|
||||||
>
|
>
|
||||||
<template #label>{{ i18n.ts.fontSize }}</template>
|
<template #label>{{ i18n.ts.fontSize }}</template>
|
||||||
</FormRange> -->
|
</FormRange> -->
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>{{ i18n.ts.appearance }}</template>
|
<template #label>{{ i18n.ts.appearance }}</template>
|
||||||
<FormSwitch v-model="showAds" class="_formBlock">{{
|
<FormSwitch v-model="showAds" class="_formBlock">{{
|
||||||
i18n.ts.showAds
|
i18n.ts.showAds
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch v-model="useBlurEffect" class="_formBlock">{{
|
<FormSwitch v-model="useBlurEffect" class="_formBlock">{{
|
||||||
i18n.ts.useBlurEffect
|
i18n.ts.useBlurEffect
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch v-model="useBlurEffectForModal" class="_formBlock">{{
|
<FormSwitch v-model="useBlurEffectForModal" class="_formBlock">{{
|
||||||
i18n.ts.useBlurEffectForModal
|
i18n.ts.useBlurEffectForModal
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch
|
<FormSwitch
|
||||||
v-model="showGapBetweenNotesInTimeline"
|
v-model="showGapBetweenNotesInTimeline"
|
||||||
class="_formBlock"
|
class="_formBlock"
|
||||||
>{{ i18n.ts.showGapBetweenNotesInTimeline }}</FormSwitch
|
>{{ i18n.ts.showGapBetweenNotesInTimeline }}</FormSwitch
|
||||||
>
|
>
|
||||||
<FormSwitch v-model="loadRawImages" class="_formBlock">{{
|
<FormSwitch v-model="loadRawImages" class="_formBlock">{{
|
||||||
i18n.ts.loadRawImages
|
i18n.ts.loadRawImages
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch v-model="squareAvatars" class="_formBlock">{{
|
<FormSwitch v-model="squareAvatars" class="_formBlock">{{
|
||||||
i18n.ts.squareAvatars
|
i18n.ts.squareAvatars
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch v-model="seperateRenoteQuote" class="_formBlock">{{
|
<FormSwitch v-model="seperateRenoteQuote" class="_formBlock">{{
|
||||||
i18n.ts.seperateRenoteQuote
|
i18n.ts.seperateRenoteQuote
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch v-model="useSystemFont" class="_formBlock">{{
|
<FormSwitch v-model="useSystemFont" class="_formBlock">{{
|
||||||
i18n.ts.useSystemFont
|
i18n.ts.useSystemFont
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch v-model="useOsNativeEmojis" class="_formBlock">
|
<FormSwitch v-model="useOsNativeEmojis" class="_formBlock">
|
||||||
{{ i18n.ts.useOsNativeEmojis }}
|
{{ i18n.ts.useOsNativeEmojis }}
|
||||||
<div>
|
<div>
|
||||||
<Mfm :key="useOsNativeEmojis" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪" />
|
<Mfm :key="useOsNativeEmojis" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪" />
|
||||||
</div>
|
</div>
|
||||||
</FormSwitch>
|
</FormSwitch>
|
||||||
<FormSwitch v-model="disableDrawer" class="_formBlock">{{
|
<FormSwitch v-model="disableDrawer" class="_formBlock">{{
|
||||||
i18n.ts.disableDrawer
|
i18n.ts.disableDrawer
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch v-model="showUpdates" class="_formBlock">{{
|
<FormSwitch v-model="showUpdates" class="_formBlock">{{
|
||||||
i18n.ts.showUpdates
|
i18n.ts.showUpdates
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch v-model="showFixedPostForm" class="_formBlock">{{
|
<FormSwitch v-model="showFixedPostForm" class="_formBlock">{{
|
||||||
i18n.ts.showFixedPostForm
|
i18n.ts.showFixedPostForm
|
||||||
}}</FormSwitch>
|
}}</FormSwitch>
|
||||||
<FormSwitch
|
<FormSelect v-model="instanceTicker" class="_formBlock">
|
||||||
v-if="$i?.isAdmin"
|
<template #label>{{ i18n.ts.instanceTicker }}</template>
|
||||||
v-model="showAdminUpdates"
|
<option value="none">{{ i18n.ts._instanceTicker.none }}</option>
|
||||||
class="_formBlock"
|
<option value="remote">
|
||||||
>{{ i18n.ts.showAdminUpdates }}</FormSwitch
|
{{ i18n.ts._instanceTicker.remote }}
|
||||||
>
|
</option>
|
||||||
<FormSelect v-model="instanceTicker" class="_formBlock">
|
<option value="always">
|
||||||
<template #label>{{ i18n.ts.instanceTicker }}</template>
|
{{ i18n.ts._instanceTicker.always }}
|
||||||
<option value="none">{{ i18n.ts._instanceTicker.none }}</option>
|
</option>
|
||||||
<option value="remote">
|
</FormSelect>
|
||||||
{{ i18n.ts._instanceTicker.remote }}
|
|
||||||
</option>
|
|
||||||
<option value="always">
|
|
||||||
{{ i18n.ts._instanceTicker.always }}
|
|
||||||
</option>
|
|
||||||
</FormSelect>
|
|
||||||
|
|
||||||
<FormSelect v-model="nsfw" class="_formBlock">
|
<FormSelect v-model="nsfw" class="_formBlock">
|
||||||
<template #label>{{ i18n.ts.nsfw }}</template>
|
<template #label>{{ i18n.ts.nsfw }}</template>
|
||||||
<option value="respect">{{ i18n.ts._nsfw.respect }}</option>
|
<option value="respect">{{ i18n.ts._nsfw.respect }}</option>
|
||||||
<option value="ignore">{{ i18n.ts._nsfw.ignore }}</option>
|
<option value="ignore">{{ i18n.ts._nsfw.ignore }}</option>
|
||||||
<option value="force">{{ i18n.ts._nsfw.force }}</option>
|
<option value="force">{{ i18n.ts._nsfw.force }}</option>
|
||||||
</FormSelect>
|
</FormSelect>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
<FormRange
|
<FormRange
|
||||||
v-model="numberOfPageCache"
|
v-model="numberOfPageCache"
|
||||||
:min="1"
|
:min="1"
|
||||||
:max="10"
|
:max="10"
|
||||||
:step="1"
|
:step="1"
|
||||||
easing
|
easing
|
||||||
class="_formBlock"
|
class="_formBlock"
|
||||||
>
|
>
|
||||||
<template #label>{{ i18n.ts.numberOfPageCache }}</template>
|
<template #label>{{ i18n.ts.numberOfPageCache }}</template>
|
||||||
<template #caption>{{
|
<template #caption>{{
|
||||||
i18n.ts.numberOfPageCacheDescription
|
i18n.ts.numberOfPageCacheDescription
|
||||||
}}</template>
|
}}</template>
|
||||||
</FormRange>
|
</FormRange>
|
||||||
|
|
||||||
<FormLink to="/settings/deck" class="_formBlock">{{
|
<FormLink to="/settings/deck" class="_formBlock">{{
|
||||||
i18n.ts.deck
|
i18n.ts.deck
|
||||||
}}</FormLink>
|
}}</FormLink>
|
||||||
|
|
||||||
<FormLink to="/settings/custom-katex-macro" class="_formBlock"
|
<FormLink to="/settings/custom-katex-macro" class="_formBlock"
|
||||||
><template #icon><i class="ph-radical ph-bold ph-lg"></i></template
|
><template #icon><i class="ph-radical ph-bold ph-lg"></i></template
|
||||||
>{{ i18n.ts.customKaTeXMacro }}</FormLink
|
>{{ i18n.ts.customKaTeXMacro }}</FormLink
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, computed, ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { $i } from "@/account";
|
|
||||||
import FormSwitch from "@/components/form/switch.vue";
|
import FormSwitch from "@/components/form/switch.vue";
|
||||||
import FormSelect from "@/components/form/select.vue";
|
import FormSelect from "@/components/form/select.vue";
|
||||||
import FormRadios from "@/components/form/radios.vue";
|
import FormRadios from "@/components/form/radios.vue";
|
||||||
|
@ -259,136 +252,132 @@ const fontSize = ref(localStorage.getItem("fontSize"));
|
||||||
const useSystemFont = ref(localStorage.getItem("useSystemFont") != null);
|
const useSystemFont = ref(localStorage.getItem("useSystemFont") != null);
|
||||||
|
|
||||||
async function reloadAsk() {
|
async function reloadAsk() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: "info",
|
type: "info",
|
||||||
text: i18n.ts.reloadToApplySetting,
|
text: i18n.ts.reloadToApplySetting,
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
unisonReload();
|
unisonReload();
|
||||||
}
|
}
|
||||||
|
|
||||||
const overridedDeviceKind = computed(
|
const overridedDeviceKind = computed(
|
||||||
defaultStore.makeGetterSetter("overridedDeviceKind")
|
defaultStore.makeGetterSetter("overridedDeviceKind")
|
||||||
);
|
);
|
||||||
const serverDisconnectedBehavior = computed(
|
const serverDisconnectedBehavior = computed(
|
||||||
defaultStore.makeGetterSetter("serverDisconnectedBehavior")
|
defaultStore.makeGetterSetter("serverDisconnectedBehavior")
|
||||||
);
|
);
|
||||||
const reduceAnimation = computed(
|
const reduceAnimation = computed(
|
||||||
defaultStore.makeGetterSetter(
|
defaultStore.makeGetterSetter(
|
||||||
"animation",
|
"animation",
|
||||||
(v) => !v,
|
(v) => !v,
|
||||||
(v) => !v
|
(v) => !v
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const useBlurEffectForModal = computed(
|
const useBlurEffectForModal = computed(
|
||||||
defaultStore.makeGetterSetter("useBlurEffectForModal")
|
defaultStore.makeGetterSetter("useBlurEffectForModal")
|
||||||
);
|
);
|
||||||
const useBlurEffect = computed(defaultStore.makeGetterSetter("useBlurEffect"));
|
const useBlurEffect = computed(defaultStore.makeGetterSetter("useBlurEffect"));
|
||||||
const showGapBetweenNotesInTimeline = computed(
|
const showGapBetweenNotesInTimeline = computed(
|
||||||
defaultStore.makeGetterSetter("showGapBetweenNotesInTimeline")
|
defaultStore.makeGetterSetter("showGapBetweenNotesInTimeline")
|
||||||
);
|
);
|
||||||
const showAds = computed(defaultStore.makeGetterSetter("showAds"));
|
const showAds = computed(defaultStore.makeGetterSetter("showAds"));
|
||||||
const advancedMfm = computed(defaultStore.makeGetterSetter("advancedMfm"));
|
const advancedMfm = computed(defaultStore.makeGetterSetter("advancedMfm"));
|
||||||
const autoplayMfm = computed(
|
const autoplayMfm = computed(
|
||||||
defaultStore.makeGetterSetter(
|
defaultStore.makeGetterSetter(
|
||||||
"animatedMfm",
|
"animatedMfm",
|
||||||
(v) => !v,
|
(v) => !v,
|
||||||
(v) => !v
|
(v) => !v
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const useOsNativeEmojis = computed(
|
const useOsNativeEmojis = computed(
|
||||||
defaultStore.makeGetterSetter("useOsNativeEmojis")
|
defaultStore.makeGetterSetter("useOsNativeEmojis")
|
||||||
);
|
);
|
||||||
const disableDrawer = computed(defaultStore.makeGetterSetter("disableDrawer"));
|
const disableDrawer = computed(defaultStore.makeGetterSetter("disableDrawer"));
|
||||||
const disableShowingAnimatedImages = computed(
|
const disableShowingAnimatedImages = computed(
|
||||||
defaultStore.makeGetterSetter("disableShowingAnimatedImages")
|
defaultStore.makeGetterSetter("disableShowingAnimatedImages")
|
||||||
);
|
);
|
||||||
const loadRawImages = computed(defaultStore.makeGetterSetter("loadRawImages"));
|
const loadRawImages = computed(defaultStore.makeGetterSetter("loadRawImages"));
|
||||||
const imageNewTab = computed(defaultStore.makeGetterSetter("imageNewTab"));
|
const imageNewTab = computed(defaultStore.makeGetterSetter("imageNewTab"));
|
||||||
const nsfw = computed(defaultStore.makeGetterSetter("nsfw"));
|
const nsfw = computed(defaultStore.makeGetterSetter("nsfw"));
|
||||||
const disablePagesScript = computed(
|
const disablePagesScript = computed(
|
||||||
defaultStore.makeGetterSetter("disablePagesScript")
|
defaultStore.makeGetterSetter("disablePagesScript")
|
||||||
);
|
);
|
||||||
const expandOnNoteClick = computed(
|
const expandOnNoteClick = computed(
|
||||||
defaultStore.makeGetterSetter("expandOnNoteClick")
|
defaultStore.makeGetterSetter("expandOnNoteClick")
|
||||||
);
|
);
|
||||||
const showFixedPostForm = computed(
|
const showFixedPostForm = computed(
|
||||||
defaultStore.makeGetterSetter("showFixedPostForm")
|
defaultStore.makeGetterSetter("showFixedPostForm")
|
||||||
);
|
);
|
||||||
const numberOfPageCache = computed(
|
const numberOfPageCache = computed(
|
||||||
defaultStore.makeGetterSetter("numberOfPageCache")
|
defaultStore.makeGetterSetter("numberOfPageCache")
|
||||||
);
|
);
|
||||||
const instanceTicker = computed(
|
const instanceTicker = computed(
|
||||||
defaultStore.makeGetterSetter("instanceTicker")
|
defaultStore.makeGetterSetter("instanceTicker")
|
||||||
);
|
);
|
||||||
const enableInfiniteScroll = computed(
|
const enableInfiniteScroll = computed(
|
||||||
defaultStore.makeGetterSetter("enableInfiniteScroll")
|
defaultStore.makeGetterSetter("enableInfiniteScroll")
|
||||||
);
|
);
|
||||||
const enterSendsMessage = computed(
|
const enterSendsMessage = computed(
|
||||||
defaultStore.makeGetterSetter("enterSendsMessage")
|
defaultStore.makeGetterSetter("enterSendsMessage")
|
||||||
);
|
);
|
||||||
const useReactionPickerForContextMenu = computed(
|
const useReactionPickerForContextMenu = computed(
|
||||||
defaultStore.makeGetterSetter("useReactionPickerForContextMenu")
|
defaultStore.makeGetterSetter("useReactionPickerForContextMenu")
|
||||||
);
|
);
|
||||||
const seperateRenoteQuote = computed(
|
const seperateRenoteQuote = computed(
|
||||||
defaultStore.makeGetterSetter("seperateRenoteQuote")
|
defaultStore.makeGetterSetter("seperateRenoteQuote")
|
||||||
);
|
);
|
||||||
const squareAvatars = computed(defaultStore.makeGetterSetter("squareAvatars"));
|
const squareAvatars = computed(defaultStore.makeGetterSetter("squareAvatars"));
|
||||||
const showUpdates = computed(defaultStore.makeGetterSetter("showUpdates"));
|
const showUpdates = computed(defaultStore.makeGetterSetter("showUpdates"));
|
||||||
const swipeOnDesktop = computed(
|
const swipeOnDesktop = computed(
|
||||||
defaultStore.makeGetterSetter("swipeOnDesktop")
|
defaultStore.makeGetterSetter("swipeOnDesktop")
|
||||||
);
|
|
||||||
const showAdminUpdates = computed(
|
|
||||||
defaultStore.makeGetterSetter("showAdminUpdates")
|
|
||||||
);
|
);
|
||||||
const showTimelineReplies = computed(
|
const showTimelineReplies = computed(
|
||||||
defaultStore.makeGetterSetter("showTimelineReplies")
|
defaultStore.makeGetterSetter("showTimelineReplies")
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(lang, () => {
|
watch(lang, () => {
|
||||||
localStorage.setItem("lang", lang.value as string);
|
localStorage.setItem("lang", lang.value as string);
|
||||||
localStorage.removeItem("locale");
|
localStorage.removeItem("locale");
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(fontSize, () => {
|
watch(fontSize, () => {
|
||||||
if (fontSize.value == null) {
|
if (fontSize.value == null) {
|
||||||
localStorage.removeItem("fontSize");
|
localStorage.removeItem("fontSize");
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem("fontSize", fontSize.value);
|
localStorage.setItem("fontSize", fontSize.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(useSystemFont, () => {
|
watch(useSystemFont, () => {
|
||||||
if (useSystemFont.value) {
|
if (useSystemFont.value) {
|
||||||
localStorage.setItem("useSystemFont", "t");
|
localStorage.setItem("useSystemFont", "t");
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem("useSystemFont");
|
localStorage.removeItem("useSystemFont");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[
|
[
|
||||||
lang,
|
lang,
|
||||||
fontSize,
|
fontSize,
|
||||||
useSystemFont,
|
useSystemFont,
|
||||||
enableInfiniteScroll,
|
enableInfiniteScroll,
|
||||||
squareAvatars,
|
squareAvatars,
|
||||||
showGapBetweenNotesInTimeline,
|
showGapBetweenNotesInTimeline,
|
||||||
instanceTicker,
|
instanceTicker,
|
||||||
overridedDeviceKind,
|
overridedDeviceKind,
|
||||||
showAds,
|
showAds,
|
||||||
showUpdates,
|
showUpdates,
|
||||||
swipeOnDesktop,
|
swipeOnDesktop,
|
||||||
seperateRenoteQuote,
|
seperateRenoteQuote,
|
||||||
showAdminUpdates,
|
advancedMfm,
|
||||||
advancedMfm,
|
autoplayMfm,
|
||||||
autoplayMfm,
|
expandOnNoteClick,
|
||||||
expandOnNoteClick,
|
],
|
||||||
],
|
async () => {
|
||||||
async () => {
|
await reloadAsk();
|
||||||
await reloadAsk();
|
}
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
const headerActions = $computed(() => []);
|
||||||
|
@ -396,7 +385,7 @@ const headerActions = $computed(() => []);
|
||||||
const headerTabs = $computed(() => []);
|
const headerTabs = $computed(() => []);
|
||||||
|
|
||||||
definePageMetadata({
|
definePageMetadata({
|
||||||
title: i18n.ts.general,
|
title: i18n.ts.general,
|
||||||
icon: "ph-gear-six ph-bold ph-lg",
|
icon: "ph-gear-six ph-bold ph-lg",
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,58 +1,58 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="_formRoot">
|
<div class="_formRoot">
|
||||||
<div :class="$style.buttons">
|
<div :class="$style.buttons">
|
||||||
<MkButton inline primary @click="saveNew">{{
|
<MkButton inline primary @click="saveNew">{{
|
||||||
ts._preferencesBackups.saveNew
|
ts._preferencesBackups.saveNew
|
||||||
}}</MkButton>
|
}}</MkButton>
|
||||||
<MkButton inline @click="loadFile">{{
|
<MkButton inline @click="loadFile">{{
|
||||||
ts._preferencesBackups.loadFile
|
ts._preferencesBackups.loadFile
|
||||||
}}</MkButton>
|
}}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>{{ ts._preferencesBackups.list }}</template>
|
<template #label>{{ ts._preferencesBackups.list }}</template>
|
||||||
<template v-if="profiles && Object.keys(profiles).length > 0">
|
<template v-if="profiles && Object.keys(profiles).length > 0">
|
||||||
<div
|
<div
|
||||||
v-for="(profile, id) in profiles"
|
v-for="(profile, id) in profiles"
|
||||||
:key="id"
|
:key="id"
|
||||||
class="_formBlock _panel"
|
class="_formBlock _panel"
|
||||||
:class="$style.profile"
|
:class="$style.profile"
|
||||||
@click="($event) => menu($event, id)"
|
@click="($event) => menu($event, id)"
|
||||||
@contextmenu.prevent.stop="($event) => menu($event, id)"
|
@contextmenu.prevent.stop="($event) => menu($event, id)"
|
||||||
>
|
>
|
||||||
<div :class="$style.profileName">{{ profile.name }}</div>
|
<div :class="$style.profileName">{{ profile.name }}</div>
|
||||||
<div :class="$style.profileTime">
|
<div :class="$style.profileTime">
|
||||||
{{
|
{{
|
||||||
t("_preferencesBackups.createdAt", {
|
t("_preferencesBackups.createdAt", {
|
||||||
date: new Date(
|
date: new Date(
|
||||||
profile.createdAt
|
profile.createdAt
|
||||||
).toLocaleDateString(),
|
).toLocaleDateString(),
|
||||||
time: new Date(
|
time: new Date(
|
||||||
profile.createdAt
|
profile.createdAt
|
||||||
).toLocaleTimeString(),
|
).toLocaleTimeString(),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="profile.updatedAt" :class="$style.profileTime">
|
<div v-if="profile.updatedAt" :class="$style.profileTime">
|
||||||
{{
|
{{
|
||||||
t("_preferencesBackups.updatedAt", {
|
t("_preferencesBackups.updatedAt", {
|
||||||
date: new Date(
|
date: new Date(
|
||||||
profile.updatedAt
|
profile.updatedAt
|
||||||
).toLocaleDateString(),
|
).toLocaleDateString(),
|
||||||
time: new Date(
|
time: new Date(
|
||||||
profile.updatedAt
|
profile.updatedAt
|
||||||
).toLocaleTimeString(),
|
).toLocaleTimeString(),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div v-else-if="profiles">
|
<div v-else-if="profiles">
|
||||||
<MkInfo>{{ ts._preferencesBackups.noBackups }}</MkInfo>
|
<MkInfo>{{ ts._preferencesBackups.noBackups }}</MkInfo>
|
||||||
</div>
|
</div>
|
||||||
<MkLoading v-else />
|
<MkLoading v-else />
|
||||||
</FormSection>
|
</FormSection>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -67,95 +67,95 @@ import { unisonReload } from "@/scripts/unison-reload";
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { version, host } from "@/config";
|
import { host, version } from "@/config";
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
|
|
||||||
const { t, ts } = i18n;
|
const { t, ts } = i18n;
|
||||||
|
|
||||||
useCssModule();
|
useCssModule();
|
||||||
|
|
||||||
const defaultStoreSaveKeys: (keyof (typeof defaultStore)["state"])[] = [
|
const defaultStoreSaveKeys: (keyof (typeof defaultStore)["state"])[] = [
|
||||||
"menu",
|
"menu",
|
||||||
"visibility",
|
"visibility",
|
||||||
"localOnly",
|
"localOnly",
|
||||||
"statusbars",
|
"statusbars",
|
||||||
"widgets",
|
"widgets",
|
||||||
"tl",
|
"tl",
|
||||||
"overridedDeviceKind",
|
"overridedDeviceKind",
|
||||||
"serverDisconnectedBehavior",
|
"serverDisconnectedBehavior",
|
||||||
"nsfw",
|
"nsfw",
|
||||||
"showAds",
|
"showAds",
|
||||||
"animation",
|
"animation",
|
||||||
"animatedMfm",
|
"animatedMfm",
|
||||||
"loadRawImages",
|
"loadRawImages",
|
||||||
"imageNewTab",
|
"imageNewTab",
|
||||||
"disableShowingAnimatedImages",
|
"disableShowingAnimatedImages",
|
||||||
"disablePagesScript",
|
"disablePagesScript",
|
||||||
"enterSendsMessage",
|
"enterSendsMessage",
|
||||||
"useOsNativeEmojis",
|
"useOsNativeEmojis",
|
||||||
"disableDrawer",
|
"disableDrawer",
|
||||||
"useBlurEffectForModal",
|
"useBlurEffectForModal",
|
||||||
"useBlurEffect",
|
"useBlurEffect",
|
||||||
"showFixedPostForm",
|
"showFixedPostForm",
|
||||||
"enableInfiniteScroll",
|
"enableInfiniteScroll",
|
||||||
"useReactionPickerForContextMenu",
|
"useReactionPickerForContextMenu",
|
||||||
"showGapBetweenNotesInTimeline",
|
"showGapBetweenNotesInTimeline",
|
||||||
"instanceTicker",
|
"instanceTicker",
|
||||||
"reactionPickerSize",
|
"reactionPickerSize",
|
||||||
"reactionPickerWidth",
|
"reactionPickerWidth",
|
||||||
"reactionPickerHeight",
|
"reactionPickerHeight",
|
||||||
"reactionPickerUseDrawerForMobile",
|
"reactionPickerUseDrawerForMobile",
|
||||||
"defaultSideView",
|
"defaultSideView",
|
||||||
"menuDisplay",
|
"menuDisplay",
|
||||||
"reportError",
|
"reportError",
|
||||||
"squareAvatars",
|
"squareAvatars",
|
||||||
"numberOfPageCache",
|
"numberOfPageCache",
|
||||||
"showUpdates",
|
"showUpdates",
|
||||||
"swipeOnDesktop",
|
"swipeOnDesktop",
|
||||||
"showAdminUpdates",
|
"enableCustomKaTeXMacro",
|
||||||
"enableCustomKaTeXMacro",
|
"enableEmojiReactions",
|
||||||
"enableEmojiReactions",
|
"showEmojisInReactionNotifications",
|
||||||
"showEmojisInReactionNotifications",
|
"showTimelineReplies",
|
||||||
"showTimelineReplies",
|
|
||||||
];
|
];
|
||||||
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
|
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
|
||||||
"lightTheme",
|
"lightTheme",
|
||||||
"darkTheme",
|
"darkTheme",
|
||||||
"syncDeviceDarkMode",
|
"syncDeviceDarkMode",
|
||||||
"plugins",
|
"plugins",
|
||||||
"mediaVolume",
|
"mediaVolume",
|
||||||
"sound_masterVolume",
|
"sound_masterVolume",
|
||||||
"sound_note",
|
"sound_note",
|
||||||
"sound_noteMy",
|
"sound_noteMy",
|
||||||
"sound_notification",
|
"sound_notification",
|
||||||
"sound_chat",
|
"sound_chat",
|
||||||
"sound_chatBg",
|
"sound_chatBg",
|
||||||
"sound_antenna",
|
"sound_antenna",
|
||||||
"sound_channel",
|
"sound_channel",
|
||||||
];
|
];
|
||||||
|
|
||||||
const scope = ["clientPreferencesProfiles"];
|
const scope = ["clientPreferencesProfiles"];
|
||||||
|
|
||||||
const profileProps = [
|
const profileProps = [
|
||||||
"name",
|
"name",
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"updatedAt",
|
"updatedAt",
|
||||||
"misskeyVersion",
|
"misskeyVersion",
|
||||||
"settings",
|
"settings",
|
||||||
];
|
];
|
||||||
|
|
||||||
type Profile = {
|
type Profile = {
|
||||||
name: string;
|
name: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string | null;
|
updatedAt: string | null;
|
||||||
misskeyVersion: string;
|
misskeyVersion: string;
|
||||||
host: string;
|
host: string;
|
||||||
settings: {
|
settings: {
|
||||||
hot: Record<keyof typeof defaultStoreSaveKeys, unknown>;
|
hot: Record<keyof typeof defaultStoreSaveKeys, unknown>;
|
||||||
cold: Record<keyof typeof coldDeviceStorageSaveKeys, unknown>;
|
cold: Record<keyof typeof coldDeviceStorageSaveKeys, unknown>;
|
||||||
fontSize: string | null;
|
fontSize: string | null;
|
||||||
useSystemFont: "t" | null;
|
useSystemFont: "t" | null;
|
||||||
wallpaper: string | null;
|
wallpaper: string | null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const connection = $i && stream.useChannel("main");
|
const connection = $i && stream.useChannel("main");
|
||||||
|
@ -163,375 +163,375 @@ const connection = $i && stream.useChannel("main");
|
||||||
let profiles = $ref<Record<string, Profile> | null>(null);
|
let profiles = $ref<Record<string, Profile> | null>(null);
|
||||||
|
|
||||||
os.api("i/registry/get-all", { scope }).then((res) => {
|
os.api("i/registry/get-all", { scope }).then((res) => {
|
||||||
profiles = res || {};
|
profiles = res || {};
|
||||||
});
|
});
|
||||||
|
|
||||||
function isObject(value: unknown): value is Record<string, unknown> {
|
function isObject(value: unknown): value is Record<string, unknown> {
|
||||||
return value != null && typeof value === "object" && !Array.isArray(value);
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate(profile: unknown): void {
|
function validate(profile: unknown): void {
|
||||||
if (!isObject(profile)) throw new Error("not an object");
|
if (!isObject(profile)) throw new Error("not an object");
|
||||||
|
|
||||||
// Check if unnecessary properties exist
|
// Check if unnecessary properties exist
|
||||||
if (
|
if (
|
||||||
Object.keys(profile).some(
|
Object.keys(profile).some(
|
||||||
(key) => !profileProps.includes(key) && key !== "host"
|
(key) => !profileProps.includes(key) && key !== "host"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
throw new Error("Unnecessary properties exist");
|
throw new Error("Unnecessary properties exist");
|
||||||
|
|
||||||
if (!profile.name) throw new Error("Missing required prop: name");
|
if (!profile.name) throw new Error("Missing required prop: name");
|
||||||
if (!profile.misskeyVersion)
|
if (!profile.misskeyVersion)
|
||||||
throw new Error("Missing required prop: misskeyVersion");
|
throw new Error("Missing required prop: misskeyVersion");
|
||||||
|
|
||||||
// Check if createdAt and updatedAt is Date
|
// Check if createdAt and updatedAt is Date
|
||||||
// https://zenn.dev/lollipop_onl/articles/eoz-judge-js-invalid-date
|
// https://zenn.dev/lollipop_onl/articles/eoz-judge-js-invalid-date
|
||||||
if (
|
if (
|
||||||
!profile.createdAt ||
|
!profile.createdAt ||
|
||||||
Number.isNaN(new Date(profile.createdAt).getTime())
|
Number.isNaN(new Date(profile.createdAt).getTime())
|
||||||
)
|
)
|
||||||
throw new Error("createdAt is falsy or not Date");
|
throw new Error("createdAt is falsy or not Date");
|
||||||
if (profile.updatedAt) {
|
if (profile.updatedAt) {
|
||||||
if (Number.isNaN(new Date(profile.updatedAt).getTime())) {
|
if (Number.isNaN(new Date(profile.updatedAt).getTime())) {
|
||||||
throw new Error("updatedAt is not Date");
|
throw new Error("updatedAt is not Date");
|
||||||
}
|
}
|
||||||
} else if (profile.updatedAt !== null) {
|
} else if (profile.updatedAt !== null) {
|
||||||
throw new Error("updatedAt is not null");
|
throw new Error("updatedAt is not null");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!profile.settings) throw new Error("Missing required prop: settings");
|
if (!profile.settings) throw new Error("Missing required prop: settings");
|
||||||
if (!isObject(profile.settings)) throw new Error("Invalid prop: settings");
|
if (!isObject(profile.settings)) throw new Error("Invalid prop: settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSettings(): Profile["settings"] {
|
function getSettings(): Profile["settings"] {
|
||||||
const hot = {} as Record<keyof typeof defaultStoreSaveKeys, unknown>;
|
const hot = {} as Record<keyof typeof defaultStoreSaveKeys, unknown>;
|
||||||
for (const key of defaultStoreSaveKeys) {
|
for (const key of defaultStoreSaveKeys) {
|
||||||
hot[key] = defaultStore.state[key];
|
hot[key] = defaultStore.state[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
const cold = {} as Record<keyof typeof coldDeviceStorageSaveKeys, unknown>;
|
const cold = {} as Record<keyof typeof coldDeviceStorageSaveKeys, unknown>;
|
||||||
for (const key of coldDeviceStorageSaveKeys) {
|
for (const key of coldDeviceStorageSaveKeys) {
|
||||||
cold[key] = ColdDeviceStorage.get(key);
|
cold[key] = ColdDeviceStorage.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hot,
|
hot,
|
||||||
cold,
|
cold,
|
||||||
fontSize: localStorage.getItem("fontSize"),
|
fontSize: localStorage.getItem("fontSize"),
|
||||||
useSystemFont: localStorage.getItem("useSystemFont") as "t" | null,
|
useSystemFont: localStorage.getItem("useSystemFont") as "t" | null,
|
||||||
wallpaper: localStorage.getItem("wallpaper"),
|
wallpaper: localStorage.getItem("wallpaper"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveNew(): Promise<void> {
|
async function saveNew(): Promise<void> {
|
||||||
if (!profiles) return;
|
if (!profiles) return;
|
||||||
|
|
||||||
const { canceled, result: name } = await os.inputText({
|
const { canceled, result: name } = await os.inputText({
|
||||||
title: ts._preferencesBackups.inputName,
|
title: ts._preferencesBackups.inputName,
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
if (Object.values(profiles).some((x) => x.name === name)) {
|
if (Object.values(profiles).some((x) => x.name === name)) {
|
||||||
return os.alert({
|
return os.alert({
|
||||||
title: ts._preferencesBackups.cannotSave,
|
title: ts._preferencesBackups.cannotSave,
|
||||||
text: t("_preferencesBackups.nameAlreadyExists", { name }),
|
text: t("_preferencesBackups.nameAlreadyExists", { name }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
const profile: Profile = {
|
const profile: Profile = {
|
||||||
name,
|
name,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: null,
|
updatedAt: null,
|
||||||
misskeyVersion: version,
|
misskeyVersion: version,
|
||||||
host,
|
host,
|
||||||
settings: getSettings(),
|
settings: getSettings(),
|
||||||
};
|
};
|
||||||
await os.apiWithDialog("i/registry/set", {
|
await os.apiWithDialog("i/registry/set", {
|
||||||
scope,
|
scope,
|
||||||
key: id,
|
key: id,
|
||||||
value: profile,
|
value: profile,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFile(): void {
|
function loadFile(): void {
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
input.multiple = false;
|
input.multiple = false;
|
||||||
input.onchange = async () => {
|
input.onchange = async () => {
|
||||||
if (!profiles) return;
|
if (!profiles) return;
|
||||||
if (!input.files || input.files.length === 0) return;
|
if (!input.files || input.files.length === 0) return;
|
||||||
|
|
||||||
const file = input.files[0];
|
const file = input.files[0];
|
||||||
|
|
||||||
if (file.type !== "application/json") {
|
if (file.type !== "application/json") {
|
||||||
return os.alert({
|
return os.alert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: ts._preferencesBackups.cannotLoad,
|
title: ts._preferencesBackups.cannotLoad,
|
||||||
text: ts._preferencesBackups.invalidFile,
|
text: ts._preferencesBackups.invalidFile,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let profile: Profile;
|
let profile: Profile;
|
||||||
try {
|
try {
|
||||||
profile = JSON.parse(await file.text()) as unknown as Profile;
|
profile = JSON.parse(await file.text()) as unknown as Profile;
|
||||||
validate(profile);
|
validate(profile);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return os.alert({
|
return os.alert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: ts._preferencesBackups.cannotLoad,
|
title: ts._preferencesBackups.cannotLoad,
|
||||||
text: err?.message,
|
text: err?.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
await os.apiWithDialog("i/registry/set", {
|
await os.apiWithDialog("i/registry/set", {
|
||||||
scope,
|
scope,
|
||||||
key: id,
|
key: id,
|
||||||
value: profile,
|
value: profile,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 一応廃棄
|
// 一応廃棄
|
||||||
(window as any).__misskey_input_ref__ = null;
|
(window as any).__misskey_input_ref__ = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://qiita.com/fukasawah/items/b9dc732d95d99551013d
|
// https://qiita.com/fukasawah/items/b9dc732d95d99551013d
|
||||||
// iOS Safari で正常に動かす為のおまじない
|
// iOS Safari で正常に動かす為のおまじない
|
||||||
(window as any).__misskey_input_ref__ = input;
|
(window as any).__misskey_input_ref__ = input;
|
||||||
|
|
||||||
input.click();
|
input.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyProfile(id: string): Promise<void> {
|
async function applyProfile(id: string): Promise<void> {
|
||||||
if (!profiles) return;
|
if (!profiles) return;
|
||||||
|
|
||||||
const profile = profiles[id];
|
const profile = profiles[id];
|
||||||
|
|
||||||
const { canceled: cancel1 } = await os.confirm({
|
const { canceled: cancel1 } = await os.confirm({
|
||||||
type: "warning",
|
type: "warning",
|
||||||
title: ts._preferencesBackups.apply,
|
title: ts._preferencesBackups.apply,
|
||||||
text: t("_preferencesBackups.applyConfirm", { name: profile.name }),
|
text: t("_preferencesBackups.applyConfirm", { name: profile.name }),
|
||||||
});
|
});
|
||||||
if (cancel1) return;
|
if (cancel1) return;
|
||||||
|
|
||||||
// TODO: バージョン or ホストが違ったらさらに警告を表示
|
// TODO: バージョン or ホストが違ったらさらに警告を表示
|
||||||
|
|
||||||
const settings = profile.settings;
|
const settings = profile.settings;
|
||||||
|
|
||||||
// defaultStore
|
// defaultStore
|
||||||
for (const key of defaultStoreSaveKeys) {
|
for (const key of defaultStoreSaveKeys) {
|
||||||
if (settings.hot[key] !== undefined) {
|
if (settings.hot[key] !== undefined) {
|
||||||
defaultStore.set(key, settings.hot[key]);
|
defaultStore.set(key, settings.hot[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// coldDeviceStorage
|
// coldDeviceStorage
|
||||||
for (const key of coldDeviceStorageSaveKeys) {
|
for (const key of coldDeviceStorageSaveKeys) {
|
||||||
if (settings.cold[key] !== undefined) {
|
if (settings.cold[key] !== undefined) {
|
||||||
ColdDeviceStorage.set(key, settings.cold[key]);
|
ColdDeviceStorage.set(key, settings.cold[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fontSize
|
// fontSize
|
||||||
if (settings.fontSize) {
|
if (settings.fontSize) {
|
||||||
localStorage.setItem("fontSize", settings.fontSize);
|
localStorage.setItem("fontSize", settings.fontSize);
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem("fontSize");
|
localStorage.removeItem("fontSize");
|
||||||
}
|
}
|
||||||
|
|
||||||
// useSystemFont
|
// useSystemFont
|
||||||
if (settings.useSystemFont) {
|
if (settings.useSystemFont) {
|
||||||
localStorage.setItem("useSystemFont", settings.useSystemFont);
|
localStorage.setItem("useSystemFont", settings.useSystemFont);
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem("useSystemFont");
|
localStorage.removeItem("useSystemFont");
|
||||||
}
|
}
|
||||||
|
|
||||||
// wallpaper
|
// wallpaper
|
||||||
if (settings.wallpaper != null) {
|
if (settings.wallpaper != null) {
|
||||||
localStorage.setItem("wallpaper", settings.wallpaper);
|
localStorage.setItem("wallpaper", settings.wallpaper);
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem("wallpaper");
|
localStorage.removeItem("wallpaper");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { canceled: cancel2 } = await os.confirm({
|
const { canceled: cancel2 } = await os.confirm({
|
||||||
type: "info",
|
type: "info",
|
||||||
text: ts.reloadToApplySetting,
|
text: ts.reloadToApplySetting,
|
||||||
});
|
});
|
||||||
if (cancel2) return;
|
if (cancel2) return;
|
||||||
|
|
||||||
unisonReload();
|
unisonReload();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteProfile(id: string): Promise<void> {
|
async function deleteProfile(id: string): Promise<void> {
|
||||||
if (!profiles) return;
|
if (!profiles) return;
|
||||||
|
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: "info",
|
type: "info",
|
||||||
title: ts.delete,
|
title: ts.delete,
|
||||||
text: t("deleteAreYouSure", { x: profiles[id].name }),
|
text: t("deleteAreYouSure", { x: profiles[id].name }),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
await os.apiWithDialog("i/registry/remove", { scope, key: id });
|
await os.apiWithDialog("i/registry/remove", { scope, key: id });
|
||||||
delete profiles[id];
|
delete profiles[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save(id: string): Promise<void> {
|
async function save(id: string): Promise<void> {
|
||||||
if (!profiles) return;
|
if (!profiles) return;
|
||||||
|
|
||||||
const { name, createdAt } = profiles[id];
|
const { name, createdAt } = profiles[id];
|
||||||
|
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: "info",
|
type: "info",
|
||||||
title: ts._preferencesBackups.save,
|
title: ts._preferencesBackups.save,
|
||||||
text: t("_preferencesBackups.saveConfirm", { name }),
|
text: t("_preferencesBackups.saveConfirm", { name }),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
const profile: Profile = {
|
const profile: Profile = {
|
||||||
name,
|
name,
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
misskeyVersion: version,
|
misskeyVersion: version,
|
||||||
host,
|
host,
|
||||||
settings: getSettings(),
|
settings: getSettings(),
|
||||||
};
|
};
|
||||||
await os.apiWithDialog("i/registry/set", {
|
await os.apiWithDialog("i/registry/set", {
|
||||||
scope,
|
scope,
|
||||||
key: id,
|
key: id,
|
||||||
value: profile,
|
value: profile,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function rename(id: string): Promise<void> {
|
async function rename(id: string): Promise<void> {
|
||||||
if (!profiles) return;
|
if (!profiles) return;
|
||||||
|
|
||||||
const { canceled: cancel1, result: name } = await os.inputText({
|
const { canceled: cancel1, result: name } = await os.inputText({
|
||||||
title: ts._preferencesBackups.inputName,
|
title: ts._preferencesBackups.inputName,
|
||||||
});
|
});
|
||||||
if (cancel1 || profiles[id].name === name) return;
|
if (cancel1 || profiles[id].name === name) return;
|
||||||
|
|
||||||
if (Object.values(profiles).some((x) => x.name === name)) {
|
if (Object.values(profiles).some((x) => x.name === name)) {
|
||||||
return os.alert({
|
return os.alert({
|
||||||
title: ts._preferencesBackups.cannotSave,
|
title: ts._preferencesBackups.cannotSave,
|
||||||
text: t("_preferencesBackups.nameAlreadyExists", { name }),
|
text: t("_preferencesBackups.nameAlreadyExists", { name }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const registry = Object.assign({}, { ...profiles[id] });
|
const registry = Object.assign({}, { ...profiles[id] });
|
||||||
|
|
||||||
const { canceled: cancel2 } = await os.confirm({
|
const { canceled: cancel2 } = await os.confirm({
|
||||||
type: "info",
|
type: "info",
|
||||||
title: ts._preferencesBackups.rename,
|
title: ts._preferencesBackups.rename,
|
||||||
text: t("_preferencesBackups.renameConfirm", {
|
text: t("_preferencesBackups.renameConfirm", {
|
||||||
old: registry.name,
|
old: registry.name,
|
||||||
new: name,
|
new: name,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (cancel2) return;
|
if (cancel2) return;
|
||||||
|
|
||||||
registry.name = name;
|
registry.name = name;
|
||||||
await os.apiWithDialog("i/registry/set", {
|
await os.apiWithDialog("i/registry/set", {
|
||||||
scope,
|
scope,
|
||||||
key: id,
|
key: id,
|
||||||
value: registry,
|
value: registry,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function menu(ev: MouseEvent, profileId: string) {
|
function menu(ev: MouseEvent, profileId: string) {
|
||||||
if (!profiles) return;
|
if (!profiles) return;
|
||||||
|
|
||||||
return os.popupMenu(
|
return os.popupMenu(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
text: ts._preferencesBackups.apply,
|
text: ts._preferencesBackups.apply,
|
||||||
icon: "ph-caret-circle-down ph-bold ph-lg",
|
icon: "ph-caret-circle-down ph-bold ph-lg",
|
||||||
action: () => applyProfile(profileId),
|
action: () => applyProfile(profileId),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "a",
|
type: "a",
|
||||||
text: ts.download,
|
text: ts.download,
|
||||||
icon: "ph-download-simple ph-bold ph-lg",
|
icon: "ph-download-simple ph-bold ph-lg",
|
||||||
href: URL.createObjectURL(
|
href: URL.createObjectURL(
|
||||||
new Blob([JSON.stringify(profiles[profileId], null, 2)], {
|
new Blob([JSON.stringify(profiles[profileId], null, 2)], {
|
||||||
type: "application/json",
|
type: "application/json",
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
download: `${profiles[profileId].name}.json`,
|
download: `${profiles[profileId].name}.json`,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
text: ts.rename,
|
text: ts.rename,
|
||||||
icon: "ph-cursor-text ph-bold ph-lg",
|
icon: "ph-cursor-text ph-bold ph-lg",
|
||||||
action: () => rename(profileId),
|
action: () => rename(profileId),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: ts._preferencesBackups.save,
|
text: ts._preferencesBackups.save,
|
||||||
icon: "ph-floppy-disk ph-bold ph-lg",
|
icon: "ph-floppy-disk ph-bold ph-lg",
|
||||||
action: () => save(profileId),
|
action: () => save(profileId),
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
text: ts._preferencesBackups.delete,
|
text: ts._preferencesBackups.delete,
|
||||||
icon: "ph-trash ph-bold ph-lg",
|
icon: "ph-trash ph-bold ph-lg",
|
||||||
action: () => deleteProfile(profileId),
|
action: () => deleteProfile(profileId),
|
||||||
danger: true,
|
danger: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
ev.currentTarget ?? ev.target
|
ev.currentTarget ?? ev.target
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// streamingのuser storage updateイベントを監視して更新
|
// streamingのuser storage updateイベントを監視して更新
|
||||||
connection?.on(
|
connection?.on(
|
||||||
"registryUpdated",
|
"registryUpdated",
|
||||||
({ scope: recievedScope, key, value }) => {
|
({ scope: recievedScope, key, value }) => {
|
||||||
if (
|
if (
|
||||||
!recievedScope ||
|
!recievedScope ||
|
||||||
recievedScope.length !== scope.length ||
|
recievedScope.length !== scope.length ||
|
||||||
recievedScope[0] !== scope[0]
|
recievedScope[0] !== scope[0]
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
if (!profiles) return;
|
if (!profiles) return;
|
||||||
|
|
||||||
profiles[key] = value;
|
profiles[key] = value;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
connection?.off("registryUpdated");
|
connection?.off("registryUpdated");
|
||||||
});
|
});
|
||||||
|
|
||||||
definePageMetadata(
|
definePageMetadata(
|
||||||
computed(() => ({
|
computed(() => ({
|
||||||
title: ts.preferencesBackups,
|
title: ts.preferencesBackups,
|
||||||
icon: "ph-floppy-disk ph-bold ph-lg",
|
icon: "ph-floppy-disk ph-bold ph-lg",
|
||||||
bg: "var(--bg)",
|
bg: "var(--bg)",
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--margin);
|
gap: var(--margin);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile {
|
.profile {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&Name {
|
&Name {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
&Time {
|
&Time {
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { markRaw, ref } from "vue";
|
import { markRaw, ref } from "vue";
|
||||||
import { Storage } from "./pizzax";
|
import { Storage } from "./pizzax";
|
||||||
import { Theme } from "./scripts/theme";
|
/**
|
||||||
|
* 常にメモリにロードしておく必要がないような設定情報を保管するストレージ(非リアクティブ)
|
||||||
|
*/
|
||||||
|
import lightTheme from "@/themes/l-rosepinedawn.json5";
|
||||||
|
import darkTheme from "@/themes/d-rosepine.json5";
|
||||||
|
|
||||||
export const postFormActions = [];
|
export const postFormActions = [];
|
||||||
export const userActions = [];
|
export const userActions = [];
|
||||||
|
@ -9,334 +13,330 @@ export const noteViewInterruptors = [];
|
||||||
export const notePostInterruptors = [];
|
export const notePostInterruptors = [];
|
||||||
|
|
||||||
const menuOptions = [
|
const menuOptions = [
|
||||||
"notifications",
|
"notifications",
|
||||||
"followRequests",
|
"followRequests",
|
||||||
"explore",
|
"explore",
|
||||||
"favorites",
|
"favorites",
|
||||||
"search",
|
"search",
|
||||||
];
|
];
|
||||||
|
|
||||||
// TODO: それぞれいちいちwhereとかdefaultというキーを付けなきゃいけないの冗長なのでなんとかする(ただ型定義が面倒になりそう)
|
// TODO: それぞれいちいちwhereとかdefaultというキーを付けなきゃいけないの冗長なのでなんとかする(ただ型定義が面倒になりそう)
|
||||||
// あと、現行の定義の仕方なら「whereが何であるかに関わらずキー名の重複不可」という制約を付けられるメリットもあるからそのメリットを引き継ぐ方法も考えないといけない
|
// あと、現行の定義の仕方なら「whereが何であるかに関わらずキー名の重複不可」という制約を付けられるメリットもあるからそのメリットを引き継ぐ方法も考えないといけない
|
||||||
export const defaultStore = markRaw(
|
export const defaultStore = markRaw(
|
||||||
new Storage("base", {
|
new Storage("base", {
|
||||||
tutorial: {
|
tutorial: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
tlHomeHintClosed: {
|
tlHomeHintClosed: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
tlLocalHintClosed: {
|
tlLocalHintClosed: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
tlRecommendedHintClosed: {
|
tlRecommendedHintClosed: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
tlSocialHintClosed: {
|
tlSocialHintClosed: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
tlGlobalHintClosed: {
|
tlGlobalHintClosed: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
keepCw: {
|
keepCw: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
showFullAcct: {
|
showFullAcct: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
rememberNoteVisibility: {
|
rememberNoteVisibility: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
defaultNoteVisibility: {
|
defaultNoteVisibility: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: "public",
|
default: "public",
|
||||||
},
|
},
|
||||||
defaultNoteLocalOnly: {
|
defaultNoteLocalOnly: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
uploadFolder: {
|
uploadFolder: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: null as string | null,
|
default: null as string | null,
|
||||||
},
|
},
|
||||||
pastedFileName: {
|
pastedFileName: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: "yyyy-MM-dd HH-mm-ss [{{number}}]",
|
default: "yyyy-MM-dd HH-mm-ss [{{number}}]",
|
||||||
},
|
},
|
||||||
keepOriginalUploading: {
|
keepOriginalUploading: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
memo: {
|
memo: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
reactions: {
|
reactions: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: [
|
default: [
|
||||||
"⭐",
|
"⭐",
|
||||||
"❤️",
|
"❤️",
|
||||||
"😆",
|
"😆",
|
||||||
"🤔",
|
"🤔",
|
||||||
"😮",
|
"😮",
|
||||||
"🎉",
|
"🎉",
|
||||||
"💢",
|
"💢",
|
||||||
"😥",
|
"😥",
|
||||||
"😇",
|
"😇",
|
||||||
"🦋",
|
"🦋",
|
||||||
"🦊",
|
"🦊",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
mutedWords: {
|
mutedWords: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
mutedAds: {
|
mutedAds: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: [] as string[],
|
default: [] as string[],
|
||||||
},
|
},
|
||||||
showAds: {
|
showAds: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
where: "deviceAccount",
|
where: "deviceAccount",
|
||||||
default: menuOptions,
|
default: menuOptions,
|
||||||
},
|
},
|
||||||
visibility: {
|
visibility: {
|
||||||
where: "deviceAccount",
|
where: "deviceAccount",
|
||||||
default: "public" as "public" | "home" | "followers" | "specified",
|
default: "public" as "public" | "home" | "followers" | "specified",
|
||||||
},
|
},
|
||||||
localOnly: {
|
localOnly: {
|
||||||
where: "deviceAccount",
|
where: "deviceAccount",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
statusbars: {
|
statusbars: {
|
||||||
where: "deviceAccount",
|
where: "deviceAccount",
|
||||||
default: [] as {
|
default: [] as {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
size: "verySmall" | "small" | "medium" | "large" | "veryLarge";
|
size: "verySmall" | "small" | "medium" | "large" | "veryLarge";
|
||||||
black: boolean;
|
black: boolean;
|
||||||
props: Record<string, any>;
|
props: Record<string, any>;
|
||||||
}[],
|
}[],
|
||||||
},
|
},
|
||||||
widgets: {
|
widgets: {
|
||||||
where: "deviceAccount",
|
where: "deviceAccount",
|
||||||
default: [] as {
|
default: [] as {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
place: string | null;
|
place: string | null;
|
||||||
data: Record<string, any>;
|
data: Record<string, any>;
|
||||||
}[],
|
}[],
|
||||||
},
|
},
|
||||||
tl: {
|
tl: {
|
||||||
where: "deviceAccount",
|
where: "deviceAccount",
|
||||||
default: {
|
default: {
|
||||||
src: "home" as "home" | "local" | "social" | "global",
|
src: "home" as "home" | "local" | "social" | "global",
|
||||||
arg: null,
|
arg: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
overridedDeviceKind: {
|
overridedDeviceKind: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: null as null | "smartphone" | "tablet" | "desktop",
|
default: null as null | "smartphone" | "tablet" | "desktop",
|
||||||
},
|
},
|
||||||
serverDisconnectedBehavior: {
|
serverDisconnectedBehavior: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: "nothing" as "nothing" | "quiet" | "reload" | "dialog",
|
default: "nothing" as "nothing" | "quiet" | "reload" | "dialog",
|
||||||
},
|
},
|
||||||
seperateRenoteQuote: {
|
seperateRenoteQuote: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
expandOnNoteClick: {
|
expandOnNoteClick: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
nsfw: {
|
nsfw: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: "respect" as "respect" | "force" | "ignore",
|
default: "respect" as "respect" | "force" | "ignore",
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
advancedMfm: {
|
advancedMfm: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
animatedMfm: {
|
animatedMfm: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
animatedMfmWarnShown: {
|
animatedMfmWarnShown: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
loadRawImages: {
|
loadRawImages: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
imageNewTab: {
|
imageNewTab: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
disableShowingAnimatedImages: {
|
disableShowingAnimatedImages: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
disablePagesScript: {
|
disablePagesScript: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
useOsNativeEmojis: {
|
useOsNativeEmojis: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
disableDrawer: {
|
disableDrawer: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
useBlurEffectForModal: {
|
useBlurEffectForModal: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
useBlurEffect: {
|
useBlurEffect: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
showFixedPostForm: {
|
showFixedPostForm: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
enableInfiniteScroll: {
|
enableInfiniteScroll: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
useReactionPickerForContextMenu: {
|
useReactionPickerForContextMenu: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
showGapBetweenNotesInTimeline: {
|
showGapBetweenNotesInTimeline: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
darkMode: {
|
darkMode: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
instanceTicker: {
|
instanceTicker: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: "remote" as "none" | "remote" | "always",
|
default: "remote" as "none" | "remote" | "always",
|
||||||
},
|
},
|
||||||
reactionPickerSkinTone: {
|
reactionPickerSkinTone: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: 1,
|
default: 1,
|
||||||
},
|
},
|
||||||
reactionPickerSize: {
|
reactionPickerSize: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: 3,
|
default: 3,
|
||||||
},
|
},
|
||||||
reactionPickerWidth: {
|
reactionPickerWidth: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: 3,
|
default: 3,
|
||||||
},
|
},
|
||||||
reactionPickerHeight: {
|
reactionPickerHeight: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: 3,
|
default: 3,
|
||||||
},
|
},
|
||||||
reactionPickerUseDrawerForMobile: {
|
reactionPickerUseDrawerForMobile: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
recentlyUsedEmojis: {
|
recentlyUsedEmojis: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: [] as string[],
|
default: [] as string[],
|
||||||
},
|
},
|
||||||
recentlyUsedUsers: {
|
recentlyUsedUsers: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: [] as string[],
|
default: [] as string[],
|
||||||
},
|
},
|
||||||
defaultSideView: {
|
defaultSideView: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
menuDisplay: {
|
menuDisplay: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: "sideFull" as "sideFull" | "sideIcon" | "top",
|
default: "sideFull" as "sideFull" | "sideIcon" | "top",
|
||||||
},
|
},
|
||||||
reportError: {
|
reportError: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
squareAvatars: {
|
squareAvatars: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
postFormWithHashtags: {
|
postFormWithHashtags: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
postFormHashtags: {
|
postFormHashtags: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
themeInitial: {
|
themeInitial: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
numberOfPageCache: {
|
numberOfPageCache: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: 5,
|
default: 5,
|
||||||
},
|
},
|
||||||
enterSendsMessage: {
|
enterSendsMessage: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
showUpdates: {
|
showUpdates: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
swipeOnDesktop: {
|
swipeOnDesktop: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
showAdminUpdates: {
|
woozyMode: {
|
||||||
where: "account",
|
where: "device",
|
||||||
default: true,
|
default: false,
|
||||||
},
|
},
|
||||||
woozyMode: {
|
enableCustomKaTeXMacro: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
enableCustomKaTeXMacro: {
|
enableEmojiReactions: {
|
||||||
where: "device",
|
where: "account",
|
||||||
default: false,
|
default: true,
|
||||||
},
|
},
|
||||||
enableEmojiReactions: {
|
showEmojisInReactionNotifications: {
|
||||||
where: "account",
|
where: "account",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
showEmojisInReactionNotifications: {
|
showTimelineReplies: {
|
||||||
where: "account",
|
where: "device",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
showTimelineReplies: {
|
})
|
||||||
where: "device",
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: 他のタブと永続化されたstateを同期
|
// TODO: 他のタブと永続化されたstateを同期
|
||||||
|
@ -344,110 +344,106 @@ export const defaultStore = markRaw(
|
||||||
const PREFIX = "miux:";
|
const PREFIX = "miux:";
|
||||||
|
|
||||||
type Plugin = {
|
type Plugin = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
configData: Record<string, any>;
|
configData: Record<string, any>;
|
||||||
token: string;
|
token: string;
|
||||||
ast: any[];
|
ast: any[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 常にメモリにロードしておく必要がないような設定情報を保管するストレージ(非リアクティブ)
|
|
||||||
*/
|
|
||||||
import lightTheme from "@/themes/l-rosepinedawn.json5";
|
|
||||||
import darkTheme from "@/themes/d-rosepine.json5";
|
|
||||||
|
|
||||||
export class ColdDeviceStorage {
|
export class ColdDeviceStorage {
|
||||||
public static default = {
|
public static default = {
|
||||||
lightTheme,
|
lightTheme,
|
||||||
darkTheme,
|
darkTheme,
|
||||||
syncDeviceDarkMode: true,
|
syncDeviceDarkMode: true,
|
||||||
plugins: [] as Plugin[],
|
plugins: [] as Plugin[],
|
||||||
mediaVolume: 0.5,
|
mediaVolume: 0.5,
|
||||||
sound_masterVolume: 0.3,
|
sound_masterVolume: 0.3,
|
||||||
sound_note: { type: "none", volume: 0 },
|
sound_note: { type: "none", volume: 0 },
|
||||||
sound_noteMy: { type: "syuilo/up", volume: 1 },
|
sound_noteMy: { type: "syuilo/up", volume: 1 },
|
||||||
sound_notification: { type: "syuilo/pope2", volume: 1 },
|
sound_notification: { type: "syuilo/pope2", volume: 1 },
|
||||||
sound_chat: { type: "syuilo/pope1", volume: 1 },
|
sound_chat: { type: "syuilo/pope1", volume: 1 },
|
||||||
sound_chatBg: { type: "syuilo/waon", volume: 1 },
|
sound_chatBg: { type: "syuilo/waon", volume: 1 },
|
||||||
sound_antenna: { type: "syuilo/triple", volume: 1 },
|
sound_antenna: { type: "syuilo/triple", volume: 1 },
|
||||||
sound_channel: { type: "syuilo/square-pico", volume: 1 },
|
sound_channel: { type: "syuilo/square-pico", volume: 1 },
|
||||||
};
|
};
|
||||||
|
|
||||||
public static watchers = [];
|
public static watchers = [];
|
||||||
|
|
||||||
public static get<T extends keyof typeof ColdDeviceStorage.default>(
|
public static get<T extends keyof typeof ColdDeviceStorage.default>(
|
||||||
key: T,
|
key: T
|
||||||
): typeof ColdDeviceStorage.default[T] {
|
): (typeof ColdDeviceStorage.default)[T] {
|
||||||
// TODO: indexedDBにする
|
// TODO: indexedDBにする
|
||||||
// ただしその際はnullチェックではなくキー存在チェックにしないとダメ
|
// ただしその際はnullチェックではなくキー存在チェックにしないとダメ
|
||||||
// (indexedDBはnullを保存できるため、ユーザーが意図してnullを格納した可能性がある)
|
// (indexedDBはnullを保存できるため、ユーザーが意図してnullを格納した可能性がある)
|
||||||
const value = localStorage.getItem(PREFIX + key);
|
const value = localStorage.getItem(PREFIX + key);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return ColdDeviceStorage.default[key];
|
return ColdDeviceStorage.default[key];
|
||||||
} else {
|
} else {
|
||||||
return JSON.parse(value);
|
return JSON.parse(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static set<T extends keyof typeof ColdDeviceStorage.default>(
|
public static set<T extends keyof typeof ColdDeviceStorage.default>(
|
||||||
key: T,
|
key: T,
|
||||||
value: typeof ColdDeviceStorage.default[T],
|
value: (typeof ColdDeviceStorage.default)[T]
|
||||||
): void {
|
): void {
|
||||||
// 呼び出し側のバグ等で undefined が来ることがある
|
// 呼び出し側のバグ等で undefined が来ることがある
|
||||||
// undefined を文字列として localStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視
|
// undefined を文字列として localStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
console.error(`attempt to store undefined value for key '${key}'`);
|
console.error(`attempt to store undefined value for key '${key}'`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem(PREFIX + key, JSON.stringify(value));
|
localStorage.setItem(PREFIX + key, JSON.stringify(value));
|
||||||
|
|
||||||
for (const watcher of this.watchers) {
|
for (const watcher of this.watchers) {
|
||||||
if (watcher.key === key) watcher.callback(value);
|
if (watcher.key === key) watcher.callback(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static watch(key, callback) {
|
public static watch(key, callback) {
|
||||||
this.watchers.push({ key, callback });
|
this.watchers.push({ key, callback });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: VueのcustomRef使うと良い感じになるかも
|
// TODO: VueのcustomRef使うと良い感じになるかも
|
||||||
public static ref<T extends keyof typeof ColdDeviceStorage.default>(key: T) {
|
public static ref<T extends keyof typeof ColdDeviceStorage.default>(
|
||||||
const v = ColdDeviceStorage.get(key);
|
key: T
|
||||||
const r = ref(v);
|
) {
|
||||||
// TODO: このままではwatcherがリークするので開放する方法を考える
|
const v = ColdDeviceStorage.get(key);
|
||||||
this.watch(key, (v) => {
|
const r = ref(v);
|
||||||
r.value = v;
|
// TODO: このままではwatcherがリークするので開放する方法を考える
|
||||||
});
|
this.watch(key, (v) => {
|
||||||
return r;
|
r.value = v;
|
||||||
}
|
});
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 特定のキーの、簡易的なgetter/setterを作ります
|
* 特定のキーの、簡易的なgetter/setterを作ります
|
||||||
* 主にvue場で設定コントロールのmodelとして使う用
|
* 主にvue場で設定コントロールのmodelとして使う用
|
||||||
*/
|
*/
|
||||||
public static makeGetterSetter<
|
public static makeGetterSetter<
|
||||||
K extends keyof typeof ColdDeviceStorage.default,
|
K extends keyof typeof ColdDeviceStorage.default
|
||||||
>(key: K) {
|
>(key: K) {
|
||||||
// TODO: VueのcustomRef使うと良い感じになるかも
|
// TODO: VueのcustomRef使うと良い感じになるかも
|
||||||
const valueRef = ColdDeviceStorage.ref(key);
|
const valueRef = ColdDeviceStorage.ref(key);
|
||||||
return {
|
return {
|
||||||
get: () => {
|
get: () => {
|
||||||
return valueRef.value;
|
return valueRef.value;
|
||||||
},
|
},
|
||||||
set: (value: unknown) => {
|
set: (value: unknown) => {
|
||||||
const val = value;
|
const val = value;
|
||||||
ColdDeviceStorage.set(key, val);
|
ColdDeviceStorage.set(key, val);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// このファイルに書きたくないけどここに書かないと何故かVeturが認識しない
|
// このファイルに書きたくないけどここに書かないと何故かVeturが認識しない
|
||||||
declare module "@vue/runtime-core" {
|
declare module "@vue/runtime-core" {
|
||||||
interface ComponentCustomProperties {
|
interface ComponentCustomProperties {
|
||||||
$store: typeof defaultStore;
|
$store: typeof defaultStore;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue