Merge remote-tracking branch 'misskey-dev/develop' into io

This commit is contained in:
まっちゃとーにゅ 2024-03-07 05:22:19 +09:00
commit 7abf5dcb78
No known key found for this signature in database
GPG key ID: 6AFBBF529601C1DB
21 changed files with 128 additions and 45 deletions

View file

@ -4,7 +4,10 @@
- -
### Client ### Client
- - Enhance: 自分のノートの添付ファイルから直接ファイルの詳細ページに飛べるように
- Enhance: リアクション・いいねの総数を表示するように
- Enhance: リアクション受け入れが「いいねのみ」の場合はリアクション絵文字一覧を表示しないように
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
### Server ### Server
- -

4
locales/index.d.ts vendored
View file

@ -9089,6 +9089,10 @@ export interface Locale extends ILocale {
* {n} * {n}
*/ */
"reactedBySomeUsers": ParameterizedString<"n">; "reactedBySomeUsers": ParameterizedString<"n">;
/**
* {n}
*/
"likedBySomeUsers": ParameterizedString<"n">;
/** /**
* {n} * {n}
*/ */

View file

@ -2403,6 +2403,7 @@ _notification:
sendTestNotification: "テスト通知を送信する" sendTestNotification: "テスト通知を送信する"
notificationWillBeDisplayedLikeThis: "通知はこのように表示されます" notificationWillBeDisplayedLikeThis: "通知はこのように表示されます"
reactedBySomeUsers: "{n}人がリアクションしました" reactedBySomeUsers: "{n}人がリアクションしました"
likedBySomeUsers: "{n}人がいいねしました"
renotedBySomeUsers: "{n}人がリノートしました" renotedBySomeUsers: "{n}人がリノートしました"
followedBySomeUsers: "{n}人にフォローされました" followedBySomeUsers: "{n}人にフォローされました"
flushNotification: "通知の履歴をリセットする" flushNotification: "通知の履歴をリセットする"

View file

@ -250,7 +250,7 @@ export class DriveFileEntityService {
folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, { folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, {
detail: true, detail: true,
}) : null, }) : null,
userId: opts.withUser ? file.userId : null, userId: file.userId,
user: (opts.withUser && file.userId) ? this.userEntityService.pack(file.userId, me) : null, user: (opts.withUser && file.userId) ? this.userEntityService.pack(file.userId, me) : null,
}); });
} }

View file

@ -338,6 +338,7 @@ export class NoteEntityService implements OnModuleInit {
visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined, visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
renoteCount: note.renoteCount, renoteCount: note.renoteCount,
repliesCount: note.repliesCount, repliesCount: note.repliesCount,
reactionCount: Object.values(note.reactions).reduce((a, b) => a + b, 0),
reactions: this.reactionService.convertLegacyReactions(note.reactions), reactions: this.reactionService.convertLegacyReactions(note.reactions),
reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host), reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host),
reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined, reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined,

View file

@ -223,6 +223,10 @@ export const packedNoteSchema = {
}], }],
}, },
}, },
reactionCount: {
type: 'number',
optional: false, nullable: false,
},
renoteCount: { renoteCount: {
type: 'number', type: 'number',
optional: false, nullable: false, optional: false, nullable: false,

View file

@ -72,8 +72,8 @@
//#endregion //#endregion
//#region Script //#region Script
function importAppScript() { async function importAppScript() {
import(`/vite/${CLIENT_ENTRY}`) await import(`/vite/${CLIENT_ENTRY}`)
.catch(async e => { .catch(async e => {
console.error(e); console.error(e);
renderError('APP_IMPORT', e); renderError('APP_IMPORT', e);

View file

@ -97,8 +97,6 @@ function showMenu(ev: MouseEvent) {
if (iAmModerator) { if (iAmModerator) {
menu.push({ menu.push({
type: 'divider',
}, {
text: props.audio.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, text: props.audio.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
icon: props.audio.isSensitive ? 'ti ti-eye' : 'ti ti-eye-exclamation', icon: props.audio.isSensitive ? 'ti ti-eye' : 'ti ti-eye-exclamation',
danger: true, danger: true,
@ -106,6 +104,17 @@ function showMenu(ev: MouseEvent) {
}); });
} }
if ($i?.id === props.audio.userId) {
menu.push({
type: 'divider',
}, {
type: 'link' as const,
text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle',
to: `/my/drive/file/${props.audio.id}`,
});
}
menuShowing.value = true; menuShowing.value = true;
os.popupMenu(menu, ev.currentTarget ?? ev.target, { os.popupMenu(menu, ev.currentTarget ?? ev.target, {
align: 'right', align: 'right',

View file

@ -125,6 +125,13 @@ function showMenu(ev: MouseEvent) {
action: () => { action: () => {
os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true }); os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true });
}, },
}] : []), ...($i?.id === props.image.userId ? [{
type: 'divider' as const,
}, {
type: 'link' as const,
text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle',
to: `/my/drive/file/${props.image.id}`,
}] : [])], ev.currentTarget ?? ev.target); }] : [])], ev.currentTarget ?? ev.target);
} }

