fix types

This commit is contained in:
Lhcfl 2024-04-23 21:30:55 +08:00
parent 850201ff71
commit 62f5c84ca6
17 changed files with 222 additions and 131 deletions

View file

@ -15,7 +15,7 @@ export const paramDef = {
} as const; } as const;
export default define(meta, paramDef, async () => { export default define(meta, paramDef, async () => {
let release; let release: unknown;
await fetch( await fetch(
"https://firefish.dev/firefish/firefish/-/raw/develop/release.json", "https://firefish.dev/firefish/firefish/-/raw/develop/release.json",

View file

@ -7,6 +7,7 @@ import { alert, api, popup, popupMenu, waiting } from "@/os";
import icon from "@/scripts/icon"; import icon from "@/scripts/icon";
import { del, get, set } from "@/scripts/idb-proxy"; import { del, get, set } from "@/scripts/idb-proxy";
import { reloadChannel, unisonReload } from "@/scripts/unison-reload"; import { reloadChannel, unisonReload } from "@/scripts/unison-reload";
import type { MenuButton, MenuUser } from "./types/menu";
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期
@ -16,7 +17,7 @@ export async function signOut() {
waiting(); waiting();
localStorage.removeItem("account"); localStorage.removeItem("account");
await removeAccount(me.id); await removeAccount(me!.id);
const accounts = await getAccounts(); const accounts = await getAccounts();
@ -26,12 +27,9 @@ export async function signOut() {
const registration = await navigator.serviceWorker.ready; const registration = await navigator.serviceWorker.ready;
const push = await registration.pushManager.getSubscription(); const push = await registration.pushManager.getSubscription();
if (push) { if (push) {
await fetch(`${apiUrl}/sw/unregister`, { await api("sw/unregister", {
method: "POST",
body: JSON.stringify({
i: me.token,
endpoint: push.endpoint, endpoint: push.endpoint,
}), i: me!.token, // FIXME: This parameter seems to be removable but I didn't test it
}); });
} }
} }
@ -117,13 +115,13 @@ function showSuspendedDialog() {
export function updateAccount(accountData) { export function updateAccount(accountData) {
for (const [key, value] of Object.entries(accountData)) { for (const [key, value] of Object.entries(accountData)) {
me[key] = value; me![key] = value;
} }
localStorage.setItem("account", JSON.stringify(me)); localStorage.setItem("account", JSON.stringify(me));
} }
export async function refreshAccount() { export async function refreshAccount() {
const accountData = await fetchAccount(me.token); const accountData = await fetchAccount(me!.token);
return updateAccount(accountData); return updateAccount(accountData);
} }
@ -186,7 +184,7 @@ export async function openAccountMenu(
async function switchAccount(account: entities.UserDetailed) { async function switchAccount(account: entities.UserDetailed) {
const storedAccounts = await getAccounts(); const storedAccounts = await getAccounts();
const token = storedAccounts.find((x) => x.id === account.id).token; const token = storedAccounts.find((x) => x.id === account.id)!.token;
switchAccountWithToken(token); switchAccountWithToken(token);
} }
@ -195,15 +193,15 @@ export async function openAccountMenu(
} }
const storedAccounts = await getAccounts().then((accounts) => const storedAccounts = await getAccounts().then((accounts) =>
accounts.filter((x) => x.id !== me.id), accounts.filter((x) => x.id !== me!.id),
); );
const accountsPromise = api("users/show", { const accountsPromise = api("users/show", {
userIds: storedAccounts.map((x) => x.id), userIds: storedAccounts.map((x) => x.id),
}); });
function createItem(account: entities.UserDetailed) { function createItem(account: entities.UserDetailed): MenuUser {
return { return {
type: "user", type: "user" as const,
user: account, user: account,
active: opts.active != null ? opts.active === account.id : false, active: opts.active != null ? opts.active === account.id : false,
action: () => { action: () => {
@ -218,10 +216,14 @@ export async function openAccountMenu(
const accountItemPromises = storedAccounts.map( const accountItemPromises = storedAccounts.map(
(a) => (a) =>
new Promise((res) => { new Promise<MenuUser>((res) => {
accountsPromise.then((accounts) => { accountsPromise.then((accounts) => {
const account = accounts.find((x) => x.id === a.id); const account = accounts.find((x) => x.id === a.id);
if (account == null) return res(null); if (account == null) {
// The user is deleted, remove it
removeAccount(a.id);
return res(null as unknown as MenuUser);
}
res(createItem(account)); res(createItem(account));
}); });
}), }),
@ -230,11 +232,10 @@ export async function openAccountMenu(
if (opts.withExtraOperation) { if (opts.withExtraOperation) {
popupMenu( popupMenu(
[ [
...[
...(isMobile ?? false ...(isMobile ?? false
? [ ? [
{ {
type: "parent", type: "parent" as const,
icon: `${icon("ph-plus")}`, icon: `${icon("ph-plus")}`,
text: i18n.ts.addAccount, text: i18n.ts.addAccount,
children: [ children: [
@ -255,28 +256,28 @@ export async function openAccountMenu(
] ]
: [ : [
{ {
type: "link", type: "link" as const,
text: i18n.ts.profile, text: i18n.ts.profile,
to: `/@${me.username}`, to: `/@${me!.username}`,
avatar: me, avatar: me!,
}, },
null, null,
]), ]),
...(opts.includeCurrentAccount ? [createItem(me)] : []), ...(opts.includeCurrentAccount ? [createItem(me!)] : []),
...accountItemPromises, ...accountItemPromises,
...(isMobile ?? false ...(isMobile ?? false
? [ ? [
null, null,
{ {
type: "link", type: "link" as const,
text: i18n.ts.profile, text: i18n.ts.profile,
to: `/@${me.username}`, to: `/@${me!.username}`,
avatar: me, avatar: me!,
}, },
] ]
: [ : [
{ {
type: "parent", type: "parent" as const,
icon: `${icon("ph-plus")}`, icon: `${icon("ph-plus")}`,
text: i18n.ts.addAccount, text: i18n.ts.addAccount,
children: [ children: [
@ -296,8 +297,7 @@ export async function openAccountMenu(
}, },
]), ]),
], ],
], (ev.currentTarget ?? ev.target) as HTMLElement,
ev.currentTarget ?? ev.target,
{ {
align: "left", align: "left",
}, },
@ -305,10 +305,10 @@ export async function openAccountMenu(
} else { } else {
popupMenu( popupMenu(
[ [
...(opts.includeCurrentAccount ? [createItem(me)] : []), ...(opts.includeCurrentAccount ? [createItem(me!)] : []),
...accountItemPromises, ...accountItemPromises,
], ],
ev.currentTarget ?? ev.target, (ev.currentTarget ?? ev.target) as HTMLElement,
{ {
align: "left", align: "left",
}, },

View file

@ -1,5 +1,5 @@
<template> <template>
<MkModal ref="modal" :z-priority="'middle'" @closed="$emit('closed')"> <MkModal ref="modal" :z-priority="'middle'" @closed="emit('closed')">
<div :class="$style.root"> <div :class="$style.root">
<div :class="$style.title"> <div :class="$style.title">
<MkSparkle v-if="isGoodNews">{{ title }}</MkSparkle> <MkSparkle v-if="isGoodNews">{{ title }}</MkSparkle>
@ -41,6 +41,10 @@ const props = defineProps<{
announcement: entities.Announcement; announcement: entities.Announcement;
}>(); }>();
const emit = defineEmits<{
closed: [];
}>();
const { id, text, title, imageUrl, isGoodNews } = props.announcement; const { id, text, title, imageUrl, isGoodNews } = props.announcement;
const modal = shallowRef<InstanceType<typeof MkModal>>(); const modal = shallowRef<InstanceType<typeof MkModal>>();

View file

@ -1,5 +1,5 @@
<template> <template>
<MkModal ref="modal" :z-priority="'middle'" @closed="$emit('closed')"> <MkModal ref="modal" :z-priority="'middle'" @closed="emit('closed')">
<div :class="$style.root"> <div :class="$style.root">
<p :class="$style.title"> <p :class="$style.title">
{{ i18n.ts.youHaveUnreadAnnouncements }} {{ i18n.ts.youHaveUnreadAnnouncements }}
@ -21,6 +21,10 @@ import MkModal from "@/components/MkModal.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
const emit = defineEmits<{
closed: [];
}>();
const modal = shallowRef<InstanceType<typeof MkModal>>(); const modal = shallowRef<InstanceType<typeof MkModal>>();
const checkAnnouncements = () => { const checkAnnouncements = () => {
modal.value!.close(); modal.value!.close();

View file

@ -160,7 +160,7 @@ const hCaptchaResponse = ref(null);
const reCaptchaResponse = ref(null); const reCaptchaResponse = ref(null);
const emit = defineEmits<{ const emit = defineEmits<{
(ev: "login", v: any): void; login: [v: { id: string; i: string }];
}>(); }>();
const props = defineProps({ const props = defineProps({

View file

@ -30,7 +30,7 @@ withDefaults(
); );
const emit = defineEmits<{ const emit = defineEmits<{
(ev: "done"): void; (ev: "done", res: { id: string; i: string }): void;
(ev: "closed"): void; (ev: "closed"): void;
(ev: "cancelled"): void; (ev: "cancelled"): void;
}>(); }>();
@ -39,11 +39,11 @@ const dialog = ref<InstanceType<typeof XModalWindow>>();
function onClose() { function onClose() {
emit("cancelled"); emit("cancelled");
dialog.value.close(); dialog.value!.close();
} }
function onLogin(res) { function onLogin(res: { id: string; i: string }) {
emit("done", res); emit("done", res);
dialog.value.close(); dialog.value!.close();
} }
</script> </script>

View file

@ -248,7 +248,7 @@
v-model="hCaptchaResponse" v-model="hCaptchaResponse"
class="_formBlock captcha" class="_formBlock captcha"
provider="hcaptcha" provider="hcaptcha"
:sitekey="instance.hcaptchaSiteKey" :sitekey="instance.hcaptchaSiteKey!"
/> />
<MkCaptcha <MkCaptcha
v-if="instance.enableRecaptcha" v-if="instance.enableRecaptcha"
@ -256,7 +256,7 @@
v-model="reCaptchaResponse" v-model="reCaptchaResponse"
class="_formBlock captcha" class="_formBlock captcha"
provider="recaptcha" provider="recaptcha"
:sitekey="instance.recaptchaSiteKey" :sitekey="instance.recaptchaSiteKey!"
/> />
<MkButton <MkButton
class="_formBlock" class="_formBlock"
@ -296,7 +296,7 @@ const props = withDefaults(
); );
const emit = defineEmits<{ const emit = defineEmits<{
(ev: "signup", user: Record<string, any>): void; (ev: "signup", user: { id: string; i: string }): void;
(ev: "signupEmailPending"): void; (ev: "signupEmailPending"): void;
}>(); }>();

View file

@ -36,13 +36,13 @@ withDefaults(
); );
const emit = defineEmits<{ const emit = defineEmits<{
(ev: "done"): void; (ev: "done", res: { id: string; i: string }): void;
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
const dialog = ref<InstanceType<typeof XModalWindow>>(); const dialog = ref<InstanceType<typeof XModalWindow>>();
function onSignup(res) { function onSignup(res: { id: string; i: string }) {
emit("done", res); emit("done", res);
dialog.value?.close(); dialog.value?.close();
} }

View file

@ -2,8 +2,8 @@
<MkModal <MkModal
ref="modal" ref="modal"
:z-priority="'middle'" :z-priority="'middle'"
@click="$refs.modal.close()" @click="modal!.close()"
@closed="$emit('closed')" @closed="emit('closed')"
> >
<div :class="$style.root"> <div :class="$style.root">
<div :class="$style.title"> <div :class="$style.title">
@ -11,8 +11,8 @@
</div> </div>
<div :class="$style.version"> {{ version }} 🚀</div> <div :class="$style.version"> {{ version }} 🚀</div>
<div v-if="newRelease" :class="$style.releaseNotes"> <div v-if="newRelease" :class="$style.releaseNotes">
<Mfm :text="data.notes" /> <Mfm :text="data?.notes ?? ''" />
<div v-if="data.screenshots.length > 0" style="max-width: 500"> <div v-if="data?.screenshots && data.screenshots.length > 0" style="max-width: 500">
<img <img
v-for="i in data.screenshots" v-for="i in data.screenshots"
:key="i" :key="i"
@ -25,7 +25,7 @@
:class="$style.gotIt" :class="$style.gotIt"
primary primary
full full
@click="$refs.modal.close()" @click="modal!.close()"
>{{ i18n.ts.gotIt }}</MkButton >{{ i18n.ts.gotIt }}</MkButton
> >
</div> </div>
@ -40,11 +40,16 @@ import MkButton from "@/components/MkButton.vue";
import { version } from "@/config"; import { version } from "@/config";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import * as os from "@/os"; import * as os from "@/os";
import type { Endpoints } from "firefish-js";
const emit = defineEmits<{
closed: [];
}>();
const modal = shallowRef<InstanceType<typeof MkModal>>(); const modal = shallowRef<InstanceType<typeof MkModal>>();
const newRelease = ref(false); const newRelease = ref(false);
const data = ref(Object); const data = ref<Endpoints["release"]["res"] | null>(null);
os.api("release").then((res) => { os.api("release").then((res) => {
data.value = res; data.value = res;
@ -52,7 +57,7 @@ os.api("release").then((res) => {
}); });
console.log(`Version: ${version}`); console.log(`Version: ${version}`);
console.log(`Data version: ${data.value.version}`); console.log(`Data version: ${data.value?.version}`);
console.log(newRelease.value); console.log(newRelease.value);
console.log(data.value); console.log(data.value);
</script> </script>

View file

@ -1,6 +1,7 @@
import { markRaw } from "vue"; import { markRaw } from "vue";
import { locale } from "@/config"; import { locale } from "@/config";
// biome-ignore lint/suspicious/noExplicitAny: temporary use any
class I18n<T extends Record<string, any>> { class I18n<T extends Record<string, any>> {
public ts: T; public ts: T;

View file

@ -245,7 +245,12 @@ function checkForSplash() {
try { try {
// 変なバージョン文字列来るとcompareVersionsでエラーになるため // 変なバージョン文字列来るとcompareVersionsでエラーになるため
if (lastVersion < version && defaultStore.state.showUpdates) { // If a strange version string comes, an error will occur in compareVersions.
if (
lastVersion != null &&
lastVersion < version &&
defaultStore.state.showUpdates
) {
// ログインしてる場合だけ // ログインしてる場合だけ
if (me) { if (me) {
popup( popup(
@ -281,7 +286,7 @@ function checkForSplash() {
"closed", "closed",
); );
} else { } else {
unreadAnnouncements.forEach((item) => { for (const item of unreadAnnouncements) {
if (item.showPopup) if (item.showPopup)
popup( popup(
defineAsyncComponent( defineAsyncComponent(
@ -291,7 +296,7 @@ function checkForSplash() {
{}, {},
"closed", "closed",
); );
}); }
} }
}) })
.catch((err) => console.log(err)); .catch((err) => console.log(err));

View file

@ -1,7 +1,7 @@
// NIRAX --- A lightweight router // NIRAX --- A lightweight router
import { EventEmitter } from "eventemitter3"; import { EventEmitter } from "eventemitter3";
import type { Component, ShallowRef } from "vue"; import type { Component } from "vue";
import { shallowRef } from "vue"; import { shallowRef } from "vue";
import { safeURIDecode } from "@/scripts/safe-uri-decode"; import { safeURIDecode } from "@/scripts/safe-uri-decode";
import { pleaseLogin } from "@/scripts/please-login"; import { pleaseLogin } from "@/scripts/please-login";
@ -36,6 +36,7 @@ export interface Resolved {
function parsePath(path: string): ParsedPath { function parsePath(path: string): ParsedPath {
const res = [] as ParsedPath; const res = [] as ParsedPath;
// biome-ignore lint/style/noParameterAssign: assign it intentionally
path = path.substring(1); path = path.substring(1);
for (const part of path.split("/")) { for (const part of path.split("/")) {
@ -76,13 +77,13 @@ export class Router extends EventEmitter<{
same: () => void; same: () => void;
}> { }> {
private routes: RouteDef[]; private routes: RouteDef[];
public current: Resolved; public current!: Resolved; // It is assigned in this.navigate
public currentRef: ShallowRef<Resolved> = shallowRef(); public currentRef = shallowRef<Resolved>();
public currentRoute: ShallowRef<RouteDef> = shallowRef(); public currentRoute = shallowRef<RouteDef>();
private currentPath: string; private currentPath: string;
private currentKey = Date.now().toString(); private currentKey = Date.now().toString();
public navHook: ((path: string, flag?: any) => boolean) | null = null; public navHook: ((path: string, flag?: unknown) => boolean) | null = null;
constructor(routes: Router["routes"], currentPath: Router["currentPath"]) { constructor(routes: Router["routes"], currentPath: Router["currentPath"]) {
super(); super();
@ -92,9 +93,10 @@ export class Router extends EventEmitter<{
this.navigate(currentPath, null, false); this.navigate(currentPath, null, false);
} }
public resolve(path: string): Resolved | null { public resolve(_path: string): Resolved | null {
let queryString: string | null = null; let queryString: string | null = null;
let hash: string | null = null; let hash: string | null = null;
let path = _path;
if (path[0] === "/") path = path.substring(1); if (path[0] === "/") path = path.substring(1);
if (path.includes("#")) { if (path.includes("#")) {
hash = path.substring(path.indexOf("#") + 1); hash = path.substring(path.indexOf("#") + 1);
@ -168,9 +170,16 @@ export class Router extends EventEmitter<{
} }
if (route.query != null && queryString != null) { if (route.query != null && queryString != null) {
const queryObject = [ // const queryObject = [
...new URLSearchParams(queryString).entries(), // ...new URLSearchParams(queryString).entries(),
].reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {}); // ].reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {});
const queryObject: Record<string, string> = Object.assign(
{},
...[...new URLSearchParams(queryString).entries()].map(
(entry) => ({ [entry[0]]: entry[1] }),
),
);
for (const q in route.query) { for (const q in route.query) {
const as = route.query[q]; const as = route.query[q];
@ -227,6 +236,7 @@ export class Router extends EventEmitter<{
} }
const isSamePath = beforePath === path; const isSamePath = beforePath === path;
// biome-ignore lint/style/noParameterAssign: assign it intentionally
if (isSamePath && key == null) key = this.currentKey; if (isSamePath && key == null) key = this.currentKey;
this.current = res; this.current = res;
this.currentRef.value = res; this.currentRef.value = res;
@ -253,7 +263,7 @@ export class Router extends EventEmitter<{
return this.currentKey; return this.currentKey;
} }
public push(path: string, flag?: any) { public push(path: string, flag?: unknown) {
const beforePath = this.currentPath; const beforePath = this.currentPath;
if (path === beforePath) { if (path === beforePath) {
this.emit("same"); this.emit("same");

View file

@ -893,9 +893,6 @@ export async function openEmojiPicker(
...opts, ...opts,
}, },
{ {
chosen: (emoji) => {
insertTextAtCursor(activeTextarea, emoji);
},
done: (emoji) => { done: (emoji) => {
insertTextAtCursor(activeTextarea, emoji); insertTextAtCursor(activeTextarea, emoji);
}, },

View file

@ -7,7 +7,7 @@
i18n.ts._accountDelete.sendEmail i18n.ts._accountDelete.sendEmail
}}</FormInfo> }}</FormInfo>
<FormButton <FormButton
v-if="!me.isDeleted" v-if="!me!.isDeleted"
danger danger
class="_formBlock" class="_formBlock"
@click="deleteAccount" @click="deleteAccount"
@ -27,6 +27,7 @@ import { signOut } from "@/account";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata"; import { definePageMetadata } from "@/scripts/page-metadata";
import icon from "@/scripts/icon"; import icon from "@/scripts/icon";
import { me } from "@/me";
async function deleteAccount() { async function deleteAccount() {
{ {

View file

@ -42,7 +42,20 @@ export function install(plugin) {
aiscript.exec(parser.parse(plugin.src)); aiscript.exec(parser.parse(plugin.src));
} }
function createPluginEnv(opts) { interface Plugin {
config?: Record<
string,
{
default: unknown;
[k: string]: unknown;
}
>;
configData: Record<string, unknown>;
token: string;
id: string;
}
function createPluginEnv(opts: { plugin: Plugin; storageKey: string }) {
const config = new Map<string, values.Value>(); const config = new Map<string, values.Value>();
for (const [k, v] of Object.entries(opts.plugin.config ?? {})) { for (const [k, v] of Object.entries(opts.plugin.config ?? {})) {
config.set( config.set(
@ -172,7 +185,7 @@ function registerNoteAction({ pluginId, title, handler }) {
if (!pluginContext) { if (!pluginContext) {
return; return;
} }
pluginContext.execFn(handler, [utils.jsToVal(user)]); pluginContext.execFn(handler, [utils.jsToVal(note)]);
}, },
}); });
} }
@ -205,16 +218,18 @@ function registerNotePostInterruptor({ pluginId, handler }) {
}); });
} }
// FIXME: where is pageViewInterruptors?
// This function currently can't do anything
function registerPageViewInterruptor({ pluginId, handler }): void { function registerPageViewInterruptor({ pluginId, handler }): void {
pageViewInterruptors.push({ // pageViewInterruptors.push({
handler: async (page) => { // handler: async (page) => {
const pluginContext = pluginContexts.get(pluginId); // const pluginContext = pluginContexts.get(pluginId);
if (!pluginContext) { // if (!pluginContext) {
return; // return;
} // }
return utils.valToJs( // return utils.valToJs(
await pluginContext.execFn(handler, [utils.jsToVal(page)]), // await pluginContext.execFn(handler, [utils.jsToVal(page)]),
); // );
}, // },
}); // });
} }

View file

@ -362,6 +362,16 @@ export type Endpoints = {
res: DriveFile[]; res: DriveFile[];
}; };
"email-address/available": {
req: {
emailAddress: string;
};
res: {
available?: boolean;
reason: string | null;
};
};
// endpoint // endpoint
endpoint: { endpoint: {
req: { endpoint: string }; req: { endpoint: string };
@ -893,6 +903,16 @@ export type Endpoints = {
// promo // promo
"promo/read": { req: TODO; res: TODO }; "promo/read": { req: TODO; res: TODO };
// release
release: {
req: null;
res: {
version: string;
notes: string;
screenshots: string[];
};
};
// request-reset-password // request-reset-password
"request-reset-password": { "request-reset-password": {
req: { username: string; email: string }; req: { username: string; email: string };
@ -915,8 +935,36 @@ export type Endpoints = {
// ck specific // ck specific
"latest-version": { req: NoParams; res: TODO }; "latest-version": { req: NoParams; res: TODO };
// signin
signin: {
req: {
username: string;
password: string;
"hcaptcha-response"?: null | string;
"g-recaptcha-response"?: null | string;
};
res:
| {
id: User["id"];
i: string;
}
| {
challenge: string;
challengeId: string;
securityKeys: {
id: string;
}[];
};
};
// sw // sw
"sw/register": { req: TODO; res: TODO }; "sw/register": { req: TODO; res: TODO };
"sw/unregister": {
req: {
endpoint: string;
};
res: null;
};
// username // username
"username/available": { "username/available": {

View file

@ -116,6 +116,7 @@ export type MeDetailed = UserDetailed & {
preventAiLearning: boolean; preventAiLearning: boolean;
receiveAnnouncementEmail: boolean; receiveAnnouncementEmail: boolean;
usePasswordLessLogin: boolean; usePasswordLessLogin: boolean;
token: string;
[other: string]: any; [other: string]: any;
}; };
@ -479,7 +480,7 @@ export type Announcement = {
imageUrl: string | null; imageUrl: string | null;
isRead?: boolean; isRead?: boolean;
isGoodNews: boolean; isGoodNews: boolean;
showPopUp: boolean; showPopup: boolean;
}; };
export type Antenna = { export type Antenna = {