diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index c877048709..5974fc24af 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -1,4 +1,4 @@ -import { In } from "typeorm"; +import { In, IsNull, Not } from "typeorm"; import * as mfm from "mfm-js"; import { Note } from "@/models/entities/note.js"; import type { User } from "@/models/entities/user.js"; @@ -10,6 +10,7 @@ import { Followings, Polls, Channels, + Notes, } from "../index.js"; import type { Packed } from "@/misc/schema.js"; import { countReactions, decodeReaction, nyaify } from "backend-rs"; @@ -101,7 +102,7 @@ export const NoteRepository = db.getRepository(Note).extend({ return true; } 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, visibleUserIds: 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, repliesCount: note.repliesCount, + // TODO: add it to database and use note.quoteCount + quoteCount: Notes.count({ + where: { + renoteId: note.id, + text: Not(IsNull()), + }, + }), + meRenoteCount: me + ? Notes.count({ + where: { + renoteId: note.id, + text: IsNull(), + userId: me.id, + }, + }) + : undefined, reactions: countReactions(note.reactions), reactionEmojis: reactionEmoji, emojis: noteEmoji, diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts index fff872b69f..e0048f7d00 100644 --- a/packages/backend/src/models/schema/note.ts +++ b/packages/backend/src/models/schema/note.ts @@ -208,5 +208,15 @@ export const packedNoteSchema = { optional: true, nullable: true, }, + meRenoteCount: { + type: "number", + optional: true, + nullable: false, + }, + quoteCount: { + type: "number", + optional: false, + nullable: false, + }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index 386a3a08df..d6ff8888af 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -42,8 +42,6 @@ export const paramDef = { type: { type: "string", nullable: true }, limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, offset: { type: "integer", default: 0 }, - sinceId: { type: "string", format: "misskey:id" }, - untilId: { type: "string", format: "misskey:id" }, }, required: ["noteId"], } as const; diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 683004ebe8..282777b2e4 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -42,6 +42,12 @@ export const paramDef = { limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, sinceId: { type: "string", format: "misskey:id" }, untilId: { type: "string", format: "misskey:id" }, + filter: { + type: "string", + enum: ["boost", "quote"], + nullable: true, + default: null, + }, }, required: ["noteId"], } as const; @@ -53,7 +59,7 @@ export default define(meta, paramDef, async (ps, user) => { throw err; }); - let query = makePaginationQuery( + const query = makePaginationQuery( Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId, @@ -61,6 +67,13 @@ export default define(meta, paramDef, async (ps, user) => { .andWhere("note.renoteId = :renoteId", { renoteId: note.id }) .innerJoinAndSelect("note.user", "user"); + if (ps.filter === "boost") { + query.andWhere("note.text IS NULL"); + } + if (ps.filter === "quote") { + query.andWhere("note.text IS NOT NULL"); + } + if (ps.userId) { query.andWhere("user.id = :userId", { userId: ps.userId }); } diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index b6c0d784ba..71e4aeb783 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -64,11 +64,11 @@ ) }} - + {{ wordWithCount( - directQuotes.length, + note.quoteCount, i18n.ts.quote, i18n.ts.quotes, ) @@ -92,32 +92,33 @@ /> - - + + + - - - - + :pagination="renotePagination" + > + + ([]); const directReplies = ref([]); const directQuotes = ref([]); const clips = ref(); -const renotes = ref(); const isRenote = ref(note.value.renoteId != null); let isScrolling: boolean; @@ -401,24 +402,32 @@ os.api("notes/clips", { clips.value = res; }); -// const pagination = { -// endpoint: "notes/renotes", -// noteId: note.id, -// limit: 10, -// }; +const renotePagination = { + endpoint: "notes/renotes" as const, + limit: 30, + params: { + noteId: note.value.id, + filter: "boost" as const, + }, +}; +const quotePagination = { + endpoint: "notes/renotes" as const, + limit: 30, + params: { + noteId: note.value.id, + filter: "quote" as const, + }, +}; -// const pagingComponent = $ref>(); - -renotes.value = null; function loadTab() { - if (tab.value === "renotes" && !renotes.value) { - os.api("notes/renotes", { - noteId: note.value.id, - limit: 100, - }).then((res) => { - renotes.value = res; - }); - } + // if (tab.value === "renotes" && !renotes.value) { + // os.api("notes/renotes", { + // noteId: note.value.id, + // limit: 100, + // }).then((res) => { + // renotes.value = res; + // }); + // } } async function onNoteUpdated( diff --git a/packages/client/src/components/MkReactedUsers.vue b/packages/client/src/components/MkReactedUsers.vue index ae7a1d6d7d..76b86ed46f 100644 --- a/packages/client/src/components/MkReactedUsers.vue +++ b/packages/client/src/components/MkReactedUsers.vue @@ -23,7 +23,13 @@ }} - + + + @@ -36,6 +42,9 @@ import type { entities } from "firefish-js"; import MkReactionIcon from "@/components/MkReactionIcon.vue"; import MkUserCardMini from "@/components/MkUserCardMini.vue"; import * as os from "@/os"; +import MkPagination, { + type MkPaginationType, +} from "@/components/MkPagination.vue"; const props = defineProps<{ noteId: entities.Note["id"]; @@ -44,16 +53,22 @@ const props = defineProps<{ const note = ref(); const tab = ref(null); const reactions = ref(); -const users = ref(); -async function updateUsers(): void { - const res = await os.api("notes/reactions", { +const pagingComponent = ref | null>(null); + +const pagination = { + endpoint: "notes/reactions" as const, + params: { noteId: props.noteId, type: tab.value, - limit: 30, - }); + }, + offsetMode: true, + limit: 30, +}; - users.value = res.map((x) => x.user); +function updateUsers(): void { + pagination.params.type = tab.value; + pagingComponent.value?.reload(); } watch(tab, updateUsers); @@ -64,7 +79,7 @@ onMounted(() => { }).then(async (res) => { reactions.value = Object.keys(res.reactions); note.value = res; - await updateUsers(); + // updateUsers(); }); }); diff --git a/packages/client/src/components/MkRenoteButton.vue b/packages/client/src/components/MkRenoteButton.vue index 7250757da4..23911eef37 100644 --- a/packages/client/src/components/MkRenoteButton.vue +++ b/packages/client/src/components/MkRenoteButton.vue @@ -27,7 +27,7 @@ import Ripple from "@/components/MkRipple.vue"; import XDetails from "@/components/MkUsersTooltip.vue"; import { pleaseLogin } from "@/scripts/please-login"; import * as os from "@/os"; -import { isSignedIn, me } from "@/me"; +import { me } from "@/me"; import { useTooltip } from "@/scripts/use-tooltip"; import { i18n } from "@/i18n"; import { defaultStore } from "@/store"; @@ -72,17 +72,9 @@ useTooltip(buttonRef, async (showing) => { ); }); -const hasRenotedBefore = ref(false); - -if (isSignedIn) { - os.api("notes/renotes", { - noteId: props.note.id, - userId: me!.id, - limit: 1, - }).then((res) => { - hasRenotedBefore.value = res.length > 0; - }); -} +const hasRenotedBefore = ref( + props.note.meRenoteCount && props.note.meRenoteCount > 0, +); const renote = (viaKeyboard = false, ev?: MouseEvent) => { pleaseLogin(); diff --git a/packages/firefish-js/src/api.types.ts b/packages/firefish-js/src/api.types.ts index c0a7c9019b..6c0e11bec5 100644 --- a/packages/firefish-js/src/api.types.ts +++ b/packages/firefish-js/src/api.types.ts @@ -773,7 +773,12 @@ export type Endpoints = { res: null; }; "notes/reactions": { - req: { noteId: Note["id"]; type?: string | null; limit?: number }; + req: { + noteId: Note["id"]; + type?: string | null; + limit?: number; + offset?: number; + }; res: NoteReaction[]; }; "notes/reactions/create": { @@ -787,6 +792,7 @@ export type Endpoints = { sinceId?: Note["id"]; untilId?: Note["id"]; noteId: Note["id"]; + filter?: "boost" | "quote"; }; res: Note[]; }; diff --git a/packages/firefish-js/src/entities.ts b/packages/firefish-js/src/entities.ts index 457d7ac935..aace6de166 100644 --- a/packages/firefish-js/src/entities.ts +++ b/packages/firefish-js/src/entities.ts @@ -174,9 +174,11 @@ export type Note = { channelId?: Channel["id"]; channel?: Channel; myReaction?: string; + meRenoteCount?: number; reactions: Record; renoteCount: number; repliesCount: number; + quoteCount: number; poll?: { expiresAt: DateString | null; multiple: boolean;