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