Merge branch 'fix/use-pagination-in-note' into 'develop'
fix: use pagination in note Co-authored-by: Lhcfl <Lhcfl@outlook.com> Closes #10906 See merge request firefish/firefish!10754
This commit is contained in:
commit
cac438b965
16 changed files with 311 additions and 229 deletions
|
@ -14,7 +14,7 @@
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"include": ["*.vue"],
|
"include": ["*.vue", "packages/client/*.ts"],
|
||||||
"linter": {
|
"linter": {
|
||||||
"rules": {
|
"rules": {
|
||||||
"style": {
|
"style": {
|
||||||
|
|
|
@ -5,6 +5,13 @@ Breaking changes are indicated by the :warning: icon.
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
- Added `antennaLimit` field to the response of `meta` and `admin/meta`, and the request of `admin/update-meta` (optional).
|
- Added `antennaLimit` field to the response of `meta` and `admin/meta`, and the request of `admin/update-meta` (optional).
|
||||||
|
- Added `filter` optional parameter to `notes/renotes` endpoint to filter the types of renotes. It can take the following values:
|
||||||
|
- `all` (default)
|
||||||
|
- `renote`
|
||||||
|
- `quote`
|
||||||
|
- :warning: Removed the following optional parameters in `notes/reactions`, as they were never taken into account due to a bug:
|
||||||
|
- `sinceId`
|
||||||
|
- `untilId`
|
||||||
|
|
||||||
## v20240413
|
## v20240413
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { In } from "typeorm";
|
import { In, IsNull, Not } from "typeorm";
|
||||||
import * as mfm from "mfm-js";
|
import * as mfm from "mfm-js";
|
||||||
import { Note } from "@/models/entities/note.js";
|
import { Note } from "@/models/entities/note.js";
|
||||||
import type { User } from "@/models/entities/user.js";
|
import type { User } from "@/models/entities/user.js";
|
||||||
|
@ -10,6 +10,7 @@ import {
|
||||||
Followings,
|
Followings,
|
||||||
Polls,
|
Polls,
|
||||||
Channels,
|
Channels,
|
||||||
|
Notes,
|
||||||
} from "../index.js";
|
} from "../index.js";
|
||||||
import type { Packed } from "@/misc/schema.js";
|
import type { Packed } from "@/misc/schema.js";
|
||||||
import { countReactions, decodeReaction, nyaify } from "backend-rs";
|
import { countReactions, decodeReaction, nyaify } from "backend-rs";
|
||||||
|
@ -101,7 +102,7 @@ export const NoteRepository = db.getRepository(Note).extend({
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// 指定されているかどうか
|
// 指定されているかどうか
|
||||||
return note.visibleUserIds.some((id: any) => meId === id);
|
return note.visibleUserIds.some((id) => meId === id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,8 +212,25 @@ export const NoteRepository = db.getRepository(Note).extend({
|
||||||
localOnly: note.localOnly || undefined,
|
localOnly: note.localOnly || undefined,
|
||||||
visibleUserIds:
|
visibleUserIds:
|
||||||
note.visibility === "specified" ? note.visibleUserIds : undefined,
|
note.visibility === "specified" ? note.visibleUserIds : undefined,
|
||||||
|
// FIXME: Deleting a post does not decrease these two numbers, causing the number to be wrong
|
||||||
renoteCount: note.renoteCount,
|
renoteCount: note.renoteCount,
|
||||||
repliesCount: note.repliesCount,
|
repliesCount: note.repliesCount,
|
||||||
|
// TODO: add it to database and use note.quoteCount
|
||||||
|
quoteCount: Notes.count({
|
||||||
|
where: {
|
||||||
|
renoteId: note.id,
|
||||||
|
text: Not(IsNull()),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
myRenoteCount: me
|
||||||
|
? Notes.count({
|
||||||
|
where: {
|
||||||
|
renoteId: note.id,
|
||||||
|
text: IsNull(),
|
||||||
|
userId: me.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
reactions: countReactions(note.reactions),
|
reactions: countReactions(note.reactions),
|
||||||
reactionEmojis: reactionEmoji,
|
reactionEmojis: reactionEmoji,
|
||||||
emojis: noteEmoji,
|
emojis: noteEmoji,
|
||||||
|
|
|
@ -208,5 +208,15 @@ export const packedNoteSchema = {
|
||||||
optional: true,
|
optional: true,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
},
|
},
|
||||||
|
myRenoteCount: {
|
||||||
|
type: "number",
|
||||||
|
optional: true,
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
|
quoteCount: {
|
||||||
|
type: "number",
|
||||||
|
optional: false,
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { SelectQueryBuilder } from "typeorm";
|
import type { ObjectLiteral, SelectQueryBuilder } from "typeorm";
|
||||||
|
|
||||||
export function makePaginationQuery<T>(
|
export function makePaginationQuery<T extends ObjectLiteral>(
|
||||||
q: SelectQueryBuilder<T>,
|
q: SelectQueryBuilder<T>,
|
||||||
sinceId?: string,
|
sinceId?: string,
|
||||||
untilId?: string,
|
untilId?: string,
|
||||||
|
|
|
@ -42,8 +42,6 @@ export const paramDef = {
|
||||||
type: { type: "string", nullable: true },
|
type: { type: "string", nullable: true },
|
||||||
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
|
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
|
||||||
offset: { type: "integer", default: 0 },
|
offset: { type: "integer", default: 0 },
|
||||||
sinceId: { type: "string", format: "misskey:id" },
|
|
||||||
untilId: { type: "string", format: "misskey:id" },
|
|
||||||
},
|
},
|
||||||
required: ["noteId"],
|
required: ["noteId"],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -42,6 +42,12 @@ export const paramDef = {
|
||||||
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
|
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
|
||||||
sinceId: { type: "string", format: "misskey:id" },
|
sinceId: { type: "string", format: "misskey:id" },
|
||||||
untilId: { type: "string", format: "misskey:id" },
|
untilId: { type: "string", format: "misskey:id" },
|
||||||
|
filter: {
|
||||||
|
type: "string",
|
||||||
|
enum: ["all", "renote", "quote"],
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
required: ["noteId"],
|
required: ["noteId"],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -53,7 +59,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
let query = makePaginationQuery(
|
const query = makePaginationQuery(
|
||||||
Notes.createQueryBuilder("note"),
|
Notes.createQueryBuilder("note"),
|
||||||
ps.sinceId,
|
ps.sinceId,
|
||||||
ps.untilId,
|
ps.untilId,
|
||||||
|
@ -61,6 +67,16 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
.andWhere("note.renoteId = :renoteId", { renoteId: note.id })
|
.andWhere("note.renoteId = :renoteId", { renoteId: note.id })
|
||||||
.innerJoinAndSelect("note.user", "user");
|
.innerJoinAndSelect("note.user", "user");
|
||||||
|
|
||||||
|
// "all" doesn't filter out anything, it's just there for
|
||||||
|
// those who prefer to set the parameter explicitly
|
||||||
|
|
||||||
|
if (ps.filter === "renote") {
|
||||||
|
query.andWhere("note.text IS NULL");
|
||||||
|
}
|
||||||
|
if (ps.filter === "quote") {
|
||||||
|
query.andWhere("note.text IS NOT NULL");
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.userId) {
|
if (ps.userId) {
|
||||||
query.andWhere("user.id = :userId", { userId: ps.userId });
|
query.andWhere("user.id = :userId", { userId: ps.userId });
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
@contextmenu.stop="onContextmenu"
|
@contextmenu.stop="onContextmenu"
|
||||||
></MkNote>
|
></MkNote>
|
||||||
|
|
||||||
<MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab">
|
<MkTab v-model="tab" :style="'underline'">
|
||||||
<option value="replies">
|
<option value="replies">
|
||||||
<!-- <i :class="icon('ph-arrow-u-up-left')"></i> -->
|
<!-- <i :class="icon('ph-arrow-u-up-left')"></i> -->
|
||||||
{{
|
{{
|
||||||
|
@ -64,11 +64,11 @@
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</option>
|
</option>
|
||||||
<option v-if="directQuotes && directQuotes.length > 0" value="quotes">
|
<option v-if="note.quoteCount > 0" value="quotes">
|
||||||
<!-- <i :class="icon('ph-quotes')"></i> -->
|
<!-- <i :class="icon('ph-quotes')"></i> -->
|
||||||
{{
|
{{
|
||||||
wordWithCount(
|
wordWithCount(
|
||||||
directQuotes.length,
|
note.quoteCount,
|
||||||
i18n.ts.quote,
|
i18n.ts.quote,
|
||||||
i18n.ts.quotes,
|
i18n.ts.quotes,
|
||||||
)
|
)
|
||||||
|
@ -80,44 +80,52 @@
|
||||||
</option>
|
</option>
|
||||||
</MkTab>
|
</MkTab>
|
||||||
|
|
||||||
|
<MkPagination
|
||||||
|
ref="repliesPagingComponent"
|
||||||
|
v-if="tab === 'replies' && note.repliesCount > 0"
|
||||||
|
v-slot="{ items }"
|
||||||
|
:pagination="repliesPagination"
|
||||||
|
>
|
||||||
<MkNoteSub
|
<MkNoteSub
|
||||||
v-for="note in directReplies"
|
v-for="note in items"
|
||||||
v-if="directReplies && tab === 'replies'"
|
|
||||||
:key="note.id"
|
:key="note.id"
|
||||||
:note="note"
|
:note="note"
|
||||||
class="reply"
|
class="reply"
|
||||||
:conversation="replies"
|
:auto-conversation="true"
|
||||||
:detailed-view="true"
|
:detailed-view="true"
|
||||||
:parent-id="note.id"
|
:parent-id="note.id"
|
||||||
|
:auto-add-replies="true"
|
||||||
/>
|
/>
|
||||||
<MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" />
|
</MkPagination>
|
||||||
|
|
||||||
|
<MkPagination
|
||||||
|
v-if="tab === 'quotes'"
|
||||||
|
v-slot="{ items }"
|
||||||
|
:pagination="quotePagination"
|
||||||
|
>
|
||||||
<MkNoteSub
|
<MkNoteSub
|
||||||
v-for="note in directQuotes"
|
v-for="note in items"
|
||||||
v-if="directQuotes && tab === 'quotes'"
|
|
||||||
:key="note.id"
|
:key="note.id"
|
||||||
:note="note"
|
:note="note"
|
||||||
class="reply"
|
class="reply"
|
||||||
:conversation="replies"
|
:auto-conversation="true"
|
||||||
:detailed-view="true"
|
:detailed-view="true"
|
||||||
:parent-id="note.id"
|
:parent-id="note.id"
|
||||||
|
:auto-add-replies="true"
|
||||||
/>
|
/>
|
||||||
<MkLoading v-else-if="tab === 'quotes' && directQuotes && directQuotes.length > 0" />
|
</MkPagination>
|
||||||
|
|
||||||
<!-- <MkPagination
|
<MkPagination
|
||||||
v-if="tab === 'renotes'"
|
v-if="tab === 'renotes'"
|
||||||
v-slot="{ items }"
|
v-slot="{ items }"
|
||||||
ref="pagingComponent"
|
:pagination="renotePagination"
|
||||||
:pagination="pagination"
|
>
|
||||||
> -->
|
|
||||||
<MkUserCardMini
|
<MkUserCardMini
|
||||||
v-for="item in renotes"
|
v-for="item in items"
|
||||||
v-if="tab === 'renotes' && renotes"
|
|
||||||
:key="item.user.id"
|
:key="item.user.id"
|
||||||
:user="item.user"
|
:user="item.user"
|
||||||
/>
|
/>
|
||||||
<!-- </MkPagination> -->
|
</MkPagination>
|
||||||
<MkLoading v-else-if="tab === 'renotes' && note.renoteCount > 0" />
|
|
||||||
|
|
||||||
<div v-if="tab === 'clips' && clips.length > 0" class="_content clips">
|
<div v-if="tab === 'clips' && clips.length > 0" class="_content clips">
|
||||||
<MkA
|
<MkA
|
||||||
|
@ -166,8 +174,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, onUnmounted, onUpdated, ref } from "vue";
|
import { onMounted, onUpdated, ref } from "vue";
|
||||||
import type { StreamTypes, entities } from "firefish-js";
|
import type { entities } from "firefish-js";
|
||||||
import MkTab from "@/components/MkTab.vue";
|
import MkTab from "@/components/MkTab.vue";
|
||||||
import MkNote from "@/components/MkNote.vue";
|
import MkNote from "@/components/MkNote.vue";
|
||||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
import MkNoteSub from "@/components/MkNoteSub.vue";
|
||||||
|
@ -185,7 +193,9 @@ import { i18n } from "@/i18n";
|
||||||
import { getNoteMenu } from "@/scripts/get-note-menu";
|
import { getNoteMenu } from "@/scripts/get-note-menu";
|
||||||
import { useNoteCapture } from "@/scripts/use-note-capture";
|
import { useNoteCapture } from "@/scripts/use-note-capture";
|
||||||
import { deepClone } from "@/scripts/clone";
|
import { deepClone } from "@/scripts/clone";
|
||||||
import { useStream } from "@/stream";
|
import MkPagination, {
|
||||||
|
type MkPaginationType,
|
||||||
|
} from "@/components/MkPagination.vue";
|
||||||
// import icon from "@/scripts/icon";
|
// import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -193,8 +203,6 @@ const props = defineProps<{
|
||||||
pinned?: boolean;
|
pinned?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const stream = useStream();
|
|
||||||
|
|
||||||
const tab = ref("replies");
|
const tab = ref("replies");
|
||||||
|
|
||||||
const note = ref(deepClone(props.note));
|
const note = ref(deepClone(props.note));
|
||||||
|
@ -225,6 +233,10 @@ if (noteViewInterruptors.length > 0) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const repliesPagingComponent = ref<MkPaginationType<"notes/replies"> | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
const el = ref<HTMLElement | null>(null);
|
const el = ref<HTMLElement | null>(null);
|
||||||
const noteEl = ref();
|
const noteEl = ref();
|
||||||
const menuButton = ref<HTMLElement>();
|
const menuButton = ref<HTMLElement>();
|
||||||
|
@ -243,11 +255,7 @@ const muted = ref(
|
||||||
const translation = ref(null);
|
const translation = ref(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
const conversation = ref<null | entities.Note[]>([]);
|
const conversation = ref<null | entities.Note[]>([]);
|
||||||
const replies = ref<entities.Note[]>([]);
|
|
||||||
const directReplies = ref<null | entities.Note[]>([]);
|
|
||||||
const directQuotes = ref<null | entities.Note[]>([]);
|
|
||||||
const clips = ref();
|
const clips = ref();
|
||||||
const renotes = ref();
|
|
||||||
const isRenote = ref(note.value.renoteId != null);
|
const isRenote = ref(note.value.renoteId != null);
|
||||||
let isScrolling: boolean;
|
let isScrolling: boolean;
|
||||||
|
|
||||||
|
@ -269,6 +277,10 @@ useNoteCapture({
|
||||||
rootEl: el,
|
rootEl: el,
|
||||||
note,
|
note,
|
||||||
isDeletedRef: isDeleted,
|
isDeletedRef: isDeleted,
|
||||||
|
onReplied: (replyNote) => {
|
||||||
|
note.value.repliesCount += 1;
|
||||||
|
repliesPagingComponent.value?.append(replyNote);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function reply(_viaKeyboard = false): void {
|
function reply(_viaKeyboard = false): void {
|
||||||
|
@ -357,32 +369,6 @@ function blur() {
|
||||||
noteEl.value.blur();
|
noteEl.value.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
directReplies.value = null;
|
|
||||||
os.api("notes/children", {
|
|
||||||
noteId: note.value.id,
|
|
||||||
limit: 30,
|
|
||||||
depth: 12,
|
|
||||||
}).then((res) => {
|
|
||||||
// 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)
|
|
||||||
.reverse();
|
|
||||||
directQuotes.value = res.filter(
|
|
||||||
(resNote) => resNote.renoteId === note.value.id,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
conversation.value = null;
|
conversation.value = null;
|
||||||
if (note.value.replyId) {
|
if (note.value.replyId) {
|
||||||
os.api("notes/conversation", {
|
os.api("notes/conversation", {
|
||||||
|
@ -401,77 +387,37 @@ os.api("notes/clips", {
|
||||||
clips.value = res;
|
clips.value = res;
|
||||||
});
|
});
|
||||||
|
|
||||||
// const pagination = {
|
const repliesPagination = {
|
||||||
// endpoint: "notes/renotes",
|
endpoint: "notes/replies" as const,
|
||||||
// noteId: note.id,
|
limit: 10,
|
||||||
// limit: 10,
|
params: {
|
||||||
// };
|
|
||||||
|
|
||||||
// const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
|
|
||||||
|
|
||||||
renotes.value = null;
|
|
||||||
function loadTab() {
|
|
||||||
if (tab.value === "renotes" && !renotes.value) {
|
|
||||||
os.api("notes/renotes", {
|
|
||||||
noteId: note.value.id,
|
noteId: note.value.id,
|
||||||
limit: 100,
|
},
|
||||||
}).then((res) => {
|
ascending: true,
|
||||||
renotes.value = res;
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onNoteUpdated(
|
const renotePagination = {
|
||||||
noteData: StreamTypes.NoteUpdatedEvent,
|
endpoint: "notes/renotes" as const,
|
||||||
): Promise<void> {
|
limit: 30,
|
||||||
const { type, id, body } = noteData;
|
params: {
|
||||||
|
noteId: note.value.id,
|
||||||
let found = -1;
|
filter: "renote" as const,
|
||||||
if (id === note.value.id) {
|
},
|
||||||
found = 0;
|
};
|
||||||
} else {
|
const quotePagination = {
|
||||||
for (let i = 0; i < replies.value.length; i++) {
|
endpoint: "notes/renotes" as const,
|
||||||
const reply = replies.value[i];
|
limit: 30,
|
||||||
if (reply.id === id) {
|
params: {
|
||||||
found = i + 1;
|
noteId: note.value.id,
|
||||||
break;
|
filter: "quote" as const,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (found === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case "replied": {
|
|
||||||
const { id: createdId } = body;
|
|
||||||
const replyNote = await os.api("notes/show", {
|
|
||||||
noteId: createdId,
|
|
||||||
});
|
|
||||||
|
|
||||||
replies.value.splice(found, 0, replyNote);
|
|
||||||
if (found === 0) {
|
|
||||||
directReplies.value!.push(replyNote);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "deleted":
|
|
||||||
if (found === 0) {
|
|
||||||
isDeleted.value = true;
|
|
||||||
} else {
|
|
||||||
replies.value.splice(found - 1, 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("wheel", () => {
|
document.addEventListener("wheel", () => {
|
||||||
isScrolling = true;
|
isScrolling = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
stream.on("noteUpdated", onNoteUpdated);
|
|
||||||
isScrolling = false;
|
isScrolling = false;
|
||||||
noteEl.value.scrollIntoView();
|
noteEl.value.scrollIntoView();
|
||||||
});
|
});
|
||||||
|
@ -484,10 +430,6 @@ onUpdated(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
stream.off("noteUpdated", onNoteUpdated);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
|
<MkLoading v-if="conversationLoading" />
|
||||||
<article
|
<article
|
||||||
v-if="!muted.muted || muted.what === 'reply'"
|
v-else-if="!muted.muted || muted.what === 'reply'"
|
||||||
:id="detailedView ? appearNote.id : undefined"
|
:id="detailedView ? appearNote.id : undefined"
|
||||||
ref="el"
|
ref="el"
|
||||||
v-size="{ max: [450, 500] }"
|
v-size="{ max: [450, 500] }"
|
||||||
|
@ -22,7 +23,7 @@
|
||||||
<div class="avatar-container">
|
<div class="avatar-container">
|
||||||
<MkAvatar class="avatar" :user="appearNote.user" />
|
<MkAvatar class="avatar" :user="appearNote.user" />
|
||||||
<div
|
<div
|
||||||
v-if="!conversation || replies.length > 0"
|
v-if="conversation == null || replies.length > 0"
|
||||||
class="line"
|
class="line"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -148,10 +149,13 @@
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="conversation">
|
<MkLoading v-if="conversationLoading" />
|
||||||
|
<template v-else-if="conversation">
|
||||||
|
<template
|
||||||
|
v-if="replyLevel < REPLY_LEVEL_UPPERBOUND && depth < DEPTH_UPPERBOUND"
|
||||||
|
>
|
||||||
<MkNoteSub
|
<MkNoteSub
|
||||||
v-for="reply in replies"
|
v-for="reply in replies.slice(0, REPLIES_LIMIT)"
|
||||||
v-if="replyLevel < 11 && depth < 5"
|
|
||||||
:key="reply.id"
|
:key="reply.id"
|
||||||
:note="reply"
|
:note="reply"
|
||||||
class="reply"
|
class="reply"
|
||||||
|
@ -161,7 +165,16 @@
|
||||||
:reply-level="replyLevel + 1"
|
:reply-level="replyLevel + 1"
|
||||||
:parent-id="appearNote.id"
|
:parent-id="appearNote.id"
|
||||||
:detailed-view="detailedView"
|
:detailed-view="detailedView"
|
||||||
|
:auto-add-replies="true"
|
||||||
/>
|
/>
|
||||||
|
<div v-if="hasMoreReplies" class="more">
|
||||||
|
<div class="line"></div>
|
||||||
|
<MkA class="text _link" :to="notePage(note)"
|
||||||
|
>{{ i18n.ts.continueThread }}
|
||||||
|
<i :class="icon('ph-caret-double-right')"></i
|
||||||
|
></MkA>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<div v-else-if="replies.length > 0" class="more">
|
<div v-else-if="replies.length > 0" class="more">
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
<MkA class="text _link" :to="notePage(note)"
|
<MkA class="text _link" :to="notePage(note)"
|
||||||
|
@ -190,7 +203,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, ref } from "vue";
|
import { computed, inject, ref, watch } from "vue";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import type { entities } from "firefish-js";
|
import type { entities } from "firefish-js";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
|
@ -221,21 +234,27 @@ import type { NoteTranslation } from "@/types/note";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const REPLIES_LIMIT = 10;
|
||||||
|
const REPLY_LEVEL_UPPERBOUND = 11;
|
||||||
|
const DEPTH_UPPERBOUND = 5;
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
note: entities.Note;
|
note: entities.Note;
|
||||||
conversation?: entities.Note[];
|
conversation?: entities.Note[];
|
||||||
parentId?;
|
autoConversation?: boolean;
|
||||||
detailedView?;
|
parentId?: string;
|
||||||
|
detailedView?: boolean;
|
||||||
// how many notes are in between this one and the note being viewed in detail
|
// how many notes are in between this one and the note being viewed in detail
|
||||||
depth?: number;
|
depth?: number;
|
||||||
// the actual reply level of this note within the conversation thread
|
// the actual reply level of this note within the conversation thread
|
||||||
replyLevel?: number;
|
replyLevel?: number;
|
||||||
|
autoAddReplies?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
depth: 1,
|
depth: 1,
|
||||||
replyLevel: 1,
|
replyLevel: 1,
|
||||||
|
autoAddReplies: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -251,6 +270,43 @@ const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
return i18n.ts.userSaysSomething;
|
return i18n.ts.userSaysSomething;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const conversation = ref(props.conversation);
|
||||||
|
const conversationLoading = ref(false);
|
||||||
|
const replies = ref<entities.Note[]>([]);
|
||||||
|
const hasMoreReplies = ref(false);
|
||||||
|
|
||||||
|
function updateReplies() {
|
||||||
|
replies.value = (conversation.value ?? [])
|
||||||
|
.filter(
|
||||||
|
(item) =>
|
||||||
|
item.replyId === props.note.id || item.renoteId === props.note.id,
|
||||||
|
)
|
||||||
|
.reverse();
|
||||||
|
hasMoreReplies.value = replies.value.length >= REPLIES_LIMIT + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(conversation, updateReplies, {
|
||||||
|
immediate: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (props.autoConversation) {
|
||||||
|
conversation.value = [];
|
||||||
|
if (note.value.repliesCount > 0 || note.value.renoteCount > 0) {
|
||||||
|
conversationLoading.value = true;
|
||||||
|
os.api("notes/children", {
|
||||||
|
noteId: note.value.id,
|
||||||
|
limit: REPLIES_LIMIT + 1,
|
||||||
|
depth: REPLY_LEVEL_UPPERBOUND + 1,
|
||||||
|
}).then((res) => {
|
||||||
|
conversation.value = res
|
||||||
|
.filter((n) => n.userId !== note.value.userId)
|
||||||
|
.reverse()
|
||||||
|
.concat(res.filter((n) => n.userId === note.value.userId));
|
||||||
|
conversationLoading.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const isRenote =
|
const isRenote =
|
||||||
note.value.renote != null &&
|
note.value.renote != null &&
|
||||||
note.value.text == null &&
|
note.value.text == null &&
|
||||||
|
@ -277,13 +333,6 @@ const muted = ref(
|
||||||
);
|
);
|
||||||
const translation = ref<NoteTranslation | null>(null);
|
const translation = ref<NoteTranslation | null>(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
const replies: entities.Note[] =
|
|
||||||
props.conversation
|
|
||||||
?.filter(
|
|
||||||
(item) =>
|
|
||||||
item.replyId === props.note.id || item.renoteId === props.note.id,
|
|
||||||
)
|
|
||||||
.reverse() ?? [];
|
|
||||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||||
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
||||||
const lang = localStorage.getItem("lang");
|
const lang = localStorage.getItem("lang");
|
||||||
|
@ -329,6 +378,14 @@ useNoteCapture({
|
||||||
rootEl: el,
|
rootEl: el,
|
||||||
note: appearNote,
|
note: appearNote,
|
||||||
isDeletedRef: isDeleted,
|
isDeletedRef: isDeleted,
|
||||||
|
onReplied: (note) => {
|
||||||
|
if (props.autoAddReplies !== true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasMoreReplies.value === false) {
|
||||||
|
conversation.value = (conversation.value ?? []).concat([note]);
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function reply(_viaKeyboard = false): void {
|
function reply(_viaKeyboard = false): void {
|
||||||
|
|
|
@ -68,7 +68,15 @@
|
||||||
|
|
||||||
<script lang="ts" setup generic="E extends PagingKey">
|
<script lang="ts" setup generic="E extends PagingKey">
|
||||||
import type { ComponentPublicInstance, ComputedRef } from "vue";
|
import type { ComponentPublicInstance, ComputedRef } from "vue";
|
||||||
import { computed, isRef, onActivated, onDeactivated, ref, watch } from "vue";
|
import {
|
||||||
|
computed,
|
||||||
|
isRef,
|
||||||
|
onActivated,
|
||||||
|
onDeactivated,
|
||||||
|
ref,
|
||||||
|
unref,
|
||||||
|
watch,
|
||||||
|
} from "vue";
|
||||||
import type { Endpoints, TypeUtils } from "firefish-js";
|
import type { Endpoints, TypeUtils } from "firefish-js";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import {
|
import {
|
||||||
|
@ -122,6 +130,12 @@ export interface Paging<E extends PagingKey = PagingKey> {
|
||||||
*/
|
*/
|
||||||
reversed?: boolean;
|
reversed?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For not-reversed, not-offsetMode,
|
||||||
|
* Sort by id in ascending order
|
||||||
|
*/
|
||||||
|
ascending?: boolean;
|
||||||
|
|
||||||
offsetMode?: boolean;
|
offsetMode?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,17 +183,19 @@ const init = async (): Promise<void> => {
|
||||||
queue.value = [];
|
queue.value = [];
|
||||||
fetching.value = true;
|
fetching.value = true;
|
||||||
|
|
||||||
const params = props.pagination.params
|
const params = props.pagination.params ? unref(props.pagination.params) : {};
|
||||||
? isRef<Param>(props.pagination.params)
|
|
||||||
? props.pagination.params.value
|
|
||||||
: props.pagination.params
|
|
||||||
: {};
|
|
||||||
await os
|
await os
|
||||||
.api(props.pagination.endpoint, {
|
.api(props.pagination.endpoint, {
|
||||||
...params,
|
...params,
|
||||||
limit: props.pagination.noPaging
|
limit: props.pagination.noPaging
|
||||||
? props.pagination.limit || 10
|
? props.pagination.limit || 10
|
||||||
: (props.pagination.limit || 10) + 1,
|
: (props.pagination.limit || 10) + 1,
|
||||||
|
...(props.pagination.ascending
|
||||||
|
? {
|
||||||
|
// An initial value smaller than all possible ids must be filled in here.
|
||||||
|
sinceId: "0",
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
(res: Item[]) => {
|
(res: Item[]) => {
|
||||||
|
@ -196,10 +212,10 @@ const init = async (): Promise<void> => {
|
||||||
res.length > (props.pagination.limit || 10)
|
res.length > (props.pagination.limit || 10)
|
||||||
) {
|
) {
|
||||||
res.pop();
|
res.pop();
|
||||||
items.value = props.pagination.reversed ? [...res].reverse() : res;
|
items.value = props.pagination.reversed ? res.toReversed() : res;
|
||||||
more.value = true;
|
more.value = true;
|
||||||
} else {
|
} else {
|
||||||
items.value = props.pagination.reversed ? [...res].reverse() : res;
|
items.value = props.pagination.reversed ? res.toReversed() : res;
|
||||||
more.value = false;
|
more.value = false;
|
||||||
}
|
}
|
||||||
offset.value = res.length;
|
offset.value = res.length;
|
||||||
|
@ -219,11 +235,7 @@ const reload = (): Promise<void> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const refresh = async (): Promise<void> => {
|
const refresh = async (): Promise<void> => {
|
||||||
const params = props.pagination.params
|
const params = props.pagination.params ? unref(props.pagination.params) : {};
|
||||||
? isRef<Param>(props.pagination.params)
|
|
||||||
? props.pagination.params.value
|
|
||||||
: props.pagination.params
|
|
||||||
: {};
|
|
||||||
await os
|
await os
|
||||||
.api(props.pagination.endpoint, {
|
.api(props.pagination.endpoint, {
|
||||||
...params,
|
...params,
|
||||||
|
@ -269,11 +281,7 @@ const fetchMore = async (): Promise<void> => {
|
||||||
return;
|
return;
|
||||||
moreFetching.value = true;
|
moreFetching.value = true;
|
||||||
backed.value = true;
|
backed.value = true;
|
||||||
const params = props.pagination.params
|
const params = props.pagination.params ? unref(props.pagination.params) : {};
|
||||||
? isRef<Param>(props.pagination.params)
|
|
||||||
? props.pagination.params.value
|
|
||||||
: props.pagination.params
|
|
||||||
: {};
|
|
||||||
await os
|
await os
|
||||||
.api(props.pagination.endpoint, {
|
.api(props.pagination.endpoint, {
|
||||||
...params,
|
...params,
|
||||||
|
@ -286,6 +294,10 @@ const fetchMore = async (): Promise<void> => {
|
||||||
? {
|
? {
|
||||||
sinceId: items.value[0].id,
|
sinceId: items.value[0].id,
|
||||||
}
|
}
|
||||||
|
: props.pagination.ascending
|
||||||
|
? {
|
||||||
|
sinceId: items.value[items.value.length - 1].id,
|
||||||
|
}
|
||||||
: {
|
: {
|
||||||
untilId: items.value[items.value.length - 1].id,
|
untilId: items.value[items.value.length - 1].id,
|
||||||
}),
|
}),
|
||||||
|
@ -303,12 +315,12 @@ const fetchMore = async (): Promise<void> => {
|
||||||
if (res.length > SECOND_FETCH_LIMIT) {
|
if (res.length > SECOND_FETCH_LIMIT) {
|
||||||
res.pop();
|
res.pop();
|
||||||
items.value = props.pagination.reversed
|
items.value = props.pagination.reversed
|
||||||
? [...res].reverse().concat(items.value)
|
? res.toReversed().concat(items.value)
|
||||||
: items.value.concat(res);
|
: items.value.concat(res);
|
||||||
more.value = true;
|
more.value = true;
|
||||||
} else {
|
} else {
|
||||||
items.value = props.pagination.reversed
|
items.value = props.pagination.reversed
|
||||||
? [...res].reverse().concat(items.value)
|
? res.toReversed().concat(items.value)
|
||||||
: items.value.concat(res);
|
: items.value.concat(res);
|
||||||
more.value = false;
|
more.value = false;
|
||||||
}
|
}
|
||||||
|
@ -330,11 +342,7 @@ const fetchMoreAhead = async (): Promise<void> => {
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
moreFetching.value = true;
|
moreFetching.value = true;
|
||||||
const params = props.pagination.params
|
const params = props.pagination.params ? unref(props.pagination.params) : {};
|
||||||
? isRef<Param>(props.pagination.params)
|
|
||||||
? props.pagination.params.value
|
|
||||||
: props.pagination.params
|
|
||||||
: {};
|
|
||||||
await os
|
await os
|
||||||
.api(props.pagination.endpoint, {
|
.api(props.pagination.endpoint, {
|
||||||
...params,
|
...params,
|
||||||
|
@ -356,12 +364,12 @@ const fetchMoreAhead = async (): Promise<void> => {
|
||||||
if (res.length > SECOND_FETCH_LIMIT) {
|
if (res.length > SECOND_FETCH_LIMIT) {
|
||||||
res.pop();
|
res.pop();
|
||||||
items.value = props.pagination.reversed
|
items.value = props.pagination.reversed
|
||||||
? [...res].reverse().concat(items.value)
|
? res.toReversed().concat(items.value)
|
||||||
: items.value.concat(res);
|
: items.value.concat(res);
|
||||||
more.value = true;
|
more.value = true;
|
||||||
} else {
|
} else {
|
||||||
items.value = props.pagination.reversed
|
items.value = props.pagination.reversed
|
||||||
? [...res].reverse().concat(items.value)
|
? res.toReversed().concat(items.value)
|
||||||
: items.value.concat(res);
|
: items.value.concat(res);
|
||||||
more.value = false;
|
more.value = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,13 @@
|
||||||
}}</span>
|
}}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<MkUserCardMini v-for="user in users" :key="user.id" :user="user" />
|
<MkPagination
|
||||||
|
ref="pagingComponent"
|
||||||
|
:pagination="pagination"
|
||||||
|
v-slot="{ items }"
|
||||||
|
>
|
||||||
|
<MkUserCardMini v-for="{ user: user } in items" :key="user.id" :user="user" />
|
||||||
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<MkLoading />
|
<MkLoading />
|
||||||
|
@ -36,6 +42,9 @@ import type { entities } from "firefish-js";
|
||||||
import MkReactionIcon from "@/components/MkReactionIcon.vue";
|
import MkReactionIcon from "@/components/MkReactionIcon.vue";
|
||||||
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
import MkPagination, {
|
||||||
|
type MkPaginationType,
|
||||||
|
} from "@/components/MkPagination.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
noteId: entities.Note["id"];
|
noteId: entities.Note["id"];
|
||||||
|
@ -44,16 +53,22 @@ const props = defineProps<{
|
||||||
const note = ref<entities.Note>();
|
const note = ref<entities.Note>();
|
||||||
const tab = ref<string | null>(null);
|
const tab = ref<string | null>(null);
|
||||||
const reactions = ref<string[]>();
|
const reactions = ref<string[]>();
|
||||||
const users = ref();
|
|
||||||
|
|
||||||
async function updateUsers(): void {
|
const pagingComponent = ref<MkPaginationType<"notes/reactions"> | null>(null);
|
||||||
const res = await os.api("notes/reactions", {
|
|
||||||
|
const pagination = {
|
||||||
|
endpoint: "notes/reactions" as const,
|
||||||
|
params: {
|
||||||
noteId: props.noteId,
|
noteId: props.noteId,
|
||||||
type: tab.value,
|
type: tab.value,
|
||||||
|
},
|
||||||
|
offsetMode: true,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
});
|
};
|
||||||
|
|
||||||
users.value = res.map((x) => x.user);
|
function updateUsers(): void {
|
||||||
|
pagination.params.type = tab.value;
|
||||||
|
pagingComponent.value?.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(tab, updateUsers);
|
watch(tab, updateUsers);
|
||||||
|
@ -64,7 +79,7 @@ onMounted(() => {
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
reactions.value = Object.keys(res.reactions);
|
reactions.value = Object.keys(res.reactions);
|
||||||
note.value = res;
|
note.value = res;
|
||||||
await updateUsers();
|
// updateUsers();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -27,7 +27,7 @@ import Ripple from "@/components/MkRipple.vue";
|
||||||
import XDetails from "@/components/MkUsersTooltip.vue";
|
import XDetails from "@/components/MkUsersTooltip.vue";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { isSignedIn, me } from "@/me";
|
import { me } from "@/me";
|
||||||
import { useTooltip } from "@/scripts/use-tooltip";
|
import { useTooltip } from "@/scripts/use-tooltip";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
|
@ -72,17 +72,9 @@ useTooltip(buttonRef, async (showing) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasRenotedBefore = ref(false);
|
const hasRenotedBefore = ref(
|
||||||
|
props.note.myRenoteCount && props.note.myRenoteCount > 0,
|
||||||
if (isSignedIn(me)) {
|
);
|
||||||
os.api("notes/renotes", {
|
|
||||||
noteId: props.note.id,
|
|
||||||
userId: me.id,
|
|
||||||
limit: 1,
|
|
||||||
}).then((res) => {
|
|
||||||
hasRenotedBefore.value = res.length > 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<p v-if="note.cw != null" class="cw">
|
<p v-if="note.cw != null" class="cw">
|
||||||
<MkA
|
<MkA
|
||||||
v-if="conversation && note.renoteId == parentId"
|
v-if="conversation && note.renoteId == parentId && parentId != null"
|
||||||
:to="
|
:to="
|
||||||
detailedView ? `#${parentId}` : `${notePage(note)}#${parentId}`
|
detailedView ? `#${parentId}` : `${notePage(note)}#${parentId}`
|
||||||
"
|
"
|
||||||
|
@ -198,8 +198,8 @@ import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: entities.Note;
|
note: entities.Note;
|
||||||
parentId?;
|
parentId?: string;
|
||||||
conversation?;
|
conversation?: entities.Note[];
|
||||||
detailed?: boolean;
|
detailed?: boolean;
|
||||||
detailedView?: boolean;
|
detailedView?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
|
@ -9,6 +9,7 @@ export function useNoteCapture(props: {
|
||||||
rootEl: Ref<HTMLElement | null>;
|
rootEl: Ref<HTMLElement | null>;
|
||||||
note: Ref<entities.Note>;
|
note: Ref<entities.Note>;
|
||||||
isDeletedRef: Ref<boolean>;
|
isDeletedRef: Ref<boolean>;
|
||||||
|
onReplied?: (note: entities.Note) => void;
|
||||||
}) {
|
}) {
|
||||||
const note = props.note;
|
const note = props.note;
|
||||||
const connection = isSignedIn(me) ? useStream() : null;
|
const connection = isSignedIn(me) ? useStream() : null;
|
||||||
|
@ -19,6 +20,16 @@ export function useNoteCapture(props: {
|
||||||
if (id !== note.value.id) return;
|
if (id !== note.value.id) return;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case "replied": {
|
||||||
|
if (props.onReplied) {
|
||||||
|
const { id: createdId } = body;
|
||||||
|
const replyNote = await os.api("notes/show", {
|
||||||
|
noteId: createdId,
|
||||||
|
});
|
||||||
|
props.onReplied(replyNote);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "reacted": {
|
case "reacted": {
|
||||||
const reaction = body.reaction;
|
const reaction = body.reaction;
|
||||||
|
|
||||||
|
|
|
@ -773,7 +773,12 @@ export type Endpoints = {
|
||||||
res: null;
|
res: null;
|
||||||
};
|
};
|
||||||
"notes/reactions": {
|
"notes/reactions": {
|
||||||
req: { noteId: Note["id"]; type?: string | null; limit?: number };
|
req: {
|
||||||
|
noteId: Note["id"];
|
||||||
|
type?: string | null;
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
};
|
||||||
res: NoteReaction[];
|
res: NoteReaction[];
|
||||||
};
|
};
|
||||||
"notes/reactions/create": {
|
"notes/reactions/create": {
|
||||||
|
@ -787,6 +792,7 @@ export type Endpoints = {
|
||||||
sinceId?: Note["id"];
|
sinceId?: Note["id"];
|
||||||
untilId?: Note["id"];
|
untilId?: Note["id"];
|
||||||
noteId: Note["id"];
|
noteId: Note["id"];
|
||||||
|
filter?: "all" | "renote" | "quote";
|
||||||
};
|
};
|
||||||
res: Note[];
|
res: Note[];
|
||||||
};
|
};
|
||||||
|
|
|
@ -174,9 +174,11 @@ export type Note = {
|
||||||
channelId?: Channel["id"];
|
channelId?: Channel["id"];
|
||||||
channel?: Channel;
|
channel?: Channel;
|
||||||
myReaction?: string;
|
myReaction?: string;
|
||||||
|
myRenoteCount?: number;
|
||||||
reactions: Record<string, number>;
|
reactions: Record<string, number>;
|
||||||
renoteCount: number;
|
renoteCount: number;
|
||||||
repliesCount: number;
|
repliesCount: number;
|
||||||
|
quoteCount: number;
|
||||||
poll?: {
|
poll?: {
|
||||||
expiresAt: DateString | null;
|
expiresAt: DateString | null;
|
||||||
multiple: boolean;
|
multiple: boolean;
|
||||||
|
|
Loading…
Reference in a new issue