From 35c7dccb498ac8f068199f09f982e80997ed7bcb Mon Sep 17 00:00:00 2001 From: Lhcfl <Lhcfl@outlook.com> Date: Sat, 20 Apr 2024 15:56:13 +0800 Subject: [PATCH 01/14] fix: use MkPagination in notes for Quote, Boost, Reaction --- .../backend/src/models/repositories/note.ts | 22 ++++- packages/backend/src/models/schema/note.ts | 10 ++ .../server/api/endpoints/notes/reactions.ts | 2 - .../src/server/api/endpoints/notes/renotes.ts | 15 ++- .../client/src/components/MkNoteDetailed.vue | 93 ++++++++++--------- .../client/src/components/MkReactedUsers.vue | 31 +++++-- .../client/src/components/MkRenoteButton.vue | 16 +--- packages/firefish-js/src/api.types.ts | 8 +- packages/firefish-js/src/entities.ts | 2 + 9 files changed, 131 insertions(+), 68 deletions(-) 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 @@ ) }} </option> - <option v-if="directQuotes && directQuotes.length > 0" value="quotes"> + <option v-if="note.quoteCount > 0" value="quotes"> <!-- <i :class="icon('ph-quotes')"></i> --> {{ wordWithCount( - directQuotes.length, + note.quoteCount, i18n.ts.quote, i18n.ts.quotes, ) @@ -92,32 +92,33 @@ /> <MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" /> - <MkNoteSub - v-for="note in directQuotes" - v-if="directQuotes && tab === 'quotes'" - :key="note.id" - :note="note" - class="reply" - :conversation="replies" - :detailed-view="true" - :parent-id="note.id" - /> - <MkLoading v-else-if="tab === 'quotes' && directQuotes && directQuotes.length > 0" /> + <MkPagination + v-if="tab === 'quotes'" + v-slot="{ items }" + :pagination="quotePagination" + > + <MkNoteSub + v-for="note in items" + :key="note.id" + :note="note" + class="reply" + :conversation="items" + :detailed-view="true" + :parent-id="note.id" + /> + </MkPagination> - <!-- <MkPagination + <MkPagination v-if="tab === 'renotes'" v-slot="{ items }" - ref="pagingComponent" - :pagination="pagination" - > --> - <MkUserCardMini - v-for="item in renotes" - v-if="tab === 'renotes' && renotes" - :key="item.user.id" - :user="item.user" - /> - <!-- </MkPagination> --> - <MkLoading v-else-if="tab === 'renotes' && note.renoteCount > 0" /> + :pagination="renotePagination" + > + <MkUserCardMini + v-for="item in items" + :key="item.user.id" + :user="item.user" + /> + </MkPagination> <div v-if="tab === 'clips' && clips.length > 0" class="_content clips"> <MkA @@ -186,6 +187,7 @@ import { getNoteMenu } from "@/scripts/get-note-menu"; import { useNoteCapture } from "@/scripts/use-note-capture"; import { deepClone } from "@/scripts/clone"; import { useStream } from "@/stream"; +import MkPagination, { Paging } from "@/components/MkPagination.vue"; // import icon from "@/scripts/icon"; const props = defineProps<{ @@ -247,7 +249,6 @@ const replies = ref<entities.Note[]>([]); const directReplies = ref<null | entities.Note[]>([]); const directQuotes = ref<null | entities.Note[]>([]); 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<InstanceType<typeof MkPagination>>(); - -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 @@ }}</span> </button> </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 v-else> <MkLoading /> @@ -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<entities.Note>(); const tab = ref<string | null>(null); const reactions = ref<string[]>(); -const users = ref(); -async function updateUsers(): void { - const res = await os.api("notes/reactions", { +const pagingComponent = ref<MkPaginationType<"notes/reactions"> | 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(); }); }); </script> 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<string, number>; renoteCount: number; repliesCount: number; + quoteCount: number; poll?: { expiresAt: DateString | null; multiple: boolean; From 39a229b8753df37a15842bb957cdf2dff57ead3b Mon Sep 17 00:00:00 2001 From: Lhcfl <Lhcfl@outlook.com> Date: Sat, 20 Apr 2024 20:55:47 +0800 Subject: [PATCH 02/14] fix: use MkPagination for replies --- biome.json | 2 +- .../api/common/make-pagination-query.ts | 4 +- .../client/src/components/MkNoteDetailed.vue | 149 +++++------------- packages/client/src/components/MkNoteSub.vue | 103 +++++++++--- .../client/src/components/MkPagination.vue | 68 ++++---- .../client/src/scripts/use-note-capture.ts | 17 +- 6 files changed, 172 insertions(+), 171 deletions(-) diff --git a/biome.json b/biome.json index 21b711f457..487165266a 100644 --- a/biome.json +++ b/biome.json @@ -14,7 +14,7 @@ }, "overrides": [ { - "include": ["*.vue"], + "include": ["*.vue", "packages/client/*.ts"], "linter": { "rules": { "style": { diff --git a/packages/backend/src/server/api/common/make-pagination-query.ts b/packages/backend/src/server/api/common/make-pagination-query.ts index a2c3275693..83827b3df1 100644 --- a/packages/backend/src/server/api/common/make-pagination-query.ts +++ b/packages/backend/src/server/api/common/make-pagination-query.ts @@ -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>, sinceId?: string, untilId?: string, diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index 71e4aeb783..30f1af0829 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -33,7 +33,7 @@ @contextmenu.stop="onContextmenu" ></MkNote> - <MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab"> + <MkTab v-model="tab" :style="'underline'"> <option value="replies"> <!-- <i :class="icon('ph-arrow-u-up-left')"></i> --> {{ @@ -80,17 +80,22 @@ </option> </MkTab> - <MkNoteSub - v-for="note in directReplies" - v-if="directReplies && tab === 'replies'" - :key="note.id" - :note="note" - class="reply" - :conversation="replies" - :detailed-view="true" - :parent-id="note.id" - /> - <MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" /> + <MkPagination + ref="repliesPagingComponent" + v-if="tab === 'replies' && note.repliesCount > 0" + v-slot="{ items }" + :pagination="repliesPagination" + > + <MkNoteSub + v-for="note in items" + :key="note.id" + :note="note" + class="reply" + :auto-conversation="true" + :detailed-view="true" + :parent-id="note.id" + /> + </MkPagination> <MkPagination v-if="tab === 'quotes'" @@ -102,7 +107,7 @@ :key="note.id" :note="note" class="reply" - :conversation="items" + :auto-conversation="true" :detailed-view="true" :parent-id="note.id" /> @@ -167,8 +172,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, onUpdated, ref } from "vue"; -import type { StreamTypes, entities } from "firefish-js"; +import { onMounted, onUpdated, ref } from "vue"; +import type { entities } from "firefish-js"; import MkTab from "@/components/MkTab.vue"; import MkNote from "@/components/MkNote.vue"; import MkNoteSub from "@/components/MkNoteSub.vue"; @@ -186,8 +191,9 @@ import { i18n } from "@/i18n"; import { getNoteMenu } from "@/scripts/get-note-menu"; import { useNoteCapture } from "@/scripts/use-note-capture"; import { deepClone } from "@/scripts/clone"; -import { useStream } from "@/stream"; -import MkPagination, { Paging } from "@/components/MkPagination.vue"; +import MkPagination, { + type MkPaginationType, +} from "@/components/MkPagination.vue"; // import icon from "@/scripts/icon"; const props = defineProps<{ @@ -195,8 +201,6 @@ const props = defineProps<{ pinned?: boolean; }>(); -const stream = useStream(); - const tab = ref("replies"); const note = ref(deepClone(props.note)); @@ -227,6 +231,10 @@ if (noteViewInterruptors.length > 0) { }); } +const repliesPagingComponent = ref<MkPaginationType<"notes/replies"> | null>( + null, +); + const el = ref<HTMLElement | null>(null); const noteEl = ref(); const menuButton = ref<HTMLElement>(); @@ -245,9 +253,6 @@ const muted = ref( const translation = ref(null); const translating = ref(false); 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 isRenote = ref(note.value.renoteId != null); let isScrolling: boolean; @@ -270,6 +275,10 @@ useNoteCapture({ rootEl: el, note, isDeletedRef: isDeleted, + onReplied: (replyNote) => { + note.value.repliesCount += 1; + repliesPagingComponent.value?.append(replyNote); + }, }); function reply(_viaKeyboard = false): void { @@ -358,32 +367,6 @@ function 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; if (note.value.replyId) { os.api("notes/conversation", { @@ -402,6 +385,15 @@ os.api("notes/clips", { clips.value = res; }); +const repliesPagination = { + endpoint: "notes/replies" as const, + limit: 10, + params: { + noteId: note.value.id, + }, + ascending: true, +}; + const renotePagination = { endpoint: "notes/renotes" as const, limit: 30, @@ -419,68 +411,11 @@ const quotePagination = { }, }; -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( - noteData: StreamTypes.NoteUpdatedEvent, -): Promise<void> { - const { type, id, body } = noteData; - - let found = -1; - if (id === note.value.id) { - found = 0; - } else { - for (let i = 0; i < replies.value.length; i++) { - const reply = replies.value[i]; - if (reply.id === id) { - found = i + 1; - break; - } - } - } - - 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", () => { isScrolling = true; }); onMounted(() => { - stream.on("noteUpdated", onNoteUpdated); isScrolling = false; noteEl.value.scrollIntoView(); }); @@ -493,10 +428,6 @@ onUpdated(() => { } } }); - -onUnmounted(() => { - stream.off("noteUpdated", onNoteUpdated); -}); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue index c7cf06d2e5..a71580c0be 100644 --- a/packages/client/src/components/MkNoteSub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -22,7 +22,7 @@ <div class="avatar-container"> <MkAvatar class="avatar" :user="appearNote.user" /> <div - v-if="!conversation || replies.length > 0" + v-if="conversation == null || replies.length > 0" class="line" ></div> </div> @@ -148,20 +148,31 @@ </footer> </div> </div> - <template v-if="conversation"> - <MkNoteSub - v-for="reply in replies" - v-if="replyLevel < 11 && depth < 5" - :key="reply.id" - :note="reply" - class="reply" - :class="{ single: replies.length == 1 }" - :conversation="conversation" - :depth="replies.length == 1 ? depth : depth + 1" - :reply-level="replyLevel + 1" - :parent-id="appearNote.id" - :detailed-view="detailedView" - /> + <MkLoading v-if="conversationLoading" /> + <template v-else-if="conversation"> + <template + v-if="replyLevel < REPLY_LEVEL_UPPERBOUND && depth < DEPTH_UPPERBOUND" + > + <MkNoteSub + v-for="reply in replies.slice(0, REPLIES_LIMIT)" + :key="reply.id" + :note="reply" + class="reply" + :class="{ single: replies.length == 1 }" + :conversation="conversation" + :depth="replies.length == 1 ? depth : depth + 1" + :reply-level="replyLevel + 1" + :parent-id="appearNote.id" + :detailed-view="detailedView" + /> + <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 class="line"></div> <MkA class="text _link" :to="notePage(note)" @@ -190,7 +201,7 @@ </template> <script lang="ts" setup> -import { computed, inject, ref } from "vue"; +import { computed, inject, ref, watch } from "vue"; import type { Ref } from "vue"; import type { entities } from "firefish-js"; import XNoteHeader from "@/components/MkNoteHeader.vue"; @@ -221,13 +232,17 @@ import type { NoteTranslation } from "@/types/note"; const router = useRouter(); +const REPLIES_LIMIT = 10; +const REPLY_LEVEL_UPPERBOUND = 11; +const DEPTH_UPPERBOUND = 5; + const props = withDefaults( defineProps<{ note: entities.Note; conversation?: entities.Note[]; - parentId?; - detailedView?; - + autoConversation?: boolean; + parentId?: string; + detailedView?: boolean; // how many notes are in between this one and the note being viewed in detail depth?: number; // the actual reply level of this note within the conversation thread @@ -251,6 +266,44 @@ const softMuteReasonI18nSrc = (what?: string) => { 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) { + 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; + }); + } else { + conversation.value = []; + } +} + const isRenote = note.value.renote != null && note.value.text == null && @@ -277,13 +330,6 @@ const muted = ref( ); const translation = ref<NoteTranslation | null>(null); 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 expandOnNoteClick = defaultStore.state.expandOnNoteClick; const lang = localStorage.getItem("lang"); @@ -329,6 +375,11 @@ useNoteCapture({ rootEl: el, note: appearNote, isDeletedRef: isDeleted, + onReplied: (note) => { + if (hasMoreReplies.value === false) { + conversation.value = (conversation.value ?? []).concat([note]); + } + }, }); function reply(_viaKeyboard = false): void { diff --git a/packages/client/src/components/MkPagination.vue b/packages/client/src/components/MkPagination.vue index 03a1f0e35f..2f092cb8ad 100644 --- a/packages/client/src/components/MkPagination.vue +++ b/packages/client/src/components/MkPagination.vue @@ -68,7 +68,15 @@ <script lang="ts" setup generic="E extends PagingKey"> 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 * as os from "@/os"; import { @@ -122,6 +130,12 @@ export interface Paging<E extends PagingKey = PagingKey> { */ reversed?: boolean; + /** + * For not-reversed, not-offsetMode, + * Sort by id in ascending order + */ + ascending?: boolean; + offsetMode?: boolean; } @@ -169,17 +183,19 @@ const init = async (): Promise<void> => { queue.value = []; fetching.value = true; - const params = props.pagination.params - ? isRef<Param>(props.pagination.params) - ? props.pagination.params.value - : props.pagination.params - : {}; + const params = props.pagination.params ? unref(props.pagination.params) : {}; await os .api(props.pagination.endpoint, { ...params, limit: props.pagination.noPaging ? props.pagination.limit || 10 : (props.pagination.limit || 10) + 1, + ...(props.pagination.ascending + ? { + // An initial value smaller than all possible ids must be filled in here. + sinceId: "0", + } + : {}), }) .then( (res: Item[]) => { @@ -196,10 +212,10 @@ const init = async (): Promise<void> => { res.length > (props.pagination.limit || 10) ) { res.pop(); - items.value = props.pagination.reversed ? [...res].reverse() : res; + items.value = props.pagination.reversed ? res.toReversed() : res; more.value = true; } else { - items.value = props.pagination.reversed ? [...res].reverse() : res; + items.value = props.pagination.reversed ? res.toReversed() : res; more.value = false; } offset.value = res.length; @@ -219,11 +235,7 @@ const reload = (): Promise<void> => { }; const refresh = async (): Promise<void> => { - const params = props.pagination.params - ? isRef<Param>(props.pagination.params) - ? props.pagination.params.value - : props.pagination.params - : {}; + const params = props.pagination.params ? unref(props.pagination.params) : {}; await os .api(props.pagination.endpoint, { ...params, @@ -269,11 +281,7 @@ const fetchMore = async (): Promise<void> => { return; moreFetching.value = true; backed.value = true; - const params = props.pagination.params - ? isRef<Param>(props.pagination.params) - ? props.pagination.params.value - : props.pagination.params - : {}; + const params = props.pagination.params ? unref(props.pagination.params) : {}; await os .api(props.pagination.endpoint, { ...params, @@ -286,9 +294,13 @@ const fetchMore = async (): Promise<void> => { ? { sinceId: items.value[0].id, } - : { - untilId: items.value[items.value.length - 1].id, - }), + : props.pagination.ascending + ? { + sinceId: items.value[items.value.length - 1].id, + } + : { + untilId: items.value[items.value.length - 1].id, + }), }) .then( (res: Item[]) => { @@ -303,12 +315,12 @@ const fetchMore = async (): Promise<void> => { if (res.length > SECOND_FETCH_LIMIT) { res.pop(); items.value = props.pagination.reversed - ? [...res].reverse().concat(items.value) + ? res.toReversed().concat(items.value) : items.value.concat(res); more.value = true; } else { items.value = props.pagination.reversed - ? [...res].reverse().concat(items.value) + ? res.toReversed().concat(items.value) : items.value.concat(res); more.value = false; } @@ -330,11 +342,7 @@ const fetchMoreAhead = async (): Promise<void> => { ) return; moreFetching.value = true; - const params = props.pagination.params - ? isRef<Param>(props.pagination.params) - ? props.pagination.params.value - : props.pagination.params - : {}; + const params = props.pagination.params ? unref(props.pagination.params) : {}; await os .api(props.pagination.endpoint, { ...params, @@ -356,12 +364,12 @@ const fetchMoreAhead = async (): Promise<void> => { if (res.length > SECOND_FETCH_LIMIT) { res.pop(); items.value = props.pagination.reversed - ? [...res].reverse().concat(items.value) + ? res.toReversed().concat(items.value) : items.value.concat(res); more.value = true; } else { items.value = props.pagination.reversed - ? [...res].reverse().concat(items.value) + ? res.toReversed().concat(items.value) : items.value.concat(res); more.value = false; } diff --git a/packages/client/src/scripts/use-note-capture.ts b/packages/client/src/scripts/use-note-capture.ts index 1bc32d5246..2d0a203fa7 100644 --- a/packages/client/src/scripts/use-note-capture.ts +++ b/packages/client/src/scripts/use-note-capture.ts @@ -9,6 +9,7 @@ export function useNoteCapture(props: { rootEl: Ref<HTMLElement | null>; note: Ref<entities.Note>; isDeletedRef: Ref<boolean>; + onReplied?: (note: entities.Note) => void; }) { const note = props.note; const connection = isSignedIn ? useStream() : null; @@ -19,6 +20,16 @@ export function useNoteCapture(props: { if (id !== note.value.id) return; 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": { const reaction = body.reaction; @@ -34,7 +45,7 @@ export function useNoteCapture(props: { note.value.reactions[reaction] = currentCount + 1; - if (isSignedIn && body.userId === me.id) { + if (isSignedIn && body.userId === me!.id) { note.value.myReaction = reaction; } break; @@ -48,7 +59,7 @@ export function useNoteCapture(props: { note.value.reactions[reaction] = Math.max(0, currentCount - 1); - if (isSignedIn && body.userId === me.id) { + if (isSignedIn && body.userId === me!.id) { note.value.myReaction = undefined; } break; @@ -62,7 +73,7 @@ export function useNoteCapture(props: { choices[choice] = { ...choices[choice], votes: choices[choice].votes + 1, - ...(isSignedIn && body.userId === me.id + ...(isSignedIn && body.userId === me!.id ? { isVoted: true, } From e5e2eedfb4e936a339d550c2110df56e37f51184 Mon Sep 17 00:00:00 2001 From: Lhcfl <Lhcfl@outlook.com> Date: Sat, 20 Apr 2024 22:04:25 +0800 Subject: [PATCH 03/14] fix: Some SubNote incorrectly added replies --- packages/client/src/components/MkNoteDetailed.vue | 2 ++ packages/client/src/components/MkNoteSub.vue | 6 ++++++ packages/client/src/components/MkSubNoteContent.vue | 6 +++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index 30f1af0829..55a13956f6 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -94,6 +94,7 @@ :auto-conversation="true" :detailed-view="true" :parent-id="note.id" + :auto-add-replies="true" /> </MkPagination> @@ -110,6 +111,7 @@ :auto-conversation="true" :detailed-view="true" :parent-id="note.id" + :auto-add-replies="true" /> </MkPagination> diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue index a71580c0be..f83c676cc1 100644 --- a/packages/client/src/components/MkNoteSub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -164,6 +164,7 @@ :reply-level="replyLevel + 1" :parent-id="appearNote.id" :detailed-view="detailedView" + :auto-add-replies="true" /> <div v-if="hasMoreReplies" class="more"> <div class="line"></div> @@ -247,10 +248,12 @@ const props = withDefaults( depth?: number; // the actual reply level of this note within the conversation thread replyLevel?: number; + autoAddReplies?: boolean; }>(), { depth: 1, replyLevel: 1, + autoAddReplies: false, }, ); @@ -376,6 +379,9 @@ useNoteCapture({ note: appearNote, isDeletedRef: isDeleted, onReplied: (note) => { + if (props.autoAddReplies !== true) { + return; + } if (hasMoreReplies.value === false) { conversation.value = (conversation.value ?? []).concat([note]); } diff --git a/packages/client/src/components/MkSubNoteContent.vue b/packages/client/src/components/MkSubNoteContent.vue index 7d350085bb..a0664c28e9 100644 --- a/packages/client/src/components/MkSubNoteContent.vue +++ b/packages/client/src/components/MkSubNoteContent.vue @@ -1,7 +1,7 @@ <template> <p v-if="note.cw != null" class="cw"> <MkA - v-if="conversation && note.renoteId == parentId" + v-if="conversation && note.renoteId == parentId && parentId != null" :to=" detailedView ? `#${parentId}` : `${notePage(note)}#${parentId}` " @@ -198,8 +198,8 @@ import icon from "@/scripts/icon"; const props = defineProps<{ note: entities.Note; - parentId?; - conversation?; + parentId?: string; + conversation?: entities.Note[]; detailed?: boolean; detailedView?: boolean; }>(); From 4e20fe589d5fc7cfc6f0bc400acdba8d3bfd05cc Mon Sep 17 00:00:00 2001 From: Lhcfl <Lhcfl@outlook.com> Date: Sat, 20 Apr 2024 22:29:55 +0800 Subject: [PATCH 04/14] fix: style may be missing in mobile mode --- packages/client/src/components/MkNoteSub.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue index f83c676cc1..326bb3d502 100644 --- a/packages/client/src/components/MkNoteSub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -1,6 +1,7 @@ <template> + <MkLoading v-if="conversationLoading" /> <article - v-if="!muted.muted || muted.what === 'reply'" + v-else-if="!muted.muted || muted.what === 'reply'" :id="detailedView ? appearNote.id : undefined" ref="el" v-size="{ max: [450, 500] }" @@ -289,6 +290,7 @@ watch(conversation, updateReplies, { }); if (props.autoConversation) { + conversation.value = []; if (note.value.repliesCount > 0 || note.value.renoteCount > 0) { conversationLoading.value = true; os.api("notes/children", { @@ -302,8 +304,6 @@ if (props.autoConversation) { .concat(res.filter((n) => n.userId === note.value.userId)); conversationLoading.value = false; }); - } else { - conversation.value = []; } } From cdc3b5181a709bbbfb492b0546d5f03cc26978ff Mon Sep 17 00:00:00 2001 From: Lhcfl <Lhcfl@outlook.com> Date: Sun, 21 Apr 2024 11:24:41 +0800 Subject: [PATCH 05/14] Update api-change.md --- docs/api-change.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/api-change.md b/docs/api-change.md index f3ed584c32..79c9b39e52 100644 --- a/docs/api-change.md +++ b/docs/api-change.md @@ -2,6 +2,15 @@ Breaking changes are indicated by the :warning: icon. +## Unreleased + +- New optional parameters are added to `notes/renotes` endpoint: + - `filter`: `"boost"` for boosts only, `"quote"` for quotes only, `null`(default) for both + +- Removed unused optional parameters for `notes/reactions` endpoint: + - `sinceId` + - `untilId` + ## v20240413 - :warning: Removed `patrons` endpoint. From 09bcbb0ff02dc01d79021049bb915ae959f4f073 Mon Sep 17 00:00:00 2001 From: Lhcfl <Lhcfl@outlook.com> Date: Sun, 21 Apr 2024 11:27:08 +0800 Subject: [PATCH 06/14] Update docs/api-change.md --- docs/api-change.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-change.md b/docs/api-change.md index 79c9b39e52..36b05d77f4 100644 --- a/docs/api-change.md +++ b/docs/api-change.md @@ -5,7 +5,7 @@ Breaking changes are indicated by the :warning: icon. ## Unreleased - New optional parameters are added to `notes/renotes` endpoint: - - `filter`: `"boost"` for boosts only, `"quote"` for quotes only, `null`(default) for both + - `filter` - Removed unused optional parameters for `notes/reactions` endpoint: - `sinceId` From dc53447fa3322f44cd01716491184e80472878a6 Mon Sep 17 00:00:00 2001 From: naskya <m@naskya.net> Date: Mon, 22 Apr 2024 05:42:31 +0900 Subject: [PATCH 07/14] chore (API): we still use the word 'renote' in our code/API --- packages/backend/src/server/api/endpoints/notes/renotes.ts | 4 ++-- packages/client/src/components/MkNoteDetailed.vue | 2 +- packages/firefish-js/src/api.types.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 282777b2e4..e69d1be7b4 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -44,7 +44,7 @@ export const paramDef = { untilId: { type: "string", format: "misskey:id" }, filter: { type: "string", - enum: ["boost", "quote"], + enum: ["all", "renote", "quote"], nullable: true, default: null, }, @@ -67,7 +67,7 @@ export default define(meta, paramDef, async (ps, user) => { .andWhere("note.renoteId = :renoteId", { renoteId: note.id }) .innerJoinAndSelect("note.user", "user"); - if (ps.filter === "boost") { + if (ps.filter === "renote") { query.andWhere("note.text IS NULL"); } if (ps.filter === "quote") { diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index 55a13956f6..2c242645b4 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -401,7 +401,7 @@ const renotePagination = { limit: 30, params: { noteId: note.value.id, - filter: "boost" as const, + filter: "renote" as const, }, }; const quotePagination = { diff --git a/packages/firefish-js/src/api.types.ts b/packages/firefish-js/src/api.types.ts index 6c0e11bec5..1ee94b9954 100644 --- a/packages/firefish-js/src/api.types.ts +++ b/packages/firefish-js/src/api.types.ts @@ -792,7 +792,7 @@ export type Endpoints = { sinceId?: Note["id"]; untilId?: Note["id"]; noteId: Note["id"]; - filter?: "boost" | "quote"; + filter?: "all" | "renote" | "quote"; }; res: Note[]; }; From b9e88ce490a31a579b7c53f583f47dbb3b0a6838 Mon Sep 17 00:00:00 2001 From: naskya <m@naskya.net> Date: Mon, 22 Apr 2024 05:37:57 +0900 Subject: [PATCH 08/14] docs: edit api-changes.md --- docs/api-change.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/api-change.md b/docs/api-change.md index 36b05d77f4..eec5f0d81c 100644 --- a/docs/api-change.md +++ b/docs/api-change.md @@ -4,10 +4,11 @@ Breaking changes are indicated by the :warning: icon. ## Unreleased -- New optional parameters are added to `notes/renotes` endpoint: - - `filter` - -- Removed unused optional parameters for `notes/reactions` endpoint: +- 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 not taken into account due to a bug: - `sinceId` - `untilId` From 509690d84d92c53cef1ba60453b8a72b99fab47e Mon Sep 17 00:00:00 2001 From: naskya <m@naskya.net> Date: Mon, 22 Apr 2024 05:43:58 +0900 Subject: [PATCH 09/14] chore: meRenoteCount -> myRenoteCount --- packages/backend/src/models/repositories/note.ts | 2 +- packages/backend/src/models/schema/note.ts | 2 +- packages/client/src/components/MkRenoteButton.vue | 2 +- packages/firefish-js/src/entities.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index 5974fc24af..7fa26373b8 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -222,7 +222,7 @@ export const NoteRepository = db.getRepository(Note).extend({ text: Not(IsNull()), }, }), - meRenoteCount: me + myRenoteCount: me ? Notes.count({ where: { renoteId: note.id, diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts index e0048f7d00..6064919960 100644 --- a/packages/backend/src/models/schema/note.ts +++ b/packages/backend/src/models/schema/note.ts @@ -208,7 +208,7 @@ export const packedNoteSchema = { optional: true, nullable: true, }, - meRenoteCount: { + myRenoteCount: { type: "number", optional: true, nullable: false, diff --git a/packages/client/src/components/MkRenoteButton.vue b/packages/client/src/components/MkRenoteButton.vue index 23911eef37..3925dc8c5a 100644 --- a/packages/client/src/components/MkRenoteButton.vue +++ b/packages/client/src/components/MkRenoteButton.vue @@ -73,7 +73,7 @@ useTooltip(buttonRef, async (showing) => { }); const hasRenotedBefore = ref( - props.note.meRenoteCount && props.note.meRenoteCount > 0, + props.note.myRenoteCount && props.note.myRenoteCount > 0, ); const renote = (viaKeyboard = false, ev?: MouseEvent) => { diff --git a/packages/firefish-js/src/entities.ts b/packages/firefish-js/src/entities.ts index aace6de166..02362194eb 100644 --- a/packages/firefish-js/src/entities.ts +++ b/packages/firefish-js/src/entities.ts @@ -174,7 +174,7 @@ export type Note = { channelId?: Channel["id"]; channel?: Channel; myReaction?: string; - meRenoteCount?: number; + myRenoteCount?: number; reactions: Record<string, number>; renoteCount: number; repliesCount: number; From 8140694a3191a33db9b81afe70677755a36cd38d Mon Sep 17 00:00:00 2001 From: naskya <m@naskya.net> Date: Mon, 22 Apr 2024 05:47:22 +0900 Subject: [PATCH 10/14] chore (backend): add comment --- packages/backend/src/server/api/endpoints/notes/renotes.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index e69d1be7b4..16304dd269 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -67,6 +67,9 @@ export default define(meta, paramDef, async (ps, user) => { .andWhere("note.renoteId = :renoteId", { renoteId: note.id }) .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"); } From 6a0ad409cdb1cd81e1cc0d630259d9708e9dc9db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=91=A8=E9=83=A8=E8=90=BD?= <laozhoubuluo@gmail.com> Date: Tue, 23 Apr 2024 20:07:52 +0800 Subject: [PATCH 11/14] fix: authorize-follow not working due to redeclare 'acct' --- packages/client/src/pages/follow.vue | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/client/src/pages/follow.vue b/packages/client/src/pages/follow.vue index a8708978ef..3be549537f 100644 --- a/packages/client/src/pages/follow.vue +++ b/packages/client/src/pages/follow.vue @@ -24,16 +24,16 @@ async function follow(user): Promise<void> { }); } -const acct = new URL(location.href).searchParams.get("acct"); -if (acct == null) { +const acctUri = new URL(location.href).searchParams.get("acct"); +if (acctUri == null) { throw new Error("acct required"); } let promise; -if (acct.startsWith("https://")) { +if (acctUri.startsWith("https://")) { promise = os.api("ap/show", { - uri: acct, + uri: acctUri, }); promise.then((res) => { if (res.type === "User") { @@ -50,7 +50,7 @@ if (acct.startsWith("https://")) { } }); } else { - promise = os.api("users/show", acct.parse(acct)); + promise = os.api("users/show", acct.parse(acctUri)); promise.then((user) => { follow(user); }); From 50b7c71ed6a701ca001e51a115616e4446c324a6 Mon Sep 17 00:00:00 2001 From: naskya <m@naskya.net> Date: Tue, 23 Apr 2024 23:56:34 +0900 Subject: [PATCH 12/14] chore (backend): use type import --- packages/backend/src/server/api/endpoints/i/update.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 4f65c59a9e..9a2b49cb3d 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -13,7 +13,7 @@ import { normalizeForSearch } from "@/misc/normalize-for-search.js"; import { verifyLink } from "@/services/fetch-rel-me.js"; import { ApiError } from "@/server/api/error.js"; import define from "@/server/api/define.js"; -import { DriveFile } from "@/models/entities/drive-file"; +import type { DriveFile } from "@/models/entities/drive-file"; export const meta = { tags: ["account"], From 067810b1bece8228f88c592e73dde68fdfb33f36 Mon Sep 17 00:00:00 2001 From: Lhcfl <Lhcfl@outlook.com> Date: Tue, 23 Apr 2024 23:59:03 +0900 Subject: [PATCH 13/14] docs (minor): update api-change.md --- docs/api-change.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-change.md b/docs/api-change.md index f55ceab58f..89cf8a9d38 100644 --- a/docs/api-change.md +++ b/docs/api-change.md @@ -9,7 +9,7 @@ Breaking changes are indicated by the :warning: icon. - `all` (default) - `renote` - `quote` -- :warning: Removed the following optional parameters in `notes/reactions`, as they were not taken into account due to a bug: +- :warning: Removed the following optional parameters in `notes/reactions`, as they were never taken into account due to a bug: - `sinceId` - `untilId` From 9eccdba0759a92010ec424c61472d14be414741b Mon Sep 17 00:00:00 2001 From: naskya <m@naskya.net> Date: Wed, 24 Apr 2024 00:23:13 +0900 Subject: [PATCH 14/14] chore (backend-rs): move add_note_to_antenna to misc --- packages/backend-rs/index.d.ts | 2 +- packages/backend-rs/index.js | 4 ++-- .../src/{service => misc}/add_note_to_antenna.rs | 12 +++++------- packages/backend-rs/src/misc/mod.rs | 1 + packages/backend-rs/src/service/mod.rs | 1 - packages/backend-rs/src/service/stream.rs | 2 ++ packages/backend-rs/src/service/stream/antenna.rs | 10 ++++++++++ 7 files changed, 21 insertions(+), 11 deletions(-) rename packages/backend-rs/src/{service => misc}/add_note_to_antenna.rs (69%) create mode 100644 packages/backend-rs/src/service/stream/antenna.rs diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts index 69de833dcc..dec480d79c 100644 --- a/packages/backend-rs/index.d.ts +++ b/packages/backend-rs/index.d.ts @@ -193,6 +193,7 @@ export interface Acct { } export function stringToAcct(acct: string): Acct export function acctToString(acct: Acct): string +export function addNoteToAntenna(antennaId: string, note: Note): void /** * @param host punycoded instance host * @returns whether the given host should be blocked @@ -1119,7 +1120,6 @@ export interface Webhook { latestSentAt: Date | null latestStatus: number | null } -export function addNoteToAntenna(antennaId: string, note: Note): void /** Initializes Cuid2 generator. Must be called before any [create_id]. */ export function initIdGenerator(length: number, fingerprint: string): void export function getTimestamp(id: string): number diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js index b4d86dccdf..d801e28fce 100644 --- a/packages/backend-rs/index.js +++ b/packages/backend-rs/index.js @@ -310,12 +310,13 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { loadEnv, loadConfig, stringToAcct, acctToString, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, addNoteToAntenna, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding +const { loadEnv, loadConfig, stringToAcct, acctToString, addNoteToAntenna, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding module.exports.loadEnv = loadEnv module.exports.loadConfig = loadConfig module.exports.stringToAcct = stringToAcct module.exports.acctToString = acctToString +module.exports.addNoteToAntenna = addNoteToAntenna module.exports.isBlockedServer = isBlockedServer module.exports.isSilencedServer = isSilencedServer module.exports.isAllowedServer = isAllowedServer @@ -352,7 +353,6 @@ module.exports.RelayStatusEnum = RelayStatusEnum module.exports.UserEmojimodpermEnum = UserEmojimodpermEnum module.exports.UserProfileFfvisibilityEnum = UserProfileFfvisibilityEnum module.exports.UserProfileMutingnotificationtypesEnum = UserProfileMutingnotificationtypesEnum -module.exports.addNoteToAntenna = addNoteToAntenna module.exports.initIdGenerator = initIdGenerator module.exports.getTimestamp = getTimestamp module.exports.genId = genId diff --git a/packages/backend-rs/src/service/add_note_to_antenna.rs b/packages/backend-rs/src/misc/add_note_to_antenna.rs similarity index 69% rename from packages/backend-rs/src/service/add_note_to_antenna.rs rename to packages/backend-rs/src/misc/add_note_to_antenna.rs index 4f294cc881..2ed698d7e6 100644 --- a/packages/backend-rs/src/service/add_note_to_antenna.rs +++ b/packages/backend-rs/src/misc/add_note_to_antenna.rs @@ -1,13 +1,14 @@ use crate::database::{redis_conn, redis_key}; use crate::model::entity::note; -use crate::service::stream::{publish_to_stream, Error, Stream}; +use crate::service::stream; use crate::util::id::get_timestamp; use redis::{streams::StreamMaxlen, Commands}; type Note = note::Model; #[crate::export] -pub fn add_note_to_antenna(antenna_id: String, note: &Note) -> Result<(), Error> { +pub fn add_note_to_antenna(antenna_id: String, note: &Note) -> Result<(), stream::Error> { + // for timeline API redis_conn()?.xadd_maxlen( redis_key(format!("antennaTimeline:{}", antenna_id)), StreamMaxlen::Approx(200), @@ -15,9 +16,6 @@ pub fn add_note_to_antenna(antenna_id: String, note: &Note) -> Result<(), Error> &[("note", ¬e.id)], )?; - publish_to_stream( - &Stream::Antenna { antenna_id }, - Some("note"), - Some(serde_json::to_string(note)?), - ) + // for streaming API + stream::antenna::publish(antenna_id, note) } diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs index 24cec14969..8246b6a31a 100644 --- a/packages/backend-rs/src/misc/mod.rs +++ b/packages/backend-rs/src/misc/mod.rs @@ -1,4 +1,5 @@ pub mod acct; +pub mod add_note_to_antenna; pub mod check_server_block; pub mod check_word_mute; pub mod convert_host; diff --git a/packages/backend-rs/src/service/mod.rs b/packages/backend-rs/src/service/mod.rs index cc239e3f9e..baf29e06ad 100644 --- a/packages/backend-rs/src/service/mod.rs +++ b/packages/backend-rs/src/service/mod.rs @@ -1,2 +1 @@ -pub mod add_note_to_antenna; pub mod stream; diff --git a/packages/backend-rs/src/service/stream.rs b/packages/backend-rs/src/service/stream.rs index 6c5e6be4dd..1e84d5f866 100644 --- a/packages/backend-rs/src/service/stream.rs +++ b/packages/backend-rs/src/service/stream.rs @@ -1,3 +1,5 @@ +pub mod antenna; + use crate::config::CONFIG; use crate::database::redis_conn; use redis::{Commands, RedisError}; diff --git a/packages/backend-rs/src/service/stream/antenna.rs b/packages/backend-rs/src/service/stream/antenna.rs new file mode 100644 index 0000000000..08ae391caf --- /dev/null +++ b/packages/backend-rs/src/service/stream/antenna.rs @@ -0,0 +1,10 @@ +use crate::model::entity::note; +use crate::service::stream::{publish_to_stream, Error, Stream}; + +pub fn publish(antenna_id: String, note: ¬e::Model) -> Result<(), Error> { + publish_to_stream( + &Stream::Antenna { antenna_id }, + Some("note"), + Some(serde_json::to_string(note)?), + ) +}