feat: per-user boost muting (#9825)
Cherry-picked from FoundKey/c414f24a2c ([commit](c414f24a2c
))
This allows us to hide specified users' boosts from the timelines (the boosts will still be visible on their user page).
Co-authored-by: Hélène <pleroma-dev@helene.moe>
Co-authored-by: naskya <m@naskya.net>
Reviewed-on: https://codeberg.org/calckey/calckey/pulls/9825
Co-authored-by: naskya <naskya@noreply.codeberg.org>
Co-committed-by: naskya <naskya@noreply.codeberg.org>
This commit is contained in:
parent
6c6350aff0
commit
a624aeebe3
|
@ -121,6 +121,8 @@ unmarkAsSensitive: "Unmark as NSFW"
|
||||||
enterFileName: "Enter filename"
|
enterFileName: "Enter filename"
|
||||||
mute: "Mute"
|
mute: "Mute"
|
||||||
unmute: "Unmute"
|
unmute: "Unmute"
|
||||||
|
renoteMute: "Mute boosts"
|
||||||
|
renoteUnmute: "Unmute boosts"
|
||||||
block: "Block"
|
block: "Block"
|
||||||
unblock: "Unblock"
|
unblock: "Unblock"
|
||||||
suspend: "Suspend"
|
suspend: "Suspend"
|
||||||
|
|
|
@ -121,6 +121,8 @@ unmarkAsSensitive: "閲覧注意を解除する"
|
||||||
enterFileName: "ファイル名を入力"
|
enterFileName: "ファイル名を入力"
|
||||||
mute: "ミュート"
|
mute: "ミュート"
|
||||||
unmute: "ミュート解除"
|
unmute: "ミュート解除"
|
||||||
|
renoteMute: "ブーストをミュート"
|
||||||
|
renoteUnmute: "ブーストのミュートを解除"
|
||||||
block: "ブロック"
|
block: "ブロック"
|
||||||
unblock: "ブロック解除"
|
unblock: "ブロック解除"
|
||||||
suspend: "凍結"
|
suspend: "凍結"
|
||||||
|
|
|
@ -116,6 +116,8 @@ unmarkAsSensitive: "取消标记为敏感内容"
|
||||||
enterFileName: "请输入文件名"
|
enterFileName: "请输入文件名"
|
||||||
mute: "屏蔽"
|
mute: "屏蔽"
|
||||||
unmute: "解除屏蔽"
|
unmute: "解除屏蔽"
|
||||||
|
renoteMute: "屏蔽转帖"
|
||||||
|
renoteUnmute: "解除屏蔽转帖"
|
||||||
block: "拉黑"
|
block: "拉黑"
|
||||||
unblock: "取消拉黑"
|
unblock: "取消拉黑"
|
||||||
suspend: "冻结"
|
suspend: "冻结"
|
||||||
|
|
|
@ -116,6 +116,8 @@ unmarkAsSensitive: "取消標記為敏感內容"
|
||||||
enterFileName: "請輸入檔案名稱"
|
enterFileName: "請輸入檔案名稱"
|
||||||
mute: "靜音"
|
mute: "靜音"
|
||||||
unmute: "解除靜音"
|
unmute: "解除靜音"
|
||||||
|
renoteMute: "靜音轉發貼文"
|
||||||
|
renoteUnmute: "解除靜音轉發貼文"
|
||||||
block: "封鎖"
|
block: "封鎖"
|
||||||
unblock: "解除封鎖"
|
unblock: "解除封鎖"
|
||||||
suspend: "凍結"
|
suspend: "凍結"
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
export class addRenoteMuting1665091090561 {
|
||||||
|
constructor() {
|
||||||
|
this.name = 'addRenoteMuting1665091090561';
|
||||||
|
}
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`CREATE TABLE "renote_muting" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "muteeId" character varying(32) NOT NULL, "muterId" character varying(32) NOT NULL, CONSTRAINT "PK_renoteMuting_id" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_renote_muting_createdAt" ON "muting" ("createdAt") `);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muteeId" ON "muting" ("muteeId") `);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muterId" ON "muting" ("muterId") `);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import { Meta } from "@/models/entities/meta.js";
|
||||||
import { Following } from "@/models/entities/following.js";
|
import { Following } from "@/models/entities/following.js";
|
||||||
import { Instance } from "@/models/entities/instance.js";
|
import { Instance } from "@/models/entities/instance.js";
|
||||||
import { Muting } from "@/models/entities/muting.js";
|
import { Muting } from "@/models/entities/muting.js";
|
||||||
|
import { RenoteMuting } from "@/models/entities/renote-muting.js";
|
||||||
import { SwSubscription } from "@/models/entities/sw-subscription.js";
|
import { SwSubscription } from "@/models/entities/sw-subscription.js";
|
||||||
import { Blocking } from "@/models/entities/blocking.js";
|
import { Blocking } from "@/models/entities/blocking.js";
|
||||||
import { UserList } from "@/models/entities/user-list.js";
|
import { UserList } from "@/models/entities/user-list.js";
|
||||||
|
@ -136,6 +137,7 @@ export const entities = [
|
||||||
Following,
|
Following,
|
||||||
FollowRequest,
|
FollowRequest,
|
||||||
Muting,
|
Muting,
|
||||||
|
RenoteMuting,
|
||||||
Blocking,
|
Blocking,
|
||||||
Note,
|
Note,
|
||||||
NoteFavorite,
|
NoteFavorite,
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { packedDriveFileSchema } from "@/models/schema/drive-file.js";
|
||||||
import { packedDriveFolderSchema } from "@/models/schema/drive-folder.js";
|
import { packedDriveFolderSchema } from "@/models/schema/drive-folder.js";
|
||||||
import { packedFollowingSchema } from "@/models/schema/following.js";
|
import { packedFollowingSchema } from "@/models/schema/following.js";
|
||||||
import { packedMutingSchema } from "@/models/schema/muting.js";
|
import { packedMutingSchema } from "@/models/schema/muting.js";
|
||||||
|
import { packedRenoteMutingSchema } from "@/models/schema/renote-muting.js";
|
||||||
import { packedBlockingSchema } from "@/models/schema/blocking.js";
|
import { packedBlockingSchema } from "@/models/schema/blocking.js";
|
||||||
import { packedNoteReactionSchema } from "@/models/schema/note-reaction.js";
|
import { packedNoteReactionSchema } from "@/models/schema/note-reaction.js";
|
||||||
import { packedHashtagSchema } from "@/models/schema/hashtag.js";
|
import { packedHashtagSchema } from "@/models/schema/hashtag.js";
|
||||||
|
@ -51,6 +52,7 @@ export const refs = {
|
||||||
DriveFolder: packedDriveFolderSchema,
|
DriveFolder: packedDriveFolderSchema,
|
||||||
Following: packedFollowingSchema,
|
Following: packedFollowingSchema,
|
||||||
Muting: packedMutingSchema,
|
Muting: packedMutingSchema,
|
||||||
|
RenoteMuting: packedRenoteMutingSchema,
|
||||||
Blocking: packedBlockingSchema,
|
Blocking: packedBlockingSchema,
|
||||||
Hashtag: packedHashtagSchema,
|
Hashtag: packedHashtagSchema,
|
||||||
Page: packedPageSchema,
|
Page: packedPageSchema,
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
|
||||||
|
import { id } from "../id.js";
|
||||||
|
import { User } from "./user.js";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
@Index(["muterId", "muteeId"], { unique: true })
|
||||||
|
export class RenoteMuting {
|
||||||
|
@PrimaryColumn(id())
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column("timestamp with time zone", {
|
||||||
|
comment: "The created date of the Muting.",
|
||||||
|
})
|
||||||
|
public createdAt: Date;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({
|
||||||
|
...id(),
|
||||||
|
comment: "The mutee user ID.",
|
||||||
|
})
|
||||||
|
public muteeId: User["id"];
|
||||||
|
|
||||||
|
@ManyToOne(type => User, {
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public mutee: User | null;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({
|
||||||
|
...id(),
|
||||||
|
comment: "The muter user ID.",
|
||||||
|
})
|
||||||
|
public muterId: User["id"];
|
||||||
|
|
||||||
|
@ManyToOne(type => User, {
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public muter: User | null;
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import { UserGroupJoining } from "./entities/user-group-joining.js";
|
||||||
import { UserGroupInvitationRepository } from "./repositories/user-group-invitation.js";
|
import { UserGroupInvitationRepository } from "./repositories/user-group-invitation.js";
|
||||||
import { FollowRequestRepository } from "./repositories/follow-request.js";
|
import { FollowRequestRepository } from "./repositories/follow-request.js";
|
||||||
import { MutingRepository } from "./repositories/muting.js";
|
import { MutingRepository } from "./repositories/muting.js";
|
||||||
|
import { RenoteMutingRepository } from "./repositories/renote-muting.js";
|
||||||
import { BlockingRepository } from "./repositories/blocking.js";
|
import { BlockingRepository } from "./repositories/blocking.js";
|
||||||
import { NoteReactionRepository } from "./repositories/note-reaction.js";
|
import { NoteReactionRepository } from "./repositories/note-reaction.js";
|
||||||
import { NotificationRepository } from "./repositories/notification.js";
|
import { NotificationRepository } from "./repositories/notification.js";
|
||||||
|
@ -102,6 +103,7 @@ export const DriveFolders = DriveFolderRepository;
|
||||||
export const Notifications = NotificationRepository;
|
export const Notifications = NotificationRepository;
|
||||||
export const Metas = db.getRepository(Meta);
|
export const Metas = db.getRepository(Meta);
|
||||||
export const Mutings = MutingRepository;
|
export const Mutings = MutingRepository;
|
||||||
|
export const RenoteMutings = RenoteMutingRepository;
|
||||||
export const Blockings = BlockingRepository;
|
export const Blockings = BlockingRepository;
|
||||||
export const SwSubscriptions = db.getRepository(SwSubscription);
|
export const SwSubscriptions = db.getRepository(SwSubscription);
|
||||||
export const Hashtags = HashtagRepository;
|
export const Hashtags = HashtagRepository;
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { db } from "@/db/postgre.js";
|
||||||
|
import { Packed } from "@/misc/schema.js";
|
||||||
|
import { RenoteMuting } from "@/models/entities/renote-muting.js";
|
||||||
|
import { User } from "@/models/entities/user.js";
|
||||||
|
import { awaitAll } from "@/prelude/await-all.js";
|
||||||
|
import { Users } from "../index.js";
|
||||||
|
|
||||||
|
export const RenoteMutingRepository = db.getRepository(RenoteMuting).extend({
|
||||||
|
async pack(
|
||||||
|
src: RenoteMuting["id"] | RenoteMuting,
|
||||||
|
me?: { id: User["id"] } | null | undefined,
|
||||||
|
): Promise<Packed<"RenoteMuting">> {
|
||||||
|
const muting = typeof src === "object" ? src : await this.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
return await awaitAll({
|
||||||
|
id: muting.id,
|
||||||
|
createdAt: muting.createdAt.toISOString(),
|
||||||
|
muteeId: muting.muteeId,
|
||||||
|
mutee: Users.pack(muting.muteeId, me, {
|
||||||
|
detail: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
packMany(
|
||||||
|
mutings: any[],
|
||||||
|
me: { id: User["id"] },
|
||||||
|
) {
|
||||||
|
return Promise.all(mutings.map(x => this.pack(x, me)));
|
||||||
|
},
|
||||||
|
});
|
|
@ -28,6 +28,7 @@ import {
|
||||||
Instances,
|
Instances,
|
||||||
MessagingMessages,
|
MessagingMessages,
|
||||||
Mutings,
|
Mutings,
|
||||||
|
RenoteMutings,
|
||||||
Notes,
|
Notes,
|
||||||
NoteUnreads,
|
NoteUnreads,
|
||||||
Notifications,
|
Notifications,
|
||||||
|
@ -171,6 +172,13 @@ export const UserRepository = db.getRepository(User).extend({
|
||||||
},
|
},
|
||||||
take: 1,
|
take: 1,
|
||||||
}).then((n) => n > 0),
|
}).then((n) => n > 0),
|
||||||
|
isRenoteMuted: RenoteMutings.count({
|
||||||
|
where: {
|
||||||
|
muterId: me,
|
||||||
|
muteeId: target,
|
||||||
|
},
|
||||||
|
take: 1,
|
||||||
|
}).then((n) => n > 0),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -585,6 +593,7 @@ export const UserRepository = db.getRepository(User).extend({
|
||||||
isBlocking: relation.isBlocking,
|
isBlocking: relation.isBlocking,
|
||||||
isBlocked: relation.isBlocked,
|
isBlocked: relation.isBlocked,
|
||||||
isMuted: relation.isMuted,
|
isMuted: relation.isMuted,
|
||||||
|
isRenoteMuted: relation.isRenoteMuted,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
} as Promiseable<Packed<"User">> as Promiseable<
|
} as Promiseable<Packed<"User">> as Promiseable<
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
export const packedRenoteMutingSchema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: "string",
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: "id",
|
||||||
|
example: "xxxxxxxxxx",
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: "string",
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: "date-time",
|
||||||
|
},
|
||||||
|
muteeId: {
|
||||||
|
type: "string",
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: "id",
|
||||||
|
},
|
||||||
|
mutee: {
|
||||||
|
type: "object",
|
||||||
|
optional: false, nullable: false,
|
||||||
|
ref: "UserDetailed",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
|
@ -335,6 +335,10 @@ export const packedUserDetailedNotMeOnlySchema = {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
isRenoteMuted: {
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false, optional: true,
|
||||||
|
},
|
||||||
//#endregion
|
//#endregion
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Brackets, SelectQueryBuilder } from "typeorm";
|
||||||
|
import { User } from "@/models/entities/user.js";
|
||||||
|
import { RenoteMutings } from "@/models/index.js";
|
||||||
|
|
||||||
|
export function generateMutedUserRenotesQueryForNotes(q: SelectQueryBuilder<any>, me: { id: User["id"] }): void {
|
||||||
|
const mutingQuery = RenoteMutings.createQueryBuilder("renote_muting")
|
||||||
|
.select("renote_muting.muteeId")
|
||||||
|
.where("renote_muting.muterId = :muterId", { muterId: me.id });
|
||||||
|
|
||||||
|
q.andWhere(new Brackets(qb => {
|
||||||
|
qb
|
||||||
|
.where(new Brackets(qb => {
|
||||||
|
qb.where("note.renoteId IS NOT NULL");
|
||||||
|
qb.andWhere("note.text IS NULL");
|
||||||
|
qb.andWhere(`note.userId NOT IN (${ mutingQuery.getQuery() })`);
|
||||||
|
}))
|
||||||
|
.orWhere("note.renoteId IS NULL")
|
||||||
|
.orWhere("note.text IS NOT NULL");
|
||||||
|
}));
|
||||||
|
|
||||||
|
q.setParameters(mutingQuery.getParameters());
|
||||||
|
}
|
|
@ -231,6 +231,9 @@ import * as ep___miauth_genToken from "./endpoints/miauth/gen-token.js";
|
||||||
import * as ep___mute_create from "./endpoints/mute/create.js";
|
import * as ep___mute_create from "./endpoints/mute/create.js";
|
||||||
import * as ep___mute_delete from "./endpoints/mute/delete.js";
|
import * as ep___mute_delete from "./endpoints/mute/delete.js";
|
||||||
import * as ep___mute_list from "./endpoints/mute/list.js";
|
import * as ep___mute_list from "./endpoints/mute/list.js";
|
||||||
|
import * as ep___renote_mute_create from "./endpoints/renote-mute/create.js";
|
||||||
|
import * as ep___renote_mute_delete from "./endpoints/renote-mute/delete.js";
|
||||||
|
import * as ep___renote_mute_list from "./endpoints/renote-mute/list.js";
|
||||||
import * as ep___my_apps from "./endpoints/my/apps.js";
|
import * as ep___my_apps from "./endpoints/my/apps.js";
|
||||||
import * as ep___notes from "./endpoints/notes.js";
|
import * as ep___notes from "./endpoints/notes.js";
|
||||||
import * as ep___notes_children from "./endpoints/notes/children.js";
|
import * as ep___notes_children from "./endpoints/notes/children.js";
|
||||||
|
@ -626,6 +629,9 @@ const eps = [
|
||||||
["ping", ep___ping],
|
["ping", ep___ping],
|
||||||
["pinned-users", ep___pinnedUsers],
|
["pinned-users", ep___pinnedUsers],
|
||||||
["recommended-instances", ep___recommendedInstances],
|
["recommended-instances", ep___recommendedInstances],
|
||||||
|
["renote-mute/create", ep___renote_mute_create],
|
||||||
|
["renote-mute/delete", ep___renote_mute_delete],
|
||||||
|
["renote-mute/list", ep___renote_mute_list],
|
||||||
["custom-motd", ep___customMOTD],
|
["custom-motd", ep___customMOTD],
|
||||||
["custom-splash-icons", ep___customSplashIcons],
|
["custom-splash-icons", ep___customSplashIcons],
|
||||||
["latest-version", ep___latestVersion],
|
["latest-version", ep___latestVersion],
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { generateMutedUserQuery } from "../../common/generate-muted-user-query.j
|
||||||
import { generateRepliesQuery } from "../../common/generate-replies-query.js";
|
import { generateRepliesQuery } from "../../common/generate-replies-query.js";
|
||||||
import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.js";
|
import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.js";
|
||||||
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
|
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
|
||||||
|
import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["notes"],
|
tags: ["notes"],
|
||||||
|
@ -86,6 +87,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
generateMutedUserQuery(query, user);
|
generateMutedUserQuery(query, user);
|
||||||
generateMutedNoteQuery(query, user);
|
generateMutedNoteQuery(query, user);
|
||||||
generateBlockedUserQuery(query, user);
|
generateBlockedUserQuery(query, user);
|
||||||
|
generateMutedUserRenotesQueryForNotes(query, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.withFiles) {
|
if (ps.withFiles) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { generateRepliesQuery } from "../../common/generate-replies-query.js";
|
||||||
import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.js";
|
import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.js";
|
||||||
import { generateChannelQuery } from "../../common/generate-channel-query.js";
|
import { generateChannelQuery } from "../../common/generate-channel-query.js";
|
||||||
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
|
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
|
||||||
|
import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["notes"],
|
tags: ["notes"],
|
||||||
|
@ -103,6 +104,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
generateMutedUserQuery(query, user);
|
generateMutedUserQuery(query, user);
|
||||||
generateMutedNoteQuery(query, user);
|
generateMutedNoteQuery(query, user);
|
||||||
generateBlockedUserQuery(query, user);
|
generateBlockedUserQuery(query, user);
|
||||||
|
generateMutedUserRenotesQueryForNotes(query, user);
|
||||||
|
|
||||||
if (ps.includeMyRenotes === false) {
|
if (ps.includeMyRenotes === false) {
|
||||||
query.andWhere(
|
query.andWhere(
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { generateRepliesQuery } from "../../common/generate-replies-query.js";
|
||||||
import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.js";
|
import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.js";
|
||||||
import { generateChannelQuery } from "../../common/generate-channel-query.js";
|
import { generateChannelQuery } from "../../common/generate-channel-query.js";
|
||||||
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
|
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
|
||||||
|
import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["notes"],
|
tags: ["notes"],
|
||||||
|
@ -96,6 +97,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
if (user) generateMutedUserQuery(query, user);
|
if (user) generateMutedUserQuery(query, user);
|
||||||
if (user) generateMutedNoteQuery(query, user);
|
if (user) generateMutedNoteQuery(query, user);
|
||||||
if (user) generateBlockedUserQuery(query, user);
|
if (user) generateBlockedUserQuery(query, user);
|
||||||
|
if (user) generateMutedUserRenotesQueryForNotes(query, user);
|
||||||
|
|
||||||
if (ps.withFiles) {
|
if (ps.withFiles) {
|
||||||
query.andWhere("note.fileIds != '{}'");
|
query.andWhere("note.fileIds != '{}'");
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { generateRepliesQuery } from "../../common/generate-replies-query.js";
|
||||||
import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.js";
|
import { generateMutedNoteQuery } from "../../common/generate-muted-note-query.js";
|
||||||
import { generateChannelQuery } from "../../common/generate-channel-query.js";
|
import { generateChannelQuery } from "../../common/generate-channel-query.js";
|
||||||
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
|
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
|
||||||
|
import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["notes"],
|
tags: ["notes"],
|
||||||
|
@ -95,6 +96,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
generateMutedUserQuery(query, user);
|
generateMutedUserQuery(query, user);
|
||||||
generateMutedNoteQuery(query, user);
|
generateMutedNoteQuery(query, user);
|
||||||
generateBlockedUserQuery(query, user);
|
generateBlockedUserQuery(query, user);
|
||||||
|
generateMutedUserRenotesQueryForNotes(query, user);
|
||||||
|
|
||||||
if (ps.includeMyRenotes === false) {
|
if (ps.includeMyRenotes === false) {
|
||||||
query.andWhere(
|
query.andWhere(
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { genId } from "@/misc/gen-id.js";
|
||||||
|
import { RenoteMutings } from "@/models/index.js";
|
||||||
|
import { RenoteMuting } from "@/models/entities/renote-muting.js";
|
||||||
|
import define from "../../define.js";
|
||||||
|
import { ApiError } from "../../error.js";
|
||||||
|
import { getUser } from "../../common/getters.js";
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ["account"],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
|
kind: "write:mutes",
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchUser: {
|
||||||
|
message: "No such user.",
|
||||||
|
code: "NO_SUCH_USER",
|
||||||
|
id: "6fef56f3-e765-4957-88e5-c6f65329b8a5",
|
||||||
|
},
|
||||||
|
|
||||||
|
muteeIsYourself: {
|
||||||
|
message: "Mutee is yourself.",
|
||||||
|
code: "MUTEE_IS_YOURSELF",
|
||||||
|
id: "a4619cb2-5f23-484b-9301-94c903074e10",
|
||||||
|
},
|
||||||
|
|
||||||
|
alreadyMuting: {
|
||||||
|
message: "You are already muting that user.",
|
||||||
|
code: "ALREADY_MUTING",
|
||||||
|
id: "7e7359cb-160c-4956-b08f-4d1c653cd007",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
userId: { type: "string", format: "misskey:id" },
|
||||||
|
},
|
||||||
|
required: ["userId"],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default define(meta, paramDef, async (ps, user) => {
|
||||||
|
const muter = user;
|
||||||
|
|
||||||
|
// 自分自身
|
||||||
|
if (user.id === ps.userId) {
|
||||||
|
throw new ApiError(meta.errors.muteeIsYourself);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mutee
|
||||||
|
const mutee = await getUser(ps.userId).catch(e => {
|
||||||
|
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if already muting
|
||||||
|
const exist = await RenoteMutings.findOneBy({
|
||||||
|
muterId: muter.id,
|
||||||
|
muteeId: mutee.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist != null) {
|
||||||
|
throw new ApiError(meta.errors.alreadyMuting);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mute
|
||||||
|
await RenoteMutings.insert({
|
||||||
|
id: genId(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
muterId: muter.id,
|
||||||
|
muteeId: mutee.id,
|
||||||
|
} as RenoteMuting);
|
||||||
|
|
||||||
|
// publishUserEvent(user.id, "mute", mutee);
|
||||||
|
});
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { RenoteMutings } from "@/models/index.js";
|
||||||
|
import define from "../../define.js";
|
||||||
|
import { ApiError } from "../../error.js";
|
||||||
|
import { getUser } from "../../common/getters.js";
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ["account"],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
|
kind: "write:mutes",
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchUser: {
|
||||||
|
message: "No such user.",
|
||||||
|
code: "NO_SUCH_USER",
|
||||||
|
id: "b851d00b-8ab1-4a56-8b1b-e24187cb48ef",
|
||||||
|
},
|
||||||
|
|
||||||
|
muteeIsYourself: {
|
||||||
|
message: "Mutee is yourself.",
|
||||||
|
code: "MUTEE_IS_YOURSELF",
|
||||||
|
id: "f428b029-6b39-4d48-a1d2-cc1ae6dd5cf9",
|
||||||
|
},
|
||||||
|
|
||||||
|
notMuting: {
|
||||||
|
message: "You are not muting that user.",
|
||||||
|
code: "NOT_MUTING",
|
||||||
|
id: "5467d020-daa9-4553-81e1-135c0c35a96d",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
userId: { type: "string", format: "misskey:id" },
|
||||||
|
},
|
||||||
|
required: ["userId"],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default define(meta, paramDef, async (ps, user) => {
|
||||||
|
const muter = user;
|
||||||
|
|
||||||
|
// Check if the mutee is yourself
|
||||||
|
if (user.id === ps.userId) {
|
||||||
|
throw new ApiError(meta.errors.muteeIsYourself);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mutee
|
||||||
|
const mutee = await getUser(ps.userId).catch(e => {
|
||||||
|
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check not muting
|
||||||
|
const exist = await RenoteMutings.findOneBy({
|
||||||
|
muterId: muter.id,
|
||||||
|
muteeId: mutee.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist == null) {
|
||||||
|
throw new ApiError(meta.errors.notMuting);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mute
|
||||||
|
await RenoteMutings.delete({
|
||||||
|
id: exist.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// publishUserEvent(user.id, "unmute", mutee);
|
||||||
|
});
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { RenoteMutings } from "@/models/index.js";
|
||||||
|
import define from "../../define.js";
|
||||||
|
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ["account"],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
|
kind: "read:mutes",
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: "array",
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
optional: false, nullable: false,
|
||||||
|
ref: "RenoteMuting",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
limit: { type: "integer", minimum: 1, maximum: 100, default: 30 },
|
||||||
|
sinceId: { type: "string", format: "misskey:id" },
|
||||||
|
untilId: { type: "string", format: "misskey:id" },
|
||||||
|
},
|
||||||
|
required: [],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default define(meta, paramDef, async (ps, me) => {
|
||||||
|
const query = makePaginationQuery(RenoteMutings.createQueryBuilder("muting"), ps.sinceId, ps.untilId)
|
||||||
|
.andWhere("muting.muterId = :meId", { meId: me.id });
|
||||||
|
|
||||||
|
const mutings = await query
|
||||||
|
.take(ps.limit)
|
||||||
|
.getMany();
|
||||||
|
|
||||||
|
return await RenoteMutings.packMany(mutings, me);
|
||||||
|
});
|
|
@ -57,6 +57,10 @@ export const meta = {
|
||||||
optional: false,
|
optional: false,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
},
|
},
|
||||||
|
isRenoteMuted: {
|
||||||
|
type: "boolean",
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -107,6 +111,10 @@ export const meta = {
|
||||||
optional: false,
|
optional: false,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
},
|
},
|
||||||
|
isRenoteMuted: {
|
||||||
|
type: "boolean",
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,6 +30,10 @@ export default abstract class Channel {
|
||||||
return this.connection.muting;
|
return this.connection.muting;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected get renoteMuting() {
|
||||||
|
return this.connection.renoteMuting;
|
||||||
|
}
|
||||||
|
|
||||||
protected get blocking() {
|
protected get blocking() {
|
||||||
return this.connection.blocking;
|
return this.connection.blocking;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,8 @@ export default class extends Channel {
|
||||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||||
if (isUserRelated(note, this.blocking)) return;
|
if (isUserRelated(note, this.blocking)) return;
|
||||||
|
|
||||||
|
if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
|
||||||
|
|
||||||
this.connection.cacheNote(note);
|
this.connection.cacheNote(note);
|
||||||
|
|
||||||
this.send("note", note);
|
this.send("note", note);
|
||||||
|
|
|
@ -36,6 +36,8 @@ export default class extends Channel {
|
||||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||||
if (isUserRelated(note, this.blocking)) return;
|
if (isUserRelated(note, this.blocking)) return;
|
||||||
|
|
||||||
|
if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
|
||||||
|
|
||||||
this.connection.cacheNote(note);
|
this.connection.cacheNote(note);
|
||||||
|
|
||||||
this.send("note", note);
|
this.send("note", note);
|
||||||
|
|
|
@ -56,6 +56,8 @@ export default class extends Channel {
|
||||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||||
if (isUserRelated(note, this.blocking)) return;
|
if (isUserRelated(note, this.blocking)) return;
|
||||||
|
|
||||||
|
if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
|
||||||
|
|
||||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||||
|
|
|
@ -37,6 +37,8 @@ export default class extends Channel {
|
||||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||||
if (isUserRelated(note, this.blocking)) return;
|
if (isUserRelated(note, this.blocking)) return;
|
||||||
|
|
||||||
|
if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
|
||||||
|
|
||||||
this.connection.cacheNote(note);
|
this.connection.cacheNote(note);
|
||||||
|
|
||||||
this.send("note", note);
|
this.send("note", note);
|
||||||
|
|
|
@ -54,6 +54,8 @@ export default class extends Channel {
|
||||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||||
if (isUserRelated(note, this.blocking)) return;
|
if (isUserRelated(note, this.blocking)) return;
|
||||||
|
|
||||||
|
if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
|
||||||
|
|
||||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||||
|
|
|
@ -71,6 +71,8 @@ export default class extends Channel {
|
||||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||||
if (isUserRelated(note, this.blocking)) return;
|
if (isUserRelated(note, this.blocking)) return;
|
||||||
|
|
||||||
|
if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
|
||||||
|
|
||||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||||
|
|
|
@ -48,6 +48,8 @@ export default class extends Channel {
|
||||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||||
if (isUserRelated(note, this.blocking)) return;
|
if (isUserRelated(note, this.blocking)) return;
|
||||||
|
|
||||||
|
if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
|
||||||
|
|
||||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||||
|
|
|
@ -56,6 +56,8 @@ export default class extends Channel {
|
||||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||||
if (isUserRelated(note, this.blocking)) return;
|
if (isUserRelated(note, this.blocking)) return;
|
||||||
|
|
||||||
|
if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
|
||||||
|
|
||||||
this.send("note", note);
|
this.send("note", note);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
Users,
|
Users,
|
||||||
Followings,
|
Followings,
|
||||||
Mutings,
|
Mutings,
|
||||||
|
RenoteMutings,
|
||||||
UserProfiles,
|
UserProfiles,
|
||||||
ChannelFollowings,
|
ChannelFollowings,
|
||||||
Blockings,
|
Blockings,
|
||||||
|
@ -36,6 +37,7 @@ export default class Connection {
|
||||||
public userProfile?: UserProfile | null;
|
public userProfile?: UserProfile | null;
|
||||||
public following: Set<User["id"]> = new Set();
|
public following: Set<User["id"]> = new Set();
|
||||||
public muting: Set<User["id"]> = new Set();
|
public muting: Set<User["id"]> = new Set();
|
||||||
|
public renoteMuting: Set<User["id"]> = new Set();
|
||||||
public blocking: Set<User["id"]> = new Set(); // "被"blocking
|
public blocking: Set<User["id"]> = new Set(); // "被"blocking
|
||||||
public followingChannels: Set<ChannelModel["id"]> = new Set();
|
public followingChannels: Set<ChannelModel["id"]> = new Set();
|
||||||
public token?: AccessToken;
|
public token?: AccessToken;
|
||||||
|
@ -80,6 +82,7 @@ export default class Connection {
|
||||||
if (this.user) {
|
if (this.user) {
|
||||||
this.updateFollowing();
|
this.updateFollowing();
|
||||||
this.updateMuting();
|
this.updateMuting();
|
||||||
|
this.updateRenoteMuting();
|
||||||
this.updateBlocking();
|
this.updateBlocking();
|
||||||
this.updateFollowingChannels();
|
this.updateFollowingChannels();
|
||||||
this.updateUserProfile();
|
this.updateUserProfile();
|
||||||
|
@ -114,6 +117,7 @@ export default class Connection {
|
||||||
this.muting.delete(data.body.id);
|
this.muting.delete(data.body.id);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// TODO: renote mute events
|
||||||
// TODO: block events
|
// TODO: block events
|
||||||
|
|
||||||
case "followChannel":
|
case "followChannel":
|
||||||
|
@ -564,6 +568,17 @@ export default class Connection {
|
||||||
this.muting = new Set<string>(mutings.map((x) => x.muteeId));
|
this.muting = new Set<string>(mutings.map((x) => x.muteeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async updateRenoteMuting() {
|
||||||
|
const renoteMutings = await RenoteMutings.find({
|
||||||
|
where: {
|
||||||
|
muterId: this.user!.id,
|
||||||
|
},
|
||||||
|
select: ["muteeId"],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.renoteMuting = new Set<string>(renoteMutings.map((x) => x.muteeId));
|
||||||
|
}
|
||||||
|
|
||||||
private async updateBlocking() {
|
private async updateBlocking() {
|
||||||
// ここでいうBlockingは被Blockingの意
|
// ここでいうBlockingは被Blockingの意
|
||||||
const blockings = await Blockings.find({
|
const blockings = await Blockings.find({
|
||||||
|
|
|
@ -765,6 +765,9 @@ export type Endpoints = {
|
||||||
"mute/create": { req: TODO; res: TODO };
|
"mute/create": { req: TODO; res: TODO };
|
||||||
"mute/delete": { req: { userId: User["id"] }; res: null };
|
"mute/delete": { req: { userId: User["id"] }; res: null };
|
||||||
"mute/list": { req: TODO; res: TODO };
|
"mute/list": { req: TODO; res: TODO };
|
||||||
|
"renote-mute/create": { req: TODO; res: TODO; };
|
||||||
|
"renote-mute/delete": { req: { userId: User['id'] }; res: null; };
|
||||||
|
"renote-mute/list": { req: TODO; res: TODO; };
|
||||||
|
|
||||||
// my
|
// my
|
||||||
"my/apps": { req: TODO; res: TODO };
|
"my/apps": { req: TODO; res: TODO };
|
||||||
|
|
|
@ -53,6 +53,7 @@ export type UserDetailed = UserLite & {
|
||||||
isLocked: boolean;
|
isLocked: boolean;
|
||||||
isModerator: boolean;
|
isModerator: boolean;
|
||||||
isMuted: boolean;
|
isMuted: boolean;
|
||||||
|
isRenoteMuted: boolean;
|
||||||
isSilenced: boolean;
|
isSilenced: boolean;
|
||||||
isSuspended: boolean;
|
isSuspended: boolean;
|
||||||
lang: string | null;
|
lang: string | null;
|
||||||
|
|
|
@ -117,7 +117,15 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleBlock() {
|
async function toggleRenoteMute(): Promise<void> {
|
||||||
|
os.apiWithDialog(user.isRenoteMuted ? "renote-mute/delete" : "renote-mute/create", {
|
||||||
|
userId: user.id,
|
||||||
|
}).then(() => {
|
||||||
|
user.isRenoteMuted = !user.isRenoteMuted;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleBlock(): Promise<void> {
|
||||||
if (
|
if (
|
||||||
!(await getConfirmed(
|
!(await getConfirmed(
|
||||||
user.isBlocking ? i18n.ts.unblockConfirm : i18n.ts.blockConfirm,
|
user.isBlocking ? i18n.ts.unblockConfirm : i18n.ts.blockConfirm,
|
||||||
|
@ -261,6 +269,13 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||||
if ($i && meId !== user.id) {
|
if ($i && meId !== user.id) {
|
||||||
menu = menu.concat([
|
menu = menu.concat([
|
||||||
null,
|
null,
|
||||||
|
{
|
||||||
|
icon: user.isRenoteMuted
|
||||||
|
? "ph-eye ph-bold ph-lg"
|
||||||
|
: "ph-eye-slash ph-bold ph-lg",
|
||||||
|
text: user.isRenoteMuted ? i18n.ts.renoteUnmute : i18n.ts.renoteMute,
|
||||||
|
action: toggleRenoteMute,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: user.isMuted
|
icon: user.isMuted
|
||||||
? "ph-eye ph-bold ph-lg"
|
? "ph-eye ph-bold ph-lg"
|
||||||
|
|
Loading…
Reference in New Issue