parent
49dc9e63e7
commit
4299588806
@ -1,4 +1,5 @@
|
|||||||
# DIFFRENCE
|
# DIFFRENCE
|
||||||
|
<<<<<<< HEAD
|
||||||
## 2024.9.0-yami-1.3.1
|
## 2024.9.0-yami-1.3.1
|
||||||
## Client
|
## Client
|
||||||
- フォロー/フォロワー/アナウンス/みつける/Play/ギャラリー/チャンネル/TL/ユーザー/ノートのページをログイン必須に
|
- フォロー/フォロワー/アナウンス/みつける/Play/ギャラリー/チャンネル/TL/ユーザー/ノートのページをログイン必須に
|
||||||
@ -12,6 +13,8 @@
|
|||||||
## Feat
|
## Feat
|
||||||
- ノート数を隠せるように(連合しません)
|
- ノート数を隠せるように(連合しません)
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> 00cf91cf30 (Merge pull request #39 from lqvp/master)
|
||||||
## 2024.9.0-yami-1.2.8
|
## 2024.9.0-yami-1.2.8
|
||||||
## Feat
|
## Feat
|
||||||
- Cherry-Pick アクティビティの非公開機能(hideki0403/kakurega.app)
|
- Cherry-Pick アクティビティの非公開機能(hideki0403/kakurega.app)
|
||||||
|
@ -1344,6 +1344,12 @@ _delivery:
|
|||||||
autoSuspendedForNotResponding: "Server is suspended due to no responding"
|
autoSuspendedForNotResponding: "Server is suspended due to no responding"
|
||||||
scheduledNoteDelete: "Time Bomb"
|
scheduledNoteDelete: "Time Bomb"
|
||||||
noteDeletationAt: "This note will be deleted at {time}"
|
noteDeletationAt: "This note will be deleted at {time}"
|
||||||
|
hideActivity: "Hide Activity"
|
||||||
|
hideActivityDescription: "This option prevents others from viewing your activity on your profile (Summary/Activity tab). Even with this option enabled, you can still view your activity from the Activity tab on your profile."
|
||||||
|
hideReactionUsers: "Hide Users Who Reacted"
|
||||||
|
hideReactionUsersDescription: "This option hides the list of users who reacted when hovering over the reaction and the list of users who reacted in the reactions tab on the note detail page."
|
||||||
|
hideReactionCount: "Hide Reaction Count"
|
||||||
|
|
||||||
|
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "How to play"
|
howToPlay: "How to play"
|
||||||
@ -2831,3 +2837,8 @@ _contextMenu:
|
|||||||
_reactionChecksMuting:
|
_reactionChecksMuting:
|
||||||
title: "Check mutings when get reactions"
|
title: "Check mutings when get reactions"
|
||||||
caption: "Check mutings when get reactions, but cache does not work and may increase traffic"
|
caption: "Check mutings when get reactions, but cache does not work and may increase traffic"
|
||||||
|
_hideReactionCount:
|
||||||
|
none: "Do not hide"
|
||||||
|
self: "Only my notes"
|
||||||
|
others: "Only notes of others"
|
||||||
|
all: "All notes"
|
||||||
|
38
locales/index.d.ts
vendored
38
locales/index.d.ts
vendored
@ -5401,6 +5401,26 @@ export interface Locale extends ILocale {
|
|||||||
"autoSuspendedForNotResponding": string;
|
"autoSuspendedForNotResponding": string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* アクティビティを非公開にする
|
||||||
|
*/
|
||||||
|
"hideActivity": string;
|
||||||
|
/**
|
||||||
|
* 自分のプロフィールのアクティビティ (概要/アクティビティタブ) を他人が見れないようにします。このオプションを有効にしても、自分であればプロフィールのアクティビティタブから引き続き閲覧できます。
|
||||||
|
*/
|
||||||
|
"hideActivityDescription": string;
|
||||||
|
/**
|
||||||
|
* 誰がリアクションをしたのかを非表示にする
|
||||||
|
*/
|
||||||
|
"hideReactionUsers": string;
|
||||||
|
/**
|
||||||
|
* リアクションをホバーした際のユーザー一覧と、ノート詳細ページのリアクションタブにあるリアクションをしたユーザー一覧を非表示にします
|
||||||
|
*/
|
||||||
|
"hideReactionUsersDescription": string;
|
||||||
|
/**
|
||||||
|
* リアクション数の非表示
|
||||||
|
*/
|
||||||
|
"hideReactionCount": string;
|
||||||
"_bubbleGame": {
|
"_bubbleGame": {
|
||||||
/**
|
/**
|
||||||
* 遊び方
|
* 遊び方
|
||||||
@ -10937,6 +10957,24 @@ export interface Locale extends ILocale {
|
|||||||
*/
|
*/
|
||||||
"caption": string;
|
"caption": string;
|
||||||
};
|
};
|
||||||
|
"_hideReactionCount": {
|
||||||
|
/**
|
||||||
|
* 非表示にしない
|
||||||
|
*/
|
||||||
|
"none": string;
|
||||||
|
/**
|
||||||
|
* 自分のノートのみ
|
||||||
|
*/
|
||||||
|
"self": string;
|
||||||
|
/**
|
||||||
|
* 自分以外のノートのみ
|
||||||
|
*/
|
||||||
|
"others": string;
|
||||||
|
/**
|
||||||
|
* 全てのノート
|
||||||
|
*/
|
||||||
|
"all": string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
declare const locales: {
|
declare const locales: {
|
||||||
[lang: string]: Locale;
|
[lang: string]: Locale;
|
||||||
|
@ -1348,6 +1348,11 @@ defaultScheduledNoteDeleteTime: "ノートの自己消滅の初期値"
|
|||||||
scheduledNoteDeleteEnabled: "ノートの自己消滅が有効になっています"
|
scheduledNoteDeleteEnabled: "ノートの自己消滅が有効になっています"
|
||||||
cannotScheduleLaterThanOneYear: "1年以上先の日時を指定することはできません"
|
cannotScheduleLaterThanOneYear: "1年以上先の日時を指定することはできません"
|
||||||
defaultScheduledNoteDelete: "デフォルトでノートが自己消滅するように"
|
defaultScheduledNoteDelete: "デフォルトでノートが自己消滅するように"
|
||||||
|
hideActivity: "アクティビティを非公開にする"
|
||||||
|
hideActivityDescription: "自分のプロフィールのアクティビティ (概要/アクティビティタブ) を他人が見れないようにします。このオプションを有効にしても、自分であればプロフィールのアクティビティタブから引き続き閲覧できます。"
|
||||||
|
hideReactionUsers: "誰がリアクションをしたのかを非表示にする"
|
||||||
|
hideReactionUsersDescription: "リアクションをホバーした際のユーザー一覧と、ノート詳細ページのリアクションタブにあるリアクションをしたユーザー一覧を非表示にします"
|
||||||
|
hideReactionCount: "リアクション数の非表示"
|
||||||
|
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "遊び方"
|
howToPlay: "遊び方"
|
||||||
@ -2911,3 +2916,8 @@ _contextMenu:
|
|||||||
_reactionChecksMuting:
|
_reactionChecksMuting:
|
||||||
title: "リアクションでミュートを考慮する"
|
title: "リアクションでミュートを考慮する"
|
||||||
caption: "リアクションがミュートを考慮しますが、キャッシュが効かず通信量が増えることがあります。"
|
caption: "リアクションがミュートを考慮しますが、キャッシュが効かず通信量が増えることがあります。"
|
||||||
|
_hideReactionCount:
|
||||||
|
none: "非表示にしない"
|
||||||
|
self: "自分のノートのみ"
|
||||||
|
others: "自分以外のノートのみ"
|
||||||
|
all: "全てのノート"
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
export class FeatHideActivity1710146785085 {
|
||||||
|
name = 'FeatHideActivity1710146785085'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" ADD "hideActivity" boolean NOT NULL DEFAULT false`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "hideActivity"`);
|
||||||
|
}
|
||||||
|
}
|
@ -587,6 +587,7 @@ export class UserEntityService implements OnModuleInit {
|
|||||||
pinnedPageId: profile!.pinnedPageId,
|
pinnedPageId: profile!.pinnedPageId,
|
||||||
pinnedPage: profile!.pinnedPageId ? this.pageEntityService.pack(profile!.pinnedPageId, me) : null,
|
pinnedPage: profile!.pinnedPageId ? this.pageEntityService.pack(profile!.pinnedPageId, me) : null,
|
||||||
publicReactions: this.isLocalUser(user) ? profile!.publicReactions : false, // https://github.com/misskey-dev/misskey/issues/12964
|
publicReactions: this.isLocalUser(user) ? profile!.publicReactions : false, // https://github.com/misskey-dev/misskey/issues/12964
|
||||||
|
hideActivity: this.isLocalUser(user) ? profile!.hideActivity : false, //
|
||||||
followersVisibility: profile!.followersVisibility,
|
followersVisibility: profile!.followersVisibility,
|
||||||
followingVisibility: profile!.followingVisibility,
|
followingVisibility: profile!.followingVisibility,
|
||||||
twoFactorEnabled: profile!.twoFactorEnabled,
|
twoFactorEnabled: profile!.twoFactorEnabled,
|
||||||
@ -627,6 +628,7 @@ export class UserEntityService implements OnModuleInit {
|
|||||||
isDeleted: user.isDeleted,
|
isDeleted: user.isDeleted,
|
||||||
twoFactorBackupCodesStock: profile?.twoFactorBackupSecret?.length === 5 ? 'full' : (profile?.twoFactorBackupSecret?.length ?? 0) > 0 ? 'partial' : 'none',
|
twoFactorBackupCodesStock: profile?.twoFactorBackupSecret?.length === 5 ? 'full' : (profile?.twoFactorBackupSecret?.length ?? 0) > 0 ? 'partial' : 'none',
|
||||||
hideOnlineStatus: user.hideOnlineStatus,
|
hideOnlineStatus: user.hideOnlineStatus,
|
||||||
|
enableGTL: profile!.enableGTL,
|
||||||
hasUnreadSpecifiedNotes: this.noteUnreadsRepository.count({
|
hasUnreadSpecifiedNotes: this.noteUnreadsRepository.count({
|
||||||
where: { userId: user.id, isSpecified: true },
|
where: { userId: user.id, isSpecified: true },
|
||||||
take: 1,
|
take: 1,
|
||||||
|
@ -112,6 +112,11 @@ export class MiUserProfile {
|
|||||||
})
|
})
|
||||||
public followersVisibility: typeof followersVisibilities[number];
|
public followersVisibility: typeof followersVisibilities[number];
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public hideActivity: boolean;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128, nullable: true,
|
length: 128, nullable: true,
|
||||||
})
|
})
|
||||||
|
@ -369,6 +369,10 @@ export const packedUserDetailedNotMeOnlySchema = {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
nullable: false, optional: false,
|
nullable: false, optional: false,
|
||||||
},
|
},
|
||||||
|
hideActivity: {
|
||||||
|
type: 'boolean',
|
||||||
|
nullable: false, optional: false,
|
||||||
|
},
|
||||||
followingVisibility: {
|
followingVisibility: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
nullable: false, optional: false,
|
nullable: false, optional: false,
|
||||||
|
@ -3,11 +3,15 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import type { UserProfilesRepository } from '@/models/_.js';
|
||||||
import { getJsonSchema } from '@/core/chart/core.js';
|
import { getJsonSchema } from '@/core/chart/core.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js';
|
import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js';
|
||||||
import { schema } from '@/core/chart/charts/entities/per-user-drive.js';
|
import { schema } from '@/core/chart/charts/entities/per-user-drive.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'drive', 'users'],
|
tags: ['charts', 'drive', 'users'],
|
||||||
@ -16,6 +20,14 @@ export const meta = {
|
|||||||
|
|
||||||
allowGet: true,
|
allowGet: true,
|
||||||
cacheSec: 60 * 60,
|
cacheSec: 60 * 60,
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
activityNotPublic: {
|
||||||
|
message: 'Activity of the user is not public.',
|
||||||
|
code: 'ACTIVITY_NOT_PUBLIC',
|
||||||
|
id: '28e59b25-7eaf-4ff4-bac5-251fd7d8449b',
|
||||||
|
},
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
@ -32,9 +44,21 @@ export const paramDef = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.userProfilesRepository)
|
||||||
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
|
private roleService: RoleService,
|
||||||
private perUserDriveChart: PerUserDriveChart,
|
private perUserDriveChart: PerUserDriveChart,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see activity of all users
|
||||||
|
if (!iAmModerator) {
|
||||||
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId });
|
||||||
|
if ((me == null || me.id !== ps.userId) && profile.hideActivity) {
|
||||||
|
throw new ApiError(meta.errors.activityNotPublic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return await this.perUserDriveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
|
return await this.perUserDriveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,15 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import type { UserProfilesRepository } from '@/models/_.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { getJsonSchema } from '@/core/chart/core.js';
|
import { getJsonSchema } from '@/core/chart/core.js';
|
||||||
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
||||||
import { schema } from '@/core/chart/charts/entities/per-user-following.js';
|
import { schema } from '@/core/chart/charts/entities/per-user-following.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'users', 'following'],
|
tags: ['charts', 'users', 'following'],
|
||||||
@ -16,6 +20,14 @@ export const meta = {
|
|||||||
|
|
||||||
allowGet: true,
|
allowGet: true,
|
||||||
cacheSec: 60 * 60,
|
cacheSec: 60 * 60,
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
activityNotPublic: {
|
||||||
|
message: 'Activity of the user is not public.',
|
||||||
|
code: 'ACTIVITY_NOT_PUBLIC',
|
||||||
|
id: '28e59b25-7eaf-4ff4-bac5-251fd7d8449b',
|
||||||
|
},
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
@ -32,9 +44,21 @@ export const paramDef = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.userProfilesRepository)
|
||||||
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
|
private roleService: RoleService,
|
||||||
private perUserFollowingChart: PerUserFollowingChart,
|
private perUserFollowingChart: PerUserFollowingChart,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see activity of all users
|
||||||
|
if (!iAmModerator) {
|
||||||
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId });
|
||||||
|
if ((me == null || me.id !== ps.userId) && profile.hideActivity) {
|
||||||
|
throw new ApiError(meta.errors.activityNotPublic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return await this.perUserFollowingChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
|
return await this.perUserFollowingChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,15 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import type { UserProfilesRepository } from '@/models/_.js';
|
||||||
import { getJsonSchema } from '@/core/chart/core.js';
|
import { getJsonSchema } from '@/core/chart/core.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js';
|
import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js';
|
||||||
import { schema } from '@/core/chart/charts/entities/per-user-notes.js';
|
import { schema } from '@/core/chart/charts/entities/per-user-notes.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'users', 'notes'],
|
tags: ['charts', 'users', 'notes'],
|
||||||
@ -16,6 +20,14 @@ export const meta = {
|
|||||||
|
|
||||||
allowGet: true,
|
allowGet: true,
|
||||||
cacheSec: 60 * 60,
|
cacheSec: 60 * 60,
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
activityNotPublic: {
|
||||||
|
message: 'Activity of the user is not public.',
|
||||||
|
code: 'ACTIVITY_NOT_PUBLIC',
|
||||||
|
id: '28e59b25-7eaf-4ff4-bac5-251fd7d8449b',
|
||||||
|
},
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
@ -32,9 +44,21 @@ export const paramDef = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.userProfilesRepository)
|
||||||
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
|
private roleService: RoleService,
|
||||||
private perUserNotesChart: PerUserNotesChart,
|
private perUserNotesChart: PerUserNotesChart,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see activity of all users
|
||||||
|
if (!iAmModerator) {
|
||||||
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId });
|
||||||
|
if ((me == null || me.id !== ps.userId) && profile.hideActivity) {
|
||||||
|
throw new ApiError(meta.errors.activityNotPublic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return await this.perUserNotesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
|
return await this.perUserNotesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,15 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import type { UserProfilesRepository } from '@/models/_.js';
|
||||||
import { getJsonSchema } from '@/core/chart/core.js';
|
import { getJsonSchema } from '@/core/chart/core.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import PerUserPvChart from '@/core/chart/charts/per-user-pv.js';
|
import PerUserPvChart from '@/core/chart/charts/per-user-pv.js';
|
||||||
import { schema } from '@/core/chart/charts/entities/per-user-pv.js';
|
import { schema } from '@/core/chart/charts/entities/per-user-pv.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'users'],
|
tags: ['charts', 'users'],
|
||||||
@ -16,6 +20,14 @@ export const meta = {
|
|||||||
|
|
||||||
allowGet: true,
|
allowGet: true,
|
||||||
cacheSec: 60 * 60,
|
cacheSec: 60 * 60,
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
activityNotPublic: {
|
||||||
|
message: 'Activity of the user is not public.',
|
||||||
|
code: 'ACTIVITY_NOT_PUBLIC',
|
||||||
|
id: '28e59b25-7eaf-4ff4-bac5-251fd7d8449b',
|
||||||
|
},
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
@ -32,9 +44,21 @@ export const paramDef = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.userProfilesRepository)
|
||||||
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
|
private roleService: RoleService,
|
||||||
private perUserPvChart: PerUserPvChart,
|
private perUserPvChart: PerUserPvChart,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see activity of all users
|
||||||
|
if (!iAmModerator) {
|
||||||
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId });
|
||||||
|
if ((me == null || me.id !== ps.userId) && profile.hideActivity) {
|
||||||
|
throw new ApiError(meta.errors.activityNotPublic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return await this.perUserPvChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
|
return await this.perUserPvChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,15 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import type { UserProfilesRepository } from '@/models/_.js';
|
||||||
import { getJsonSchema } from '@/core/chart/core.js';
|
import { getJsonSchema } from '@/core/chart/core.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import PerUserReactionsChart from '@/core/chart/charts/per-user-reactions.js';
|
import PerUserReactionsChart from '@/core/chart/charts/per-user-reactions.js';
|
||||||
import { schema } from '@/core/chart/charts/entities/per-user-reactions.js';
|
import { schema } from '@/core/chart/charts/entities/per-user-reactions.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'users', 'reactions'],
|
tags: ['charts', 'users', 'reactions'],
|
||||||
@ -16,6 +20,14 @@ export const meta = {
|
|||||||
|
|
||||||
allowGet: true,
|
allowGet: true,
|
||||||
cacheSec: 60 * 60,
|
cacheSec: 60 * 60,
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
activityNotPublic: {
|
||||||
|
message: 'Activity of the user is not public.',
|
||||||
|
code: 'ACTIVITY_NOT_PUBLIC',
|
||||||
|
id: '28e59b25-7eaf-4ff4-bac5-251fd7d8449b',
|
||||||
|
},
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
@ -32,9 +44,21 @@ export const paramDef = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.userProfilesRepository)
|
||||||
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
|
private roleService: RoleService,
|
||||||
private perUserReactionsChart: PerUserReactionsChart,
|
private perUserReactionsChart: PerUserReactionsChart,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see activity of all users
|
||||||
|
if (!iAmModerator) {
|
||||||
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId });
|
||||||
|
if ((me == null || me.id !== ps.userId) && profile.hideActivity) {
|
||||||
|
throw new ApiError(meta.errors.activityNotPublic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return await this.perUserReactionsChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
|
return await this.perUserReactionsChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -181,6 +181,7 @@ export const paramDef = {
|
|||||||
isExplorable: { type: 'boolean' },
|
isExplorable: { type: 'boolean' },
|
||||||
hideOnlineStatus: { type: 'boolean' },
|
hideOnlineStatus: { type: 'boolean' },
|
||||||
publicReactions: { type: 'boolean' },
|
publicReactions: { type: 'boolean' },
|
||||||
|
hideActivity: { type: 'boolean' },
|
||||||
carefulBot: { type: 'boolean' },
|
carefulBot: { type: 'boolean' },
|
||||||
autoAcceptFollowed: { type: 'boolean' },
|
autoAcceptFollowed: { type: 'boolean' },
|
||||||
autoRejectFollowRequest: { type: 'boolean' },
|
autoRejectFollowRequest: { type: 'boolean' },
|
||||||
@ -335,6 +336,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus;
|
if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus;
|
||||||
if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions;
|
if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions;
|
||||||
if (typeof ps.noindex === 'boolean') updates.noindex = ps.noindex;
|
if (typeof ps.noindex === 'boolean') updates.noindex = ps.noindex;
|
||||||
|
if (typeof ps.hideActivity === 'boolean') profileUpdates.hideActivity = ps.hideActivity;
|
||||||
if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot;
|
if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot;
|
||||||
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
|
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
|
||||||
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
||||||
|
@ -11,7 +11,6 @@ import { QueryService } from '@/core/QueryService.js';
|
|||||||
import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js';
|
import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
@ -82,7 +81,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private followingEntityService: FollowingEntityService,
|
private followingEntityService: FollowingEntityService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
private roleService: RoleService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const user = await this.usersRepository.findOneBy(ps.userId != null
|
const user = await this.usersRepository.findOneBy(ps.userId != null
|
||||||
@ -95,24 +93,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
|
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||||
|
|
||||||
if (profile.followersVisibility !== 'public' && !await this.roleService.isModerator(me)) {
|
if (profile.followersVisibility === 'private') {
|
||||||
if (profile.followersVisibility === 'private') {
|
if (me == null || (me.id !== user.id)) {
|
||||||
if (me == null || (me.id !== user.id)) {
|
throw new ApiError(meta.errors.forbidden);
|
||||||
throw new ApiError(meta.errors.forbidden);
|
}
|
||||||
}
|
} else if (profile.followersVisibility === 'followers') {
|
||||||
} else if (profile.followersVisibility === 'followers') {
|
if (me == null) {
|
||||||
if (me == null) {
|
throw new ApiError(meta.errors.forbidden);
|
||||||
throw new ApiError(meta.errors.forbidden);
|
} else if (me.id !== user.id) {
|
||||||
} else if (me.id !== user.id) {
|
const isFollowing = await this.followingsRepository.exists({
|
||||||
const isFollowing = await this.followingsRepository.exists({
|
where: {
|
||||||
where: {
|
followeeId: user.id,
|
||||||
followeeId: user.id,
|
followerId: me.id,
|
||||||
followerId: me.id,
|
},
|
||||||
},
|
});
|
||||||
});
|
if (!isFollowing) {
|
||||||
if (!isFollowing) {
|
|
||||||
throw new ApiError(meta.errors.forbidden);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import { QueryService } from '@/core/QueryService.js';
|
|||||||
import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js';
|
import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
@ -91,7 +90,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private followingEntityService: FollowingEntityService,
|
private followingEntityService: FollowingEntityService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
private roleService: RoleService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const user = await this.usersRepository.findOneBy(ps.userId != null
|
const user = await this.usersRepository.findOneBy(ps.userId != null
|
||||||
@ -104,24 +102,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
|
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||||
|
|
||||||
if (profile.followingVisibility !== 'public' && !await this.roleService.isModerator(me)) {
|
if (profile.followingVisibility === 'private') {
|
||||||
if (profile.followingVisibility === 'private') {
|
if (me == null || (me.id !== user.id)) {
|
||||||
if (me == null || (me.id !== user.id)) {
|
throw new ApiError(meta.errors.forbidden);
|
||||||
throw new ApiError(meta.errors.forbidden);
|
}
|
||||||
}
|
} else if (profile.followingVisibility === 'followers') {
|
||||||
} else if (profile.followingVisibility === 'followers') {
|
if (me == null) {
|
||||||
if (me == null) {
|
throw new ApiError(meta.errors.forbidden);
|
||||||
throw new ApiError(meta.errors.forbidden);
|
} else if (me.id !== user.id) {
|
||||||
} else if (me.id !== user.id) {
|
const isFollowing = await this.followingsRepository.exists({
|
||||||
const isFollowing = await this.followingsRepository.exists({
|
where: {
|
||||||
where: {
|
followeeId: user.id,
|
||||||
followeeId: user.id,
|
followerId: me.id,
|
||||||
followerId: me.id,
|
},
|
||||||
},
|
});
|
||||||
});
|
if (!isFollowing) {
|
||||||
if (!isFollowing) {
|
|
||||||
throw new ApiError(meta.errors.forbidden);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,9 +196,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'reactions'" :class="$style.tab_reactions">
|
<div v-else-if="tab === 'reactions'" :class="$style.tab_reactions">
|
||||||
<div :class="$style.reactionTabs">
|
<div :class="$style.reactionTabs">
|
||||||
<button v-for="reaction in Object.keys(appearNote.reactions)" :key="reaction" :class="[$style.reactionTab, { [$style.reactionTabActive]: reactionTabType === reaction }]" class="_button" @click="reactionTabType = reaction">
|
<button v-for="reaction in Object.keys(appearNote.reactions)" :key="reaction" :class="[$style.reactionTab, { [$style.reactionTabActive]: reactionTabType === reaction }]" class="_button" @click="reactionTabType = defaultStore.state.hideReactionUsers ? null : reaction">
|
||||||
<MkReactionIcon :reaction="reaction"/>
|
<MkReactionIcon :reaction="reaction"/>
|
||||||
<span style="margin-left: 4px;">{{ appearNote.reactions[reaction] }}</span>
|
<span v-if="!hideReactionCount" style="margin-left: 4px;">{{ appearNote.reactions[reaction] }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<MkButton v-if="reactionTabType" :class="$style.reactionMuteButton" @click="reactionMuteToggle(reactionTabTypeTrimLocal)">
|
<MkButton v-if="reactionTabType" :class="$style.reactionMuteButton" @click="reactionMuteToggle(reactionTabTypeTrimLocal)">
|
||||||
@ -352,6 +352,15 @@ if ($i) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let renoting = false;
|
let renoting = false;
|
||||||
|
const hideReactionCount = computed(() => {
|
||||||
|
switch (defaultStore.state.hideReactionCount) {
|
||||||
|
case 'none': return false;
|
||||||
|
case 'all': return true;
|
||||||
|
case 'self': return props.note.userId === $i?.id;
|
||||||
|
case 'others': return props.note.userId !== $i?.id;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||||
type: 'lookup',
|
type: 'lookup',
|
||||||
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkReactionIcon :reaction="reaction" :class="$style.reactionIcon" :noStyle="true"/>
|
<MkReactionIcon :reaction="reaction" :class="$style.reactionIcon" :noStyle="true"/>
|
||||||
<div :class="$style.reactionName">{{ getReactionName(reaction) }}</div>
|
<div :class="$style.reactionName">{{ getReactionName(reaction) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.users">
|
<div v-if="users.length" :class="$style.users">
|
||||||
<div v-for="u in users" :key="u.id" :class="$style.user">
|
<div v-for="u in users" :key="u.id" :class="$style.user">
|
||||||
<MkAvatar :class="$style.avatar" :user="u"/>
|
<MkAvatar :class="$style.avatar" :user="u"/>
|
||||||
<MkUserName :user="u" :nowrap="true"/>
|
<MkUserName :user="u" :nowrap="true"/>
|
||||||
@ -57,9 +57,7 @@ function getReactionName(reaction: string): string {
|
|||||||
|
|
||||||
.reaction {
|
.reaction {
|
||||||
max-width: 100px;
|
max-width: 100px;
|
||||||
padding-right: 10px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-right: solid 0.5px var(--divider);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.reactionIcon {
|
.reactionIcon {
|
||||||
@ -80,6 +78,8 @@ function getReactionName(reaction: string): string {
|
|||||||
margin: -4px 14px 0 10px;
|
margin: -4px 14px 0 10px;
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
padding-left: 10px;
|
||||||
|
border-left: solid 0.5px var(--divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
@contextmenu.prevent.stop="menu"
|
@contextmenu.prevent.stop="menu"
|
||||||
>
|
>
|
||||||
<MkReactionIcon :class="defaultStore.state.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]" @click="toggleReaction()" @click.stop/>
|
<MkReactionIcon :class="defaultStore.state.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]" @click="toggleReaction()" @click.stop/>
|
||||||
<span :class="$style.count">{{ count }}</span>
|
<span v-if="!hideReactionCount" :class="$style.count">{{ count }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -56,11 +56,29 @@ const buttonEl = shallowRef<HTMLElement>();
|
|||||||
const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
|
const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
|
||||||
const emoji = computed(() => customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction));
|
const emoji = computed(() => customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction));
|
||||||
|
|
||||||
|
function getReactionName(reaction: string, formated = false) {
|
||||||
|
const r = reaction.replaceAll(':', '').replace(/@.*/, '');
|
||||||
|
return formated ? `:${r}:` : r;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLocal = computed(() => !props.reaction.match(/@\w/));
|
||||||
|
const isAvailable = computed(() => isLocal.value ? true : customEmojisMap.has(getReactionName(props.reaction)));
|
||||||
|
|
||||||
const canToggle = computed(() => {
|
const canToggle = computed(() => {
|
||||||
return !props.reaction.match(/@\w/) && $i && emoji.value && checkReactionPermissions($i, props.note, emoji.value);
|
return !props.reaction.match(/@\w/) && $i && emoji.value && checkReactionPermissions($i, props.note, emoji.value);
|
||||||
});
|
});
|
||||||
const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));
|
const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));
|
||||||
|
|
||||||
|
const hideReactionCount = computed(() => {
|
||||||
|
switch (defaultStore.state.hideReactionCount) {
|
||||||
|
case 'none': return false;
|
||||||
|
case 'all': return true;
|
||||||
|
case 'self': return props.note.userId === $i?.id;
|
||||||
|
case 'others': return props.note.userId !== $i?.id;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function toggleReaction() {
|
async function toggleReaction() {
|
||||||
if (!canToggle.value) return;
|
if (!canToggle.value) return;
|
||||||
|
|
||||||
@ -150,12 +168,12 @@ if (!mock) {
|
|||||||
useTooltip(buttonEl, async (showing) => {
|
useTooltip(buttonEl, async (showing) => {
|
||||||
const useGet = !reactionChecksMuting.value;
|
const useGet = !reactionChecksMuting.value;
|
||||||
const apiCall = useGet ? misskeyApiGet : misskeyApi;
|
const apiCall = useGet ? misskeyApiGet : misskeyApi;
|
||||||
const reactions = await apiCall('notes/reactions', {
|
const reactions = !defaultStore.state.hideReactionUsers ? await apiCall('notes/reactions', {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
type: props.reaction,
|
type: props.reaction,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
_cacheKey_: props.count,
|
_cacheKey_: props.count,
|
||||||
});
|
}) : [];
|
||||||
|
|
||||||
const users = reactions.map(x => x.user);
|
const users = reactions.map(x => x.user);
|
||||||
const count = users.length;
|
const count = users.length;
|
||||||
|
@ -204,7 +204,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'reactions'" :class="$style.tab_reactions">
|
<div v-else-if="tab === 'reactions'" :class="$style.tab_reactions">
|
||||||
<div :class="$style.reactionTabs">
|
<div :class="$style.reactionTabs">
|
||||||
<button v-for="reaction in Object.keys(appearNote.reactions)" :key="reaction" :class="[$style.reactionTab, { [$style.reactionTabActive]: reactionTabType === reaction }]" class="_button" @click="reactionTabType = reaction">
|
<button v-for="reaction in Object.keys(appearNote.reactions)" :key="reaction" :class="[$style.reactionTab, { [$style.reactionTabActive]: reactionTabType === reaction }]" class="_button" @click="reactionTabType = defaultStore.state.hideReactionUsers ? null : reaction">
|
||||||
<MkReactionIcon :reaction="reaction"/>
|
<MkReactionIcon :reaction="reaction"/>
|
||||||
<span style="margin-left: 4px;">{{ appearNote.reactions[reaction] }}</span>
|
<span style="margin-left: 4px;">{{ appearNote.reactions[reaction] }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -103,6 +103,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<option value="misskey"><i class="sk-icons sk-misskey sk-icons-lg" style="top: 2px;position: relative;"></i> Misskey</option>
|
<option value="misskey"><i class="sk-icons sk-misskey sk-icons-lg" style="top: 2px;position: relative;"></i> Misskey</option>
|
||||||
</MkRadios>
|
</MkRadios>
|
||||||
<MkSwitch v-model="limitWidthOfReaction">{{ i18n.ts.limitWidthOfReaction }}</MkSwitch>
|
<MkSwitch v-model="limitWidthOfReaction">{{ i18n.ts.limitWidthOfReaction }}</MkSwitch>
|
||||||
|
<MkSwitch v-model="hideReactionUsers">
|
||||||
|
<template #caption>{{ i18n.ts.hideReactionUsersDescription }}</template>
|
||||||
|
{{ i18n.ts.hideReactionUsers }}
|
||||||
|
<span class="_beta">{{ i18n.ts.originalFeature }}</span>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSelect v-model="hideReactionCount">
|
||||||
|
<template #label>{{ i18n.ts.hideReactionCount }}<span class="_beta">{{ i18n.ts.originalFeature }}</span></template>
|
||||||
|
<option value="none">{{ i18n.ts._hideReactionCount.none }}</option>
|
||||||
|
<option value="self">{{ i18n.ts._hideReactionCount.self }}</option>
|
||||||
|
<option value="others">{{ i18n.ts._hideReactionCount.others }}</option>
|
||||||
|
<option value="all">{{ i18n.ts._hideReactionCount.all }}</option>
|
||||||
|
</MkSelect>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkSelect v-model="instanceTicker">
|
<MkSelect v-model="instanceTicker">
|
||||||
@ -382,6 +394,8 @@ const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNot
|
|||||||
const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showClipButtonInNoteFooter'));
|
const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showClipButtonInNoteFooter'));
|
||||||
const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize'));
|
const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize'));
|
||||||
const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthOfReaction'));
|
const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthOfReaction'));
|
||||||
|
const hideReactionUsers = computed(defaultStore.makeGetterSetter('hideReactionUsers'));
|
||||||
|
const hideReactionCount = computed(defaultStore.makeGetterSetter('hideReactionCount'));
|
||||||
const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
|
const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
|
||||||
const collapseNotesRepliedTo = computed(defaultStore.makeGetterSetter('collapseNotesRepliedTo'));
|
const collapseNotesRepliedTo = computed(defaultStore.makeGetterSetter('collapseNotesRepliedTo'));
|
||||||
const clickToOpen = computed(defaultStore.makeGetterSetter('clickToOpen'));
|
const clickToOpen = computed(defaultStore.makeGetterSetter('clickToOpen'));
|
||||||
@ -494,6 +508,7 @@ watch([
|
|||||||
showGapBetweenNotesInTimeline,
|
showGapBetweenNotesInTimeline,
|
||||||
instanceTicker,
|
instanceTicker,
|
||||||
instanceIcon,
|
instanceIcon,
|
||||||
|
hideReactionCount,
|
||||||
overridedDeviceKind,
|
overridedDeviceKind,
|
||||||
mediaListWithOneImageAppearance,
|
mediaListWithOneImageAppearance,
|
||||||
reactionsDisplaySize,
|
reactionsDisplaySize,
|
||||||
|
@ -17,6 +17,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #caption>{{ i18n.ts.makeReactionsPublicDescription }}</template>
|
<template #caption>{{ i18n.ts.makeReactionsPublicDescription }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
|
||||||
|
<MkSwitch v-model="hideActivity" @update:modelValue="save()">
|
||||||
|
{{ i18n.ts.hideActivity }}<span class="_beta">{{ i18n.ts.originalFeature }}</span>
|
||||||
|
<template #caption>{{ i18n.ts.hideActivityDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
|
||||||
<MkSelect v-model="followingVisibility" @update:modelValue="save()">
|
<MkSelect v-model="followingVisibility" @update:modelValue="save()">
|
||||||
<template #label>{{ i18n.ts.followingVisibility }}</template>
|
<template #label>{{ i18n.ts.followingVisibility }}</template>
|
||||||
<option value="public">{{ i18n.ts._ffVisibility.public }}</option>
|
<option value="public">{{ i18n.ts._ffVisibility.public }}</option>
|
||||||
@ -97,6 +102,7 @@ const noindex = ref($i.noindex);
|
|||||||
const isExplorable = ref($i.isExplorable);
|
const isExplorable = ref($i.isExplorable);
|
||||||
const hideOnlineStatus = ref($i.hideOnlineStatus);
|
const hideOnlineStatus = ref($i.hideOnlineStatus);
|
||||||
const publicReactions = ref($i.publicReactions);
|
const publicReactions = ref($i.publicReactions);
|
||||||
|
const hideActivity = ref($i.hideActivity);
|
||||||
const followingVisibility = ref($i.followingVisibility);
|
const followingVisibility = ref($i.followingVisibility);
|
||||||
const followersVisibility = ref($i.followersVisibility);
|
const followersVisibility = ref($i.followersVisibility);
|
||||||
|
|
||||||
@ -115,6 +121,7 @@ function save() {
|
|||||||
isExplorable: !!isExplorable.value,
|
isExplorable: !!isExplorable.value,
|
||||||
hideOnlineStatus: !!hideOnlineStatus.value,
|
hideOnlineStatus: !!hideOnlineStatus.value,
|
||||||
publicReactions: !!publicReactions.value,
|
publicReactions: !!publicReactions.value,
|
||||||
|
hideActivity: !!hideActivity.value,
|
||||||
followingVisibility: followingVisibility.value,
|
followingVisibility: followingVisibility.value,
|
||||||
followersVisibility: followersVisibility.value,
|
followersVisibility: followersVisibility.value,
|
||||||
});
|
});
|
||||||
|
@ -156,7 +156,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!narrow" class="sub _gaps" style="container-type: inline-size;">
|
<div v-if="!narrow" class="sub _gaps" style="container-type: inline-size;">
|
||||||
<XFiles :key="user.id" :user="user"/>
|
<XFiles :key="user.id" :user="user"/>
|
||||||
<XActivity :key="user.id" :user="user"/>
|
<XActivity v-if="!user.hideActivity" :key="user.id" :user="user"/>
|
||||||
<XListenBrainz v-if="user.listenbrainz && listenbrainzdata" :key="user.id" :user="user"/>
|
<XListenBrainz v-if="user.listenbrainz && listenbrainzdata" :key="user.id" :user="user"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,11 +88,11 @@ const headerTabs = computed(() => user.value ? [{
|
|||||||
key: 'notes',
|
key: 'notes',
|
||||||
title: i18n.ts.notes,
|
title: i18n.ts.notes,
|
||||||
icon: 'ti ti-pencil',
|
icon: 'ti ti-pencil',
|
||||||
}, {
|
}, ...($i && ($i.id === user.value.id || $i.isAdmin || $i.isModerator)) || !user.value.hideActivity ? [{
|
||||||
key: 'activity',
|
key: 'activity',
|
||||||
title: i18n.ts.activity,
|
title: i18n.ts.activity,
|
||||||
icon: 'ti ti-chart-line',
|
icon: 'ti ti-chart-line',
|
||||||
}, ...(user.value.host == null ? [{
|
}] : [], ...(user.value.host == null ? [{
|
||||||
key: 'achievements',
|
key: 'achievements',
|
||||||
title: i18n.ts.achievements,
|
title: i18n.ts.achievements,
|
||||||
icon: 'ti ti-medal',
|
icon: 'ti ti-medal',
|
||||||
|
@ -3888,6 +3888,7 @@ export type components = {
|
|||||||
pinnedPageId: string | null;
|
pinnedPageId: string | null;
|
||||||
pinnedPage: components['schemas']['Page'] | null;
|
pinnedPage: components['schemas']['Page'] | null;
|
||||||
publicReactions: boolean;
|
publicReactions: boolean;
|
||||||
|
hideActivity: boolean;
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
followingVisibility: 'public' | 'followers' | 'private';
|
followingVisibility: 'public' | 'followers' | 'private';
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
@ -20239,6 +20240,7 @@ export type operations = {
|
|||||||
isExplorable?: boolean;
|
isExplorable?: boolean;
|
||||||
hideOnlineStatus?: boolean;
|
hideOnlineStatus?: boolean;
|
||||||
publicReactions?: boolean;
|
publicReactions?: boolean;
|
||||||
|
hideActivity?: boolean;
|
||||||
carefulBot?: boolean;
|
carefulBot?: boolean;
|
||||||
autoAcceptFollowed?: boolean;
|
autoAcceptFollowed?: boolean;
|
||||||
noCrawle?: boolean;
|
noCrawle?: boolean;
|
||||||
|
Loading…
Reference in New Issue
Block a user