feat: add post history page
This commit is contained in:
parent
d64d133d7f
commit
bab704992f
11 changed files with 186 additions and 5 deletions
|
@ -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"
|
||||||
|
|
|
@ -2060,3 +2060,4 @@ noAltTextWarning: 有些附件没有描述。您是否忘记写描述了?
|
||||||
showNoAltTextWarning: 当您尝试发布没有描述的帖子附件时显示警告
|
showNoAltTextWarning: 当您尝试发布没有描述的帖子附件时显示警告
|
||||||
autocorrectNoteLanguage: 当帖子语言不符合自动检测的结果的时候显示警告
|
autocorrectNoteLanguage: 当帖子语言不符合自动检测的结果的时候显示警告
|
||||||
incorrectLanguageWarning: "看上去您帖子使用的语言是{detected},但您选择的语言是{current}。\n要改为以{detected}发帖吗?"
|
incorrectLanguageWarning: "看上去您帖子使用的语言是{detected},但您选择的语言是{current}。\n要改为以{detected}发帖吗?"
|
||||||
|
noteEditHistory: "帖子编辑历史"
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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}`;
|
||||||
};
|
};
|
||||||
|
|
123
packages/client/src/pages/note-history.vue
Normal file
123
packages/client/src/pages/note-history.vue
Normal 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>
|
|
@ -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")),
|
||||||
|
|
|
@ -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")}`,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue