nanka iroiro (#6847)
* wip * wip * wip * wip * Update ja-JP.yml * wip * wip * wip
This commit is contained in:
		
							parent
							
								
									50e917d232
								
							
						
					
					
						commit
						0044d83801
					
				|  | @ -316,6 +316,8 @@ bannerUrl: "バナー画像のURL" | |||
| basicInfo: "基本情報" | ||||
| pinnedUsers: "ピン留めユーザー" | ||||
| pinnedUsersDescription: "「みつける」ページなどにピン留めしたいユーザーを改行で区切って記述します。" | ||||
| pinnedPages: "ピン留めページ" | ||||
| pinnedPagesDescription: "インスタンスのトップページにピン留めしたいページのパスを改行で区切って記述します。" | ||||
| hcaptcha: "hCaptcha" | ||||
| enableHcaptcha: "hCaptchaを有効にする" | ||||
| hcaptchaSiteKey: "サイトキー" | ||||
|  | @ -1117,6 +1119,7 @@ _pages: | |||
|   unlike: "いいね解除" | ||||
|   my: "自分のページ" | ||||
|   liked: "いいねしたページ" | ||||
|   featured: "人気" | ||||
|   inspector: "インスペクター" | ||||
|   contents: "コンテンツ" | ||||
|   content: "ページブロック" | ||||
|  |  | |||
|  | @ -0,0 +1,14 @@ | |||
| import {MigrationInterface, QueryRunner} from "typeorm"; | ||||
| 
 | ||||
| export class instancePinnedPages1605585339718 implements MigrationInterface { | ||||
|     name = 'instancePinnedPages1605585339718' | ||||
| 
 | ||||
|     public async up(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "pinnedPages" character varying(512) array NOT NULL DEFAULT '{"/announcements", "/featured", "/channels", "/pages", "/explore", "/games/reversi", "/about-misskey"}'::varchar[]`); | ||||
|     } | ||||
| 
 | ||||
|     public async down(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "pinnedPages"`); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -8,7 +8,7 @@ | |||
| 	<MkError v-if="error" @retry="init()"/> | ||||
| 
 | ||||
| 	<div v-show="more && reversed" style="margin-bottom: var(--margin);"> | ||||
| 		<button class="_loadMore" v-appear="$store.state.device.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> | ||||
| 		<button class="_loadMore" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> | ||||
| 			<template v-if="!moreFetching">{{ $t('loadMore') }}</template> | ||||
| 			<template v-if="moreFetching"><MkLoading inline/></template> | ||||
| 		</button> | ||||
|  |  | |||
|  | @ -1,26 +1,32 @@ | |||
| <template> | ||||
| <div class="pxhvhrfw" v-size="{ max: [500] }"> | ||||
| 	<button v-for="item in items" class="_button" @click="$emit('update:value', item.value)" :class="{ active: value === item.value }" :disabled="value === item.value" :key="item.value"><Fa v-if="item.icon" :icon="item.icon" class="icon"/>{{ item.label }}</button> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineComponent, h, resolveDirective, withDirectives } from 'vue'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| 		items: { | ||||
| 			type: Array, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		value: { | ||||
| 			required: true, | ||||
| 		}, | ||||
| 	}, | ||||
| 	render() { | ||||
| 		const options = this.$slots.default(); | ||||
| 
 | ||||
| 		return withDirectives(h('div', { | ||||
| 			class: 'pxhvhrfw', | ||||
| 		}, options.map(option => h('button', { | ||||
| 			class: ['_button', { active: this.value === option.props.value }], | ||||
| 			key: option.props.value, | ||||
| 			disabled: this.value === option.props.value, | ||||
| 			onClick: () => { | ||||
| 				this.$emit('update:value', option.props.value); | ||||
| 			} | ||||
| 		}, option.children))), [ | ||||
| 			[resolveDirective('size'), { max: [500] }] | ||||
| 		]); | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| <style lang="scss"> | ||||
| .pxhvhrfw { | ||||
| 	display: flex; | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,7 +9,10 @@ | |||
| 	<template #header>Req Viewer</template> | ||||
| 
 | ||||
| 	<div class="rlkneywz"> | ||||
| 		<MkTab v-model:value="tab" :items="[{ label: 'Request', value: 'req', }, { label: 'Response', value: 'res', }]" style="border-bottom: solid 1px var(--divider);"/> | ||||
| 		<MkTab v-model:value="tab" style="border-bottom: solid 1px var(--divider);"> | ||||
| 			<option value="req">Request</option> | ||||
| 			<option value="res">Response</option> | ||||
| 		</MkTab> | ||||
| 
 | ||||
| 		<code v-if="tab === 'req'">{{ reqStr }}</code> | ||||
| 		<code v-if="tab === 'res'">{{ resStr }}</code> | ||||
|  |  | |||
|  | @ -4,7 +4,12 @@ | |||
| 		<Fa :icon="faTerminal" style="margin-right: 0.5em;"/>Task Manager | ||||
| 	</template> | ||||
| 	<div class="qljqmnzj"> | ||||
| 		<MkTab v-model:value="tab" :items="[{ label: 'Windows', value: 'windows', }, { label: 'Stream', value: 'stream', }, { label: 'Stream (Pool)', value: 'streamPool', }, { label: 'API', value: 'api', }]" style="border-bottom: solid 1px var(--divider);"/> | ||||
| 		<MkTab v-model:value="tab" style="border-bottom: solid 1px var(--divider);"> | ||||
| 			<option value="windows">Windows</option> | ||||
| 			<option value="stream">Stream</option> | ||||
| 			<option value="streamPool">Stream (Pool)</option> | ||||
| 			<option value="api">API</option> | ||||
| 		</MkTab> | ||||
| 
 | ||||
| 		<div class="content"> | ||||
| 			<div v-if="tab === 'windows'" class="windows" v-follow> | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ | |||
| 		</div> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<XPostForm :channel="channel" class="post-form _content _panel _vMargin" fixed/> | ||||
| 	<XPostForm :channel="channel" class="post-form _content _panel _vMargin" fixed v-if="this.$store.getters.isSignedIn"/> | ||||
| 
 | ||||
| 	<XTimeline class="_content _vMargin" src="channel" :channel="channelId" @before="before" @after="after"/> | ||||
| </div> | ||||
|  |  | |||
|  | @ -1,26 +1,30 @@ | |||
| <template> | ||||
| <div> | ||||
| 	<div class="_section" style="padding: 0;"> | ||||
| 		<MkTab class="_content" v-model:value="tab" :items="[{ label: $t('_channel.featured'), value: 'featured', icon: faFireAlt }, { label: $t('_channel.following'), value: 'following', icon: faHeart }, { label: $t('_channel.owned'), value: 'owned', icon: faEdit }]"/> | ||||
| 	<div class="_section" style="padding: 0;" v-if="this.$store.getters.isSignedIn"> | ||||
| 		<MkTab class="_content" v-model:value="tab"> | ||||
| 			<option value="featured"><Fa :icon="faFireAlt"/> {{ $t('_channel.featured') }}</option> | ||||
| 			<option value="following"><Fa :icon="faHeart"/> {{ $t('_channel.following') }}</option> | ||||
| 			<option value="owned"><Fa :icon="faEdit"/> {{ $t('_channel.owned') }}</option> | ||||
| 		</MkTab> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<div class="_section"> | ||||
| 		<div class="_content grwlizim featured" v-if="tab === 'featured'"> | ||||
| 			<MkPagination :pagination="featuredPagination" #default="{items}"> | ||||
| 				<MkChannelPreview v-for="channel in items" class="uveselbe" :channel="channel" :key="channel.id"/> | ||||
| 				<MkChannelPreview v-for="channel in items" class="_vMargin" :channel="channel" :key="channel.id"/> | ||||
| 			</MkPagination> | ||||
| 		</div> | ||||
| 
 | ||||
| 		<div class="_content grwlizim following" v-if="tab === 'following'"> | ||||
| 			<MkPagination :pagination="followingPagination" #default="{items}"> | ||||
| 				<MkChannelPreview v-for="channel in items" class="uveselbe" :channel="channel" :key="channel.id"/> | ||||
| 				<MkChannelPreview v-for="channel in items" class="_vMargin" :channel="channel" :key="channel.id"/> | ||||
| 			</MkPagination> | ||||
| 		</div> | ||||
| 
 | ||||
| 		<div class="_content grwlizim owned" v-if="tab === 'owned'"> | ||||
| 			<MkButton class="new" @click="create()"><Fa :icon="faPlus"/></MkButton> | ||||
| 			<MkPagination :pagination="ownedPagination" #default="{items}"> | ||||
| 				<MkChannelPreview v-for="channel in items" class="uveselbe" :channel="channel" :key="channel.id"/> | ||||
| 				<MkChannelPreview v-for="channel in items" class="_vMargin" :channel="channel" :key="channel.id"/> | ||||
| 			</MkPagination> | ||||
| 		</div> | ||||
| 	</div> | ||||
|  | @ -44,7 +48,11 @@ export default defineComponent({ | |||
| 		return { | ||||
| 			INFO: { | ||||
| 				title: this.$t('channel'), | ||||
| 				icon: faSatelliteDish | ||||
| 				icon: faSatelliteDish, | ||||
| 				action: { | ||||
| 					icon: faPlus, | ||||
| 					handler: this.create | ||||
| 				} | ||||
| 			}, | ||||
| 			tab: 'featured', | ||||
| 			featuredPagination: { | ||||
|  | @ -69,23 +77,3 @@ export default defineComponent({ | |||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .grwlizim { | ||||
| 	padding: 16px 0; | ||||
| 
 | ||||
| 	&.my .uveselbe:first-child { | ||||
| 		margin-top: 16px; | ||||
| 	} | ||||
| 
 | ||||
| 	.uveselbe:not(:last-child) { | ||||
| 		margin-bottom: 8px; | ||||
| 	} | ||||
| 
 | ||||
| 	@media (min-width: 500px) { | ||||
| 		.uveselbe:not(:last-child) { | ||||
| 			margin-bottom: 16px; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,7 +1,10 @@ | |||
| <template> | ||||
| <div class="mk-instance-emojis"> | ||||
| 	<div class="_section" style="padding: 0;"> | ||||
| 		<MkTab v-model:value="tab" :items="[{ label: $t('local'), value: 'local' }, { label: $t('remote'), value: 'remote' }]"/> | ||||
| 		<MkTab v-model:value="tab"> | ||||
| 			<option value="local">{{ $t('local') }}</option> | ||||
| 			<option value="remote">{{ $t('remote') }}</option> | ||||
| 		</MkTab> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<div class="_section"> | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <template> | ||||
| <div v-if="meta"> | ||||
| 	<section class="_section info"> | ||||
| <div v-if="meta" class="_section"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faInfoCircle"/> {{ $t('basicInfo') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkInput v-model:value="name">{{ $t('instanceName') }}</MkInput> | ||||
|  | @ -16,7 +16,7 @@ | |||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_section info"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_content"> | ||||
| 			<MkInput v-model:value="maxNoteTextLength" type="number" :save="() => save()"><template #icon><Fa :icon="faPencilAlt"/></template>{{ $t('maxNoteTextLength') }}</MkInput> | ||||
| 		</div> | ||||
|  | @ -30,7 +30,7 @@ | |||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_section info"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faUser"/> {{ $t('registration') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkSwitch v-model:value="enableRegistration" @update:value="save()">{{ $t('enableRegistration') }}</MkSwitch> | ||||
|  | @ -38,7 +38,7 @@ | |||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_section"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faShieldAlt"/> {{ $t('hcaptcha') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkSwitch v-model:value="enableHcaptcha">{{ $t('enableHcaptcha') }}</MkSwitch> | ||||
|  | @ -56,7 +56,7 @@ | |||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_section"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faShieldAlt"/> {{ $t('recaptcha') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkSwitch v-model:value="enableRecaptcha" ref="enableRecaptcha">{{ $t('enableRecaptcha') }}</MkSwitch> | ||||
|  | @ -74,7 +74,7 @@ | |||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_section"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faEnvelope" /> {{ $t('emailConfig') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkSwitch v-model:value="enableEmail" @update:value="save()">{{ $t('enableEmail') }}<template #desc>{{ $t('emailConfigInfo') }}</template></MkSwitch> | ||||
|  | @ -97,7 +97,7 @@ | |||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_section"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faBolt"/> {{ $t('serviceworker') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkSwitch v-model:value="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworkerInfo') }}</template></MkSwitch> | ||||
|  | @ -113,7 +113,7 @@ | |||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_section"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faThumbtack"/> {{ $t('pinnedUsers') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkTextarea v-model:value="pinnedUsers"> | ||||
|  | @ -125,7 +125,19 @@ | |||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_section"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faThumbtack"/> {{ $t('pinnedPages') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkTextarea v-model:value="pinnedPages"> | ||||
| 				<template #desc>{{ $t('pinnedPagesDescription') }}</template> | ||||
| 			</MkTextarea> | ||||
| 		</div> | ||||
| 		<div class="_footer"> | ||||
| 			<MkButton primary @click="save(true)"><Fa :icon="faSave"/> {{ $t('save') }}</MkButton> | ||||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faCloud"/> {{ $t('files') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkSwitch v-model:value="cacheRemoteFiles">{{ $t('cacheRemoteFiles') }}<template #desc>{{ $t('cacheRemoteFilesDescription') }}</template></MkSwitch> | ||||
|  | @ -138,7 +150,7 @@ | |||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_section"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faCloud"/> {{ $t('objectStorage') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkSwitch v-model:value="useObjectStorage">{{ $t('useObjectStorage') }}</MkSwitch> | ||||
|  | @ -166,7 +178,7 @@ | |||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_section"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faGhost"/> {{ $t('proxyAccount') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkInput :value="proxyAccount ? proxyAccount.username : null" disabled><template #prefix>@</template>{{ $t('proxyAccount') }}<template #desc>{{ $t('proxyAccountDescription') }}</template></MkInput> | ||||
|  | @ -174,7 +186,7 @@ | |||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_section"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faBan"/> {{ $t('blockedInstances') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkTextarea v-model:value="blockedHosts"> | ||||
|  | @ -186,7 +198,7 @@ | |||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_section"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faShareAlt"/> {{ $t('integration') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<header><Fa :icon="faTwitter"/> Twitter</header> | ||||
|  | @ -220,7 +232,7 @@ | |||
| 		</div> | ||||
| 	</section> | ||||
| 
 | ||||
| 	<section class="_section"> | ||||
| 	<section class="_card _vMargin"> | ||||
| 		<div class="_title"><Fa :icon="faArchway" /> Summaly Proxy</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkInput v-model:value="summalyProxy">URL</MkInput> | ||||
|  | @ -260,6 +272,7 @@ export default defineComponent({ | |||
| 				title: this.$t('instance'), | ||||
| 				icon: faCog, | ||||
| 			}, | ||||
| 			meta: null, | ||||
| 			url, | ||||
| 			proxyAccount: null, | ||||
| 			proxyAccountId: null, | ||||
|  | @ -269,6 +282,7 @@ export default defineComponent({ | |||
| 			remoteDriveCapacityMb: 0, | ||||
| 			blockedHosts: '', | ||||
| 			pinnedUsers: '', | ||||
| 			pinnedPages: '', | ||||
| 			maintainerName: null, | ||||
| 			maintainerEmail: null, | ||||
| 			name: null, | ||||
|  | @ -323,13 +337,9 @@ export default defineComponent({ | |||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		meta() { | ||||
| 			return this.$store.state.instance.meta; | ||||
| 		}, | ||||
| 	}, | ||||
| 	async created() { | ||||
| 		this.meta = await os.api('meta', { detail: true }); | ||||
| 
 | ||||
| 	created() { | ||||
| 		this.name = this.meta.name; | ||||
| 		this.description = this.meta.description; | ||||
| 		this.tosUrl = this.meta.tosUrl; | ||||
|  | @ -356,6 +366,7 @@ export default defineComponent({ | |||
| 		this.remoteDriveCapacityMb = this.meta.driveCapacityPerRemoteUserMb; | ||||
| 		this.blockedHosts = this.meta.blockedHosts.join('\n'); | ||||
| 		this.pinnedUsers = this.meta.pinnedUsers.join('\n'); | ||||
| 		this.pinnedPages = this.meta.pinnedPages.join('\n'); | ||||
| 		this.enableServiceWorker = this.meta.enableServiceWorker; | ||||
| 		this.swPublicKey = this.meta.swPublickey; | ||||
| 		this.swPrivateKey = this.meta.swPrivateKey; | ||||
|  | @ -506,6 +517,7 @@ export default defineComponent({ | |||
| 				remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10), | ||||
| 				blockedHosts: this.blockedHosts.split('\n') || [], | ||||
| 				pinnedUsers: this.pinnedUsers ? this.pinnedUsers.split('\n') : [], | ||||
| 				pinnedPages: this.pinnedPages ? this.pinnedPages.split('\n') : [], | ||||
| 				enableServiceWorker: this.enableServiceWorker, | ||||
| 				swPublicKey: this.swPublicKey, | ||||
| 				swPrivateKey: this.swPrivateKey, | ||||
|  |  | |||
|  | @ -1,7 +1,11 @@ | |||
| <template> | ||||
| <div class=""> | ||||
| 	<div class="_section" style="padding: 0;"> | ||||
| 		<MkTab v-model:value="tab" :items="[{ label: $t('ownedGroups'), value: 'owned' }, { label: $t('joinedGroups'), value: 'joined' }, { label: $t('invites'), icon: faEnvelopeOpenText, value: 'invites' }]"/> | ||||
| 		<MkTab v-model:value="tab"> | ||||
| 			<option value="owned">{{ $t('ownedGroups') }}</option> | ||||
| 			<option value="joined">{{ $t('joinedGroups') }}</option> | ||||
| 			<option value="invites"><Fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</option> | ||||
| 		</MkTab> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<div class="_section"> | ||||
|  |  | |||
|  | @ -1,21 +1,31 @@ | |||
| <template> | ||||
| <div class="fcuexfpr"> | ||||
| 	<div v-if="note" class="note"> | ||||
| 		<div class="_section"> | ||||
| 			<XNotes v-if="showNext" class="_content" :pagination="next"/> | ||||
| 			<MkButton v-else-if="hasNext" class="load _content" @click="showNext = true"><Fa :icon="faChevronUp"/></MkButton> | ||||
| 		<div class="_section" v-if="showNext"> | ||||
| 			<XNotes class="_content" :pagination="next"/> | ||||
| 		</div> | ||||
| 
 | ||||
| 		<div class="_section"> | ||||
| 			<div class="_content"> | ||||
| 		<div class="_section main"> | ||||
| 			<MkButton v-if="!showNext && hasNext" class="load next _content" @click="showNext = true"><Fa :icon="faChevronUp"/></MkButton> | ||||
| 			<div class="_content _vMargin"> | ||||
| 				<MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" class="_vMargin"/> | ||||
| 				<XNote v-model:note="note" :key="note.id" :detail="true" class="_vMargin"/> | ||||
| 			</div> | ||||
| 			<div class="_content clips _vMargin" v-if="clips && clips.length > 0"> | ||||
| 				<div class="title">{{ $t('clip') }}</div> | ||||
| 				<MkA v-for="item in clips" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _vMargin"> | ||||
| 					<b>{{ item.name }}</b> | ||||
| 					<div v-if="item.description" class="description">{{ item.description }}</div> | ||||
| 					<div class="user"> | ||||
| 						<MkAvatar :user="item.user" class="avatar"/> <MkUserName :user="item.user" :nowrap="false"/> | ||||
| 					</div> | ||||
| 				</MkA> | ||||
| 			</div> | ||||
| 			<MkButton v-if="!showPrev && hasPrev" class="load prev _content" @click="showPrev = true"><Fa :icon="faChevronDown"/></MkButton> | ||||
| 		</div> | ||||
| 
 | ||||
| 		<div class="_section"> | ||||
| 			<XNotes v-if="showPrev" class="_content" :pagination="prev"/> | ||||
| 			<MkButton v-else-if="hasPrev" class="load _content" @click="showPrev = true"><Fa :icon="faChevronDown"/></MkButton> | ||||
| 		<div class="_section" v-if="showPrev"> | ||||
| 			<XNotes class="_content" :pagination="prev"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 
 | ||||
|  | @ -28,7 +38,6 @@ | |||
| <script lang="ts"> | ||||
| import { computed, defineComponent } from 'vue'; | ||||
| import { faChevronUp, faChevronDown } from '@fortawesome/free-solid-svg-icons'; | ||||
| import Progress from '@/scripts/loading'; | ||||
| import XNote from '@/components/note.vue'; | ||||
| import XNotes from '@/components/notes.vue'; | ||||
| import MkRemoteCaution from '@/components/remote-caution.vue'; | ||||
|  | @ -55,6 +64,7 @@ export default defineComponent({ | |||
| 				avatar: this.note.user, | ||||
| 			} : null), | ||||
| 			note: null, | ||||
| 			clips: null, | ||||
| 			hasPrev: false, | ||||
| 			hasNext: false, | ||||
| 			showPrev: false, | ||||
|  | @ -88,11 +98,13 @@ export default defineComponent({ | |||
| 	}, | ||||
| 	methods: { | ||||
| 		fetch() { | ||||
| 			Progress.start(); | ||||
| 			os.api('notes/show', { | ||||
| 				noteId: this.noteId | ||||
| 			}).then(note => { | ||||
| 				Promise.all([ | ||||
| 					os.api('notes/clips', { | ||||
| 						noteId: note.id, | ||||
| 					}), | ||||
| 					os.api('users/notes', { | ||||
| 						userId: note.userId, | ||||
| 						untilId: note.id, | ||||
|  | @ -103,15 +115,14 @@ export default defineComponent({ | |||
| 						sinceId: note.id, | ||||
| 						limit: 1, | ||||
| 					}), | ||||
| 				]).then(([prev, next]) => { | ||||
| 				]).then(([clips, prev, next]) => { | ||||
| 					this.clips = clips; | ||||
| 					this.hasPrev = prev.length !== 0; | ||||
| 					this.hasNext = next.length !== 0; | ||||
| 					this.note = note; | ||||
| 				}); | ||||
| 			}).catch(e => { | ||||
| 				this.error = e; | ||||
| 			}).finally(() => { | ||||
| 				Progress.done(); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | @ -121,10 +132,46 @@ export default defineComponent({ | |||
| <style lang="scss" scoped> | ||||
| .fcuexfpr { | ||||
| 	> .note { | ||||
| 		> ._section { | ||||
| 		> .main { | ||||
| 			> .load { | ||||
| 				min-width: 0; | ||||
| 				border-radius: 999px; | ||||
| 
 | ||||
| 				&.next { | ||||
| 					margin-bottom: var(--margin); | ||||
| 				} | ||||
| 
 | ||||
| 				&.prev { | ||||
| 					margin-top: var(--margin); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			> .clips { | ||||
| 				> .title { | ||||
| 					font-weight: bold; | ||||
| 					padding: 12px; | ||||
| 				} | ||||
| 
 | ||||
| 				> .item { | ||||
| 					display: block; | ||||
| 					padding: 16px; | ||||
| 
 | ||||
| 					> .description { | ||||
| 						padding: 8px 0; | ||||
| 					} | ||||
| 
 | ||||
| 					> .user { | ||||
| 						$height: 32px; | ||||
| 						padding-top: 16px; | ||||
| 						border-top: solid 1px var(--divider); | ||||
| 						line-height: $height; | ||||
| 
 | ||||
| 						> .avatar { | ||||
| 							width: $height; | ||||
| 							height: $height; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -277,7 +277,7 @@ export default defineComponent({ | |||
| 						type: 'success', | ||||
| 						text: this.$t('_pages.created') | ||||
| 					}); | ||||
| 					this.$router.push(`/my/pages/edit/${this.pageId}`); | ||||
| 					this.$router.push(`/pages/edit/${this.pageId}`); | ||||
| 				}).catch(onError); | ||||
| 			} | ||||
| 		}, | ||||
|  | @ -296,7 +296,7 @@ export default defineComponent({ | |||
| 						type: 'success', | ||||
| 						text: this.$t('_pages.deleted') | ||||
| 					}); | ||||
| 					this.$router.push(`/my/pages`); | ||||
| 					this.$router.push(`/pages`); | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ | |||
| 		<div class="_content"> | ||||
| 			<MkA :to="`./${page.name}/view-source`" class="link">{{ $t('_pages.viewSource') }}</MkA> | ||||
| 			<template v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId"> | ||||
| 				<MkA :to="`/my/pages/edit/${page.id}`" class="link">{{ $t('_pages.editThisPage') }}</MkA> | ||||
| 				<MkA :to="`/pages/edit/${page.id}`" class="link">{{ $t('_pages.editThisPage') }}</MkA> | ||||
| 				<button v-if="$store.state.i.pinnedPageId === page.id" @click="pin(false)" class="link _textButton">{{ $t('unpin') }}</button> | ||||
| 				<button v-else @click="pin(true)" class="link _textButton">{{ $t('pin') }}</button> | ||||
| 			</template> | ||||
|  |  | |||
|  | @ -1,8 +1,18 @@ | |||
| <template> | ||||
| <div> | ||||
| 	<MkTab v-model:value="tab" :items="[{ label: $t('_pages.my'), value: 'my', icon: faEdit }, { label: $t('_pages.liked'), value: 'liked', icon: faHeart }]"/> | ||||
| 	<MkTab v-model:value="tab" v-if="this.$store.getters.isSignedIn"> | ||||
| 		<option value="featured"><Fa :icon="faFireAlt"/> {{ $t('_pages.featured') }}</option> | ||||
| 		<option value="my"><Fa :icon="faEdit"/> {{ $t('_pages.my') }}</option> | ||||
| 		<option value="liked"><Fa :icon="faHeart"/> {{ $t('_pages.liked') }}</option> | ||||
| 	</MkTab> | ||||
| 
 | ||||
| 	<div class="_section"> | ||||
| 		<div class="rknalgpo _content" v-if="tab === 'featured'"> | ||||
| 			<MkPagination :pagination="featuredPagesPagination" #default="{items}"> | ||||
| 				<MkPagePreview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/> | ||||
| 			</MkPagination> | ||||
| 		</div> | ||||
| 
 | ||||
| 		<div class="rknalgpo _content my" v-if="tab === 'my'"> | ||||
| 			<MkButton class="new" @click="create()"><Fa :icon="faPlus"/></MkButton> | ||||
| 			<MkPagination :pagination="myPagesPagination" #default="{items}"> | ||||
|  | @ -21,7 +31,7 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faPlus, faEdit } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faPlus, faEdit, faFireAlt } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faStickyNote, faHeart } from '@fortawesome/free-regular-svg-icons'; | ||||
| import MkPagePreview from '@/components/page-preview.vue'; | ||||
| import MkPagination from '@/components/ui/pagination.vue'; | ||||
|  | @ -42,7 +52,11 @@ export default defineComponent({ | |||
| 					handler: this.create | ||||
| 				} | ||||
| 			}, | ||||
| 			tab: 'my', | ||||
| 			tab: 'featured', | ||||
| 			featuredPagesPagination: { | ||||
| 				endpoint: 'pages/featured', | ||||
| 				noPaging: true, | ||||
| 			}, | ||||
| 			myPagesPagination: { | ||||
| 				endpoint: 'i/pages', | ||||
| 				limit: 5, | ||||
|  | @ -51,12 +65,12 @@ export default defineComponent({ | |||
| 				endpoint: 'i/page-likes', | ||||
| 				limit: 5, | ||||
| 			}, | ||||
| 			faStickyNote, faPlus, faEdit, faHeart | ||||
| 			faStickyNote, faPlus, faEdit, faHeart, faFireAlt | ||||
| 		}; | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		create() { | ||||
| 			this.$router.push(`/my/pages/new`); | ||||
| 			this.$router.push(`/pages/new`); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -1,6 +1,9 @@ | |||
| <template> | ||||
| <section class="rrfwjxfl _section"> | ||||
| 	<MkTab v-model:value="tab" :items="[{ label: $t('mutedUsers'), value: 'mute' }, { label: $t('blockedUsers'), value: 'block' }]" style="margin-bottom: var(--margin);"/> | ||||
| 	<MkTab v-model:value="tab" style="margin-bottom: var(--margin);"> | ||||
| 		<option value="mute">{{ $t('mutedUsers') }}</option> | ||||
| 		<option value="block">{{ $t('blockedUsers') }}</option> | ||||
| 	</MkTab> | ||||
| 	<div class="_content" v-if="tab === 'mute'"> | ||||
| 		<MkPagination :pagination="mutingPagination" class="muting"> | ||||
| 			<template #empty><MkInfo>{{ $t('noUsers') }}</MkInfo></template> | ||||
|  |  | |||
|  | @ -1,7 +1,10 @@ | |||
| <template> | ||||
| <div class="_section"> | ||||
| 	<div class="_card"> | ||||
| 		<MkTab v-model:value="tab" :items="[{ label: $t('_wordMute.soft'), value: 'soft' }, { label: $t('_wordMute.hard'), value: 'hard' }]"/> | ||||
| 		<MkTab v-model:value="tab"> | ||||
| 			<option value="soft">{{ $t('_wordMute.soft') }}</option> | ||||
| 			<option value="hard">{{ $t('_wordMute.hard') }}</option> | ||||
| 		</MkTab> | ||||
| 		<div class="_content"> | ||||
| 			<div v-show="tab === 'soft'"> | ||||
| 				<MkInfo>{{ $t('_wordMute.softDescription') }}</MkInfo> | ||||
|  |  | |||
|  | @ -0,0 +1,141 @@ | |||
| <template> | ||||
| <div class="xyeqzsjl _panel"> | ||||
| 	<header> | ||||
| 		<button class="_button" @click="back()" v-if="history.length > 0"><Fa :icon="faChevronLeft"/></button> | ||||
| 		<XHeader class="title" :info="pageInfo" :with-back="false"/> | ||||
| 	</header> | ||||
| 	<div> | ||||
| 		<component :is="component" v-bind="props" :ref="changePage"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; | ||||
| import XWindow from '@/components/ui/window.vue'; | ||||
| import XHeader from '@/ui/_common_/header.vue'; | ||||
| import { popout } from '@/scripts/popout'; | ||||
| import { resolve } from '@/router'; | ||||
| import { url } from '@/config'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XWindow, | ||||
| 		XHeader, | ||||
| 	}, | ||||
| 
 | ||||
| 	provide() { | ||||
| 		return { | ||||
| 			navHook: (path) => { | ||||
| 				this.navigate(path); | ||||
| 			} | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		initialPath: { | ||||
| 			type: String, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			pageInfo: null, | ||||
| 			path: this.initialPath, | ||||
| 			component: null, | ||||
| 			props: null, | ||||
| 			history: [], | ||||
| 			faChevronLeft, | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		url(): string { | ||||
| 			return url + this.path; | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	created() { | ||||
| 		const { component, props } = resolve(this.initialPath); | ||||
| 		this.component = component; | ||||
| 		this.props = props; | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		changePage(page) { | ||||
| 			if (page == null) return; | ||||
| 			if (page.INFO) { | ||||
| 				this.pageInfo = page.INFO; | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		navigate(path, record = true) { | ||||
| 			if (record) this.history.push(this.path); | ||||
| 			this.path = path; | ||||
| 			const { component, props } = resolve(path); | ||||
| 			this.component = component; | ||||
| 			this.props = props; | ||||
| 		}, | ||||
| 
 | ||||
| 		back() { | ||||
| 			this.navigate(this.history.pop(), false); | ||||
| 		}, | ||||
| 
 | ||||
| 		expand() { | ||||
| 			this.$router.push(this.path); | ||||
| 			this.$refs.window.close(); | ||||
| 		}, | ||||
| 
 | ||||
| 		popout() { | ||||
| 			popout(this.path, this.$el); | ||||
| 			this.$refs.window.close(); | ||||
| 		}, | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .xyeqzsjl { | ||||
| 	--section-padding: 16px; | ||||
| 
 | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	contain: content; | ||||
| 
 | ||||
| 	> header { | ||||
| 		$height: 50px; | ||||
| 		display: flex; | ||||
| 		position: relative; | ||||
| 		z-index: 1; | ||||
| 		height: $height; | ||||
| 		line-height: $height; | ||||
| 		box-shadow: 0px 1px var(--divider); | ||||
| 
 | ||||
| 		> button { | ||||
| 			height: $height; | ||||
| 			width: $height; | ||||
| 
 | ||||
| 			&:hover { | ||||
| 				color: var(--fgHighlighted); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		> .title { | ||||
| 			flex: 1; | ||||
| 			position: relative; | ||||
| 			line-height: $height; | ||||
| 			white-space: nowrap; | ||||
| 			overflow: hidden; | ||||
| 			text-overflow: ellipsis; | ||||
| 			text-align: center; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> div { | ||||
| 		flex: 1; | ||||
| 		overflow: auto; | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  | @ -1,18 +1,13 @@ | |||
| <template> | ||||
| <div class="rsqzvsbo"> | ||||
| 	<div class="_section"> | ||||
| 		<div class="_content _panel about" v-if="meta"> | ||||
| 			<div class="body"> | ||||
| 				<div class="desc" v-html="meta.description || $t('introMisskey')"></div> | ||||
| 				<MkButton @click="signup()" style="display: inline-block; margin-right: 16px;" primary>{{ $t('signup') }}</MkButton> | ||||
| 				<MkButton @click="signin()" style="display: inline-block;">{{ $t('login') }}</MkButton> | ||||
| 			</div> | ||||
| 		</div> | ||||
| <div class="rsqzvsbo _section" v-if="meta"> | ||||
| 	<div class="about"> | ||||
| 		<h1>{{ instanceName }}</h1> | ||||
| 		<div class="desc" v-html="meta.description || $t('introMisskey')"></div> | ||||
| 		<MkButton @click="signup()" style="display: inline-block; margin-right: 16px;" primary>{{ $t('signup') }}</MkButton> | ||||
| 		<MkButton @click="signin()" style="display: inline-block;">{{ $t('login') }}</MkButton> | ||||
| 	</div> | ||||
| 	<div class="_section"> | ||||
| 		<div class="_content"> | ||||
| 			<XNotes :pagination="featuredPagination"/> | ||||
| 		</div> | ||||
| 	<div class="blocks"> | ||||
| 		<XBlock class="block" v-for="path in meta.pinnedPages" :initial-path="path" :key="path"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | @ -24,33 +19,30 @@ import XSigninDialog from '@/components/signin-dialog.vue'; | |||
| import XSignupDialog from '@/components/signup-dialog.vue'; | ||||
| import MkButton from '@/components/ui/button.vue'; | ||||
| import XNotes from '@/components/notes.vue'; | ||||
| import { host } from '@/config'; | ||||
| import XBlock from './welcome.entrance.block.vue'; | ||||
| import { host, instanceName } from '@/config'; | ||||
| import * as os from '@/os'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkButton, | ||||
| 		XNotes, | ||||
| 		XBlock, | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			featuredPagination: { | ||||
| 				endpoint: 'notes/featured', | ||||
| 				limit: 10, | ||||
| 				noPaging: true, | ||||
| 			}, | ||||
| 			host: toUnicode(host), | ||||
| 			instanceName, | ||||
| 			meta: null, | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		meta() { | ||||
| 			return this.$store.state.instance.meta; | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	created() { | ||||
| 		os.api('meta', { detail: true }).then(meta => { | ||||
| 			this.meta = meta; | ||||
| 		}); | ||||
| 
 | ||||
| 		os.api('stats').then(stats => { | ||||
| 			this.stats = stats; | ||||
| 		}); | ||||
|  | @ -74,15 +66,42 @@ export default defineComponent({ | |||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .rsqzvsbo { | ||||
| 	> ._section { | ||||
| 		> .about { | ||||
| 			> .body { | ||||
| 				padding: 32px; | ||||
| 	text-align: center; | ||||
| 
 | ||||
| 				@media (max-width: 500px) { | ||||
| 					padding: 16px; | ||||
| 				} | ||||
| 			} | ||||
| 	> .about { | ||||
| 		display: inline-block; | ||||
| 		padding: 24px; | ||||
| 		margin-bottom: var(--margin); | ||||
| 		-webkit-backdrop-filter: blur(8px); | ||||
| 		backdrop-filter: blur(8px); | ||||
| 		background: rgba(0, 0, 0, 0.5); | ||||
| 		border-radius: var(--radius); | ||||
| 		text-align: center; | ||||
| 		box-sizing: border-box; | ||||
| 		min-width: 300px; | ||||
| 		max-width: 800px; | ||||
| 
 | ||||
| 		&, * { | ||||
| 			color: #fff !important; | ||||
| 		} | ||||
| 
 | ||||
| 		> h1 { | ||||
| 			margin: 0 0 16px 0; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> .blocks { | ||||
| 		display: grid; | ||||
| 		grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); | ||||
| 		grid-gap: var(--margin); | ||||
| 		text-align: left; | ||||
| 
 | ||||
| 		> .block { | ||||
| 			height: 600px; | ||||
| 		} | ||||
| 
 | ||||
| 		@media (max-width: 800px) { | ||||
| 			grid-template-columns: 1fr; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import { defineComponent } from 'vue'; | |||
| import XSetup from './welcome.setup.vue'; | ||||
| import XEntrance from './welcome.entrance.vue'; | ||||
| import { instanceName } from '@/config'; | ||||
| import * as os from '@/os'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
|  | @ -20,16 +21,17 @@ export default defineComponent({ | |||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				title: instanceName || 'Misskey', | ||||
| 				title: instanceName, | ||||
| 				icon: null | ||||
| 			}, | ||||
| 			meta: null | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		meta() { | ||||
| 			return this.$store.state.instance.meta; | ||||
| 		}, | ||||
| 	}, | ||||
| 	created() { | ||||
| 		os.api('meta', { detail: true }).then(meta => { | ||||
| 			this.meta = meta; | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -33,6 +33,9 @@ export const router = createRouter({ | |||
| 		{ path: '/explore', component: page('explore') }, | ||||
| 		{ path: '/explore/tags/:tag', props: true, component: page('explore') }, | ||||
| 		{ path: '/search', component: page('search') }, | ||||
| 		{ path: '/pages', name: 'pages', component: page('pages') }, | ||||
| 		{ path: '/pages/new', component: page('page-editor/page-editor') }, | ||||
| 		{ path: '/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) }, | ||||
| 		{ path: '/channels', component: page('channels') }, | ||||
| 		{ path: '/channels/new', component: page('channel-editor') }, | ||||
| 		{ path: '/channels/:channelId/edit', component: page('channel-editor'), props: true }, | ||||
|  | @ -47,9 +50,6 @@ export const router = createRouter({ | |||
| 		{ path: '/my/messaging/group/:group', component: page('messaging/messaging-room'), props: route => ({ groupId: route.params.group }) }, | ||||
| 		{ path: '/my/drive', name: 'drive', component: page('drive') }, | ||||
| 		{ path: '/my/drive/folder/:folder', component: page('drive') }, | ||||
| 		{ path: '/my/pages', name: 'pages', component: page('pages') }, | ||||
| 		{ path: '/my/pages/new', component: page('page-editor/page-editor') }, | ||||
| 		{ path: '/my/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) }, | ||||
| 		{ path: '/my/follow-requests', component: page('follow-requests') }, | ||||
| 		{ path: '/my/lists', component: page('my-lists/index') }, | ||||
| 		{ path: '/my/lists/:list', component: page('my-lists/list') }, | ||||
|  |  | |||
|  | @ -96,8 +96,7 @@ export const sidebarDef = { | |||
| 	pages: { | ||||
| 		title: 'pages', | ||||
| 		icon: faFileAlt, | ||||
| 		show: computed(() => store.getters.isSignedIn), | ||||
| 		to: '/my/pages', | ||||
| 		to: '/pages', | ||||
| 	}, | ||||
| 	clips: { | ||||
| 		title: 'clip', | ||||
|  |  | |||
|  | @ -7,12 +7,12 @@ | |||
| 		<MkA class="link" to="/about">{{ $t('aboutX', { x: instanceName }) }}</MkA> | ||||
| 	</header> | ||||
| 
 | ||||
| 	<div class="banner" :style="{ backgroundImage: `url(${ $store.state.instance.meta.bannerUrl })` }"> | ||||
| 		<h1>{{ instanceName }}</h1> | ||||
| 	<div class="banner" :class="{ asBg: $route.path === '/' }" :style="{ backgroundImage: `url(${ $store.state.instance.meta.bannerUrl })` }"> | ||||
| 		<h1 v-if="$route.path !== '/'">{{ instanceName }}</h1> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<div class="contents" ref="contents" :class="{ wallpaper }"> | ||||
| 		<header class="header" ref="header"> | ||||
| 		<header class="header" ref="header" v-show="$route.path !== '/'"> | ||||
| 			<XHeader :info="pageInfo"/> | ||||
| 		</header> | ||||
| 		<main ref="main"> | ||||
|  | @ -116,11 +116,10 @@ export default defineComponent({ | |||
| <style lang="scss" scoped> | ||||
| .mk-app { | ||||
| 	min-height: 100vh; | ||||
| 	max-width: 1300px; | ||||
| 	margin: 0 auto; | ||||
| 	box-shadow: 1px 0 var(--divider), -1px 0 var(--divider); | ||||
| 
 | ||||
| 	> header { | ||||
| 		position: relative; | ||||
| 		z-index: 1; | ||||
| 		background: var(--panel); | ||||
| 		padding: 0 16px; | ||||
| 		text-align: center; | ||||
|  | @ -145,6 +144,12 @@ export default defineComponent({ | |||
| 		background-size: cover; | ||||
| 		background-position: center; | ||||
| 
 | ||||
| 		&.asBg { | ||||
| 			position: absolute; | ||||
| 			left: 0; | ||||
| 			height: 320px; | ||||
| 		} | ||||
| 
 | ||||
| 		&:after { | ||||
| 			content: ""; | ||||
| 			display: block; | ||||
|  | @ -166,6 +171,9 @@ export default defineComponent({ | |||
| 	} | ||||
| 
 | ||||
| 	> .contents { | ||||
| 		position: relative; | ||||
| 		z-index: 1; | ||||
| 
 | ||||
| 		> .header { | ||||
| 			position: sticky; | ||||
| 			top: 0; | ||||
|  |  | |||
|  | @ -76,6 +76,11 @@ export class Meta { | |||
| 	}) | ||||
| 	public blockedHosts: string[]; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 512, array: true, default: '{"/announcements", "/featured", "/channels", "/explore", "/games/reversi", "/about-misskey"}' | ||||
| 	}) | ||||
| 	public pinnedPages: string[]; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 512, | ||||
| 		nullable: true, | ||||
|  |  | |||
|  | @ -85,8 +85,9 @@ export class PageRepository extends Repository<Page> { | |||
| 
 | ||||
| 	public packMany( | ||||
| 		pages: Page[], | ||||
| 		me?: User['id'] | User | null | undefined, | ||||
| 	) { | ||||
| 		return Promise.all(pages.map(x => this.pack(x))); | ||||
| 		return Promise.all(pages.map(x => this.pack(x, me))); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -208,6 +208,10 @@ export const meta = { | |||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		pinnedPages: { | ||||
| 			validator: $.optional.arr($.str), | ||||
| 		}, | ||||
| 
 | ||||
| 		langs: { | ||||
| 			validator: $.optional.arr($.str), | ||||
| 			desc: { | ||||
|  | @ -537,6 +541,10 @@ export default define(meta, async (ps, me) => { | |||
| 		set.langs = ps.langs.filter(Boolean); | ||||
| 	} | ||||
| 
 | ||||
| 	if (Array.isArray(ps.pinnedPages)) { | ||||
| 		set.pinnedPages = ps.pinnedPages.filter(Boolean); | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.summalyProxy !== undefined) { | ||||
| 		set.summalyProxy = ps.summalyProxy; | ||||
| 	} | ||||
|  |  | |||
|  | @ -99,8 +99,6 @@ export default define(meta, async (ps, me) => { | |||
| 		} | ||||
| 	}); | ||||
| 
 | ||||
| 	const proxyAccount = instance.proxyAccountId ? await Users.pack(instance.proxyAccountId).catch(() => null) : null; | ||||
| 
 | ||||
| 	const response: any = { | ||||
| 		maintainerName: instance.maintainerName, | ||||
| 		maintainerEmail: instance.maintainerEmail, | ||||
|  | @ -122,8 +120,6 @@ export default define(meta, async (ps, me) => { | |||
| 		disableGlobalTimeline: instance.disableGlobalTimeline, | ||||
| 		driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, | ||||
| 		driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, | ||||
| 		cacheRemoteFiles: instance.cacheRemoteFiles, | ||||
| 		proxyRemoteFiles: instance.proxyRemoteFiles, | ||||
| 		enableHcaptcha: instance.enableHcaptcha, | ||||
| 		hcaptchaSiteKey: instance.hcaptchaSiteKey, | ||||
| 		enableRecaptcha: instance.enableRecaptcha, | ||||
|  | @ -135,9 +131,6 @@ export default define(meta, async (ps, me) => { | |||
| 		iconUrl: instance.iconUrl, | ||||
| 		maxNoteTextLength: Math.min(instance.maxNoteTextLength, DB_MAX_NOTE_TEXT_LENGTH), | ||||
| 		emojis: await Emojis.packMany(emojis), | ||||
| 		requireSetup: (await Users.count({ | ||||
| 			host: null, | ||||
| 		})) === 0, | ||||
| 		enableEmail: instance.enableEmail, | ||||
| 
 | ||||
| 		enableTwitterIntegration: instance.enableTwitterIntegration, | ||||
|  | @ -146,10 +139,20 @@ export default define(meta, async (ps, me) => { | |||
| 
 | ||||
| 		enableServiceWorker: instance.enableServiceWorker, | ||||
| 
 | ||||
| 		proxyAccountName: proxyAccount ? proxyAccount.username : null, | ||||
| 		...(ps.detail ? { | ||||
| 			pinnedPages: instance.pinnedPages, | ||||
| 			cacheRemoteFiles: instance.cacheRemoteFiles, | ||||
| 			proxyRemoteFiles: instance.proxyRemoteFiles, | ||||
| 			requireSetup: (await Users.count({ | ||||
| 				host: null, | ||||
| 			})) === 0, | ||||
| 		} : {}) | ||||
| 	}; | ||||
| 
 | ||||
| 	if (ps.detail) { | ||||
| 		const proxyAccount = instance.proxyAccountId ? await Users.pack(instance.proxyAccountId).catch(() => null) : null; | ||||
| 
 | ||||
| 		response.proxyAccountName = proxyAccount ? proxyAccount.username : null; | ||||
| 		response.features = { | ||||
| 			registration: !instance.disableRegistration, | ||||
| 			localTimeLine: !instance.disableLocalTimeline, | ||||
|  | @ -164,42 +167,42 @@ export default define(meta, async (ps, me) => { | |||
| 			serviceWorker: instance.enableServiceWorker, | ||||
| 			miauth: true, | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	if (me && me.isAdmin) { | ||||
| 		response.useStarForReactionFallback = instance.useStarForReactionFallback; | ||||
| 		response.pinnedUsers = instance.pinnedUsers; | ||||
| 		response.hiddenTags = instance.hiddenTags; | ||||
| 		response.blockedHosts = instance.blockedHosts; | ||||
| 		response.hcaptchaSecretKey = instance.hcaptchaSecretKey; | ||||
| 		response.recaptchaSecretKey = instance.recaptchaSecretKey; | ||||
| 		response.proxyAccountId = instance.proxyAccountId; | ||||
| 		response.twitterConsumerKey = instance.twitterConsumerKey; | ||||
| 		response.twitterConsumerSecret = instance.twitterConsumerSecret; | ||||
| 		response.githubClientId = instance.githubClientId; | ||||
| 		response.githubClientSecret = instance.githubClientSecret; | ||||
| 		response.discordClientId = instance.discordClientId; | ||||
| 		response.discordClientSecret = instance.discordClientSecret; | ||||
| 		response.summalyProxy = instance.summalyProxy; | ||||
| 		response.email = instance.email; | ||||
| 		response.smtpSecure = instance.smtpSecure; | ||||
| 		response.smtpHost = instance.smtpHost; | ||||
| 		response.smtpPort = instance.smtpPort; | ||||
| 		response.smtpUser = instance.smtpUser; | ||||
| 		response.smtpPass = instance.smtpPass; | ||||
| 		response.swPrivateKey = instance.swPrivateKey; | ||||
| 		response.useObjectStorage = instance.useObjectStorage; | ||||
| 		response.objectStorageBaseUrl = instance.objectStorageBaseUrl; | ||||
| 		response.objectStorageBucket = instance.objectStorageBucket; | ||||
| 		response.objectStoragePrefix = instance.objectStoragePrefix; | ||||
| 		response.objectStorageEndpoint = instance.objectStorageEndpoint; | ||||
| 		response.objectStorageRegion = instance.objectStorageRegion; | ||||
| 		response.objectStoragePort = instance.objectStoragePort; | ||||
| 		response.objectStorageAccessKey = instance.objectStorageAccessKey; | ||||
| 		response.objectStorageSecretKey = instance.objectStorageSecretKey; | ||||
| 		response.objectStorageUseSSL = instance.objectStorageUseSSL; | ||||
| 		response.objectStorageUseProxy = instance.objectStorageUseProxy; | ||||
| 		response.objectStorageSetPublicRead = instance.objectStorageSetPublicRead; | ||||
| 		if (me && me.isAdmin) { | ||||
| 			response.useStarForReactionFallback = instance.useStarForReactionFallback; | ||||
| 			response.pinnedUsers = instance.pinnedUsers; | ||||
| 			response.hiddenTags = instance.hiddenTags; | ||||
| 			response.blockedHosts = instance.blockedHosts; | ||||
| 			response.hcaptchaSecretKey = instance.hcaptchaSecretKey; | ||||
| 			response.recaptchaSecretKey = instance.recaptchaSecretKey; | ||||
| 			response.proxyAccountId = instance.proxyAccountId; | ||||
| 			response.twitterConsumerKey = instance.twitterConsumerKey; | ||||
| 			response.twitterConsumerSecret = instance.twitterConsumerSecret; | ||||
| 			response.githubClientId = instance.githubClientId; | ||||
| 			response.githubClientSecret = instance.githubClientSecret; | ||||
| 			response.discordClientId = instance.discordClientId; | ||||
| 			response.discordClientSecret = instance.discordClientSecret; | ||||
| 			response.summalyProxy = instance.summalyProxy; | ||||
| 			response.email = instance.email; | ||||
| 			response.smtpSecure = instance.smtpSecure; | ||||
| 			response.smtpHost = instance.smtpHost; | ||||
| 			response.smtpPort = instance.smtpPort; | ||||
| 			response.smtpUser = instance.smtpUser; | ||||
| 			response.smtpPass = instance.smtpPass; | ||||
| 			response.swPrivateKey = instance.swPrivateKey; | ||||
| 			response.useObjectStorage = instance.useObjectStorage; | ||||
| 			response.objectStorageBaseUrl = instance.objectStorageBaseUrl; | ||||
| 			response.objectStorageBucket = instance.objectStorageBucket; | ||||
| 			response.objectStoragePrefix = instance.objectStoragePrefix; | ||||
| 			response.objectStorageEndpoint = instance.objectStorageEndpoint; | ||||
| 			response.objectStorageRegion = instance.objectStorageRegion; | ||||
| 			response.objectStoragePort = instance.objectStoragePort; | ||||
| 			response.objectStorageAccessKey = instance.objectStorageAccessKey; | ||||
| 			response.objectStorageSecretKey = instance.objectStorageSecretKey; | ||||
| 			response.objectStorageUseSSL = instance.objectStorageUseSSL; | ||||
| 			response.objectStorageUseProxy = instance.objectStorageUseProxy; | ||||
| 			response.objectStorageSetPublicRead = instance.objectStorageSetPublicRead; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return response; | ||||
|  |  | |||
|  | @ -0,0 +1,54 @@ | |||
| import $ from 'cafy'; | ||||
| import { ID } from '../../../../misc/cafy-id'; | ||||
| import define from '../../define'; | ||||
| import { ClipNotes, Clips } from '../../../../models'; | ||||
| import { getNote } from '../../common/getters'; | ||||
| import { ApiError } from '../../error'; | ||||
| import { In } from 'typeorm'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	tags: ['clips', 'notes'], | ||||
| 
 | ||||
| 	requireCredential: false as const, | ||||
| 
 | ||||
| 	params: { | ||||
| 		noteId: { | ||||
| 			validator: $.type(ID), | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	res: { | ||||
| 		type: 'array' as const, | ||||
| 		optional: false as const, nullable: false as const, | ||||
| 		items: { | ||||
| 			type: 'object' as const, | ||||
| 			optional: false as const, nullable: false as const, | ||||
| 			ref: 'Note', | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	errors: { | ||||
| 		noSuchNote: { | ||||
| 			message: 'No such note.', | ||||
| 			code: 'NO_SUCH_NOTE', | ||||
| 			id: '47db1a1c-b0af-458d-8fb4-986e4efafe1e' | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| export default define(meta, async (ps, me) => { | ||||
| 	const note = await getNote(ps.noteId).catch(e => { | ||||
| 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); | ||||
| 		throw e; | ||||
| 	}); | ||||
| 
 | ||||
| 	const clipNotes = await ClipNotes.find({ | ||||
| 		noteId: note.id, | ||||
| 	}); | ||||
| 
 | ||||
| 	const clips = await Clips.find({ | ||||
| 		id: In(clipNotes.map(x => x.clipId)), | ||||
| 	}); | ||||
| 
 | ||||
| 	return await Promise.all(clips.map(x => Clips.pack(x))); | ||||
| }); | ||||
|  | @ -0,0 +1,29 @@ | |||
| import define from '../../define'; | ||||
| import { Pages } from '../../../../models'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	tags: ['pages'], | ||||
| 
 | ||||
| 	requireCredential: false as const, | ||||
| 
 | ||||
| 	res: { | ||||
| 		type: 'array' as const, | ||||
| 		optional: false as const, nullable: false as const, | ||||
| 		items: { | ||||
| 			type: 'object' as const, | ||||
| 			optional: false as const, nullable: false as const, | ||||
| 			ref: 'Page', | ||||
| 		} | ||||
| 	}, | ||||
| }; | ||||
| 
 | ||||
| export default define(meta, async (ps, me) => { | ||||
| 	const query = Pages.createQueryBuilder('page') | ||||
| 		.where('page.visibility = \'public\'') | ||||
| 		.andWhere('page.likedCount > 0') | ||||
| 		.orderBy('page.likedCount', 'DESC'); | ||||
| 
 | ||||
| 	const pages = await query.take(10).getMany(); | ||||
| 
 | ||||
| 	return await Pages.packMany(pages, me); | ||||
| }); | ||||
|  | @ -299,6 +299,7 @@ router.get('/@:user/pages/:page', async ctx => { | |||
| }); | ||||
| 
 | ||||
| // Clip
 | ||||
| // TODO: 非publicなclipのハンドリング
 | ||||
| router.get('/clips/:clip', async ctx => { | ||||
| 	const clip = await Clips.findOne({ | ||||
| 		id: ctx.params.clip, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue