fix types

This commit is contained in:
Lhcfl 2024-04-13 23:08:58 +08:00
parent 3e43819ba1
commit 16880b1231
16 changed files with 135 additions and 102 deletions

View file

@ -1,3 +1,4 @@
// biome-ignore lint/suspicious/noExplicitAny:
type FIXME = any;
declare const _LANGS_: string[][];

View file

@ -1,7 +1,7 @@
<template>
<article
v-if="!muted.muted || muted.what === 'reply'"
:id="detailedView ? appearNote.id : null"
:id="detailedView ? appearNote.id : undefined"
ref="el"
v-size="{ max: [450, 500] }"
class="wrpstxzv"
@ -35,10 +35,10 @@
:parent-id="parentId"
:conversation="conversation"
:detailed-view="detailedView"
@focusfooter="footerEl.focus()"
@focusfooter="footerEl!.focus()"
/>
<div v-if="translating || translation" class="translation">
<MkLoading v-if="translating" mini />
<MkLoading v-if="translating || translation == null" mini />
<div v-else class="translated">
<b
>{{
@ -217,6 +217,7 @@ import { useNoteCapture } from "@/scripts/use-note-capture";
import { defaultStore } from "@/store";
import { deepClone } from "@/scripts/clone";
import icon from "@/scripts/icon";
import type { NoteTranslation } from "@/types/note";
const router = useRouter();
@ -256,12 +257,12 @@ const isRenote =
note.value.fileIds.length === 0 &&
note.value.poll == null;
const el = ref<HTMLElement>();
const footerEl = ref<HTMLElement>();
const el = ref<HTMLElement | null>(null);
const footerEl = ref<HTMLElement | null>(null);
const menuButton = ref<HTMLElement>();
const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const reactButton = ref<HTMLElement>();
const starButton = ref<InstanceType<typeof XStarButton> | null>(null);
const renoteButton = ref<InstanceType<typeof XRenoteButton> | null>(null);
const reactButton = ref<HTMLElement | null>(null);
const appearNote = computed(() =>
isRenote ? (note.value.renote as entities.Note) : note.value,
);
@ -274,7 +275,7 @@ const muted = ref(
defaultStore.state.mutedLangs,
),
);
const translation = ref(null);
const translation = ref<NoteTranslation | null>(null);
const translating = ref(false);
const replies: entities.Note[] =
props.conversation
@ -330,21 +331,21 @@ useNoteCapture({
isDeletedRef: isDeleted,
});
function reply(viaKeyboard = false): void {
function reply(_viaKeyboard = false): void {
pleaseLogin();
os.post({
reply: appearNote.value,
animation: !viaKeyboard,
// animation: !viaKeyboard,
}).then(() => {
focus();
});
}
function react(viaKeyboard = false): void {
function react(_viaKeyboard = false): void {
pleaseLogin();
blur();
reactionPicker.show(
reactButton.value,
reactButton.value!,
(reaction) => {
os.api("notes/reactions/create", {
noteId: appearNote.value.id,
@ -388,14 +389,15 @@ function menu(viaKeyboard = false): void {
}
function onContextmenu(ev: MouseEvent): void {
const isLink = (el: HTMLElement) => {
const isLink = (el: HTMLElement | null) => {
if (el == null) return;
if (el.tagName === "A") return true;
if (el.parentElement) {
return isLink(el.parentElement);
}
};
if (isLink(ev.target)) return;
if (window.getSelection().toString() !== "") return;
if (isLink(ev.target as HTMLElement | null)) return;
if (window.getSelection()?.toString() !== "") return;
if (defaultStore.state.useReactionPickerForContextMenu) {
ev.preventDefault();
@ -454,15 +456,15 @@ function onContextmenu(ev: MouseEvent): void {
}
function focus() {
el.value.focus();
el.value!.focus();
}
function blur() {
el.value.blur();
el.value!.blur();
}
function noteClick(e) {
if (document.getSelection().type === "Range" || !expandOnNoteClick) {
function noteClick(e: MouseEvent) {
if (document.getSelection()?.type === "Range" || !expandOnNoteClick) {
e.stopPropagation();
} else {
router.push(notePage(props.note));

View file

@ -84,14 +84,10 @@ import { formatDateTimeString } from "@/scripts/format-time-string";
import { addTime } from "@/scripts/time";
import { i18n } from "@/i18n";
import icon from "@/scripts/icon";
import type { PollType } from "@/types/post-form";
const props = defineProps<{
modelValue: {
expiresAt: string;
expiredAfter: number;
choices: string[];
multiple: boolean;
};
modelValue: PollType;
}>();
const emit = defineEmits<{
"update:modelValue": [

View file

@ -20,7 +20,7 @@
class="account _button"
@click="openAccountMenu"
>
<MkAvatar :user="postAccount ?? me" class="avatar" />
<MkAvatar :user="postAccount ?? me!" class="avatar" />
</button>
<div class="right">
<span
@ -297,14 +297,22 @@
</template>
<script lang="ts" setup>
import { computed, inject, nextTick, onMounted, ref, watch } from "vue";
import {
type Ref,
computed,
inject,
nextTick,
onMounted,
ref,
watch,
} from "vue";
import * as mfm from "mfm-js";
import autosize from "autosize";
import insertTextAtCursor from "insert-text-at-cursor";
import { length } from "stringz";
import { toASCII } from "punycode/";
import { acct } from "firefish-js";
import type { entities, languages } from "firefish-js";
import type { ApiTypes, entities, languages } from "firefish-js";
import { throttle } from "throttle-debounce";
import XNoteSimple from "@/components/MkNoteSimple.vue";
import XNotePreview from "@/components/MkNotePreview.vue";
@ -341,6 +349,7 @@ import type { MenuItem } from "@/types/menu";
import icon from "@/scripts/icon";
import MkVisibilityPicker from "@/components/MkVisibilityPicker.vue";
import type { NoteVisibility } from "@/types/note";
import type { NoteDraft, PollType } from "@/types/post-form";
const modal = inject("modal");
@ -353,11 +362,11 @@ const props = withDefaults(
specified?: entities.User;
initialText?: string;
initialVisibility?: NoteVisibility;
initialLanguage?: typeof languages;
initialLanguage?: (typeof languages)[number];
initialFiles?: entities.DriveFile[];
initialLocalOnly?: boolean;
initialVisibleUsers?: entities.User[];
initialNote?: entities.Note;
initialNote?: NoteDraft;
instant?: boolean;
fixed?: boolean;
autofocus?: boolean;
@ -390,12 +399,7 @@ const showBigPostButton = defaultStore.state.showBigPostButton;
const posting = ref(false);
const text = ref(props.initialText ?? "");
const files = ref(props.initialFiles ?? ([] as entities.DriveFile[]));
const poll = ref<{
choices: string[];
multiple: boolean;
expiresAt: string | null;
expiredAfter: string | null;
} | null>(null);
const poll = ref<PollType | null>(null);
const useCw = ref(false);
const showPreview = ref(defaultStore.state.showPreviewByDefault);
const cw = ref<string | null>(null);
@ -411,12 +415,12 @@ const visibility = ref(
: defaultStore.state.defaultNoteVisibility),
);
const visibleUsers = ref([]);
const visibleUsers = ref<entities.User[]>([]);
if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser);
}
const draghover = ref(false);
const quoteId = ref(null);
const quoteId = ref<string | null>(null);
const hasNotSpecifiedMentions = ref(false);
const recentHashtags = ref(
JSON.parse(localStorage.getItem("hashtags") || "[]"),
@ -500,7 +504,9 @@ const canPost = computed((): boolean => {
const withHashtags = computed(
defaultStore.makeGetterSetter("postFormWithHashtags"),
);
const hashtags = computed(defaultStore.makeGetterSetter("postFormHashtags"));
const hashtags = computed(
defaultStore.makeGetterSetter("postFormHashtags"),
) as Ref<string | null>;
watch(text, () => {
checkMissingMention();
@ -525,7 +531,7 @@ if (props.mention) {
if (
props.reply &&
(props.reply.user.username !== me.username ||
(props.reply.user.username !== me!.username ||
(props.reply.user.host != null && props.reply.user.host !== host))
) {
text.value = `@${props.reply.user.username}${
@ -545,7 +551,7 @@ if (props.reply && props.reply.text != null) {
: `@${x.username}@${toASCII(otherHost)}`;
// exclude me
if (me.username === x.username && (x.host == null || x.host === host))
if (me!.username === x.username && (x.host == null || x.host === host))
continue;
// remove duplicates
@ -579,7 +585,7 @@ if (
if (props.reply.visibleUserIds) {
os.api("users/show", {
userIds: props.reply.visibleUserIds.filter(
(uid) => uid !== me.id && uid !== props.reply.userId,
(uid) => uid !== me!.id && uid !== props.reply!.userId,
),
}).then((users) => {
users.forEach(pushVisibleUser);
@ -588,7 +594,7 @@ if (
visibility.value = "private";
}
if (props.reply.userId !== me.id) {
if (props.reply.userId !== me!.id) {
os.api("users/show", { userId: props.reply.userId }).then((user) => {
pushVisibleUser(user);
});
@ -615,7 +621,7 @@ const addRe = (s: string) => {
if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
useCw.value = true;
cw.value =
props.reply.user.username === me.username
props.reply.user.username === me!.username
? props.reply.cw
: addRe(props.reply.cw);
}
@ -894,11 +900,14 @@ function onCompositionEnd(ev: CompositionEvent) {
}
async function onPaste(ev: ClipboardEvent) {
if (ev.clipboardData == null) return;
for (const { item, i } of Array.from(ev.clipboardData.items).map(
(item, i) => ({ item, i }),
)) {
if (item.kind === "file") {
const file = item.getAsFile();
if (file == null) continue;
const lio = file.name.lastIndexOf(".");
const ext = lio >= 0 ? file.name.slice(lio) : "";
const formatted = `${formatTimeString(
@ -911,7 +920,7 @@ async function onPaste(ev: ClipboardEvent) {
const paste = ev.clipboardData?.getData("text") ?? "";
if (!props.renote && !quoteId.value && paste.startsWith(url + "/notes/")) {
if (!props.renote && !quoteId.value && paste.startsWith(`${url}/notes/`)) {
ev.preventDefault();
os.yesno({
@ -919,13 +928,13 @@ async function onPaste(ev: ClipboardEvent) {
text: i18n.ts.quoteQuestion,
}).then(({ canceled }) => {
if (canceled) {
insertTextAtCursor(textareaEl.value, paste);
insertTextAtCursor(textareaEl.value!, paste);
return;
}
quoteId.value = paste
.substring(url.length)
.match(/^\/notes\/(.+?)\/?$/)[1];
.match(/^\/notes\/(.+?)\/?$/)![1];
});
}
}
@ -956,16 +965,17 @@ function onDragover(ev) {
}
}
function onDragenter(ev) {
function onDragenter(_ev) {
draghover.value = true;
}
function onDragleave(ev) {
function onDragleave(_ev) {
draghover.value = false;
}
function onDrop(ev): void {
function onDrop(ev: DragEvent): void {
draghover.value = false;
if (ev.dataTransfer == null) return;
//
if (ev.dataTransfer.files.length > 0) {
@ -1064,7 +1074,7 @@ async function post() {
const processedText = preprocess(text.value);
let postData = {
let postData: ApiTypes.NoteSubmitReq = {
editId: props.editId ? props.editId : undefined,
text: processedText === "" ? undefined : processedText,
fileIds: files.value.length > 0 ? files.value.map((f) => f.id) : undefined,
@ -1092,7 +1102,7 @@ async function post() {
const hashtags_ = hashtags.value
.trim()
.split(" ")
.map((x) => (x.startsWith("#") ? x : "#" + x))
.map((x) => (x.startsWith("#") ? x : `#${x}`))
.join(" ");
postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_;
}
@ -1104,11 +1114,11 @@ async function post() {
}
}
let token;
let token: string | undefined;
if (postAccount.value) {
const storedAccounts = await getAccounts();
token = storedAccounts.find((x) => x.id === postAccount.value.id)?.token;
token = storedAccounts.find((x) => x.id === postAccount.value!.id)?.token;
}
posting.value = true;
@ -1119,10 +1129,11 @@ async function post() {
deleteDraft();
emit("posted");
if (postData.text && postData.text !== "") {
const hashtags_ = mfm
.parse(postData.text)
.filter((x) => x.type === "hashtag")
.map((x) => x.props.hashtag);
const hashtags_ = (
mfm
.parse(postData.text)
.filter((x) => x.type === "hashtag") as mfm.MfmHashtag[]
).map((x) => x.props.hashtag);
const history = JSON.parse(
localStorage.getItem("hashtags") || "[]",
) as string[];
@ -1133,7 +1144,7 @@ async function post() {
}
posting.value = false;
postAccount.value = null;
nextTick(() => autosize.update(textareaEl.value));
nextTick(() => autosize.update(textareaEl.value!));
});
})
.catch((err: { message: string; id: string }) => {
@ -1169,19 +1180,23 @@ function cancel() {
function insertMention() {
os.selectUser().then((user) => {
insertTextAtCursor(textareaEl.value, "@" + acct.toString(user) + " ");
insertTextAtCursor(textareaEl.value!, `@${acct.toString(user)} `);
});
}
async function insertEmoji(ev: MouseEvent) {
os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textareaEl.value);
os.openEmojiPicker(
(ev.currentTarget ?? ev.target) as HTMLElement,
{},
textareaEl.value,
);
}
async function openCheatSheet(ev: MouseEvent) {
os.popup(XCheatSheet, {}, {}, "closed");
}
function showActions(ev) {
function showActions(ev: MouseEvent) {
os.popupMenu(
postFormActions.map((action) => ({
text: action.title,
@ -1198,7 +1213,7 @@ function showActions(ev) {
);
},
})),
ev.currentTarget ?? ev.target,
(ev.currentTarget ?? ev.target) as HTMLElement,
);
}
@ -1209,9 +1224,9 @@ function openAccountMenu(ev: MouseEvent) {
{
withExtraOperation: false,
includeCurrentAccount: true,
active: postAccount.value != null ? postAccount.value.id : me.id,
active: postAccount.value != null ? postAccount.value.id : me!.id,
onChoose: (account) => {
if (account.id === me.id) {
if (account.id === me!.id) {
postAccount.value = null;
} else {
postAccount.value = account;
@ -1232,14 +1247,14 @@ onMounted(() => {
}
// TODO: detach when unmount
new Autocomplete(textareaEl.value, text);
new Autocomplete(cwInputEl.value, cw);
new Autocomplete(hashtagsInputEl.value, hashtags);
new Autocomplete(textareaEl.value!, text);
new Autocomplete(cwInputEl.value!, cw as Ref<string>);
new Autocomplete(hashtagsInputEl.value!, hashtags as Ref<string>);
autosize(textareaEl.value);
autosize(textareaEl.value!);
nextTick(() => {
autosize(textareaEl.value);
autosize(textareaEl.value!);
// 稿
if (!props.instant && !props.mention && !props.specified) {
const draft = JSON.parse(localStorage.getItem("drafts") || "{}")[
@ -1275,8 +1290,8 @@ onMounted(() => {
};
}
visibility.value = init.visibility;
localOnly.value = init.localOnly;
language.value = init.lang;
localOnly.value = init.localOnly ?? false;
language.value = init.lang ?? null;
quoteId.value = init.renote ? init.renote.id : null;
}
@ -1289,7 +1304,7 @@ onMounted(() => {
}
nextTick(() => watchForDraft());
nextTick(() => autosize.update(textareaEl.value));
nextTick(() => autosize.update(textareaEl.value!));
});
});
</script>

View file

@ -25,6 +25,7 @@ import type { entities, languages } from "firefish-js";
import MkModal from "@/components/MkModal.vue";
import MkPostForm from "@/components/MkPostForm.vue";
import type { NoteVisibility } from "@/types/note";
import type { NoteDraft } from "@/types/post-form";
const props = defineProps<{
reply?: entities.Note;
@ -34,11 +35,11 @@ const props = defineProps<{
specified?: entities.User;
initialText?: string;
initialVisibility?: NoteVisibility;
initialLanguage?: typeof languages;
initialLanguage?: (typeof languages)[number];
initialFiles?: entities.DriveFile[];
initialLocalOnly?: boolean;
initialVisibleUsers?: entities.User[];
initialNote?: entities.Note;
initialNote?: NoteDraft;
instant?: boolean;
fixed?: boolean;
autofocus?: boolean;
@ -53,7 +54,7 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
const form = shallowRef<InstanceType<typeof MkPostForm>>();
function onPosted() {
modal.value.close({
modal.value!.close({
useSendAnimation: true,
});
}

View file

@ -319,7 +319,7 @@ const usernameState = ref<
| "invalid-format"
| "min-range"
| "max-range"
>(null);
>(null);
const invitationState = ref<null | "entered">(null);
const emailState = ref<
| null
@ -331,9 +331,10 @@ const emailState = ref<
| "unavailable:mx"
| "unavailable:smtp"
| "unavailable"
| "error">(null);
| "error"
>(null);
const passwordStrength = ref<"" | "low" | "medium" | "high">("");
const passwordRetypeState = ref<null | "match" | "not-match" >(null);
const passwordRetypeState = ref<null | "match" | "not-match">(null);
const submitting = ref(false);
const ToSAgreement = ref(false);
const hCaptchaResponse = ref(null);

View file

@ -76,6 +76,7 @@ onMounted(() => {
src: "/client-assets/tagcanvas.min.js",
}),
)
// biome-ignore lint/suspicious/noAssignInExpressions: assign it intentially
.addEventListener("load", () => (available.value = true));
}
});

View file

@ -153,7 +153,7 @@ const props = withDefaults(
defineProps<{
currentVisibility: NoteVisibility;
currentLocalOnly: boolean;
src?: HTMLElement;
src?: HTMLElement | null;
}>(),
{},
);

View file

@ -48,7 +48,7 @@ const id = os.getUniqueId();
const props = withDefaults(
defineProps<{
modelValue: number;
modelValue: number | null;
disabled?: boolean;
min: number;
max: number;

View file

@ -75,29 +75,29 @@ const headerActions = computed(() =>
icon: `${icon("ph-pencil")}`,
text: i18n.ts.toEdit,
handler: async (): Promise<void> => {
const { canceled, result } = await os.form(clip.value.name, {
const { canceled, result } = await os.form(clip.value!.name, {
name: {
type: "string",
label: i18n.ts.name,
default: clip.value.name,
default: clip.value!.name,
},
description: {
type: "string",
required: false,
multiline: true,
label: i18n.ts.description,
default: clip.value.description,
default: clip.value!.description,
},
isPublic: {
type: "boolean",
label: i18n.ts.public,
default: clip.value.isPublic,
default: clip.value!.isPublic,
},
});
if (canceled) return;
os.apiWithDialog("clips/update", {
clipId: clip.value.id,
clipId: clip.value!.id,
...result,
});
},

View file

@ -2,14 +2,15 @@ import { markRaw, ref } from "vue";
import { isSignedIn } from "./me";
import { Storage } from "./pizzax";
import type { NoteVisibility } from "@/types/note";
import type { entities, ApiTypes } from "firefish-js";
export const postFormActions: {
title: string;
handler: (note: entities.Note) => void | Promise<void>;
handler: (from, update) => void | Promise<void>;
}[] = [];
export const userActions: {
title: string;
handler: (note: entities.Note) => void | Promise<void>;
handler: (user: entities.User) => void | Promise<void>;
}[] = [];
export const noteActions: {
title: string;
@ -19,7 +20,7 @@ export const noteViewInterruptors: {
handler: (note: entities.Note) => Promise<entities.Note>;
}[] = [];
export const notePostInterruptors: {
handler: (note: entities.Note) => Promise<entities.Note>;
handler: (note: ApiTypes.NoteSubmitReq) => Promise<ApiTypes.NoteSubmitReq>;
}[] = [];
const menuOptions = [
@ -466,7 +467,6 @@ 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";
import { entities } from "firefish-js";
export class ColdDeviceStorage {
public static default = {

View file

@ -0,0 +1,12 @@
import type { entities } from "firefish-js";
export type PollType = {
choices: string[];
multiple: boolean;
expiresAt: string | null;
expiredAfter: number | null;
};
export type NoteDraft = entities.Note & {
poll?: PollType;
};

View file

@ -30,5 +30,5 @@
"jsx": "preserve"
},
"compileOnSave": false,
"include": ["./src/**/*.ts", "./src/**/*.vue"]
"include": ["./src/**/*.ts", "./src/**/*.vue", "./@types"]
}

View file

@ -36,7 +36,6 @@ import type {
UserDetailed,
UserGroup,
UserList,
UserLite,
UserSorting,
} from "./entities";
@ -46,11 +45,13 @@ type TODO = Record<string, any> | null;
type NoParams = Record<string, never>;
type ShowUserReq = { username: string; host?: string } | { userId: User["id"] };
type ShowUserReq =
| { username: string; host?: string | null }
| { userId: User["id"] };
type NoteSubmitReq = {
export type NoteSubmitReq = {
editId?: null | Note["id"];
visibility?: "public" | "home" | "followers" | "specified";
visibility?: (typeof consts.noteVisibilities)[number];
visibleUserIds?: User["id"][];
text?: null | string;
cw?: null | string;
@ -62,10 +63,11 @@ type NoteSubmitReq = {
channelId?: null | Channel["id"];
poll?: null | {
choices: string[];
multiple?: boolean;
expiresAt?: null | number;
expiredAfter?: null | number;
multiple: boolean;
expiresAt: string | null;
expiredAfter: number | null;
};
lang?: string;
};
export type Endpoints = {

View file

@ -29,7 +29,7 @@ export type UserLite = {
isIndexable: boolean;
isCat?: boolean;
speakAsCat?: boolean;
driveCapacityOverrideMb: number | null,
driveCapacityOverrideMb: number | null;
};
export type UserDetailed = UserLite & {
@ -238,8 +238,8 @@ export interface RenoteNotification extends BaseNotification {
user: User;
userId: User["id"];
note: Note & {
renote: Note,
renoteId: string,
renote: Note;
renoteId: string;
};
}
export interface QuoteNotification extends BaseNotification {

View file

@ -1,6 +1,7 @@
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";
@ -8,6 +9,7 @@ import type * as TypeUtils from "./type-utils";
export {
Endpoints,
type ApiTypes,
Stream,
Connection as ChannelConnection,
StreamTypes,