Merge branch 'develop' into feat/schedule-create

This commit is contained in:
naskya 2024-05-16 17:32:42 +09:00
commit 452728b352
No known key found for this signature in database
GPG key ID: 712D413B3A9FED5C
85 changed files with 840 additions and 988 deletions

View file

@ -98,6 +98,7 @@ test:build:backend_ts_only:
changes:
paths:
- packages/backend/**/*
- packages/firefish-js/**/*
- packages/megalodon/**/*
when: always
before_script:
@ -115,7 +116,7 @@ test:build:backend_ts_only:
- psql --host postgres --user "${POSTGRES_USER}" --dbname "${POSTGRES_DB}" --command 'CREATE EXTENSION pgroonga'
script:
- pnpm install --frozen-lockfile
- pnpm --filter 'backend' --filter 'megalodon' run build:debug
- pnpm --filter 'backend' --filter 'firefish-js' --filter 'megalodon' run build:debug
- pnpm run migrate
test:build:client_only:

View file

@ -55,6 +55,7 @@
"escape-regexp": "0.0.1",
"feed": "4.2.2",
"file-type": "19.0.0",
"firefish-js": "workspace:*",
"fluent-ffmpeg": "2.1.2",
"form-data": "4.0.0",
"got": "14.2.1",

View file

@ -1,234 +1,7 @@
import {
packedUserLiteSchema,
packedUserDetailedNotMeOnlySchema,
packedMeDetailedOnlySchema,
packedUserDetailedNotMeSchema,
packedMeDetailedSchema,
packedUserDetailedSchema,
packedUserSchema,
} from "@/models/schema/user.js";
import { packedNoteSchema } from "@/models/schema/note.js";
import { packedUserListSchema } from "@/models/schema/user-list.js";
import { packedAppSchema } from "@/models/schema/app.js";
import { packedMessagingMessageSchema } from "@/models/schema/messaging-message.js";
import { packedNotificationSchema } from "@/models/schema/notification.js";
import { packedDriveFileSchema } from "@/models/schema/drive-file.js";
import { packedDriveFolderSchema } from "@/models/schema/drive-folder.js";
import { packedFollowingSchema } from "@/models/schema/following.js";
import { packedMutingSchema } from "@/models/schema/muting.js";
import { packedRenoteMutingSchema } from "@/models/schema/renote-muting.js";
import { packedReplyMutingSchema } from "@/models/schema/reply-muting.js";
import { packedBlockingSchema } from "@/models/schema/blocking.js";
import { packedNoteReactionSchema } from "@/models/schema/note-reaction.js";
import { packedHashtagSchema } from "@/models/schema/hashtag.js";
import { packedPageSchema } from "@/models/schema/page.js";
import { packedUserGroupSchema } from "@/models/schema/user-group.js";
import { packedNoteFavoriteSchema } from "@/models/schema/note-favorite.js";
import { packedChannelSchema } from "@/models/schema/channel.js";
import { packedAntennaSchema } from "@/models/schema/antenna.js";
import { packedClipSchema } from "@/models/schema/clip.js";
import { packedFederationInstanceSchema } from "@/models/schema/federation-instance.js";
import { packedQueueCountSchema } from "@/models/schema/queue.js";
import { packedGalleryPostSchema } from "@/models/schema/gallery-post.js";
import { packedEmojiSchema } from "@/models/schema/emoji.js";
import { packedNoteEdit } from "@/models/schema/note-edit.js";
import { packedNoteFileSchema } from "@/models/schema/note-file.js";
import { packedAbuseUserReportSchema } from "@/models/schema/abuse-user-report.js";
// TODO: use firefish-js
import { Schema as _Schema } from "firefish-js";
export const refs = {
AbuseUserReport: packedAbuseUserReportSchema,
UserLite: packedUserLiteSchema,
UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema,
MeDetailedOnly: packedMeDetailedOnlySchema,
UserDetailedNotMe: packedUserDetailedNotMeSchema,
MeDetailed: packedMeDetailedSchema,
UserDetailed: packedUserDetailedSchema,
User: packedUserSchema,
UserList: packedUserListSchema,
UserGroup: packedUserGroupSchema,
App: packedAppSchema,
MessagingMessage: packedMessagingMessageSchema,
Note: packedNoteSchema,
NoteFile: packedNoteFileSchema,
NoteEdit: packedNoteEdit,
NoteReaction: packedNoteReactionSchema,
NoteFavorite: packedNoteFavoriteSchema,
Notification: packedNotificationSchema,
DriveFile: packedDriveFileSchema,
DriveFolder: packedDriveFolderSchema,
Following: packedFollowingSchema,
Muting: packedMutingSchema,
RenoteMuting: packedRenoteMutingSchema,
ReplyMuting: packedReplyMutingSchema,
Blocking: packedBlockingSchema,
Hashtag: packedHashtagSchema,
Page: packedPageSchema,
Channel: packedChannelSchema,
QueueCount: packedQueueCountSchema,
Antenna: packedAntennaSchema,
Clip: packedClipSchema,
FederationInstance: packedFederationInstanceSchema,
GalleryPost: packedGalleryPostSchema,
Emoji: packedEmojiSchema,
};
export type Packed<x extends keyof typeof refs> = SchemaType<(typeof refs)[x]>;
type TypeStringef =
| "null"
| "boolean"
| "integer"
| "number"
| "string"
| "array"
| "object"
| "any";
type StringDefToType<T extends TypeStringef> = T extends "null"
? null
: T extends "boolean"
? boolean
: T extends "integer"
? number
: T extends "number"
? number
: T extends "string"
? string | Date
: T extends "array"
? ReadonlyArray<any>
: T extends "object"
? Record<string, any>
: any;
// https://swagger.io/specification/?sbsearch=optional#schema-object
type OfSchema = {
readonly anyOf?: ReadonlyArray<Schema>;
readonly oneOf?: ReadonlyArray<Schema>;
readonly allOf?: ReadonlyArray<Schema>;
};
export interface Schema extends OfSchema {
readonly type?: TypeStringef;
readonly nullable?: boolean;
readonly optional?: boolean;
readonly items?: Schema;
readonly properties?: Obj;
readonly required?: ReadonlyArray<
Extract<keyof NonNullable<this["properties"]>, string>
>;
readonly description?: string;
readonly example?: any;
readonly format?: string;
readonly ref?: keyof typeof refs;
readonly enum?: ReadonlyArray<string>;
readonly default?:
| (this["type"] extends TypeStringef ? StringDefToType<this["type"]> : any)
| null;
readonly maxLength?: number;
readonly minLength?: number;
readonly maximum?: number;
readonly minimum?: number;
readonly pattern?: string;
}
type RequiredPropertyNames<s extends Obj> = {
[K in keyof s]: // K is not optional
s[K]["optional"] extends false
? K
: // K has default value
s[K]["default"] extends
| null
| string
| number
| boolean
| Record<string, unknown>
? K
: never;
}[keyof s];
export type Obj = Record<string, Schema>;
// https://github.com/misskey-dev/misskey/issues/8535
// To avoid excessive stack depth error,
// deceive TypeScript with UnionToIntersection (or more precisely, `infer` expression within it).
export type ObjType<
s extends Obj,
RequiredProps extends keyof s,
> = UnionToIntersection<
{
-readonly [R in RequiredPropertyNames<s>]-?: SchemaType<s[R]>;
} & {
-readonly [R in RequiredProps]-?: SchemaType<s[R]>;
} & {
-readonly [P in keyof s]?: SchemaType<s[P]>;
}
>;
type NullOrUndefined<p extends Schema, T> =
| (p["nullable"] extends true ? null : never)
| (p["optional"] extends true ? undefined : never)
| T;
// https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
// Get intersection from union
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
) => void
? I
: never;
// https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552
// To get union, we use `Foo extends any ? Hoge<Foo> : never`
type UnionSchemaType<
a extends readonly any[],
X extends Schema = a[number],
> = X extends any ? SchemaType<X> : never;
type ArrayUnion<T> = T extends any ? Array<T> : never;
export type SchemaTypeDef<p extends Schema> = p["type"] extends "null"
? null
: p["type"] extends "integer"
? number
: p["type"] extends "number"
? number
: p["type"] extends "string"
? p["enum"] extends readonly string[]
? p["enum"][number]
: p["format"] extends "date-time"
? string
: // Dateにする
string
: p["type"] extends "boolean"
? boolean
: p["type"] extends "object"
? p["ref"] extends keyof typeof refs
? Packed<p["ref"]>
: p["properties"] extends NonNullable<Obj>
? ObjType<p["properties"], NonNullable<p["required"]>[number]>
: p["anyOf"] extends ReadonlyArray<Schema>
? UnionSchemaType<p["anyOf"]> &
Partial<UnionToIntersection<UnionSchemaType<p["anyOf"]>>>
: p["allOf"] extends ReadonlyArray<Schema>
? UnionToIntersection<UnionSchemaType<p["allOf"]>>
: any
: p["type"] extends "array"
? p["items"] extends OfSchema
? p["items"]["anyOf"] extends ReadonlyArray<Schema>
? UnionSchemaType<NonNullable<p["items"]["anyOf"]>>[]
: p["items"]["oneOf"] extends ReadonlyArray<Schema>
? ArrayUnion<
UnionSchemaType<NonNullable<p["items"]["oneOf"]>>
>
: p["items"]["allOf"] extends ReadonlyArray<Schema>
? UnionToIntersection<
UnionSchemaType<NonNullable<p["items"]["allOf"]>>
>[]
: never
: p["items"] extends NonNullable<Schema>
? SchemaTypeDef<p["items"]>[]
: any[]
: p["oneOf"] extends ReadonlyArray<Schema>
? UnionSchemaType<p["oneOf"]>
: any;
export type SchemaType<p extends Schema> = NullOrUndefined<p, SchemaTypeDef<p>>;
export const refs = _Schema.refs;
export type Packed<T extends keyof typeof refs> = _Schema.Packed<T>;
export type Schema = _Schema.Schema;
export type SchemaType<P extends _Schema.Schema> = _Schema.SchemaType<P>;

