Merge remote-branch 'misskey/develop'
This commit is contained in:
commit
9df37ca79a
|
@ -19,6 +19,7 @@
|
||||||
- Feat: メールアドレスの認証にverifymail.ioを使えるように (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/971ba07a44550f68d2ba31c62066db2d43a0caed)
|
- Feat: メールアドレスの認証にverifymail.ioを使えるように (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/971ba07a44550f68d2ba31c62066db2d43a0caed)
|
||||||
- Feat: モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能を追加 (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/e0eb5a752f6e5616d6312bb7c9790302f9dbff83)
|
- Feat: モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能を追加 (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/e0eb5a752f6e5616d6312bb7c9790302f9dbff83)
|
||||||
- Feat: TL上からノートが見えなくなるワードミュートであるハードミュートを追加
|
- Feat: TL上からノートが見えなくなるワードミュートであるハードミュートを追加
|
||||||
|
- Enhance: アイコンデコレーションを複数設定できるように
|
||||||
- Fix: MFM `$[unixtime ]` に不正な値を入力した際に発生する各種エラーを修正
|
- Fix: MFM `$[unixtime ]` に不正な値を入力した際に発生する各種エラーを修正
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
|
|
7
locales/index.d.ts
vendored
7
locales/index.d.ts
vendored
|
@ -4,8 +4,6 @@
|
||||||
export interface Locale {
|
export interface Locale {
|
||||||
"_lang_": string;
|
"_lang_": string;
|
||||||
"cannotBeUsedFunc": string;
|
"cannotBeUsedFunc": string;
|
||||||
"maxinumLayerError": string;
|
|
||||||
"layer": string;
|
|
||||||
"Xcoordinate": string;
|
"Xcoordinate": string;
|
||||||
"Ycoordinate": string;
|
"Ycoordinate": string;
|
||||||
"scale": string;
|
"scale": string;
|
||||||
|
@ -337,6 +335,7 @@ export interface Locale {
|
||||||
"removeAreYouSure": string;
|
"removeAreYouSure": string;
|
||||||
"deleteAreYouSure": string;
|
"deleteAreYouSure": string;
|
||||||
"resetAreYouSure": string;
|
"resetAreYouSure": string;
|
||||||
|
"areYouSure": string;
|
||||||
"saved": string;
|
"saved": string;
|
||||||
"messaging": string;
|
"messaging": string;
|
||||||
"upload": string;
|
"upload": string;
|
||||||
|
@ -1265,6 +1264,7 @@ export interface Locale {
|
||||||
"avatarDecorations": string;
|
"avatarDecorations": string;
|
||||||
"attach": string;
|
"attach": string;
|
||||||
"detach": string;
|
"detach": string;
|
||||||
|
"detachAll": string;
|
||||||
"angle": string;
|
"angle": string;
|
||||||
"flip": string;
|
"flip": string;
|
||||||
"showAvatarDecorations": string;
|
"showAvatarDecorations": string;
|
||||||
|
@ -1278,6 +1278,7 @@ export interface Locale {
|
||||||
"doReaction": string;
|
"doReaction": string;
|
||||||
"code": string;
|
"code": string;
|
||||||
"reloadRequiredToApplySettings": string;
|
"reloadRequiredToApplySettings": string;
|
||||||
|
"remainingN": string;
|
||||||
"showUnreadNotificationsCount": string;
|
"showUnreadNotificationsCount": string;
|
||||||
"showCatOnly": string;
|
"showCatOnly": string;
|
||||||
"additionalPermissionsForFlash": string;
|
"additionalPermissionsForFlash": string;
|
||||||
|
@ -1919,6 +1920,7 @@ export interface Locale {
|
||||||
"canHideAds": string;
|
"canHideAds": string;
|
||||||
"canSearchNotes": string;
|
"canSearchNotes": string;
|
||||||
"canUseTranslator": string;
|
"canUseTranslator": string;
|
||||||
|
"avatarDecorationLimit": string;
|
||||||
};
|
};
|
||||||
"_condition": {
|
"_condition": {
|
||||||
"isLocal": string;
|
"isLocal": string;
|
||||||
|
@ -2500,6 +2502,7 @@ export interface Locale {
|
||||||
"changeAvatar": string;
|
"changeAvatar": string;
|
||||||
"changeBanner": string;
|
"changeBanner": string;
|
||||||
"verifiedLinkDescription": string;
|
"verifiedLinkDescription": string;
|
||||||
|
"avatarDecorationMax": string;
|
||||||
};
|
};
|
||||||
"_exportOrImport": {
|
"_exportOrImport": {
|
||||||
"allNotes": string;
|
"allNotes": string;
|
||||||
|
|
|
@ -332,6 +332,7 @@ removed: "削除しました"
|
||||||
removeAreYouSure: "「{x}」を削除しますか?"
|
removeAreYouSure: "「{x}」を削除しますか?"
|
||||||
deleteAreYouSure: "「{x}」を削除しますか?"
|
deleteAreYouSure: "「{x}」を削除しますか?"
|
||||||
resetAreYouSure: "リセットしますか?"
|
resetAreYouSure: "リセットしますか?"
|
||||||
|
areYouSure: "よろしいですか?"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
messaging: "チャット"
|
messaging: "チャット"
|
||||||
upload: "アップロード"
|
upload: "アップロード"
|
||||||
|
@ -1260,6 +1261,7 @@ tosAndPrivacyPolicy: "利用規約・プライバシーポリシー"
|
||||||
avatarDecorations: "アイコンデコレーション"
|
avatarDecorations: "アイコンデコレーション"
|
||||||
attach: "付ける"
|
attach: "付ける"
|
||||||
detach: "外す"
|
detach: "外す"
|
||||||
|
detachAll: "全て外す"
|
||||||
angle: "角度"
|
angle: "角度"
|
||||||
flip: "反転"
|
flip: "反転"
|
||||||
showAvatarDecorations: "アイコンのデコレーションを表示"
|
showAvatarDecorations: "アイコンのデコレーションを表示"
|
||||||
|
@ -1273,6 +1275,7 @@ cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述
|
||||||
doReaction: "リアクションする"
|
doReaction: "リアクションする"
|
||||||
code: "コード"
|
code: "コード"
|
||||||
reloadRequiredToApplySettings: "設定の反映にはリロードが必要です。"
|
reloadRequiredToApplySettings: "設定の反映にはリロードが必要です。"
|
||||||
|
remainingN: "残り: {n}"
|
||||||
showUnreadNotificationsCount: "未読の通知の数を表示する"
|
showUnreadNotificationsCount: "未読の通知の数を表示する"
|
||||||
showCatOnly: "キャット付きのみ"
|
showCatOnly: "キャット付きのみ"
|
||||||
additionalPermissionsForFlash: "Playへの追加許可"
|
additionalPermissionsForFlash: "Playへの追加許可"
|
||||||
|
@ -1824,6 +1827,7 @@ _role:
|
||||||
canHideAds: "広告の非表示"
|
canHideAds: "広告の非表示"
|
||||||
canSearchNotes: "ノート検索の利用"
|
canSearchNotes: "ノート検索の利用"
|
||||||
canUseTranslator: "翻訳機能の利用"
|
canUseTranslator: "翻訳機能の利用"
|
||||||
|
avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
|
||||||
_condition:
|
_condition:
|
||||||
isLocal: "ローカルユーザー"
|
isLocal: "ローカルユーザー"
|
||||||
isRemote: "リモートユーザー"
|
isRemote: "リモートユーザー"
|
||||||
|
@ -2397,6 +2401,7 @@ _profile:
|
||||||
changeAvatar: "アイコン画像を変更"
|
changeAvatar: "アイコン画像を変更"
|
||||||
changeBanner: "バナー画像を変更"
|
changeBanner: "バナー画像を変更"
|
||||||
verifiedLinkDescription: "内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。"
|
verifiedLinkDescription: "内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。"
|
||||||
|
avatarDecorationMax: "最大{max}つまでデコレーションを付けられます。"
|
||||||
|
|
||||||
_exportOrImport:
|
_exportOrImport:
|
||||||
allNotes: "全てのノート"
|
allNotes: "全てのノート"
|
||||||
|
|
|
@ -48,6 +48,7 @@ export type RolePolicies = {
|
||||||
userListLimit: number;
|
userListLimit: number;
|
||||||
userEachUserListsLimit: number;
|
userEachUserListsLimit: number;
|
||||||
rateLimitFactor: number;
|
rateLimitFactor: number;
|
||||||
|
avatarDecorationLimit: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_POLICIES: RolePolicies = {
|
export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
|
@ -75,6 +76,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
userListLimit: 10,
|
userListLimit: 10,
|
||||||
userEachUserListsLimit: 50,
|
userEachUserListsLimit: 50,
|
||||||
rateLimitFactor: 1,
|
rateLimitFactor: 1,
|
||||||
|
avatarDecorationLimit: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -329,6 +331,7 @@ export class RoleService implements OnApplicationShutdown {
|
||||||
userListLimit: calc('userListLimit', vs => Math.max(...vs)),
|
userListLimit: calc('userListLimit', vs => Math.max(...vs)),
|
||||||
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)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,7 @@ export const packedRoleSchema = {
|
||||||
userEachUserListsLimit: rolePolicyValue,
|
userEachUserListsLimit: rolePolicyValue,
|
||||||
canManageAvatarDecorations: rolePolicyValue,
|
canManageAvatarDecorations: rolePolicyValue,
|
||||||
canUseTranslator: rolePolicyValue,
|
canUseTranslator: rolePolicyValue,
|
||||||
|
avatarDecorationLimit: rolePolicyValue,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
usersCount: {
|
usersCount: {
|
||||||
|
|
|
@ -693,6 +693,10 @@ export const packedMeDetailedOnlySchema = {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
nullable: false, optional: false,
|
nullable: false, optional: false,
|
||||||
},
|
},
|
||||||
|
avatarDecorationLimit: {
|
||||||
|
type: 'number',
|
||||||
|
nullable: false, optional: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
//#region secrets
|
//#region secrets
|
||||||
|
|
|
@ -137,7 +137,7 @@ export const paramDef = {
|
||||||
birthday: { ...birthdaySchema, nullable: true },
|
birthday: { ...birthdaySchema, nullable: true },
|
||||||
lang: { type: 'string', enum: [null, ...Object.keys(langmap)] as string[], nullable: true },
|
lang: { type: 'string', enum: [null, ...Object.keys(langmap)] as string[], nullable: true },
|
||||||
avatarId: { type: 'string', format: 'misskey:id', nullable: true },
|
avatarId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||||
avatarDecorations: { type: 'array', maxItems: 1, items: {
|
avatarDecorations: { type: 'array', maxItems: 16, items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
id: { type: 'string', format: 'misskey:id' },
|
id: { type: 'string', format: 'misskey:id' },
|
||||||
|
@ -333,12 +333,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
if (ps.avatarDecorations) {
|
if (ps.avatarDecorations) {
|
||||||
const decorations = await this.avatarDecorationService.getAll(true);
|
const decorations = await this.avatarDecorationService.getAll(true);
|
||||||
const myRoles = await this.roleService.getUserRoles(user.id);
|
const [myRoles, myPolicies] = await Promise.all([this.roleService.getUserRoles(user.id), this.roleService.getUserPolicies(user.id)]);
|
||||||
const allRoles = await this.roleService.getRoles();
|
const allRoles = await this.roleService.getRoles();
|
||||||
const decorationIds = decorations
|
const decorationIds = decorations
|
||||||
.filter(d => d.roleIdsThatCanBeUsedThisDecoration.filter(roleId => allRoles.some(r => r.id === roleId)).length === 0 || myRoles.some(r => d.roleIdsThatCanBeUsedThisDecoration.includes(r.id)))
|
.filter(d => d.roleIdsThatCanBeUsedThisDecoration.filter(roleId => allRoles.some(r => r.id === roleId)).length === 0 || myRoles.some(r => d.roleIdsThatCanBeUsedThisDecoration.includes(r.id)))
|
||||||
.map(d => d.id);
|
.map(d => d.id);
|
||||||
|
|
||||||
|
if (ps.avatarDecorations.length > myPolicies.avatarDecorationLimit) throw new ApiError(meta.errors.restrictedByRole);
|
||||||
|
|
||||||
updates.avatarDecorations = ps.avatarDecorations.filter(d => decorationIds.includes(d.id)).map(d => ({
|
updates.avatarDecorations = ps.avatarDecorations.filter(d => decorationIds.includes(d.id)).map(d => ({
|
||||||
id: d.id,
|
id: d.id,
|
||||||
angle: d.angle ?? 0,
|
angle: d.angle ?? 0,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* version: 4.6.0-beta.2
|
* version: 4.6.0-beta.2
|
||||||
* basedMisskeyVersion: 2023.12.0-beta.3
|
* basedMisskeyVersion: 2023.12.0-beta.3
|
||||||
* generatedAt: 2023-12-13T07:55:59.676Z
|
* generatedAt: 2023-12-13T08:44:16.948Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { SwitchCaseResponseType } from '../api.js';
|
import type { SwitchCaseResponseType } from '../api.js';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* version: 4.6.0-beta.2
|
* version: 4.6.0-beta.2
|
||||||
* basedMisskeyVersion: 2023.12.0-beta.3
|
* basedMisskeyVersion: 2023.12.0-beta.3
|
||||||
* generatedAt: 2023-12-13T07:55:59.674Z
|
* generatedAt: 2023-12-13T08:44:16.944Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* version: 4.6.0-beta.2
|
* version: 4.6.0-beta.2
|
||||||
* basedMisskeyVersion: 2023.12.0-beta.3
|
* basedMisskeyVersion: 2023.12.0-beta.3
|
||||||
* generatedAt: 2023-12-13T07:55:59.673Z
|
* generatedAt: 2023-12-13T08:44:16.943Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { operations } from './types.js';
|
import { operations } from './types.js';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* version: 4.6.0-beta.2
|
* version: 4.6.0-beta.2
|
||||||
* basedMisskeyVersion: 2023.12.0-beta.3
|
* basedMisskeyVersion: 2023.12.0-beta.3
|
||||||
* generatedAt: 2023-12-13T07:55:59.672Z
|
* generatedAt: 2023-12-13T08:44:16.941Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { components } from './types.js';
|
import { components } from './types.js';
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
/*
|
/*
|
||||||
* version: 4.6.0-beta.2
|
* version: 4.6.0-beta.2
|
||||||
* basedMisskeyVersion: 2023.12.0-beta.3
|
* basedMisskeyVersion: 2023.12.0-beta.3
|
||||||
* generatedAt: 2023-12-13T07:55:59.592Z
|
* generatedAt: 2023-12-13T08:44:16.816Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3960,6 +3960,7 @@ export type components = {
|
||||||
userListLimit: number;
|
userListLimit: number;
|
||||||
userEachUserListsLimit: number;
|
userEachUserListsLimit: number;
|
||||||
rateLimitFactor: number;
|
rateLimitFactor: number;
|
||||||
|
avatarDecorationLimit: number;
|
||||||
};
|
};
|
||||||
email?: string | null;
|
email?: string | null;
|
||||||
emailVerified?: boolean | null;
|
emailVerified?: boolean | null;
|
||||||
|
@ -4675,6 +4676,11 @@ export type components = {
|
||||||
priority: number;
|
priority: number;
|
||||||
useDefault: boolean;
|
useDefault: boolean;
|
||||||
};
|
};
|
||||||
|
avatarDecorationLimit: {
|
||||||
|
value: number | boolean;
|
||||||
|
priority: number;
|
||||||
|
useDefault: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
usersCount: number;
|
usersCount: number;
|
||||||
});
|
});
|
||||||
|
|
|
@ -323,7 +323,7 @@ export async function openAccountMenu(opts: {
|
||||||
text: i18n.ts.profile,
|
text: i18n.ts.profile,
|
||||||
to: `/@${ $i.username }`,
|
to: `/@${ $i.username }`,
|
||||||
avatar: $i,
|
avatar: $i,
|
||||||
}, null, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, {
|
}, { type: 'divider' }, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, {
|
||||||
type: 'parent' as const,
|
type: 'parent' as const,
|
||||||
icon: 'ti ti-plus',
|
icon: 'ti ti-plus',
|
||||||
text: i18n.ts.addAccount,
|
text: i18n.ts.addAccount,
|
||||||
|
|
|
@ -39,6 +39,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { claimAchievement } from '@/scripts/achievements.js';
|
import { claimAchievement } from '@/scripts/achievements.js';
|
||||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
|
import { MenuItem } from '@/types/menu.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
folder: Misskey.entities.DriveFolder;
|
folder: Misskey.entities.DriveFolder;
|
||||||
|
@ -250,7 +251,7 @@ function setAsUploadFolder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onContextmenu(ev: MouseEvent) {
|
function onContextmenu(ev: MouseEvent) {
|
||||||
let menu;
|
let menu: MenuItem[];
|
||||||
menu = [{
|
menu = [{
|
||||||
text: i18n.ts.openInWindow,
|
text: i18n.ts.openInWindow,
|
||||||
icon: 'ti ti-app-window',
|
icon: 'ti ti-app-window',
|
||||||
|
@ -260,18 +261,18 @@ function onContextmenu(ev: MouseEvent) {
|
||||||
}, {
|
}, {
|
||||||
}, 'closed');
|
}, 'closed');
|
||||||
},
|
},
|
||||||
}, null, {
|
}, { type: 'divider' }, {
|
||||||
text: i18n.ts.rename,
|
text: i18n.ts.rename,
|
||||||
icon: 'ti ti-forms',
|
icon: 'ti ti-forms',
|
||||||
action: rename,
|
action: rename,
|
||||||
}, null, {
|
}, { type: 'divider' }, {
|
||||||
text: i18n.ts.delete,
|
text: i18n.ts.delete,
|
||||||
icon: 'ti ti-trash',
|
icon: 'ti ti-trash',
|
||||||
danger: true,
|
danger: true,
|
||||||
action: deleteFolder,
|
action: deleteFolder,
|
||||||
}];
|
}];
|
||||||
if (defaultStore.state.devMode) {
|
if (defaultStore.state.devMode) {
|
||||||
menu = menu.concat([null, {
|
menu = menu.concat([{ type: 'divider' }, {
|
||||||
icon: 'ti ti-id',
|
icon: 'ti ti-id',
|
||||||
text: i18n.ts.copyFolderId,
|
text: i18n.ts.copyFolderId,
|
||||||
action: () => {
|
action: () => {
|
||||||
|
|
|
@ -4,32 +4,34 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="$style.root" :style="{ color }" :title="acct(user)" @click="onClick">
|
<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="$style.root" :style="{ color }" :title="acct(user)" @click.stop="onClick">
|
||||||
<MkImgWithBlurhash
|
<MkImgWithBlurhash
|
||||||
:class="[$style.inner, { [$style.reduceBlurEffect]: !defaultStore.state.useBlurEffect, [$style.noDrag]: noDrag }]"
|
:class="[$style.inner, { [$style.reduceBlurEffect]: !defaultStore.state.useBlurEffect, [$style.noDrag]: noDrag }]"
|
||||||
:src="url"
|
:src="url"
|
||||||
:hash="user.avatarBlurhash"
|
:hash="user.avatarBlurhash"
|
||||||
:cover="true"
|
:cover="true"
|
||||||
:onlyAvgColor="true"
|
:onlyAvgColor="true"
|
||||||
:noDrag="true"
|
:noDrag="true"
|
||||||
@mouseover="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = true : ''"
|
@mouseover="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = true : ''"
|
||||||
@mouseout="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = false : ''"
|
@mouseout="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = false : ''"
|
||||||
@touchstart="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = true : ''"
|
@touchstart="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = true : ''"
|
||||||
@touchend="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = false : ''"
|
@touchend="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = false : ''"
|
||||||
/>
|
/>
|
||||||
<img
|
<template v-if="showDecoration && defaultStore.state.friendlyShowAvatarDecorationsInNavBtn">
|
||||||
v-if="showDecoration && (decoration || user.avatarDecorations.length > 0) && defaultStore.state.friendlyShowAvatarDecorationsInNavBtn"
|
<img
|
||||||
:class="[$style.decoration]"
|
v-for="decoration in decorations ?? user.avatarDecorations"
|
||||||
:src="decoration?.url ?? user.avatarDecorations[0].url"
|
:class="[$style.decoration]"
|
||||||
:style="{
|
:src="decoration.url"
|
||||||
rotate: getDecorationAngle(),
|
:style="{
|
||||||
scale: getDecorationScale(),
|
rotate: getDecorationAngle(decoration),
|
||||||
transform: getDecorationTransform(),
|
scale: getDecorationScale(decoration),
|
||||||
opacity: getDecorationOpacity(),
|
transform: getDecorationTransform(decoration),
|
||||||
}"
|
opacity: getDecorationOpacity(decoration),
|
||||||
alt=""
|
}"
|
||||||
>
|
alt=""
|
||||||
</component>
|
>
|
||||||
|
</template>
|
||||||
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -47,22 +49,13 @@ const props = withDefaults(defineProps<{
|
||||||
target?: string | null;
|
target?: string | null;
|
||||||
link?: boolean;
|
link?: boolean;
|
||||||
preview?: boolean;
|
preview?: boolean;
|
||||||
decoration?: {
|
decorations?: Misskey.entities.UserDetailed['avatarDecorations'][number][];
|
||||||
url: string;
|
|
||||||
angle?: number;
|
|
||||||
flipH?: boolean;
|
|
||||||
flipV?: boolean;
|
|
||||||
scale?: number;
|
|
||||||
moveX?: number;
|
|
||||||
moveY?: number;
|
|
||||||
opacity?: number;
|
|
||||||
};
|
|
||||||
forceShowDecoration?: boolean;
|
forceShowDecoration?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
target: null,
|
target: null,
|
||||||
link: false,
|
link: false,
|
||||||
preview: false,
|
preview: false,
|
||||||
decoration: undefined,
|
decorations: undefined,
|
||||||
forceShowDecoration: false,
|
forceShowDecoration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -88,59 +81,25 @@ function onClick(ev: MouseEvent): void {
|
||||||
emit('click', ev);
|
emit('click', ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDecorationAngle() {
|
function getDecorationAngle(decoration: Misskey.entities.UserDetailed['avatarDecorations'][number]) {
|
||||||
let angle;
|
const angle = decoration.angle ?? 0;
|
||||||
if (props.decoration) {
|
|
||||||
angle = props.decoration.angle ?? 0;
|
|
||||||
} else if (props.user.avatarDecorations.length > 0) {
|
|
||||||
angle = props.user.avatarDecorations[0].angle ?? 0;
|
|
||||||
} else {
|
|
||||||
angle = 0;
|
|
||||||
}
|
|
||||||
return angle === 0 ? undefined : `${angle * 360}deg`;
|
return angle === 0 ? undefined : `${angle * 360}deg`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDecorationScale() {
|
function getDecorationScale(decoration: Misskey.entities.UserDetailed['avatarDecorations'][number]) {
|
||||||
let scaleX;
|
const scaleX = decoration.flipH ? -1 : 1;
|
||||||
if (props.decoration) {
|
|
||||||
scaleX = props.decoration.flipH ? -1 : 1;
|
|
||||||
} else if (props.user.avatarDecorations.length > 0) {
|
|
||||||
scaleX = props.user.avatarDecorations[0].flipH ? -1 : 1;
|
|
||||||
} else {
|
|
||||||
scaleX = 1;
|
|
||||||
}
|
|
||||||
return scaleX === 1 ? undefined : `${scaleX} 1`;
|
return scaleX === 1 ? undefined : `${scaleX} 1`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDecorationTransform() {
|
function getDecorationTransform(decoration: Misskey.entities.UserDetailed['avatarDecorations'][number]) {
|
||||||
let scale;
|
const scale = decoration.scale ?? 1;
|
||||||
let moveX;
|
const moveX = decoration.moveX ?? 0;
|
||||||
let moveY;
|
const moveY = decoration.moveY ?? 0;
|
||||||
if (props.decoration) {
|
|
||||||
scale = props.decoration.scale ?? 1;
|
|
||||||
moveX = props.decoration.moveX ?? 0;
|
|
||||||
moveY = props.decoration.moveY ?? 0;
|
|
||||||
} else if (props.user.avatarDecorations.length > 0) {
|
|
||||||
scale = props.user.avatarDecorations[0].scale ?? 1;
|
|
||||||
moveX = props.user.avatarDecorations[0].moveX ?? 0;
|
|
||||||
moveY = props.user.avatarDecorations[0].moveY ?? 0;
|
|
||||||
} else {
|
|
||||||
scale = 1;
|
|
||||||
moveX = 0;
|
|
||||||
moveY = 0;
|
|
||||||
}
|
|
||||||
return `${scale === 1 ? '' : `scale(${scale})`} ${moveX === 0 && moveY === 0 ? '' : `translate(${moveX}%, ${moveY}%)`}`;
|
return `${scale === 1 ? '' : `scale(${scale})`} ${moveX === 0 && moveY === 0 ? '' : `translate(${moveX}%, ${moveY}%)`}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDecorationOpacity() {
|
function getDecorationOpacity(decoration: Misskey.entities.UserDetailed['avatarDecorations'][number]) {
|
||||||
let opacity;
|
const opacity = decoration.opacity ?? 1;
|
||||||
if (props.decoration) {
|
|
||||||
opacity = props.decoration.opacity ?? 1;
|
|
||||||
} else if (props.user.avatarDecorations.length > 0) {
|
|
||||||
opacity = props.user.avatarDecorations[0].opacity ?? 1;
|
|
||||||
} else {
|
|
||||||
opacity = 1;
|
|
||||||
}
|
|
||||||
return opacity === 1 ? undefined : opacity;
|
return opacity === 1 ? undefined : opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ function onContextmenu(ev) {
|
||||||
action: () => {
|
action: () => {
|
||||||
router.push(props.to, 'forcePage');
|
router.push(props.to, 'forcePage');
|
||||||
},
|
},
|
||||||
}, null, {
|
}, { type: 'divider' }, {
|
||||||
icon: 'ti ti-external-link',
|
icon: 'ti ti-external-link',
|
||||||
text: i18n.ts.openInNewTab,
|
text: i18n.ts.openInNewTab,
|
||||||
action: () => {
|
action: () => {
|
||||||
|
|
|
@ -33,18 +33,20 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<img
|
<template v-if="showDecoration">
|
||||||
v-if="showDecoration && (decoration || user.avatarDecorations.length > 0)"
|
<img
|
||||||
:class="[$style.decoration]"
|
v-for="decoration in decorations ?? user.avatarDecorations"
|
||||||
:src="decoration?.url ?? user.avatarDecorations[0].url"
|
:class="[$style.decoration]"
|
||||||
:style="{
|
:src="decoration.url"
|
||||||
rotate: getDecorationAngle(),
|
:style="{
|
||||||
scale: getDecorationScale(),
|
rotate: getDecorationAngle(decoration),
|
||||||
transform: getDecorationTransform(),
|
scale: getDecorationScale(decoration),
|
||||||
opacity: getDecorationOpacity(),
|
transform: getDecorationTransform(decoration),
|
||||||
}"
|
opacity: getDecorationOpacity(decoration),
|
||||||
alt=""
|
}"
|
||||||
>
|
alt=""
|
||||||
|
>
|
||||||
|
</template>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -69,23 +71,14 @@ const props = withDefaults(defineProps<{
|
||||||
link?: boolean;
|
link?: boolean;
|
||||||
preview?: boolean;
|
preview?: boolean;
|
||||||
indicator?: boolean;
|
indicator?: boolean;
|
||||||
decoration?: {
|
decorations?: Misskey.entities.UserDetailed['avatarDecorations'][number][];
|
||||||
url: string;
|
|
||||||
angle?: number;
|
|
||||||
flipH?: boolean;
|
|
||||||
flipV?: boolean;
|
|
||||||
scale?: number;
|
|
||||||
moveX?: number;
|
|
||||||
moveY?: number;
|
|
||||||
opacity?: number;
|
|
||||||
};
|
|
||||||
forceShowDecoration?: boolean;
|
forceShowDecoration?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
target: null,
|
target: null,
|
||||||
link: false,
|
link: false,
|
||||||
preview: false,
|
preview: false,
|
||||||
indicator: false,
|
indicator: false,
|
||||||
decoration: undefined,
|
decorations: undefined,
|
||||||
forceShowDecoration: false,
|
forceShowDecoration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -111,59 +104,25 @@ function onClick(ev: MouseEvent): void {
|
||||||
emit('click', ev);
|
emit('click', ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDecorationAngle() {
|
function getDecorationAngle(decoration: Misskey.entities.UserDetailed['avatarDecorations'][number]) {
|
||||||
let angle;
|
const angle = decoration.angle ?? 0;
|
||||||
if (props.decoration) {
|
|
||||||
angle = props.decoration.angle ?? 0;
|
|
||||||
} else if (props.user.avatarDecorations.length > 0) {
|
|
||||||
angle = props.user.avatarDecorations[0].angle ?? 0;
|
|
||||||
} else {
|
|
||||||
angle = 0;
|
|
||||||
}
|
|
||||||
return angle === 0 ? undefined : `${angle * 360}deg`;
|
return angle === 0 ? undefined : `${angle * 360}deg`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDecorationScale() {
|
function getDecorationScale(decoration: Misskey.entities.UserDetailed['avatarDecorations'][number]) {
|
||||||
let scaleX;
|
const scaleX = decoration.flipH ? -1 : 1;
|
||||||
if (props.decoration) {
|
|
||||||
scaleX = props.decoration.flipH ? -1 : 1;
|
|
||||||
} else if (props.user.avatarDecorations.length > 0) {
|
|
||||||
scaleX = props.user.avatarDecorations[0].flipH ? -1 : 1;
|
|
||||||
} else {
|
|
||||||
scaleX = 1;
|
|
||||||
}
|
|
||||||
return scaleX === 1 ? undefined : `${scaleX} 1`;
|
return scaleX === 1 ? undefined : `${scaleX} 1`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDecorationTransform() {
|
function getDecorationTransform(decoration: Misskey.entities.UserDetailed['avatarDecorations'][number]) {
|
||||||
let scale;
|
const scale = decoration.scale ?? 1;
|
||||||
let moveX;
|
const moveX = decoration.moveX ?? 0;
|
||||||
let moveY;
|
const moveY = decoration.moveY ?? 0;
|
||||||
if (props.decoration) {
|
|
||||||
scale = props.decoration.scale ?? 1;
|
|
||||||
moveX = props.decoration.moveX ?? 0;
|
|
||||||
moveY = props.decoration.moveY ?? 0;
|
|
||||||
} else if (props.user.avatarDecorations.length > 0) {
|
|
||||||
scale = props.user.avatarDecorations[0].scale ?? 1;
|
|
||||||
moveX = props.user.avatarDecorations[0].moveX ?? 0;
|
|
||||||
moveY = props.user.avatarDecorations[0].moveY ?? 0;
|
|
||||||
} else {
|
|
||||||
scale = 1;
|
|
||||||
moveX = 0;
|
|
||||||
moveY = 0;
|
|
||||||
}
|
|
||||||
return `${scale === 1 ? '' : `scale(${scale})`} ${moveX === 0 && moveY === 0 ? '' : `translate(${moveX}%, ${moveY}%)`}`;
|
return `${scale === 1 ? '' : `scale(${scale})`} ${moveX === 0 && moveY === 0 ? '' : `translate(${moveX}%, ${moveY}%)`}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDecorationOpacity() {
|
function getDecorationOpacity(decoration: Misskey.entities.UserDetailed['avatarDecorations'][number]) {
|
||||||
let opacity;
|
const opacity = decoration.opacity ?? 1;
|
||||||
if (props.decoration) {
|
|
||||||
opacity = props.decoration.opacity ?? 1;
|
|
||||||
} else if (props.user.avatarDecorations.length > 0) {
|
|
||||||
opacity = props.user.avatarDecorations[0].opacity ?? 1;
|
|
||||||
} else {
|
|
||||||
opacity = 1;
|
|
||||||
}
|
|
||||||
return opacity === 1 ? undefined : opacity;
|
return opacity === 1 ? undefined : opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,35 +4,37 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="$style.root" :style="{ color }" :title="acct(user)" @click="onClick">
|
<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="$style.root" :style="{ color }" :title="acct(user)" @click.stop="onClick">
|
||||||
<MkImgWithBlurhash
|
<MkImgWithBlurhash
|
||||||
:class="$style.inner"
|
:class="$style.inner"
|
||||||
:src="url"
|
:src="url"
|
||||||
:hash="user.avatarBlurhash"
|
:hash="user.avatarBlurhash"
|
||||||
:cover="true"
|
:cover="true"
|
||||||
:onlyAvgColor="true"
|
:onlyAvgColor="true"
|
||||||
@mouseover="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = true : ''"
|
@mouseover="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = true : ''"
|
||||||
@mouseout="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = false : ''"
|
@mouseout="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = false : ''"
|
||||||
@touchstart="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = true : ''"
|
@touchstart="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = true : ''"
|
||||||
@touchend="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = false : ''"
|
@touchend="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = false : ''"
|
||||||
/>
|
/>
|
||||||
<img
|
<template v-if="showDecoration">
|
||||||
v-if="showDecoration && (decoration || user.avatarDecorations.length > 0)"
|
<img
|
||||||
:class="[$style.decoration]"
|
v-for="decoration in decorations ?? user.avatarDecorations"
|
||||||
:src="decoration?.url ?? user.avatarDecorations[0].url"
|
:class="[$style.decoration]"
|
||||||
:style="{
|
:src="decoration.url"
|
||||||
rotate: getDecorationAngle(decoration),
|
:style="{
|
||||||
scale: getDecorationScale(decoration),
|
rotate: getDecorationAngle(decoration),
|
||||||
transform: getDecorationTransform(decoration),
|
scale: getDecorationScale(decoration),
|
||||||
opacity: getDecorationOpacity(decoration),
|
transform: getDecorationTransform(decoration),
|
||||||
}"
|
opacity: getDecorationOpacity(decoration),
|
||||||
alt=""
|
}"
|
||||||
>
|
alt=""
|
||||||
</component>
|
>
|
||||||
|
</template>
|
||||||
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
import { onMounted, onUnmounted, watch, ref, computed } from 'vue';
|
||||||
import * as Misskey from 'cherrypick-js';
|
import * as Misskey from 'cherrypick-js';
|
||||||
import MkA from '@/components/global/MkA.vue';
|
import MkA from '@/components/global/MkA.vue';
|
||||||
import MkImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
|
import MkImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
|
||||||
|
@ -46,22 +48,13 @@ const props = withDefaults(defineProps<{
|
||||||
target?: string | null;
|
target?: string | null;
|
||||||
link?: boolean;
|
link?: boolean;
|
||||||
preview?: boolean;
|
preview?: boolean;
|
||||||
decoration?: {
|
decorations?: Misskey.entities.UserDetailed['avatarDecorations'][number][];
|
||||||
url: string;
|
|
||||||
angle?: number;
|
|
||||||
flipH?: boolean;
|
|
||||||
flipV?: boolean;
|
|
||||||
scale?: number;
|
|
||||||
moveX?: number;
|
|
||||||
moveY?: number;
|
|
||||||
opacity?: number;
|
|
||||||
};
|
|
||||||
forceShowDecoration?: boolean;
|
forceShowDecoration?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
target: null,
|
target: null,
|
||||||
link: false,
|
link: false,
|
||||||
preview: false,
|
preview: false,
|
||||||
decoration: undefined,
|
decorations: undefined,
|
||||||
forceShowDecoration: false,
|
forceShowDecoration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -87,59 +80,25 @@ function onClick(ev: MouseEvent): void {
|
||||||
emit('click', ev);
|
emit('click', ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDecorationAngle() {
|
function getDecorationAngle(decoration: Misskey.entities.UserDetailed['avatarDecorations'][number]) {
|
||||||
let angle;
|
const angle = decoration.angle ?? 0;
|
||||||
if (props.decoration) {
|
|
||||||
angle = props.decoration.angle ?? 0;
|
|
||||||
} else if (props.user.avatarDecorations.length > 0) {
|
|
||||||
angle = props.user.avatarDecorations[0].angle ?? 0;
|
|
||||||
} else {
|
|
||||||
angle = 0;
|
|
||||||
}
|
|
||||||
return angle === 0 ? undefined : `${angle * 360}deg`;
|
return angle === 0 ? undefined : `${angle * 360}deg`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDecorationScale() {
|
function getDecorationScale(decoration: Misskey.entities.UserDetailed['avatarDecorations'][number]) {
|
||||||
let scaleX;
|
const scaleX = decoration.flipH ? -1 : 1;
|
||||||
if (props.decoration) {
|
|
||||||
scaleX = props.decoration.flipH ? -1 : 1;
|
|
||||||
} else if (props.user.avatarDecorations.length > 0) {
|
|
||||||
scaleX = props.user.avatarDecorations[0].flipH ? -1 : 1;
|
|
||||||
} else {
|
|
||||||
scaleX = 1;
|
|
||||||
}
|
|
||||||
return scaleX === 1 ? undefined : `${scaleX} 1`;
|
return scaleX === 1 ? undefined : `${scaleX} 1`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDecorationTransform() {
|
function getDecorationTransform(decoration: Misskey.entities.UserDetailed['avatarDecorations'][number]) {
|
||||||
let scale;
|
const scale = decoration.scale ?? 1;
|
||||||
let moveX;
|
const moveX = decoration.moveX ?? 0;
|
||||||
let moveY;
|
const moveY = decoration.moveY ?? 0;
|
||||||
if (props.decoration) {
|
|
||||||
scale = props.decoration.scale ?? 1;
|
|
||||||
moveX = props.decoration.moveX ?? 0;
|
|
||||||
moveY = props.decoration.moveY ?? 0;
|
|
||||||
} else if (props.user.avatarDecorations.length > 0) {
|
|
||||||
scale = props.user.avatarDecorations[0].scale ?? 1;
|
|
||||||
moveX = props.user.avatarDecorations[0].moveX ?? 0;
|
|
||||||
moveY = props.user.avatarDecorations[0].moveY ?? 0;
|
|
||||||
} else {
|
|
||||||
scale = 1;
|
|
||||||
moveX = 0;
|
|
||||||
moveY = 0;
|
|
||||||
}
|
|
||||||
return `${scale === 1 ? '' : `scale(${scale})`} ${moveX === 0 && moveY === 0 ? '' : `translate(${moveX}%, ${moveY}%)`}`;
|
return `${scale === 1 ? '' : `scale(${scale})`} ${moveX === 0 && moveY === 0 ? '' : `translate(${moveX}%, ${moveY}%)`}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDecorationOpacity() {
|
function getDecorationOpacity(decoration: Misskey.entities.UserDetailed['avatarDecorations'][number]) {
|
||||||
let opacity;
|
const opacity = decoration.opacity ?? 1;
|
||||||
if (props.decoration) {
|
|
||||||
opacity = props.decoration.opacity ?? 1;
|
|
||||||
} else if (props.user.avatarDecorations.length > 0) {
|
|
||||||
opacity = props.user.avatarDecorations[0].opacity ?? 1;
|
|
||||||
} else {
|
|
||||||
opacity = 1;
|
|
||||||
}
|
|
||||||
return opacity === 1 ? undefined : opacity;
|
return opacity === 1 ? undefined : opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,11 +158,11 @@ onUnmounted(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.decoration {
|
.decoration {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
top: -50%;
|
top: -50%;
|
||||||
left: -50%;
|
left: -50%;
|
||||||
width: 200%;
|
width: 200%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -82,6 +82,7 @@ export const ROLE_POLICIES = [
|
||||||
'userListLimit',
|
'userListLimit',
|
||||||
'userEachUserListsLimit',
|
'userEachUserListsLimit',
|
||||||
'rateLimitFactor',
|
'rateLimitFactor',
|
||||||
|
'avatarDecorationLimit',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// なんか動かない
|
// なんか動かない
|
||||||
|
|
|
@ -551,6 +551,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkRange>
|
</MkRange>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.avatarDecorationLimit, 'avatarDecorationLimit'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.avatarDecorationLimit }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.avatarDecorationLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.avatarDecorationLimit.value }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.avatarDecorationLimit)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.avatarDecorationLimit.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkInput v-model="role.policies.avatarDecorationLimit.value" type="number" :min="0">
|
||||||
|
<template #label>{{ i18n.ts._role._options.avatarDecorationLimit }}</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkRange v-model="role.policies.avatarDecorationLimit.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>
|
</div>
|
||||||
</FormSlot>
|
</FormSlot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -569,7 +589,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkRange from '@/components/MkRange.vue';
|
import MkRange from '@/components/MkRange.vue';
|
||||||
import FormSlot from '@/components/form/slot.vue';
|
import FormSlot from '@/components/form/slot.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { ROLE_POLICIES } from '@/const';
|
import { ROLE_POLICIES } from '@/const.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import { deepClone } from '@/scripts/clone.js';
|
import { deepClone } from '@/scripts/clone.js';
|
||||||
|
|
||||||
|
|
|
@ -200,6 +200,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.avatarDecorationLimit, 'avatarDecorationLimit'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.avatarDecorationLimit }}</template>
|
||||||
|
<template #suffix>{{ policies.avatarDecorationLimit }}</template>
|
||||||
|
<MkInput v-model="policies.avatarDecorationLimit" type="number" :min="0">
|
||||||
|
</MkInput>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkButton primary rounded @click="updateBaseRole">{{ i18n.ts.save }}</MkButton>
|
<MkButton primary rounded @click="updateBaseRole">{{ i18n.ts.save }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkModalWindow
|
<MkModalWindow
|
||||||
ref="dialog"
|
ref="dialog"
|
||||||
:width="400"
|
:width="400"
|
||||||
:height="700"
|
:height="450"
|
||||||
@close="cancel"
|
@close="cancel"
|
||||||
@closed="emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSpacer :marginMin="20" :marginMax="28">
|
<MkSpacer :marginMin="20" :marginMax="28">
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
<div :class="$style.name">{{ decoration.name }}</div>
|
<div :class="$style.name">{{ decoration.name }}</div>
|
||||||
<MkAvatar style="width: 64px; height: 64px; margin-bottom: 20px;" :user="$i" :decoration="{ url: decoration.url, angle, flipH, scale, moveX, moveY, opacity }" forceShowDecoration/>
|
<MkAvatar style="width: 64px; height: 64px; margin-bottom: 20px;" :user="$i" :decorations="[...$i.avatarDecorations, { url: decoration.url, angle, flipH, scale, moveX, moveY, opacity }]" forceShowDecoration/>
|
||||||
</div>
|
</div>
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkRange v-model="angle" continuousUpdate :min="-0.5" :max="0.5" :step="0.025" :textConverter="(v) => `${Math.floor(v * 360)}°`">
|
<MkRange v-model="angle" continuousUpdate :min="-0.5" :max="0.5" :step="0.025" :textConverter="(v) => `${Math.floor(v * 360)}°`">
|
||||||
|
@ -66,6 +66,7 @@ const props = defineProps<{
|
||||||
decoration: {
|
decoration: {
|
||||||
id: string;
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
name: string;
|
||||||
}
|
}
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -97,18 +98,18 @@ async function attach() {
|
||||||
opacity: opacity.value,
|
opacity: opacity.value,
|
||||||
};
|
};
|
||||||
await os.apiWithDialog('i/update', {
|
await os.apiWithDialog('i/update', {
|
||||||
avatarDecorations: [decoration],
|
avatarDecorations: [...$i.avatarDecorations, decoration],
|
||||||
});
|
});
|
||||||
$i.avatarDecorations = [decoration];
|
$i.avatarDecorations = [...$i.avatarDecorations, decoration];
|
||||||
|
|
||||||
dialog.value.close();
|
dialog.value.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function detach() {
|
async function detach() {
|
||||||
await os.apiWithDialog('i/update', {
|
await os.apiWithDialog('i/update', {
|
||||||
avatarDecorations: [],
|
avatarDecorations: $i.avatarDecorations.filter(x => x.id !== props.decoration.id),
|
||||||
});
|
});
|
||||||
$i.avatarDecorations = [];
|
$i.avatarDecorations = $i.avatarDecorations.filter(x => x.id !== props.decoration.id);
|
||||||
|
|
||||||
dialog.value.close();
|
dialog.value.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,16 +87,22 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #icon><i class="ti ti-sparkles"></i></template>
|
<template #icon><i class="ti ti-sparkles"></i></template>
|
||||||
<template #label>{{ i18n.ts.avatarDecorations }}</template>
|
<template #label>{{ i18n.ts.avatarDecorations }}</template>
|
||||||
|
|
||||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); grid-gap: 12px;">
|
<div class="_gaps">
|
||||||
<div
|
<MkInfo>{{ i18n.t('_profile.avatarDecorationMax', { max: $i?.policies.avatarDecorationLimit }) }} ({{ i18n.t('remainingN', { n: $i?.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
|
||||||
v-for="avatarDecoration in avatarDecorations"
|
|
||||||
:key="avatarDecoration.id"
|
<MkButton v-if="$i.avatarDecorations.length > 0" danger @click="detachAllDecorations">{{ i18n.ts.detachAll }}</MkButton>
|
||||||
:class="[$style.avatarDecoration, { [$style.avatarDecorationActive]: $i.avatarDecorations.some(x => x.id === avatarDecoration.id) }]"
|
|
||||||
@click="openDecoration(avatarDecoration)"
|
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); grid-gap: 12px;">
|
||||||
>
|
<div
|
||||||
<div :class="$style.avatarDecorationName"><MkCondensedLine :minScale="0.5">{{ avatarDecoration.name }}</MkCondensedLine></div>
|
v-for="avatarDecoration in avatarDecorations"
|
||||||
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decoration="{ url: avatarDecoration.url }" forceShowDecoration/>
|
:key="avatarDecoration.id"
|
||||||
<i v-if="avatarDecoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => avatarDecoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id))" :class="$style.avatarDecorationLock" class="ti ti-lock"></i>
|
:class="[$style.avatarDecoration, { [$style.avatarDecorationActive]: $i.avatarDecorations.some(x => x.id === avatarDecoration.id) }]"
|
||||||
|
@click="openDecoration(avatarDecoration)"
|
||||||
|
>
|
||||||
|
<div :class="$style.avatarDecorationName"><MkCondensedLine :minScale="0.5">{{ avatarDecoration.name }}</MkCondensedLine></div>
|
||||||
|
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[{ url: avatarDecoration.url }]" forceShowDecoration/>
|
||||||
|
<i v-if="avatarDecoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => avatarDecoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id))" :class="$style.avatarDecorationLock" class="ti ti-lock"></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
@ -285,6 +291,19 @@ function openDecoration(avatarDecoration) {
|
||||||
}, {}, 'closed');
|
}, {}, 'closed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function detachAllDecorations() {
|
||||||
|
os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.ts.areYouSure,
|
||||||
|
}).then(async ({ canceled }) => {
|
||||||
|
if (canceled) return;
|
||||||
|
await os.apiWithDialog('i/update', {
|
||||||
|
avatarDecorations: [],
|
||||||
|
});
|
||||||
|
$i.avatarDecorations = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function reloadAsk() {
|
async function reloadAsk() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
|
|
Loading…
Reference in a new issue