Skip to content

Commit

Permalink
Merge branch 'develop' into ed25519
Browse files Browse the repository at this point in the history
  • Loading branch information
tamaina committed Mar 7, 2024
2 parents 844feb1 + f4a5740 commit 9973610
Show file tree
Hide file tree
Showing 24 changed files with 157 additions and 53 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@

### Client
- Enhance: 自分のノートの添付ファイルから直接ファイルの詳細ページに飛べるように
- Enhance: 広告がMisskeyと同一ドメインの場合はRouterで遷移するように
- Enhance: リアクション・いいねの総数を表示するように
- Enhance: リアクション受け入れが「いいねのみ」の場合はリアクション絵文字一覧を表示しないように
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
- Fix: 周年の実績が閏年を考慮しない問題を修正

### Server
-
Expand Down
4 changes: 4 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8909,6 +8909,10 @@ export interface Locale extends ILocale {
* {n}人がリアクションしました
*/
"reactedBySomeUsers": ParameterizedString<"n">;
/**
* {n}人がいいねしました
*/
"likedBySomeUsers": ParameterizedString<"n">;
/**
* {n}人がリノートしました
*/
Expand Down
1 change: 1 addition & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2355,6 +2355,7 @@ _notification:
sendTestNotification: "テスト通知を送信する"
notificationWillBeDisplayedLikeThis: "通知はこのように表示されます"
reactedBySomeUsers: "{n}人がリアクションしました"
likedBySomeUsers: "{n}人がいいねしました"
renotedBySomeUsers: "{n}人がリノートしました"
followedBySomeUsers: "{n}人にフォローされました"
flushNotification: "通知の履歴をリセットする"
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"watch": "node watch.mjs",
"restart": "pnpm build && pnpm start",
"dev": "nodemon -w src -e ts,js,mjs,cjs,json --exec \"cross-env NODE_ENV=development pnpm run restart\"",
"typecheck": "tsc --noEmit",
"typecheck": "tsc --noEmit && tsc -p test --noEmit",
"eslint": "eslint --quiet \"src/**/*.ts\"",
"lint": "pnpm typecheck && pnpm eslint",
"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs",
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/core/entities/NoteEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ export class NoteEntityService implements OnModuleInit {
visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
renoteCount: note.renoteCount,
repliesCount: note.repliesCount,
reactionCount: Object.values(note.reactions).reduce((a, b) => a + b, 0),
reactions: this.reactionService.convertLegacyReactions(note.reactions),
reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host),
reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined,
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/models/json-schema/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ export const packedNoteSchema = {
}],
},
},
reactionCount: {
type: 'number',
optional: false, nullable: false,
},
renoteCount: {
type: 'number',
optional: false, nullable: false,
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/server/web/boot.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@
//#endregion

//#region Script
function importAppScript() {
import(`/vite/${CLIENT_ENTRY}`)
async function importAppScript() {
await import(`/vite/${CLIENT_ENTRY}`)
.catch(async e => {
console.error(e);
renderError('APP_IMPORT', e);
Expand Down
8 changes: 4 additions & 4 deletions packages/backend/test/e2e/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ describe('Note', () => {
priority: 0,
value: true,
},
},
} as any,
}, alice);

assert.strictEqual(res.status, 200);
Expand Down Expand Up @@ -784,7 +784,7 @@ describe('Note', () => {
priority: 1,
value: 0,
},
},
} as any,
}, alice);

assert.strictEqual(res.status, 200);
Expand Down Expand Up @@ -838,7 +838,7 @@ describe('Note', () => {
priority: 1,
value: 0,
},
},
} as any,
}, alice);

assert.strictEqual(res.status, 200);
Expand Down Expand Up @@ -894,7 +894,7 @@ describe('Note', () => {
priority: 1,
value: 1,
},
},
} as any,
}, alice);

