feat: add post history page

This commit is contained in:
Lhcfl 2024-03-26 16:59:42 +08:00
parent d64d133d7f
commit bab704992f
11 changed files with 186 additions and 5 deletions

View file

@ -2232,3 +2232,4 @@ messagingUnencryptedInfo: "Chats on Firefish are not end-to-end encrypted. Don't
any sensitive infomation over Firefish." any sensitive infomation over Firefish."
autocorrectNoteLanguage: "Show a warning if the post language does not match the auto-detected result" autocorrectNoteLanguage: "Show a warning if the post language does not match the auto-detected result"
incorrectLanguageWarning: "It looks like your post is in {detected}, but you selected {current}.\nWould you like to set the language to {detected} instead?" incorrectLanguageWarning: "It looks like your post is in {detected}, but you selected {current}.\nWould you like to set the language to {detected} instead?"
noteEditHistory: "Post edit history"

View file

@ -2060,3 +2060,4 @@ noAltTextWarning: 有些附件没有描述。您是否忘记写描述了?
showNoAltTextWarning: 当您尝试发布没有描述的帖子附件时显示警告 showNoAltTextWarning: 当您尝试发布没有描述的帖子附件时显示警告
autocorrectNoteLanguage: 当帖子语言不符合自动检测的结果的时候显示警告 autocorrectNoteLanguage: 当帖子语言不符合自动检测的结果的时候显示警告
incorrectLanguageWarning: "看上去您帖子使用的语言是{detected},但您选择的语言是{current}。\n要改为以{detected}发帖吗?" incorrectLanguageWarning: "看上去您帖子使用的语言是{detected},但您选择的语言是{current}。\n要改为以{detected}发帖吗?"
noteEditHistory: "帖子编辑历史"

View file

@ -58,6 +58,9 @@ export default define(meta, paramDef, async (ps, user) => {
}, },
take: ps.limit, take: ps.limit,
skip: ps.offset, skip: ps.offset,
order: {
id: "DESC"
},
}); });
return await NoteEdits.packMany(history); return await NoteEdits.packMany(history);

View file