View file

@ -53,7 +53,7 @@ import { UserProfiles } from "@/models/index.js";
import { In } from "typeorm";
import { config } from "@/config.js";
import { truncate } from "@/misc/truncate.js";
import { langmap } from "@/misc/langmap.js";
import { langmap } from "firefish-js";
import { inspect } from "node:util";
export function validateNote(object: any, uri: string) {

View file

@ -18,8 +18,8 @@ import { ApiError } from "@/server/api/error.js";
import define from "@/server/api/define.js";
import { HOUR, genId } from "backend-rs";
import { getNote } from "@/server/api/common/getters.js";
import { langmap } from "@/misc/langmap.js";
import { createScheduledCreateNoteJob } from "@/queue";
import { langmap } from "firefish-js";
import { createScheduledCreateNoteJob } from "@/queue/index.js";
export const meta = {
tags: ["notes"],

View file

@ -33,7 +33,7 @@ import renderNote from "@/remote/activitypub/renderer/note.js";
import renderUpdate from "@/remote/activitypub/renderer/update.js";
import { deliverToRelays } from "@/services/relay.js";
// import { deliverQuestionUpdate } from "@/services/note/polls/update.js";
import { langmap } from "@/misc/langmap.js";
import { langmap } from "firefish-js";
export const meta = {
tags: ["notes"],

View file

@ -1,7 +1,7 @@
import { ApiError } from "@/server/api/error.js";
import { getNote } from "@/server/api/common/getters.js";
import { translate } from "@/misc/translate.js";
import type { PostLanguage } from "@/misc/langmap.js";
import type { PostLanguage } from "firefish-js";
import define from "@/server/api/define.js";
export const meta = {

View file

@ -63,7 +63,7 @@ import { db } from "@/db/postgre.js";
import { getActiveWebhooks } from "@/misc/webhook-cache.js";
import { redisClient } from "@/db/redis.js";
import { Mutex } from "redis-semaphore";
import { langmap } from "@/misc/langmap.js";
import { langmap } from "firefish-js";
import Logger from "@/services/logger.js";
import { inspect } from "node:util";
import { toRustObject } from "@/prelude/undefined-to-null.js";

View file

@ -7,6 +7,7 @@ import { alert, api, popup, popupMenu, waiting } from "@/os";
import icon from "@/scripts/icon";
import { del, get, set } from "@/scripts/idb-proxy";
import { reloadChannel, unisonReload } from "@/scripts/unison-reload";
import type { MenuButton, MenuUser } from "./types/menu";
// TODO: 他のタブと永続化されたstateを同期
@ -16,7 +17,7 @@ export async function signOut() {
waiting();
localStorage.removeItem("account");
await removeAccount(me.id);
await removeAccount(me!.id);
const accounts = await getAccounts();
@ -26,12 +27,9 @@ export async function signOut() {
const registration = await navigator.serviceWorker.ready;
const push = await registration.pushManager.getSubscription();
if (push) {
await fetch(`${apiUrl}/sw/unregister`, {
method: "POST",
body: JSON.stringify({
i: me.token,
endpoint: push.endpoint,
}),
await api("sw/unregister", {
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) {
for (const [key, value] of Object.entries(accountData)) {
me[key] = value;
me![key] = value;
}
localStorage.setItem("account", JSON.stringify(me));
}
export async function refreshAccount() {
const accountData = await fetchAccount(me.token);
const accountData = await fetchAccount(me!.token);
return updateAccount(accountData);
}
@ -189,7 +187,7 @@ export async function openAccountMenu(
async function switchAccount(account: entities.UserDetailed) {
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);
}
@ -198,15 +196,15 @@ export async function openAccountMenu(
}
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", {
userIds: storedAccounts.map((x) => x.id),
});
function createItem(account: entities.UserDetailed) {
function createItem(account: entities.UserDetailed): MenuUser {
return {
type: "user",
type: "user" as const,
user: account,
active: opts.active != null ? opts.active === account.id : false,
action: () => {
@ -221,10 +219,14 @@ export async function openAccountMenu(
const accountItemPromises = storedAccounts.map(
(a) =>
new Promise((res) => {
new Promise<MenuUser>((res) => {
accountsPromise.then((accounts) => {
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));
});
}),
@ -233,74 +235,72 @@ export async function openAccountMenu(
if (opts.withExtraOperation) {
popupMenu(
[
...[
...(isMobile ?? false
? [
{
type: "parent",
icon: `${icon("ph-plus")}`,
text: i18n.ts.addAccount,
children: [
{
text: i18n.ts.existingAccount,
action: () => {
showSigninDialog();
},
...(isMobile ?? false
? [
{
type: "parent" as const,
icon: `${icon("ph-plus")}`,
text: i18n.ts.addAccount,
children: [
{
text: i18n.ts.existingAccount,
action: () => {
showSigninDialog();
},
{
text: i18n.ts.createAccount,
action: () => {
createAccount();
},
},
{
text: i18n.ts.createAccount,
action: () => {
createAccount();
},
],
},
]
: [
{
type: "link",
text: i18n.ts.profile,
to: `/@${me.username}`,
avatar: me,
},
null,
]),
...(opts.includeCurrentAccount ? [createItem(me)] : []),
...accountItemPromises,
...(isMobile ?? false
? [
null,
{
type: "link",
text: i18n.ts.profile,
to: `/@${me.username}`,
avatar: me,
},
]
: [
{
type: "parent",
icon: `${icon("ph-plus")}`,
text: i18n.ts.addAccount,
children: [
{
text: i18n.ts.existingAccount,
action: () => {
showSigninDialog();
},
},
],
},
]
: [
{
type: "link" as const,
text: i18n.ts.profile,
to: `/@${me!.username}`,
avatar: me!,
},
null,
]),
...(opts.includeCurrentAccount ? [createItem(me!)] : []),
...accountItemPromises,
...(isMobile ?? false
? [
null,
{
type: "link" as const,
text: i18n.ts.profile,
to: `/@${me!.username}`,
avatar: me!,
},
]
: [
{
type: "parent" as const,
icon: `${icon("ph-plus")}`,
text: i18n.ts.addAccount,
children: [
{
text: i18n.ts.existingAccount,
action: () => {
showSigninDialog();
},
{
text: i18n.ts.createAccount,
action: () => {
createAccount();
},
},
{
text: i18n.ts.createAccount,
action: () => {
createAccount();
},
],
},
]),
],
},
],
},
]),
],
ev.currentTarget ?? ev.target,
(ev.currentTarget ?? ev.target) as HTMLElement,
{
align: "left",
},
@ -308,10 +308,10 @@ export async function openAccountMenu(
} else {
popupMenu(
[
...(opts.includeCurrentAccount ? [createItem(me)] : []),
...(opts.includeCurrentAccount ? [createItem(me!)] : []),
...accountItemPromises,
],
ev.currentTarget ?? ev.target,
(ev.currentTarget ?? ev.target) as HTMLElement,
{
align: "left",
},

View file

@ -0,0 +1,121 @@
import { ref as vueRef } from "vue";
import type { UnwrapRef } from "vue";
// TODO: 他のタブと永続化されたstateを同期
const PREFIX = "miux:";
interface Plugin {
id: string;
name: string;
active: boolean;
configData: Record<string, unknown>;
token: string;
ast: unknown[];
}
import darkTheme from "@/themes/d-rosepine.json5";
/**
* Storage for configuration information that does not need to be constantly loaded into memory (non-reactive)
*/
import lightTheme from "@/themes/l-rosepinedawn.json5";
const ColdStoreDefault = {
lightTheme,
darkTheme,
syncDeviceDarkMode: true,
plugins: [] as Plugin[],
mediaVolume: 0.5,
vibrate: false,
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 },
};
const watchers: {
key: string;
callback: (value) => void;
}[] = [];
function get<T extends keyof typeof ColdStoreDefault>(
key: T,
): (typeof ColdStoreDefault)[T] {
// TODO: indexedDBにする
// ただしその際はnullチェックではなくキー存在チェックにしないとダメ
// (indexedDBはnullを保存できるため、ユーザーが意図してnullを格納した可能性がある)
const value = localStorage.getItem(PREFIX + key);
if (value == null) {
return ColdStoreDefault[key];
} else {
return JSON.parse(value);
}
}
function set<T extends keyof typeof ColdStoreDefault>(
key: T,
value: (typeof ColdStoreDefault)[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));
for (const watcher of watchers) {
if (watcher.key === key) watcher.callback(value);
}
}
function watch<T extends keyof typeof ColdStoreDefault>(
key: T,
callback: (value: (typeof ColdStoreDefault)[T]) => void,
) {
watchers.push({ key, callback });
}
// TODO: VueのcustomRef使うと良い感じになるかも
function ref<T extends keyof typeof ColdStoreDefault>(key: T) {
const v = get(key);
const r = vueRef(v);
// TODO: このままではwatcherがリークするので開放する方法を考える
watch(key, (v) => {
r.value = v as UnwrapRef<typeof v>;
});
return r;
}
/**
* getter/setterを作ります
* vue場で設定コントロールのmodelとして使う用
*/
function makeGetterSetter<K extends keyof typeof ColdStoreDefault>(key: K) {
// TODO: VueのcustomRef使うと良い感じになるかも
const valueRef = ref(key);
return {
get: () => {
return valueRef.value;
},
set: (value: (typeof ColdStoreDefault)[K]) => {
const val = value;
set(key, val);
},
};
}
export default {
default: ColdStoreDefault,
watchers,
get,
set,
watch,
ref,
makeGetterSetter,
};

View file

@ -1,5 +1,5 @@
<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.title">
<MkSparkle v-if="isGoodNews">{{ title }}</MkSparkle>
@ -41,6 +41,10 @@ const props = defineProps<{
announcement: entities.Announcement;
}>();
const emit = defineEmits<{
closed: [];
}>();
const { id, text, title, imageUrl, isGoodNews } = props.announcement;
const modal = shallowRef<InstanceType<typeof MkModal>>();

View file

@ -182,7 +182,7 @@ export default {
const props = defineProps<{
type: string;
q: string | null;
textarea: HTMLTextAreaElement;
textarea: HTMLTextAreaElement | HTMLInputElement;
close: () => void;
x: number;
y: number;
@ -435,7 +435,7 @@ onUpdated(() => {
onMounted(() => {
setPosition();
props.textarea.addEventListener("keydown", onKeydown);
(props.textarea as HTMLTextAreaElement).addEventListener("keydown", onKeydown);
document.body.addEventListener("mousedown", onMousedown);
nextTick(() => {
@ -453,7 +453,7 @@ onMounted(() => {
});
onBeforeUnmount(() => {
props.textarea.removeEventListener("keydown", onKeydown);
(props.textarea as HTMLTextAreaElement).removeEventListener("keydown", onKeydown);
document.body.removeEventListener("mousedown", onMousedown);
});
</script>

View file

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

View file

@ -852,13 +852,17 @@ function setLanguage() {
actions.push(null);
}
if (language.value != null)
if (language.value != null && langmap[language.value] != null) {
actions.push({
text: langmap[language.value].nativeName,
danger: false,
active: true,
action: () => {},
});
} else {
// Unrecognized language, set to null
language.value = null;
}
const langs = Object.keys(langmap);

View file

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

View file

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

View file

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

View file

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

View file

@ -2,8 +2,8 @@
<MkModal
ref="modal"
:z-priority="'middle'"
@click="$refs.modal.close()"
@closed="$emit('closed')"
@click="modal!.close()"
@closed="emit('closed')"
>
<div :class="$style.root">
<div :class="$style.title">
@ -14,7 +14,7 @@
:class="$style.gotIt"
primary
full
@click="$refs.modal.close()"
@click="modal!.close()"
>{{ i18n.ts.gotIt }}</MkButton
>
</div>

View file

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

View file

@ -245,7 +245,12 @@ function checkForSplash() {
try {
// 変なバージョン文字列来ると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) {
popup(
@ -281,7 +286,7 @@ function checkForSplash() {
"closed",
);
} else {
unreadAnnouncements.forEach((item) => {
for (const item of unreadAnnouncements) {
if (item.showPopup)
popup(
defineAsyncComponent(
@ -291,7 +296,7 @@ function checkForSplash() {
{},
"closed",
);
});
}
}
})
.catch((err) => console.log(err));

View file

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

View file

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

View file

@ -7,7 +7,7 @@
i18n.ts._accountDelete.sendEmail
}}</FormInfo>
<FormButton
v-if="!me?.isDeleted"
v-if="!me!.isDeleted"
danger
class="_formBlock"
@click="deleteAccount"

View file

@ -5,12 +5,13 @@ import { onUnmounted, ref, watch } from "vue";
import { api } from "./os";
import { useStream } from "./stream";
import { isSignedIn, me } from "@/me";
import type { TypeUtils } from "firefish-js";
type StateDef = Record<
string,
{
where: "account" | "device" | "deviceAccount";
default: any;
default: unknown;
}
>;
@ -82,11 +83,12 @@ export class Storage<T extends StateDef> {
for (const [k, v] of Object.entries(state)) {
reactiveState[k] = ref(v);
}
this.state = state as any;
this.reactiveState = reactiveState as any;
this.state = state as typeof this.state;
this.reactiveState = reactiveState as typeof this.reactiveState;
if (isSignedIn(me)) {
// なぜかsetTimeoutしないとapi関数内でエラーになる(おそらく循環参照してることに原因がありそう)
// For some reason, if I don't setTimeout, an error occurs in the api function (probably caused by circular references)
window.setTimeout(() => {
api("i/registry/get-all", { scope: ["client", this.key] }).then(
(kvs) => {
@ -104,7 +106,7 @@ export class Storage<T extends StateDef> {
}
}
localStorage.setItem(
`${this.keyForLocalStorage}::cache::${me.id}`,
`${this.keyForLocalStorage}::cache::${me!.id}`,
JSON.stringify(cache),
);
},
@ -118,11 +120,12 @@ export class Storage<T extends StateDef> {
key,
value,
}: {
scope: string[];
scope?: string[];
key: keyof T;
value: T[typeof key]["default"];
}) => {
if (
scope == null ||
scope.length !== 2 ||
scope[0] !== "client" ||
scope[1] !== this.key ||
@ -135,13 +138,13 @@ export class Storage<T extends StateDef> {
const cache = JSON.parse(
localStorage.getItem(
`${this.keyForLocalStorage}::cache::${me.id}`,
`${this.keyForLocalStorage}::cache::${me!.id}`,
) || "{}",
);
if (cache[key] !== value) {
cache[key] = value;
localStorage.setItem(
`${this.keyForLocalStorage}::cache::${me.id}`,
`${this.keyForLocalStorage}::cache::${me!.id}`,
JSON.stringify(cache),
);
}
@ -150,7 +153,7 @@ export class Storage<T extends StateDef> {
}
}
public set<K extends keyof T>(key: K, value: T[K]["default"]): void {
public set<K extends keyof T>(key: K & string, value: T[K]["default"]): void {
if (_DEV_) console.log("set", key, value);
this.state[key] = value;
@ -201,15 +204,15 @@ export class Storage<T extends StateDef> {
}
}
public push<K extends keyof T>(
key: K,
public push<K extends TypeUtils.PropertyOfType<T, { default: unknown[] }>>(
key: K & string,
value: ArrayElement<T[K]["default"]>,
): void {
const currentState = this.state[key];
const currentState = this.state[key] as unknown[];
this.set(key, [...currentState, value]);
}
public reset(key: keyof T) {
public reset(key: keyof T & string) {
this.set(key, this.def[key].default);
}
@ -218,11 +221,11 @@ export class Storage<T extends StateDef> {
* vue場で設定コントロールのmodelとして使う用
*/
public makeGetterSetter<K extends keyof T>(
key: K,
getter?: (v: T[K]) => unknown,
setter?: (v: unknown) => T[K],
key: K & string,
getter?: (oldV: T[K]["default"]) => T[K]["default"],
setter?: (oldV: T[K]["default"]) => T[K]["default"],
) {
const valueRef = ref(this.state[key]);
const valueRef = ref(this.state[key]) as Ref<T[K]["default"]>;
const stop = watch(this.reactiveState[key], (val) => {
valueRef.value = val;
@ -242,7 +245,7 @@ export class Storage<T extends StateDef> {
return valueRef.value;
}
},
set: (value: unknown) => {
set: (value: T[K]["default"]) => {
const val = setter ? setter(value) : value;
this.set(key, val);
valueRef.value = val;

View file

@ -42,7 +42,20 @@ export function install(plugin) {
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>();
for (const [k, v] of Object.entries(opts.plugin.config ?? {})) {
config.set(
@ -172,7 +185,7 @@ function registerNoteAction({ pluginId, title, handler }) {
if (!pluginContext) {
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 {
pageViewInterruptors.push({
handler: async (page) => {
const pluginContext = pluginContexts.get(pluginId);
if (!pluginContext) {
return;
}
return utils.valToJs(
await pluginContext.execFn(handler, [utils.jsToVal(page)]),
);
},
});
// pageViewInterruptors.push({
// handler: async (page) => {
// const pluginContext = pluginContexts.get(pluginId);
// if (!pluginContext) {
// return;
// }
// return utils.valToJs(
// await pluginContext.execFn(handler, [utils.jsToVal(page)]),
// );
// },
// });
}

View file

@ -9,7 +9,7 @@ export function byteify(string: string, encoding: "ascii" | "base64" | "hex") {
);
case "hex":
return new Uint8Array(
string.match(/.{1,2}/g).map((byte) => Number.parseInt(byte, 16)),
string.match(/.{1,2}/g)!.map((byte) => Number.parseInt(byte, 16)),
);
}
}

View file

@ -1,4 +1,4 @@
import type { EndoRelation, Predicate } from "./relation";
import type { EndoRelation, Predicate } from "@/types/relation";
/**
* Count the number of elements that satisfy the predicate
@ -126,7 +126,7 @@ export function lessThan(xs: number[], ys: number[]): boolean {
* Returns the longest prefix of elements that satisfy the predicate
*/
export function takeWhile<T>(f: Predicate<T>, xs: T[]): T[] {
const ys = [];
const ys: T[] = [];
for (const x of xs) {
if (f(x)) {
ys.push(x);

View file

@ -13,7 +13,7 @@ export class Autocomplete {
} | null;
private textarea: HTMLInputElement | HTMLTextAreaElement;
private currentType: string;
private currentType?: string;
private textRef: Ref<string>;
private opening: boolean;
@ -69,7 +69,7 @@ export class Autocomplete {
*
*/
private onInput() {
const caretPos = this.textarea.selectionStart;
const caretPos = this.textarea.selectionStart!;
const text = this.text.substring(0, caretPos).split("\n").pop()!;
const mentionIndex = text.lastIndexOf("@");
@ -147,10 +147,10 @@ export class Autocomplete {
this.opening = true;
this.currentType = type;
// #region サジェストを表示すべき位置を計算
// #region Calculate the position where suggestions should be displayed
const caretPosition = getCaretCoordinates(
this.textarea,
this.textarea.selectionStart,
this.textarea.selectionStart!,
);
const rect = this.textarea.getBoundingClientRect();
@ -216,7 +216,7 @@ export class Autocomplete {
private complete({ type, value }) {
this.close();
const caret = this.textarea.selectionStart;
const caret = this.textarea.selectionStart!;
if (type === "user") {
const source = this.text;

View file

@ -1,4 +1,5 @@
import type { entities } from "firefish-js";
import { detectLanguage, languageContains } from "./language-utils";
export interface Muted {
muted: boolean;
@ -12,11 +13,12 @@ function checkLangMute(
note: entities.Note,
mutedLangs: Array<string | string[]>,
): Muted {
const mutedLangList = new Set(
mutedLangs.reduce((arr, x) => [...arr, ...(Array.isArray(x) ? x : [x])]),
);
if (mutedLangList.has((note.lang?.[0]?.lang || "").split("-")[0])) {
return { muted: true, matched: [note.lang?.[0]?.lang] };
const mutedLangList = mutedLangs.flat();
const noteLang = note.lang ?? detectLanguage(note.text ?? "") ?? "no-lang";
for (const mutedLang of mutedLangList) {
if (languageContains(mutedLang, noteLang)) {
return { muted: true, matched: [noteLang] };
}
}
return NotMuted;
}
@ -32,7 +34,7 @@ function checkWordMute(
if (text === "") return NotMuted;
const result = { muted: false, matched: [] };
const result = { muted: false, matched: [] as string[] };
for (const mutePattern of mutedWords) {
if (Array.isArray(mutePattern)) {
@ -74,7 +76,7 @@ function checkWordMute(
}
export function getWordSoftMute(
note: firefish.entities.Note,
note: entities.Note,
meId: string | null | undefined,
mutedWords: Array<string | string[]>,
mutedLangs: Array<string | string[]>,

View file

@ -1,6 +1,8 @@
export function collectPageVars(content) {
const pageVars = [];
const collect = (xs: any[]) => {
import type { PageContent, PageVar } from "@/types/page";
export function collectPageVars(content: PageContent[]) {
const pageVars: PageVar[] = [];
const collect = (xs: PageContent[]) => {
for (const x of xs) {
if (x.type === "textInput") {
pageVars.push({
@ -24,7 +26,7 @@ export function collectPageVars(content) {
pageVars.push({
name: x.name,
type: "boolean",
value: x.default,
value: x.default!,
});
} else if (x.type === "counter") {
pageVars.push({

View file

@ -1,7 +1,7 @@
/**
* Clipboardに値をコピー(TODO: 文字列以外も対応)
*/
export default (val) => {
function obsoleteCopyToClipboard(val: string) {
// 空div 生成
const tmp = document.createElement("div");
// 選択用のタグ生成
@ -21,7 +21,7 @@ export default (val) => {
// body に追加
document.body.appendChild(tmp);
// 要素を選択
document.getSelection().selectAllChildren(tmp);
document.getSelection()?.selectAllChildren(tmp);
// クリップボードにコピー
const result = document.execCommand("copy");
@ -30,4 +30,20 @@ export default (val) => {
document.body.removeChild(tmp);
return result;
};
}
export default async function (val?: string | null) {
if (val == null) return true;
const clipboardObj = window.navigator?.clipboard;
if (clipboardObj == null) {
// not supported
return obsoleteCopyToClipboard(val);
} else {
return new Promise<boolean>((res) => {
clipboardObj
.writeText(val)
.then(() => res(true))
.catch(() => res(obsoleteCopyToClipboard(val)));
});
}
}

View file

@ -6,7 +6,10 @@ export function extractMentions(
nodes: mfm.MfmNode[],
): mfm.MfmMention["props"][] {
// TODO: 重複を削除
const mentionNodes = mfm.extract(nodes, (node) => node.type === "mention");
const mentionNodes = mfm.extract(
nodes,
(node) => node.type === "mention",
) as mfm.MfmMention[];
const mentions = mentionNodes.map((x) => x.props);
return mentions;

View file

@ -15,7 +15,8 @@ const animatedMfm = [
export function extractMfmWithAnimation(nodes: mfm.MfmNode[]): string[] {
const mfmNodes = mfm.extract(nodes, (node) => {
return node.type === "fn" && animatedMfm.includes(node.props.name);
});
}) as mfm.MfmFn[];
// FIXME: mfm type error
const mfms = mfmNodes.map((x) => x.props.fn);
return mfms;

View file

@ -14,7 +14,7 @@ export function extractUrlFromMfm(
node.type === "url" ||
(node.type === "link" && !(respectSilentFlag && node.props.silent))
);
});
}) as (mfm.MfmLink | mfm.MfmUrl)[];
const urls: string[] = unique(urlNodes.map((x) => x.props.url));
return urls.reduce((array, url) => {

View file

@ -1,5 +1,6 @@
export function focusPrev(el: Element | null, self = false, scroll = true) {
if (el == null) return;
// biome-ignore lint/style/noParameterAssign: assign it intentionally
if (!self) el = el.previousElementSibling;
if (el) {
if (el.hasAttribute("tabindex")) {
@ -14,6 +15,7 @@ export function focusPrev(el: Element | null, self = false, scroll = true) {
export function focusNext(el: Element | null, self = false, scroll = true) {
if (el == null) return;
// biome-ignore lint/style/noParameterAssign: assign it intentionally
if (!self) el = el.nextElementSibling;
if (el) {
if (el.hasAttribute("tabindex")) {

View file

@ -1,38 +0,0 @@
import { acct } from "firefish-js";
import { host as localHost } from "@/config";
export async function genSearchQuery(v: any, q: string) {
let host: string;
let userId: string;
if (q.split(" ").some((x) => x.startsWith("@"))) {
for (const at of q
.split(" ")
.filter((x) => x.startsWith("@"))
.map((x) => x.slice(1))) {
if (at.includes(".")) {
if (at === localHost || at === ".") {
host = null;
} else {
host = at;
}
} else {
const user = await v.os
.api("users/show", acct.parse(at))
.catch((x) => null);
if (user) {
userId = user.id;
} else {
// todo: show error
}
}
}
}
return {
query: q
.split(" ")
.filter((x) => !(x.startsWith("/") || x.startsWith("@")))
.join(" "),
host,
userId,
};
}

View file

@ -15,6 +15,7 @@ import { useRouter } from "@/router";
import { notePage } from "@/filters/note";
import type { NoteTranslation } from "@/types/note";
import type { MenuItem } from "@/types/menu";
import type { NoteDraft } from "@/types/post-form";
const router = useRouter();
@ -72,7 +73,7 @@ export function getNoteMenu(props: {
});
os.post({
initialNote: appearNote,
initialNote: appearNote as NoteDraft,
renote: appearNote.renote,
reply: appearNote.reply,
channel: appearNote.channel,
@ -83,7 +84,7 @@ export function getNoteMenu(props: {
async function edit() {
os.post({
initialNote: appearNote,
initialNote: appearNote as NoteDraft,
renote: appearNote.renote,
reply: appearNote.reply,
channel: appearNote.channel,

View file

@ -1,385 +1,6 @@
// TODO: sharedに置いてバックエンドのと統合したい
export const iso639Langs1 = {
af: {
nativeName: "Afrikaans",
},
ak: {
nativeName: "Tɕɥi",
},
ar: {
nativeName: "العربية",
rtl: true,
},
ay: {
nativeName: "Aymar aru",
},
az: {
nativeName: "Azərbaycan dili",
},
be: {
nativeName: "Беларуская",
},
bg: {
nativeName: "Български",
},
bn: {
nativeName: "বাংলা",
},
br: {
nativeName: "Brezhoneg",
},
bs: {
nativeName: "Bosanski",
},
ca: {
nativeName: "Català",
},
cs: {
nativeName: "Čeština",
},
cy: {
nativeName: "Cymraeg",
},
da: {
nativeName: "Dansk",
},
de: {
nativeName: "Deutsch",
},
el: {
nativeName: "Ελληνικά",
},
en: {
nativeName: "English",
},
eo: {
nativeName: "Esperanto",
},
es: {
nativeName: "Español",
},
et: {
nativeName: "eesti keel",
},
eu: {
nativeName: "Euskara",
},
fa: {
nativeName: "فارسی",
rtl: true,
},
ff: {
nativeName: "Fulah",
},
fi: {
nativeName: "Suomi",
},
fo: {
nativeName: "Føroyskt",
},
fr: {
nativeName: "Français",
},
fy: {
nativeName: "Frysk",
},
ga: {
nativeName: "Gaeilge",
},
gd: {
nativeName: "Gàidhlig",
},
gl: {
nativeName: "Galego",
},
gn: {
nativeName: "Avañe'ẽ",
},
gu: {
nativeName: "ગુજરાતી",
},
gv: {
nativeName: "Gaelg",
},
he: {
nativeName: "עברית‏",
rtl: true,
},
hi: {
nativeName: "हिन्दी",
},
hr: {
nativeName: "Hrvatski",
},
ht: {
nativeName: "Kreyòl",
},
hu: {
nativeName: "Magyar",
},
hy: {
nativeName: "Հայերեն",
},
id: {
nativeName: "Bahasa Indonesia",
},
is: {
nativeName: "Íslenska",
},
it: {
nativeName: "Italiano",
},
ja: {
nativeName: "日本語",
},
jv: {
nativeName: "Basa Jawa",
},
ka: {
nativeName: "ქართული",
},
kk: {
nativeName: "Қазақша",
},
kl: {
nativeName: "kalaallisut",
},
km: {
nativeName: "ភាសាខ្មែរ",
},
kn: {
nativeName: "ಕನ್ನಡ",
},
ko: {
nativeName: "한국어",
},
ku: {
nativeName: "Kurdî",
},
kw: {
nativeName: "Kernewek",
},
la: {
nativeName: "Latin",
},
lb: {
nativeName: "Lëtzebuergesch",
},
li: {
nativeName: "Lèmbörgs",
},
lt: {
nativeName: "Lietuvių",
},
lv: {
nativeName: "Latviešu",
},
mg: {
nativeName: "Malagasy",
},
mk: {
nativeName: "Македонски",
},
ml: {
nativeName: "മലയാളം",
},
mn: {
nativeName: "Монгол",
},
mr: {
nativeName: "मराठी",
},
ms: {
nativeName: "Bahasa Melayu",
},
mt: {
nativeName: "Malti",
},
my: {
nativeName: "ဗမာစကာ",
},
no: {
nativeName: "Norsk",
},
nb: {
nativeName: "Norsk (bokmål)",
},
ne: {
nativeName: "नेपाली",
},
nl: {
nativeName: "Nederlands",
},
nn: {
nativeName: "Norsk (nynorsk)",
},
oc: {
nativeName: "Occitan",
},
or: {
nativeName: "ଓଡ଼ିଆ",
},
pa: {
nativeName: "ਪੰਜਾਬੀ",
},
pl: {
nativeName: "Polski",
},
ps: {
nativeName: "پښتو",
rtl: true,
},
pt: {
nativeName: "Português",
},
qu: {
nativeName: "Qhichwa",
},
rm: {
nativeName: "Rumantsch",
},
ro: {
nativeName: "Română",
},
ru: {
nativeName: "Русский",
},
sa: {
nativeName: "संस्कृतम्",
},
se: {
nativeName: "Davvisámegiella",
},
sh: {
nativeName: "српскохрватски",
},
si: {
nativeName: "සිංහල",
},
sk: {
nativeName: "Slovenčina",
},
sl: {
nativeName: "Slovenščina",
},
so: {
nativeName: "Soomaaliga",
},
sq: {
nativeName: "Shqip",
},
sr: {
nativeName: "Српски",
},
su: {
nativeName: "Basa Sunda",
},
sv: {
nativeName: "Svenska",
},
sw: {
nativeName: "Kiswahili",
},
ta: {
nativeName: "தமிழ்",
},
te: {
nativeName: "తెలుగు",
},
tg: {
nativeName: "забо́ни тоҷикӣ́",
},
th: {
nativeName: "ภาษาไทย",
},
tr: {
nativeName: "Türkçe",
},
tt: {
nativeName: "татарча",
},
uk: {
nativeName: "Українська",
},
ur: {
nativeName: "اردو",
rtl: true,
},
uz: {
nativeName: "O'zbek",
},
vi: {
nativeName: "Tiếng Việt",
},
xh: {
nativeName: "isiXhosa",
},
yi: {
nativeName: "ייִדיש",
rtl: true,
},
zh: {
nativeName: "中文",
},
zu: {
nativeName: "isiZulu",
},
};
import { langmap as _langmap } from "firefish-js";
export const iso639Langs3 = {
ach: {
nativeName: "Lwo",
},
ady: {
nativeName: "Адыгэбзэ",
},
cak: {
nativeName: "Maya Kaqchikel",
},
chr: {
nativeName: "ᏣᎳᎩ (tsalagi)",
},
dsb: {
nativeName: "Dolnoserbšćina",
},
fil: {
nativeName: "Filipino",
},
hsb: {
nativeName: "Hornjoserbšćina",
},
kab: {
nativeName: "Taqbaylit",
},
mai: {
nativeName: "मैथिली, মৈথিলী",
},
tlh: {
nativeName: "tlhIngan-Hol",
},
tok: {
nativeName: "Toki Pona",
},
yue: {
nativeName: "粵語",
},
nan: {
nativeName: "閩南語",
},
};
export const langmapNoRegion = Object.assign({}, iso639Langs1, iso639Langs3);
export const iso639Regional = {
"zh-hans": {
nativeName: "中文(简体)",
},
"zh-hant": {
nativeName: "中文(繁體)",
},
};
export const langmap = Object.assign({}, langmapNoRegion, iso639Regional);
export const langmap = _langmap;
/**
* @see https://github.com/komodojp/tinyld/blob/develop/docs/langs.md

View file

@ -39,7 +39,7 @@ export function languageContains(
) {
if (!langCode1 || !langCode2) return false;
return parentLanguage(langCode2) === langCode1;
return langCode1 === langCode2 || parentLanguage(langCode2) === langCode1;
}
export function parentLanguage(langCode: string | null) {

View file

@ -1,9 +1,12 @@
import { markRaw, ref } from "vue";
import { markRaw } from "vue";
import type { ApiTypes, entities } from "firefish-js";
import { isSignedIn, me } from "./me";
import { Storage } from "./pizzax";
import type { NoteVisibility } from "@/types/note";
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
type TODO = any;
export const postFormActions: {
title: string;
handler: (from, update) => void | Promise<void>;
@ -152,7 +155,7 @@ export const defaultStore = markRaw(
type: string;
size: "verySmall" | "small" | "medium" | "large" | "veryLarge";
black: boolean;
props: Record<string, any>;
props: Record<string, TODO>;
}[],
},
widgets: {
@ -161,7 +164,7 @@ export const defaultStore = markRaw(
name: string;
id: string;
place: string | null;
data: Record<string, any>;
data: Record<string, TODO>;
}[],
},
tl: {
@ -465,109 +468,6 @@ export const defaultStore = markRaw(
}),
);
// TODO: 他のタブと永続化されたstateを同期
import ColdStore from "./cold-store";
const PREFIX = "miux:";
interface Plugin {
id: string;
name: string;
active: boolean;
configData: Record<string, any>;
token: string;
ast: any[];
}
import darkTheme from "@/themes/d-rosepine.json5";
/**
* Storage for configuration information that does not need to be constantly loaded into memory (non-reactive)
*/
import lightTheme from "@/themes/l-rosepinedawn.json5";
export class ColdDeviceStorage {
public static default = {
lightTheme,
darkTheme,
syncDeviceDarkMode: true,
plugins: [] as Plugin[],
mediaVolume: 0.5,
vibrate: false,
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 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;
}
localStorage.setItem(PREFIX + key, JSON.stringify(value));
for (const watcher of this.watchers) {
if (watcher.key === key) watcher.callback(value);
}
}
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;
}
/**
* 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);
},
};
}
}
export const ColdDeviceStorage = ColdStore;

View file

@ -31,6 +31,7 @@ export function reloadStream() {
isReloading = true;
stream.close();
// biome-ignore lint/suspicious/noAssignInExpressions: assign intentionally
stream.once("_connected_", () => (isReloading = false));
stream.stream.reconnect();
isReloading = false;

View file

@ -17,7 +17,8 @@ export async function fetchThemes(): Promise<void> {
key: "themes",
});
localStorage.setItem(lsCacheKey, JSON.stringify(themes));
} catch (err) {
// biome-ignore lint/suspicious/noExplicitAny: Safely any
} catch (err: any) {
if (err.code === "NO_SUCH_KEY") return;
throw err;
}

View file

@ -0,0 +1,78 @@
import type { TypeUtils } from "firefish-js";
export type BasePageContent = {
name: string;
};
export type PageContentTextInput = BasePageContent & {
type: "textInput";
default: string;
};
export type PageContentTextareaInput = BasePageContent & {
type: "textareaInput";
default?: string;
};
export type PageContentNumberInput = BasePageContent & {
type: "numberInput";
default?: number;
};
export type PageContentSwitch = BasePageContent & {
type: "switch";
default?: boolean;
};
export type PageContentCounter = BasePageContent & {
type: "counter";
default?: number;
};
export type PageContentRadioButton = BasePageContent & {
type: "radioButton";
default?: string;
};
export type PageContentChildren =
| PageContentTextInput
| PageContentTextareaInput
| PageContentNumberInput
| PageContentSwitch
| PageContentCounter
| PageContentRadioButton;
export type PageContentParent = {
type: "parent";
children: PageContentChildren[];
};
export type PageContent = PageContentParent | PageContentChildren;
export type GetPageVar<T extends PageContentChildren> = {
name: string;
type: TypeUtils.NonUndefinedAble<T["default"]> extends string
? "string"
: TypeUtils.NonUndefinedAble<T["default"]> extends boolean
? "boolean"
: TypeUtils.NonUndefinedAble<T["default"]> extends number
? "number"
: never;
value: TypeUtils.NonUndefinedAble<T["default"]>;
};
export type PageVar =
| {
name: string;
type: "string";
value: string;
}
| {
name: string;
type: "boolean";
value: boolean;
}
| {
name: string;
type: "number";
value: number;
};

View file

@ -0,0 +1,5 @@
export type Predicate<T> = (a: T) => boolean;
export type Relation<T, U> = (a: T, b: U) => boolean;
export type EndoRelation<T> = Relation<T, T>;

View file

@ -17,7 +17,7 @@
},
"minify": false,
"module": {
"type": "commonjs",
"type": "es6",
"strict": true,
"resolveFully": true
}

View file

@ -4,6 +4,7 @@
"description": "Firefish SDK for JavaScript",
"homepage": "https://firefish.dev/firefish/firefish/-/tree/develop/packages/firefish-js",
"main": "./built/index.js",
"type": "module",
"types": "./src/index.ts",
"license": "MIT",
"scripts": {
@ -35,7 +36,7 @@
"typescript": "5.4.5"
},
"files": [
"built"
"built", "src"
],
"dependencies": {
"eventemitter3": "5.0.1",

View file

@ -1,4 +1,4 @@
import type { Endpoints } from "./api.types";
import type { Endpoints } from "./api.types.js";
const MK_API_ERROR = Symbol();
@ -7,10 +7,12 @@ export type APIError = {
code: string;
message: string;
kind: "client" | "server";
info: Record<string, any>;
info: Record<string, unknown>;
};
export function isAPIError(reason: any): reason is APIError {
// biome-ignore lint/suspicious/noExplicitAny: used it intentially
type ExplicitlyUsedAny = any;
export function isAPIError(reason: ExplicitlyUsedAny): reason is APIError {
return reason[MK_API_ERROR] === true;
}
@ -24,7 +26,7 @@ export type FetchLike = (
},
) => Promise<{
status: number;
json(): Promise<any>;
json(): Promise<ExplicitlyUsedAny>;
}>;
type IsNeverType<T> = [T] extends [never] ? true : false;
@ -36,7 +38,10 @@ type IsCaseMatched<
P extends Endpoints[E]["req"],
C extends number,
> = IsNeverType<
StrictExtract<Endpoints[E]["res"]["$switch"]["$cases"][C], [P, any]>
StrictExtract<
Endpoints[E]["res"]["$switch"]["$cases"][C],
[P, ExplicitlyUsedAny]
>
> extends false
? true
: false;
@ -45,7 +50,10 @@ type GetCaseResult<
E extends keyof Endpoints,
P extends Endpoints[E]["req"],
C extends number,
> = StrictExtract<Endpoints[E]["res"]["$switch"]["$cases"][C], [P, any]>[1];
> = StrictExtract<
Endpoints[E]["res"]["$switch"]["$cases"][C],
[P, ExplicitlyUsedAny]
>[1];
export class APIClient {
public origin: string;
@ -70,7 +78,7 @@ export class APIClient {
credential?: string | null | undefined,
): Promise<
Endpoints[E]["res"] extends {
$switch: { $cases: [any, any][]; $default: any };
$switch: { $cases: [unknown, unknown][]; $default: unknown };
}
? IsCaseMatched<E, P, 0> extends true
? GetCaseResult<E, P, 0>

View file

@ -38,7 +38,7 @@ import type {
UserList,
UserLite,
UserSorting,
} from "./entities";
} from "./entities.js";
import type * as consts from "./consts";
@ -264,9 +264,9 @@ export type Endpoints = {
// clips
"clips/add-note": { req: TODO; res: TODO };
"clips/create": { req: TODO; res: TODO };
"clips/create": { req: TODO; res: Clip };
"clips/delete": { req: { clipId: Clip["id"] }; res: null };
"clips/list": { req: TODO; res: TODO };
"clips/list": { req: TODO; res: Clip[] };
"clips/notes": { req: TODO; res: TODO };
"clips/show": { req: TODO; res: TODO };
"clips/update": { req: TODO; res: TODO };
@ -363,6 +363,16 @@ export type Endpoints = {
res: DriveFile[];
};
"email-address/available": {
req: {
emailAddress: string;
};
res: {
available?: boolean;
reason: string | null;
};
};
// endpoint
endpoint: {
req: { endpoint: string };
@ -739,6 +749,18 @@ export type Endpoints = {
};
res: Note[];
};
"notes/thread-muting/create": {
req: {
noteId: Note["id"];
};
res: null;
};
"notes/thread-muting/delete": {
req: {
noteId: Note["id"];
};
res: null;
};
"notes/hybrid-timeline": {
req: {
limit?: number;
@ -759,6 +781,12 @@ export type Endpoints = {
};
res: Note[];
};
"notes/make-private": {
req: {
noteId: Note["id"];
};
res: null;
};
"notes/mentions": {
req: {
following?: boolean;
@ -900,6 +928,16 @@ export type Endpoints = {
// promo
"promo/read": { req: TODO; res: TODO };
// release
release: {
req: null;
res: {
version: string;
notes: string;
screenshots: string[];
};
};
// request-reset-password
"request-reset-password": {
req: { username: string; email: string };
@ -922,8 +960,36 @@ export type Endpoints = {
// ck specific
"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/register": { req: TODO; res: TODO };
"sw/unregister": {
req: {
endpoint: string;
};
res: null;
};
// username
"username/available": {

View file

@ -1,4 +1,5 @@
import type * as consts from "./consts";
import type * as consts from "./consts.js";
import type { Packed } from "./misc/schema.js";
export type ID = string;
export type DateString = string;
@ -116,6 +117,7 @@ export type MeDetailed = UserDetailed & {
preventAiLearning: boolean;
receiveAnnouncementEmail: boolean;
usePasswordLessLogin: boolean;
token: string;
[other: string]: any;
};
@ -482,7 +484,7 @@ export type Announcement = {
imageUrl: string | null;
isRead?: boolean;
isGoodNews: boolean;
showPopUp: boolean;
showPopup: boolean;
};
export type Antenna = {
@ -513,7 +515,7 @@ export type AuthSession = {
export type Ad = TODO;
export type Clip = TODO;
export type Clip = Packed<"Clip">;
export type NoteFavorite = {
id: ID;

View file

@ -1,14 +1,17 @@
import * as acct from "./acct";
import type { Acct } from "./acct";
import { Endpoints } from "./api.types";
import type * as ApiTypes from "./api.types";
import * as consts from "./consts";
import Stream, { Connection } from "./streaming";
import * as StreamTypes from "./streaming.types";
import type * as TypeUtils from "./type-utils";
import * as acct from "./acct.js";
import type { Acct } from "./acct.js";
import type { Endpoints } from "./api.types.js";
import type * as ApiTypes from "./api.types.js";
import * as consts from "./consts.js";
import Stream, { Connection } from "./streaming.js";
import * as StreamTypes from "./streaming.types.js";
import type * as TypeUtils from "./type-utils.js";
import type * as SchemaTypes from "./misc/schema.js";
import * as Schema from "./misc/schema.js";
export {
Endpoints,
type Endpoints,
type ApiTypes,
Stream,
Connection as ChannelConnection,
@ -16,6 +19,8 @@ export {
acct,
type Acct,
type TypeUtils,
Schema,
type SchemaTypes,
};
export const permissions = consts.permissions;
@ -26,9 +31,12 @@ export const languages = consts.languages;
export const ffVisibility = consts.ffVisibility;
export const instanceSortParam = consts.instanceSortParam;
import { langmap, type PostLanguage } from "./misc/langmap.js";
export { langmap, type PostLanguage };
// api extractor not supported yet
//export * as api from './api';
//export * as entities from './entities';
import * as api from "./api";
import * as entities from "./entities";
import * as api from "./api.js";
import * as entities from "./entities.js";
export { api, entities };

View file

@ -1,4 +1,3 @@
// TODO: sharedに置いてバックエンドのと統合したい
export const iso639Langs1 = {
af: {
nativeName: "Afrikaans",

View file

@ -0,0 +1,241 @@
import {
packedUserLiteSchema,
packedUserDetailedNotMeOnlySchema,
packedMeDetailedOnlySchema,
packedUserDetailedNotMeSchema,
packedMeDetailedSchema,
packedUserDetailedSchema,
packedUserSchema,
} from "../schema/user.js";
import { packedNoteSchema } from "../schema/note.js";
import { packedUserListSchema } from "../schema/user-list.js";
import { packedAppSchema } from "../schema/app.js";
import { packedMessagingMessageSchema } from "../schema/messaging-message.js";
import { packedNotificationSchema } from "../schema/notification.js";
import { packedDriveFileSchema } from "../schema/drive-file.js";
import { packedDriveFolderSchema } from "../schema/drive-folder.js";
import { packedFollowingSchema } from "../schema/following.js";
import { packedMutingSchema } from "../schema/muting.js";
import { packedRenoteMutingSchema } from "../schema/renote-muting.js";
import { packedReplyMutingSchema } from "../schema/reply-muting.js";
import { packedBlockingSchema } from "../schema/blocking.js";
import { packedNoteReactionSchema } from "../schema/note-reaction.js";
import { packedHashtagSchema } from "../schema/hashtag.js";
import { packedPageSchema } from "../schema/page.js";
import { packedUserGroupSchema } from "../schema/user-group.js";
import { packedNoteFavoriteSchema } from "../schema/note-favorite.js";
import { packedChannelSchema } from "../schema/channel.js";
import { packedAntennaSchema } from "../schema/antenna.js";
import { packedClipSchema } from "../schema/clip.js";
import { packedFederationInstanceSchema } from "../schema/federation-instance.js";
import { packedQueueCountSchema } from "../schema/queue.js";
import { packedGalleryPostSchema } from "../schema/gallery-post.js";
import { packedEmojiSchema } from "../schema/emoji.js";
import { packedNoteEdit } from "../schema/note-edit.js";
import { packedNoteFileSchema } from "../schema/note-file.js";
import { packedAbuseUserReportSchema } from "../schema/abuse-user-report.js";
export const refs = {
AbuseUserReport: packedAbuseUserReportSchema,
UserLite: packedUserLiteSchema,
UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema,
MeDetailedOnly: packedMeDetailedOnlySchema,
UserDetailedNotMe: packedUserDetailedNotMeSchema,
MeDetailed: packedMeDetailedSchema,
UserDetailed: packedUserDetailedSchema,
User: packedUserSchema,
UserList: packedUserListSchema,
UserGroup: packedUserGroupSchema,
App: packedAppSchema,
MessagingMessage: packedMessagingMessageSchema,
Note: packedNoteSchema,
NoteFile: packedNoteFileSchema,
NoteEdit: packedNoteEdit,
NoteReaction: packedNoteReactionSchema,
NoteFavorite: packedNoteFavoriteSchema,
Notification: packedNotificationSchema,
DriveFile: packedDriveFileSchema,
DriveFolder: packedDriveFolderSchema,
Following: packedFollowingSchema,
Muting: packedMutingSchema,
RenoteMuting: packedRenoteMutingSchema,
ReplyMuting: packedReplyMutingSchema,
Blocking: packedBlockingSchema,
Hashtag: packedHashtagSchema,
Page: packedPageSchema,
Channel: packedChannelSchema,
QueueCount: packedQueueCountSchema,
Antenna: packedAntennaSchema,
Clip: packedClipSchema,
FederationInstance: packedFederationInstanceSchema,
GalleryPost: packedGalleryPostSchema,
Emoji: packedEmojiSchema,
};
// biome-ignore lint/suspicious/noExplicitAny: used it intentially
type ExplicitlyUsedAny = any;
export type Packed<x extends keyof typeof refs> = SchemaType<(typeof refs)[x]>;
type TypeStringef =
| "null"
| "boolean"
| "integer"
| "number"
| "string"
| "array"
| "object"
| "any";
type StringDefToType<T extends TypeStringef> = T extends "null"
? null
: T extends "boolean"
? boolean
: T extends "integer"
? number
: T extends "number"
? number
: T extends "string"
? string | Date
: T extends "array"
? ReadonlyArray<ExplicitlyUsedAny>
: T extends "object"
? Record<string, ExplicitlyUsedAny>
: ExplicitlyUsedAny;
// https://swagger.io/specification/?sbsearch=optional#schema-object
type OfSchema = {
readonly anyOf?: ReadonlyArray<Schema>;
readonly oneOf?: ReadonlyArray<Schema>;
readonly allOf?: ReadonlyArray<Schema>;
};
export interface Schema extends OfSchema {
readonly type?: TypeStringef;
readonly nullable?: boolean;
readonly optional?: boolean;
readonly items?: Schema;
readonly properties?: Obj;
readonly required?: ReadonlyArray<
Extract<keyof NonNullable<this["properties"]>, string>
>;
readonly description?: string;
readonly example?: ExplicitlyUsedAny;
readonly format?: string;
readonly ref?: keyof typeof refs;
readonly enum?: ReadonlyArray<string>;
readonly default?:
| (this["type"] extends TypeStringef
? StringDefToType<this["type"]>
: ExplicitlyUsedAny)
| null;
readonly maxLength?: number;
readonly minLength?: number;
readonly maximum?: number;
readonly minimum?: number;
readonly pattern?: string;
}
type RequiredPropertyNames<s extends Obj> = {
[K in keyof s]: // K is not optional
s[K]["optional"] extends false
? K
: // K has default value
s[K]["default"] extends
| null
| string
| number
| boolean
| Record<string, unknown>
? K
: never;
}[keyof s];
export type Obj = Record<string, Schema>;
// https://github.com/misskey-dev/misskey/issues/8535
// To avoid excessive stack depth error,
// deceive TypeScript with UnionToIntersection (or more precisely, `infer` expression within it).
export type ObjType<
s extends Obj,
RequiredProps extends keyof s,
> = UnionToIntersection<
{
-readonly [R in RequiredPropertyNames<s>]-?: SchemaType<s[R]>;
} & {
-readonly [R in RequiredProps]-?: SchemaType<s[R]>;
} & {
-readonly [P in keyof s]?: SchemaType<s[P]>;
}
>;
type NullOrUndefined<p extends Schema, T> =
| (p["nullable"] extends true ? null : never)
| (p["optional"] extends true ? undefined : never)
| T;
// https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
// Get intersection from union
type UnionToIntersection<U> = (
U extends ExplicitlyUsedAny
? (k: U) => void
: never
) extends (k: infer I) => void
? I
: never;
// https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552
// To get union, we use `Foo extends ExplicitlyUsedAny ? Hoge<Foo> : never`
type UnionSchemaType<
a extends readonly ExplicitlyUsedAny[],
X extends Schema = a[number],
> = X extends ExplicitlyUsedAny ? SchemaType<X> : never;
type ArrayUnion<T> = T extends ExplicitlyUsedAny ? Array<T> : never;
export type SchemaTypeDef<p extends Schema> = p["type"] extends "null"
? null
: p["type"] extends "integer"
? number
: p["type"] extends "number"
? number
: p["type"] extends "string"
? p["enum"] extends readonly string[]
? p["enum"][number]
: p["format"] extends "date-time"
? string
: // Dateにする
string
: p["type"] extends "boolean"
? boolean
: p["type"] extends "object"
? p["ref"] extends keyof typeof refs
? Packed<p["ref"]>
: p["properties"] extends NonNullable<Obj>
? ObjType<p["properties"], NonNullable<p["required"]>[number]>
: p["anyOf"] extends ReadonlyArray<Schema>
? UnionSchemaType<p["anyOf"]> &
Partial<UnionToIntersection<UnionSchemaType<p["anyOf"]>>>
: p["allOf"] extends ReadonlyArray<Schema>
? UnionToIntersection<UnionSchemaType<p["allOf"]>>
: ExplicitlyUsedAny
: p["type"] extends "array"
? p["items"] extends OfSchema
? p["items"]["anyOf"] extends ReadonlyArray<Schema>
? UnionSchemaType<NonNullable<p["items"]["anyOf"]>>[]
: p["items"]["oneOf"] extends ReadonlyArray<Schema>
? ArrayUnion<
UnionSchemaType<NonNullable<p["items"]["oneOf"]>>
>
: p["items"]["allOf"] extends ReadonlyArray<Schema>
? UnionToIntersection<
UnionSchemaType<NonNullable<p["items"]["allOf"]>>
>[]
: never
: p["items"] extends NonNullable<Schema>
? SchemaTypeDef<p["items"]>[]
: ExplicitlyUsedAny[]
: p["oneOf"] extends ReadonlyArray<Schema>
? UnionSchemaType<p["oneOf"]>
: ExplicitlyUsedAny;
export type SchemaType<p extends Schema> = NullOrUndefined<p, SchemaTypeDef<p>>;

View file

@ -1,5 +1,3 @@
import { config } from "@/config.js";
export const packedFederationInstanceSchema = {
type: "object",
properties: {
@ -83,7 +81,7 @@ export const packedFederationInstanceSchema = {
type: "string",
optional: false,
nullable: true,
example: config.version,
example: "20240424",
},
openRegistrations: {
type: "boolean",

View file

@ -1,4 +1,4 @@
import { langmap } from "@/misc/langmap.js";
import { langmap } from "../misc/langmap.js";
export const packedNoteSchema = {
type: "object",
@ -208,15 +208,5 @@ export const packedNoteSchema = {
optional: true,
nullable: true,
},
myRenoteCount: {
type: "number",
optional: true,
nullable: false,
},
quoteCount: {
type: "number",
optional: false,
nullable: false,
},
},
} as const;

View file

@ -1,4 +1,4 @@
import { notificationTypes } from "@/types.js";
import { notificationTypes } from "../consts.js";
export const packedNotificationSchema = {
type: "object",

View file

@ -1,6 +1,6 @@
import { EventEmitter } from "eventemitter3";
import ReconnectingWebsocket from "reconnecting";
import type { BroadcastEvents, Channels } from "./streaming.types";
import type { BroadcastEvents, Channels } from "./streaming.types.js";
function autobind(instance: any): void {
const prototype = Object.getPrototypeOf(instance);

View file

@ -12,7 +12,7 @@ import type {
UserGroup,
UserLite,
} from "./entities";
import type { Connection } from "./streaming";
import type { Connection } from "./streaming.js";
type FIXME = any;

View file

@ -1,7 +1,9 @@
import type { Endpoints } from "./api.types";
import type { Endpoints } from "./api.types.js";
type PropertyOfType<Type, U> = {
export type PropertyOfType<Type, U> = {
[K in keyof Type]: Type[K] extends U ? K : never;
}[keyof Type];
export type EndpointsOf<T> = PropertyOfType<Endpoints, { res: T }>;
export type NonUndefinedAble<T> = T extends undefined ? never : T;

View file

@ -141,6 +141,9 @@ importers:
file-type:
specifier: 19.0.0
version: 19.0.0
firefish-js:
specifier: workspace:*
version: link:../firefish-js
fluent-ffmpeg:
specifier: 2.1.2
version: 2.1.2