assert.strictEqual(res.status, 200);
Expand Down
20 changes: 19 additions & 1 deletion packages/backend/test/e2e/timelines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -890,17 +890,35 @@ describe('Timelines', () => {

const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice);
await sleep(1000);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });

await waitForPushToTl();

const res = await api('notes/user-list-timeline', { listId: list.id, withReplies: false }, alice);
const res = await api('notes/user-list-timeline', { listId: list.id }, alice);

assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
});

test.concurrent('withReplies: false でリスインしているフォローしていないユーザーの他人への返信が含まれない', async () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);

const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });

await waitForPushToTl();

const res = await api('notes/user-list-timeline', { listId: list.id }, alice);

assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
});

test.concurrent('withReplies: true でリスインしているフォローしていないユーザーの他人への返信が含まれる', async () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);

Expand Down
7 changes: 7 additions & 0 deletions packages/backend/test/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type FIXME = any;
4 changes: 2 additions & 2 deletions packages/backend/test/prelude/get-api-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
*/

import Ajv from 'ajv';
import { Schema } from '@/misc/schema';
import { Schema } from '@/misc/json-schema.js';

export const getValidator = (paramDef: Schema) => {
const ajv = new Ajv({
const ajv = new Ajv.default({
useDefaults: true,
});
ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
Expand Down
3 changes: 2 additions & 1 deletion packages/backend/test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedParameters": false,
"noUnusedLocals": true,
"noUnusedLocals": false,
"noFallthroughCasesInSwitch": true,
"declaration": false,
"sourceMap": true,
Expand All @@ -18,6 +18,7 @@
"strict": true,
"strictNullChecks": true,
"strictPropertyInitialization": false,
"skipLibCheck": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"resolveJsonModule": true,
Expand Down
3 changes: 2 additions & 1 deletion packages/backend/test/unit/RelayService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ describe('RelayService', () => {

expect(queueService.deliver).toHaveBeenCalled();
expect(queueService.deliver.mock.lastCall![1]?.type).toBe('Undo');
expect(queueService.deliver.mock.lastCall![1]?.object.type).toBe('Follow');
expect(typeof queueService.deliver.mock.lastCall![1]?.object).toBe('object');
expect((queueService.deliver.mock.lastCall![1]?.object as any).type).toBe('Follow');
expect(queueService.deliver.mock.lastCall![2]).toBe('https://example.com');
//expect(queueService.deliver.mock.lastCall![0].username).toBe('relay.actor');

Expand Down
28 changes: 20 additions & 8 deletions packages/frontend/src/boot/main-boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,26 @@ export async function mainBoot() {
if ($i.followersCount >= 500) claimAchievement('followers500');
if ($i.followersCount >= 1000) claimAchievement('followers1000');

if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365) {
claimAchievement('passedSinceAccountCreated1');
}
if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 2) {
claimAchievement('passedSinceAccountCreated2');
}
if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 3) {
const createdAt = new Date($i.createdAt);
const createdAtThreeYearsLater = new Date($i.createdAt);
createdAtThreeYearsLater.setFullYear(createdAtThreeYearsLater.getFullYear() + 3);
if (now >= createdAtThreeYearsLater) {
claimAchievement('passedSinceAccountCreated3');
claimAchievement('passedSinceAccountCreated2');
claimAchievement('passedSinceAccountCreated1');
} else {
const createdAtTwoYearsLater = new Date($i.createdAt);
createdAtTwoYearsLater.setFullYear(createdAtTwoYearsLater.getFullYear() + 2);
if (now >= createdAtTwoYearsLater) {
claimAchievement('passedSinceAccountCreated2');
claimAchievement('passedSinceAccountCreated1');
} else {
const createdAtOneYearLater = new Date($i.createdAt);
createdAtOneYearLater.setFullYear(createdAtOneYearLater.getFullYear() + 1);
if (now >= createdAtOneYearLater) {
claimAchievement('passedSinceAccountCreated1');
}
}
}

if (claimedAchievements.length >= 30) {
Expand Down Expand Up @@ -229,7 +241,7 @@ export async function mainBoot() {

const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt');
const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo');
if (neverShowDonationInfo !== 'true' && (new Date($i.createdAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) {
if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) {
if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) {
popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {}, 'closed');
}
Expand Down
25 changes: 17 additions & 8 deletions packages/frontend/src/components/MkNote.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</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>
</div>
<MkReactionsViewer :note="appearNote" :maxNumber="16" @mockUpdateMyReaction="emitUpdReaction">
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" :note="appearNote" :maxNumber="16" @mockUpdateMyReaction="emitUpdReaction">
<template #more>
<div :class="$style.reactionOmitted">{{ i18n.ts.more }}</div>
</template>
</MkReactionsViewer>
<footer :class="$style.footer">
<button :class="$style.footerButton" class="_button" @click="reply()">
<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
v-if="canRenote"
Expand All @@ -111,17 +111,17 @@ SPDX-License-Identifier: AGPL-3.0-only
@mousedown="renote()"
>
<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 v-else :class="$style.footerButton" class="_button" disabled>
<i class="ti ti-ban"></i>
</button>
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()">
<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
<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>
</button>
<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)">
<i class="ti ti-minus"></i>
<p v-if="appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
</button>
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()">
<i class="ti ti-paperclip"></i>
Expand Down Expand Up @@ -175,6 +175,7 @@ import { pleaseLogin } from '@/scripts/please-login.js';
import { focusPrev, focusNext } from '@/scripts/focus.js';
import { checkWordMute } from '@/scripts/check-word-mute.js';
import { userPage } from '@/filters/user.js';
import number from '@/filters/number.js';
import * as os from '@/os.js';
import * as sound from '@/scripts/sound.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
Expand Down Expand Up @@ -420,6 +421,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 {
if (props.mock) {
return;
Expand Down
31 changes: 20 additions & 11 deletions packages/frontend/src/components/MkNoteDetailed.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkTime :time="appearNote.createdAt" mode="detail" colored/>
</MkA>
</div>
<MkReactionsViewer ref="reactionsViewer" :note="appearNote"/>
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" ref="reactionsViewer" :note="appearNote"/>
<button class="_button" :class="$style.noteFooterButton" @click="reply()">
<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
v-if="canRenote"
Expand All @@ -119,17 +119,17 @@ SPDX-License-Identifier: AGPL-3.0-only
@mousedown="renote()"
>
<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 v-else class="_button" :class="$style.noteFooterButton" disabled>
<i class="ti ti-ban"></i>
</button>
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @mousedown="react()">
<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
<button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
<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>
</button>
<button v-if="appearNote.myReaction != null" ref="reactButton" class="_button" :class="[$style.noteFooterButton, $style.reacted]" @click="undoReact(appearNote)">
<i class="ti ti-minus"></i>
<p v-if="appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p>
</button>
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown="clip()">
<i class="ti ti-paperclip"></i>
Expand Down Expand Up @@ -209,6 +209,7 @@ import { pleaseLogin } from '@/scripts/please-login.js';
import { checkWordMute } from '@/scripts/check-word-mute.js';
import { userPage } from '@/filters/user.js';
import { notePage } from '@/filters/note.js';
import number from '@/filters/number.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import * as sound from '@/scripts/sound.js';
Expand Down Expand Up @@ -401,14 +402,22 @@ function react(viaKeyboard = false): void {
}
}
function undoReact(note): void {
const oldReaction = note.myReaction;
function undoReact(targetNote: Misskey.entities.Note): void {
const oldReaction = targetNote.myReaction;
if (!oldReaction) return;
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 {
const isLink = (el: HTMLElement): boolean => {
if (el.tagName === 'A') return true;
Expand Down
Loading

0 comments on commit 9973610

Please sign in to comment.