@ -10,7 +10,7 @@ export default defineComponent({
props: { props: {
items: { items: {
type: Array as PropType< type: Array as PropType<
{ id: string; createdAt: string; _shouldInsertAd_: boolean }[] { id: string; createdAt: string; _shouldInsertAd_?: boolean }[]
>, >,
required: true, required: true,
}, },

View file

@ -154,7 +154,12 @@
{{ appearNote.channel.name }}</MkA {{ appearNote.channel.name }}</MkA
> >
</div> </div>
<footer ref="footerEl" class="footer" tabindex="-1"> <footer
v-show="!hideFooter"
ref="footerEl"
class="footer"
tabindex="-1"
>
<XReactionsViewer <XReactionsViewer
v-if="enableEmojiReactions" v-if="enableEmojiReactions"
ref="reactionsViewer" ref="reactionsViewer"
@ -312,6 +317,7 @@ const props = defineProps<{
pinned?: boolean; pinned?: boolean;
detailedView?: boolean; detailedView?: boolean;
collapsedReply?: boolean; collapsedReply?: boolean;
hideFooter?: boolean;
}>(); }>();
const inChannel = inject("inChannel", null); const inChannel = inject("inChannel", null);

View file

@ -1,5 +1,12 @@
import type { entities } from "firefish-js"; import type { entities } from "firefish-js";
export const notePage = (note: entities.Note) => { export function notePage(
note: entities.Note,
options?: {
historyPage?: boolean
}) {
if (options?.historyPage) {
return `/notes/${note.id}/history`;
}
return `/notes/${note.id}`; return `/notes/${note.id}`;
}; };

View file

@ -0,0 +1,123 @@
<template>
<MkStickyContainer>
<template #header
><MkPageHeader
:display-back-button="true"
/></template>
<MkSpacer :content-max="800">
<MkLoading v-if="!loaded" />
<MkPagination
v-else
ref="pagingComponent"
v-slot="{ items: noteEdits }"
:pagination="pagination"
>
<div ref="tlEl" class="giivymft noGap">
<XList
v-slot="{ item: noteEdit }"
:items="convertNoteEditsToNotes(noteEdits as NoteEdit[])"
class="notes"
:no-gap="true"
>
<XNote
:key="noteEdit.id"
class="qtqtichx"
:note="noteEdit"
:hide-footer="true"
/>
</XList>
</div>
</MkPagination>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from "vue";
import MkPagination from "@/components/MkPagination.vue"
import type { Paging } from "@/components/MkPagination.vue"
import { api } from "@/os";
import XList from "@/components/MkDateSeparatedList.vue";
import XNote from "@/components/MkNote.vue";
import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata";
import icon from "@/scripts/icon";
import type { Note, NoteEdit } from "firefish-js/src/entities";
const pagingComponent = ref<InstanceType<typeof MkPagination>>();
const props = defineProps<{
noteId: string;
}>();
const pagination: Paging = {
endpoint: "notes/history" as const,
limit: 10,
offsetMode: true,
params: computed(() => ({
noteId: props.noteId
})),
};
definePageMetadata(
computed(() => ({
title: i18n.t("noteEditHistory"),
icon: `${icon("ph-clock-countdown")}`,
})),
);
const note = ref<Note>({} as Note);
const loaded = ref(false);
onMounted(() => {
api("notes/show", {
noteId: props.noteId,
}).then((res) => {
// Remove unnecessary parts
res.renote = undefined;
res.renoteId = null;
res.reply = undefined;
res.replyId = null;
note.value = res;
loaded.value = true;
});
});
function convertNoteEditsToNotes(noteEdits: NoteEdit[]) {
return [note.value].concat(
noteEdits.map(e => convertNoteEditToNote(e))
);
}
function convertNoteEditToNote(noteEdit: NoteEdit): Note {
return Object.assign({}, note.value, {
id: crypto.randomUUID(), // Don't use noteId
createdAt: noteEdit.updatedAt,
text: noteEdit.text,
cw: noteEdit.cw,
_shouldInsertAd_: false,
});
}
</script>
<style lang="scss" scoped>
.giivymft {
&.noGap {
> .notes {
background: var(--panel) !important;
border-radius: var(--radius);
}
}
&:not(.noGap) {
> .notes {
.qtqtichx {
background: var(--panel);
border-radius: var(--radius);
}
}
}
}
</style>

View file

@ -39,6 +39,11 @@ export const routes = [
path: "/notes/:noteId", path: "/notes/:noteId",
component: page(() => import("./pages/note.vue")), component: page(() => import("./pages/note.vue")),
}, },
{
name: "note-history",
path: "/notes/:noteId/history",
component: page(() => import("./pages/note-history.vue")),
},
{ {
path: "/clips/:clipId", path: "/clips/:clipId",
component: page(() => import("./pages/clip.vue")), component: page(() => import("./pages/clip.vue")),

View file

@ -11,6 +11,10 @@ import { noteActions } from "@/store";
import { shareAvailable } from "@/scripts/share-available"; import { shareAvailable } from "@/scripts/share-available";
import { getUserMenu } from "@/scripts/get-user-menu"; import { getUserMenu } from "@/scripts/get-user-menu";
import icon from "@/scripts/icon"; import icon from "@/scripts/icon";
import { useRouter } from "@/router";
import { notePage } from "@/filters/note";
const router = useRouter();
export function getNoteMenu(props: { export function getNoteMenu(props: {
note: entities.Note; note: entities.Note;
@ -73,6 +77,10 @@ export function getNoteMenu(props: {
}); });
} }
function showEditHistory(): void {
router.push(notePage(appearNote, { historyPage: true }));
}
function makePrivate(): void { function makePrivate(): void {
os.confirm({ os.confirm({
type: "warning", type: "warning",
@ -288,6 +296,8 @@ export function getNoteMenu(props: {
noteId: appearNote.id, noteId: appearNote.id,
}); });
const isEdited = !!appearNote.updatedAt;
const isAppearAuthor = appearNote.userId === me.id; const isAppearAuthor = appearNote.userId === me.id;
menu = [ menu = [
@ -361,6 +371,13 @@ export function getNoteMenu(props: {
action: () => togglePin(true), action: () => togglePin(true),
} }
: undefined, : undefined,
isEdited
? {
icon: `${icon('ph-clock-countdown')}`,
text: i18n.ts.noteEditHistory,
action: () => showEditHistory(),
}
: undefined,
instance.translatorAvailable instance.translatorAvailable
? { ? {
icon: `${icon("ph-translate")}`, icon: `${icon("ph-translate")}`,

View file

@ -22,6 +22,7 @@ import type {
MeDetailed, MeDetailed,
MessagingMessage, MessagingMessage,
Note, Note,
NoteEdit,
NoteFavorite, NoteFavorite,
NoteReaction, NoteReaction,
Notification, Notification,
@ -657,6 +658,14 @@ export type Endpoints = {
}; };
res: Note[]; res: Note[];
}; };
"notes/history": {
req: {
noteId: Note["id"];
limit?: number;
offset?: number;
};
res: NoteEdit[]
};
"notes/recommended-timeline": { "notes/recommended-timeline": {
req: { req: {
limit?: number; limit?: number;

View file

@ -143,9 +143,9 @@ export type Note = {
user: User; user: User;
userId: User["id"]; userId: User["id"];
reply?: Note; reply?: Note;
replyId: Note["id"]; replyId: Note["id"] | null;
renote?: Note; renote?: Note;
renoteId: Note["id"]; renoteId: Note["id"] | null;
files: DriveFile[]; files: DriveFile[];
fileIds: DriveFile["id"][]; fileIds: DriveFile["id"][];
visibility: "public" | "home" | "followers" | "specified"; visibility: "public" | "home" | "followers" | "specified";
@ -176,6 +176,15 @@ export type Note = {
isHidden?: boolean; isHidden?: boolean;
}; };
export type NoteEdit = {
id: string;
noteId: string;
text: string | null;
cw: string | null;
updatedAt: string;
fileIds: DriveFile["id"];
}
export type NoteReaction = { export type NoteReaction = {
id: ID; id: ID;
createdAt: DateString; createdAt: DateString;