nanka iroiro
This commit is contained in:
		
							parent
							
								
									873d4bd707
								
							
						
					
					
						commit
						10cb15b000
					
				
					 8 changed files with 225 additions and 87 deletions
				
			
		|  | @ -673,6 +673,7 @@ narrow: "狭い" | |||
| reloadToApplySetting: "設定はページリロード後に反映されます。今すぐリロードしますか?" | ||||
| showTitlebar: "タイトルバーを表示する" | ||||
| clearCache: "キャッシュをクリア" | ||||
| onlineUsersCount: "{n}人がオンライン" | ||||
| 
 | ||||
| _aboutMisskey: | ||||
|   about: "Misskeyはsyuiloによって2014年から開発されている、オープンソースのソフトウェアです。" | ||||
|  | @ -1015,6 +1016,7 @@ _widgets: | |||
|   postForm: "投稿フォーム" | ||||
|   slideshow: "スライドショー" | ||||
|   button: "ボタン" | ||||
|   onlineUsers: "オンラインユーザー" | ||||
| 
 | ||||
| _cw: | ||||
|   hide: "隠す" | ||||
|  |  | |||
|  | @ -11,6 +11,11 @@ export default defineComponent({ | |||
| 			required: false, | ||||
| 			default: 'span', | ||||
| 		}, | ||||
| 		textTag: { | ||||
| 			type: String, | ||||
| 			required: false, | ||||
| 			default: null, | ||||
| 		}, | ||||
| 	}, | ||||
| 	render() { | ||||
| 		let str = this.src; | ||||
|  | @ -32,6 +37,6 @@ export default defineComponent({ | |||
| 			str = str.substr(nextBracketClose + 1); | ||||
| 		} | ||||
| 
 | ||||
| 		return h(this.tag, parsed.map(x => typeof x === 'string' ? x : this.$slots[x.arg]())); | ||||
| 		return h(this.tag, parsed.map(x => typeof x === 'string' ? (this.textTag ? h(this.textTag, x) : x) : this.$slots[x.arg]())); | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <template> | ||||
| <XWindow ref="window" | ||||
| 	:initial-width="700" | ||||
| 	:initial-width="500" | ||||
| 	:initial-height="500" | ||||
| 	:can-resize="true" | ||||
| 	:close-right="true" | ||||
|  |  | |||
|  | @ -1,32 +1,36 @@ | |||
| <template> | ||||
| <div class="rsqzvsbo _section" v-if="meta"> | ||||
| 	<div class="overview _monospace" v-if="stats"> | ||||
| 		<div class="stats"> | ||||
| 			<div><span>Users</span><span>{{ number(stats.originalUsersCount) }}</span></div> | ||||
| 			<div><span>Notes</span><span>{{ number(stats.originalNotesCount) }}</span></div> | ||||
| 			<div><span>Reactions</span><span>{{ number(stats.reactionsCount) }}</span></div> | ||||
| <div class="rsqzvsbo" v-if="meta"> | ||||
| 	<div class="top"> | ||||
| 		<div class="main _panel"> | ||||
| 			<div class="bg" :style="{ backgroundImage: `url(${ meta.bannerUrl })` }"> | ||||
| 				<div class="fade"></div> | ||||
| 			</div> | ||||
| 			<div class="fg"> | ||||
| 				<h1> | ||||
| 					<img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span> | ||||
| 				</h1> | ||||
| 				<div class="about"> | ||||
| 					<div class="desc" v-html="meta.description || $ts.introMisskey"></div> | ||||
| 				</div> | ||||
| 				<div class="action"> | ||||
| 					<MkButton @click="signup()" inline primary>{{ $ts.signup }}</MkButton> | ||||
| 					<MkButton @click="signin()" inline>{{ $ts.login }}</MkButton> | ||||
| 				</div> | ||||
| 				<div class="status" v-if="onlineUsersCount"> | ||||
| 					<I18n :src="$ts.onlineUsersCount" text-tag="span" class="online"> | ||||
| 						<template #n><b>{{ onlineUsersCount }}</b></template> | ||||
| 					</I18n> | ||||
| 				</div> | ||||
| 				<button class="_button _acrylic menu" @click="showMenu"><Fa :icon="faEllipsisH"/></button> | ||||
| 			</div> | ||||
| 		<div class="tags"> | ||||
| 			<MkA class="tag" v-for="tag in tags" :to="`/tags/${encodeURIComponent(tag.tag)}`">#{{ tag.tag }}</MkA> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<template v-if="meta.pinnedClipId"> | ||||
| 		<h2># {{ $ts.pinnedNotes }}</h2> | ||||
| 		<MkPagination :pagination="clipPagination" #default="{items}"> | ||||
| 			<XNote class="kmkqjgkl" v-for="note in items" :note="note" :key="note.id"/> | ||||
| 		</MkPagination> | ||||
| 	</template> | ||||
| 	<template v-else> | ||||
| 		<h2># {{ $ts.featured }}</h2> | ||||
| 		<MkPagination :pagination="featuredPagination" #default="{items}"> | ||||
| 			<XNote class="kmkqjgkl" v-for="note in items" :note="note" :key="note.id"/> | ||||
| 		</MkPagination> | ||||
| 	</template> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faEllipsisH, faInfoCircle, faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { toUnicode } from 'punycode'; | ||||
| import XSigninDialog from '@/components/signin-dialog.vue'; | ||||
| import XSignupDialog from '@/components/signup-dialog.vue'; | ||||
|  | @ -51,6 +55,7 @@ export default defineComponent({ | |||
| 			meta: null, | ||||
| 			stats: null, | ||||
| 			tags: [], | ||||
| 			onlineUsersCount: null, | ||||
| 			clipPagination: { | ||||
| 				endpoint: 'clips/notes', | ||||
| 				limit: 10, | ||||
|  | @ -63,6 +68,7 @@ export default defineComponent({ | |||
| 				limit: 10, | ||||
| 				offsetMode: true | ||||
| 			}, | ||||
| 			faEllipsisH | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
|  | @ -75,6 +81,10 @@ export default defineComponent({ | |||
| 			this.stats = stats; | ||||
| 		}); | ||||
| 
 | ||||
| 		os.api('get-online-users-count').then(res => { | ||||
| 			this.onlineUsersCount = res.count; | ||||
| 		}); | ||||
| 
 | ||||
| 		os.api('hashtags/list', { | ||||
| 			sort: '+mentionedLocalUsers', | ||||
| 			limit: 8 | ||||
|  | @ -96,6 +106,28 @@ export default defineComponent({ | |||
| 			}, {}, 'closed'); | ||||
| 		}, | ||||
| 
 | ||||
| 		showMenu(ev) { | ||||
| 			os.modalMenu([{ | ||||
| 				text: this.$t('aboutX', { x: instanceName }), | ||||
| 				icon: faInfoCircle, | ||||
| 				action: () => { | ||||
| 					os.pageWindow('/about'); | ||||
| 				} | ||||
| 			}, { | ||||
| 				text: this.$ts.aboutMisskey, | ||||
| 				icon: faInfoCircle, | ||||
| 				action: () => { | ||||
| 					os.pageWindow('/about-misskey'); | ||||
| 				} | ||||
| 			}, null, { | ||||
| 				text: this.$ts.help, | ||||
| 				icon: faQuestionCircle, | ||||
| 				action: () => { | ||||
| 					os.pageWindow('/docs'); | ||||
| 				} | ||||
| 			}], ev.currentTarget || ev.target); | ||||
| 		}, | ||||
| 
 | ||||
| 		number | ||||
| 	} | ||||
| }); | ||||
|  | @ -103,79 +135,87 @@ export default defineComponent({ | |||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .rsqzvsbo { | ||||
| 	text-align: center; | ||||
| 
 | ||||
| 	> h2 { | ||||
| 		display: inline-block; | ||||
| 		color: #fff; | ||||
| 		margin: 16px; | ||||
| 		padding: 8px 12px; | ||||
| 		background: rgba(0, 0, 0, 0.5); | ||||
| 	} | ||||
| 
 | ||||
| 	> .overview { | ||||
| 		> .stats, > .tags { | ||||
| 			display: inline-block; | ||||
| 			vertical-align: top; | ||||
| 			width: 530px; | ||||
| 			padding: 32px; | ||||
| 			margin: 16px; | ||||
| 			box-sizing: border-box; | ||||
| 
 | ||||
| 			@media (max-width: 800px) { | ||||
| 				display: block; | ||||
| 				width: 100%; | ||||
| 				margin: 12px 0; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		> .stats { | ||||
| 			background: var(--accent); | ||||
| 			border-radius: 12px; | ||||
| 			color: #fff; | ||||
| 			font-size: 1.5em; | ||||
| 
 | ||||
| 			> div { | ||||
| 	> .top { | ||||
| 		display: flex; | ||||
| 
 | ||||
| 				> span:first-child { | ||||
| 					opacity: 0.7; | ||||
| 					font-weight: bold; | ||||
| 				} | ||||
| 
 | ||||
| 				> span:last-child { | ||||
| 					margin-left: auto; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		> .tags { | ||||
| 			background: var(--panel); | ||||
| 			border-radius: 12px; | ||||
| 			color: var(--fg); | ||||
| 			font-size: 1.5em; | ||||
| 
 | ||||
| 			> .tag { | ||||
| 				margin-right: 1em; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| .kmkqjgkl { | ||||
| 	display: inline-block; | ||||
| 	vertical-align: middle; | ||||
| 	width: 530px; | ||||
| 	margin: 16px; | ||||
| 		text-align: center; | ||||
| 		min-height: 100vh; | ||||
| 		box-sizing: border-box; | ||||
| 	text-align: left; | ||||
| 	box-shadow: 0 6px 46px rgb(0 0 0 / 25%); | ||||
| 	border-radius: 12px; | ||||
| 		padding: 16px; | ||||
| 
 | ||||
| 	@media (max-width: 800px) { | ||||
| 		display: block; | ||||
| 		> .main { | ||||
| 			position: relative; | ||||
| 			width: min(490px, 100%); | ||||
| 			margin: auto; | ||||
| 			box-shadow: 0 12px 32px rgb(0 0 0 / 25%); | ||||
| 
 | ||||
| 			> .bg { | ||||
| 				position: absolute; | ||||
| 				top: 0; | ||||
| 				left: 0; | ||||
| 				width: 100%; | ||||
| 		margin: 12px 0; | ||||
| 				height: 128px; | ||||
| 				background-position: center; | ||||
| 				background-size: cover; | ||||
| 				opacity: 0.75; | ||||
| 
 | ||||
| 				> .fade { | ||||
| 					position: absolute; | ||||
| 					bottom: 0; | ||||
| 					left: 0; | ||||
| 					width: 100%; | ||||
| 					height: 128px; | ||||
| 					background: linear-gradient(0deg, var(--panel), var(--X15)); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			> .fg { | ||||
| 				position: relative; | ||||
| 				z-index: 1; | ||||
| 
 | ||||
| 				> h1 { | ||||
| 					display: block; | ||||
| 					margin: 0; | ||||
| 					padding: 32px 32px 24px 32px; | ||||
| 
 | ||||
| 					> .logo { | ||||
| 						vertical-align: bottom; | ||||
| 						max-height: 120px; | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				> .about { | ||||
| 					padding: 0 32px; | ||||
| 				} | ||||
| 
 | ||||
| 				> .action { | ||||
| 					padding: 32px; | ||||
| 				} | ||||
| 
 | ||||
| 				> .status { | ||||
| 					border-top: solid 1px var(--divider); | ||||
| 					padding: 32px; | ||||
| 
 | ||||
| 					> .online { | ||||
| 						::v-deep(b) { | ||||
| 							color: #41b781; | ||||
| 						} | ||||
| 
 | ||||
| 						::v-deep(span) { | ||||
| 							opacity: 0.7; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				> .menu { | ||||
| 					position: absolute; | ||||
| 					top: 16px; | ||||
| 					right: 16px; | ||||
| 					width: 32px; | ||||
| 					height: 32px; | ||||
| 					border-radius: 8px; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -2,12 +2,12 @@ | |||
| <div class="mk-app" :style="{ backgroundImage: root ? `url(${ $instance.backgroundImageUrl })` : 'none' }"> | ||||
| 	<a v-if="root" href="https://github.com/syuilo/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--panel); color:var(--fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a> | ||||
| 
 | ||||
| 	<div class="side" v-if="!narrow"> | ||||
| 		<XKanban class="kanban" full :transparent="root" :powered-by="root"/> | ||||
| 	<div class="side" v-if="!narrow && !root"> | ||||
| 		<XKanban class="kanban" full/> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<div class="main"> | ||||
| 		<XKanban class="banner" :full="root" :transparent="root" :powered-by="root" v-if="narrow"/> | ||||
| 		<XKanban class="banner" :powered-by="root" v-if="narrow && !root"/> | ||||
| 
 | ||||
| 		<div class="contents"> | ||||
| 			<XHeader class="header" :info="pageInfo" v-if="!root"/> | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ export default function(app: App) { | |||
| 	app.component('MkwFederation', defineAsyncComponent(() => import('./federation.vue'))); | ||||
| 	app.component('MkwPostForm', defineAsyncComponent(() => import('./post-form.vue'))); | ||||
| 	app.component('MkwSlideshow', defineAsyncComponent(() => import('./slideshow.vue'))); | ||||
| 	app.component('MkwOnlineUsers', defineAsyncComponent(() => import('./online-users.vue'))); | ||||
| 	app.component('MkwButton', defineAsyncComponent(() => import('./button.vue'))); | ||||
| } | ||||
| 
 | ||||
|  | @ -31,5 +32,6 @@ export const widgets = [ | |||
| 	'federation', | ||||
| 	'postForm', | ||||
| 	'slideshow', | ||||
| 	'onlineUsers', | ||||
| 	'button', | ||||
| ]; | ||||
|  |  | |||
							
								
								
									
										67
									
								
								src/client/widgets/online-users.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/client/widgets/online-users.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | |||
| <template> | ||||
| <div class="mkw-onlineUsers" :class="{ _panel: !props.transparent, pad: !props.transparent }"> | ||||
| 	<I18n v-if="onlineUsersCount" :src="$ts.onlineUsersCount" text-tag="span" class="text"> | ||||
| 		<template #n><b>{{ onlineUsersCount }}</b></template> | ||||
| 	</I18n> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import define from './define'; | ||||
| import * as os from '@/os'; | ||||
| 
 | ||||
| const widget = define({ | ||||
| 	name: 'onlineUsers', | ||||
| 	props: () => ({ | ||||
| 		transparent: { | ||||
| 			type: 'boolean', | ||||
| 			default: true, | ||||
| 		}, | ||||
| 	}) | ||||
| }); | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	extends: widget, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			onlineUsersCount: null, | ||||
| 			clock: null, | ||||
| 		}; | ||||
| 	}, | ||||
| 	created() { | ||||
| 		this.tick(); | ||||
| 		this.clock = setInterval(this.tick, 1000 * 15); | ||||
| 	}, | ||||
| 	beforeUnmount() { | ||||
| 		clearInterval(this.clock); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		tick() { | ||||
| 			os.api('get-online-users-count').then(res => { | ||||
| 				this.onlineUsersCount = res.count; | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .mkw-onlineUsers { | ||||
| 	text-align: center; | ||||
| 
 | ||||
| 	&.pad { | ||||
| 		padding: 16px 0; | ||||
| 	} | ||||
| 
 | ||||
| 	> .text { | ||||
| 		::v-deep(b) { | ||||
| 			color: #41b781; | ||||
| 		} | ||||
| 
 | ||||
| 		::v-deep(span) { | ||||
| 			opacity: 0.7; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										22
									
								
								src/server/api/endpoints/get-online-users-count.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/server/api/endpoints/get-online-users-count.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| import define from '../define'; | ||||
| import redis from '../../../db/redis'; | ||||
| import config from '../../../config'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	tags: ['meta'], | ||||
| 
 | ||||
| 	requireCredential: false as const, | ||||
| 
 | ||||
| 	params: { | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| export default define(meta, (ps, user) => { | ||||
| 	return new Promise((res, rej) => { | ||||
| 		redis.pubsub('numsub', config.host, (_, x) => { | ||||
| 			res({ | ||||
| 				count: x[1] | ||||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
| }); | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue