Skip to content

Commit

Permalink
feat: 프로필 아이콘 모양 설정 연합
Browse files Browse the repository at this point in the history
  - 자신의 프로필 아이콘 모양<small>(원형, 사각형)</small>을 로컬 사용자 및 원격 사용자에게 연합시킬 수 있습니다.
    - 로컬 사용자 뿐만 아니라, CherryPick `4.15.0` 이상 버전을 사용하는 서버에서는 해당 사용자가 지정한 모양으로 프로필이 표시됩니다.
  - CherryPick `4.15.0` 이상 버전을 사용하는 서버에서만 이 기능을 사용할 수 있습니다.
  - 이 설정은 서버마다 추구하는 방향이 다를 수 있기 때문에, 역할에서 `프로필 아이콘 모양 설정 연합 허용`이 제한되지 않은 상태에서만 사용할 수 있습니다.
    - 이 역할이 꺼져있으면 프로필 아이콘 모양 설정이 연합되지 않으며, 모든 사용자의 프로필 아이콘 모양이 `프로필 아이콘을 사각형으로 표시` 설정에 따라 표시됩니다.
  • Loading branch information
noridev committed Feb 5, 2025
1 parent 9742794 commit c026822
Show file tree
Hide file tree
Showing 25 changed files with 176 additions and 11 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG_CHERRYPICK.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE
### General
- Feat: 계정 정리 기능 ([yodangang-express/cherrypick@dc51c907](https://github.com/yodangang-express/cherrypick/commit/dc51c907236570d6f072409832d312c937239514))
- `다이렉트 메시지``고정된 노트`와 관련된 파일을 제외한 모든 노트와 파일을 자동으로 삭제할 수 있음
- Feat: 프로필 아이콘 모양 설정 연합
- 자신의 프로필 아이콘 모양<small>(원형, 사각형)</small>을 로컬 사용자 및 원격 사용자에게 연합시킬 수 있습니다.
- 로컬 사용자 뿐만 아니라, CherryPick `4.15.0` 이상 버전을 사용하는 서버에서는 해당 사용자가 지정한 모양으로 프로필이 표시됩니다.
- CherryPick `4.15.0` 이상 버전을 사용하는 서버에서만 이 기능을 사용할 수 있습니다.
- 이 설정은 서버마다 추구하는 방향이 다를 수 있기 때문에, 역할에서 `프로필 아이콘 모양 설정 연합 허용`이 제한되지 않은 상태에서만 사용할 수 있습니다.
- 이 역할이 꺼져있으면 프로필 아이콘 모양 설정이 연합되지 않으며, 모든 사용자의 프로필 아이콘 모양이 `프로필 아이콘을 사각형으로 표시` 설정에 따라 표시됩니다.

### Client
- Enhance: 사용자 페이지에서 `이름`, `자기소개`, `팔로우 메시지`, `추가 정보`에 포함된 외부 이모지를 가져올 수 있음
Expand Down
3 changes: 3 additions & 0 deletions locales/en-US.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
---
_lang_: "English"
setFederationAvatarShape: "Federation of avatar shape settings"
setFederationAvatarShapeDescription: "You can federation the shape of the avatar shape settings(circle, square) for your local and remote users."
keyboardShortcuts: "Keyboard shortcuts"
truncateAccount: "Truncate account"
truncateAccountConfirm: "All notes and files will be deleted except those associated with direct messages and pinned notes. Still continue?"
Expand Down Expand Up @@ -2073,6 +2075,7 @@ _role:
canImportFollowing: "Allow importing following"
canImportMuting: "Allow importing muting"
canImportUserLists: "Allow importing lists"
canSetFederationAvatarShape: "Allow federation of avatar shape settings"
_condition:
roleAssignedTo: "Assigned to manual roles"
isLocal: "Local user"
Expand Down
12 changes: 12 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ export interface Locale extends ILocale {
* 日本語
*/
"_lang_": string;
/**
* アイコンの形設定の連合
*/
"setFederationAvatarShape": string;
/**
* 自分のアイコンの形(円形、四角形)をローカルユーザおよびリモートユーザに連合させることができます。
*/
"setFederationAvatarShapeDescription": string;
/**
* キーボードショートカット
*/
Expand Down Expand Up @@ -8102,6 +8110,10 @@ export interface Locale extends ILocale {
* リストのインポートを許可
*/
"canImportUserLists": string;
/**
* アイコンの形設定の連合を許可
*/
"canSetFederationAvatarShape": string;
};
"_condition": {
/**
Expand Down
3 changes: 3 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
_lang_: "日本語"

setFederationAvatarShape: "アイコンの形設定の連合"
setFederationAvatarShapeDescription: "自分のアイコンの形(円形、四角形)をローカルユーザおよびリモートユーザに連合させることができます。"
keyboardShortcuts: "キーボードショートカット"
truncateAccount: "アカウント整理"
truncateAccountConfirm: "ダイレクトメッセージと固定されたノートに関連付けられているファイルを除くすべてのノートとファイルが削除されます。それでも続行しますか?"
Expand Down Expand Up @@ -2095,6 +2097,7 @@ _role:
canImportFollowing: "フォローのインポートを許可"
canImportMuting: "ミュートのインポートを許可"
canImportUserLists: "リストのインポートを許可"
canSetFederationAvatarShape: "アイコンの形設定の連合を許可"
_condition:
roleAssignedTo: "マニュアルロールにアサイン済み"
isLocal: "ローカルユーザー"
Expand Down
3 changes: 3 additions & 0 deletions locales/ko-KR.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
---
_lang_: "한국어"
setFederationAvatarShape: "프로필 아이콘 모양 설정 연합"
setFederationAvatarShapeDescription: "내 프로필 아이콘의 모양(원형, 사각형)을 로컬 사용자 및 원격 사용자에게 연합시킬 수 있어요."
keyboardShortcuts: "키보드 단축키"
truncateAccount: "계정 정리"
truncateAccountConfirm: "다이렉트 메시지 및 고정된 노트와 관련된 파일을 제외한 모든 노트와 파일이 삭제돼요. 그래도 계속할까요?"
Expand Down Expand Up @@ -2073,6 +2075,7 @@ _role:
canImportFollowing: "팔로우 가져오기 허용"
canImportMuting: "뮤트 목록 가져오기 허용"
canImportUserLists: "리스트 목록 가져오기 허용"
canSetFederationAvatarShape: "프로필 아이콘 모양 설정 연합 허용"
_condition:
roleAssignedTo: "수동 역할에 이미 할당됨"
isLocal: "로컬 사용자"
Expand Down
18 changes: 18 additions & 0 deletions packages/backend/migration/1738776889000-setRemoteAvatarShape.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class setFederationAvatarShape1738776889000 {
name = 'setFederationAvatarShape1738776889000'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" ADD "setFederationAvatarShape" boolean NOT NULL DEFAULT true`);
await queryRunner.query(`ALTER TABLE "user" ADD "isSquareAvatars" boolean NOT NULL DEFAULT true`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "setFederationAvatarShape"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isSquareAvatars"`);
}
}
3 changes: 3 additions & 0 deletions packages/backend/src/core/RoleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type RolePolicies = {
canImportFollowing: boolean;
canImportMuting: boolean;
canImportUserLists: boolean;
canSetFederationAvatarShape: boolean;
};

export const DEFAULT_POLICIES: RolePolicies = {
Expand Down Expand Up @@ -105,6 +106,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
canImportFollowing: true,
canImportMuting: true,
canImportUserLists: true,
canSetFederationAvatarShape: true,
};

@Injectable()
Expand Down Expand Up @@ -416,6 +418,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
canImportFollowing: calc('canImportFollowing', vs => vs.some(v => v === true)),
canImportMuting: calc('canImportMuting', vs => vs.some(v => v === true)),
canImportUserLists: calc('canImportUserLists', vs => vs.some(v => v === true)),
canSetFederationAvatarShape: calc('canSetFederationAvatarShape', vs => vs.some(v => v === true)),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,8 @@ export class ApPersonService implements OnModuleInit {
makeNotesFollowersOnlyBefore: (person as any).makeNotesFollowersOnlyBefore ?? null,
makeNotesHiddenBefore: (person as any).makeNotesHiddenBefore ?? null,
emojis,
setFederationAvatarShape: person.setFederationAvatarShape,
isSquareAvatars: person.isSquareAvatars,
})) as MiRemoteUser;

let _description: string | null = null;
Expand Down Expand Up @@ -719,7 +721,7 @@ export class ApPersonService implements OnModuleInit {
alsoKnownAs: person.alsoKnownAs ?? null,
isExplorable: person.discoverable,
...(await this.resolveAvatarAndBanner(exist, person.icon, person.image).catch(() => ({}))),
} as Partial<MiRemoteUser> & Pick<MiRemoteUser, 'isBot' | 'isCat' | 'isLocked' | 'movedToUri' | 'alsoKnownAs' | 'isExplorable'>;
} as Partial<MiRemoteUser> & Pick<MiRemoteUser, 'isBot' | 'isCat' | 'isLocked' | 'movedToUri' | 'alsoKnownAs' | 'isExplorable' | 'setFederationAvatarShape' | 'isSquareAvatars'>;

const moving = ((): boolean => {
// 移行先がない→ある
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/core/activitypub/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ export interface IActor extends IObject {
};
'vcard:bday'?: string;
'vcard:Address'?: string;
setFederationAvatarShape?: boolean,
isSquareAvatars?: boolean,
}

export const isCollection = (object: IObject): object is ICollection =>
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/core/entities/UserEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,8 @@ export class UserEntityService implements OnModuleInit {
displayOrder: r.displayOrder,
})),
) : undefined,
setFederationAvatarShape: user.setFederationAvatarShape,
isSquareAvatars: user.isSquareAvatars,

...(isDetailed ? {
url: profile!.url,
Expand Down
10 changes: 10 additions & 0 deletions packages/backend/src/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,16 @@ export class MiUser {
})
public token: string | null;

@Column('boolean', {
default: true,
})
public setFederationAvatarShape: boolean;

@Column('boolean', {
default: true,
})
public isSquareAvatars: boolean;

constructor(data: Partial<MiUser>) {
if (data == null) return;

Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/models/json-schema/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,10 @@ export const packedRolePoliciesSchema = {
type: 'integer',
optional: false, nullable: false,
},
canSetFederationAvatarShape: {
type: 'boolean',
optional: false, nullable: false,
},
},
} as const;

Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/models/json-schema/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,14 @@ export const packedMeDetailedOnlySchema = {
nullable: false, optional: false,
default: false,
},
setFederationAvatarShape: {
type: 'boolean',
nullable: false, optional: false,
},
isSquareAvatars: {
type: 'boolean',
nullable: false, optional: false,
},
//#region secrets
email: {
type: 'string',
Expand Down
10 changes: 10 additions & 0 deletions packages/backend/src/server/api/endpoints/admin/show-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ export const meta = {
},
},
},
setFederationAvatarShape: {
type: 'boolean',
optional: false, nullable: false,
},
isSquareAvatars: {
type: 'boolean',
optional: false, nullable: false,
},
},
},
} as const;
Expand Down Expand Up @@ -257,6 +265,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
expiresAt: a.expiresAt ? a.expiresAt.toISOString() : null,
roleId: a.roleId,
})),
setFederationAvatarShape: user.setFederationAvatarShape,
isSquareAvatars: user.isSquareAvatars,
};
});
}
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/server/api/endpoints/i/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ export const paramDef = {
uniqueItems: true,
items: { type: 'string' },
},
setFederationAvatarShape: { type: 'boolean' },
isSquareAvatars: { type: 'boolean' },
},
} as const;

Expand Down Expand Up @@ -462,6 +464,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
updates.alsoKnownAs = newAlsoKnownAs.size > 0 ? Array.from(newAlsoKnownAs) : null;
}

if (typeof ps.setFederationAvatarShape === 'boolean') updates.setFederationAvatarShape = ps.setFederationAvatarShape;
if (typeof ps.isSquareAvatars === 'boolean') updates.isSquareAvatars = ps.isSquareAvatars;

//#region emojis/tags

let emojis = [] as string[];
Expand Down
7 changes: 7 additions & 0 deletions packages/cherrypick-js/src/autogen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4383,6 +4383,8 @@ export type components = {
usePasswordLessLogin: boolean;
/** @default false */
securityKeys: boolean;
setFederationAvatarShape: boolean;
isSquareAvatars: boolean;
email?: string | null;
emailVerified?: boolean | null;
securityKeysList?: {
Expand Down Expand Up @@ -5356,6 +5358,7 @@ export type components = {
canImportUserLists: boolean;
canEditNote: boolean;
scheduleNoteMax: number;
canSetFederationAvatarShape: boolean;
};
ReversiGameLite: {
/** Format: id */
Expand Down Expand Up @@ -10719,6 +10722,8 @@ export type operations = {
expiresAt: string | null;
roleId: string;
})[];
setFederationAvatarShape: boolean;
isSquareAvatars: boolean;
};
};
};
Expand Down Expand Up @@ -22237,6 +22242,8 @@ export type operations = {
};
emailNotificationTypes?: string[];
alsoKnownAs?: string[];
setFederationAvatarShape?: boolean;
isSquareAvatars?: boolean;
};
};
};
Expand Down
1 change: 1 addition & 0 deletions packages/frontend-shared/js/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export const ROLE_POLICIES = [
'canImportFollowing',
'canImportMuting',
'canImportUserLists',
'canSetFederationAvatarShape',
] as const;

