nanka iroiro (#6847)
* wip * wip * wip * wip * Update ja-JP.yml * wip * wip * wip
This commit is contained in:
		
							parent
							
								
									50e917d232
								
							
						
					
					
						commit
						0044d83801
					
				
					 30 changed files with 558 additions and 183 deletions
				
			
		|  | @ -316,6 +316,8 @@ bannerUrl: "バナー画像のURL" | ||||||
| basicInfo: "基本情報" | basicInfo: "基本情報" | ||||||
| pinnedUsers: "ピン留めユーザー" | pinnedUsers: "ピン留めユーザー" | ||||||
| pinnedUsersDescription: "「みつける」ページなどにピン留めしたいユーザーを改行で区切って記述します。" | pinnedUsersDescription: "「みつける」ページなどにピン留めしたいユーザーを改行で区切って記述します。" | ||||||
|  | pinnedPages: "ピン留めページ" | ||||||
|  | pinnedPagesDescription: "インスタンスのトップページにピン留めしたいページのパスを改行で区切って記述します。" | ||||||
| hcaptcha: "hCaptcha" | hcaptcha: "hCaptcha" | ||||||
| enableHcaptcha: "hCaptchaを有効にする" | enableHcaptcha: "hCaptchaを有効にする" | ||||||
| hcaptchaSiteKey: "サイトキー" | hcaptchaSiteKey: "サイトキー" | ||||||
|  | @ -1117,6 +1119,7 @@ _pages: | ||||||
|   unlike: "いいね解除" |   unlike: "いいね解除" | ||||||
|   my: "自分のページ" |   my: "自分のページ" | ||||||
|   liked: "いいねしたページ" |   liked: "いいねしたページ" | ||||||
|  |   featured: "人気" | ||||||
|   inspector: "インスペクター" |   inspector: "インスペクター" | ||||||
|   contents: "コンテンツ" |   contents: "コンテンツ" | ||||||
|   content: "ページブロック" |   content: "ページブロック" | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								migration/1605585339718-instance-pinned-pages.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								migration/1605585339718-instance-pinned-pages.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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()"/> | 	<MkError v-if="error" @retry="init()"/> | ||||||
| 
 | 
 | ||||||
| 	<div v-show="more && reversed" style="margin-bottom: var(--margin);"> | 	<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">{{ $t('loadMore') }}</template> | ||||||
| 			<template v-if="moreFetching"><MkLoading inline/></template> | 			<template v-if="moreFetching"><MkLoading inline/></template> | ||||||
| 		</button> | 		</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"> | <script lang="ts"> | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent, h, resolveDirective, withDirectives } from 'vue'; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	props: { | 	props: { | ||||||
| 		items: { |  | ||||||
| 			type: Array, |  | ||||||
| 			required: true, |  | ||||||
| 		}, |  | ||||||
| 		value: { | 		value: { | ||||||
| 			required: true, | 			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> | </script> | ||||||
| 
 | 
 | ||||||
| <style lang="scss" scoped> | <style lang="scss"> | ||||||
| .pxhvhrfw { | .pxhvhrfw { | ||||||
| 	display: flex; | 	display: flex; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,7 +9,10 @@ | ||||||
| 	<template #header>Req Viewer</template> | 	<template #header>Req Viewer</template> | ||||||
| 
 | 
 | ||||||
| 	<div class="rlkneywz"> | 	<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 === 'req'">{{ reqStr }}</code> | ||||||
| 		<code v-if="tab === 'res'">{{ resStr }}</code> | 		<code v-if="tab === 'res'">{{ resStr }}</code> | ||||||
|  |  | ||||||
|  | @ -4,7 +4,12 @@ | ||||||
| 		<Fa :icon="faTerminal" style="margin-right: 0.5em;"/>Task Manager | 		<Fa :icon="faTerminal" style="margin-right: 0.5em;"/>Task Manager | ||||||
| 	</template> | 	</template> | ||||||
| 	<div class="qljqmnzj"> | 	<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 class="content"> | ||||||
| 			<div v-if="tab === 'windows'" class="windows" v-follow> | 			<div v-if="tab === 'windows'" class="windows" v-follow> | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</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"/> | 	<XTimeline class="_content _vMargin" src="channel" :channel="channelId" @before="before" @after="after"/> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | @ -1,26 +1,30 @@ | ||||||
| <template> | <template> | ||||||
| <div> | <div> | ||||||
| 	<div class="_section" style="padding: 0;"> | 	<div class="_section" style="padding: 0;" v-if="this.$store.getters.isSignedIn"> | ||||||
| 		<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 }]"/> | 		<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> | ||||||
| 
 | 
 | ||||||
| 	<div class="_section"> | 	<div class="_section"> | ||||||
| 		<div class="_content grwlizim featured" v-if="tab === 'featured'"> | 		<div class="_content grwlizim featured" v-if="tab === 'featured'"> | ||||||
| 			<MkPagination :pagination="featuredPagination" #default="{items}"> | 			<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> | 			</MkPagination> | ||||||
| 		</div> | 		</div> | ||||||
| 
 | 
 | ||||||
| 		<div class="_content grwlizim following" v-if="tab === 'following'"> | 		<div class="_content grwlizim following" v-if="tab === 'following'"> | ||||||
| 			<MkPagination :pagination="followingPagination" #default="{items}"> | 			<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> | 			</MkPagination> | ||||||
| 		</div> | 		</div> | ||||||
| 
 | 
 | ||||||
| 		<div class="_content grwlizim owned" v-if="tab === 'owned'"> | 		<div class="_content grwlizim owned" v-if="tab === 'owned'"> | ||||||
| 			<MkButton class="new" @click="create()"><Fa :icon="faPlus"/></MkButton> | 			<MkButton class="new" @click="create()"><Fa :icon="faPlus"/></MkButton> | ||||||
| 			<MkPagination :pagination="ownedPagination" #default="{items}"> | 			<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> | 			</MkPagination> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
|  | @ -44,7 +48,11 @@ export default defineComponent({ | ||||||
| 		return { | 		return { | ||||||
| 			INFO: { | 			INFO: { | ||||||
| 				title: this.$t('channel'), | 				title: this.$t('channel'), | ||||||
| 				icon: faSatelliteDish | 				icon: faSatelliteDish, | ||||||
|  | 				action: { | ||||||
|  | 					icon: faPlus, | ||||||
|  | 					handler: this.create | ||||||
|  | 				} | ||||||
| 			}, | 			}, | ||||||
| 			tab: 'featured', | 			tab: 'featured', | ||||||
| 			featuredPagination: { | 			featuredPagination: { | ||||||
|  | @ -69,23 +77,3 @@ export default defineComponent({ | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| </script> | </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> | <template> | ||||||
| <div class="mk-instance-emojis"> | <div class="mk-instance-emojis"> | ||||||
| 	<div class="_section" style="padding: 0;"> | 	<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> | ||||||
| 
 | 
 | ||||||
| 	<div class="_section"> | 	<div class="_section"> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| <template> | <template> | ||||||
| <div v-if="meta"> | <div v-if="meta" class="_section"> | ||||||
| 	<section class="_section info"> | 	<section class="_card _vMargin"> | ||||||
| 		<div class="_title"><Fa :icon="faInfoCircle"/> {{ $t('basicInfo') }}</div> | 		<div class="_title"><Fa :icon="faInfoCircle"/> {{ $t('basicInfo') }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkInput v-model:value="name">{{ $t('instanceName') }}</MkInput> | 			<MkInput v-model:value="name">{{ $t('instanceName') }}</MkInput> | ||||||
|  | @ -16,7 +16,7 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section class="_section info"> | 	<section class="_card _vMargin"> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkInput v-model:value="maxNoteTextLength" type="number" :save="() => save()"><template #icon><Fa :icon="faPencilAlt"/></template>{{ $t('maxNoteTextLength') }}</MkInput> | 			<MkInput v-model:value="maxNoteTextLength" type="number" :save="() => save()"><template #icon><Fa :icon="faPencilAlt"/></template>{{ $t('maxNoteTextLength') }}</MkInput> | ||||||
| 		</div> | 		</div> | ||||||
|  | @ -30,7 +30,7 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section class="_section info"> | 	<section class="_card _vMargin"> | ||||||
| 		<div class="_title"><Fa :icon="faUser"/> {{ $t('registration') }}</div> | 		<div class="_title"><Fa :icon="faUser"/> {{ $t('registration') }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkSwitch v-model:value="enableRegistration" @update:value="save()">{{ $t('enableRegistration') }}</MkSwitch> | 			<MkSwitch v-model:value="enableRegistration" @update:value="save()">{{ $t('enableRegistration') }}</MkSwitch> | ||||||
|  | @ -38,7 +38,7 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section class="_section"> | 	<section class="_card _vMargin"> | ||||||
| 		<div class="_title"><Fa :icon="faShieldAlt"/> {{ $t('hcaptcha') }}</div> | 		<div class="_title"><Fa :icon="faShieldAlt"/> {{ $t('hcaptcha') }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkSwitch v-model:value="enableHcaptcha">{{ $t('enableHcaptcha') }}</MkSwitch> | 			<MkSwitch v-model:value="enableHcaptcha">{{ $t('enableHcaptcha') }}</MkSwitch> | ||||||
|  | @ -56,7 +56,7 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section class="_section"> | 	<section class="_card _vMargin"> | ||||||
| 		<div class="_title"><Fa :icon="faShieldAlt"/> {{ $t('recaptcha') }}</div> | 		<div class="_title"><Fa :icon="faShieldAlt"/> {{ $t('recaptcha') }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkSwitch v-model:value="enableRecaptcha" ref="enableRecaptcha">{{ $t('enableRecaptcha') }}</MkSwitch> | 			<MkSwitch v-model:value="enableRecaptcha" ref="enableRecaptcha">{{ $t('enableRecaptcha') }}</MkSwitch> | ||||||
|  | @ -74,7 +74,7 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section class="_section"> | 	<section class="_card _vMargin"> | ||||||
| 		<div class="_title"><Fa :icon="faEnvelope" /> {{ $t('emailConfig') }}</div> | 		<div class="_title"><Fa :icon="faEnvelope" /> {{ $t('emailConfig') }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkSwitch v-model:value="enableEmail" @update:value="save()">{{ $t('enableEmail') }}<template #desc>{{ $t('emailConfigInfo') }}</template></MkSwitch> | 			<MkSwitch v-model:value="enableEmail" @update:value="save()">{{ $t('enableEmail') }}<template #desc>{{ $t('emailConfigInfo') }}</template></MkSwitch> | ||||||
|  | @ -97,7 +97,7 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section class="_section"> | 	<section class="_card _vMargin"> | ||||||
| 		<div class="_title"><Fa :icon="faBolt"/> {{ $t('serviceworker') }}</div> | 		<div class="_title"><Fa :icon="faBolt"/> {{ $t('serviceworker') }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkSwitch v-model:value="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworkerInfo') }}</template></MkSwitch> | 			<MkSwitch v-model:value="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworkerInfo') }}</template></MkSwitch> | ||||||
|  | @ -113,7 +113,7 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section class="_section"> | 	<section class="_card _vMargin"> | ||||||
| 		<div class="_title"><Fa :icon="faThumbtack"/> {{ $t('pinnedUsers') }}</div> | 		<div class="_title"><Fa :icon="faThumbtack"/> {{ $t('pinnedUsers') }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkTextarea v-model:value="pinnedUsers"> | 			<MkTextarea v-model:value="pinnedUsers"> | ||||||
|  | @ -125,7 +125,19 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</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="_title"><Fa :icon="faCloud"/> {{ $t('files') }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkSwitch v-model:value="cacheRemoteFiles">{{ $t('cacheRemoteFiles') }}<template #desc>{{ $t('cacheRemoteFilesDescription') }}</template></MkSwitch> | 			<MkSwitch v-model:value="cacheRemoteFiles">{{ $t('cacheRemoteFiles') }}<template #desc>{{ $t('cacheRemoteFilesDescription') }}</template></MkSwitch> | ||||||
|  | @ -138,7 +150,7 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section class="_section"> | 	<section class="_card _vMargin"> | ||||||
| 		<div class="_title"><Fa :icon="faCloud"/> {{ $t('objectStorage') }}</div> | 		<div class="_title"><Fa :icon="faCloud"/> {{ $t('objectStorage') }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkSwitch v-model:value="useObjectStorage">{{ $t('useObjectStorage') }}</MkSwitch> | 			<MkSwitch v-model:value="useObjectStorage">{{ $t('useObjectStorage') }}</MkSwitch> | ||||||
|  | @ -166,7 +178,7 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section class="_section"> | 	<section class="_card _vMargin"> | ||||||
| 		<div class="_title"><Fa :icon="faGhost"/> {{ $t('proxyAccount') }}</div> | 		<div class="_title"><Fa :icon="faGhost"/> {{ $t('proxyAccount') }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkInput :value="proxyAccount ? proxyAccount.username : null" disabled><template #prefix>@</template>{{ $t('proxyAccount') }}<template #desc>{{ $t('proxyAccountDescription') }}</template></MkInput> | 			<MkInput :value="proxyAccount ? proxyAccount.username : null" disabled><template #prefix>@</template>{{ $t('proxyAccount') }}<template #desc>{{ $t('proxyAccountDescription') }}</template></MkInput> | ||||||
|  | @ -174,7 +186,7 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section class="_section"> | 	<section class="_card _vMargin"> | ||||||
| 		<div class="_title"><Fa :icon="faBan"/> {{ $t('blockedInstances') }}</div> | 		<div class="_title"><Fa :icon="faBan"/> {{ $t('blockedInstances') }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkTextarea v-model:value="blockedHosts"> | 			<MkTextarea v-model:value="blockedHosts"> | ||||||
|  | @ -186,7 +198,7 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section class="_section"> | 	<section class="_card _vMargin"> | ||||||
| 		<div class="_title"><Fa :icon="faShareAlt"/> {{ $t('integration') }}</div> | 		<div class="_title"><Fa :icon="faShareAlt"/> {{ $t('integration') }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<header><Fa :icon="faTwitter"/> Twitter</header> | 			<header><Fa :icon="faTwitter"/> Twitter</header> | ||||||
|  | @ -220,7 +232,7 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section class="_section"> | 	<section class="_card _vMargin"> | ||||||
| 		<div class="_title"><Fa :icon="faArchway" /> Summaly Proxy</div> | 		<div class="_title"><Fa :icon="faArchway" /> Summaly Proxy</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkInput v-model:value="summalyProxy">URL</MkInput> | 			<MkInput v-model:value="summalyProxy">URL</MkInput> | ||||||
|  | @ -260,6 +272,7 @@ export default defineComponent({ | ||||||
| 				title: this.$t('instance'), | 				title: this.$t('instance'), | ||||||
| 				icon: faCog, | 				icon: faCog, | ||||||
| 			}, | 			}, | ||||||
|  | 			meta: null, | ||||||
| 			url, | 			url, | ||||||
| 			proxyAccount: null, | 			proxyAccount: null, | ||||||
| 			proxyAccountId: null, | 			proxyAccountId: null, | ||||||
|  | @ -269,6 +282,7 @@ export default defineComponent({ | ||||||
| 			remoteDriveCapacityMb: 0, | 			remoteDriveCapacityMb: 0, | ||||||
| 			blockedHosts: '', | 			blockedHosts: '', | ||||||
| 			pinnedUsers: '', | 			pinnedUsers: '', | ||||||
|  | 			pinnedPages: '', | ||||||
| 			maintainerName: null, | 			maintainerName: null, | ||||||
| 			maintainerEmail: null, | 			maintainerEmail: null, | ||||||
| 			name: null, | 			name: null, | ||||||
|  | @ -323,13 +337,9 @@ export default defineComponent({ | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	computed: { | 	async created() { | ||||||
| 		meta() { | 		this.meta = await os.api('meta', { detail: true }); | ||||||
| 			return this.$store.state.instance.meta; |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 
 | 
 | ||||||
| 	created() { |  | ||||||
| 		this.name = this.meta.name; | 		this.name = this.meta.name; | ||||||
| 		this.description = this.meta.description; | 		this.description = this.meta.description; | ||||||
| 		this.tosUrl = this.meta.tosUrl; | 		this.tosUrl = this.meta.tosUrl; | ||||||
|  | @ -356,6 +366,7 @@ export default defineComponent({ | ||||||
| 		this.remoteDriveCapacityMb = this.meta.driveCapacityPerRemoteUserMb; | 		this.remoteDriveCapacityMb = this.meta.driveCapacityPerRemoteUserMb; | ||||||
| 		this.blockedHosts = this.meta.blockedHosts.join('\n'); | 		this.blockedHosts = this.meta.blockedHosts.join('\n'); | ||||||
| 		this.pinnedUsers = this.meta.pinnedUsers.join('\n'); | 		this.pinnedUsers = this.meta.pinnedUsers.join('\n'); | ||||||
|  | 		this.pinnedPages = this.meta.pinnedPages.join('\n'); | ||||||
| 		this.enableServiceWorker = this.meta.enableServiceWorker; | 		this.enableServiceWorker = this.meta.enableServiceWorker; | ||||||
| 		this.swPublicKey = this.meta.swPublickey; | 		this.swPublicKey = this.meta.swPublickey; | ||||||
| 		this.swPrivateKey = this.meta.swPrivateKey; | 		this.swPrivateKey = this.meta.swPrivateKey; | ||||||
|  | @ -506,6 +517,7 @@ export default defineComponent({ | ||||||
| 				remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10), | 				remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10), | ||||||
| 				blockedHosts: this.blockedHosts.split('\n') || [], | 				blockedHosts: this.blockedHosts.split('\n') || [], | ||||||
| 				pinnedUsers: this.pinnedUsers ? this.pinnedUsers.split('\n') : [], | 				pinnedUsers: this.pinnedUsers ? this.pinnedUsers.split('\n') : [], | ||||||
|  | 				pinnedPages: this.pinnedPages ? this.pinnedPages.split('\n') : [], | ||||||
| 				enableServiceWorker: this.enableServiceWorker, | 				enableServiceWorker: this.enableServiceWorker, | ||||||
| 				swPublicKey: this.swPublicKey, | 				swPublicKey: this.swPublicKey, | ||||||
| 				swPrivateKey: this.swPrivateKey, | 				swPrivateKey: this.swPrivateKey, | ||||||
|  |  | ||||||
|  | @ -1,7 +1,11 @@ | ||||||
| <template> | <template> | ||||||
| <div class=""> | <div class=""> | ||||||
| 	<div class="_section" style="padding: 0;"> | 	<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> | ||||||
| 
 | 
 | ||||||
| 	<div class="_section"> | 	<div class="_section"> | ||||||
|  |  | ||||||
|  | @ -1,21 +1,31 @@ | ||||||
| <template> | <template> | ||||||
| <div class="fcuexfpr"> | <div class="fcuexfpr"> | ||||||
| 	<div v-if="note" class="note"> | 	<div v-if="note" class="note"> | ||||||
| 		<div class="_section"> | 		<div class="_section" v-if="showNext"> | ||||||
| 			<XNotes v-if="showNext" class="_content" :pagination="next"/> | 			<XNotes class="_content" :pagination="next"/> | ||||||
| 			<MkButton v-else-if="hasNext" class="load _content" @click="showNext = true"><Fa :icon="faChevronUp"/></MkButton> |  | ||||||
| 		</div> | 		</div> | ||||||
| 
 | 
 | ||||||
| 		<div class="_section"> | 		<div class="_section main"> | ||||||
| 			<div class="_content"> | 			<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"/> | 				<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"/> | 				<XNote v-model:note="note" :key="note.id" :detail="true" class="_vMargin"/> | ||||||
| 			</div> | 			</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> | ||||||
| 
 | 
 | ||||||
| 		<div class="_section"> | 		<div class="_section" v-if="showPrev"> | ||||||
| 			<XNotes v-if="showPrev" class="_content" :pagination="prev"/> | 			<XNotes class="_content" :pagination="prev"/> | ||||||
| 			<MkButton v-else-if="hasPrev" class="load _content" @click="showPrev = true"><Fa :icon="faChevronDown"/></MkButton> |  | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| 
 | 
 | ||||||
|  | @ -28,7 +38,6 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { computed, defineComponent } from 'vue'; | import { computed, defineComponent } from 'vue'; | ||||||
| import { faChevronUp, faChevronDown } from '@fortawesome/free-solid-svg-icons'; | import { faChevronUp, faChevronDown } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import Progress from '@/scripts/loading'; |  | ||||||
| import XNote from '@/components/note.vue'; | import XNote from '@/components/note.vue'; | ||||||
| import XNotes from '@/components/notes.vue'; | import XNotes from '@/components/notes.vue'; | ||||||
| import MkRemoteCaution from '@/components/remote-caution.vue'; | import MkRemoteCaution from '@/components/remote-caution.vue'; | ||||||
|  | @ -55,6 +64,7 @@ export default defineComponent({ | ||||||
| 				avatar: this.note.user, | 				avatar: this.note.user, | ||||||
| 			} : null), | 			} : null), | ||||||
| 			note: null, | 			note: null, | ||||||
|  | 			clips: null, | ||||||
| 			hasPrev: false, | 			hasPrev: false, | ||||||
| 			hasNext: false, | 			hasNext: false, | ||||||
| 			showPrev: false, | 			showPrev: false, | ||||||
|  | @ -88,11 +98,13 @@ export default defineComponent({ | ||||||
| 	}, | 	}, | ||||||
| 	methods: { | 	methods: { | ||||||
| 		fetch() { | 		fetch() { | ||||||
| 			Progress.start(); |  | ||||||
| 			os.api('notes/show', { | 			os.api('notes/show', { | ||||||
| 				noteId: this.noteId | 				noteId: this.noteId | ||||||
| 			}).then(note => { | 			}).then(note => { | ||||||
| 				Promise.all([ | 				Promise.all([ | ||||||
|  | 					os.api('notes/clips', { | ||||||
|  | 						noteId: note.id, | ||||||
|  | 					}), | ||||||
| 					os.api('users/notes', { | 					os.api('users/notes', { | ||||||
| 						userId: note.userId, | 						userId: note.userId, | ||||||
| 						untilId: note.id, | 						untilId: note.id, | ||||||
|  | @ -103,15 +115,14 @@ export default defineComponent({ | ||||||
| 						sinceId: note.id, | 						sinceId: note.id, | ||||||
| 						limit: 1, | 						limit: 1, | ||||||
| 					}), | 					}), | ||||||
| 				]).then(([prev, next]) => { | 				]).then(([clips, prev, next]) => { | ||||||
|  | 					this.clips = clips; | ||||||
| 					this.hasPrev = prev.length !== 0; | 					this.hasPrev = prev.length !== 0; | ||||||
| 					this.hasNext = next.length !== 0; | 					this.hasNext = next.length !== 0; | ||||||
| 					this.note = note; | 					this.note = note; | ||||||
| 				}); | 				}); | ||||||
| 			}).catch(e => { | 			}).catch(e => { | ||||||
| 				this.error = e; | 				this.error = e; | ||||||
| 			}).finally(() => { |  | ||||||
| 				Progress.done(); |  | ||||||
| 			}); | 			}); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -121,10 +132,46 @@ export default defineComponent({ | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .fcuexfpr { | .fcuexfpr { | ||||||
| 	> .note { | 	> .note { | ||||||
| 		> ._section { | 		> .main { | ||||||
| 			> .load { | 			> .load { | ||||||
| 				min-width: 0; | 				min-width: 0; | ||||||
| 				border-radius: 999px; | 				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', | 						type: 'success', | ||||||
| 						text: this.$t('_pages.created') | 						text: this.$t('_pages.created') | ||||||
| 					}); | 					}); | ||||||
| 					this.$router.push(`/my/pages/edit/${this.pageId}`); | 					this.$router.push(`/pages/edit/${this.pageId}`); | ||||||
| 				}).catch(onError); | 				}).catch(onError); | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | @ -296,7 +296,7 @@ export default defineComponent({ | ||||||
| 						type: 'success', | 						type: 'success', | ||||||
| 						text: this.$t('_pages.deleted') | 						text: this.$t('_pages.deleted') | ||||||
| 					}); | 					}); | ||||||
| 					this.$router.push(`/my/pages`); | 					this.$router.push(`/pages`); | ||||||
| 				}); | 				}); | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkA :to="`./${page.name}/view-source`" class="link">{{ $t('_pages.viewSource') }}</MkA> | 			<MkA :to="`./${page.name}/view-source`" class="link">{{ $t('_pages.viewSource') }}</MkA> | ||||||
| 			<template v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId"> | 			<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-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> | 				<button v-else @click="pin(true)" class="link _textButton">{{ $t('pin') }}</button> | ||||||
| 			</template> | 			</template> | ||||||
|  |  | ||||||
|  | @ -1,8 +1,18 @@ | ||||||
| <template> | <template> | ||||||
| <div> | <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="_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'"> | 		<div class="rknalgpo _content my" v-if="tab === 'my'"> | ||||||
| 			<MkButton class="new" @click="create()"><Fa :icon="faPlus"/></MkButton> | 			<MkButton class="new" @click="create()"><Fa :icon="faPlus"/></MkButton> | ||||||
| 			<MkPagination :pagination="myPagesPagination" #default="{items}"> | 			<MkPagination :pagination="myPagesPagination" #default="{items}"> | ||||||
|  | @ -21,7 +31,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent } from 'vue'; | 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 { faStickyNote, faHeart } from '@fortawesome/free-regular-svg-icons'; | ||||||
| import MkPagePreview from '@/components/page-preview.vue'; | import MkPagePreview from '@/components/page-preview.vue'; | ||||||
| import MkPagination from '@/components/ui/pagination.vue'; | import MkPagination from '@/components/ui/pagination.vue'; | ||||||
|  | @ -42,7 +52,11 @@ export default defineComponent({ | ||||||
| 					handler: this.create | 					handler: this.create | ||||||
| 				} | 				} | ||||||
| 			}, | 			}, | ||||||
| 			tab: 'my', | 			tab: 'featured', | ||||||
|  | 			featuredPagesPagination: { | ||||||
|  | 				endpoint: 'pages/featured', | ||||||
|  | 				noPaging: true, | ||||||
|  | 			}, | ||||||
| 			myPagesPagination: { | 			myPagesPagination: { | ||||||
| 				endpoint: 'i/pages', | 				endpoint: 'i/pages', | ||||||
| 				limit: 5, | 				limit: 5, | ||||||
|  | @ -51,12 +65,12 @@ export default defineComponent({ | ||||||
| 				endpoint: 'i/page-likes', | 				endpoint: 'i/page-likes', | ||||||
| 				limit: 5, | 				limit: 5, | ||||||
| 			}, | 			}, | ||||||
| 			faStickyNote, faPlus, faEdit, faHeart | 			faStickyNote, faPlus, faEdit, faHeart, faFireAlt | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 	methods: { | 	methods: { | ||||||
| 		create() { | 		create() { | ||||||
| 			this.$router.push(`/my/pages/new`); | 			this.$router.push(`/pages/new`); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -1,6 +1,9 @@ | ||||||
| <template> | <template> | ||||||
| <section class="rrfwjxfl _section"> | <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'"> | 	<div class="_content" v-if="tab === 'mute'"> | ||||||
| 		<MkPagination :pagination="mutingPagination" class="muting"> | 		<MkPagination :pagination="mutingPagination" class="muting"> | ||||||
| 			<template #empty><MkInfo>{{ $t('noUsers') }}</MkInfo></template> | 			<template #empty><MkInfo>{{ $t('noUsers') }}</MkInfo></template> | ||||||
|  |  | ||||||
|  | @ -1,7 +1,10 @@ | ||||||
| <template> | <template> | ||||||
| <div class="_section"> | <div class="_section"> | ||||||
| 	<div class="_card"> | 	<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 class="_content"> | ||||||
| 			<div v-show="tab === 'soft'"> | 			<div v-show="tab === 'soft'"> | ||||||
| 				<MkInfo>{{ $t('_wordMute.softDescription') }}</MkInfo> | 				<MkInfo>{{ $t('_wordMute.softDescription') }}</MkInfo> | ||||||
|  |  | ||||||
							
								
								
									
										141
									
								
								src/client/pages/welcome.entrance.block.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								src/client/pages/welcome.entrance.block.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -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> | <template> | ||||||
| <div class="rsqzvsbo"> | <div class="rsqzvsbo _section" v-if="meta"> | ||||||
| 	<div class="_section"> | 	<div class="about"> | ||||||
| 		<div class="_content _panel about" v-if="meta"> | 		<h1>{{ instanceName }}</h1> | ||||||
| 			<div class="body"> | 		<div class="desc" v-html="meta.description || $t('introMisskey')"></div> | ||||||
| 				<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="signup()" style="display: inline-block; margin-right: 16px;" primary>{{ $t('signup') }}</MkButton> | 		<MkButton @click="signin()" style="display: inline-block;">{{ $t('login') }}</MkButton> | ||||||
| 				<MkButton @click="signin()" style="display: inline-block;">{{ $t('login') }}</MkButton> |  | ||||||
| 			</div> |  | ||||||
| 		</div> |  | ||||||
| 	</div> | 	</div> | ||||||
| 	<div class="_section"> | 	<div class="blocks"> | ||||||
| 		<div class="_content"> | 		<XBlock class="block" v-for="path in meta.pinnedPages" :initial-path="path" :key="path"/> | ||||||
| 			<XNotes :pagination="featuredPagination"/> |  | ||||||
| 		</div> |  | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  | @ -24,33 +19,30 @@ import XSigninDialog from '@/components/signin-dialog.vue'; | ||||||
| import XSignupDialog from '@/components/signup-dialog.vue'; | import XSignupDialog from '@/components/signup-dialog.vue'; | ||||||
| import MkButton from '@/components/ui/button.vue'; | import MkButton from '@/components/ui/button.vue'; | ||||||
| import XNotes from '@/components/notes.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'; | import * as os from '@/os'; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	components: { | 	components: { | ||||||
| 		MkButton, | 		MkButton, | ||||||
| 		XNotes, | 		XNotes, | ||||||
|  | 		XBlock, | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			featuredPagination: { |  | ||||||
| 				endpoint: 'notes/featured', |  | ||||||
| 				limit: 10, |  | ||||||
| 				noPaging: true, |  | ||||||
| 			}, |  | ||||||
| 			host: toUnicode(host), | 			host: toUnicode(host), | ||||||
|  | 			instanceName, | ||||||
|  | 			meta: null, | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	computed: { |  | ||||||
| 		meta() { |  | ||||||
| 			return this.$store.state.instance.meta; |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	created() { | 	created() { | ||||||
|  | 		os.api('meta', { detail: true }).then(meta => { | ||||||
|  | 			this.meta = meta; | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
| 		os.api('stats').then(stats => { | 		os.api('stats').then(stats => { | ||||||
| 			this.stats = stats; | 			this.stats = stats; | ||||||
| 		}); | 		}); | ||||||
|  | @ -74,15 +66,42 @@ export default defineComponent({ | ||||||
| 
 | 
 | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .rsqzvsbo { | .rsqzvsbo { | ||||||
| 	> ._section { | 	text-align: center; | ||||||
| 		> .about { |  | ||||||
| 			> .body { |  | ||||||
| 				padding: 32px; |  | ||||||
| 
 | 
 | ||||||
| 				@media (max-width: 500px) { | 	> .about { | ||||||
| 					padding: 16px; | 		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 XSetup from './welcome.setup.vue'; | ||||||
| import XEntrance from './welcome.entrance.vue'; | import XEntrance from './welcome.entrance.vue'; | ||||||
| import { instanceName } from '@/config'; | import { instanceName } from '@/config'; | ||||||
|  | import * as os from '@/os'; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	components: { | 	components: { | ||||||
|  | @ -20,16 +21,17 @@ export default defineComponent({ | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			INFO: { | 			INFO: { | ||||||
| 				title: instanceName || 'Misskey', | 				title: instanceName, | ||||||
| 				icon: null | 				icon: null | ||||||
| 			}, | 			}, | ||||||
|  | 			meta: null | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	computed: { | 	created() { | ||||||
| 		meta() { | 		os.api('meta', { detail: true }).then(meta => { | ||||||
| 			return this.$store.state.instance.meta; | 			this.meta = meta; | ||||||
| 		}, | 		}); | ||||||
| 	}, | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -33,6 +33,9 @@ export const router = createRouter({ | ||||||
| 		{ path: '/explore', component: page('explore') }, | 		{ path: '/explore', component: page('explore') }, | ||||||
| 		{ path: '/explore/tags/:tag', props: true, component: page('explore') }, | 		{ path: '/explore/tags/:tag', props: true, component: page('explore') }, | ||||||
| 		{ path: '/search', component: page('search') }, | 		{ 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', component: page('channels') }, | ||||||
| 		{ path: '/channels/new', component: page('channel-editor') }, | 		{ path: '/channels/new', component: page('channel-editor') }, | ||||||
| 		{ path: '/channels/:channelId/edit', component: page('channel-editor'), props: true }, | 		{ 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/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', name: 'drive', component: page('drive') }, | ||||||
| 		{ path: '/my/drive/folder/:folder', 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/follow-requests', component: page('follow-requests') }, | ||||||
| 		{ path: '/my/lists', component: page('my-lists/index') }, | 		{ path: '/my/lists', component: page('my-lists/index') }, | ||||||
| 		{ path: '/my/lists/:list', component: page('my-lists/list') }, | 		{ path: '/my/lists/:list', component: page('my-lists/list') }, | ||||||
|  |  | ||||||
|  | @ -96,8 +96,7 @@ export const sidebarDef = { | ||||||
| 	pages: { | 	pages: { | ||||||
| 		title: 'pages', | 		title: 'pages', | ||||||
| 		icon: faFileAlt, | 		icon: faFileAlt, | ||||||
| 		show: computed(() => store.getters.isSignedIn), | 		to: '/pages', | ||||||
| 		to: '/my/pages', |  | ||||||
| 	}, | 	}, | ||||||
| 	clips: { | 	clips: { | ||||||
| 		title: 'clip', | 		title: 'clip', | ||||||
|  |  | ||||||
|  | @ -7,12 +7,12 @@ | ||||||
| 		<MkA class="link" to="/about">{{ $t('aboutX', { x: instanceName }) }}</MkA> | 		<MkA class="link" to="/about">{{ $t('aboutX', { x: instanceName }) }}</MkA> | ||||||
| 	</header> | 	</header> | ||||||
| 
 | 
 | ||||||
| 	<div class="banner" :style="{ backgroundImage: `url(${ $store.state.instance.meta.bannerUrl })` }"> | 	<div class="banner" :class="{ asBg: $route.path === '/' }" :style="{ backgroundImage: `url(${ $store.state.instance.meta.bannerUrl })` }"> | ||||||
| 		<h1>{{ instanceName }}</h1> | 		<h1 v-if="$route.path !== '/'">{{ instanceName }}</h1> | ||||||
| 	</div> | 	</div> | ||||||
| 
 | 
 | ||||||
| 	<div class="contents" ref="contents" :class="{ wallpaper }"> | 	<div class="contents" ref="contents" :class="{ wallpaper }"> | ||||||
| 		<header class="header" ref="header"> | 		<header class="header" ref="header" v-show="$route.path !== '/'"> | ||||||
| 			<XHeader :info="pageInfo"/> | 			<XHeader :info="pageInfo"/> | ||||||
| 		</header> | 		</header> | ||||||
| 		<main ref="main"> | 		<main ref="main"> | ||||||
|  | @ -116,11 +116,10 @@ export default defineComponent({ | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .mk-app { | .mk-app { | ||||||
| 	min-height: 100vh; | 	min-height: 100vh; | ||||||
| 	max-width: 1300px; |  | ||||||
| 	margin: 0 auto; |  | ||||||
| 	box-shadow: 1px 0 var(--divider), -1px 0 var(--divider); |  | ||||||
| 
 | 
 | ||||||
| 	> header { | 	> header { | ||||||
|  | 		position: relative; | ||||||
|  | 		z-index: 1; | ||||||
| 		background: var(--panel); | 		background: var(--panel); | ||||||
| 		padding: 0 16px; | 		padding: 0 16px; | ||||||
| 		text-align: center; | 		text-align: center; | ||||||
|  | @ -145,6 +144,12 @@ export default defineComponent({ | ||||||
| 		background-size: cover; | 		background-size: cover; | ||||||
| 		background-position: center; | 		background-position: center; | ||||||
| 
 | 
 | ||||||
|  | 		&.asBg { | ||||||
|  | 			position: absolute; | ||||||
|  | 			left: 0; | ||||||
|  | 			height: 320px; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		&:after { | 		&:after { | ||||||
| 			content: ""; | 			content: ""; | ||||||
| 			display: block; | 			display: block; | ||||||
|  | @ -166,6 +171,9 @@ export default defineComponent({ | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	> .contents { | 	> .contents { | ||||||
|  | 		position: relative; | ||||||
|  | 		z-index: 1; | ||||||
|  | 
 | ||||||
| 		> .header { | 		> .header { | ||||||
| 			position: sticky; | 			position: sticky; | ||||||
| 			top: 0; | 			top: 0; | ||||||
|  |  | ||||||
|  | @ -76,6 +76,11 @@ export class Meta { | ||||||
| 	}) | 	}) | ||||||
| 	public blockedHosts: string[]; | 	public blockedHosts: string[]; | ||||||
| 
 | 
 | ||||||
|  | 	@Column('varchar', { | ||||||
|  | 		length: 512, array: true, default: '{"/announcements", "/featured", "/channels", "/explore", "/games/reversi", "/about-misskey"}' | ||||||
|  | 	}) | ||||||
|  | 	public pinnedPages: string[]; | ||||||
|  | 
 | ||||||
| 	@Column('varchar', { | 	@Column('varchar', { | ||||||
| 		length: 512, | 		length: 512, | ||||||
| 		nullable: true, | 		nullable: true, | ||||||
|  |  | ||||||
|  | @ -85,8 +85,9 @@ export class PageRepository extends Repository<Page> { | ||||||
| 
 | 
 | ||||||
| 	public packMany( | 	public packMany( | ||||||
| 		pages: Page[], | 		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: { | 		langs: { | ||||||
| 			validator: $.optional.arr($.str), | 			validator: $.optional.arr($.str), | ||||||
| 			desc: { | 			desc: { | ||||||
|  | @ -537,6 +541,10 @@ export default define(meta, async (ps, me) => { | ||||||
| 		set.langs = ps.langs.filter(Boolean); | 		set.langs = ps.langs.filter(Boolean); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if (Array.isArray(ps.pinnedPages)) { | ||||||
|  | 		set.pinnedPages = ps.pinnedPages.filter(Boolean); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if (ps.summalyProxy !== undefined) { | 	if (ps.summalyProxy !== undefined) { | ||||||
| 		set.summalyProxy = ps.summalyProxy; | 		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 = { | 	const response: any = { | ||||||
| 		maintainerName: instance.maintainerName, | 		maintainerName: instance.maintainerName, | ||||||
| 		maintainerEmail: instance.maintainerEmail, | 		maintainerEmail: instance.maintainerEmail, | ||||||
|  | @ -122,8 +120,6 @@ export default define(meta, async (ps, me) => { | ||||||
| 		disableGlobalTimeline: instance.disableGlobalTimeline, | 		disableGlobalTimeline: instance.disableGlobalTimeline, | ||||||
| 		driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, | 		driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, | ||||||
| 		driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, | 		driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, | ||||||
| 		cacheRemoteFiles: instance.cacheRemoteFiles, |  | ||||||
| 		proxyRemoteFiles: instance.proxyRemoteFiles, |  | ||||||
| 		enableHcaptcha: instance.enableHcaptcha, | 		enableHcaptcha: instance.enableHcaptcha, | ||||||
| 		hcaptchaSiteKey: instance.hcaptchaSiteKey, | 		hcaptchaSiteKey: instance.hcaptchaSiteKey, | ||||||
| 		enableRecaptcha: instance.enableRecaptcha, | 		enableRecaptcha: instance.enableRecaptcha, | ||||||
|  | @ -135,9 +131,6 @@ export default define(meta, async (ps, me) => { | ||||||
| 		iconUrl: instance.iconUrl, | 		iconUrl: instance.iconUrl, | ||||||
| 		maxNoteTextLength: Math.min(instance.maxNoteTextLength, DB_MAX_NOTE_TEXT_LENGTH), | 		maxNoteTextLength: Math.min(instance.maxNoteTextLength, DB_MAX_NOTE_TEXT_LENGTH), | ||||||
| 		emojis: await Emojis.packMany(emojis), | 		emojis: await Emojis.packMany(emojis), | ||||||
| 		requireSetup: (await Users.count({ |  | ||||||
| 			host: null, |  | ||||||
| 		})) === 0, |  | ||||||
| 		enableEmail: instance.enableEmail, | 		enableEmail: instance.enableEmail, | ||||||
| 
 | 
 | ||||||
| 		enableTwitterIntegration: instance.enableTwitterIntegration, | 		enableTwitterIntegration: instance.enableTwitterIntegration, | ||||||
|  | @ -146,10 +139,20 @@ export default define(meta, async (ps, me) => { | ||||||
| 
 | 
 | ||||||
| 		enableServiceWorker: instance.enableServiceWorker, | 		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) { | 	if (ps.detail) { | ||||||
|  | 		const proxyAccount = instance.proxyAccountId ? await Users.pack(instance.proxyAccountId).catch(() => null) : null; | ||||||
|  | 
 | ||||||
|  | 		response.proxyAccountName = proxyAccount ? proxyAccount.username : null; | ||||||
| 		response.features = { | 		response.features = { | ||||||
| 			registration: !instance.disableRegistration, | 			registration: !instance.disableRegistration, | ||||||
| 			localTimeLine: !instance.disableLocalTimeline, | 			localTimeLine: !instance.disableLocalTimeline, | ||||||
|  | @ -164,42 +167,42 @@ export default define(meta, async (ps, me) => { | ||||||
| 			serviceWorker: instance.enableServiceWorker, | 			serviceWorker: instance.enableServiceWorker, | ||||||
| 			miauth: true, | 			miauth: true, | ||||||
| 		}; | 		}; | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	if (me && me.isAdmin) { | 		if (me && me.isAdmin) { | ||||||
| 		response.useStarForReactionFallback = instance.useStarForReactionFallback; | 			response.useStarForReactionFallback = instance.useStarForReactionFallback; | ||||||
| 		response.pinnedUsers = instance.pinnedUsers; | 			response.pinnedUsers = instance.pinnedUsers; | ||||||
| 		response.hiddenTags = instance.hiddenTags; | 			response.hiddenTags = instance.hiddenTags; | ||||||
| 		response.blockedHosts = instance.blockedHosts; | 			response.blockedHosts = instance.blockedHosts; | ||||||
| 		response.hcaptchaSecretKey = instance.hcaptchaSecretKey; | 			response.hcaptchaSecretKey = instance.hcaptchaSecretKey; | ||||||
| 		response.recaptchaSecretKey = instance.recaptchaSecretKey; | 			response.recaptchaSecretKey = instance.recaptchaSecretKey; | ||||||
| 		response.proxyAccountId = instance.proxyAccountId; | 			response.proxyAccountId = instance.proxyAccountId; | ||||||
| 		response.twitterConsumerKey = instance.twitterConsumerKey; | 			response.twitterConsumerKey = instance.twitterConsumerKey; | ||||||
| 		response.twitterConsumerSecret = instance.twitterConsumerSecret; | 			response.twitterConsumerSecret = instance.twitterConsumerSecret; | ||||||
| 		response.githubClientId = instance.githubClientId; | 			response.githubClientId = instance.githubClientId; | ||||||
| 		response.githubClientSecret = instance.githubClientSecret; | 			response.githubClientSecret = instance.githubClientSecret; | ||||||
| 		response.discordClientId = instance.discordClientId; | 			response.discordClientId = instance.discordClientId; | ||||||
| 		response.discordClientSecret = instance.discordClientSecret; | 			response.discordClientSecret = instance.discordClientSecret; | ||||||
| 		response.summalyProxy = instance.summalyProxy; | 			response.summalyProxy = instance.summalyProxy; | ||||||
| 		response.email = instance.email; | 			response.email = instance.email; | ||||||
| 		response.smtpSecure = instance.smtpSecure; | 			response.smtpSecure = instance.smtpSecure; | ||||||
| 		response.smtpHost = instance.smtpHost; | 			response.smtpHost = instance.smtpHost; | ||||||
| 		response.smtpPort = instance.smtpPort; | 			response.smtpPort = instance.smtpPort; | ||||||
| 		response.smtpUser = instance.smtpUser; | 			response.smtpUser = instance.smtpUser; | ||||||
| 		response.smtpPass = instance.smtpPass; | 			response.smtpPass = instance.smtpPass; | ||||||
| 		response.swPrivateKey = instance.swPrivateKey; | 			response.swPrivateKey = instance.swPrivateKey; | ||||||
| 		response.useObjectStorage = instance.useObjectStorage; | 			response.useObjectStorage = instance.useObjectStorage; | ||||||
| 		response.objectStorageBaseUrl = instance.objectStorageBaseUrl; | 			response.objectStorageBaseUrl = instance.objectStorageBaseUrl; | ||||||
| 		response.objectStorageBucket = instance.objectStorageBucket; | 			response.objectStorageBucket = instance.objectStorageBucket; | ||||||
| 		response.objectStoragePrefix = instance.objectStoragePrefix; | 			response.objectStoragePrefix = instance.objectStoragePrefix; | ||||||
| 		response.objectStorageEndpoint = instance.objectStorageEndpoint; | 			response.objectStorageEndpoint = instance.objectStorageEndpoint; | ||||||
| 		response.objectStorageRegion = instance.objectStorageRegion; | 			response.objectStorageRegion = instance.objectStorageRegion; | ||||||
| 		response.objectStoragePort = instance.objectStoragePort; | 			response.objectStoragePort = instance.objectStoragePort; | ||||||
| 		response.objectStorageAccessKey = instance.objectStorageAccessKey; | 			response.objectStorageAccessKey = instance.objectStorageAccessKey; | ||||||
| 		response.objectStorageSecretKey = instance.objectStorageSecretKey; | 			response.objectStorageSecretKey = instance.objectStorageSecretKey; | ||||||
| 		response.objectStorageUseSSL = instance.objectStorageUseSSL; | 			response.objectStorageUseSSL = instance.objectStorageUseSSL; | ||||||
| 		response.objectStorageUseProxy = instance.objectStorageUseProxy; | 			response.objectStorageUseProxy = instance.objectStorageUseProxy; | ||||||
| 		response.objectStorageSetPublicRead = instance.objectStorageSetPublicRead; | 			response.objectStorageSetPublicRead = instance.objectStorageSetPublicRead; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return response; | 	return response; | ||||||
|  |  | ||||||
							
								
								
									
										54
									
								
								src/server/api/endpoints/notes/clips.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/server/api/endpoints/notes/clips.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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))); | ||||||
|  | }); | ||||||
							
								
								
									
										29
									
								
								src/server/api/endpoints/pages/featured.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/server/api/endpoints/pages/featured.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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
 | // Clip
 | ||||||
|  | // TODO: 非publicなclipのハンドリング
 | ||||||
| router.get('/clips/:clip', async ctx => { | router.get('/clips/:clip', async ctx => { | ||||||
| 	const clip = await Clips.findOne({ | 	const clip = await Clips.findOne({ | ||||||
| 		id: ctx.params.clip, | 		id: ctx.params.clip, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue