ノートの編集履歴を見れるように(新規ノートのみ)

This commit is contained in:
GrapeApple0 2023-09-30 05:53:12 +00:00
parent 6840434661
commit 25763ee679
6 changed files with 47 additions and 1 deletions

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class NoteEditHistory1696044626209 {
name = 'NoteEditHistory1696044626209'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" ADD "noteEditHistory" varchar(3000) array DEFAULT '{}'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP "noteEditHistory"`);
}
}

View File

@ -309,6 +309,7 @@ export class NoteEntityService implements OnModuleInit {
id: note.id, id: note.id,
createdAt: note.createdAt.toISOString(), createdAt: note.createdAt.toISOString(),
updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined, updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined,
noteEditHistory: note.noteEditHistory.length ? note.noteEditHistory : undefined,
userId: note.userId, userId: note.userId,
user: this.userEntityService.pack(note.user ?? note.userId, me, { user: this.userEntityService.pack(note.user ?? note.userId, me, {
detail: false, detail: false,

View File

@ -29,6 +29,13 @@ export class MiNote {
}) })
public updatedAt: Date | null; public updatedAt: Date | null;
@Column('varchar', {
length: 3000,
array: true,
default: '{}',
})
public noteEditHistory: string[];
@Index() @Index()
@Column({ @Column({
...id(), ...id(),

View File

@ -22,6 +22,10 @@ export const packedNoteSchema = {
optional: true, nullable: true, optional: true, nullable: true,
format: 'date-time', format: 'date-time',
}, },
noteEditHistory: {
type: 'array',
optional: true, nullable: false,
},
deletedAt: { deletedAt: {
type: 'string', type: 'string',
optional: true, nullable: true, optional: true, nullable: true,

View File

@ -73,11 +73,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (note.userId !== me.id) { if (note.userId !== me.id) {
throw new ApiError(meta.errors.noSuchNote); throw new ApiError(meta.errors.noSuchNote);
} }
await this.notesRepository.update({ id: note.id }, { await this.notesRepository.update({ id: note.id }, {
updatedAt: new Date(), updatedAt: new Date(),
cw: ps.cw, cw: ps.cw,
text: ps.text, text: ps.text,
noteEditHistory: [...note.noteEditHistory, note.text!],
}); });
this.globalEventService.publishNoteStream(note.id, 'updated', { this.globalEventService.publishNoteStream(note.id, 'updated', {

View File

@ -137,6 +137,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'replies' }]" @click="tab = 'replies'"><i class="ti ti-arrow-back-up"></i> {{ i18n.ts.replies }}</button> <button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'replies' }]" @click="tab = 'replies'"><i class="ti ti-arrow-back-up"></i> {{ i18n.ts.replies }}</button>
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'renotes' }]" @click="tab = 'renotes'"><i class="ti ti-repeat"></i> {{ i18n.ts.renotes }}</button> <button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'renotes' }]" @click="tab = 'renotes'"><i class="ti ti-repeat"></i> {{ i18n.ts.renotes }}</button>
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'reactions' }]" @click="tab = 'reactions'"><i class="ti ti-icons"></i> {{ i18n.ts.reactions }}</button> <button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'reactions' }]" @click="tab = 'reactions'"><i class="ti ti-icons"></i> {{ i18n.ts.reactions }}</button>
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'history' }]" @click="tab = 'history'"><i class="ti ti-pencil"></i> {{ i18n.ts.edited }}</button>
</div> </div>
<div> <div>
<div v-if="tab === 'replies'" :class="$style.tab_replies"> <div v-if="tab === 'replies'" :class="$style.tab_replies">
@ -173,6 +174,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
</MkPagination> </MkPagination>
</div> </div>
<div v-else-if="tab === 'history'" :class="$style.tab_history">
<div style="display: grid;">
<div v-for="text in appearNote.noteEditHistory.reverse()" :key="text">
<MkNotePreview :class="$style.historyNote" :text="text"/>
</div>
</div>
</div>
</div> </div>
</div> </div>
<div v-else class="_panel" :class="$style.muted" @click="muted = false"> <div v-else class="_panel" :class="$style.muted" @click="muted = false">
@ -192,6 +200,7 @@ import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkNoteSub from '@/components/MkNoteSub.vue'; import MkNoteSub from '@/components/MkNoteSub.vue';
import MkNoteSimple from '@/components/MkNoteSimple.vue'; import MkNoteSimple from '@/components/MkNoteSimple.vue';
import MkNotePreview from '@/components/MkNotePreview.vue';
import MkReactionsViewer from '@/components/MkReactionsViewer.vue'; import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
import MkMediaList from '@/components/MkMediaList.vue'; import MkMediaList from '@/components/MkMediaList.vue';
import MkCwButton from '@/components/MkCwButton.vue'; import MkCwButton from '@/components/MkCwButton.vue';
@ -747,6 +756,9 @@ function loadConversation() {
padding: 16px; padding: 16px;
} }
.tab_history {
padding: 16px;
}
.reactionTabs { .reactionTabs {
display: flex; display: flex;
gap: 8px; gap: 8px;
@ -810,6 +822,12 @@ function loadConversation() {
} }
} }
.historyNote {
padding-top: 10px;
min-height: 75px;
overflow: auto;
}
.muted { .muted {
padding: 8px; padding: 8px;
text-align: center; text-align: center;