Merge upstream
This commit is contained in:
commit
ad42eccfa4
@ -596,6 +596,8 @@ unsetUserAvatar: "アイコンを解除"
|
|||||||
unsetUserAvatarConfirm: "アイコンを解除しますか?"
|
unsetUserAvatarConfirm: "アイコンを解除しますか?"
|
||||||
unsetUserBanner: "バナーを解除"
|
unsetUserBanner: "バナーを解除"
|
||||||
unsetUserBannerConfirm: "バナーを解除しますか?"
|
unsetUserBannerConfirm: "バナーを解除しますか?"
|
||||||
|
unsetUserMutualLink: "相互リンクを削除"
|
||||||
|
unsetUserMutualLinkConfirm: "相互リンクを削除しますか?"
|
||||||
deleteAllFiles: "すべてのファイルを削除"
|
deleteAllFiles: "すべてのファイルを削除"
|
||||||
deleteAllFilesConfirm: "すべてのファイルを削除しますか?"
|
deleteAllFilesConfirm: "すべてのファイルを削除しますか?"
|
||||||
removeAllFollowing: "フォローを全解除"
|
removeAllFollowing: "フォローを全解除"
|
||||||
@ -1282,6 +1284,9 @@ onboarding: "オンボーディング"
|
|||||||
refreshMetadata: "サーバー情報を更新"
|
refreshMetadata: "サーバー情報を更新"
|
||||||
removeAllFollowings: "相互フォロー解除"
|
removeAllFollowings: "相互フォロー解除"
|
||||||
areYouSureToRemoveAllFollowings: "本当に{host}とのすべてのフォロー関係を削除しますか? 実行後は元に戻せません。 相手インスタンスが閉鎖されたと判断した場合のみ実行してください。"
|
areYouSureToRemoveAllFollowings: "本当に{host}とのすべてのフォロー関係を削除しますか? 実行後は元に戻せません。 相手インスタンスが閉鎖されたと判断した場合のみ実行してください。"
|
||||||
|
mutualLink: "相互リンク"
|
||||||
|
mutualBannerThisUser: "このユーザーのバナー"
|
||||||
|
maximum: "最大"
|
||||||
|
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "遊び方"
|
howToPlay: "遊び方"
|
||||||
@ -1803,6 +1808,8 @@ _role:
|
|||||||
canUseDriveFileInSoundSettings: "サウンド設定でドライブのファイルを利用"
|
canUseDriveFileInSoundSettings: "サウンド設定でドライブのファイルを利用"
|
||||||
canUseReaction: "リアクションの利用"
|
canUseReaction: "リアクションの利用"
|
||||||
avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
|
avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
|
||||||
|
mutualLinkSectionLimit: "相互リンクのセクションの最大数"
|
||||||
|
mutualLinkLimit: "セクション内の相互リンクの最大数"
|
||||||
_condition:
|
_condition:
|
||||||
roleAssignedTo: "マニュアルロールにアサイン済み"
|
roleAssignedTo: "マニュアルロールにアサイン済み"
|
||||||
isLocal: "ローカルユーザー"
|
isLocal: "ローカルユーザー"
|
||||||
@ -2185,6 +2192,7 @@ _permissions:
|
|||||||
"write:admin:suspend-user": "ユーザーを凍結する"
|
"write:admin:suspend-user": "ユーザーを凍結する"
|
||||||
"write:admin:unset-user-avatar": "ユーザーのアバターを削除する"
|
"write:admin:unset-user-avatar": "ユーザーのアバターを削除する"
|
||||||
"write:admin:unset-user-banner": "ユーザーのバーナーを削除する"
|
"write:admin:unset-user-banner": "ユーザーのバーナーを削除する"
|
||||||
|
"write:admin:unset-user-mutual-link": "ユーザーの相互リンクを削除する"
|
||||||
"write:admin:unsuspend-user": "ユーザーの凍結を解除する"
|
"write:admin:unsuspend-user": "ユーザーの凍結を解除する"
|
||||||
"write:admin:meta": "インスタンスのメタデータを操作する"
|
"write:admin:meta": "インスタンスのメタデータを操作する"
|
||||||
"write:admin:user-note": "モデレーションノートを操作する"
|
"write:admin:user-note": "モデレーションノートを操作する"
|
||||||
@ -2346,6 +2354,17 @@ _profile:
|
|||||||
changeBanner: "バナー画像を変更"
|
changeBanner: "バナー画像を変更"
|
||||||
verifiedLinkDescription: "内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。"
|
verifiedLinkDescription: "内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。"
|
||||||
avatarDecorationMax: "最大{max}つまでデコレーションを付けられます。"
|
avatarDecorationMax: "最大{max}つまでデコレーションを付けられます。"
|
||||||
|
mutualLinksEdit: "相互リンクを編集"
|
||||||
|
mutualLinksBanner: "相互リンクのバナー"
|
||||||
|
mutualLinksDescriptionEdit: "説明"
|
||||||
|
mutualLinksUrl: "リンク先のURL"
|
||||||
|
mutualLinkPining: "このセクションをプロフィールにピン留め"
|
||||||
|
mutualLinksDescription: "相互リンクを設定すると、あなたのプロフィールにバナーが表示されます。"
|
||||||
|
addMutualLink: "相互リンクを追加"
|
||||||
|
addMutualLinkSection: "セクションを追加"
|
||||||
|
sectionName: "セクション名"
|
||||||
|
sectionNameNoneDescription: "セクション名を表示しないようにする"
|
||||||
|
sectionNameNone: "セクション名を表示しない"
|
||||||
|
|
||||||
_exportOrImport:
|
_exportOrImport:
|
||||||
allNotes: "全てのノート"
|
allNotes: "全てのノート"
|
||||||
|
11
packages/backend/migration/1723311628855-mutuallinks.js
Normal file
11
packages/backend/migration/1723311628855-mutuallinks.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export class Mutuallinks1723311628855 {
|
||||||
|
name = 'Mutuallinks1723311628855'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" ADD "mutualLinkSections" jsonb NOT NULL DEFAULT '[]'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "mutualLinkSections"`);
|
||||||
|
}
|
||||||
|
}
|
@ -70,6 +70,8 @@ export type RolePolicies = {
|
|||||||
rateLimitFactor: number;
|
rateLimitFactor: number;
|
||||||
avatarDecorationLimit: number;
|
avatarDecorationLimit: number;
|
||||||
canUseAccountRemoval: boolean;
|
canUseAccountRemoval: boolean;
|
||||||
|
mutualLinkSectionLimit: number;
|
||||||
|
mutualLinkLimit: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_POLICIES: RolePolicies = {
|
export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
@ -110,6 +112,8 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
|||||||
rateLimitFactor: 1,
|
rateLimitFactor: 1,
|
||||||
avatarDecorationLimit: 1,
|
avatarDecorationLimit: 1,
|
||||||
canUseAccountRemoval: true,
|
canUseAccountRemoval: true,
|
||||||
|
mutualLinkSectionLimit: 1,
|
||||||
|
mutualLinkLimit: 15,
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -423,6 +427,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)),
|
userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)),
|
||||||
rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)),
|
rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)),
|
||||||
avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)),
|
avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)),
|
||||||
|
mutualLinkSectionLimit: calc('mutualLinkSectionLimit', vs => Math.max(...vs)),
|
||||||
|
mutualLinkLimit: calc('mutualLinkLimit', vs => Math.max(...vs)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,6 @@ import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
|||||||
import { isNotNull } from '@/misc/is-not-null.js';
|
import { isNotNull } from '@/misc/is-not-null.js';
|
||||||
import type { OnModuleInit } from '@nestjs/common';
|
import type { OnModuleInit } from '@nestjs/common';
|
||||||
import type { NoteEntityService } from './NoteEntityService.js';
|
import type { NoteEntityService } from './NoteEntityService.js';
|
||||||
import type { DriveFileEntityService } from './DriveFileEntityService.js';
|
|
||||||
import type { PageEntityService } from './PageEntityService.js';
|
import type { PageEntityService } from './PageEntityService.js';
|
||||||
|
|
||||||
const Ajv = _Ajv.default;
|
const Ajv = _Ajv.default;
|
||||||
@ -535,6 +534,7 @@ export class UserEntityService implements OnModuleInit {
|
|||||||
lang: profile?.lang,
|
lang: profile?.lang,
|
||||||
fields: profile?.fields,
|
fields: profile?.fields,
|
||||||
verifiedLinks: profile?.verifiedLinks,
|
verifiedLinks: profile?.verifiedLinks,
|
||||||
|
mutualLinkSections: profile?.mutualLinkSections,
|
||||||
followersCount: followersCount ?? 0,
|
followersCount: followersCount ?? 0,
|
||||||
followingCount: followingCount ?? 0,
|
followingCount: followingCount ?? 0,
|
||||||
notesCount: user.notesCount,
|
notesCount: user.notesCount,
|
||||||
@ -564,7 +564,7 @@ export class UserEntityService implements OnModuleInit {
|
|||||||
isModerator: role.isModerator,
|
isModerator: role.isModerator,
|
||||||
isAdministrator: role.isAdministrator,
|
isAdministrator: role.isAdministrator,
|
||||||
displayOrder: role.displayOrder,
|
displayOrder: role.displayOrder,
|
||||||
}))
|
})),
|
||||||
),
|
),
|
||||||
memo: memo,
|
memo: memo,
|
||||||
moderationNote: iAmModerator ? (profile?.moderationNote ?? '') : undefined,
|
moderationNote: iAmModerator ? (profile?.moderationNote ?? '') : undefined,
|
||||||
|
@ -4,11 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
|
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
|
||||||
import { obsoleteNotificationTypes, followingVisibilities, followersVisibilities, notificationTypes } from '@/types.js';
|
import { followingVisibilities, followersVisibilities, notificationTypes } from '@/types.js';
|
||||||
import { id } from './util/id.js';
|
import { id } from './util/id.js';
|
||||||
import { MiUser } from './User.js';
|
import { MiUser } from './User.js';
|
||||||
import { MiPage } from './Page.js';
|
import { MiPage } from './Page.js';
|
||||||
import { MiUserList } from './UserList.js';
|
import { MiUserList } from './UserList.js';
|
||||||
|
import type { MiDriveFile } from './DriveFile.js';
|
||||||
|
|
||||||
// TODO: このテーブルで管理している情報すべてレジストリで管理するようにしても良いかも
|
// TODO: このテーブルで管理している情報すべてレジストリで管理するようにしても良いかも
|
||||||
// ただ、「emailVerified が true なユーザーを find する」のようなクエリは書けなくなるからウーン
|
// ただ、「emailVerified が true なユーザーを find する」のようなクエリは書けなくなるからウーン
|
||||||
@ -42,6 +43,18 @@ export class MiUserProfile {
|
|||||||
})
|
})
|
||||||
public description: string | null;
|
public description: string | null;
|
||||||
|
|
||||||
|
@Column('jsonb', {
|
||||||
|
default: [],
|
||||||
|
})
|
||||||
|
public mutualLinkSections: {
|
||||||
|
name: string | null;
|
||||||
|
mutualLinks: {
|
||||||
|
fileId: MiDriveFile['id'];
|
||||||
|
description: string | null;
|
||||||
|
imgSrc: string;
|
||||||
|
}[];
|
||||||
|
}[] | [];
|
||||||
|
|
||||||
@Column('jsonb', {
|
@Column('jsonb', {
|
||||||
default: [],
|
default: [],
|
||||||
})
|
})
|
||||||
|
@ -316,6 +316,14 @@ export const packedRolePoliciesSchema = {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
mutualLinkSectionLimit: {
|
||||||
|
type: 'integer',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
mutualLinkLimit: {
|
||||||
|
type: 'integer',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ export const packedUserLiteSchema = {
|
|||||||
behavior: {
|
behavior: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
nullable: false, optional: true,
|
nullable: false, optional: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -386,6 +386,29 @@ export const packedUserDetailedNotMeOnlySchema = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
nullable: false, optional: true,
|
nullable: false, optional: true,
|
||||||
},
|
},
|
||||||
|
mutualLinkSections: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: { type: 'string', nullable: true },
|
||||||
|
mutualLinks: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
url: { type: 'string' },
|
||||||
|
fileId: { type: 'string', format: 'misskey:id' },
|
||||||
|
description: { type: 'string', nullable: true },
|
||||||
|
imgSrc: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['url', 'fileId'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['mutualLinks'],
|
||||||
|
},
|
||||||
|
},
|
||||||
//#region relations
|
//#region relations
|
||||||
isFollowing: {
|
isFollowing: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
@ -32,6 +32,7 @@ import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-d
|
|||||||
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
|
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
|
||||||
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
|
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
|
||||||
import * as ep___admin_disposeCache from './endpoints/admin/dispose-cache.js';
|
import * as ep___admin_disposeCache from './endpoints/admin/dispose-cache.js';
|
||||||
|
import * as ep___admin_unsetUserMutualLink from './endpoints/admin/unset-user-mutual-link.js';
|
||||||
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
|
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
|
||||||
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
|
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
|
||||||
import * as ep___admin_drive_deleteAllFilesOfAUser from './endpoints/admin/drive/delete-all-files-of-a-user.js';
|
import * as ep___admin_drive_deleteAllFilesOfAUser from './endpoints/admin/drive/delete-all-files-of-a-user.js';
|
||||||
@ -423,6 +424,7 @@ const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-de
|
|||||||
const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default };
|
const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default };
|
||||||
const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default };
|
const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default };
|
||||||
const $admin_disposeCache: Provider = { provide: 'ep:admin/dispose-cache', useClass: ep___admin_disposeCache.default };
|
const $admin_disposeCache: Provider = { provide: 'ep:admin/dispose-cache', useClass: ep___admin_disposeCache.default };
|
||||||
|
const $admin_unsetUserMutualLink: Provider = { provide: 'ep:admin/unset-user-mutual-link', useClass: ep___admin_unsetUserMutualLink.default };
|
||||||
const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default };
|
const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default };
|
||||||
const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default };
|
const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default };
|
||||||
const $admin_drive_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/drive/delete-all-files-of-a-user', useClass: ep___admin_drive_deleteAllFilesOfAUser.default };
|
const $admin_drive_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/drive/delete-all-files-of-a-user', useClass: ep___admin_drive_deleteAllFilesOfAUser.default };
|
||||||
@ -818,6 +820,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||||||
$admin_unsetUserAvatar,
|
$admin_unsetUserAvatar,
|
||||||
$admin_unsetUserBanner,
|
$admin_unsetUserBanner,
|
||||||
$admin_disposeCache,
|
$admin_disposeCache,
|
||||||
|
$admin_unsetUserMutualLink,
|
||||||
$admin_drive_cleanRemoteFiles,
|
$admin_drive_cleanRemoteFiles,
|
||||||
$admin_drive_cleanup,
|
$admin_drive_cleanup,
|
||||||
$admin_drive_deleteAllFilesOfAUser,
|
$admin_drive_deleteAllFilesOfAUser,
|
||||||
@ -1207,6 +1210,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||||||
$admin_unsetUserAvatar,
|
$admin_unsetUserAvatar,
|
||||||
$admin_unsetUserBanner,
|
$admin_unsetUserBanner,
|
||||||
$admin_disposeCache,
|
$admin_disposeCache,
|
||||||
|
$admin_unsetUserMutualLink,
|
||||||
$admin_drive_cleanRemoteFiles,
|
$admin_drive_cleanRemoteFiles,
|
||||||
$admin_drive_cleanup,
|
$admin_drive_cleanup,
|
||||||
$admin_drive_deleteAllFilesOfAUser,
|
$admin_drive_deleteAllFilesOfAUser,
|
||||||
|
@ -18,6 +18,17 @@ const ajv = new Ajv({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
|
ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
|
||||||
|
ajv.addFormat('url', {
|
||||||
|
type: 'string',
|
||||||
|
validate: (url: string) => {
|
||||||
|
try {
|
||||||
|
new URL(url);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export type Response = Record<string, any> | void;
|
export type Response = Record<string, any> | void;
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-d
|
|||||||
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
|
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
|
||||||
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
|
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
|
||||||
import * as ep___admin_disposeCache from './endpoints/admin/dispose-cache.js';
|
import * as ep___admin_disposeCache from './endpoints/admin/dispose-cache.js';
|
||||||
|
import * as ep___admin_unsetUserMutualLink from './endpoints/admin/unset-user-mutual-link.js';
|
||||||
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
|
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
|
||||||
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
|
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
|
||||||
import * as ep___admin_drive_deleteAllFilesOfAUser from './endpoints/admin/drive/delete-all-files-of-a-user.js';
|
import * as ep___admin_drive_deleteAllFilesOfAUser from './endpoints/admin/drive/delete-all-files-of-a-user.js';
|
||||||
@ -421,6 +422,7 @@ const eps = [
|
|||||||
['admin/unset-user-avatar', ep___admin_unsetUserAvatar],
|
['admin/unset-user-avatar', ep___admin_unsetUserAvatar],
|
||||||
['admin/unset-user-banner', ep___admin_unsetUserBanner],
|
['admin/unset-user-banner', ep___admin_unsetUserBanner],
|
||||||
['admin/dispose-cache', ep___admin_disposeCache],
|
['admin/dispose-cache', ep___admin_disposeCache],
|
||||||
|
['admin/unset-user-mutual-link', ep___admin_unsetUserMutualLink],
|
||||||
['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles],
|
['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles],
|
||||||
['admin/drive/cleanup', ep___admin_drive_cleanup],
|
['admin/drive/cleanup', ep___admin_drive_cleanup],
|
||||||
['admin/drive/delete-all-files-of-a-user', ep___admin_drive_deleteAllFilesOfAUser],
|
['admin/drive/delete-all-files-of-a-user', ep___admin_drive_deleteAllFilesOfAUser],
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import type {
|
||||||
|
UsersRepository,
|
||||||
|
UserProfilesRepository,
|
||||||
|
} from '@/models/_.js';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
kind: 'write:admin:unset-user-mutual-link',
|
||||||
|
} 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
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.usersRepository)
|
||||||
|
private usersRepository: UsersRepository,
|
||||||
|
@Inject(DI.userProfilesRepository)
|
||||||
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
|
private moderationLogService: ModerationLogService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const user = await this.usersRepository.findOneBy({ id: ps.userId });
|
||||||
|
const userProfile = await this.userProfilesRepository.findOneBy({ userId: ps.userId });
|
||||||
|
|
||||||
|
if (user == null || userProfile == null) {
|
||||||
|
throw new Error('user not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.userProfilesRepository.update(user.id, {
|
||||||
|
mutualLinkSections: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.moderationLogService.log(me, 'unsetUserMutualLink', {
|
||||||
|
userId: user.id,
|
||||||
|
userUsername: user.username,
|
||||||
|
userMutualLinkSections: userProfile.mutualLinkSections,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -56,6 +56,12 @@ export const meta = {
|
|||||||
id: '539f3a45-f215-4f81-a9a8-31293640207f',
|
id: '539f3a45-f215-4f81-a9a8-31293640207f',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
noSuchFile: {
|
||||||
|
message: 'No such file.',
|
||||||
|
code: 'NO_SUCH_FILE',
|
||||||
|
id: 'e0f0d3c7-e704-4314-a0b5-04286d69a65c',
|
||||||
|
},
|
||||||
|
|
||||||
noSuchBanner: {
|
noSuchBanner: {
|
||||||
message: 'No such banner file.',
|
message: 'No such banner file.',
|
||||||
code: 'NO_SUCH_BANNER',
|
code: 'NO_SUCH_BANNER',
|
||||||
@ -68,6 +74,12 @@ export const meta = {
|
|||||||
id: 'f419f9f8-2f4d-46b1-9fb4-49d3a2fd7191',
|
id: 'f419f9f8-2f4d-46b1-9fb4-49d3a2fd7191',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fileNotAnImage: {
|
||||||
|
message: 'The specified file is not an image.',
|
||||||
|
code: 'FILE_NOT_AN_IMAGE',
|
||||||
|
id: '2851568b-5ad1-4031-bf0d-5320afebf3a9',
|
||||||
|
},
|
||||||
|
|
||||||
bannerNotAnImage: {
|
bannerNotAnImage: {
|
||||||
message: 'The file specified as a banner is not an image.',
|
message: 'The file specified as a banner is not an image.',
|
||||||
code: 'BANNER_NOT_AN_IMAGE',
|
code: 'BANNER_NOT_AN_IMAGE',
|
||||||
@ -178,8 +190,8 @@ export const paramDef = {
|
|||||||
mutedWords: { type: 'array', items: {
|
mutedWords: { type: 'array', items: {
|
||||||
oneOf: [
|
oneOf: [
|
||||||
{ type: 'array', items: { type: 'string' } },
|
{ type: 'array', items: { type: 'string' } },
|
||||||
{ type: 'string' }
|
{ type: 'string' },
|
||||||
]
|
],
|
||||||
} },
|
} },
|
||||||
mutedInstances: { type: 'array', items: {
|
mutedInstances: { type: 'array', items: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -213,6 +225,28 @@ export const paramDef = {
|
|||||||
uniqueItems: true,
|
uniqueItems: true,
|
||||||
items: { type: 'string' },
|
items: { type: 'string' },
|
||||||
},
|
},
|
||||||
|
mutualLinkSections: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: { type: 'string', nullable: true },
|
||||||
|
mutualLinks: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
url: { type: 'string', format: 'url' },
|
||||||
|
fileId: { type: 'string', format: 'misskey:id' },
|
||||||
|
description: { type: 'string', nullable: true },
|
||||||
|
},
|
||||||
|
required: ['url', 'fileId'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['mutualLinks'],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@ -322,6 +356,43 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
updates.avatarBlurhash = null;
|
updates.avatarBlurhash = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.mutualLinkSections) {
|
||||||
|
if (ps.mutualLinkSections.length > policy.mutualLinkSectionLimit) {
|
||||||
|
throw new ApiError(meta.errors.restrictedByRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutualLinkSections = ps.mutualLinkSections.map(async (section) => {
|
||||||
|
if (section.mutualLinks.length > policy.mutualLinkLimit) {
|
||||||
|
throw new ApiError(meta.errors.restrictedByRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutualLinks = await Promise.all(section.mutualLinks.map(async (mutualLink) => {
|
||||||
|
const file = await this.driveFilesRepository.findOneBy({ id: mutualLink.fileId });
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
throw new ApiError(meta.errors.noSuchFile);
|
||||||
|
}
|
||||||
|
if (!file.type.startsWith('image/')) {
|
||||||
|
throw new ApiError(meta.errors.fileNotAnImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: mutualLink.url,
|
||||||
|
fileId: file.id,
|
||||||
|
imgSrc: this.driveFileEntityService.getPublicUrl(file),
|
||||||
|
description: mutualLink.description ?? null,
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: section.name ?? null,
|
||||||
|
mutualLinks,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
profileUpdates.mutualLinkSections = await Promise.all(mutualLinkSections);
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.bannerId) {
|
if (ps.bannerId) {
|
||||||
if (!policy.canUpdateBanner) throw new ApiError(meta.errors.restrictedByRole);
|
if (!policy.canUpdateBanner) throw new ApiError(meta.errors.restrictedByRole);
|
||||||
const banner = await this.driveFilesRepository.findOneBy({ id: ps.bannerId });
|
const banner = await this.driveFilesRepository.findOneBy({ id: ps.bannerId });
|
||||||
|
@ -96,6 +96,7 @@ export const moderationLogTypes = [
|
|||||||
'deleteAvatarDecoration',
|
'deleteAvatarDecoration',
|
||||||
'unsetUserAvatar',
|
'unsetUserAvatar',
|
||||||
'unsetUserBanner',
|
'unsetUserBanner',
|
||||||
|
'unsetUserMutualLink',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type ModerationLogPayloads = {
|
export type ModerationLogPayloads = {
|
||||||
@ -314,6 +315,11 @@ export type ModerationLogPayloads = {
|
|||||||
userHost: string | null;
|
userHost: string | null;
|
||||||
fileId: string;
|
fileId: string;
|
||||||
};
|
};
|
||||||
|
unsetUserMutualLink: {
|
||||||
|
userId: string;
|
||||||
|
userUsername: string;
|
||||||
|
userMutualLinkSections: { name: string | null; mutualLinks: { fileId: string; description: string | null; imgSrc: string; }[]; }[] | []
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Serialized<T> = {
|
export type Serialized<T> = {
|
||||||
|
@ -8,7 +8,7 @@ process.env.NODE_ENV = 'test';
|
|||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { inspect } from 'node:util';
|
import { inspect } from 'node:util';
|
||||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||||
import { api, post, role, signup, successfulApiCall, uploadFile, failedApiCall } from '../utils.js';
|
import { api, failedApiCall, post, role, signup, successfulApiCall, uploadFile } from '../utils.js';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('ユーザー', () => {
|
describe('ユーザー', () => {
|
||||||
@ -75,6 +75,7 @@ describe('ユーザー', () => {
|
|||||||
lang: user.lang,
|
lang: user.lang,
|
||||||
fields: user.fields,
|
fields: user.fields,
|
||||||
verifiedLinks: user.verifiedLinks,
|
verifiedLinks: user.verifiedLinks,
|
||||||
|
mutualLinkSections: user.mutualLinkSections,
|
||||||
followersCount: user.followersCount,
|
followersCount: user.followersCount,
|
||||||
followingCount: user.followingCount,
|
followingCount: user.followingCount,
|
||||||
notesCount: user.notesCount,
|
notesCount: user.notesCount,
|
||||||
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
@click="(ev: MouseEvent) => warningExternalWebsite(ev, url)"
|
@click="(ev: MouseEvent) => warningExternalWebsite(ev, url)"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<i v-if="target === '_blank'" class="ti ti-external-link" :class="$style.icon"></i>
|
<i v-if="target === '_blank' && !hideIcon" class="ti ti-external-link" :class="$style.icon"></i>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -34,7 +34,9 @@ const props = withDefaults(defineProps<{
|
|||||||
url: string;
|
url: string;
|
||||||
rel?: null | string;
|
rel?: null | string;
|
||||||
navigationBehavior?: MkABehavior;
|
navigationBehavior?: MkABehavior;
|
||||||
|
hideIcon?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
|
hideIcon: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const self = props.url.startsWith(local);
|
const self = props.url.startsWith(local);
|
||||||
|
@ -109,6 +109,8 @@ export const ROLE_POLICIES = [
|
|||||||
'rateLimitFactor',
|
'rateLimitFactor',
|
||||||
'avatarDecorationLimit',
|
'avatarDecorationLimit',
|
||||||
'canUseAccountRemoval',
|
'canUseAccountRemoval',
|
||||||
|
'mutualLinkSectionLimit',
|
||||||
|
'mutualLinkLimit',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// なんか動かない
|
// なんか動かない
|
||||||
|
@ -66,6 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkButton v-if="user?.host == null" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton>
|
<MkButton v-if="user?.host == null" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton>
|
||||||
<MkButton inline danger @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton>
|
<MkButton inline danger @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton>
|
||||||
<MkButton inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
|
<MkButton inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
|
||||||
|
<MkButton inline danger @click="unsetUserMutualLink"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserMutualLink }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
@ -292,7 +293,7 @@ function createFetcher() {
|
|||||||
|
|
||||||
watch(moderationNote, async () => {
|
watch(moderationNote, async () => {
|
||||||
await misskeyApi('admin/update-user-note', {
|
await misskeyApi('admin/update-user-note', {
|
||||||
userId: user.value.id, text: moderationNote.value
|
userId: user.value.id, text: moderationNote.value,
|
||||||
}).then(refreshUser);
|
}).then(refreshUser);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -304,7 +305,7 @@ function refreshUser() {
|
|||||||
|
|
||||||
async function updateRemoteUser() {
|
async function updateRemoteUser() {
|
||||||
await os.apiWithDialog('federation/update-remote-user', {
|
await os.apiWithDialog('federation/update-remote-user', {
|
||||||
userId: user.value.id
|
userId: user.value.id,
|
||||||
}).then(refreshUser);
|
}).then(refreshUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,7 +336,7 @@ async function toggleSuspend(v) {
|
|||||||
suspended.value = !v;
|
suspended.value = !v;
|
||||||
} else {
|
} else {
|
||||||
await misskeyApi(v ? 'admin/suspend-user' : 'admin/unsuspend-user', {
|
await misskeyApi(v ? 'admin/suspend-user' : 'admin/unsuspend-user', {
|
||||||
userId: user.value.id
|
userId: user.value.id,
|
||||||
}).then(refreshUser);
|
}).then(refreshUser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -348,7 +349,7 @@ async function unsetUserAvatar() {
|
|||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
|
|
||||||
await os.apiWithDialog('admin/unset-user-avatar', {
|
await os.apiWithDialog('admin/unset-user-avatar', {
|
||||||
userId: user.value.id
|
userId: user.value.id,
|
||||||
}).then(refreshUser);
|
}).then(refreshUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,7 +361,19 @@ async function unsetUserBanner() {
|
|||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
|
|
||||||
await os.apiWithDialog('admin/unset-user-banner', {
|
await os.apiWithDialog('admin/unset-user-banner', {
|
||||||
userId: user.value.id
|
userId: user.value.id,
|
||||||
|
}).then(refreshUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unsetUserMutualLink() {
|
||||||
|
const confirm = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.ts.unsetUserMutualLinkConfirm,
|
||||||
|
});
|
||||||
|
if (confirm.canceled) return;
|
||||||
|
|
||||||
|
await os.apiWithDialog('admin/unset-user-mutual-banner', {
|
||||||
|
userId: user.value.id,
|
||||||
}).then(refreshUser);
|
}).then(refreshUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,7 +391,7 @@ async function deleteAllFiles() {
|
|||||||
|
|
||||||
if (typed.result === user.value?.username) {
|
if (typed.result === user.value?.username) {
|
||||||
await os.apiWithDialog('admin/drive/delete-all-files-of-a-user', {
|
await os.apiWithDialog('admin/drive/delete-all-files-of-a-user', {
|
||||||
userId: user.value.id
|
userId: user.value.id,
|
||||||
}).then(refreshUser);
|
}).then(refreshUser);
|
||||||
} else {
|
} else {
|
||||||
os.alert({
|
os.alert({
|
||||||
@ -447,7 +460,7 @@ async function assignRole() {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
await os.apiWithDialog('admin/roles/assign', {
|
await os.apiWithDialog('admin/roles/assign', {
|
||||||
roleId, userId: user.value.id, expiresAt
|
roleId, userId: user.value.id, expiresAt,
|
||||||
}).then(refreshUser);
|
}).then(refreshUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,7 +471,7 @@ async function unassignRole(role, ev) {
|
|||||||
danger: true,
|
danger: true,
|
||||||
action: async () => {
|
action: async () => {
|
||||||
await os.apiWithDialog('admin/roles/unassign', {
|
await os.apiWithDialog('admin/roles/unassign', {
|
||||||
roleId: role.id, userId: user.value.id
|
roleId: role.id, userId: user.value.id,
|
||||||
}).then(refreshUser);
|
}).then(refreshUser);
|
||||||
},
|
},
|
||||||
}], ev.currentTarget ?? ev.target);
|
}], ev.currentTarget ?? ev.target);
|
||||||
|
@ -775,6 +775,44 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.mutualLinkLimit, 'mutualLinkLimit'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.mutualLinkLimit }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.mutualLinkLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.mutualLinkLimit.value }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.mutualLinkLimit)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.mutualLinkLimit.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkInput v-model="role.policies.mutualLinkLimit.value" :disabled="role.policies.mutualLinkLimit.useDefault" type="number" :readonly="readonly">
|
||||||
|
</MkInput>
|
||||||
|
<MkRange v-model="role.policies.mutualLinkLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||||
|
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||||
|
</MkRange>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.mutualLinkSectionLimit, 'mutualLinkSectionLimit'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.mutualLinkSectionLimit }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.mutualLinkSectionLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.mutualLinkSectionLimit.value }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.mutualLinkSectionLimit)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.mutualLinkSectionLimit.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkInput v-model="role.policies.mutualLinkSectionLimit.value" :disabled="role.policies.mutualLinkSectionLimit.useDefault" type="number" :readonly="readonly">
|
||||||
|
</MkInput>
|
||||||
|
<MkRange v-model="role.policies.mutualLinkSectionLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||||
|
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||||
|
</MkRange>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canHideAds, 'canHideAds'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canHideAds, 'canHideAds'])">
|
||||||
<template #label>{{ i18n.ts._role._options.canHideAds }}</template>
|
<template #label>{{ i18n.ts._role._options.canHideAds }}</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
|
@ -278,6 +278,20 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</MkInput>
|
</MkInput>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.mutualLinkSectionLimit, 'mutualLinkSectionLimit'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.mutualLinkSectionLimit }}</template>
|
||||||
|
<template #suffix>{{ policies.mutualLinkSectionLimit }}</template>
|
||||||
|
<MkInput v-model="policies.mutualLinkSectionLimit" type="number">
|
||||||
|
</MkInput>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.mutualLinkLimit, 'mutualLinkLimit'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.mutualLinkLimit }}</template>
|
||||||
|
<template #suffix>{{ policies.mutualLinkLimit }}</template>
|
||||||
|
<MkInput v-model="policies.mutualLinkLimit" type="number">
|
||||||
|
</MkInput>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canHideAds, 'canHideAds'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canHideAds, 'canHideAds'])">
|
||||||
<template #label>{{ i18n.ts._role._options.canHideAds }}</template>
|
<template #label>{{ i18n.ts._role._options.canHideAds }}</template>
|
||||||
<template #suffix>{{ policies.canHideAds ? i18n.ts.yes : i18n.ts.no }}</template>
|
<template #suffix>{{ policies.canHideAds ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
@ -87,6 +87,79 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</MkFolder>
|
</MkFolder>
|
||||||
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
|
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
|
||||||
</FormSlot>
|
</FormSlot>
|
||||||
|
<FormSlot>
|
||||||
|
<MkFolder>
|
||||||
|
<template #icon><i class="ti ti-link"></i></template>
|
||||||
|
<template #label>{{ i18n.ts._profile.mutualLinksEdit }}</template>
|
||||||
|
|
||||||
|
<div :class="$style.metadataRoot">
|
||||||
|
<div :class="$style.metadataMargin">
|
||||||
|
<MkButton inline style="margin-right: 8px;" :disabled="mutualLinkSections.length >= $i.policies.mutualLinkSectionLimit" @click="addMutualLinkSections"><i class="ti ti-plus"></i> {{ i18n.ts._profile.addMutualLinkSection }}</MkButton>
|
||||||
|
<MkButton v-if="!mutualLinkSectionEditMode" inline danger style="margin-right: 8px;" @click="mutualLinkSectionEditMode = !mutualLinkSectionEditMode"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||||
|
<MkButton v-else inline style="margin-right: 8px;" @click="mutualLinkSectionEditMode = !mutualLinkSectionEditMode"><i class="ti ti-arrows-sort"></i> {{ i18n.ts.rearrange }}</MkButton>
|
||||||
|
<MkButton inline primary @click="saveMutualLinks"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Sortable
|
||||||
|
v-model="mutualLinkSections"
|
||||||
|
class="_gaps_s"
|
||||||
|
itemKey="id"
|
||||||
|
:animation="150"
|
||||||
|
:handle="'.' + $style.dragItemHandle"
|
||||||
|
@start="e => e.item.classList.add('active')"
|
||||||
|
@end="e => e.item.classList.remove('active')"
|
||||||
|
>
|
||||||
|
<template #item="{element: sectionElement,index: sectionIndex}">
|
||||||
|
<div :class="$style.mutualLinkSectionRoot">
|
||||||
|
<button v-if="!mutualLinkSectionEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
|
||||||
|
<button v-if="mutualLinkSectionEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteMutualLinkSection(sectionIndex)"><i class="ti ti-x"></i></button>
|
||||||
|
<FormSlot :style="{flexGrow: 1}">
|
||||||
|
<MkFolder>
|
||||||
|
<template #label>{{ sectionElement.name || i18n.ts._profile.sectionNameNone }}</template>
|
||||||
|
|
||||||
|
<div :class="$style.metadataMargin">
|
||||||
|
<MkInput v-model="sectionElement.name" :disabled="sectionElement.none" :placeholder="i18n.ts._profile.sectionName" :max="32"></MkInput>
|
||||||
|
<MkSwitch v-model="sectionElement.none" @update:modelValue="()=>{sectionElement.name = null}">{{ i18n.ts._profile.sectionNameNoneDescription }}</MkSwitch>
|
||||||
|
<MkButton inline style="margin-right: 8px;" :disabled="sectionElement.mutualLinks.length >= $i.policies.mutualLinkLimit" @click="addMutualLinks(sectionIndex)"><i class="ti ti-plus"></i> {{ i18n.ts._profile.addMutualLink }}</MkButton>
|
||||||
|
</div>
|
||||||
|
<Sortable
|
||||||
|
v-model="sectionElement.mutualLinks"
|
||||||
|
class="_gaps_s"
|
||||||
|
itemKey="id"
|
||||||
|
:animation="150"
|
||||||
|
:handle="'.' + $style.dragItemHandle"
|
||||||
|
@start="e => e.item.classList.add('active')"
|
||||||
|
@end="e => e.item.classList.remove('active')"
|
||||||
|
>
|
||||||
|
<template #item="{element: linkElement,index: linkIndex}">
|
||||||
|
<div :class="$style.mutualLinkRoot">
|
||||||
|
<button v-if="!mutualLinkSectionEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
|
||||||
|
<button v-if="mutualLinkSectionEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteMutualLink(sectionIndex,linkIndex)"><i class="ti ti-x"></i></button>
|
||||||
|
|
||||||
|
<div class="_gaps_s" :style="{flex: 1}">
|
||||||
|
<MkInput v-model="linkElement.url" small>
|
||||||
|
<template #label>{{ i18n.ts._profile.mutualLinksUrl }}</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkInput v-model="linkElement.description" small>
|
||||||
|
<template #label>{{ i18n.ts._profile.mutualLinksDescriptionEdit }}</template>
|
||||||
|
</MkInput>
|
||||||
|
<span>{{ i18n.ts._profile.mutualLinksBanner }}</span>
|
||||||
|
<img :class="$style.mutualLinkImg" :src="linkElement.imgSrc">
|
||||||
|
<MkButton class="_button" @click="ev => changeMutualLinkFile(ev, sectionIndex, linkIndex)">{{ i18n.ts.selectFile }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Sortable>
|
||||||
|
</MkFolder>
|
||||||
|
</FormSlot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Sortable>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<template #caption>{{ i18n.ts._profile.mutualLinksDescription }}</template>
|
||||||
|
</FormSlot>
|
||||||
|
|
||||||
<MkFolder>
|
<MkFolder>
|
||||||
<template #label>{{ i18n.ts.advancedSettings }}</template>
|
<template #label>{{ i18n.ts.advancedSettings }}</template>
|
||||||
@ -109,7 +182,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, reactive, ref, watch, defineAsyncComponent } from 'vue';
|
import { computed, reactive, ref, watch, defineAsyncComponent, Ref } from 'vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
@ -128,11 +201,11 @@ import { defaultStore } from '@/store.js';
|
|||||||
import { globalEvents } from '@/events.js';
|
import { globalEvents } from '@/events.js';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
|
import * as Misskey from "misskey-js";
|
||||||
|
|
||||||
const $i = signinRequired();
|
const $i = signinRequired();
|
||||||
|
|
||||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
||||||
|
|
||||||
const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
|
const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
|
||||||
|
|
||||||
const profile = reactive({
|
const profile = reactive({
|
||||||
@ -151,8 +224,10 @@ watch(() => profile, () => {
|
|||||||
deep: true,
|
deep: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mutualLinkSections = ref($i.mutualLinkSections ?? []) as Ref<Misskey.entities.UserDetailed['mutualLinkSections']>;
|
||||||
const fields = ref($i.fields.map(field => ({ id: Math.random().toString(), name: field.name, value: field.value })) ?? []);
|
const fields = ref($i.fields.map(field => ({ id: Math.random().toString(), name: field.name, value: field.value })) ?? []);
|
||||||
const fieldEditMode = ref(false);
|
const fieldEditMode = ref(false);
|
||||||
|
const mutualLinkSectionEditMode = ref(false);
|
||||||
|
|
||||||
function addField() {
|
function addField() {
|
||||||
fields.value.push({
|
fields.value.push({
|
||||||
@ -162,6 +237,22 @@ function addField() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addMutualLinks(index:number) {
|
||||||
|
mutualLinkSections.value[index].mutualLinks.push({
|
||||||
|
fileId: '',
|
||||||
|
url: '',
|
||||||
|
imgSrc: '',
|
||||||
|
description: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMutualLinkSections() {
|
||||||
|
mutualLinkSections.value.push({
|
||||||
|
name: null,
|
||||||
|
mutualLinks: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
while (fields.value.length < 4) {
|
while (fields.value.length < 4) {
|
||||||
addField();
|
addField();
|
||||||
}
|
}
|
||||||
@ -170,6 +261,14 @@ function deleteField(index: number) {
|
|||||||
fields.value.splice(index, 1);
|
fields.value.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteMutualLinkSection(index: number) {
|
||||||
|
mutualLinkSections.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteMutualLink(sectionIndex:number, index: number) {
|
||||||
|
mutualLinkSections.value[sectionIndex].mutualLinks.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
function saveFields() {
|
function saveFields() {
|
||||||
os.apiWithDialog('i/update', {
|
os.apiWithDialog('i/update', {
|
||||||
fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })),
|
fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })),
|
||||||
@ -177,6 +276,12 @@ function saveFields() {
|
|||||||
globalEvents.emit('requestClearPageCache');
|
globalEvents.emit('requestClearPageCache');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveMutualLinks() {
|
||||||
|
os.apiWithDialog('i/update', {
|
||||||
|
mutualLinkSections: mutualLinkSections.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
os.apiWithDialog('i/update', {
|
os.apiWithDialog('i/update', {
|
||||||
// 空文字列をnullにしたいので??は使うな
|
// 空文字列をnullにしたいので??は使うな
|
||||||
@ -203,6 +308,13 @@ function save() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function changeMutualLinkFile(ev: MouseEvent, sectionIndex: number, linkIndex: number) {
|
||||||
|
selectFile(ev.currentTarget ?? ev.target, i18n.ts.mutualLink).then(async (file) => {
|
||||||
|
mutualLinkSections.value[sectionIndex].mutualLinks[linkIndex].imgSrc = file.url;
|
||||||
|
mutualLinkSections.value[sectionIndex].mutualLinks[linkIndex].fileId = file.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function changeAvatar(ev) {
|
function changeAvatar(ev) {
|
||||||
selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => {
|
selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => {
|
||||||
let originalOrCropped = file;
|
let originalOrCropped = file;
|
||||||
@ -299,6 +411,36 @@ definePageMetadata(() => ({
|
|||||||
container-type: inline-size;
|
container-type: inline-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mutualLinkRoot{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8px;
|
||||||
|
padding-bottom: .75em;
|
||||||
|
border-bottom: solid 0.5px var(--divider);
|
||||||
|
flex: 1;
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.mutualLinkSectionRoot{
|
||||||
|
display: flex;
|
||||||
|
padding-bottom: .75em;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: solid 0.5px var(--divider);
|
||||||
|
overflow: clip;
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (drag button) 32px + (drag button margin) 8px + (input width) 200px * 2 + (input gap) 12px = 452px */
|
||||||
|
@container (max-width: 452px) {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
.metadataMargin {
|
.metadataMargin {
|
||||||
margin-bottom: 1.5em;
|
margin-bottom: 1.5em;
|
||||||
}
|
}
|
||||||
@ -350,4 +492,11 @@ definePageMetadata(() => ({
|
|||||||
.dragItemForm {
|
.dragItemForm {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mutualLinkImg {
|
||||||
|
max-width: 150px;
|
||||||
|
max-height: 30px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -80,6 +80,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p>
|
<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p>
|
||||||
</MkOmit>
|
</MkOmit>
|
||||||
</div>
|
</div>
|
||||||
|
<MkContainer v-if="user?.mutualLinkSections?.length > 0" :showHeader="false" :max-height="200" class="fields" :style="{borderRadius: 0}">
|
||||||
|
<div v-for="(section, index) in user?.mutualLinkSections" :key="index" :class="$style.mutualLinkSections">
|
||||||
|
<span v-if="section.name">{{ section.name }}</span>
|
||||||
|
<div :class="$style.mutualLinks">
|
||||||
|
<div v-for="(mutualLink, i) in section.mutualLinks" :key="i">
|
||||||
|
<MkLink :hideIcon="true" :url="mutualLink.url">
|
||||||
|
<img :class="$style.mutualLinkImg" :src="mutualLink.imgSrc" :alt="mutualLink.description"/>
|
||||||
|
</MkLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkContainer>
|
||||||
<div class="fields system">
|
<div class="fields system">
|
||||||
<dl v-if="user.location" class="field">
|
<dl v-if="user.location" class="field">
|
||||||
<dt class="name"><i class="ti ti-map-pin ti-fw"></i> {{ i18n.ts.location }}</dt>
|
<dt class="name"><i class="ti ti-map-pin ti-fw"></i> {{ i18n.ts.location }}</dt>
|
||||||
@ -143,6 +155,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<MkA :to="userPage(user)">
|
<MkA :to="userPage(user)">
|
||||||
<b>{{ number(user.notesCount) }}</b>
|
<b>{{ number(user.notesCount) }}</b>
|
||||||
@ -213,6 +226,8 @@ import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
|
|||||||
import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
|
import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
import { useRouter } from '@/router/supplier.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
|
import MkLink from '@/components/MkLink.vue';
|
||||||
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
|
|
||||||
function calcAge(birthdate: string): number {
|
function calcAge(birthdate: string): number {
|
||||||
const date = new Date(birthdate);
|
const date = new Date(birthdate);
|
||||||
@ -792,4 +807,37 @@ onUnmounted(() => {
|
|||||||
color: rgb(255, 255, 255);
|
color: rgb(255, 255, 255);
|
||||||
background-color: rgb(54, 54, 54);
|
background-color: rgb(54, 54, 54);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mutualLinkSections {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--panel);
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.mutualLinks {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
padding-top: 8px;
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mutualLink {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mutualLinkImg {
|
||||||
|
max-width: 150px;
|
||||||
|
max-height: 30px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -376,6 +376,9 @@ type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requ
|
|||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json'];
|
type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type AdminUnsetUserMutualLinkRequest = operations['admin___unset-user-mutual-link']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json'];
|
type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
@ -1232,6 +1235,7 @@ declare namespace entities {
|
|||||||
AdminAvatarDecorationsUpdateRequest,
|
AdminAvatarDecorationsUpdateRequest,
|
||||||
AdminUnsetUserAvatarRequest,
|
AdminUnsetUserAvatarRequest,
|
||||||
AdminUnsetUserBannerRequest,
|
AdminUnsetUserBannerRequest,
|
||||||
|
AdminUnsetUserMutualLinkRequest,
|
||||||
AdminDriveDeleteAllFilesOfAUserRequest,
|
AdminDriveDeleteAllFilesOfAUserRequest,
|
||||||
AdminDriveFilesRequest,
|
AdminDriveFilesRequest,
|
||||||
AdminDriveFilesResponse,
|
AdminDriveFilesResponse,
|
||||||
@ -2502,7 +2506,7 @@ type ModerationLog = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createIndieAuthClient", "updateIndieAuthClient", "deleteIndieAuthClient", "createSSOServiceProvider", "updateSSOServiceProvider", "deleteSSOServiceProvider", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner"];
|
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createIndieAuthClient", "updateIndieAuthClient", "deleteIndieAuthClient", "createSSOServiceProvider", "updateSSOServiceProvider", "deleteSSOServiceProvider", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner", "unsetUserMutualBanner"];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json'];
|
type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json'];
|
||||||
@ -2754,7 +2758,7 @@ type PagesUpdateRequest = operations['pages___update']['requestBody']['content']
|
|||||||
function parse(acct: string): Acct;
|
function parse(acct: string): Acct;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "read:admin:abuse-report-resolvers", "write:admin:abuse-report-resolvers", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:indie-auth", "read:admin:indie-auth", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:sso", "read:admin:sso", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
|
export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "read:admin:abuse-report-resolvers", "write:admin:abuse-report-resolvers", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unset-user-mutual-link", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:indie-auth", "read:admin:indie-auth", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:sso", "read:admin:sso", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
|
type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
|
||||||
|
@ -40,6 +40,7 @@ export type AdminAvatarDecorationsListResponse = operations['admin___avatar-deco
|
|||||||
export type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json'];
|
export type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json'];
|
||||||
export type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json'];
|
export type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json'];
|
||||||
export type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json'];
|
export type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json'];
|
||||||
|
export type AdminUnsetUserMutualLinkRequest = operations['admin___unset-user-mutual-link']['requestBody']['content']['application/json'];
|
||||||
export type AdminDriveDeleteAllFilesOfAUserRequest = operations['admin___drive___delete-all-files-of-a-user']['requestBody']['content']['application/json'];
|
export type AdminDriveDeleteAllFilesOfAUserRequest = operations['admin___drive___delete-all-files-of-a-user']['requestBody']['content']['application/json'];
|
||||||
export type AdminDriveFilesRequest = operations['admin___drive___files']['requestBody']['content']['application/json'];
|
export type AdminDriveFilesRequest = operations['admin___drive___files']['requestBody']['content']['application/json'];
|
||||||
export type AdminDriveFilesResponse = operations['admin___drive___files']['responses']['200']['content']['application/json'];
|
export type AdminDriveFilesResponse = operations['admin___drive___files']['responses']['200']['content']['application/json'];
|
||||||
|
@ -62,6 +62,7 @@ export const permissions = [
|
|||||||
'write:admin:suspend-user',
|
'write:admin:suspend-user',
|
||||||
'write:admin:unset-user-avatar',
|
'write:admin:unset-user-avatar',
|
||||||
'write:admin:unset-user-banner',
|
'write:admin:unset-user-banner',
|
||||||
|
'write:admin:unset-user-mutual-link',
|
||||||
'write:admin:unsuspend-user',
|
'write:admin:unsuspend-user',
|
||||||
'write:admin:meta',
|
'write:admin:meta',
|
||||||
'write:admin:user-note',
|
'write:admin:user-note',
|
||||||
@ -144,6 +145,7 @@ export const moderationLogTypes = [
|
|||||||
'deleteAvatarDecoration',
|
'deleteAvatarDecoration',
|
||||||
'unsetUserAvatar',
|
'unsetUserAvatar',
|
||||||
'unsetUserBanner',
|
'unsetUserBanner',
|
||||||
|
'unsetUserMutualBanner',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type ModerationLogPayloads = {
|
export type ModerationLogPayloads = {
|
||||||
@ -362,4 +364,9 @@ export type ModerationLogPayloads = {
|
|||||||
userHost: string | null;
|
userHost: string | null;
|
||||||
fileId: string;
|
fileId: string;
|
||||||
};
|
};
|
||||||
|
unsetUserMutualLink: {
|
||||||
|
userId: string;
|
||||||
|
userUsername: string;
|
||||||
|
mutualLinkSections: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user