View file

@ -127,8 +127,6 @@ function showMenu(ev: MouseEvent) {
if (iAmModerator) { if (iAmModerator) {
menu.push({ menu.push({
type: 'divider',
}, {
text: props.video.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, text: props.video.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
icon: props.video.isSensitive ? 'ti ti-eye' : 'ti ti-eye-exclamation', icon: props.video.isSensitive ? 'ti ti-eye' : 'ti ti-eye-exclamation',
danger: true, danger: true,
@ -136,6 +134,17 @@ function showMenu(ev: MouseEvent) {
}); });
} }
if ($i?.id === props.video.userId) {
menu.push({
type: 'divider',
}, {
type: 'link' as const,
text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle',
to: `/my/drive/file/${props.video.id}`,
});
}
menuShowing.value = true; menuShowing.value = true;
os.popupMenu(menu, ev.currentTarget ?? ev.target, { os.popupMenu(menu, ev.currentTarget ?? ev.target, {
align: 'right', align: 'right',

View file

@ -93,7 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA> <MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
</div> </div>
<MkReactionsViewer :note="appearNote" :maxNumber="16" @mockUpdateMyReaction="emitUpdReaction"> <MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" :note="appearNote" :maxNumber="16" @mockUpdateMyReaction="emitUpdReaction">
<template #more> <template #more>
<div :class="$style.reactionOmitted">{{ i18n.ts.more }}</div> <div :class="$style.reactionOmitted">{{ i18n.ts.more }}</div>
</template> </template>
@ -101,7 +101,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<footer :class="$style.footer"> <footer :class="$style.footer">
<button :class="$style.footerButton" class="_button" @click="reply()"> <button :class="$style.footerButton" class="_button" @click="reply()">
<i class="ti ti-arrow-back-up"></i> <i class="ti ti-arrow-back-up"></i>
<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ appearNote.repliesCount }}</p> <p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.repliesCount) }}</p>
</button> </button>
<button <button
v-if="canRenote" v-if="canRenote"
@ -111,17 +111,17 @@ SPDX-License-Identifier: AGPL-3.0-only
@mousedown="renote()" @mousedown="renote()"
> >
<i class="ti ti-repeat"></i> <i class="ti ti-repeat"></i>
<p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ appearNote.renoteCount }}</p> <p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.renoteCount) }}</p>
</button> </button>
<button v-else :class="$style.footerButton" class="_button" disabled> <button v-else :class="$style.footerButton" class="_button" disabled>
<i class="ti ti-ban"></i> <i class="ti ti-ban"></i>
</button> </button>
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()"> <button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i>
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
<i v-else class="ti ti-plus"></i> <i v-else class="ti ti-plus"></i>
</button> <p v-if="appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)">
<i class="ti ti-minus"></i>
</button> </button>
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()"> <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()">
<i class="ti ti-paperclip"></i> <i class="ti ti-paperclip"></i>
@ -169,6 +169,7 @@ import { pleaseLogin } from '@/scripts/please-login.js';
import { focusPrev, focusNext } from '@/scripts/focus.js'; import { focusPrev, focusNext } from '@/scripts/focus.js';
import { checkWordMute } from '@/scripts/check-word-mute.js'; import { checkWordMute } from '@/scripts/check-word-mute.js';
import { userPage } from '@/filters/user.js'; import { userPage } from '@/filters/user.js';
import number from '@/filters/number.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import * as sound from '@/scripts/sound.js'; import * as sound from '@/scripts/sound.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
@ -406,6 +407,14 @@ function undoReact(targetNote: Misskey.entities.Note): void {
}); });
} }
function toggleReact() {
if (appearNote.value.myReaction == null) {
react();
} else {
undoReact(appearNote.value);
}
}
function onContextmenu(ev: MouseEvent): void { function onContextmenu(ev: MouseEvent): void {
if (props.mock) { if (props.mock) {
return; return;

View file

@ -106,10 +106,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkTime :time="appearNote.createdAt" mode="detail" colored/> <MkTime :time="appearNote.createdAt" mode="detail" colored/>
</MkA> </MkA>
</div> </div>
<MkReactionsViewer ref="reactionsViewer" :note="appearNote"/> <MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" ref="reactionsViewer" :note="appearNote"/>
<button class="_button" :class="$style.noteFooterButton" @click="reply()"> <button class="_button" :class="$style.noteFooterButton" @click="reply()">
<i class="ti ti-arrow-back-up"></i> <i class="ti ti-arrow-back-up"></i>
<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.repliesCount }}</p> <p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.repliesCount) }}</p>
</button> </button>
<button <button
v-if="canRenote" v-if="canRenote"
@ -119,17 +119,17 @@ SPDX-License-Identifier: AGPL-3.0-only
@mousedown="renote()" @mousedown="renote()"
> >
<i class="ti ti-repeat"></i> <i class="ti ti-repeat"></i>
<p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.renoteCount }}</p> <p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.renoteCount) }}</p>
</button> </button>
<button v-else class="_button" :class="$style.noteFooterButton" disabled> <button v-else class="_button" :class="$style.noteFooterButton" disabled>
<i class="ti ti-ban"></i> <i class="ti ti-ban"></i>
</button> </button>
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @mousedown="react()"> <button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i>
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
<i v-else class="ti ti-plus"></i> <i v-else class="ti ti-plus"></i>
</button> <p v-if="appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p>
<button v-if="appearNote.myReaction != null" ref="reactButton" class="_button" :class="[$style.noteFooterButton, $style.reacted]" @click="undoReact(appearNote)">
<i class="ti ti-minus"></i>
</button> </button>
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown="clip()"> <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown="clip()">
<i class="ti ti-paperclip"></i> <i class="ti ti-paperclip"></i>
@ -209,6 +209,7 @@ import { pleaseLogin } from '@/scripts/please-login.js';
import { checkWordMute } from '@/scripts/check-word-mute.js'; import { checkWordMute } from '@/scripts/check-word-mute.js';
import { userPage } from '@/filters/user.js'; import { userPage } from '@/filters/user.js';
import { notePage } from '@/filters/note.js'; import { notePage } from '@/filters/note.js';
import number from '@/filters/number.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import * as sound from '@/scripts/sound.js'; import * as sound from '@/scripts/sound.js';
@ -402,14 +403,22 @@ function react(viaKeyboard = false): void {
} }
} }
function undoReact(note): void { function undoReact(targetNote: Misskey.entities.Note): void {
const oldReaction = note.myReaction; const oldReaction = targetNote.myReaction;
if (!oldReaction) return; if (!oldReaction) return;
misskeyApi('notes/reactions/delete', { misskeyApi('notes/reactions/delete', {
noteId: note.id, noteId: targetNote.id,
}); });
} }
function toggleReact() {
if (appearNote.value.myReaction == null) {
react();
} else {
undoReact(appearNote.value);
}
}
function onContextmenu(ev: MouseEvent): void { function onContextmenu(ev: MouseEvent): void {
const isLink = (el: HTMLElement): boolean => { const isLink = (el: HTMLElement): boolean => {
if (el.tagName === 'A') return true; if (el.tagName === 'A') return true;

View file

@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.head"> <div :class="$style.head">
<MkAvatar v-if="['pollEnded', 'note'].includes(notification.type) && notification.note" :class="$style.icon" :user="notification.note.user" link preview/> <MkAvatar v-if="['pollEnded', 'note'].includes(notification.type) && notification.note" :class="$style.icon" :user="notification.note.user" link preview/>
<MkAvatar v-else-if="['roleAssigned', 'achievementEarned'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/> <MkAvatar v-else-if="['roleAssigned', 'achievementEarned'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
<div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroupHeart]"><i class="ti ti-heart" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div> <div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div> <div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/> <img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
@ -57,6 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span> <span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span> <span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA> <MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
<span v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'">{{ i18n.tsx._notification.likedBySomeUsers({ n: notification.reactions.length }) }}</span>
<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: notification.reactions.length }) }}</span> <span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: notification.reactions.length }) }}</span>
<span v-else-if="notification.type === 'renote:grouped'">{{ i18n.tsx._notification.renotedBySomeUsers({ n: notification.users.length }) }}</span> <span v-else-if="notification.type === 'renote:grouped'">{{ i18n.tsx._notification.renotedBySomeUsers({ n: notification.users.length }) }}</span>
<span v-else-if="notification.type === 'app'">{{ notification.header }}</span> <span v-else-if="notification.type === 'app'">{{ notification.header }}</span>
@ -201,6 +203,7 @@ const rejectFollowRequest = () => {
} }
.icon_reactionGroup, .icon_reactionGroup,
.icon_reactionGroupHeart,
.icon_renoteGroup { .icon_renoteGroup {
display: grid; display: grid;
align-items: center; align-items: center;
@ -213,11 +216,15 @@ const rejectFollowRequest = () => {
} }
.icon_reactionGroup { .icon_reactionGroup {
background: #e99a0b; background: var(--eventReaction);
}
.icon_reactionGroupHeart {
background: var(--eventReactionHeart);
} }
.icon_renoteGroup { .icon_renoteGroup {
background: #36d298; background: var(--eventRenote);
} }
.icon_app { .icon_app {
@ -246,49 +253,49 @@ const rejectFollowRequest = () => {
.t_follow, .t_followRequestAccepted, .t_receiveFollowRequest { .t_follow, .t_followRequestAccepted, .t_receiveFollowRequest {
padding: 3px; padding: 3px;
background: #36aed2; background: var(--eventFollow);
pointer-events: none; pointer-events: none;
} }
.t_renote { .t_renote {
padding: 3px; padding: 3px;
background: #36d298; background: var(--eventRenote);
pointer-events: none; pointer-events: none;
} }
.t_quote { .t_quote {
padding: 3px; padding: 3px;
background: #36d298; background: var(--eventRenote);
pointer-events: none; pointer-events: none;
} }
.t_reply { .t_reply {
padding: 3px; padding: 3px;
background: #007aff; background: var(--eventReply);
pointer-events: none; pointer-events: none;
} }
.t_mention { .t_mention {
padding: 3px; padding: 3px;
background: #88a6b7; background: var(--eventOther);
pointer-events: none; pointer-events: none;
} }
.t_pollEnded { .t_pollEnded {
padding: 3px; padding: 3px;
background: #88a6b7; background: var(--eventOther);
pointer-events: none; pointer-events: none;
} }
.t_achievementEarned { .t_achievementEarned {
padding: 3px; padding: 3px;
background: #cb9a11; background: var(--eventAchievement);
pointer-events: none; pointer-events: none;
} }
.t_roleAssigned { .t_roleAssigned {
padding: 3px; padding: 3px;
background: #88a6b7; background: var(--eventOther);
pointer-events: none; pointer-events: none;
} }

View file

@ -63,6 +63,7 @@ const exampleNote = reactive<Misskey.entities.Note>({
reactionAcceptance: null, reactionAcceptance: null,
renoteCount: 0, renoteCount: 0,
repliesCount: 1, repliesCount: 1,
reactionCount: 0,
reactions: {}, reactions: {},
reactionEmojis: {}, reactionEmojis: {},
fileIds: [], fileIds: [],

View file

@ -68,6 +68,7 @@ const exampleCWNote = reactive<Misskey.entities.Note>({
reactionAcceptance: null, reactionAcceptance: null,
renoteCount: 0, renoteCount: 0,
repliesCount: 1, repliesCount: 1,
reactionCount: 0,
reactions: {}, reactions: {},
reactionEmojis: {}, reactionEmojis: {},
fileIds: [], fileIds: [],

View file

@ -58,6 +58,7 @@ const exampleNote = reactive<Misskey.entities.Note>({
reactionAcceptance: null, reactionAcceptance: null,
renoteCount: 0, renoteCount: 0,
repliesCount: 1, repliesCount: 1,
reactionCount: 0,
reactions: {}, reactions: {},
reactionEmojis: {}, reactionEmojis: {},
fileIds: ['0000000002'], fileIds: ['0000000002'],

View file

@ -373,7 +373,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
this.currentRoute.value = res.route; this.currentRoute.value = res.route;
this.currentKey = res.route.globalCacheKey ?? key ?? path; this.currentKey = res.route.globalCacheKey ?? key ?? path;
if (emitChange) { if (emitChange && res.route.path !== '/:(*)') {
this.emit('change', { this.emit('change', {
beforePath, beforePath,
path, path,
@ -408,13 +408,17 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
if (cancel) return; if (cancel) return;
} }
const res = this.navigate(path, null); const res = this.navigate(path, null);
this.emit('push', { if (res.route.path === '/:(*)') {
beforePath, location.href = path;
path: res._parsedRoute.fullPath, } else {
route: res.route, this.emit('push', {
props: res.props, beforePath,
key: this.currentKey, path: res._parsedRoute.fullPath,
}); route: res.route,
props: res.props,
key: this.currentKey,
});
}
} }
public replace(path: string, key?: string | null) { public replace(path: string, key?: string | null) {

View file

@ -35,6 +35,7 @@ export function useNoteCapture(props: {
const currentCount = (note.value.reactions || {})[reaction] || 0; const currentCount = (note.value.reactions || {})[reaction] || 0;
note.value.reactions[reaction] = currentCount + 1; note.value.reactions[reaction] = currentCount + 1;
note.value.reactionCount += 1;
if ($i && (body.userId === $i.id)) { if ($i && (body.userId === $i.id)) {
note.value.myReaction = reaction; note.value.myReaction = reaction;
@ -49,6 +50,7 @@ export function useNoteCapture(props: {
const currentCount = (note.value.reactions || {})[reaction] || 0; const currentCount = (note.value.reactions || {})[reaction] || 0;
note.value.reactions[reaction] = Math.max(0, currentCount - 1); note.value.reactions[reaction] = Math.max(0, currentCount - 1);
note.value.reactionCount = Math.max(0, note.value.reactionCount - 1);
if (note.value.reactions[reaction] === 0) delete note.value.reactions[reaction]; if (note.value.reactions[reaction] === 0) delete note.value.reactions[reaction];
if ($i && (body.userId === $i.id)) { if ($i && (body.userId === $i.id)) {

View file

@ -22,6 +22,13 @@
} }
//--ad: rgb(255 169 0 / 10%); //--ad: rgb(255 169 0 / 10%);
--eventFollow: #36aed2;
--eventRenote: #36d298;
--eventReply: #007aff;
--eventReactionHeart: #dd2e44;
--eventReaction: #e99a0b;
--eventAchievement: #cb9a11;
--eventOther: #88a6b7;
} }
::selection { ::selection {

View file

@ -48,6 +48,9 @@ const devConfig = {
}, },
'/url': httpUrl, '/url': httpUrl,
'/proxy': httpUrl, '/proxy': httpUrl,
'/_info_card_': httpUrl,
'/bios': httpUrl,
'/cli': httpUrl,
}, },
}, },
build: { build: {

View file

@ -4095,6 +4095,7 @@ export type components = {
reactions: { reactions: {
[key: string]: number; [key: string]: number;
}; };
reactionCount: number;
renoteCount: number; renoteCount: number;
repliesCount: number; repliesCount: number;
uri?: string; uri?: string;