diff --git a/src/client/components/note.vue b/src/client/components/note.vue index f3e2bb8ba9..dc3cce9e57 100644 --- a/src/client/components/note.vue +++ b/src/client/components/note.vue @@ -1,7 +1,7 @@ <template> <div class="note _panel" - v-show="!isDeleted && !hideThisNote" + v-show="!isDeleted" :tabindex="!isDeleted ? '-1' : null" :class="{ renote: isRenote }" v-hotkey="keymap" @@ -37,16 +37,16 @@ <mk-avatar class="avatar" :user="appearNote.user" v-once/> <div class="main"> <x-note-header class="header" :note="appearNote" :mini="true"/> - <div class="body" v-if="appearNote.deletedAt == null" ref="noteBody"> + <div class="body" ref="noteBody"> <p v-if="appearNote.cw != null" class="cw"> - <mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis" v-once/> + <mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$store.state.i" :custom-emojis="emojis" v-once/> <x-cw-button v-model="showContent" :note="appearNote"/> </p> <div class="content" v-show="appearNote.cw == null || showContent"> <div class="text"> <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $t('private') }})</span> <router-link class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><fa :icon="faReply"/></router-link> - <mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis" v-once/> + <mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="emojis" v-once/> <a class="rp" v-if="appearNote.renote != null">RN:</a> </div> <div class="files" v-if="appearNote.files.length > 0"> @@ -57,8 +57,8 @@ <div class="renote" v-if="appearNote.renote"><x-note-preview :note="appearNote.renote"/></div> </div> </div> - <footer v-if="appearNote.deletedAt == null" class="footer"> - <x-reactions-viewer :note="appearNote" ref="reactionsViewer"/> + <footer class="footer"> + <x-reactions-viewer :note="appearNote" :reactions="reactions" :my-reaction="myReaction" :emojis="emojis" ref="reactionsViewer"/> <button @click="reply()" class="button _button"> <template v-if="appearNote.reply"><fa :icon="faReplyAll"/></template> <template v-else><fa :icon="faReply"/></template> @@ -70,17 +70,16 @@ <button v-else class="button _button"> <fa :icon="faBan"/> </button> - <button v-if="!isMyNote && appearNote.myReaction == null" class="button _button" @click="react()" ref="reactButton"> + <button v-if="!isMyNote && myReaction == null" class="button _button" @click="react()" ref="reactButton"> <fa :icon="faPlus"/> </button> - <button v-if="!isMyNote && appearNote.myReaction != null" class="button _button reacted" @click="undoReact(appearNote)" ref="reactButton"> + <button v-if="!isMyNote && myReaction != null" class="button _button reacted" @click="undoReact()" ref="reactButton"> <fa :icon="faMinus"/> </button> <button class="button _button" @click="menu()" ref="menuButton"> <fa :icon="faEllipsisH"/> </button> </footer> - <div class="deleted" v-if="appearNote.deletedAt != null">{{ $t('deleted') }}</div> </div> </article> <x-sub v-for="note in replies" :key="note.id" :note="note" class="reply" :detail="true"/> @@ -142,7 +141,10 @@ export default Vue.extend({ conversation: [], replies: [], showContent: false, - hideThisNote: false, + isDeleted: false, + myReaction: null, + reactions: {}, + emojis: [], noteBody: this.$refs.noteBody, faEdit, faBolt, faTimes, faBullhorn, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faBiohazard, faPlug }; @@ -186,10 +188,6 @@ export default Vue.extend({ return this.isRenote ? this.note.renote : this.note; }, - isDeleted(): boolean { - return this.appearNote.deletedAt != null || this.note.deletedAt != null; - }, - isMyNote(): boolean { return this.$store.getters.isSignedIn && (this.$store.state.i.id === this.appearNote.userId); }, @@ -203,9 +201,7 @@ export default Vue.extend({ }, reactionsCount(): number { - return this.appearNote.reactions - ? sum(Object.values(this.appearNote.reactions)) - : 0; + return sum(Object.values(this.reactions)); }, urls(): string[] { @@ -232,6 +228,10 @@ export default Vue.extend({ }, created() { + this.emojis = [...this.appearNote.emojis]; + this.reactions = { ...this.appearNote.reactions }; + this.myReaction = this.appearNote.myReaction; + if (this.$store.getters.isSignedIn) { this.connection = this.$root.stream; } @@ -261,7 +261,7 @@ export default Vue.extend({ this.connection.on('_connected_', this.onStreamConnected); } - this.noteBody = this.$refs.noteBody + this.noteBody = this.$refs.noteBody; }, beforeDestroy() { @@ -277,7 +277,7 @@ export default Vue.extend({ (this as any).$root.api('promo/read', { noteId: this.appearNote.id }); - this.hideThisNote = true; + this.isDeleted = true; }, capture(withHandler = false) { @@ -310,26 +310,20 @@ export default Vue.extend({ const reaction = body.reaction; if (body.emoji) { - const emojis = this.appearNote.emojis || []; - if (!emojis.includes(body.emoji)) { - emojis.push(body.emoji); - Vue.set(this.appearNote, 'emojis', emojis); + if (!this.emojis.includes(body.emoji)) { + this.emojis.push(body.emoji); } } - if (this.appearNote.reactions == null) { - Vue.set(this.appearNote, 'reactions', {}); - } - - if (this.appearNote.reactions[reaction] == null) { - Vue.set(this.appearNote.reactions, reaction, 0); + if (this.reactions[reaction] == null) { + Vue.set(this.reactions, reaction, 0); } // Increment the count - this.appearNote.reactions[reaction]++; + this.reactions[reaction]++; - if (body.userId == this.$store.state.i.id) { - Vue.set(this.appearNote, 'myReaction', reaction); + if (body.userId === this.$store.state.i.id) { + this.myReaction = reaction; } break; } @@ -337,19 +331,15 @@ export default Vue.extend({ case 'unreacted': { const reaction = body.reaction; - if (this.appearNote.reactions == null) { - return; - } - - if (this.appearNote.reactions[reaction] == null) { + if (this.reactions[reaction] == null) { return; } // Decrement the count - if (this.appearNote.reactions[reaction] > 0) this.appearNote.reactions[reaction]--; + if (this.reactions[reaction] > 0) this.reactions[reaction]--; - if (body.userId == this.$store.state.i.id) { - Vue.set(this.appearNote, 'myReaction', null); + if (body.userId === this.$store.state.i.id) { + this.myReaction = null; } break; } @@ -357,19 +347,14 @@ export default Vue.extend({ case 'pollVoted': { const choice = body.choice; this.appearNote.poll.choices[choice].votes++; - if (body.userId == this.$store.state.i.id) { + if (body.userId === this.$store.state.i.id) { Vue.set(this.appearNote.poll.choices[choice], 'isVoted', true); } break; } case 'deleted': { - Vue.set(this.appearNote, 'deletedAt', body.deletedAt); - Vue.set(this.appearNote, 'renote', null); - this.appearNote.text = null; - this.appearNote.fileIds = []; - this.appearNote.poll = null; - this.appearNote.cw = null; + this.isDeleted = true; break; } } @@ -442,11 +427,11 @@ export default Vue.extend({ }); }, - undoReact(note) { - const oldReaction = note.myReaction; + undoReact() { + const oldReaction = this.myReaction; if (!oldReaction) return; this.$root.api('notes/reactions/delete', { - noteId: note.id + noteId: this.appearNote.id }); }, @@ -638,7 +623,7 @@ export default Vue.extend({ this.$root.api('notes/delete', { noteId: this.note.id }); - Vue.set(this.note, 'deletedAt', new Date()); + this.isDeleted = true; } }], source: this.$refs.renoteTime, @@ -925,10 +910,6 @@ export default Vue.extend({ } } } - - > .deleted { - opacity: 0.7; - } } } diff --git a/src/client/components/reactions-viewer.reaction.vue b/src/client/components/reactions-viewer.reaction.vue index 639a1603ca..97d019d17f 100644 --- a/src/client/components/reactions-viewer.reaction.vue +++ b/src/client/components/reactions-viewer.reaction.vue @@ -1,7 +1,7 @@ <template> <button class="hkzvhatu _button" - :class="{ reacted: note.myReaction == reaction, canToggle }" + :class="{ reacted: myReaction == reaction, canToggle }" @click="toggleReaction(reaction)" v-if="count > 0" @touchstart="onMouseover" @@ -11,7 +11,7 @@ ref="reaction" v-particle="canToggle" > - <x-reaction-icon :reaction="reaction" :custom-emojis="note.emojis" ref="icon"/> + <x-reaction-icon :reaction="reaction" :custom-emojis="emojis" ref="icon"/> <span>{{ count }}</span> </button> </template> @@ -30,6 +30,14 @@ export default Vue.extend({ type: String, required: true, }, + myReaction: { + type: String, + required: false, + }, + emojis: { + type: Array, + required: true, + }, count: { type: Number, required: true, @@ -71,7 +79,7 @@ export default Vue.extend({ toggleReaction() { if (!this.canToggle) return; - const oldReaction = this.note.myReaction; + const oldReaction = this.myReaction; if (oldReaction) { this.$root.api('notes/reactions/delete', { noteId: this.note.id diff --git a/src/client/components/reactions-viewer.vue b/src/client/components/reactions-viewer.vue index 88e7df4646..353e72ccfa 100644 --- a/src/client/components/reactions-viewer.vue +++ b/src/client/components/reactions-viewer.vue @@ -1,6 +1,6 @@ <template> <div class="tdflqwzn" :class="{ isMe }"> - <x-reaction v-for="(count, reaction) in note.reactions" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note" :key="reaction"/> + <x-reaction v-for="(count, reaction) in reactions" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note" :my-reaction="myReaction" :emojis="emojis" :key="reaction"/> </div> </template> @@ -12,16 +12,28 @@ export default Vue.extend({ components: { XReaction }, - data() { - return { - initialReactions: new Set(Object.keys(this.note.reactions)) - }; - }, props: { note: { type: Object, required: true }, + reactions: { + type: Object, + required: true + }, + myReaction: { + type: String, + required: false, + }, + emojis: { + type: Array, + required: true, + }, + }, + data() { + return { + initialReactions: new Set(Object.keys(this.note.reactions)) + }; }, computed: { isMe(): boolean { diff --git a/src/client/components/timeline.vue b/src/client/components/timeline.vue index ce0fd95caf..5fd55e8ca2 100644 --- a/src/client/components/timeline.vue +++ b/src/client/components/timeline.vue @@ -52,8 +52,8 @@ export default Vue.extend({ }); const prepend = note => { - const _note = JSON.parse(JSON.stringify(note)); // deepcopy - (this.$refs.tl as any).prepend(_note); + Object.freeze(note); + (this.$refs.tl as any).prepend(note); this.$emit('note'); diff --git a/src/client/scripts/paging.ts b/src/client/scripts/paging.ts index 832f0720e0..006d23875c 100644 --- a/src/client/scripts/paging.ts +++ b/src/client/scripts/paging.ts @@ -74,10 +74,6 @@ export default (opts) => ({ }, methods: { - updateItem(i, item) { - Vue.set((this as any).items, i, item); - }, - reload() { this.items = []; this.init(); @@ -94,6 +90,9 @@ export default (opts) => ({ ...params, limit: this.pagination.noPaging ? (this.pagination.limit || 10) : (this.pagination.limit || 10) + 1, }).then(items => { + for (const item of items) { + Object.freeze(item); + } if (!this.pagination.noPaging && (items.length > (this.pagination.limit || 10))) { items.pop(); this.items = this.pagination.reversed ? [...items].reverse() : items; @@ -130,6 +129,9 @@ export default (opts) => ({ untilId: this.items[this.items.length - 1].id, }), }).then(items => { + for (const item of items) { + Object.freeze(item); + } if (items.length > SECOND_FETCH_LIMIT) { items.pop(); this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items);