// なんか動かない
Expand Down
10 changes: 10 additions & 0 deletions packages/frontend/src/boot/main-boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { type Keymap, makeHotkey } from '@/scripts/hotkey.js';
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
import { userName } from '@/filters/user.js';
import { vibrate } from '@/scripts/vibrate.js';
import { misskeyApi } from '@/scripts/misskey-api.js';

export async function mainBoot() {
const { isClientUpdated, isClientMigrated } = await common(() => {
Expand Down Expand Up @@ -423,6 +424,15 @@ export async function mainBoot() {
main.on('myTokenRegenerated', () => {
signout();
});

// 프로필 아이콘 모양 설정 연합
if (!$i.policies.canSetFederationAvatarShape) {
await defaultStore.set('setFederationAvatarShape', false);
await misskeyApi('i/update', {
setFederationAvatarShape: false,
isSquareAvatars: defaultStore.state.squareAvatars,
});
}
}

// shortcut
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/components/MkUserPopup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.banner" :style="user.bannerUrl ? `background-image: url(${defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(user.bannerUrl) : user.bannerUrl})` : ''">
<span v-if="$i && $i.id != user.id && user.isFollowed" :class="$style.followed">{{ i18n.ts.followsYou }}</span>
</div>
<svg v-if="!defaultStore.state.squareAvatars" viewBox="0 0 128 128" :class="$style.avatarBack">
<svg v-if="(!defaultStore.state.setFederationAvatarShape && !defaultStore.state.squareAvatars) || (defaultStore.state.setFederationAvatarShape && user.setFederationAvatarShape && !user.isSquareAvatars)" viewBox="0 0 128 128" :class="$style.avatarBack">
<g transform="matrix(1.6,0,0,1.6,-38.4,-51.2)">
<path d="M64,32C81.661,32 96,46.339 96,64C95.891,72.184 104,72 104,72C104,72 74.096,80 64,80C52.755,80 24,72 24,72C24,72 31.854,72.018 32,64C32,46.339 46.339,32 64,32Z" style="fill: var(--MI_THEME-popup);"/>
</g>
Expand Down
3 changes: 2 additions & 1 deletion packages/frontend/src/components/global/MkAvatar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ import MkUserOnlineIndicator from '@/components/MkUserOnlineIndicator.vue';
import { defaultStore } from '@/store.js';

const animation = ref(defaultStore.state.animation);
const squareAvatars = ref(defaultStore.state.squareAvatars);

const props = withDefaults(defineProps<{
user: Misskey.entities.User;
Expand All @@ -129,6 +128,8 @@ const props = withDefaults(defineProps<{
noteClick: false,
});

const squareAvatars = ref((!defaultStore.state.setFederationAvatarShape && defaultStore.state.squareAvatars) || (defaultStore.state.setFederationAvatarShape && props.user.setFederationAvatarShape && props.user.isSquareAvatars));

const emit = defineEmits<{
(ev: 'click', v: MouseEvent): void;
}>();
Expand Down
20 changes: 20 additions & 0 deletions packages/frontend/src/pages/admin/roles.editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,26 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkRange>
</div>
</MkFolder>

<MkFolder v-if="matchQuery([i18n.ts._role._options.canSetFederationAvatarShape, 'canSetFederationAvatarShape'])">
<template #label>{{ i18n.ts._role._options.canSetFederationAvatarShape }}</template>
<template #suffix>
<span v-if="role.policies.canSetFederationAvatarShape.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ role.policies.canSetFederationAvatarShape.value ? i18n.ts.yes : i18n.ts.no }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canSetFederationAvatarShape)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="role.policies.canSetFederationAvatarShape.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
</MkSwitch>
<MkSwitch v-model="role.policies.canSetFederationAvatarShape.value" :disabled="role.policies.canSetFederationAvatarShape.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
<MkRange v-model="role.policies.canSetFederationAvatarShape.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>
</div>
</FormSlot>
</div>
Expand Down
8 changes: 8 additions & 0 deletions packages/frontend/src/pages/admin/roles.vue
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
</MkFolder>

<MkFolder v-if="matchQuery([i18n.ts._role._options.canSetFederationAvatarShape, 'canSetFederationAvatarShape'])">
<template #label>{{ i18n.ts._role._options.canSetFederationAvatarShape }}</template>
<template #suffix>{{ policies.canSetFederationAvatarShape ? i18n.ts.yes : i18n.ts.no }}</template>
<MkSwitch v-model="policies.canSetFederationAvatarShape">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
</MkFolder>
</div>
</MkFolder>
<div class="_gaps_s">
Expand Down
Loading

0 comments on commit c026822

Please sign in to comment.