spec(profile): 相互リンク機能のロールによる制御の仕様変更 (MisskeyIO#701)
This commit is contained in:
		
							parent
							
								
									80389a9140
								
							
						
					
					
						commit
						09c419e11b
					
				|  | @ -2273,6 +2273,7 @@ _profile: | |||
|   sectionName: "Section name" | ||||
|   sectionNameNoneDescription: "Do not display the section name" | ||||
|   sectionNameNone: "Section without name" | ||||
|   policyDisplayLimitExceeded: "The number of items displayed exceeds the current support plan's limit ({max}). This item will not be displayed. You can upgrade your plan [here](https://go.misskey.io/donate)." | ||||
| _exportOrImport: | ||||
|   allNotes: "All notes" | ||||
|   favoritedNotes: "Favorite notes" | ||||
|  |  | |||
|  | @ -8862,6 +8862,10 @@ export interface Locale extends ILocale { | |||
|          * 名前が表示されないセクション | ||||
|          */ | ||||
|         "sectionNameNone": string; | ||||
|         /** | ||||
|          * 現在の支援プランの表示上限({max}個)を超えているため、この項目は表示されません。[ここ](https://go.misskey.io/donate)からプランをアップグレードできます。
 | ||||
|          */ | ||||
|         "policyDisplayLimitExceeded": ParameterizedString<"max">; | ||||
|     }; | ||||
|     "_exportOrImport": { | ||||
|         /** | ||||
|  |  | |||
|  | @ -2329,6 +2329,7 @@ _profile: | |||
|   sectionName: "セクション名" | ||||
|   sectionNameNoneDescription: "セクション名を表示しないようにする" | ||||
|   sectionNameNone: "名前が表示されないセクション" | ||||
|   policyDisplayLimitExceeded: "現在の支援プランの表示上限({max}個)を超えているため、この項目は表示されません。[ここ](https://go.misskey.io/donate)からプランをアップグレードできます。" | ||||
| 
 | ||||
| _exportOrImport: | ||||
|   allNotes: "全てのノート" | ||||
|  |  | |||
|  | @ -2255,8 +2255,9 @@ _profile: | |||
|   addMutualLink: "서로링크 추가" | ||||
|   addMutualLinkSection: "섹션 추가" | ||||
|   sectionName: "섹션 이름" | ||||
|   sectionNameNoneDescription: "섹션 이름이 표시되지 않도록 합니다." | ||||
|   sectionNameNoneDescription: "섹션 이름이 표시되지 않도록 합니다" | ||||
|   sectionNameNone: "이름이 표시되지 않는 섹션" | ||||
|   policyDisplayLimitExceeded: "현재 지원 플랜의 표시 제한({max}개)을 초과하였기 때문에 이 항목은 표시되지 않습니다. [여기](https://go.misskey.io/donate)에서 플랜을 업그레이드할 수 있습니다." | ||||
| _exportOrImport: | ||||
|   allNotes: "모든 노트" | ||||
|   favoritedNotes: "즐겨찾기한 노트" | ||||
|  |  | |||
|  | @ -111,7 +111,7 @@ export const DEFAULT_POLICIES: RolePolicies = { | |||
| 	rateLimitFactor: 1, | ||||
| 	avatarDecorationLimit: 1, | ||||
| 	mutualLinkSectionLimit: 1, | ||||
| 	mutualLinkLimit: 15, | ||||
| 	mutualLinkLimit: 3, | ||||
| }; | ||||
| 
 | ||||
| @Injectable() | ||||
|  |  | |||
|  | @ -533,7 +533,7 @@ export class UserEntityService implements OnModuleInit { | |||
| 				lang: profile!.lang, | ||||
| 				fields: profile!.fields, | ||||
| 				verifiedLinks: profile!.verifiedLinks, | ||||
| 				mutualLinkSections: profile!.mutualLinkSections, | ||||
| 				mutualLinkSections: isMe ? profile!.mutualLinkSections : profile!.mutualLinkSections.slice(0, policies!.mutualLinkSectionLimit).map(section => ({ ...section, mutualLinks: section.mutualLinks.slice(0, policies!.mutualLinkLimit) })), | ||||
| 				followersCount: followersCount ?? 0, | ||||
| 				followingCount: followingCount ?? 0, | ||||
| 				notesCount: user.notesCount, | ||||
|  |  | |||
|  | @ -117,6 +117,12 @@ export const meta = { | |||
| 			id: 'bf326f31-d430-4f97-9933-5d61e4d48a23', | ||||
| 		}, | ||||
| 
 | ||||
| 		invalidUrl: { | ||||
| 			message: 'Invalid URL', | ||||
| 			code: 'INVALID_URL', | ||||
| 			id: 'b2452e00-2bd0-4da8-a2d0-972859da7358', | ||||
| 		}, | ||||
| 
 | ||||
| 		forbiddenToSetYourself: { | ||||
| 			message: 'You can\'t set yourself as your own alias.', | ||||
| 			code: 'FORBIDDEN_TO_SET_YOURSELF', | ||||
|  | @ -228,12 +234,14 @@ export const paramDef = { | |||
| 		}, | ||||
| 		mutualLinkSections: { | ||||
| 			type: 'array', | ||||
| 			maxItems: 10, | ||||
| 			items: { | ||||
| 				type: 'object', | ||||
| 				properties: { | ||||
| 					name: { type: 'string', nullable: true }, | ||||
| 					mutualLinks: { | ||||
| 						type: 'array', | ||||
| 						maxItems: 30, | ||||
| 						items: { | ||||
| 							type: 'object', | ||||
| 							properties: { | ||||
|  | @ -359,24 +367,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 			} | ||||
| 
 | ||||
| 			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 (!RegExp(/^https?:\/\//).test(mutualLink.url)) throw new ApiError(meta.errors.invalidUrl); | ||||
| 
 | ||||
| 						if (!file) { | ||||
| 							throw new ApiError(meta.errors.noSuchFile); | ||||
| 						} | ||||
| 						if (!file.type.startsWith('image/')) { | ||||
| 							throw new ApiError(meta.errors.fileNotAnImage); | ||||
| 						} | ||||
| 						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 { | ||||
| 							id: this.idService.gen(), | ||||
|  |  | |||
|  | @ -99,7 +99,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 								<template #icon><i class="ti ti-message"></i></template> | ||||
| 								{{ i18n.ts.support }} | ||||
| 							</FormLink> | ||||
| 							<FormLink to="https://misskeyhq.fanbox.cc" external> | ||||
| 							<FormLink to="https://go.misskey.io/donate" external> | ||||
| 								<template #icon><i class="ti ti-pig-money"></i></template> | ||||
| 								{{ i18n.tsx.supportThisInstance({ name: instance.name ?? host }) }} | ||||
| 								<template #suffix>pixivFANBOX</template> | ||||
|  |  | |||
|  | @ -755,25 +755,6 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 				</div> | ||||
| 			</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> | ||||
|  | @ -793,6 +774,25 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 				</div> | ||||
| 			</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.canHideAds, 'canHideAds'])"> | ||||
| 				<template #label>{{ i18n.ts._role._options.canHideAds }}</template> | ||||
| 				<template #suffix> | ||||
|  |  | |||
|  | @ -94,7 +94,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 
 | ||||
| 			<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 inline style="margin-right: 8px;" @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> | ||||
|  | @ -109,7 +109,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 					@start="e => e.item.classList.add('active')" | ||||
| 					@end="e => e.item.classList.remove('active')" | ||||
| 				> | ||||
| 					<template #item="{element: sectionElement,index: sectionIndex}"> | ||||
| 					<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> | ||||
|  | @ -118,9 +118,10 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 									<template #label>{{ sectionElement.name || i18n.ts._profile.sectionNameNone }}</template> | ||||
| 
 | ||||
| 									<div class="_gaps_s" :class="$style.metadataMargin"> | ||||
| 										<MkInfo v-if="sectionIndex >= $i.policies.mutualLinkSectionLimit" warn><Mfm :text="i18n.tsx._profile.policyDisplayLimitExceeded({ max: $i.policies.mutualLinkSectionLimit })"/></MkInfo> | ||||
| 										<MkInput v-if="sectionElement.name !== null" v-model="sectionElement.name" :placeholder="i18n.ts._profile.sectionName" :max="32"></MkInput> | ||||
| 										<MkSwitch v-model="sectionElement.none" @update:modelValue="()=>{ sectionElement.none ? sectionElement.name = null : sectionElement.name = 'New Section' }">{{ 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> | ||||
| 										<MkButton inline style="margin-right: 8px;" @click="addMutualLinks(sectionIndex)"><i class="ti ti-plus"></i> {{ i18n.ts._profile.addMutualLink }}</MkButton> | ||||
| 									</div> | ||||
| 
 | ||||
| 									<Sortable | ||||
|  | @ -132,12 +133,13 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 										@start="e => e.item.classList.add('active')" | ||||
| 										@end="e => e.item.classList.remove('active')" | ||||
| 									> | ||||
| 										<template #item="{element: linkElement,index: linkIndex}"> | ||||
| 										<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}"> | ||||
| 													<MkInfo v-if="linkIndex >= $i.policies.mutualLinkLimit" warn><Mfm :text="i18n.tsx._profile.policyDisplayLimitExceeded({ max: $i.policies.mutualLinkLimit })"/></MkInfo> | ||||
| 													<MkInput v-model="linkElement.url" small> | ||||
| 														<template #label>{{ i18n.ts._profile.mutualLinksUrl }}</template> | ||||
| 													</MkInput> | ||||
|  |  | |||
|  | @ -80,7 +80,19 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 							<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p> | ||||
| 						</MkOmit> | ||||
| 					</div> | ||||
| 					<MkContainer v-if="user?.mutualLinkSections?.length > 0" :showHeader="false" :max-height="200" class="fields" :style="{borderRadius: 0}"> | ||||
| 					<MkContainer v-if="$i && $i.id == user.id && user?.mutualLinkSections?.slice(0, $i.policies.mutualLinkSectionLimit).length > 0" :showHeader="false" :max-height="200" class="fields" :style="{borderRadius: 0}"> | ||||
| 						<div v-for="(section, index) in user?.mutualLinkSections.slice(0, $i.policies.mutualLinkSectionLimit)" :key="index" :class="$style.mutualLinkSections"> | ||||
| 							<span v-if="section.name">{{ section.name }}</span> | ||||
| 							<div :class="$style.mutualLinks"> | ||||
| 								<div v-for="mutualLink in section.mutualLinks.slice(0, $i.policies.mutualLinkLimit)" :key="mutualLink.id"> | ||||
| 									<MkLink :hideIcon="true" :url="mutualLink.url"> | ||||
| 										<img :class="$style.mutualLinkImg" :src="mutualLink.imgSrc" :alt="mutualLink.description"/> | ||||
| 									</MkLink> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</MkContainer> | ||||
| 					<MkContainer v-else-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"> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue