Merge branch 'refactor/lists' into 'develop'
refactor: Fix types of components Co-authored-by: Lhcfl <Lhcfl@outlook.com> See merge request firefish/firefish!10730
This commit is contained in:
commit
cb7e9ab449
45 changed files with 608 additions and 420 deletions
24
biome.json
24
biome.json
|
@ -1,12 +1,30 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.0.0/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/1.6.4/schema.json",
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
"recommended": true,
|
||||
"style": {
|
||||
"noUselessElse": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"include": ["*.vue"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"useImportType": "warn",
|
||||
"useShorthandFunctionType": "warn",
|
||||
"useTemplate": "warn",
|
||||
"noNonNullAssertion": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"caughtErrorsIgnorePattern": "^_",
|
||||
"destructuredArrayIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
],
|
||||
"vue/no-setup-props-destructure": "off"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"build:debug": "pnpm run build",
|
||||
"lint": "pnpm biome check **/*.ts --apply ; pnpm run lint:vue",
|
||||
"lint:vue": "pnpm eslint src --fix '**/*.vue' --cache ; pnpm run format",
|
||||
"types:check": "pnpm vue-tsc --noEmit",
|
||||
"format": "pnpm biome format * --write"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -86,6 +87,7 @@
|
|||
"vue": "3.4.21",
|
||||
"vue-draggable-plus": "^0.3.5",
|
||||
"vue-plyr": "^7.0.0",
|
||||
"vue-prism-editor": "2.0.0-alpha.2"
|
||||
"vue-prism-editor": "2.0.0-alpha.2",
|
||||
"vue-tsc": "2.0.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,13 +24,14 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import MkChannelPreview from "@/components/MkChannelPreview.vue";
|
||||
import type { Paging } from "@/components/MkPagination.vue";
|
||||
import type { PagingOf } from "@/components/MkPagination.vue";
|
||||
import MkPagination from "@/components/MkPagination.vue";
|
||||
import { i18n } from "@/i18n";
|
||||
import type { entities } from "firefish-js";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
pagination: Paging;
|
||||
pagination: PagingOf<entities.Channel>;
|
||||
noGap?: boolean;
|
||||
extractor?: (item: any) => any;
|
||||
}>(),
|
||||
|
|
|
@ -1,130 +1,83 @@
|
|||
<script lang="ts">
|
||||
import type { PropType } from "vue";
|
||||
import { TransitionGroup, defineComponent, h } from "vue";
|
||||
<template>
|
||||
<component
|
||||
:is="defaultStore.state.animation? TransitionGroup : 'div'"
|
||||
tag="div"
|
||||
class="sqadhkmv"
|
||||
name="list"
|
||||
:class="{ noGap }"
|
||||
:data-direction = "props.direction"
|
||||
:data-reversed = "props.reversed ? 'true' : 'false'"
|
||||
>
|
||||
<template v-for="(item, index) in items" :key="item.id">
|
||||
<slot :item="item"> </slot>
|
||||
<div
|
||||
v-if="index !== items.length - 1 &&
|
||||
new Date(item.createdAt).getDate() !==
|
||||
new Date(items[index + 1].createdAt).getDate()"
|
||||
class="separator"
|
||||
>
|
||||
<p class="date">
|
||||
<span>
|
||||
<i class="icon" :class="icon('ph-caret-up')"></i>
|
||||
{{ getDateText(item.createdAt) }}
|
||||
</span>
|
||||
<span>
|
||||
{{ getDateText(items[index + 1].createdAt) }}
|
||||
<i class="icon" :class="icon('ph-caret-down')"></i>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- class="a" means advertise -->
|
||||
<MkAd
|
||||
v-else-if="ad && item._shouldInsertAd_"
|
||||
class="a"
|
||||
:prefer="['inline', 'inline-big']"
|
||||
/>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup generic="T extends Item">
|
||||
import { TransitionGroup } from "vue";
|
||||
import MkAd from "@/components/global/MkAd.vue";
|
||||
import { i18n } from "@/i18n";
|
||||
import { defaultStore } from "@/store";
|
||||
import icon from "@/scripts/icon";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
items: {
|
||||
type: Array as PropType<
|
||||
{ id: string; createdAt: string; _shouldInsertAd_?: boolean }[]
|
||||
>,
|
||||
required: true,
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "down",
|
||||
},
|
||||
reversed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
noGap: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
ad: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
export interface Item {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
_shouldInsertAd_?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
items: T[];
|
||||
direction?: string;
|
||||
reversed?: boolean;
|
||||
noGap?: boolean;
|
||||
ad?: boolean;
|
||||
}>(),
|
||||
{
|
||||
direction: "down",
|
||||
reversed: false,
|
||||
noGap: false,
|
||||
ad: false,
|
||||
},
|
||||
);
|
||||
|
||||
setup(props, { slots, expose }) {
|
||||
function getDateText(time: string) {
|
||||
const date = new Date(time).getDate();
|
||||
const month = new Date(time).getMonth() + 1;
|
||||
return i18n.t("monthAndDay", {
|
||||
month: month.toString(),
|
||||
day: date.toString(),
|
||||
});
|
||||
}
|
||||
const slots = defineSlots<{
|
||||
default(props: { item: T }): unknown;
|
||||
}>();
|
||||
|
||||
if (props.items.length === 0) return;
|
||||
|
||||
const renderChildren = () =>
|
||||
props.items.map((item, i) => {
|
||||
if (!slots || !slots.default) return;
|
||||
|
||||
const el = slots.default({
|
||||
item,
|
||||
})[0];
|
||||
if (el.key == null && item.id) el.key = item.id;
|
||||
|
||||
if (
|
||||
i !== props.items.length - 1 &&
|
||||
new Date(item.createdAt).getDate() !==
|
||||
new Date(props.items[i + 1].createdAt).getDate()
|
||||
) {
|
||||
const separator = h(
|
||||
"div",
|
||||
{
|
||||
class: "separator",
|
||||
key: item.id + ":separator",
|
||||
},
|
||||
h(
|
||||
"p",
|
||||
{
|
||||
class: "date",
|
||||
},
|
||||
[
|
||||
h("span", [
|
||||
h("i", {
|
||||
class: `${icon("ph-caret-up")} icon`,
|
||||
}),
|
||||
getDateText(item.createdAt),
|
||||
]),
|
||||
h("span", [
|
||||
getDateText(props.items[i + 1].createdAt),
|
||||
h("i", {
|
||||
class: `${icon("ph-caret-down")} icon`,
|
||||
}),
|
||||
]),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return [el, separator];
|
||||
} else {
|
||||
if (props.ad && item._shouldInsertAd_) {
|
||||
return [
|
||||
h(MkAd, {
|
||||
class: "a", // advertiseの意(ブロッカー対策)
|
||||
key: item.id + ":ad",
|
||||
prefer: ["inline", "inline-big"],
|
||||
}),
|
||||
el,
|
||||
];
|
||||
} else {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () =>
|
||||
h(
|
||||
defaultStore.state.animation ? TransitionGroup : "div",
|
||||
defaultStore.state.animation
|
||||
? {
|
||||
class: "sqadhkmv" + (props.noGap ? " noGap" : ""),
|
||||
name: "list",
|
||||
tag: "div",
|
||||
"data-direction": props.direction,
|
||||
"data-reversed": props.reversed ? "true" : "false",
|
||||
}
|
||||
: {
|
||||
class: "sqadhkmv" + (props.noGap ? " noGap" : ""),
|
||||
},
|
||||
{ default: renderChildren },
|
||||
);
|
||||
},
|
||||
});
|
||||
function getDateText(time: string) {
|
||||
const date = new Date(time).getDate();
|
||||
const month = new Date(time).getMonth() + 1;
|
||||
return i18n.t("monthAndDay", {
|
||||
month: month.toString(),
|
||||
day: date.toString(),
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<MkPagination
|
||||
v-slot="{ items }"
|
||||
v-slot="{ items }: { items: entities.DriveFile[]}"
|
||||
:pagination="pagination"
|
||||
class="urempief"
|
||||
:class="{ grid: viewMode === 'grid' }"
|
||||
|
@ -53,13 +53,15 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { acct } from "firefish-js";
|
||||
import type { entities } from "firefish-js";
|
||||
import MkPagination from "@/components/MkPagination.vue";
|
||||
import type { PagingOf } from "@/components/MkPagination.vue";
|
||||
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
|
||||
import bytes from "@/filters/bytes";
|
||||
import { i18n } from "@/i18n";
|
||||
|
||||
defineProps<{
|
||||
pagination: any;
|
||||
pagination: PagingOf<entities.DriveFile>;
|
||||
viewMode: "grid" | "list";
|
||||
}>();
|
||||
</script>
|
||||
|
|
|
@ -40,17 +40,18 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import type { Paging } from "@/components/MkPagination.vue";
|
||||
import type { PagingOf } from "@/components/MkPagination.vue";
|
||||
import XNote from "@/components/MkNote.vue";
|
||||
import XList from "@/components/MkDateSeparatedList.vue";
|
||||
import MkPagination from "@/components/MkPagination.vue";
|
||||
import { i18n } from "@/i18n";
|
||||
import { scroll } from "@/scripts/scroll";
|
||||
import type { entities } from "firefish-js";
|
||||
|
||||
const tlEl = ref<HTMLElement>();
|
||||
|
||||
defineProps<{
|
||||
pagination: Paging;
|
||||
pagination: PagingOf<entities.Note>;
|
||||
noGap?: boolean;
|
||||
disableAutoLoad?: boolean;
|
||||
}>();
|
||||
|
|
|
@ -19,12 +19,8 @@
|
|||
:no-gap="true"
|
||||
>
|
||||
<XNote
|
||||
v-if="
|
||||
['reply', 'quote', 'mention'].includes(
|
||||
notification.type,
|
||||
)
|
||||
"
|
||||
:key="notification.id"
|
||||
v-if="isNoteNotification(notification)"
|
||||
:key="'nn-' + notification.id"
|
||||
:note="notification.note"
|
||||
:collapsed-reply="
|
||||
notification.type === 'reply' ||
|
||||
|
@ -34,7 +30,7 @@
|
|||
/>
|
||||
<XNotification
|
||||
v-else
|
||||
:key="notification.id"
|
||||
:key="'n-' + notification.id"
|
||||
:notification="notification"
|
||||
:with-time="true"
|
||||
:full="true"
|
||||
|
@ -47,8 +43,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
import type { notificationTypes } from "firefish-js";
|
||||
import type { Paging } from "@/components/MkPagination.vue";
|
||||
import type { StreamTypes, entities, notificationTypes } from "firefish-js";
|
||||
import MkPagination from "@/components/MkPagination.vue";
|
||||
import XNotification from "@/components/MkNotification.vue";
|
||||
import XList from "@/components/MkDateSeparatedList.vue";
|
||||
|
@ -66,20 +61,29 @@ const stream = useStream();
|
|||
|
||||
const pagingComponent = ref<InstanceType<typeof MkPagination>>();
|
||||
|
||||
const pagination: Paging = {
|
||||
const pagination = {
|
||||
endpoint: "i/notifications" as const,
|
||||
limit: 10,
|
||||
params: computed(() => ({
|
||||
includeTypes: props.includeTypes ?? undefined,
|
||||
excludeTypes: props.includeTypes ? undefined : me.mutingNotificationTypes,
|
||||
excludeTypes: props.includeTypes ? undefined : me?.mutingNotificationTypes,
|
||||
unreadOnly: props.unreadOnly,
|
||||
})),
|
||||
};
|
||||
|
||||
const onNotification = (notification) => {
|
||||
function isNoteNotification(
|
||||
n: entities.Notification,
|
||||
): n is
|
||||
| entities.ReplyNotification
|
||||
| entities.QuoteNotification
|
||||
| entities.MentionNotification {
|
||||
return n.type === "reply" || n.type === "quote" || n.type === "mention";
|
||||
}
|
||||
|
||||
const onNotification = (notification: entities.Notification) => {
|
||||
const isMuted = props.includeTypes
|
||||
? !props.includeTypes.includes(notification.type)
|
||||
: me.mutingNotificationTypes.includes(notification.type);
|
||||
: me?.mutingNotificationTypes.includes(notification.type);
|
||||
if (isMuted || document.visibilityState === "visible") {
|
||||
stream.send("readNotification", {
|
||||
id: notification.id,
|
||||
|
@ -94,7 +98,7 @@ const onNotification = (notification) => {
|
|||
}
|
||||
};
|
||||
|
||||
let connection;
|
||||
let connection: StreamTypes.ChannelOf<"main"> | undefined;
|
||||
|
||||
onMounted(() => {
|
||||
connection = stream.useChannel("main");
|
||||
|
|
|
@ -66,10 +66,10 @@
|
|||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
<script lang="ts" setup generic="E extends PagingKey">
|
||||
import type { ComputedRef } from "vue";
|
||||
import { computed, isRef, onActivated, onDeactivated, ref, watch } from "vue";
|
||||
import type { Endpoints } from "firefish-js";
|
||||
import type { Endpoints, TypeUtils } from "firefish-js";
|
||||
import * as os from "@/os";
|
||||
import {
|
||||
getScrollContainer,
|
||||
|
@ -81,7 +81,10 @@ import MkButton from "@/components/MkButton.vue";
|
|||
import { i18n } from "@/i18n";
|
||||
import { defaultStore } from "@/store";
|
||||
|
||||
export interface Paging<E extends keyof Endpoints = keyof Endpoints> {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Used Intentionally
|
||||
export type PagingKey = TypeUtils.EndpointsOf<any[]>;
|
||||
|
||||
export interface Paging<E extends PagingKey = PagingKey> {
|
||||
endpoint: E;
|
||||
limit: number;
|
||||
params?: Endpoints[E]["req"] | ComputedRef<Endpoints[E]["req"]>;
|
||||
|
@ -100,11 +103,13 @@ export interface Paging<E extends keyof Endpoints = keyof Endpoints> {
|
|||
offsetMode?: boolean;
|
||||
}
|
||||
|
||||
export type PagingOf<T> = Paging<TypeUtils.EndpointsOf<T[]>>;
|
||||
|
||||
const SECOND_FETCH_LIMIT = 30;
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
pagination: Paging;
|
||||
pagination: Paging<E>;
|
||||
disableAutoLoad?: boolean;
|
||||
displayLimit?: number;
|
||||
}>(),
|
||||
|
@ -113,14 +118,18 @@ const props = withDefaults(
|
|||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: "queue", count: number): void;
|
||||
(ev: "status", error: boolean): void;
|
||||
const slots = defineSlots<{
|
||||
default(props: { items: Item[] }): unknown;
|
||||
empty(props: Record<string, never>): never;
|
||||
}>();
|
||||
|
||||
type Item = Endpoints[typeof props.pagination.endpoint]["res"] & {
|
||||
id: string;
|
||||
};
|
||||
const emit = defineEmits<{
|
||||
(ev: "queue", count: number): void;
|
||||
(ev: "status", hasError: boolean): void;
|
||||
}>();
|
||||
|
||||
type Param = Endpoints[E]["req"] | Record<string, never>;
|
||||
type Item = Endpoints[E]["res"][number];
|
||||
|
||||
const rootEl = ref<HTMLElement>();
|
||||
const items = ref<Item[]>([]);
|
||||
|
@ -137,8 +146,9 @@ const error = ref(false);
|
|||
const init = async (): Promise<void> => {
|
||||
queue.value = [];
|
||||
fetching.value = true;
|
||||
|
||||
const params = props.pagination.params
|
||||
? isRef(props.pagination.params)
|
||||
? isRef<Param>(props.pagination.params)
|
||||
? props.pagination.params.value
|
||||
: props.pagination.params
|
||||
: {};
|
||||
|
@ -150,7 +160,7 @@ const init = async (): Promise<void> => {
|
|||
: (props.pagination.limit || 10) + 1,
|
||||
})
|
||||
.then(
|
||||
(res) => {
|
||||
(res: Item[]) => {
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
const item = res[i];
|
||||
if (props.pagination.reversed) {
|
||||
|
@ -174,7 +184,7 @@ const init = async (): Promise<void> => {
|
|||
error.value = false;
|
||||
fetching.value = false;
|
||||
},
|
||||
(err) => {
|
||||
(_err) => {
|
||||
error.value = true;
|
||||
fetching.value = false;
|
||||
},
|
||||
|
@ -188,7 +198,7 @@ const reload = (): Promise<void> => {
|
|||
|
||||
const refresh = async (): Promise<void> => {
|
||||
const params = props.pagination.params
|
||||
? isRef(props.pagination.params)
|
||||
? isRef<Param>(props.pagination.params)
|
||||
? props.pagination.params.value
|
||||
: props.pagination.params
|
||||
: {};
|
||||
|
@ -199,7 +209,7 @@ const refresh = async (): Promise<void> => {
|
|||
offset: 0,
|
||||
})
|
||||
.then(
|
||||
(res) => {
|
||||
(res: Item[]) => {
|
||||
const ids = items.value.reduce(
|
||||
(a, b) => {
|
||||
a[b.id] = true;
|
||||
|
@ -210,7 +220,7 @@ const refresh = async (): Promise<void> => {
|
|||
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
const item = res[i];
|
||||
if (!updateItem(item.id, (old) => item)) {
|
||||
if (!updateItem(item.id, (_old) => item)) {
|
||||
append(item);
|
||||
}
|
||||
delete ids[item.id];
|
||||
|
@ -220,7 +230,7 @@ const refresh = async (): Promise<void> => {
|
|||
removeItem((i) => i.id === id);
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
(_err) => {
|
||||
error.value = true;
|
||||
fetching.value = false;
|
||||
},
|
||||
|
@ -238,7 +248,7 @@ const fetchMore = async (): Promise<void> => {
|
|||
moreFetching.value = true;
|
||||
backed.value = true;
|
||||
const params = props.pagination.params
|
||||
? isRef(props.pagination.params)
|
||||
? isRef<Param>(props.pagination.params)
|
||||
? props.pagination.params.value
|
||||
: props.pagination.params
|
||||
: {};
|
||||
|
@ -259,7 +269,7 @@ const fetchMore = async (): Promise<void> => {
|
|||
}),
|
||||
})
|
||||
.then(
|
||||
(res) => {
|
||||
(res: Item[]) => {
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
const item = res[i];
|
||||
if (props.pagination.reversed) {
|
||||
|
@ -283,7 +293,7 @@ const fetchMore = async (): Promise<void> => {
|
|||
offset.value += res.length;
|
||||
moreFetching.value = false;
|
||||
},
|
||||
(err) => {
|
||||
(_err) => {
|
||||
moreFetching.value = false;
|
||||
},
|
||||
);
|
||||
|
@ -299,7 +309,7 @@ const fetchMoreAhead = async (): Promise<void> => {
|
|||
return;
|
||||
moreFetching.value = true;
|
||||
const params = props.pagination.params
|
||||
? isRef(props.pagination.params)
|
||||
? isRef<Param>(props.pagination.params)
|
||||
? props.pagination.params.value
|
||||
: props.pagination.params
|
||||
: {};
|
||||
|
@ -320,7 +330,7 @@ const fetchMoreAhead = async (): Promise<void> => {
|
|||
}),
|
||||
})
|
||||
.then(
|
||||
(res) => {
|
||||
(res: Item[]) => {
|
||||
if (res.length > SECOND_FETCH_LIMIT) {
|
||||
res.pop();
|
||||
items.value = props.pagination.reversed
|
||||
|
@ -336,7 +346,7 @@ const fetchMoreAhead = async (): Promise<void> => {
|
|||
offset.value += res.length;
|
||||
moreFetching.value = false;
|
||||
},
|
||||
(err) => {
|
||||
(_err) => {
|
||||
moreFetching.value = false;
|
||||
},
|
||||
);
|
||||
|
@ -428,7 +438,7 @@ const updateItem = (id: Item["id"], replacer: (old: Item) => Item): boolean => {
|
|||
return true;
|
||||
};
|
||||
|
||||
if (props.pagination.params && isRef(props.pagination.params)) {
|
||||
if (props.pagination.params && isRef<Param>(props.pagination.params)) {
|
||||
watch(props.pagination.params, init, { deep: true });
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onUnmounted, provide, ref } from "vue";
|
||||
import type { Endpoints } from "firefish-js";
|
||||
import type { entities, StreamTypes } from "firefish-js";
|
||||
import MkPullToRefresh from "@/components/MkPullToRefresh.vue";
|
||||
import XNotes from "@/components/MkNotes.vue";
|
||||
import MkInfo from "@/components/MkInfo.vue";
|
||||
|
@ -54,10 +54,23 @@ import { isSignedIn, me } from "@/me";
|
|||
import { i18n } from "@/i18n";
|
||||
import { defaultStore } from "@/store";
|
||||
import icon from "@/scripts/icon";
|
||||
import type { Paging } from "@/components/MkPagination.vue";
|
||||
import type { EndpointsOf } from "@/components/MkPagination.vue";
|
||||
|
||||
export type TimelineSource =
|
||||
| "antenna"
|
||||
| "home"
|
||||
| "local"
|
||||
| "recommended"
|
||||
| "social"
|
||||
| "global"
|
||||
| "mentions"
|
||||
| "directs"
|
||||
| "list"
|
||||
| "channel"
|
||||
| "file";
|
||||
|
||||
const props = defineProps<{
|
||||
src: string;
|
||||
src: TimelineSource;
|
||||
list?: string;
|
||||
antenna?: string;
|
||||
channel?: string;
|
||||
|
@ -73,7 +86,7 @@ const emit = defineEmits<{
|
|||
const tlComponent = ref<InstanceType<typeof XNotes>>();
|
||||
const pullToRefreshComponent = ref<InstanceType<typeof MkPullToRefresh>>();
|
||||
|
||||
let endpoint = ""; // keyof Endpoints
|
||||
let endpoint: EndpointsOf<entities.Note[]>; // keyof Endpoints
|
||||
let query: {
|
||||
antennaId?: string | undefined;
|
||||
withReplies?: boolean;
|
||||
|
@ -81,14 +94,19 @@ let query: {
|
|||
listId?: string | undefined;
|
||||
channelId?: string | undefined;
|
||||
fileId?: string | undefined;
|
||||
};
|
||||
let connection: {
|
||||
on: (
|
||||
arg0: string,
|
||||
arg1: { (note: any): void; (note: any): void; (note: any): void },
|
||||
) => void;
|
||||
dispose: () => void;
|
||||
};
|
||||
} = {};
|
||||
|
||||
// FIXME: The type defination is wrong here, need fix
|
||||
let connection:
|
||||
| StreamTypes.ChannelOf<"antenna">
|
||||
| StreamTypes.ChannelOf<"homeTimeline">
|
||||
| StreamTypes.ChannelOf<"recommendedTimeline">
|
||||
| StreamTypes.ChannelOf<"hybridTimeline">
|
||||
| StreamTypes.ChannelOf<"globalTimeline">
|
||||
| StreamTypes.ChannelOf<"main">
|
||||
| StreamTypes.ChannelOf<"userList">
|
||||
| StreamTypes.ChannelOf<"channel">;
|
||||
|
||||
let connection2: { dispose: () => void } | null;
|
||||
|
||||
let tlHint: string;
|
||||
|
@ -96,14 +114,14 @@ let tlHintClosed: boolean;
|
|||
let tlNotesCount = 0;
|
||||
const queue = ref(0);
|
||||
|
||||
const prepend = (note) => {
|
||||
const prepend = (note: entities.Note) => {
|
||||
tlNotesCount++;
|
||||
tlComponent.value?.pagingComponent?.prepend(note);
|
||||
|
||||
emit("note");
|
||||
|
||||
if (props.sound) {
|
||||
sound.play(isSignedIn && note.userId === me.id ? "noteMy" : "note");
|
||||
sound.play(isSignedIn && note.userId === me?.id ? "noteMy" : "note");
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -169,6 +187,8 @@ if (props.src === "antenna") {
|
|||
query = {
|
||||
fileId: props.fileId,
|
||||
};
|
||||
} else {
|
||||
throw "NoEndpointError";
|
||||
}
|
||||
|
||||
const stream = useStream();
|
||||
|
@ -194,8 +214,9 @@ function connectChannel() {
|
|||
}
|
||||
|
||||
if (props.src === "antenna") {
|
||||
if (!props.antenna) throw "NoAntennaProvided";
|
||||
connection = stream.useChannel("antenna", {
|
||||
antennaId: props.antenna!,
|
||||
antennaId: props.antenna,
|
||||
});
|
||||
} else if (props.src === "home") {
|
||||
connection = stream.useChannel("homeTimeline", {
|
||||
|
@ -272,8 +293,8 @@ function reloadTimeline() {
|
|||
});
|
||||
}
|
||||
|
||||
const pagination: Paging = {
|
||||
endpoint: endpoint as keyof Endpoints,
|
||||
const pagination = {
|
||||
endpoint,
|
||||
limit: 10,
|
||||
params: query,
|
||||
};
|
||||
|
|
|
@ -36,7 +36,7 @@ const props = defineProps<{
|
|||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: "update:modelValue", v: boolean): void;
|
||||
"update:modelValue": [v: boolean];
|
||||
}>();
|
||||
|
||||
const button = ref<HTMLElement>();
|
||||
|
@ -46,9 +46,9 @@ const toggle = () => {
|
|||
emit("update:modelValue", !checked.value);
|
||||
|
||||
if (!checked.value) {
|
||||
const rect = button.value.getBoundingClientRect();
|
||||
const x = rect.left + button.value.offsetWidth / 2;
|
||||
const y = rect.top + button.value.offsetHeight / 2;
|
||||
const rect = button.value!.getBoundingClientRect();
|
||||
const x = rect.left + button.value!.offsetWidth / 2;
|
||||
const y = rect.top + button.value!.offsetHeight / 2;
|
||||
os.popup(Ripple, { x, y, particle: false }, {}, "end");
|
||||
}
|
||||
};
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
@input="onInput"
|
||||
/>
|
||||
<datalist v-if="datalist" :id="id">
|
||||
<option v-for="data in datalist" :value="data" />
|
||||
<option v-for="data in datalist" :key="data" :value="data" />
|
||||
</datalist>
|
||||
<div ref="suffixEl" class="suffix">
|
||||
<slot name="suffix"></slot>
|
||||
|
@ -47,7 +47,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, ref, toRefs, watch } from "vue";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { debounce as Debounce } from "throttle-debounce";
|
||||
import MkButton from "@/components/MkButton.vue";
|
||||
import { useInterval } from "@/scripts/use-interval";
|
||||
import { i18n } from "@/i18n";
|
||||
|
@ -72,7 +72,7 @@ const props = defineProps<{
|
|||
autofocus?: boolean;
|
||||
autocomplete?: string;
|
||||
spellcheck?: boolean;
|
||||
step?: any;
|
||||
step?: number | string;
|
||||
datalist?: string[];
|
||||
inline?: boolean;
|
||||
debounce?: boolean;
|
||||
|
@ -82,10 +82,10 @@ const props = defineProps<{
|
|||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: "change", _ev: KeyboardEvent): void;
|
||||
(ev: "change", _ev: Event): void;
|
||||
(ev: "keydown", _ev: KeyboardEvent): void;
|
||||
(ev: "enter"): void;
|
||||
(ev: "update:modelValue", value: string | number): void;
|
||||
(ev: "update:modelValue", value: string | number | null): void;
|
||||
}>();
|
||||
|
||||
const { modelValue, type, autofocus } = toRefs(props);
|
||||
|
@ -94,14 +94,15 @@ const id = Math.random().toString(); // TODO: uuid?
|
|||
const focused = ref(false);
|
||||
const changed = ref(false);
|
||||
const invalid = ref(false);
|
||||
const inputEl = ref<HTMLElement>();
|
||||
const inputEl = ref<HTMLInputElement>();
|
||||
const prefixEl = ref<HTMLElement>();
|
||||
const suffixEl = ref<HTMLElement>();
|
||||
const height = props.small ? 36 : props.large ? 40 : 38;
|
||||
|
||||
const focus = () => inputEl.value.focus();
|
||||
const selectRange = (start, end) => inputEl.value.setSelectionRange(start, end);
|
||||
const onInput = (ev: KeyboardEvent) => {
|
||||
const focus = () => inputEl.value!.focus();
|
||||
const selectRange = (start, end) =>
|
||||
inputEl.value!.setSelectionRange(start, end);
|
||||
const onInput = (ev: Event) => {
|
||||
changed.value = true;
|
||||
emit("change", ev);
|
||||
};
|
||||
|
@ -116,13 +117,13 @@ const onKeydown = (ev: KeyboardEvent) => {
|
|||
const updated = () => {
|
||||
changed.value = false;
|
||||
if (type.value === "number") {
|
||||
emit("update:modelValue", parseFloat(v.value));
|
||||
emit("update:modelValue", Number.parseFloat(v.value as string));
|
||||
} else {
|
||||
emit("update:modelValue", v.value);
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedUpdated = debounce(1000, updated);
|
||||
const debouncedUpdated = Debounce(1000, updated);
|
||||
|
||||
watch(modelValue, (newValue) => {
|
||||
v.value = newValue;
|
||||
|
@ -137,7 +138,7 @@ watch(v, (_) => {
|
|||
}
|
||||
}
|
||||
|
||||
invalid.value = inputEl.value.validity.badInput;
|
||||
invalid.value = inputEl.value!.validity.badInput;
|
||||
});
|
||||
|
||||
// このコンポーネントが作成された時、非表示状態である場合がある
|
||||
|
@ -146,12 +147,12 @@ useInterval(
|
|||
() => {
|
||||
if (prefixEl.value) {
|
||||
if (prefixEl.value.offsetWidth) {
|
||||
inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + "px";
|
||||
inputEl.value!.style.paddingLeft = `${prefixEl.value.offsetWidth}px`;
|
||||
}
|
||||
}
|
||||
if (suffixEl.value) {
|
||||
if (suffixEl.value.offsetWidth) {
|
||||
inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + "px";
|
||||
inputEl.value!.style.paddingRight = `${suffixEl.value.offsetWidth}px`;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -16,19 +16,22 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: FIXME
|
||||
type ValueType = any;
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: any;
|
||||
value: any;
|
||||
modelValue: ValueType;
|
||||
value: ValueType;
|
||||
disabled: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: "update:modelValue", value: any): void;
|
||||
"update:modelValue": [value: ValueType];
|
||||
}>();
|
||||
|
||||
const checked = computed(() => props.modelValue === props.value);
|
||||
|
||||
function toggle(x) {
|
||||
function toggle(_ev: Event) {
|
||||
if (props.disabled) return;
|
||||
emit("update:modelValue", props.value);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, h } from "vue";
|
||||
import { type VNode, defineComponent, h } from "vue";
|
||||
import MkRadio from "./radio.vue";
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -22,13 +22,17 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
render() {
|
||||
let options = this.$slots.default();
|
||||
const label = this.$slots.label && this.$slots.label();
|
||||
const caption = this.$slots.caption && this.$slots.caption();
|
||||
let options = this.$slots.default!();
|
||||
const label = this.$slots.label && this.$slots.label!();
|
||||
const caption = this.$slots.caption && this.$slots.caption!();
|
||||
|
||||
// なぜかFragmentになることがあるため
|
||||
if (options.length === 1 && options[0].props == null)
|
||||
options = options[0].children;
|
||||
if (
|
||||
options.length === 1 &&
|
||||
options[0].props == null &&
|
||||
Array.isArray(options[0].children)
|
||||
)
|
||||
options = options[0].children as VNode[];
|
||||
|
||||
return h(
|
||||
"fieldset",
|
||||
|
@ -56,11 +60,15 @@ export default defineComponent({
|
|||
h(
|
||||
MkRadio,
|
||||
{
|
||||
// FIXME: It seems that there is a type error
|
||||
key: option.key,
|
||||
value: option.props?.value,
|
||||
disabled: option.props?.disabled,
|
||||
modelValue: this.value,
|
||||
"onUpdate:modelValue": (value) => (this.value = value),
|
||||
"onUpdate:modelValue": (value) => {
|
||||
this.value = value;
|
||||
return value;
|
||||
},
|
||||
},
|
||||
option.children,
|
||||
),
|
||||
|
@ -83,7 +91,7 @@ export default defineComponent({
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.novjtcto {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
@mouseleave="tooltipHide"
|
||||
@input="
|
||||
(x) => {
|
||||
inputVal = x.target.value;
|
||||
inputVal = Number((x.target as HTMLInputElement).value);
|
||||
if (instant) onChange(x);
|
||||
}
|
||||
"
|
||||
|
@ -29,6 +29,7 @@
|
|||
<datalist v-if="showTicks && steps" :id="id">
|
||||
<option
|
||||
v-for="i in steps"
|
||||
:key="`step-${i}`"
|
||||
:value="i + min"
|
||||
:label="(i + min).toString()"
|
||||
></option>
|
||||
|
@ -69,11 +70,11 @@ const props = withDefaults(
|
|||
},
|
||||
);
|
||||
|
||||
const inputEl = ref<HTMLElement>();
|
||||
const inputEl = ref<HTMLInputElement>();
|
||||
const inputVal = ref(props.modelValue);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: "update:modelValue", value: number): void;
|
||||
"update:modelValue": [value: number];
|
||||
}>();
|
||||
|
||||
const steps = computed(() => {
|
||||
|
@ -84,7 +85,7 @@ const steps = computed(() => {
|
|||
}
|
||||
});
|
||||
|
||||
function onChange(x) {
|
||||
function onChange(_x) {
|
||||
emit("update:modelValue", inputVal.value);
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ import * as os from "@/os";
|
|||
import { useInterval } from "@/scripts/use-interval";
|
||||
import { i18n } from "@/i18n";
|
||||
import icon from "@/scripts/icon";
|
||||
import type { MenuItem } from "@/types/menu";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string | null;
|
||||
|
@ -85,13 +86,13 @@ const focused = ref(false);
|
|||
const opening = ref(false);
|
||||
const changed = ref(false);
|
||||
const invalid = ref(false);
|
||||
const inputEl = ref(null);
|
||||
const prefixEl = ref(null);
|
||||
const suffixEl = ref(null);
|
||||
const container = ref(null);
|
||||
const inputEl = ref<HTMLInputElement | null>(null);
|
||||
const prefixEl = ref<HTMLElement | null>(null);
|
||||
const suffixEl = ref<HTMLElement | null>(null);
|
||||
const container = ref<HTMLElement | null>(null);
|
||||
const height = props.small ? 33 : props.large ? 39 : 36;
|
||||
|
||||
const focus = () => inputEl.value.focus();
|
||||
const focus = () => inputEl.value!.focus();
|
||||
const onInput = (ev) => {
|
||||
changed.value = true;
|
||||
emit("change", ev);
|
||||
|
@ -106,26 +107,27 @@ watch(modelValue, (newValue) => {
|
|||
v.value = newValue;
|
||||
});
|
||||
|
||||
watch(v, (newValue) => {
|
||||
watch(v, (_newValue) => {
|
||||
if (!props.manualSave) {
|
||||
updated();
|
||||
}
|
||||
|
||||
invalid.value = inputEl.value.validity.badInput;
|
||||
invalid.value = inputEl.value!.validity.badInput;
|
||||
});
|
||||
|
||||
// このコンポーネントが作成された時、非表示状態である場合がある
|
||||
// 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する
|
||||
useInterval(
|
||||
() => {
|
||||
if (inputEl.value == null) return;
|
||||
if (prefixEl.value) {
|
||||
if (prefixEl.value.offsetWidth) {
|
||||
inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + "px";
|
||||
inputEl.value.style.paddingLeft = `${prefixEl.value.offsetWidth}px`;
|
||||
}
|
||||
}
|
||||
if (suffixEl.value) {
|
||||
if (suffixEl.value.offsetWidth) {
|
||||
inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + "px";
|
||||
inputEl.value.style.paddingRight = `${suffixEl.value.offsetWidth}px`;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -144,19 +146,19 @@ onMounted(() => {
|
|||
});
|
||||
});
|
||||
|
||||
function show(ev: MouseEvent) {
|
||||
function show(_ev: MouseEvent) {
|
||||
focused.value = true;
|
||||
opening.value = true;
|
||||
|
||||
const menu = [];
|
||||
const menu: MenuItem[] = [];
|
||||
const options = slots.default!();
|
||||
|
||||
const pushOption = (option: VNode) => {
|
||||
menu.push({
|
||||
text: option.children,
|
||||
active: computed(() => v.value === option.props.value),
|
||||
text: option.children as string,
|
||||
active: computed(() => v.value === option.props?.value).value,
|
||||
action: () => {
|
||||
v.value = option.props.value;
|
||||
v.value = option.props?.value;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -167,13 +169,13 @@ function show(ev: MouseEvent) {
|
|||
const optgroup = vnode;
|
||||
menu.push({
|
||||
type: "label",
|
||||
text: optgroup.props.label,
|
||||
text: optgroup.props?.label,
|
||||
});
|
||||
scanOptions(optgroup.children);
|
||||
scanOptions(optgroup.children as VNode[]);
|
||||
} else if (Array.isArray(vnode.children)) {
|
||||
// 何故かフラグメントになってくることがある
|
||||
const fragment = vnode;
|
||||
scanOptions(fragment.children);
|
||||
scanOptions(fragment.children as VNode[]);
|
||||
} else if (vnode.props == null) {
|
||||
// v-if で条件が false のときにこうなる
|
||||
// nop?
|
||||
|
@ -186,12 +188,13 @@ function show(ev: MouseEvent) {
|
|||
|
||||
scanOptions(options);
|
||||
|
||||
os.popupMenu(menu, container.value, {
|
||||
width: container.value.offsetWidth,
|
||||
onClosing: () => {
|
||||
opening.value = false;
|
||||
},
|
||||
os.popupMenu(menu, container.value!, {
|
||||
width: container.value!.offsetWidth,
|
||||
// onClosing: () => {
|
||||
// opening.value = false;
|
||||
// },
|
||||
}).then(() => {
|
||||
opening.value = false;
|
||||
focused.value = false;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ const props = withDefaults(
|
|||
},
|
||||
);
|
||||
|
||||
const minWidth = props.minWidth + "px";
|
||||
const minWidth = `${props.minWidth}px`;
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -39,12 +39,13 @@ export default defineComponent({
|
|||
|
||||
props: {
|
||||
p: {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: FIXME
|
||||
type: Function as PropType<() => Promise<any>>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, context) {
|
||||
setup(props, _context) {
|
||||
const pending = ref(true);
|
||||
const resolved = ref(false);
|
||||
const rejected = ref(false);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<label class="ziffeomt">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="modelValue"
|
||||
:checked="toValue(modelValue)"
|
||||
:disabled="disabled"
|
||||
@change="(x) => toggle(x)"
|
||||
/>
|
||||
|
@ -18,7 +18,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from "vue";
|
||||
import { type Ref, toValue } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean | Ref<boolean>;
|
||||
|
@ -26,12 +26,12 @@ const props = defineProps<{
|
|||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: "update:modelValue", v: boolean): void;
|
||||
"update:modelValue": [v: boolean];
|
||||
}>();
|
||||
|
||||
function toggle(x) {
|
||||
function toggle(x: Event) {
|
||||
if (props.disabled) return;
|
||||
emit("update:modelValue", x.target.checked);
|
||||
emit("update:modelValue", (x.target as HTMLInputElement).checked);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ export default defineComponent({
|
|||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
|
@ -92,9 +93,11 @@ export default defineComponent({
|
|||
default: false,
|
||||
},
|
||||
autocomplete: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
spellcheck: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
code: {
|
||||
|
@ -132,9 +135,9 @@ export default defineComponent({
|
|||
const changed = ref(false);
|
||||
const invalid = ref(false);
|
||||
const filled = computed(() => v.value !== "" && v.value != null);
|
||||
const inputEl = ref(null);
|
||||
const inputEl = ref<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const focus = () => inputEl.value.focus();
|
||||
const focus = () => inputEl.value!.focus();
|
||||
const onInput = (ev) => {
|
||||
changed.value = true;
|
||||
context.emit("change", ev);
|
||||
|
@ -167,7 +170,7 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
invalid.value = inputEl.value.validity.badInput;
|
||||
invalid.value = inputEl.value!.validity.badInput;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
v-for="chosenItem in chosen"
|
||||
v-if="chosen && chosen.length > 0 && defaultStore.state.showAds"
|
||||
v-if="chosen && Array.isArray(chosen) && chosen.length > 0 && defaultStore.state.showAds"
|
||||
class="qiivuoyo"
|
||||
>
|
||||
<div v-if="!showMenu" class="main" :class="chosenItem.place">
|
||||
|
@ -10,7 +10,7 @@
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="chosen && defaultStore.state.showAds" class="qiivuoyo">
|
||||
<div v-else-if="chosen && !Array.isArray(chosen) && defaultStore.state.showAds" class="qiivuoyo">
|
||||
<div v-if="!showMenu" class="main" :class="chosen.place">
|
||||
<a :href="chosen.url" target="_blank">
|
||||
<img :src="chosen.imageUrl" />
|
||||
|
@ -60,7 +60,7 @@ const toggleMenu = (): void => {
|
|||
showMenu.value = !showMenu.value;
|
||||
};
|
||||
|
||||
const choseAd = (): Ad | null => {
|
||||
const choseAd = (): Ad | Ad[] | null => {
|
||||
if (props.specify) {
|
||||
return props.specify;
|
||||
}
|
||||
|
@ -113,6 +113,7 @@ const chosen = ref(choseAd());
|
|||
|
||||
function reduceFrequency(): void {
|
||||
if (chosen.value == null) return;
|
||||
if (Array.isArray(chosen.value)) return;
|
||||
if (defaultStore.state.mutedAds.includes(chosen.value.id)) return;
|
||||
defaultStore.push("mutedAds", chosen.value.id);
|
||||
os.success();
|
||||
|
|
|
@ -98,7 +98,7 @@ const props = withDefaults(
|
|||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: "click", v: MouseEvent): void;
|
||||
click: [v: MouseEvent];
|
||||
}>();
|
||||
|
||||
const url = computed(() =>
|
||||
|
@ -123,18 +123,18 @@ watch(
|
|||
},
|
||||
);
|
||||
|
||||
const gallery = ref(null);
|
||||
const gallery = ref<HTMLElement | null>(null);
|
||||
|
||||
onMounted(() => {
|
||||
const lightbox = new PhotoSwipeLightbox({
|
||||
dataSource: [
|
||||
{
|
||||
src: url,
|
||||
src: url.value,
|
||||
w: 300,
|
||||
h: 300,
|
||||
},
|
||||
],
|
||||
gallery: gallery.value,
|
||||
gallery: gallery.value || undefined,
|
||||
children: ".avatar",
|
||||
thumbSelector: ".avatar",
|
||||
loop: false,
|
||||
|
@ -174,7 +174,7 @@ onMounted(() => {
|
|||
history.pushState(null, "", location.href);
|
||||
addEventListener("popstate", close);
|
||||
// This is a workaround. Not sure why, but when clicking to open, it doesn't move focus to the photoswipe. Preventing using esc to close. However when using keyboard to open it already focuses the lightbox fine.
|
||||
lightbox.pswp.element.focus();
|
||||
lightbox.pswp?.element?.focus();
|
||||
});
|
||||
lightbox.on("close", () => {
|
||||
removeEventListener("popstate", close);
|
||||
|
@ -186,7 +186,7 @@ onMounted(() => {
|
|||
function close() {
|
||||
removeEventListener("popstate", close);
|
||||
history.forward();
|
||||
lightbox.pswp.close();
|
||||
lightbox.pswp?.close();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -4,16 +4,16 @@
|
|||
class="mk-emoji custom"
|
||||
:class="{ normal, noStyle }"
|
||||
:src="url"
|
||||
:alt="alt"
|
||||
:title="alt"
|
||||
:alt="alt || undefined"
|
||||
:title="alt || undefined"
|
||||
decoding="async"
|
||||
/>
|
||||
<img
|
||||
v-else-if="char && !useOsNativeEmojis"
|
||||
class="mk-emoji"
|
||||
:src="url"
|
||||
:alt="alt"
|
||||
:title="alt"
|
||||
:alt="alt || undefined"
|
||||
:title="alt || undefined"
|
||||
decoding="async"
|
||||
/>
|
||||
<span v-else-if="char && useOsNativeEmojis">{{ char }}</span>
|
||||
|
@ -32,7 +32,7 @@ const props = defineProps<{
|
|||
emoji: string;
|
||||
normal?: boolean;
|
||||
noStyle?: boolean;
|
||||
customEmojis?: entities.CustomEmoji[];
|
||||
customEmojis?: entities.EmojiLite[];
|
||||
isReaction?: boolean;
|
||||
}>();
|
||||
|
||||
|
@ -50,6 +50,7 @@ const customEmoji = computed(() =>
|
|||
: null,
|
||||
);
|
||||
const url = computed(() => {
|
||||
if (!customEmoji.value) return undefined;
|
||||
if (char.value) {
|
||||
return char2filePath(char.value);
|
||||
} else {
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = withDefaults(
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
inline?: boolean;
|
||||
colored?: boolean;
|
||||
|
|
|
@ -19,22 +19,22 @@
|
|||
import {} from "vue";
|
||||
import MfmCore from "@/components/mfm";
|
||||
import { defaultStore } from "@/store";
|
||||
import type { entities } from "firefish-js";
|
||||
|
||||
const props = withDefaults(
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
text: string;
|
||||
plain?: boolean;
|
||||
nowrap?: boolean;
|
||||
author?: any;
|
||||
customEmojis?: any;
|
||||
author?: entities.User;
|
||||
customEmojis?: entities.EmojiLite[];
|
||||
isNote?: boolean;
|
||||
advancedMfm: boolean;
|
||||
advancedMfm?: boolean;
|
||||
lang?: string;
|
||||
}>(),
|
||||
{
|
||||
plain: false,
|
||||
nowrap: false,
|
||||
author: null,
|
||||
isNote: true,
|
||||
},
|
||||
);
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
v-if="show"
|
||||
ref="el"
|
||||
class="fdidabkb"
|
||||
:class="{ thin: thin_, tabs: tabs?.length > 0 }"
|
||||
:style="{ background: bg }"
|
||||
:class="{ thin: thin_, tabs: isTabs(tabs)}"
|
||||
:style="{ background: bg || undefined }"
|
||||
@click="onClick"
|
||||
>
|
||||
<div class="left">
|
||||
|
@ -70,14 +70,14 @@
|
|||
</div>
|
||||
<template v-if="metadata">
|
||||
<nav
|
||||
v-if="hasTabs"
|
||||
v-if="isTabs(tabs)"
|
||||
ref="tabsEl"
|
||||
class="tabs"
|
||||
:class="{ collapse: hasTabs && tabs.length > 3 }"
|
||||
:class="{ collapse: tabs.length > 3 }"
|
||||
>
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:ref="(el) => (tabRefs[tab.key] = el)"
|
||||
:ref="(el) => (tab.key && (tabRefs[tab.key] = el))"
|
||||
v-tooltip.noDelay="tab.title"
|
||||
v-vibrate="5"
|
||||
class="tab _button"
|
||||
|
@ -157,6 +157,7 @@ const props = defineProps<{
|
|||
actions?: {
|
||||
text: string;
|
||||
icon: string;
|
||||
highlighted?: boolean;
|
||||
handler: (ev: MouseEvent) => void;
|
||||
}[];
|
||||
thin?: boolean;
|
||||
|
@ -171,7 +172,7 @@ const displayBackButton =
|
|||
inject("shouldBackButton", true);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: "update:tab", key: string);
|
||||
"update:tab": [key: string];
|
||||
}>();
|
||||
|
||||
const metadata = injectPageMetadata();
|
||||
|
@ -183,9 +184,14 @@ const el = ref<HTMLElement | null>(null);
|
|||
const tabRefs = {};
|
||||
const tabHighlightEl = ref<HTMLElement | null>(null);
|
||||
const tabsEl = ref<HTMLElement | null>(null);
|
||||
const bg = ref(null);
|
||||
const bg = ref<string | null | number>(null);
|
||||
const narrow = ref(false);
|
||||
const hasTabs = computed(() => props.tabs && props.tabs.length > 0);
|
||||
|
||||
function isTabs(t: Tab[] | undefined): t is Tab[] {
|
||||
return t != null && t.length > 0;
|
||||
}
|
||||
|
||||
const hasActions = computed(() => props.actions && props.actions.length > 0);
|
||||
const show = computed(() => {
|
||||
return !hideTitle || hasTabs.value || hasActions.value;
|
||||
|
@ -201,7 +207,7 @@ const openAccountMenu = (ev: MouseEvent) => {
|
|||
};
|
||||
|
||||
const showTabsPopup = (ev: MouseEvent) => {
|
||||
if (!hasTabs.value) return;
|
||||
if (!isTabs(props.tabs)) return;
|
||||
if (!narrow.value) return;
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
@ -213,7 +219,7 @@ const showTabsPopup = (ev: MouseEvent) => {
|
|||
onTabClick(tab, ev);
|
||||
},
|
||||
}));
|
||||
popupMenu(menu, ev.currentTarget ?? ev.target);
|
||||
popupMenu(menu, (ev.currentTarget ?? ev.target) as HTMLElement);
|
||||
};
|
||||
|
||||
const preventDrag = (ev: TouchEvent) => {
|
||||
|
@ -224,7 +230,9 @@ const onClick = () => {
|
|||
if (props.to) {
|
||||
location.href = props.to;
|
||||
} else {
|
||||
scrollToTop(el.value, { behavior: "smooth" });
|
||||
if (el.value) {
|
||||
scrollToTop(el.value, { behavior: "smooth" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -257,6 +265,8 @@ onMounted(() => {
|
|||
() => [props.tab, props.tabs],
|
||||
() => {
|
||||
nextTick(() => {
|
||||
if (props.tab == null) return;
|
||||
if (!isTabs(props.tabs)) return;
|
||||
const tabEl = tabRefs[props.tab];
|
||||
if (tabEl && tabHighlightEl.value) {
|
||||
// offsetWidth や offsetLeft は少数を丸めてしまうため getBoundingClientRect を使う必要がある
|
||||
|
@ -266,7 +276,8 @@ onMounted(() => {
|
|||
tabEl.style = `--width: ${tabSizeX}px`;
|
||||
}
|
||||
setTimeout(() => {
|
||||
tabHighlightEl.value.style.width = tabSizeX + "px";
|
||||
if (tabHighlightEl.value == null) return;
|
||||
tabHighlightEl.value.style.width = `${tabSizeX}px`;
|
||||
tabHighlightEl.value.style.transform = `translateX(${tabEl.offsetLeft}px)`;
|
||||
window.requestAnimationFrame(() => {
|
||||
tabsEl.value?.scrollTo({
|
||||
|
@ -283,10 +294,10 @@ onMounted(() => {
|
|||
},
|
||||
);
|
||||
|
||||
if (el.value && el.value.parentElement) {
|
||||
if (el.value?.parentElement) {
|
||||
narrow.value = el.value.parentElement.offsetWidth < 500;
|
||||
ro = new ResizeObserver((entries, observer) => {
|
||||
if (el.value.parentElement && document.body.contains(el.value)) {
|
||||
ro = new ResizeObserver((_entries, _observer) => {
|
||||
if (el.value?.parentElement && document.body.contains(el.value)) {
|
||||
narrow.value = el.value.parentElement.offsetWidth < 500;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -28,8 +28,8 @@ const parentStickyTop = inject<Ref<number>>(CURRENT_STICKY_TOP, ref(0));
|
|||
provide(CURRENT_STICKY_TOP, childStickyTop);
|
||||
|
||||
const calc = () => {
|
||||
childStickyTop.value = parentStickyTop.value + headerEl.value.offsetHeight;
|
||||
headerHeight.value = headerEl.value.offsetHeight.toString();
|
||||
childStickyTop.value = parentStickyTop.value + headerEl.value!.offsetHeight;
|
||||
headerHeight.value = headerEl.value!.offsetHeight.toString();
|
||||
};
|
||||
|
||||
const observer = new ResizeObserver(() => {
|
||||
|
@ -46,7 +46,7 @@ onMounted(() => {
|
|||
watch(
|
||||
childStickyTop,
|
||||
() => {
|
||||
bodyEl.value.style.setProperty(
|
||||
bodyEl.value!.style.setProperty(
|
||||
"--stickyTop",
|
||||
`${childStickyTop.value}px`,
|
||||
);
|
||||
|
@ -56,7 +56,7 @@ onMounted(() => {
|
|||
},
|
||||
);
|
||||
|
||||
observer.observe(headerEl.value);
|
||||
observer.observe(headerEl.value!);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
|
|
@ -27,7 +27,7 @@ const props = withDefaults(
|
|||
|
||||
const _time =
|
||||
props.time == null
|
||||
? NaN
|
||||
? Number.NaN
|
||||
: typeof props.time === "number"
|
||||
? props.time
|
||||
: (props.time instanceof Date
|
||||
|
|
|
@ -38,13 +38,14 @@ export default defineComponent({
|
|||
|
||||
return h(
|
||||
this.tag,
|
||||
parsed.map((x) =>
|
||||
typeof x === "string"
|
||||
? this.textTag
|
||||
? h(this.textTag, x)
|
||||
: x
|
||||
: this.$slots[x.arg](),
|
||||
),
|
||||
parsed.map((x) => {
|
||||
if (typeof x === "string") {
|
||||
return this.textTag ? h(this.textTag, x) : x;
|
||||
} else {
|
||||
const t = this.$slots[x.arg];
|
||||
return t ? t() : `I18n[${x.arg}]`;
|
||||
}
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { defineComponent, h } from "vue";
|
||||
import * as mfm from "mfm-js";
|
||||
import type { VNode } from "vue";
|
||||
import type { VNode, PropType } from "vue";
|
||||
import MkUrl from "@/components/global/MkUrl.vue";
|
||||
import MkLink from "@/components/MkLink.vue";
|
||||
import MkMention from "@/components/MkMention.vue";
|
||||
|
@ -14,6 +14,7 @@ import MkA from "@/components/global/MkA.vue";
|
|||
import { host } from "@/config";
|
||||
import { reducedMotion } from "@/scripts/reduced-motion";
|
||||
import { defaultStore } from "@/store";
|
||||
import type { entities } from "firefish-js";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
@ -38,6 +39,7 @@ export default defineComponent({
|
|||
default: null,
|
||||
},
|
||||
customEmojis: {
|
||||
type: Array as PropType<entities.EmojiLite[]>,
|
||||
required: false,
|
||||
},
|
||||
isNote: {
|
||||
|
|
|
@ -855,8 +855,8 @@ export function popupMenu(
|
|||
noReturnFocus?: boolean;
|
||||
},
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let dispose;
|
||||
return new Promise<void>((resolve, _reject) => {
|
||||
let dispose: () => void;
|
||||
popup(
|
||||
defineAsyncComponent({
|
||||
loader: () => import("@/components/MkPopupMenu.vue"),
|
||||
|
|
|
@ -113,10 +113,11 @@ import MkInstanceCardMini from "@/components/MkInstanceCardMini.vue";
|
|||
import FormSplit from "@/components/form/split.vue";
|
||||
import { i18n } from "@/i18n";
|
||||
import icon from "@/scripts/icon";
|
||||
import type { instanceSortParam } from "firefish-js";
|
||||
|
||||
const host = ref("");
|
||||
const state = ref("federating");
|
||||
const sort = ref("+pubSub");
|
||||
const sort = ref<(typeof instanceSortParam)[number]>("+pubSub");
|
||||
const pagination = {
|
||||
endpoint: "federation/instances" as const,
|
||||
limit: 10,
|
||||
|
|
|
@ -94,7 +94,7 @@ const origin = ref("local");
|
|||
const type = ref(null);
|
||||
const searchHost = ref("");
|
||||
const userId = ref("");
|
||||
const viewMode = ref("grid");
|
||||
const viewMode = ref<"list" | "grid">("grid");
|
||||
const pagination = {
|
||||
endpoint: "admin/drive/files" as const,
|
||||
limit: 10,
|
||||
|
|
|
@ -313,7 +313,7 @@ const isSilenced = ref(false);
|
|||
const faviconUrl = ref<string | null>(null);
|
||||
|
||||
const usersPagination = {
|
||||
endpoint: isAdmin ? "admin/show-users" : ("users" as const),
|
||||
endpoint: isAdmin ? ("admin/show-users" as const) : ("users" as const),
|
||||
limit: 10,
|
||||
params: {
|
||||
sort: "+updatedAt",
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
<MkPagination
|
||||
v-else
|
||||
ref="pagingComponent"
|
||||
v-slot="{ items }: { items: entities.NoteEdit[] }"
|
||||
v-slot="{ items }"
|
||||
:pagination="pagination"
|
||||
>
|
||||
<div ref="tlEl" class="giivymft noGap">
|
||||
<XList
|
||||
v-slot="{ item }: { item: entities.Note }"
|
||||
v-slot="{ item }"
|
||||
:items="convertNoteEditsToNotes(items)"
|
||||
class="notes"
|
||||
:no-gap="true"
|
||||
|
@ -35,7 +35,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import MkPagination from "@/components/MkPagination.vue";
|
||||
import type { Paging } from "@/components/MkPagination.vue";
|
||||
import { api } from "@/os";
|
||||
import XList from "@/components/MkDateSeparatedList.vue";
|
||||
import XNote from "@/components/MkNote.vue";
|
||||
|
@ -50,7 +49,7 @@ const props = defineProps<{
|
|||
noteId: string;
|
||||
}>();
|
||||
|
||||
const pagination: Paging = {
|
||||
const pagination = {
|
||||
endpoint: "notes/history" as const,
|
||||
limit: 10,
|
||||
offsetMode: true,
|
||||
|
|
|
@ -9,7 +9,7 @@ export interface PageMetadata {
|
|||
title: string;
|
||||
subtitle?: string;
|
||||
icon?: string | null;
|
||||
avatar?: entities.User | null;
|
||||
avatar?: entities.UserDetailed | null;
|
||||
userName?: entities.User | null;
|
||||
bg?: string;
|
||||
}
|
||||
|
|
|
@ -26,10 +26,9 @@
|
|||
"@/*": ["./src/*"]
|
||||
},
|
||||
"typeRoots": ["node_modules/@types", "@types"],
|
||||
"types": ["vite/client"],
|
||||
"lib": ["esnext", "dom"],
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"compileOnSave": false,
|
||||
"include": ["./**/*.ts", "./**/*.vue"]
|
||||
"include": ["./src/**/*.ts", "./src/**/*.vue"]
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ import type {
|
|||
UserSorting,
|
||||
} from "./entities";
|
||||
|
||||
import type * as consts from "./consts";
|
||||
|
||||
type TODO = Record<string, any> | null;
|
||||
|
||||
type NoParams = Record<string, never>;
|
||||
|
@ -84,7 +86,7 @@ export type Endpoints = {
|
|||
"admin/server-info": { req: TODO; res: TODO };
|
||||
"admin/show-moderation-logs": { req: TODO; res: TODO };
|
||||
"admin/show-user": { req: TODO; res: TODO };
|
||||
"admin/show-users": { req: TODO; res: TODO };
|
||||
"admin/show-users": { req: TODO; res: User[] };
|
||||
"admin/silence-user": { req: TODO; res: TODO };
|
||||
"admin/suspend-user": { req: TODO; res: TODO };
|
||||
"admin/unsilence-user": { req: TODO; res: TODO };
|
||||
|
@ -101,7 +103,18 @@ export type Endpoints = {
|
|||
"admin/announcements/update": { req: TODO; res: TODO };
|
||||
"admin/drive/clean-remote-files": { req: TODO; res: TODO };
|
||||
"admin/drive/cleanup": { req: TODO; res: TODO };
|
||||
"admin/drive/files": { req: TODO; res: TODO };
|
||||
"admin/drive/files": {
|
||||
req: {
|
||||
limit?: number;
|
||||
sinceId?: DriveFile["id"];
|
||||
untilId?: DriveFile["id"];
|
||||
userId?: User["id"];
|
||||
type?: string;
|
||||
origin?: "combined" | "local" | "remote";
|
||||
hostname?: string;
|
||||
};
|
||||
res: DriveFile[];
|
||||
};
|
||||
"admin/drive/show-file": { req: TODO; res: TODO };
|
||||
"admin/emoji/add": { req: TODO; res: TODO };
|
||||
"admin/emoji/copy": { req: TODO; res: TODO };
|
||||
|
@ -200,7 +213,7 @@ export type Endpoints = {
|
|||
"channels/owned": { req: TODO; res: TODO };
|
||||
"channels/pin-note": { req: TODO; res: TODO };
|
||||
"channels/show": { req: TODO; res: TODO };
|
||||
"channels/timeline": { req: TODO; res: TODO };
|
||||
"channels/timeline": { req: TODO; res: Note[] };
|
||||
"channels/unfollow": { req: TODO; res: TODO };
|
||||
"channels/update": { req: TODO; res: TODO };
|
||||
|
||||
|
@ -238,7 +251,7 @@ export type Endpoints = {
|
|||
};
|
||||
res: DriveFile[];
|
||||
};
|
||||
"drive/files/attached-notes": { req: TODO; res: TODO };
|
||||
"drive/files/attached-notes": { req: TODO; res: Note[] };
|
||||
"drive/files/check-existence": { req: TODO; res: TODO };
|
||||
"drive/files/create": { req: TODO; res: TODO };
|
||||
"drive/files/delete": { req: { fileId: DriveFile["id"] }; res: null };
|
||||
|
@ -360,25 +373,7 @@ export type Endpoints = {
|
|||
publishing?: boolean | null;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
sort?:
|
||||
| "+pubSub"
|
||||
| "-pubSub"
|
||||
| "+notes"
|
||||
| "-notes"
|
||||
| "+users"
|
||||
| "-users"
|
||||
| "+following"
|
||||
| "-following"
|
||||
| "+followers"
|
||||
| "-followers"
|
||||
| "+caughtAt"
|
||||
| "-caughtAt"
|
||||
| "+lastCommunicatedAt"
|
||||
| "-lastCommunicatedAt"
|
||||
| "+driveUsage"
|
||||
| "-driveUsage"
|
||||
| "+driveFiles"
|
||||
| "-driveFiles";
|
||||
sort?: (typeof consts.instanceSortParam)[number];
|
||||
};
|
||||
res: Instance[];
|
||||
};
|
||||
|
|
|
@ -151,3 +151,24 @@ export const languages = [
|
|||
"yi",
|
||||
"zh",
|
||||
] as const;
|
||||
|
||||
export const instanceSortParam = [
|
||||
"+pubSub",
|
||||
"-pubSub",
|
||||
"+notes",
|
||||
"-notes",
|
||||
"+users",
|
||||
"-users",
|
||||
"+following",
|
||||
"-following",
|
||||
"+followers",
|
||||
"-followers",
|
||||
"+caughtAt",
|
||||
"-caughtAt",
|
||||
"+lastCommunicatedAt",
|
||||
"-lastCommunicatedAt",
|
||||
"+driveUsage",
|
||||
"-driveUsage",
|
||||
"+driveFiles",
|
||||
"-driveFiles",
|
||||
] as const;
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import type * as consts from "./consts";
|
||||
|
||||
export type ID = string;
|
||||
export type DateString = string;
|
||||
|
||||
type TODO = Record<string, any>;
|
||||
|
||||
// NOTE: 極力この型を使うのは避け、UserLite か UserDetailed か明示するように
|
||||
export type User = UserLite | UserDetailed;
|
||||
export type User = UserLite & Partial<UserDetailed>;
|
||||
|
||||
export type UserLite = {
|
||||
id: ID;
|
||||
|
@ -108,7 +110,7 @@ export type MeDetailed = UserDetailed & {
|
|||
isExplorable: boolean;
|
||||
mutedWords: string[][];
|
||||
mutedPatterns: string[];
|
||||
mutingNotificationTypes: string[];
|
||||
mutingNotificationTypes: (typeof consts.notificationTypes)[number][];
|
||||
noCrawle: boolean;
|
||||
preventAiLearning: boolean;
|
||||
receiveAnnouncementEmail: boolean;
|
||||
|
@ -129,6 +131,8 @@ export type DriveFile = {
|
|||
blurhash: string;
|
||||
comment: string | null;
|
||||
properties: Record<string, any>;
|
||||
userId?: User["id"];
|
||||
user?: User;
|
||||
};
|
||||
|
||||
export type DriveFolder = TODO;
|
||||
|
@ -152,7 +156,8 @@ export type Note = {
|
|||
visibleUserIds?: User["id"][];
|
||||
lang?: string;
|
||||
localOnly?: boolean;
|
||||
channel?: Channel["id"];
|
||||
channelId?: Channel["id"];
|
||||
channel?: Channel;
|
||||
myReaction?: string;
|
||||
reactions: Record<string, number>;
|
||||
renoteCount: number;
|
||||
|
@ -199,82 +204,98 @@ export type NoteReaction = {
|
|||
type: string;
|
||||
};
|
||||
|
||||
export type Notification = {
|
||||
interface BaseNotification {
|
||||
id: ID;
|
||||
createdAt: DateString;
|
||||
isRead: boolean;
|
||||
} & (
|
||||
| {
|
||||
type: "reaction";
|
||||
reaction: string;
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
| {
|
||||
type: "reply";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
| {
|
||||
type: "renote";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
| {
|
||||
type: "quote";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
| {
|
||||
type: "mention";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
| {
|
||||
type: "pollVote";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
| {
|
||||
type: "pollEnded";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
| {
|
||||
type: "follow";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
}
|
||||
| {
|
||||
type: "followRequestAccepted";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
}
|
||||
| {
|
||||
type: "receiveFollowRequest";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
}
|
||||
| {
|
||||
type: "groupInvited";
|
||||
invitation: UserGroup;
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
}
|
||||
| {
|
||||
type: "app";
|
||||
header?: string | null;
|
||||
body: string;
|
||||
icon?: string | null;
|
||||
}
|
||||
);
|
||||
type: (typeof consts.notificationTypes)[number];
|
||||
}
|
||||
|
||||
export interface ReactionNotification extends BaseNotification {
|
||||
type: "reaction";
|
||||
reaction: string;
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
export interface ReplyNotification extends BaseNotification {
|
||||
type: "reply";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
export interface RenoteNotification extends BaseNotification {
|
||||
type: "renote";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
export interface QuoteNotification extends BaseNotification {
|
||||
type: "quote";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
export interface MentionNotification extends BaseNotification {
|
||||
type: "mention";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
export interface PollVoteNotification extends BaseNotification {
|
||||
type: "pollVote";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
export interface PollEndedNotification extends BaseNotification {
|
||||
type: "pollEnded";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
note: Note;
|
||||
}
|
||||
export interface FollowNotification extends BaseNotification {
|
||||
type: "follow";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
}
|
||||
|
||||
export interface FollowRequestAcceptedNotification extends BaseNotification {
|
||||
type: "followRequestAccepted";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
}
|
||||
export interface ReceiveFollowRequestNotification extends BaseNotification {
|
||||
type: "receiveFollowRequest";
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
}
|
||||
export interface GroupInvitedNotification extends BaseNotification {
|
||||
type: "groupInvited";
|
||||
invitation: UserGroup;
|
||||
user: User;
|
||||
userId: User["id"];
|
||||
}
|
||||
export interface AppNotification extends BaseNotification {
|
||||
type: "app";
|
||||
header?: string | null;
|
||||
body: string;
|
||||
icon?: string | null;
|
||||
}
|
||||
|
||||
export type Notification =
|
||||
| ReactionNotification
|
||||
| ReplyNotification
|
||||
| RenoteNotification
|
||||
| QuoteNotification
|
||||
| MentionNotification
|
||||
| PollVoteNotification
|
||||
| PollEndedNotification
|
||||
| FollowNotification
|
||||
| FollowRequestAcceptedNotification
|
||||
| ReceiveFollowRequestNotification
|
||||
| GroupInvitedNotification
|
||||
| AppNotification;
|
||||
|
||||
export type MessagingMessage = {
|
||||
id: ID;
|
||||
|
@ -300,6 +321,11 @@ export type CustomEmoji = {
|
|||
aliases: string[];
|
||||
};
|
||||
|
||||
export type EmojiLite = {
|
||||
name: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type LiteInstanceMetadata = {
|
||||
maintainerName: string | null;
|
||||
maintainerEmail: string | null;
|
||||
|
@ -449,6 +475,7 @@ export type FollowRequest = {
|
|||
|
||||
export type Channel = {
|
||||
id: ID;
|
||||
name: string;
|
||||
// TODO
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Endpoints } 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";
|
||||
|
||||
export {
|
||||
Endpoints,
|
||||
|
@ -12,6 +13,7 @@ export {
|
|||
StreamTypes,
|
||||
acct,
|
||||
type Acct,
|
||||
type TypeUtils,
|
||||
};
|
||||
|
||||
export const permissions = consts.permissions;
|
||||
|
@ -20,6 +22,7 @@ export const noteVisibilities = consts.noteVisibilities;
|
|||
export const mutedNoteReasons = consts.mutedNoteReasons;
|
||||
export const languages = consts.languages;
|
||||
export const ffVisibility = consts.ffVisibility;
|
||||
export const instanceSortParam = consts.instanceSortParam;
|
||||
|
||||
// api extractor not supported yet
|
||||
//export * as api from './api';
|
||||
|
|
|
@ -236,14 +236,14 @@ export default class Stream extends EventEmitter<StreamEvents> {
|
|||
// TODO: これらのクラスを Stream クラスの内部クラスにすれば余計なメンバをpublicにしないで済むかも?
|
||||
// もしくは @internal を使う? https://www.typescriptlang.org/tsconfig#stripInternal
|
||||
class Pool {
|
||||
public channel: string;
|
||||
public channel: keyof Channels;
|
||||
public id: string;
|
||||
protected stream: Stream;
|
||||
public users = 0;
|
||||
private disposeTimerId: any;
|
||||
private isConnected = false;
|
||||
|
||||
constructor(stream: Stream, channel: string, id: string) {
|
||||
constructor(stream: Stream, channel: keyof Channels, id: string) {
|
||||
this.channel = channel;
|
||||
this.stream = stream;
|
||||
this.id = id;
|
||||
|
@ -301,7 +301,7 @@ class Pool {
|
|||
export abstract class Connection<
|
||||
Channel extends AnyOf<Channels> = any,
|
||||
> extends EventEmitter<Channel["events"]> {
|
||||
public channel: string;
|
||||
public channel: keyof Channels;
|
||||
protected stream: Stream;
|
||||
public abstract id: string;
|
||||
|
||||
|
@ -309,7 +309,7 @@ export abstract class Connection<
|
|||
public inCount = 0; // for debug
|
||||
public outCount = 0; // for debug
|
||||
|
||||
constructor(stream: Stream, channel: string, name?: string) {
|
||||
constructor(stream: Stream, channel: keyof Channels, name?: string) {
|
||||
super();
|
||||
|
||||
this.stream = stream;
|
||||
|
@ -342,7 +342,12 @@ class SharedConnection<
|
|||
return this.pool.id;
|
||||
}
|
||||
|
||||
constructor(stream: Stream, channel: string, pool: Pool, name?: string) {
|
||||
constructor(
|
||||
stream: Stream,
|
||||
channel: keyof Channels,
|
||||
pool: Pool,
|
||||
name?: string,
|
||||
) {
|
||||
super(stream, channel, name);
|
||||
|
||||
this.pool = pool;
|
||||
|
@ -364,7 +369,7 @@ class NonSharedConnection<
|
|||
|
||||
constructor(
|
||||
stream: Stream,
|
||||
channel: string,
|
||||
channel: keyof Channels,
|
||||
id: string,
|
||||
params: Channel["params"],
|
||||
) {
|
||||
|
|
|
@ -10,9 +10,14 @@ import type {
|
|||
User,
|
||||
UserGroup,
|
||||
} from "./entities";
|
||||
import type { Connection } from "./streaming";
|
||||
|
||||
type FIXME = any;
|
||||
|
||||
type TimelineParams = {
|
||||
withReplies?: boolean;
|
||||
};
|
||||
|
||||
export type Channels = {
|
||||
main: {
|
||||
params: null;
|
||||
|
@ -56,35 +61,35 @@ export type Channels = {
|
|||
receives: null;
|
||||
};
|
||||
homeTimeline: {
|
||||
params: null;
|
||||
params?: TimelineParams;
|
||||
events: {
|
||||
note: (payload: Note) => void;
|
||||
};
|
||||
receives: null;
|
||||
};
|
||||
localTimeline: {
|
||||
params: null;
|
||||
params: TimelineParams;
|
||||
events: {
|
||||
note: (payload: Note) => void;
|
||||
};
|
||||
receives: null;
|
||||
};
|
||||
hybridTimeline: {
|
||||
params: null;
|
||||
params: TimelineParams;
|
||||
events: {
|
||||
note: (payload: Note) => void;
|
||||
};
|
||||
receives: null;
|
||||
};
|
||||
recommendedTimeline: {
|
||||
params: null;
|
||||
params: TimelineParams;
|
||||
events: {
|
||||
note: (payload: Note) => void;
|
||||
};
|
||||
receives: null;
|
||||
};
|
||||
globalTimeline: {
|
||||
params: null;
|
||||
params: TimelineParams;
|
||||
events: {
|
||||
note: (payload: Note) => void;
|
||||
};
|
||||
|
@ -195,3 +200,5 @@ export type BroadcastEvents = {
|
|||
emoji: CustomEmoji;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
export type ChannelOf<C extends keyof Channels> = Connection<Channels[C]>;
|
||||
|
|
7
packages/firefish-js/src/type-utils.ts
Normal file
7
packages/firefish-js/src/type-utils.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import type { Endpoints } from "./api.types";
|
||||
|
||||
type PropertyOfType<Type, U> = {
|
||||
[K in keyof Type]: Type[K] extends U ? K : never;
|
||||
}[keyof Type];
|
||||
|
||||
export type EndpointsOf<T> = PropertyOfType<Endpoints, { res: T }>;
|
|
@ -785,6 +785,9 @@ importers:
|
|||
vue-prism-editor:
|
||||
specifier: 2.0.0-alpha.2
|
||||
version: 2.0.0-alpha.2(vue@3.4.21)
|
||||
vue-tsc:
|
||||
specifier: 2.0.6
|
||||
version: 2.0.6(typescript@5.4.3)
|
||||
|
||||
packages/firefish-js:
|
||||
dependencies:
|
||||
|
@ -4805,6 +4808,25 @@ packages:
|
|||
vue: 3.4.21(typescript@5.4.3)
|
||||
dev: true
|
||||
|
||||
/@volar/language-core@2.1.6:
|
||||
resolution: {integrity: sha512-pAlMCGX/HatBSiDFMdMyqUshkbwWbLxpN/RL7HCQDOo2gYBE+uS+nanosLc1qR6pTQ/U8q00xt8bdrrAFPSC0A==}
|
||||
dependencies:
|
||||
'@volar/source-map': 2.1.6
|
||||
dev: true
|
||||
|
||||
/@volar/source-map@2.1.6:
|
||||
resolution: {integrity: sha512-TeyH8pHHonRCHYI91J7fWUoxi0zWV8whZTVRlsWHSYfjm58Blalkf9LrZ+pj6OiverPTmrHRkBsG17ScQyWECw==}
|
||||
dependencies:
|
||||
muggle-string: 0.4.1
|
||||
dev: true
|
||||
|
||||
/@volar/typescript@2.1.6:
|
||||
resolution: {integrity: sha512-JgPGhORHqXuyC3r6skPmPHIZj4LoMmGlYErFTuPNBq9Nhc9VTv7ctHY7A3jMN3ngKEfRrfnUcwXHztvdSQqNfw==}
|
||||
dependencies:
|
||||
'@volar/language-core': 2.1.6
|
||||
path-browserify: 1.0.1
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-core@3.4.21:
|
||||
resolution: {integrity: sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==}
|
||||
dependencies:
|
||||
|
@ -4851,6 +4873,24 @@ packages:
|
|||
'@vue/shared': 3.4.21
|
||||
dev: true
|
||||
|
||||
/@vue/language-core@2.0.6(typescript@5.4.3):
|
||||
resolution: {integrity: sha512-UzqU12tzf9XLqRO3TiWPwRNpP4fyUzE6MAfOQWQNZ4jy6a30ARRUpmODDKq6O8C4goMc2AlPqTmjOHPjHkilSg==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@volar/language-core': 2.1.6
|
||||
'@vue/compiler-dom': 3.4.21
|
||||
'@vue/shared': 3.4.21
|
||||
computeds: 0.0.1
|
||||
minimatch: 9.0.3
|
||||
path-browserify: 1.0.1
|
||||
typescript: 5.4.3
|
||||
vue-template-compiler: 2.7.16
|
||||
dev: true
|
||||
|
||||
/@vue/reactivity@3.4.21:
|
||||
resolution: {integrity: sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==}
|
||||
dependencies:
|
||||
|
@ -6795,6 +6835,10 @@ packages:
|
|||
readable-stream: 4.5.2
|
||||
dev: false
|
||||
|
||||
/computeds@0.0.1:
|
||||
resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==}
|
||||
dev: true
|
||||
|
||||
/concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
|
@ -7317,6 +7361,10 @@ packages:
|
|||
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
|
||||
dev: false
|
||||
|
||||
/de-indent@1.0.2:
|
||||
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
||||
dev: true
|
||||
|
||||
/debug@2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
peerDependencies:
|
||||
|
@ -12896,6 +12944,10 @@ packages:
|
|||
msgpackr-extract: 3.0.2
|
||||
dev: false
|
||||
|
||||
/muggle-string@0.4.1:
|
||||
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
|
||||
dev: true
|
||||
|
||||
/multer@1.4.5-lts.1:
|
||||
resolution: {integrity: sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
@ -13676,6 +13728,10 @@ packages:
|
|||
resolution: {integrity: sha512-Wy8PXTLqPAN0oEgBrlnsXPMww3SYJ44tQ8aVrGAI4h4JZYCS0oYqsPqtPR8OhJpv6qFbpbB7XAn0liKV7EXubA==}
|
||||
dev: false
|
||||
|
||||
/path-browserify@1.0.1:
|
||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||
dev: true
|
||||
|
||||
/path-dirname@1.0.2:
|
||||
resolution: {integrity: sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==}
|
||||
dev: false
|
||||
|
@ -17233,6 +17289,25 @@ packages:
|
|||
vue: 3.4.21(typescript@5.4.3)
|
||||
dev: true
|
||||
|
||||
/vue-template-compiler@2.7.16:
|
||||
resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==}
|
||||
dependencies:
|
||||
de-indent: 1.0.2
|
||||
he: 1.2.0
|
||||
dev: true
|
||||
|
||||
/vue-tsc@2.0.6(typescript@5.4.3):
|
||||
resolution: {integrity: sha512-kK50W4XqQL34vHRkxlRWLicrT6+F9xfgCgJ4KSmCHcytKzc1u3c94XXgI+CjmhOSxyw0krpExF7Obo7y4+0dVQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
dependencies:
|
||||
'@volar/typescript': 2.1.6
|
||||
'@vue/language-core': 2.0.6(typescript@5.4.3)
|
||||
semver: 7.6.0
|
||||
typescript: 5.4.3
|
||||
dev: true
|
||||
|
||||
/vue@2.7.14:
|
||||
resolution: {integrity: sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==}
|
||||
deprecated: Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.
|
||||
|
|
Loading…
Reference in a new issue