This commit is contained in:
NoriDev 2023-10-19 18:48:59 +09:00 committed by mattyatea
parent e1eee2872d
commit 23508dc56e
7 changed files with 118 additions and 42 deletions

0
CHANGELOG_CHERRYPICK.md Normal file
View File

View File

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class NoteUpdateAtHistory1696318192428 {
name = 'NoteUpdateAtHistory1696318192428'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" ADD "updatedAtHistory" TIMESTAMP WITH TIME ZONE array`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP "updatedAtHistory"`);
}
}

View File

@ -324,6 +324,7 @@ export class NoteEntityService implements OnModuleInit {
id: note.id, id: note.id,
createdAt: this.idService.parse(note.id).date.toISOString(), createdAt: this.idService.parse(note.id).date.toISOString(),
updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined, updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined,
updatedAtHistory: note.updatedAtHistory ? note.updatedAtHistory.map(x => x.toISOString()) : undefined,
noteEditHistory: note.noteEditHistory.length ? note.noteEditHistory : 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),

View File

@ -19,6 +19,12 @@ export class MiNote {
}) })
public updatedAt: Date | null; public updatedAt: Date | null;
@Column('timestamp with time zone', {
array: true,
default: null,
})
public updatedAtHistory: Date[] | null;
@Column('varchar', { @Column('varchar', {
length: 3000, length: 3000,
array: true, array: true,

View File

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

View File

View File

@ -77,7 +77,6 @@ SPDX-License-Identifier: AGPL-3.0-only
v-if="appearNote.text" v-if="appearNote.text"
:parsedNodes="parsed" :parsedNodes="parsed"
:text="appearNote.text" :text="appearNote.text"
:author="appearNote.user"
:nyaize="'respect'" :nyaize="'respect'"
:emojiUrls="appearNote.emojis" :emojiUrls="appearNote.emojis"
:enableEmojiMenu="true" :enableEmojiMenu="true"
@ -88,7 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkLoading v-if="translating" mini/> <MkLoading v-if="translating" mini/>
<div v-else-if="translation"> <div v-else-if="translation">
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b> <b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/> <Mfm :text="translation.text" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
</div> </div>
</div> </div>
<div v-if="appearNote.files && appearNote.files.length > 0"> <div v-if="appearNote.files && appearNote.files.length > 0">
@ -143,11 +142,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</footer> </footer>
</article> </article>
<div :class="$style.tabs"> <div :class="$style.tabs">
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'replies' },{[$style.gamingDark]: gaming === 'dark',[$style.gamingLight]: gaming === 'light' && 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' },{[$style.gamingDark]: gaming === 'dark',[$style.gamingLight]: gaming === 'light' && 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'},{[$style.gamingDark]: gaming === 'dark',[$style.gamingLight]: gaming === 'light' && 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'},{[$style.gamingDark]: gaming === 'dark',[$style.gamingLight]: gaming === 'light' && 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'},{[$style.gamingDark]: gaming === 'dark',[$style.gamingLight]: gaming === 'light' && 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'},{[$style.gamingDark]: gaming === 'dark',[$style.gamingLight]: gaming === 'light' && 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' },{[$style.gamingDark]: gaming === 'dark',[$style.gamingLight]: gaming === 'light']" @click="tab = 'history'"><i class="ti ti-pencil"></i> {{ i18n.ts.edited }}</button> <button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'history' },{[$style.gamingDark]: gaming === 'dark',[$style.gamingLight]: gaming === 'light'}]" @click="tab = 'history'"><i class="ti ti-pencil"></i> {{ i18n.ts.edited }}</button>
</div> </div>
<div> <div>
<div v-if="tab === 'replies'"> <div v-if="tab === 'replies'">
@ -186,26 +184,39 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div v-else-if="tab === 'history'" :class="$style.tab_history"> <div v-else-if="tab === 'history'" :class="$style.tab_history">
<div style="display: grid;"> <div style="display: grid;">
<div v-for="text in appearNote.noteEditHistory.reverse()" :key="text"> <div v-for="(text, index) in appearNote.noteEditHistory" :key="text" :class="$style.historyRoot">
<MkNotePreview :class="$style.historyNote" :text="text"/> <MkAvatar :class="$style.avatar" :user="appearNote.user" link preview/>
<div :class="$style.historyMain">
<div :class="$style.historyHeader">
<MkUserName :user="appearNote.user" :nowrap="true"/>
<MkTime :class="$style.updatedAt" :time="appearNote.updatedAtHistory![index]"/>
</div>
<div>
<div>
<Mfm :text="text.trim()" :author="appearNote.user" :i="$i"/>
</div>
<CodeDiff
:oldString="appearNote.noteEditHistory[index - 1] || ''"
:newString="text"
:trim="true"
:hideHeader="true"
diffStyle="char"
/>
</div>
</div>
</div>
<div v-if="appearNote.noteEditHistory == null" class="_fullinfo">
<img :src="infoImageUrl" class="_ghost"/>
<div>{{ i18n.ts.nothing }}</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div v-else class="_panel" :class="$style.muted" @click="muted = false">
<I18n :src="i18n.ts.userSaysSomething" tag="small">
<template #name>
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
<MkUserName :user="appearNote.user"/>
</MkA>
</template>
</I18n>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {computed, inject, onMounted, provide, ref, shallowRef, watch} from 'vue'; import { computed, inject, onMounted, provide, ref, shallowRef, watch } from 'vue';
import * as mfm from 'mfm-js'; 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';
@ -241,6 +252,13 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkPagination, { type Paging } from '@/components/MkPagination.vue'; import MkPagination, { type Paging } from '@/components/MkPagination.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { miLocalStorage } from '@/local-storage.js';
import { infoImageUrl, instance } from '@/instance.js';
import MkPostForm from '@/components/MkPostFormSimple.vue';
import { deviceKind } from '@/scripts/device-kind.js';
const MOBILE_THRESHOLD = 500;
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
const props = defineProps<{ const props = defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
@ -250,7 +268,6 @@ const inChannel = inject('inChannel', null);
const note = ref(deepClone(props.note)); const note = ref(deepClone(props.note));
let gaming = ref(''); let gaming = ref('');
const gamingMode = computed(defaultStore.makeGetterSetter('gamingMode')); const gamingMode = computed(defaultStore.makeGetterSetter('gamingMode'));
@ -271,7 +288,7 @@ watch(darkMode, () => {
} else { } else {
gaming.value = ''; gaming.value = '';
} }
}) });
watch(gamingMode, () => { watch(gamingMode, () => {
if (darkMode.value && gamingMode.value == true) { if (darkMode.value && gamingMode.value == true) {
@ -281,7 +298,7 @@ watch(gamingMode, () => {
} else { } else {
gaming.value = ''; gaming.value = '';
} }
}) });
// plugin // plugin
if (noteViewInterruptors.length > 0) { if (noteViewInterruptors.length > 0) {
onMounted(async () => { onMounted(async () => {
@ -831,13 +848,33 @@ function loadConversation() {
width: 50px; width: 50px;
height: 50px; height: 50px;
} }
.noteFooterButton { .noteFooterButton {
&:not(:last-child) { &:not(:last-child) {
margin-right: 12px; margin-right: 12px;
} }
} }
} }
.historyRoot {
display: flex;
margin: 0;
padding: 10px;
overflow: clip;
font-size: 0.95em;
}
.historyMain {
flex: 1;
min-width: 0;
}
.historyHeader {
display: flex;
margin-bottom: 2px;
font-weight: bold;
width: 100%;
overflow: clip;
text-overflow: ellipsis;
}
.historyNote { .historyNote {
padding-top: 10px; padding-top: 10px;
@ -845,6 +882,12 @@ function loadConversation() {
overflow: auto; overflow: auto;
} }
.updatedAt {
flex-shrink: 0;
margin-left: auto;
font-size: 0.9em;
}
.muted { .muted {
padding: 8px; padding: 8px;
text-align: center; text-align: center;