fix types of components

This commit is contained in:
Lhcfl 2024-04-12 16:37:32 +08:00
parent ea6ef881c2
commit e4927c9b9b
38 changed files with 319 additions and 159 deletions

View file

@ -20,15 +20,10 @@ import { ref } from "vue";
import { instanceName, version } from "@/config";
import { instance as Instance } from "@/instance";
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
import type { entities } from "firefish-js";
const props = defineProps<{
instance?: {
faviconUrl?: string;
name: string;
themeColor?: string;
softwareName?: string;
softwareVersion?: string;
};
instance?: entities.InstanceLite;
}>();
const ticker = ref<HTMLElement | null>(null);

View file

@ -9,7 +9,7 @@
v-vibrate="5"
:aria-label="accessibleLabel"
class="tkcbzcuz note-container"
:tabindex="!isDeleted ? '-1' : null"
:tabindex="!isDeleted ? '-1' : undefined"
:class="{ renote: isRenote }"
>
<MkNoteSub
@ -112,9 +112,9 @@
:note="appearNote"
:detailed="true"
:detailed-view="detailedView"
:parent-id="appearNote.parentId"
:parent-id="appearNote.id"
@push="(e) => router.push(notePage(e))"
@focusfooter="footerEl.focus()"
@focusfooter="footerEl!.focus()"
@expanded="(e) => setPostExpanded(e)"
></MkSubNoteContent>
<div v-if="translating || translation" class="translation">
@ -312,11 +312,17 @@ import { notePage } from "@/filters/note";
import { deepClone } from "@/scripts/clone";
import { getNoteSummary } from "@/scripts/get-note-summary";
import icon from "@/scripts/icon";
import type { NoteTranslation } from "@/types/note";
const router = useRouter();
type NoteType = entities.Note & {
_featuredId_?: string;
_prId_?: string;
};
const props = defineProps<{
note: entities.Note;
note: NoteType;
pinned?: boolean;
detailedView?: boolean;
collapsedReply?: boolean;
@ -354,18 +360,18 @@ const isRenote =
note.value.fileIds.length === 0 &&
note.value.poll == null;
const el = ref<HTMLElement>();
const el = ref<HTMLElement | null>(null);
const footerEl = ref<HTMLElement>();
const menuButton = ref<HTMLElement>();
const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton> | null>(null);
const renoteTime = ref<HTMLElement>();
const reactButton = ref<HTMLElement>();
const reactButton = ref<HTMLElement | null>(null);
const appearNote = computed(() =>
isRenote ? (note.value.renote as entities.Note) : note.value,
isRenote ? (note.value.renote as NoteType) : note.value,
);
const isMyRenote = isSignedIn && me.id === note.value.userId;
const showContent = ref(false);
const isMyRenote = isSignedIn && me!.id === note.value.userId;
// const showContent = ref(false);
const isDeleted = ref(false);
const muted = ref(
getWordSoftMute(
@ -375,7 +381,7 @@ const muted = ref(
defaultStore.state.mutedLangs,
),
);
const translation = ref(null);
const translation = ref<NoteTranslation | null>(null);
const translating = ref(false);
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
@ -391,7 +397,7 @@ const isForeignLanguage: boolean =
return postLang !== "" && postLang !== targetLang;
})();
async function translate_(noteId, targetLang: string) {
async function translate_(noteId: string, targetLang: string) {
return await os.api("notes/translate", {
noteId,
targetLang,
@ -421,12 +427,13 @@ async function translate() {
const keymap = {
r: () => reply(true),
"e|a|plus": () => react(true),
q: () => renoteButton.value.renote(true),
q: () => renoteButton.value!.renote(true),
"up|k": focusBefore,
"down|j": focusAfter,
esc: blur,
"m|o": () => menu(true),
s: () => showContent.value !== showContent.value,
// FIXME: What's this?
// s: () => showContent.value !== showContent.value,
};
if (appearNote.value.historyId == null) {
@ -437,12 +444,12 @@ if (appearNote.value.historyId == null) {
});
}
function reply(viaKeyboard = false): void {
function reply(_viaKeyboard = false): void {
pleaseLogin();
os.post(
{
reply: appearNote.value,
animation: !viaKeyboard,
// animation: !viaKeyboard,
},
() => {
focus();
@ -450,11 +457,11 @@ function reply(viaKeyboard = false): void {
);
}
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,
@ -467,7 +474,7 @@ function react(viaKeyboard = false): void {
);
}
function undoReact(note): void {
function undoReact(note: NoteType): void {
const oldReaction = note.myReaction;
if (!oldReaction) return;
os.api("notes/reactions/delete", {
@ -481,16 +488,17 @@ const currentClipPage = inject<Ref<entities.Clip> | null>(
);
function onContextmenu(ev: MouseEvent): void {
const isLink = (el: HTMLElement) => {
const isLink = (el: HTMLElement): boolean => {
if (el.tagName === "A") return true;
// The Audio element's context menu is the browser default, such as for selecting playback speed.
if (el.tagName === "AUDIO") return true;
if (el.parentElement) {
return isLink(el.parentElement);
}
return false;
};
if (isLink(ev.target)) return;
if (window.getSelection().toString() !== "") return;
if (isLink(ev.target as HTMLElement)) return;
if (window.getSelection()?.toString() !== "") return;
if (defaultStore.state.useReactionPickerForContextMenu) {
ev.preventDefault();
@ -509,7 +517,7 @@ function onContextmenu(ev: MouseEvent): void {
os.pageWindow(notePage(appearNote.value));
},
},
notePage(appearNote.value) != location.pathname
notePage(appearNote.value) !== location.pathname
? {
icon: `${icon("ph-arrows-out-simple")}`,
text: i18n.ts.showInPage,
@ -589,11 +597,11 @@ function showRenoteMenu(viaKeyboard = false): void {
}
function focus() {
el.value.focus();
el.value!.focus();
}
function blur() {
el.value.blur();
el.value!.blur();
}
function focusBefore() {
@ -605,12 +613,12 @@ function focusAfter() {
}
function scrollIntoView() {
el.value.scrollIntoView();
el.value!.scrollIntoView();
}
function noteClick(e) {
if (
document.getSelection().type === "Range" ||
document.getSelection()?.type === "Range" ||
props.detailedView ||
!expandOnNoteClick
) {

View file

@ -6,7 +6,7 @@
v-hotkey="keymap"
v-size="{ max: [500, 350, 300] }"
class="lxwezrsl _block"
:tabindex="!isDeleted ? '-1' : null"
:tabindex="!isDeleted ? '-1' : undefined"
:class="{ renote: isRenote }"
>
<MkNoteSub
@ -64,7 +64,7 @@
)
}}
</option>
<option v-if="directQuotes?.length > 0" value="quotes">
<option v-if="directQuotes && directQuotes.length > 0" value="quotes">
<!-- <i :class="icon('ph-quotes')"></i> -->
{{
wordWithCount(
@ -102,7 +102,7 @@
:detailed-view="true"
:parent-id="note.id"
/>
<MkLoading v-else-if="tab === 'quotes' && directQuotes.length > 0" />
<MkLoading v-else-if="tab === 'quotes' && directQuotes && directQuotes.length > 0" />
<!-- <MkPagination
v-if="tab === 'renotes'"
@ -225,12 +225,12 @@ if (noteViewInterruptors.length > 0) {
});
}
const el = ref<HTMLElement>();
const el = ref<HTMLElement | null>(null);
const noteEl = ref();
const menuButton = ref<HTMLElement>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const reactButton = ref<HTMLElement>();
const showContent = ref(false);
// const showContent = ref(false);
const isDeleted = ref(false);
const muted = ref(
getWordSoftMute(
@ -248,7 +248,8 @@ const directReplies = ref<null | entities.Note[]>([]);
const directQuotes = ref<null | entities.Note[]>([]);
const clips = ref();
const renotes = ref();
let isScrolling;
const isRenote = ref(note.value.renoteId != null);
let isScrolling: boolean;
const reactionsCount = Object.values(props.note.reactions).reduce(
(x, y) => x + y,
@ -258,10 +259,10 @@ const reactionsCount = Object.values(props.note.reactions).reduce(
const keymap = {
r: () => reply(true),
"e|a|plus": () => react(true),
q: () => renoteButton.value.renote(true),
q: () => renoteButton.value!.renote(true),
esc: blur,
"m|o": () => menu(true),
s: () => showContent.value !== showContent.value,
// s: () => showContent.value !== showContent.value,
};
useNoteCapture({
@ -270,21 +271,21 @@ useNoteCapture({
isDeletedRef: isDeleted,
});
function reply(viaKeyboard = false): void {
function reply(_viaKeyboard = false): void {
pleaseLogin();
os.post({
reply: note.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: note.value.id,
@ -297,13 +298,13 @@ function react(viaKeyboard = false): void {
);
}
function undoReact(note): void {
const oldReaction = note.myReaction;
if (!oldReaction) return;
os.api("notes/reactions/delete", {
noteId: note.id,
});
}
// function undoReact(note): void {
// const oldReaction = note.myReaction;
// if (!oldReaction) return;
// os.api("notes/reactions/delete", {
// noteId: note.id,
// });
// }
function onContextmenu(ev: MouseEvent): void {
const isLink = (el: HTMLElement) => {
@ -312,8 +313,8 @@ function onContextmenu(ev: MouseEvent): void {
return isLink(el.parentElement);
}
};
if (isLink(ev.target)) return;
if (window.getSelection().toString() !== "") return;
if (isLink(ev.target as HTMLElement)) return;
if (window.getSelection()?.toString() !== "") return;
if (defaultStore.state.useReactionPickerForContextMenu) {
ev.preventDefault();
@ -362,12 +363,17 @@ os.api("notes/children", {
limit: 30,
depth: 12,
}).then((res) => {
res = res.reduce((acc, resNote) => {
if (resNote.userId == note.value.userId) {
return [...acc, resNote];
}
return [resNote, ...acc];
}, []);
// biome-ignore lint/style/noParameterAssign: assign it intentially
res = res
.filter((n) => n.userId !== note.value.userId)
.reverse()
.concat(res.filter((n) => n.userId === note.value.userId));
// res = res.reduce((acc: entities.Note[], resNote) => {
// if (resNote.userId === note.value.userId) {
// return [...acc, resNote];
// }
// return [resNote, ...acc];
// }, []);
replies.value = res;
directReplies.value = res
.filter((resNote) => resNote.replyId === note.value.id)
@ -438,7 +444,7 @@ async function onNoteUpdated(
}
switch (type) {
case "replied":
case "replied": {
const { id: createdId } = body;
const replyNote = await os.api("notes/show", {
noteId: createdId,
@ -446,10 +452,10 @@ async function onNoteUpdated(
replies.value.splice(found, 0, replyNote);
if (found === 0) {
directReplies.value.push(replyNote);
directReplies.value!.push(replyNote);
}
break;
}
case "deleted":
if (found === 0) {
isDeleted.value = true;

View file

@ -1,9 +1,9 @@
<template>
<div v-size="{ min: [350, 500] }" class="fefdfafb">
<MkAvatar class="avatar" :user="me" disable-link />
<MkAvatar class="avatar" :user="me!" disable-link />
<div class="main">
<div class="header">
<MkUserName :user="me" />
<MkUserName :user="me!" />
</div>
<div class="body">
<div class="content">

View file

@ -40,7 +40,11 @@
<script lang="ts" setup>
import { ref } from "vue";
import type { PagingOf } from "@/components/MkPagination.vue";
import type {
MkPaginationType,
PagingKeyOf,
PagingOf,
} from "@/components/MkPagination.vue";
import XNote from "@/components/MkNote.vue";
import XList from "@/components/MkDateSeparatedList.vue";
import MkPagination from "@/components/MkPagination.vue";
@ -56,10 +60,14 @@ defineProps<{
disableAutoLoad?: boolean;
}>();
const pagingComponent = ref<InstanceType<typeof MkPagination>>();
const pagingComponent = ref<MkPaginationType<
PagingKeyOf<entities.Note>
> | null>(null);
function scrollTop() {
scroll(tlEl.value, { top: 0, behavior: "smooth" });
if (tlEl.value) {
scroll(tlEl.value, { top: 0, behavior: "smooth" });
}
}
defineExpose({

View file

@ -6,7 +6,7 @@
:with-ok-button="true"
:ok-button-disabled="false"
@ok="ok()"
@close="dialog.close()"
@close="dialog!.close()"
@closed="emit('closed')"
>
<template #header>{{ i18n.ts.notificationSetting }}</template>
@ -68,7 +68,7 @@ const includingTypes = computed(() => props.includingTypes || []);
const dialog = ref<InstanceType<typeof XModalWindow>>();
const typesMap = ref<Record<(typeof notificationTypes)[number], boolean>>({});
const typesMap = ref({} as Record<(typeof notificationTypes)[number], boolean>);
const useGlobalSetting = ref(
(includingTypes.value === null || includingTypes.value.length === 0) &&
props.showGlobalToggle,
@ -89,7 +89,7 @@ function ok() {
});
}
dialog.value.close();
dialog.value!.close();
}
function disableAll() {

View file

@ -19,9 +19,10 @@ import { onMounted, ref } from "vue";
import XNotification from "@/components/MkNotification.vue";
import * as os from "@/os";
import { defaultStore } from "@/store";
import type { entities } from "firefish-js";
defineProps<{
notification: any; // TODO
notification: entities.Notification;
}>();
const emit = defineEmits<{

View file

@ -44,7 +44,9 @@
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, ref } from "vue";
import type { StreamTypes, entities, notificationTypes } from "firefish-js";
import MkPagination from "@/components/MkPagination.vue";
import MkPagination, {
type MkPaginationType,
} from "@/components/MkPagination.vue";
import XNotification from "@/components/MkNotification.vue";
import XList from "@/components/MkDateSeparatedList.vue";
import XNote from "@/components/MkNote.vue";
@ -59,7 +61,7 @@ const props = defineProps<{
const stream = useStream();
const pagingComponent = ref<InstanceType<typeof MkPagination>>();
const pagingComponent = ref<MkPaginationType<"i/notifications"> | null>(null);
const pagination = {
endpoint: "i/notifications" as const,

View file

@ -67,7 +67,7 @@
</template>
<script lang="ts" setup generic="E extends PagingKey">
import type { ComputedRef } from "vue";
import type { ComponentPublicInstance, ComputedRef } from "vue";
import { computed, isRef, onActivated, onDeactivated, ref, watch } from "vue";
import type { Endpoints, TypeUtils } from "firefish-js";
import * as os from "@/os";
@ -81,8 +81,30 @@ import MkButton from "@/components/MkButton.vue";
import { i18n } from "@/i18n";
import { defaultStore } from "@/store";
/**
* ref type of MkPagination<E>
* Due to Vue's incomplete type support for generic components,
* we have to manually maintain this type instead of
* using `InstanceType<typeof MkPagination>`
*/
export type MkPaginationType<
E extends PagingKey,
Item = Endpoints[E]["res"][number],
> = ComponentPublicInstance & {
items: Item[];
queue: Item[];
backed: boolean;
reload: () => Promise<void>;
refresh: () => Promise<void>;
prepend: (item: Item) => Promise<void>;
append: (item: Item) => Promise<void>;
removeItem: (finder: (item: Item) => boolean) => boolean;
updateItem: (id: string, replacer: (old: Item) => Item) => boolean;
};
export type PagingKeyOf<T> = TypeUtils.EndpointsOf<T[]>;
// biome-ignore lint/suspicious/noExplicitAny: Used Intentionally
export type PagingKey = TypeUtils.EndpointsOf<any[]>;
export type PagingKey = PagingKeyOf<any>;
export interface Paging<E extends PagingKey = PagingKey> {
endpoint: E;

View file

@ -2,7 +2,7 @@
<MkModal
ref="modal"
:prefer-type="'dialog'"
@click="modal.close()"
@click="modal!.close()"
@closed="onModalClosed()"
>
<MkPostForm
@ -12,8 +12,8 @@
autofocus
freeze-after-posted
@posted="onPosted"
@cancel="modal.close()"
@esc="modal.close()"
@cancel="modal!.close()"
@esc="modal!.close()"
/>
</MkModal>
</template>

View file

@ -77,7 +77,7 @@ const hasRenotedBefore = ref(false);
if (isSignedIn) {
os.api("notes/renotes", {
noteId: props.note.id,
userId: me.id,
userId: me!.id,
limit: 1,
}).then((res) => {
hasRenotedBefore.value = res.length > 0;
@ -251,6 +251,10 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
os.popupMenu(buttonActions, buttonRef.value, { viaKeyboard });
};
defineExpose({
renote,
});
</script>
<style lang="scss" scoped>

View file

@ -31,7 +31,6 @@
:text="note.cw"
:author="note.user"
:lang="note.lang"
:i="me"
:custom-emojis="note.emojis"
/>
</p>
@ -63,8 +62,8 @@
<div
class="body"
v-bind="{
'aria-hidden': note.cw && !showContent ? 'true' : null,
tabindex: !showContent ? '-1' : null,
'aria-hidden': note.cw && !showContent ? 'true' : undefined,
tabindex: !showContent ? '-1' : undefined,
}"
>
<span v-if="note.deletedAt" style="opacity: 0.5"
@ -103,7 +102,6 @@
v-if="note.text"
:text="note.text"
:author="note.user"
:i="me"
:lang="note.lang"
:custom-emojis="note.emojis"
/>
@ -256,7 +254,7 @@ async function toggleMfm() {
}
function focusFooter(ev) {
if (ev.key == "Tab" && !ev.getModifierState("Shift")) {
if (ev.key === "Tab" && !ev.getModifierState("Shift")) {
emit("focusfooter");
}
}

View file

@ -11,10 +11,10 @@
</div>
</template>
<template #default="{ items: users }">
<template #default="{ items }: { items: entities.UserDetailed[] }">
<div class="efvhhmdq">
<MkUserInfo
v-for="user in users"
v-for="user in items"
:key="user.id"
class="user"
:user="user"
@ -27,16 +27,21 @@
<script lang="ts" setup>
import { ref } from "vue";
import MkUserInfo from "@/components/MkUserInfo.vue";
import type { Paging } from "@/components/MkPagination.vue";
import type {
MkPaginationType,
PagingKeyOf,
PagingOf,
} from "@/components/MkPagination.vue";
import MkPagination from "@/components/MkPagination.vue";
import { i18n } from "@/i18n";
import type { entities } from "firefish-js";
defineProps<{
pagination: Paging;
pagination: PagingOf<entities.UserDetailed>;
noGap?: boolean;
}>();
const pagingComponent = ref<InstanceType<typeof MkPagination>>();
const pagingComponent = ref<MkPaginationType<PagingKeyOf<entities.User>>>();
</script>
<style lang="scss" scoped>

View file

@ -94,9 +94,9 @@ import { defaultStore } from "@/store";
import { i18n } from "@/i18n";
const emit = defineEmits<{
(ev: "ok", selected: entities.UserDetailed): void;
(ev: "cancel"): void;
(ev: "closed"): void;
ok: [selected: entities.UserDetailed];
cancel: [];
closed: [];
}>();
const username = ref("");
@ -114,7 +114,7 @@ const search = () => {
query: username.value,
origin: "local",
limit: 10,
detail: false,
detail: true,
}).then((_users) => {
users.value = _users;
});
@ -127,7 +127,7 @@ const ok = () => {
// 使
let recents = defaultStore.state.recentlyUsedUsers;
recents = recents.filter((x) => x !== selected.value.id);
recents = recents.filter((x) => x !== selected.value!.id);
recents.unshift(selected.value.id);
defaultStore.set("recentlyUsedUsers", recents.splice(0, 16));
};

View file

@ -26,7 +26,7 @@ withDefaults(
text: string;
plain?: boolean;
nowrap?: boolean;
author?: entities.User;
author?: entities.User | null;
customEmojis?: entities.EmojiLite[];
isNote?: boolean;
advancedMfm?: boolean;

View file

@ -30,7 +30,7 @@ export default defineComponent({
default: false,
},
author: {
type: Object as PropType<entities.User>,
type: Object as PropType<entities.User | null>,
default: null,
},
// TODO: This variable is not used in the code and may be removed

View file

@ -13,7 +13,7 @@ export const wsUrl = `${url
export const lang = localStorage.getItem("lang");
export const langs = _LANGS_;
export const locale = JSON.parse(localStorage.getItem("locale") || "en-US");
export const version = _VERSION_;
export const version: string = _VERSION_;
export const instanceName = siteName === "Firefish" ? host : siteName;
export const ui = localStorage.getItem("ui");
export const debug = localStorage.getItem("debug") === "true";

View file

@ -22,7 +22,7 @@ const apiClient = new firefishApi.APIClient({
export const api = ((
endpoint: string,
data: Record<string, any> = {},
data: Record<string, unknown> = {},
token?: string | null | undefined,
useToken = true,
) => {
@ -174,13 +174,14 @@ export function promiseDialog<T>(
}
let popupIdCount = 0;
export const popups = ref<
{
id: number;
component: Component;
props: Record<string, unknown>;
}[]
>([]);
type PopupType = {
id: number;
component: Component;
props: Record<string, unknown>;
events: Record<string, unknown>;
};
export const popups = ref<PopupType[]>([]);
const zIndexes = {
low: 1000000,
@ -922,18 +923,27 @@ export function contextMenu(
});
}
export function post(props: Record<string, any> = {}) {
return new Promise((resolve, reject) => {
export function post(
props: InstanceType<typeof MkPostFormDialog>["$props"] = {},
onClosed?: () => void,
) {
return new Promise<void>((resolve, reject) => {
// NOTE: MkPostFormDialogをdynamic importするとiOSでテキストエリアに自動フォーカスできない
// NOTE: ただ、dynamic importしない場合、MkPostFormDialogインスタンスが使いまわされ、
// Vueが渡されたコンポーネントに内部的に__propsというプロパティを生やす影響で、
// 複数のpost formを開いたときに場合によってはエラーになる
// もちろん複数のpost formを開けること自体Misskeyサイドのバグなのだが
let dispose;
// NOTE: Text area cannot be auto-focused on iOS when dynamically importing MkPostFormDialog
// NOTE: However, if you do not dynamically import, the MkPostFormDialog instance will be reused,
// Due to the effect that Vue internally creates a property called __props on the passed component,
// Sometimes an error occurs when opening multiple post forms
// Of course, opening multiple post forms is itself a bug on Misskey's side.
let dispose: () => void;
popup(MkPostFormDialog, props, {
closed: () => {
resolve();
dispose();
onClosed?.();
},
}).then((res) => {
dispose = res.dispose;

View file

@ -176,6 +176,7 @@
import { computed, onMounted, ref, watch } from "vue";
import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue";
import type { Swiper as SwiperType } from "swiper/types";
import XEmojis from "./about.emojis.vue";
import XFederation from "./about.federation.vue";
import { host, version } from "@/config";
@ -294,19 +295,19 @@ watch(iconSrc, (newValue, oldValue) => {
}
});
let swiperRef = null;
let swiperRef: SwiperType | null = null;
function setSwiperRef(swiper) {
function setSwiperRef(swiper: SwiperType) {
swiperRef = swiper;
syncSlide(tabs.indexOf(tab.value));
}
function onSlideChange() {
tab.value = tabs[swiperRef.activeIndex];
tab.value = tabs[swiperRef!.activeIndex];
}
function syncSlide(index) {
swiperRef.slideTo(index);
function syncSlide(index: number) {
swiperRef!.slideTo(index);
}
</script>

View file

@ -94,14 +94,16 @@
import { computed, ref } from "vue";
import MkSelect from "@/components/form/select.vue";
import MkPagination from "@/components/MkPagination.vue";
import MkPagination, {
type MkPaginationType,
} from "@/components/MkPagination.vue";
import XAbuseReport from "@/components/MkAbuseReport.vue";
import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata";
import icon from "@/scripts/icon";
import type { entities } from "firefish-js";
const reports = ref<InstanceType<typeof MkPagination>>();
const reports = ref<MkPaginationType<typeof pagination.endpoint> | null>(null);
const state = ref("unresolved");
const reporterOrigin = ref<entities.OriginType>("combined");

View file

@ -153,7 +153,9 @@
import { computed, defineAsyncComponent, ref } from "vue";
import MkButton from "@/components/MkButton.vue";
import MkInput from "@/components/form/input.vue";
import MkPagination from "@/components/MkPagination.vue";
import MkPagination, {
type MkPaginationType,
} from "@/components/MkPagination.vue";
import MkSwitch from "@/components/form/switch.vue";
import FormSplit from "@/components/form/split.vue";
import { selectFile, selectFiles } from "@/scripts/select-file";
@ -162,7 +164,8 @@ import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata";
import icon from "@/scripts/icon";
const emojisPaginationComponent = ref<InstanceType<typeof MkPagination>>();
const emojisPaginationComponent =
ref<MkPaginationType<"admin/emoji/list"> | null>(null);
const tab = ref("local");
const query = ref(null);

View file

@ -126,7 +126,9 @@
import { computed, ref } from "vue";
import MkInput from "@/components/form/input.vue";
import MkSelect from "@/components/form/select.vue";
import MkPagination from "@/components/MkPagination.vue";
import MkPagination, {
type MkPaginationType,
} from "@/components/MkPagination.vue";
import * as os from "@/os";
import { lookupUser } from "@/scripts/lookup-user";
import { i18n } from "@/i18n";
@ -134,7 +136,9 @@ import { definePageMetadata } from "@/scripts/page-metadata";
import MkUserCardMini from "@/components/MkUserCardMini.vue";
import icon from "@/scripts/icon";
const paginationComponent = ref<InstanceType<typeof MkPagination>>();
const paginationComponent = ref<MkPaginationType<
typeof pagination.endpoint
> | null>(null);
const sort = ref("+createdAt");
const state = ref("all");

View file

@ -54,6 +54,7 @@
<script lang="ts" setup>
import { computed, ref } from "vue";
import MkPagination from "@/components/MkPagination.vue";
import type { MkPaginationType } from "@/components/MkPagination.vue";
import MkButton from "@/components/MkButton.vue";
import * as os from "@/os";
import { i18n } from "@/i18n";
@ -66,7 +67,7 @@ const pagination = {
limit: 10,
};
const paginationEl = ref<InstanceType<typeof MkPagination>>();
const paginationEl = ref<MkPaginationType<"announcements"> | null>(null);
function read(id: string) {
if (!paginationEl.value) return;
paginationEl.value.updateItem(id, (announcement) => {

View file

@ -37,6 +37,7 @@
<script lang="ts" setup>
import { ref } from "vue";
import MkPagination from "@/components/MkPagination.vue";
import type { MkPaginationType } from "@/components/MkPagination.vue";
import XNote from "@/components/MkNote.vue";
import XList from "@/components/MkDateSeparatedList.vue";
import { i18n } from "@/i18n";
@ -48,7 +49,9 @@ const pagination = {
limit: 10,
};
const pagingComponent = ref<InstanceType<typeof MkPagination>>();
const pagingComponent = ref<MkPaginationType<
typeof pagination.endpoint
> | null>(null);
definePageMetadata({
title: i18n.ts.favorites,

View file

@ -66,14 +66,17 @@
import { computed, ref } from "vue";
import { acct } from "firefish-js";
import MkPagination from "@/components/MkPagination.vue";
import type { MkPaginationType } from "@/components/MkPagination.vue";
import { userPage } from "@/filters/user";
import * as os from "@/os";
// import * as os from "@/os";
import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata";
import { me } from "@/me";
import icon from "@/scripts/icon";
const paginationComponent = ref<InstanceType<typeof MkPagination>>();
const paginationComponent = ref<MkPaginationType<
typeof pagination.endpoint
> | null>(null);
const pagination = {
endpoint: "following/requests/sent" as const,

View file

@ -85,6 +85,7 @@
import { computed, ref } from "vue";
import { acct } from "firefish-js";
import MkPagination from "@/components/MkPagination.vue";
import type { MkPaginationType } from "@/components/MkPagination.vue";
import { userPage } from "@/filters/user";
import * as os from "@/os";
import { i18n } from "@/i18n";
@ -92,7 +93,9 @@ import { definePageMetadata } from "@/scripts/page-metadata";
import { me } from "@/me";
import icon from "@/scripts/icon";
const paginationComponent = ref<InstanceType<typeof MkPagination>>();
const paginationComponent = ref<MkPaginationType<
typeof pagination.endpoint
> | null>(null);
const pagination = {
endpoint: "following/requests/list" as const,
@ -102,13 +105,13 @@ const pagination = {
function accept(user) {
os.api("following/requests/accept", { userId: user.id }).then(() => {
paginationComponent.value.reload();
paginationComponent.value!.reload();
});
}
function reject(user) {
os.api("following/requests/reject", { userId: user.id }).then(() => {
paginationComponent.value.reload();
paginationComponent.value!.reload();
});
}

View file

@ -110,7 +110,7 @@ import { acct } from "firefish-js";
import XMessage from "./messaging-room.message.vue";
import XForm from "./messaging-room.form.vue";
import XList from "@/components/MkDateSeparatedList.vue";
import type { Paging } from "@/components/MkPagination.vue";
import type { MkPaginationType, Paging } from "@/components/MkPagination.vue";
import MkPagination from "@/components/MkPagination.vue";
import {
isBottomVisible,
@ -136,7 +136,9 @@ const stream = useStream();
const rootEl = ref<HTMLDivElement>();
const formEl = ref<InstanceType<typeof XForm>>();
const pagingComponent = ref<InstanceType<typeof MkPagination>>();
const pagingComponent = ref<MkPaginationType<"messaging/messages"> | null>(
null,
);
const fetching = ref(true);
const user = ref<entities.UserDetailed | null>(null);

View file

@ -54,7 +54,7 @@
<script lang="ts" setup>
import { computed, onActivated, onDeactivated, ref } from "vue";
import MkPagination from "@/components/MkPagination.vue";
import MkPagination, { MkPaginationType } from "@/components/MkPagination.vue";
import MkButton from "@/components/MkButton.vue";
import MkInfo from "@/components/MkInfo.vue";
import { i18n } from "@/i18n";
@ -70,7 +70,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
const list = ref<typeof MkPagination | null>(null);
const list = ref<MkPaginationType<typeof pagination.endpoint> | null>(null);
let isCached = false;
let refreshTimer: number | null = null;

View file

@ -40,7 +40,9 @@
<script lang="ts" setup>
import { computed, ref } from "vue";
import MkPagination from "@/components/MkPagination.vue";
import MkPagination, {
type MkPaginationType,
} from "@/components/MkPagination.vue";
import MkInfo from "@/components/MkInfo.vue";
import * as os from "@/os";
import { i18n } from "@/i18n";
@ -52,7 +54,9 @@ const pagination = {
limit: 10,
};
const pagingComponent = ref<InstanceType<typeof MkPagination>>();
const pagingComponent = ref<MkPaginationType<
typeof pagination.endpoint
> | null>(null);
async function create() {
const { canceled, result } = await os.form(i18n.ts.createNewClip, {

View file

@ -44,7 +44,9 @@
<script lang="ts" setup>
import { computed, ref } from "vue";
import MkPagination from "@/components/MkPagination.vue";
import MkPagination, {
type MkPaginationType,
} from "@/components/MkPagination.vue";
import MkButton from "@/components/MkButton.vue";
import MkAvatars from "@/components/MkAvatars.vue";
import MkInfo from "@/components/MkInfo.vue";
@ -53,7 +55,9 @@ import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata";
import icon from "@/scripts/icon";
const pagingComponent = ref<InstanceType<typeof MkPagination>>();
const pagingComponent = ref<MkPaginationType<
typeof pagination.endpoint
> | null>(null);
const pagination = {
endpoint: "users/lists/list" as const,

View file

@ -34,7 +34,9 @@
<script lang="ts" setup>
import { computed, onMounted, ref } from "vue";
import MkPagination from "@/components/MkPagination.vue";
import MkPagination, {
type MkPaginationType,
} from "@/components/MkPagination.vue";
import { api } from "@/os";
import XList from "@/components/MkDateSeparatedList.vue";
import XNote from "@/components/MkNote.vue";
@ -43,7 +45,9 @@ import { definePageMetadata } from "@/scripts/page-metadata";
import icon from "@/scripts/icon";
import type { entities } from "firefish-js";
const pagingComponent = ref<InstanceType<typeof MkPagination>>();
const pagingComponent = ref<MkPaginationType<
typeof pagination.endpoint
> | null>(null);
const props = defineProps<{
noteId: string;

View file

@ -89,8 +89,9 @@ const usersPagination = {
endpoint: "users/search" as const,
limit: 10,
params: computed(() => ({
query: props.query,
origin: "combined",
// FIXME: query is necessary for user search
query: props.query!,
origin: "combined" as const,
})),
};

View file

@ -13,16 +13,17 @@ import { getUserMenu } from "@/scripts/get-user-menu";
import icon from "@/scripts/icon";
import { useRouter } from "@/router";
import { notePage } from "@/filters/note";
import type { NoteTranslation } from "@/types/note";
const router = useRouter();
export function getNoteMenu(props: {
note: entities.Note;
menuButton: Ref<HTMLElement | undefined>;
translation: Ref<any>;
translation: Ref<NoteTranslation | null>;
translating: Ref<boolean>;
isDeleted: Ref<boolean>;
currentClipPage?: Ref<entities.Clip>;
currentClipPage?: Ref<entities.Clip> | null;
}) {
const isRenote =
props.note.renote != null &&

View file

@ -6,7 +6,7 @@ import { isSignedIn, me } from "@/me";
import * as os from "@/os";
export function useNoteCapture(props: {
rootEl: Ref<HTMLElement>;
rootEl: Ref<HTMLElement | null>;
note: Ref<entities.Note>;
isDeletedRef: Ref<boolean>;
}) {

View file

@ -3,11 +3,24 @@ import { isSignedIn } from "./me";
import { Storage } from "./pizzax";
import type { NoteVisibility } from "@/types/note";
export const postFormActions = [];
export const userActions = [];
export const noteActions = [];
export const noteViewInterruptors = [];
export const notePostInterruptors = [];
export const postFormActions: {
title: string;
handler: (note: entities.Note) => void | Promise<void>;
}[] = [];
export const userActions: {
title: string;
handler: (note: entities.Note) => void | Promise<void>;
}[] = [];
export const noteActions: {
title: string;
handler: (note: entities.Note) => void | Promise<void>;
}[] = [];
export const noteViewInterruptors: {
handler: (note: entities.Note) => Promise<entities.Note>;
}[] = [];
export const notePostInterruptors: {
handler: (note: entities.Note) => Promise<entities.Note>;
}[] = [];
const menuOptions = [
"notifications",
@ -453,6 +466,7 @@ 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

@ -1,3 +1,8 @@
import type { noteVisibilities } from "firefish-js";
export type NoteVisibility = (typeof noteVisibilities)[number] | "private";
export type NoteTranslation = {
sourceLang: string;
text: string;
};

View file

@ -36,6 +36,7 @@ import type {
UserDetailed,
UserGroup,
UserList,
UserLite,
UserSorting,
} from "./entities";
@ -686,7 +687,14 @@ export type Endpoints = {
res: Note[];
};
"notes/clips": { req: TODO; res: TODO };
"notes/conversation": { req: TODO; res: TODO };
"notes/conversation": {
req: {
noteId: string;
limit?: number;
offset?: number;
};
res: Note[];
};
"notes/create": {
req: NoteSubmitReq;
res: { createdNote: Note };
@ -789,7 +797,24 @@ export type Endpoints = {
res: Note[];
};
"notes/search-by-tag": { req: TODO; res: TODO };
"notes/search": { req: TODO; res: TODO };
"notes/search": {
req: {
query: string;
sinceId?: string;
untilId?: string;
sinceDate?: number;
untilDate?: number;
limit?: number;
offset?: number;
host?: string;
userId?: string;
withFiles?: boolean;
searchCwAndAlt?: boolean;
channelId?: string;
order?: "chronological" | "relevancy";
};
res: Note[];
};
"notes/show": { req: { noteId: Note["id"] }; res: Note };
"notes/state": { req: TODO; res: TODO };
"notes/timeline": {
@ -802,6 +827,16 @@ export type Endpoints = {
};
res: Note[];
};
"notes/translate": {
req: {
noteId: string;
targetLang: string;
};
res: {
sourceLang: string;
text: string;
};
};
"notes/unrenote": { req: { noteId: Note["id"] }; res: null };
"notes/user-list-timeline": {
req: {
@ -972,7 +1007,16 @@ export type Endpoints = {
"users/relation": { req: TODO; res: TODO };
"users/report-abuse": { req: TODO; res: TODO };
"users/search-by-username-and-host": { req: TODO; res: TODO };
"users/search": { req: TODO; res: TODO };
"users/search": {
req: {
query: string;
offset?: number;
limit?: number;
origin?: "local" | "remote" | "combined";
detail?: true; // FIXME: when false, returns UserLite
};
res: UserDetailed[];
};
"users/show": {
req: ShowUserReq | { userIds: User["id"][] };
res: {

View file

@ -19,14 +19,7 @@ export type UserLite = {
alsoKnownAs: string[];
movedToUri: any;
emojis: EmojiLite[];
instance?: {
name: Instance["name"];
softwareName: Instance["softwareName"];
softwareVersion: Instance["softwareVersion"];
iconUrl: Instance["iconUrl"];
faviconUrl: Instance["faviconUrl"];
themeColor: Instance["themeColor"];
};
instance?: InstanceLite;
};
export type UserDetailed = UserLite & {
@ -556,6 +549,15 @@ export type Blocking = {
blockee: UserDetailed;
};
export type InstanceLite = {
name: Instance["name"];
softwareName: Instance["softwareName"];
softwareVersion: Instance["softwareVersion"];
iconUrl: Instance["iconUrl"];
faviconUrl: Instance["faviconUrl"];
themeColor: Instance["themeColor"];
};
export type Instance = {
id: ID;
caughtAt: DateString;