fix: use MkPagination in notes for Quote, Boost, Reaction

This commit is contained in:
Lhcfl 2024-04-20 15:56:13 +08:00
parent dd3ad89b64
commit 35c7dccb49
9 changed files with 131 additions and 68 deletions

View file

@ -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()),
},
}),
meRenoteCount: 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,

View file

@ -208,5 +208,15 @@ export const packedNoteSchema = {
optional: true, optional: true,
nullable: true, nullable: true,
}, },
meRenoteCount: {
type: "number",
optional: true,
nullable: false,
},
quoteCount: {
type: "number",
optional: false,
nullable: false,
},
}, },
} as const; } as const;

View file

@ -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;

View file

@ -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: ["boost", "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,13 @@ 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");
if (ps.filter === "boost") {
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 });
} }

View file

@ -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,
) )
@ -92,32 +92,33 @@
/> />
<MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" /> <MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" />
<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" :conversation="items"
:detailed-view="true" :detailed-view="true"
:parent-id="note.id" :parent-id="note.id"
/> />
<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
@ -186,6 +187,7 @@ 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 { useStream } from "@/stream";
import MkPagination, { Paging } from "@/components/MkPagination.vue";
// import icon from "@/scripts/icon"; // import icon from "@/scripts/icon";
const props = defineProps<{ const props = defineProps<{
@ -247,7 +249,6 @@ const replies = ref<entities.Note[]>([]);
const directReplies = ref<null | entities.Note[]>([]); const directReplies = ref<null | entities.Note[]>([]);
const directQuotes = 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;
@ -401,24 +402,32 @@ os.api("notes/clips", {
clips.value = res; clips.value = res;
}); });
// const pagination = { const renotePagination = {
// endpoint: "notes/renotes", endpoint: "notes/renotes" as const,
// noteId: note.id, limit: 30,
// 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, filter: "boost" as const,
}).then((res) => { },
renotes.value = res; };
}); const quotePagination = {
} endpoint: "notes/renotes" as const,
limit: 30,
params: {
noteId: note.value.id,
filter: "quote" as const,
},
};
function loadTab() {
// if (tab.value === "renotes" && !renotes.value) {
// os.api("notes/renotes", {
// noteId: note.value.id,
// limit: 100,
// }).then((res) => {
// renotes.value = res;
// });
// }
} }
async function onNoteUpdated( async function onNoteUpdated(

View file

@ -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>

View file

@ -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.meRenoteCount && props.note.meRenoteCount > 0,
if (isSignedIn) { );
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();

View file

@ -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?: "boost" | "quote";
}; };
res: Note[]; res: Note[];
}; };

View file

@ -174,9 +174,11 @@ export type Note = {
channelId?: Channel["id"]; channelId?: Channel["id"];
channel?: Channel; channel?: Channel;
myReaction?: string; myReaction?: string;
meRenoteCount?: 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;