[Client] Messagingをいろいろ
This commit is contained in:
		@@ -20,3 +20,7 @@ require('./twitter-setting.tag');
 | 
			
		||||
require('./authorized-apps.tag');
 | 
			
		||||
require('./poll.tag');
 | 
			
		||||
require('./poll-editor.tag');
 | 
			
		||||
require('./messaging/room.tag');
 | 
			
		||||
require('./messaging/message.tag');
 | 
			
		||||
require('./messaging/index.tag');
 | 
			
		||||
require('./messaging/form.tag');
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										159
									
								
								src/web/app/common/tags/messaging/form.tag
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								src/web/app/common/tags/messaging/form.tag
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,159 @@
 | 
			
		||||
<mk-messaging-form>
 | 
			
		||||
	<textarea ref="text" onkeypress={ onkeypress } onpaste={ onpaste } placeholder="ここにメッセージを入力"></textarea>
 | 
			
		||||
	<div class="files"></div>
 | 
			
		||||
	<mk-uploader ref="uploader"></mk-uploader>
 | 
			
		||||
	<button class="send" onclick={ send } disabled={ sending } title="メッセージを送信"><i class="fa fa-paper-plane" if={ !sending }></i><i class="fa fa-spinner fa-spin" if={ sending }></i></button>
 | 
			
		||||
	<button class="attach-from-local" type="button" title="PCから画像を添付する"><i class="fa fa-upload"></i></button>
 | 
			
		||||
	<button class="attach-from-drive" type="button" title="アルバムから画像を添付する"><i class="fa fa-folder-open"></i></button>
 | 
			
		||||
	<input name="file" type="file" accept="image/*"/>
 | 
			
		||||
	<style type="stylus">
 | 
			
		||||
		:scope
 | 
			
		||||
			display block
 | 
			
		||||
 | 
			
		||||
			> textarea
 | 
			
		||||
				cursor auto
 | 
			
		||||
				display block
 | 
			
		||||
				width 100%
 | 
			
		||||
				min-width 100%
 | 
			
		||||
				max-width 100%
 | 
			
		||||
				height 64px
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 8px
 | 
			
		||||
				font-size 1em
 | 
			
		||||
				color #000
 | 
			
		||||
				outline none
 | 
			
		||||
				border none
 | 
			
		||||
				border-top solid 1px #eee
 | 
			
		||||
				border-radius 0
 | 
			
		||||
				box-shadow none
 | 
			
		||||
				background transparent
 | 
			
		||||
 | 
			
		||||
			> .send
 | 
			
		||||
				position absolute
 | 
			
		||||
				bottom 0
 | 
			
		||||
				right 0
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 10px 14px
 | 
			
		||||
				line-height 1em
 | 
			
		||||
				font-size 1em
 | 
			
		||||
				color #aaa
 | 
			
		||||
				transition color 0.1s ease
 | 
			
		||||
 | 
			
		||||
				&:hover
 | 
			
		||||
					color $theme-color
 | 
			
		||||
 | 
			
		||||
				&:active
 | 
			
		||||
					color darken($theme-color, 10%)
 | 
			
		||||
					transition color 0s ease
 | 
			
		||||
 | 
			
		||||
			.files
 | 
			
		||||
				display block
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 0 8px
 | 
			
		||||
				list-style none
 | 
			
		||||
 | 
			
		||||
				&:after
 | 
			
		||||
					content ''
 | 
			
		||||
					display block
 | 
			
		||||
					clear both
 | 
			
		||||
 | 
			
		||||
				> li
 | 
			
		||||
					display block
 | 
			
		||||
					float left
 | 
			
		||||
					margin 4px
 | 
			
		||||
					padding 0
 | 
			
		||||
					width 64px
 | 
			
		||||
					height 64px
 | 
			
		||||
					background-color #eee
 | 
			
		||||
					background-repeat no-repeat
 | 
			
		||||
					background-position center center
 | 
			
		||||
					background-size cover
 | 
			
		||||
					cursor move
 | 
			
		||||
 | 
			
		||||
					&:hover
 | 
			
		||||
						> .remove
 | 
			
		||||
							display block
 | 
			
		||||
 | 
			
		||||
					> .remove
 | 
			
		||||
						display none
 | 
			
		||||
						position absolute
 | 
			
		||||
						right -6px
 | 
			
		||||
						top -6px
 | 
			
		||||
						margin 0
 | 
			
		||||
						padding 0
 | 
			
		||||
						background transparent
 | 
			
		||||
						outline none
 | 
			
		||||
						border none
 | 
			
		||||
						border-radius 0
 | 
			
		||||
						box-shadow none
 | 
			
		||||
						cursor pointer
 | 
			
		||||
 | 
			
		||||
			.attach-from-local
 | 
			
		||||
			.attach-from-drive
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 10px 14px
 | 
			
		||||
				line-height 1em
 | 
			
		||||
				font-size 1em
 | 
			
		||||
				font-weight normal
 | 
			
		||||
				text-decoration none
 | 
			
		||||
				color #aaa
 | 
			
		||||
				transition color 0.1s ease
 | 
			
		||||
 | 
			
		||||
				&:hover
 | 
			
		||||
					color $theme-color
 | 
			
		||||
 | 
			
		||||
				&:active
 | 
			
		||||
					color darken($theme-color, 10%)
 | 
			
		||||
					transition color 0s ease
 | 
			
		||||
 | 
			
		||||
			input[type=file]
 | 
			
		||||
				display none
 | 
			
		||||
 | 
			
		||||
	</style>
 | 
			
		||||
	<script>
 | 
			
		||||
		@mixin \api
 | 
			
		||||
 | 
			
		||||
		@onpaste = (e) ~>
 | 
			
		||||
			data = e.clipboard-data
 | 
			
		||||
			items = data.items
 | 
			
		||||
			for i from 0 to items.length - 1
 | 
			
		||||
				item = items[i]
 | 
			
		||||
				switch (item.kind)
 | 
			
		||||
					| \file =>
 | 
			
		||||
						@upload item.get-as-file!
 | 
			
		||||
 | 
			
		||||
		@onkeypress = (e) ~>
 | 
			
		||||
			if (e.which == 10 || e.which == 13) && e.ctrl-key
 | 
			
		||||
				@send!
 | 
			
		||||
 | 
			
		||||
		@select-file = ~>
 | 
			
		||||
			@refs.file.click!
 | 
			
		||||
 | 
			
		||||
		@select-file-from-drive = ~>
 | 
			
		||||
			browser = document.body.append-child document.create-element \mk-select-file-from-drive-window
 | 
			
		||||
			event = riot.observable!
 | 
			
		||||
			riot.mount browser, do
 | 
			
		||||
				multiple: true
 | 
			
		||||
				event: event
 | 
			
		||||
			event.one \selected (files) ~>
 | 
			
		||||
				files.for-each @add-file
 | 
			
		||||
 | 
			
		||||
		@send = ~>
 | 
			
		||||
			@sending = true
 | 
			
		||||
			@api \messaging/messages/create do
 | 
			
		||||
				user_id: @opts.user.id
 | 
			
		||||
				text: @refs.text.value
 | 
			
		||||
			.then (message) ~>
 | 
			
		||||
				@clear!
 | 
			
		||||
			.catch (err) ~>
 | 
			
		||||
				console.error err
 | 
			
		||||
			.then ~>
 | 
			
		||||
				@sending = false
 | 
			
		||||
				@update!
 | 
			
		||||
 | 
			
		||||
		@clear = ~>
 | 
			
		||||
			@refs.text.value = ''
 | 
			
		||||
			@files = []
 | 
			
		||||
			@update!
 | 
			
		||||
	</script>
 | 
			
		||||
</mk-messaging-form>
 | 
			
		||||
							
								
								
									
										328
									
								
								src/web/app/common/tags/messaging/index.tag
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										328
									
								
								src/web/app/common/tags/messaging/index.tag
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,328 @@
 | 
			
		||||
<mk-messaging>
 | 
			
		||||
	<div class="search">
 | 
			
		||||
		<div class="form">
 | 
			
		||||
			<label for="search-input"><i class="fa fa-search"></i></label>
 | 
			
		||||
			<input ref="searchInput" type="search" oninput={ search } placeholder="ユーザーを探す"/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="result">
 | 
			
		||||
			<ol class="users" if={ searchResult.length > 0 }>
 | 
			
		||||
				<li each={ user in searchResult }>
 | 
			
		||||
					<a onclick={ user._click }>
 | 
			
		||||
						<img class="avatar" src={ user.avatar_url + '?thumbnail&size=32' } alt=""/>
 | 
			
		||||
						<span class="name">{ user.name }</span>
 | 
			
		||||
						<span class="username">@{ user.username }</span>
 | 
			
		||||
					</a>
 | 
			
		||||
				</li>
 | 
			
		||||
			</ol>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="history" if={ history.length > 0 }>
 | 
			
		||||
		<virtual each={ history }>
 | 
			
		||||
			<a class="user" data-is-me={ is_me } data-is-read={ is_read } onclick={ _click }>
 | 
			
		||||
				<div>
 | 
			
		||||
					<img class="avatar" src={ (is_me ? recipient.avatar_url : user.avatar_url) + '?thumbnail&size=64' } alt=""/>
 | 
			
		||||
					<header>
 | 
			
		||||
						<span class="name">{ is_me ? recipient.name : user.name }</span>
 | 
			
		||||
						<span class="username">{ '@' + (is_me ? recipient.username : user.username ) }</span>
 | 
			
		||||
						<mk-time time={ created_at }></mk-time>
 | 
			
		||||
					</header>
 | 
			
		||||
					<div class="body">
 | 
			
		||||
						<p class="text"><span class="me" if={ is_me }>あなた:</span>{ text }</p>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</a>
 | 
			
		||||
		</virtual>
 | 
			
		||||
	</div>
 | 
			
		||||
	<p class="no-history" if={ history.length == 0 }>履歴はありません。<br/>ユーザーを検索して、いつでもメッセージを送受信できます。</p>
 | 
			
		||||
	<style type="stylus">
 | 
			
		||||
		:scope
 | 
			
		||||
			display block
 | 
			
		||||
 | 
			
		||||
			> .search
 | 
			
		||||
				display block
 | 
			
		||||
				position -webkit-sticky
 | 
			
		||||
				position sticky
 | 
			
		||||
				top 0
 | 
			
		||||
				left 0
 | 
			
		||||
				z-index 1
 | 
			
		||||
				width 100%
 | 
			
		||||
				background #fff
 | 
			
		||||
				box-shadow 0 0px 2px rgba(0, 0, 0, 0.2)
 | 
			
		||||
 | 
			
		||||
				> .form
 | 
			
		||||
					padding 8px
 | 
			
		||||
					background #f7f7f7
 | 
			
		||||
 | 
			
		||||
					> label
 | 
			
		||||
						display block
 | 
			
		||||
						position absolute
 | 
			
		||||
						top 0
 | 
			
		||||
						left 8px
 | 
			
		||||
						z-index 1
 | 
			
		||||
						height 100%
 | 
			
		||||
						width 38px
 | 
			
		||||
						pointer-events none
 | 
			
		||||
 | 
			
		||||
						> i
 | 
			
		||||
							display block
 | 
			
		||||
							position absolute
 | 
			
		||||
							top 0
 | 
			
		||||
							right 0
 | 
			
		||||
							bottom 0
 | 
			
		||||
							left 0
 | 
			
		||||
							width 1em
 | 
			
		||||
							height 1em
 | 
			
		||||
							margin auto
 | 
			
		||||
							color #555
 | 
			
		||||
 | 
			
		||||
					> input
 | 
			
		||||
						margin 0
 | 
			
		||||
						padding 0 12px 0 38px
 | 
			
		||||
						width 100%
 | 
			
		||||
						font-size 1em
 | 
			
		||||
						line-height 38px
 | 
			
		||||
						color #000
 | 
			
		||||
						outline none
 | 
			
		||||
						border solid 1px #eee
 | 
			
		||||
						border-radius 5px
 | 
			
		||||
						box-shadow none
 | 
			
		||||
						transition color 0.5s ease, border 0.5s ease
 | 
			
		||||
 | 
			
		||||
						&:hover
 | 
			
		||||
							border solid 1px #ddd
 | 
			
		||||
							transition border 0.2s ease
 | 
			
		||||
 | 
			
		||||
						&:focus
 | 
			
		||||
							color darken($theme-color, 20%)
 | 
			
		||||
							border solid 1px $theme-color
 | 
			
		||||
							transition color 0, border 0
 | 
			
		||||
 | 
			
		||||
				> .result
 | 
			
		||||
					display block
 | 
			
		||||
					top 0
 | 
			
		||||
					left 0
 | 
			
		||||
					z-index 2
 | 
			
		||||
					width 100%
 | 
			
		||||
					margin 0
 | 
			
		||||
					padding 0
 | 
			
		||||
					background #fff
 | 
			
		||||
 | 
			
		||||
					> .users
 | 
			
		||||
						margin 0
 | 
			
		||||
						padding 0
 | 
			
		||||
						list-style none
 | 
			
		||||
 | 
			
		||||
						> li
 | 
			
		||||
							> a
 | 
			
		||||
								display inline-block
 | 
			
		||||
								z-index 1
 | 
			
		||||
								width 100%
 | 
			
		||||
								padding 8px 32px
 | 
			
		||||
								vertical-align top
 | 
			
		||||
								white-space nowrap
 | 
			
		||||
								overflow hidden
 | 
			
		||||
								color rgba(0, 0, 0, 0.8)
 | 
			
		||||
								text-decoration none
 | 
			
		||||
								transition none
 | 
			
		||||
 | 
			
		||||
								&:hover
 | 
			
		||||
									color #fff
 | 
			
		||||
									background $theme-color
 | 
			
		||||
 | 
			
		||||
									.name
 | 
			
		||||
										color #fff
 | 
			
		||||
 | 
			
		||||
									.username
 | 
			
		||||
										color #fff
 | 
			
		||||
 | 
			
		||||
								&:active
 | 
			
		||||
									color #fff
 | 
			
		||||
									background darken($theme-color, 10%)
 | 
			
		||||
 | 
			
		||||
									.name
 | 
			
		||||
										color #fff
 | 
			
		||||
 | 
			
		||||
									.username
 | 
			
		||||
										color #fff
 | 
			
		||||
 | 
			
		||||
								.avatar
 | 
			
		||||
									vertical-align middle
 | 
			
		||||
									min-width 32px
 | 
			
		||||
									min-height 32px
 | 
			
		||||
									max-width 32px
 | 
			
		||||
									max-height 32px
 | 
			
		||||
									margin 0 8px 0 0
 | 
			
		||||
									border-radius 6px
 | 
			
		||||
 | 
			
		||||
								.name
 | 
			
		||||
									margin 0 8px 0 0
 | 
			
		||||
									/*font-weight bold*/
 | 
			
		||||
									font-weight normal
 | 
			
		||||
									color rgba(0, 0, 0, 0.8)
 | 
			
		||||
 | 
			
		||||
								.username
 | 
			
		||||
									font-weight normal
 | 
			
		||||
									color rgba(0, 0, 0, 0.3)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
			> .history
 | 
			
		||||
 | 
			
		||||
				> a
 | 
			
		||||
					display block
 | 
			
		||||
					padding 20px 30px
 | 
			
		||||
					text-decoration none
 | 
			
		||||
					background #fff
 | 
			
		||||
					border-bottom solid 1px #eee
 | 
			
		||||
 | 
			
		||||
					*
 | 
			
		||||
						pointer-events none
 | 
			
		||||
						user-select none
 | 
			
		||||
 | 
			
		||||
					&:hover
 | 
			
		||||
						background #fafafa
 | 
			
		||||
 | 
			
		||||
						> .avatar
 | 
			
		||||
							filter saturate(200%)
 | 
			
		||||
 | 
			
		||||
					&:active
 | 
			
		||||
						background #eee
 | 
			
		||||
 | 
			
		||||
					&[data-is-read]
 | 
			
		||||
					&[data-is-me]
 | 
			
		||||
						opacity 0.8
 | 
			
		||||
 | 
			
		||||
					&:not([data-is-me]):not([data-is-read])
 | 
			
		||||
						background-image url("/_/resources/desktop/unread.svg")
 | 
			
		||||
						background-repeat no-repeat
 | 
			
		||||
						background-position 0 center
 | 
			
		||||
 | 
			
		||||
					&:after
 | 
			
		||||
						content ""
 | 
			
		||||
						display block
 | 
			
		||||
						clear both
 | 
			
		||||
 | 
			
		||||
					> div
 | 
			
		||||
						max-width 500px
 | 
			
		||||
						margin 0 auto
 | 
			
		||||
 | 
			
		||||
						> header
 | 
			
		||||
							margin-bottom 2px
 | 
			
		||||
							white-space nowrap
 | 
			
		||||
							overflow hidden
 | 
			
		||||
 | 
			
		||||
							> .name
 | 
			
		||||
								text-align left
 | 
			
		||||
								display inline
 | 
			
		||||
								margin 0
 | 
			
		||||
								padding 0
 | 
			
		||||
								font-size 1em
 | 
			
		||||
								color rgba(0, 0, 0, 0.9)
 | 
			
		||||
								font-weight bold
 | 
			
		||||
								transition all 0.1s ease
 | 
			
		||||
 | 
			
		||||
							> .username
 | 
			
		||||
								text-align left
 | 
			
		||||
								margin 0 0 0 8px
 | 
			
		||||
								color rgba(0, 0, 0, 0.5)
 | 
			
		||||
 | 
			
		||||
							> mk-time
 | 
			
		||||
								position absolute
 | 
			
		||||
								top 0
 | 
			
		||||
								right 0
 | 
			
		||||
								display inline
 | 
			
		||||
								color rgba(0, 0, 0, 0.5)
 | 
			
		||||
								font-size 80%
 | 
			
		||||
 | 
			
		||||
						> .avatar
 | 
			
		||||
							float left
 | 
			
		||||
							width 54px
 | 
			
		||||
							height 54px
 | 
			
		||||
							margin 0 16px 0 0
 | 
			
		||||
							border-radius 8px
 | 
			
		||||
							transition all 0.1s ease
 | 
			
		||||
 | 
			
		||||
						> .body
 | 
			
		||||
 | 
			
		||||
							> .text
 | 
			
		||||
								display block
 | 
			
		||||
								margin 0 0 0 0
 | 
			
		||||
								padding 0
 | 
			
		||||
								overflow hidden
 | 
			
		||||
								overflow-wrap break-word
 | 
			
		||||
								font-size 1.1em
 | 
			
		||||
								color rgba(0, 0, 0, 0.8)
 | 
			
		||||
 | 
			
		||||
								.me
 | 
			
		||||
									color rgba(0, 0, 0, 0.4)
 | 
			
		||||
 | 
			
		||||
							> .image
 | 
			
		||||
								display block
 | 
			
		||||
								max-width 100%
 | 
			
		||||
								max-height 512px
 | 
			
		||||
 | 
			
		||||
			> .no-history
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 2em 1em
 | 
			
		||||
				text-align center
 | 
			
		||||
				color #999
 | 
			
		||||
				font-weight 500
 | 
			
		||||
 | 
			
		||||
			// TODO: element base media query
 | 
			
		||||
			@media (max-width 400px)
 | 
			
		||||
				> .search
 | 
			
		||||
					> .result
 | 
			
		||||
						> .users
 | 
			
		||||
							> li
 | 
			
		||||
								> a
 | 
			
		||||
									padding 8px 16px
 | 
			
		||||
 | 
			
		||||
				> .history
 | 
			
		||||
					> a
 | 
			
		||||
						padding 16px
 | 
			
		||||
						font-size 14px
 | 
			
		||||
 | 
			
		||||
						> div
 | 
			
		||||
							> .avatar
 | 
			
		||||
								margin 0 12px 0 0
 | 
			
		||||
 | 
			
		||||
	</style>
 | 
			
		||||
	<script>
 | 
			
		||||
		@mixin \i
 | 
			
		||||
		@mixin \api
 | 
			
		||||
 | 
			
		||||
		@search-result = []
 | 
			
		||||
 | 
			
		||||
		@on \mount ~>
 | 
			
		||||
			@api \messaging/history
 | 
			
		||||
			.then (history) ~>
 | 
			
		||||
				@is-loading = false
 | 
			
		||||
				history.for-each (message) ~>
 | 
			
		||||
					message.is_me = message.user_id == @I.id
 | 
			
		||||
					message._click = ~>
 | 
			
		||||
						if message.is_me
 | 
			
		||||
							@trigger \navigate-user message.recipient
 | 
			
		||||
						else
 | 
			
		||||
							@trigger \navigate-user message.user
 | 
			
		||||
				@history = history
 | 
			
		||||
				@update!
 | 
			
		||||
			.catch (err) ~>
 | 
			
		||||
				console.error err
 | 
			
		||||
 | 
			
		||||
		@search = ~>
 | 
			
		||||
			q = @refs.search-input.value
 | 
			
		||||
			if q == ''
 | 
			
		||||
				@search-result = []
 | 
			
		||||
			else
 | 
			
		||||
				@api \users/search do
 | 
			
		||||
					query: q
 | 
			
		||||
				.then (users) ~>
 | 
			
		||||
					users.for-each (user) ~>
 | 
			
		||||
						user._click = ~>
 | 
			
		||||
							@trigger \navigate-user user
 | 
			
		||||
							@search-result = []
 | 
			
		||||
					@search-result = users
 | 
			
		||||
					@update!
 | 
			
		||||
				.catch (err) ~>
 | 
			
		||||
					console.error err
 | 
			
		||||
	</script>
 | 
			
		||||
</mk-messaging>
 | 
			
		||||
							
								
								
									
										230
									
								
								src/web/app/common/tags/messaging/message.tag
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								src/web/app/common/tags/messaging/message.tag
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,230 @@
 | 
			
		||||
<mk-messaging-message data-is-me={ message.is_me }><a class="avatar-anchor" href={ CONFIG.url + '/' + message.user.username } title={ message.user.username } target="_blank"><img class="avatar" src={ message.user.avatar_url + '?thumbnail&size=64' } alt=""/></a>
 | 
			
		||||
	<div class="content-container">
 | 
			
		||||
		<div class="balloon">
 | 
			
		||||
			<p class="read" if={ message.is_me && message.is_read }>既読</p>
 | 
			
		||||
			<button class="delete-button" if={ message.is_me } title="メッセージを削除"><img src="/_/resources/desktop/messaging/delete.png" alt="Delete"/></button>
 | 
			
		||||
			<div class="content" if={ !message.is_deleted }>
 | 
			
		||||
				<div ref="text"></div>
 | 
			
		||||
				<div class="image" if={ message.file }><img src={ message.file.url } alt="image" title={ message.file.name }/></div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="content" if={ message.is_deleted }>
 | 
			
		||||
				<p class="is-deleted">このメッセージは削除されました</p>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<footer>
 | 
			
		||||
			<mk-time time={ message.created_at }></mk-time><i class="fa fa-pencil is-edited" if={ message.is_edited }></i>
 | 
			
		||||
		</footer>
 | 
			
		||||
	</div>
 | 
			
		||||
	<style type="stylus">
 | 
			
		||||
		:scope
 | 
			
		||||
			$me-balloon-color = #23A7B6
 | 
			
		||||
 | 
			
		||||
			display block
 | 
			
		||||
			padding 10px 12px 10px 12px
 | 
			
		||||
			background-color transparent
 | 
			
		||||
 | 
			
		||||
			&:after
 | 
			
		||||
				content ""
 | 
			
		||||
				display block
 | 
			
		||||
				clear both
 | 
			
		||||
 | 
			
		||||
			> .avatar-anchor
 | 
			
		||||
				display block
 | 
			
		||||
 | 
			
		||||
				> .avatar
 | 
			
		||||
					display block
 | 
			
		||||
					min-width 54px
 | 
			
		||||
					min-height 54px
 | 
			
		||||
					max-width 54px
 | 
			
		||||
					max-height 54px
 | 
			
		||||
					margin 0
 | 
			
		||||
					border-radius 8px
 | 
			
		||||
					transition all 0.1s ease
 | 
			
		||||
 | 
			
		||||
			> .content-container
 | 
			
		||||
				display block
 | 
			
		||||
				margin 0 12px
 | 
			
		||||
				padding 0
 | 
			
		||||
				max-width calc(100% - 78px)
 | 
			
		||||
 | 
			
		||||
				> .balloon
 | 
			
		||||
					display block
 | 
			
		||||
					float inherit
 | 
			
		||||
					margin 0
 | 
			
		||||
					padding 0
 | 
			
		||||
					max-width 100%
 | 
			
		||||
					min-height 38px
 | 
			
		||||
					border-radius 16px
 | 
			
		||||
 | 
			
		||||
					&:before
 | 
			
		||||
						content ""
 | 
			
		||||
						pointer-events none
 | 
			
		||||
						display block
 | 
			
		||||
						position absolute
 | 
			
		||||
						top 12px
 | 
			
		||||
 | 
			
		||||
					&:hover
 | 
			
		||||
						> .delete-button
 | 
			
		||||
							display block
 | 
			
		||||
 | 
			
		||||
					> .delete-button
 | 
			
		||||
						display none
 | 
			
		||||
						position absolute
 | 
			
		||||
						z-index 1
 | 
			
		||||
						top -4px
 | 
			
		||||
						right -4px
 | 
			
		||||
						margin 0
 | 
			
		||||
						padding 0
 | 
			
		||||
						cursor pointer
 | 
			
		||||
						outline none
 | 
			
		||||
						border none
 | 
			
		||||
						border-radius 0
 | 
			
		||||
						box-shadow none
 | 
			
		||||
						background transparent
 | 
			
		||||
 | 
			
		||||
						> img
 | 
			
		||||
							vertical-align bottom
 | 
			
		||||
							width 16px
 | 
			
		||||
							height 16px
 | 
			
		||||
							cursor pointer
 | 
			
		||||
 | 
			
		||||
					> .read
 | 
			
		||||
						user-select none
 | 
			
		||||
						display block
 | 
			
		||||
						position absolute
 | 
			
		||||
						z-index 1
 | 
			
		||||
						bottom -4px
 | 
			
		||||
						left -12px
 | 
			
		||||
						margin 0
 | 
			
		||||
						color rgba(0, 0, 0, 0.5)
 | 
			
		||||
						font-size 11px
 | 
			
		||||
 | 
			
		||||
					> .content
 | 
			
		||||
 | 
			
		||||
						> .is-deleted
 | 
			
		||||
							display block
 | 
			
		||||
							margin 0
 | 
			
		||||
							padding 0
 | 
			
		||||
							overflow hidden
 | 
			
		||||
							overflow-wrap break-word
 | 
			
		||||
							font-size 1em
 | 
			
		||||
							color rgba(0, 0, 0, 0.5)
 | 
			
		||||
 | 
			
		||||
						> [ref='text']
 | 
			
		||||
							display block
 | 
			
		||||
							margin 0
 | 
			
		||||
							padding 8px 16px
 | 
			
		||||
							overflow hidden
 | 
			
		||||
							overflow-wrap break-word
 | 
			
		||||
							font-size 1em
 | 
			
		||||
							color rgba(0, 0, 0, 0.8)
 | 
			
		||||
 | 
			
		||||
							&, *
 | 
			
		||||
								user-select text
 | 
			
		||||
								cursor auto
 | 
			
		||||
 | 
			
		||||
							& + .file
 | 
			
		||||
								&.image
 | 
			
		||||
									> img
 | 
			
		||||
										border-radius 0 0 16px 16px
 | 
			
		||||
 | 
			
		||||
						> .file
 | 
			
		||||
							&.image
 | 
			
		||||
								> img
 | 
			
		||||
									display block
 | 
			
		||||
									max-width 100%
 | 
			
		||||
									max-height 512px
 | 
			
		||||
									border-radius 16px
 | 
			
		||||
 | 
			
		||||
				> footer
 | 
			
		||||
					display block
 | 
			
		||||
					clear both
 | 
			
		||||
					margin 0
 | 
			
		||||
					padding 2px
 | 
			
		||||
					font-size 10px
 | 
			
		||||
					color rgba(0, 0, 0, 0.4)
 | 
			
		||||
 | 
			
		||||
					> .is-edited
 | 
			
		||||
						margin-left 4px
 | 
			
		||||
 | 
			
		||||
			&:not([data-is-me='true'])
 | 
			
		||||
				> .avatar-anchor
 | 
			
		||||
					float left
 | 
			
		||||
 | 
			
		||||
				> .content-container
 | 
			
		||||
					float left
 | 
			
		||||
 | 
			
		||||
					> .balloon
 | 
			
		||||
						background #eee
 | 
			
		||||
 | 
			
		||||
						&:before
 | 
			
		||||
							left -14px
 | 
			
		||||
							border-top solid 8px transparent
 | 
			
		||||
							border-right solid 8px #eee
 | 
			
		||||
							border-bottom solid 8px transparent
 | 
			
		||||
							border-left solid 8px transparent
 | 
			
		||||
 | 
			
		||||
					> footer
 | 
			
		||||
						text-align left
 | 
			
		||||
 | 
			
		||||
			&[data-is-me='true']
 | 
			
		||||
				> .avatar-anchor
 | 
			
		||||
					float right
 | 
			
		||||
 | 
			
		||||
				> .content-container
 | 
			
		||||
					float right
 | 
			
		||||
 | 
			
		||||
					> .balloon
 | 
			
		||||
						background $me-balloon-color
 | 
			
		||||
 | 
			
		||||
						&:before
 | 
			
		||||
							right -14px
 | 
			
		||||
							left auto
 | 
			
		||||
							border-top solid 8px transparent
 | 
			
		||||
							border-right solid 8px transparent
 | 
			
		||||
							border-bottom solid 8px transparent
 | 
			
		||||
							border-left solid 8px $me-balloon-color
 | 
			
		||||
 | 
			
		||||
						> .content
 | 
			
		||||
 | 
			
		||||
							> p.is-deleted
 | 
			
		||||
								color rgba(255, 255, 255, 0.5)
 | 
			
		||||
 | 
			
		||||
							> [ref='text']
 | 
			
		||||
								&, *
 | 
			
		||||
									color #fff !important
 | 
			
		||||
 | 
			
		||||
					> footer
 | 
			
		||||
						text-align right
 | 
			
		||||
 | 
			
		||||
			&[data-is-deleted='true']
 | 
			
		||||
					> .content-container
 | 
			
		||||
						opacity 0.5
 | 
			
		||||
 | 
			
		||||
	</style>
 | 
			
		||||
	<script>
 | 
			
		||||
		@mixin \i
 | 
			
		||||
		@mixin \text
 | 
			
		||||
 | 
			
		||||
		@message = @opts.message
 | 
			
		||||
		@message.is_me = @message.user.id == @I.id
 | 
			
		||||
 | 
			
		||||
		@on \mount ~>
 | 
			
		||||
			if @message.text?
 | 
			
		||||
				tokens = @analyze @message.text
 | 
			
		||||
 | 
			
		||||
				@refs.text.innerHTML = @compile tokens
 | 
			
		||||
 | 
			
		||||
				@refs.text.children.for-each (e) ~>
 | 
			
		||||
					if e.tag-name == \MK-URL
 | 
			
		||||
						riot.mount e
 | 
			
		||||
 | 
			
		||||
				# URLをプレビュー
 | 
			
		||||
				tokens
 | 
			
		||||
					.filter (t) -> t.type == \link
 | 
			
		||||
					.map (t) ~>
 | 
			
		||||
						@preview = @refs.text.append-child document.create-element \mk-url-preview
 | 
			
		||||
						riot.mount @preview, do
 | 
			
		||||
							url: t.content
 | 
			
		||||
	</script>
 | 
			
		||||
</mk-messaging-message>
 | 
			
		||||
							
								
								
									
										224
									
								
								src/web/app/common/tags/messaging/room.tag
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								src/web/app/common/tags/messaging/room.tag
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,224 @@
 | 
			
		||||
<mk-messaging-room>
 | 
			
		||||
	<div class="stream" ref="stream">
 | 
			
		||||
		<p class="initializing" if={ init }><i class="fa fa-spinner fa-spin"></i>読み込み中</p>
 | 
			
		||||
		<p class="empty" if={ !init && messages.length == 0 }><i class="fa fa-info-circle"></i>このユーザーとまだ会話したことがありません</p>
 | 
			
		||||
		<virtual each={ message, i in messages }>
 | 
			
		||||
			<mk-messaging-message message={ message }></mk-messaging-message>
 | 
			
		||||
			<p class="date" if={ i != messages.length - 1 && message._date != messages[i + 1]._date }><span>{ messages[i + 1]._datetext }</span></p>
 | 
			
		||||
		</virtual>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="typings"></div>
 | 
			
		||||
	<footer>
 | 
			
		||||
		<div ref="notifications"></div>
 | 
			
		||||
		<div class="grippie" title="ドラッグしてフォームの広さを調整"></div>
 | 
			
		||||
		<mk-messaging-form user={ user }></mk-messaging-form>
 | 
			
		||||
	</footer>
 | 
			
		||||
	<style type="stylus">
 | 
			
		||||
		:scope
 | 
			
		||||
			display block
 | 
			
		||||
 | 
			
		||||
			> .stream
 | 
			
		||||
				max-width 600px
 | 
			
		||||
				margin 0 auto
 | 
			
		||||
 | 
			
		||||
				> .empty
 | 
			
		||||
					width 100%
 | 
			
		||||
					margin 0
 | 
			
		||||
					padding 16px 8px 8px 8px
 | 
			
		||||
					text-align center
 | 
			
		||||
					font-size 0.8em
 | 
			
		||||
					color rgba(0, 0, 0, 0.4)
 | 
			
		||||
 | 
			
		||||
					i
 | 
			
		||||
						margin-right 4px
 | 
			
		||||
 | 
			
		||||
				> .no-history
 | 
			
		||||
					display block
 | 
			
		||||
					margin 0
 | 
			
		||||
					padding 16px
 | 
			
		||||
					text-align center
 | 
			
		||||
					font-size 0.8em
 | 
			
		||||
					color rgba(0, 0, 0, 0.4)
 | 
			
		||||
 | 
			
		||||
					i
 | 
			
		||||
						margin-right 4px
 | 
			
		||||
 | 
			
		||||
				> .message
 | 
			
		||||
					// something
 | 
			
		||||
 | 
			
		||||
				> .date
 | 
			
		||||
					display block
 | 
			
		||||
					margin 8px 0
 | 
			
		||||
					text-align center
 | 
			
		||||
 | 
			
		||||
					&:before
 | 
			
		||||
						content ''
 | 
			
		||||
						display block
 | 
			
		||||
						position absolute
 | 
			
		||||
						height 1px
 | 
			
		||||
						width 90%
 | 
			
		||||
						top 16px
 | 
			
		||||
						left 0
 | 
			
		||||
						right 0
 | 
			
		||||
						margin 0 auto
 | 
			
		||||
						background rgba(0, 0, 0, 0.1)
 | 
			
		||||
 | 
			
		||||
					> span
 | 
			
		||||
						display inline-block
 | 
			
		||||
						margin 0
 | 
			
		||||
						padding 0 16px
 | 
			
		||||
						//font-weight bold
 | 
			
		||||
						line-height 32px
 | 
			
		||||
						color rgba(0, 0, 0, 0.3)
 | 
			
		||||
						background #fff
 | 
			
		||||
 | 
			
		||||
			> footer
 | 
			
		||||
				position -webkit-sticky
 | 
			
		||||
				position sticky
 | 
			
		||||
				z-index 2
 | 
			
		||||
				bottom 0
 | 
			
		||||
				width 100%
 | 
			
		||||
				max-width 600px
 | 
			
		||||
				margin 0 auto
 | 
			
		||||
				padding 0
 | 
			
		||||
				background rgba(255, 255, 255, 0.95)
 | 
			
		||||
				background-clip content-box
 | 
			
		||||
 | 
			
		||||
				> [ref='notifications']
 | 
			
		||||
					position absolute
 | 
			
		||||
					top -48px
 | 
			
		||||
					width 100%
 | 
			
		||||
					padding 8px 0
 | 
			
		||||
					text-align center
 | 
			
		||||
 | 
			
		||||
					> p
 | 
			
		||||
						display inline-block
 | 
			
		||||
						margin 0
 | 
			
		||||
						padding 0 12px 0 28px
 | 
			
		||||
						cursor pointer
 | 
			
		||||
						line-height 32px
 | 
			
		||||
						font-size 12px
 | 
			
		||||
						color $theme-color-foreground
 | 
			
		||||
						background $theme-color
 | 
			
		||||
						border-radius 16px
 | 
			
		||||
						transition opacity 1s ease
 | 
			
		||||
 | 
			
		||||
						> i
 | 
			
		||||
							position absolute
 | 
			
		||||
							top 0
 | 
			
		||||
							left 10px
 | 
			
		||||
							line-height 32px
 | 
			
		||||
							font-size 16px
 | 
			
		||||
 | 
			
		||||
				> .grippie
 | 
			
		||||
					height 10px
 | 
			
		||||
					margin-top -10px
 | 
			
		||||
					background transparent
 | 
			
		||||
					cursor ns-resize
 | 
			
		||||
 | 
			
		||||
					&:hover
 | 
			
		||||
						//background rgba(0, 0, 0, 0.1)
 | 
			
		||||
 | 
			
		||||
					&:active
 | 
			
		||||
						//background rgba(0, 0, 0, 0.2)
 | 
			
		||||
 | 
			
		||||
	</style>
 | 
			
		||||
	<script>
 | 
			
		||||
		@mixin \i
 | 
			
		||||
		@mixin \api
 | 
			
		||||
		@mixin \messaging-stream
 | 
			
		||||
 | 
			
		||||
		@user = @opts.user
 | 
			
		||||
		@init = true
 | 
			
		||||
		@sending = false
 | 
			
		||||
		@messages = []
 | 
			
		||||
 | 
			
		||||
		@connection = new @MessagingStreamConnection @I, @user.id
 | 
			
		||||
 | 
			
		||||
		@on \mount ~>
 | 
			
		||||
			@connection.event.on \message @on-message
 | 
			
		||||
			@connection.event.on \read @on-read
 | 
			
		||||
 | 
			
		||||
			document.add-event-listener \visibilitychange @on-visibilitychange
 | 
			
		||||
 | 
			
		||||
			@api \messaging/messages do
 | 
			
		||||
				user_id: @user.id
 | 
			
		||||
			.then (messages) ~>
 | 
			
		||||
				@init = false
 | 
			
		||||
				@messages = messages.reverse!
 | 
			
		||||
				@update!
 | 
			
		||||
				@scroll-to-bottom!
 | 
			
		||||
			.catch (err) ~>
 | 
			
		||||
				console.error err
 | 
			
		||||
 | 
			
		||||
		@on \unmount ~>
 | 
			
		||||
			@connection.event.off \message @on-message
 | 
			
		||||
			@connection.event.off \read @on-read
 | 
			
		||||
			@connection.close!
 | 
			
		||||
 | 
			
		||||
			document.remove-event-listener \visibilitychange @on-visibilitychange
 | 
			
		||||
 | 
			
		||||
		@on \update ~>
 | 
			
		||||
			@messages.for-each (message) ~>
 | 
			
		||||
				date = (new Date message.created_at).get-date!
 | 
			
		||||
				month = (new Date message.created_at).get-month! + 1
 | 
			
		||||
				message._date = date
 | 
			
		||||
				message._datetext = month + '月 ' + date + '日'
 | 
			
		||||
 | 
			
		||||
		@on-message = (message) ~>
 | 
			
		||||
			is-bottom = @is-bottom!
 | 
			
		||||
 | 
			
		||||
			@messages.push message
 | 
			
		||||
			if message.user_id != @I.id and not document.hidden
 | 
			
		||||
				@connection.socket.send JSON.stringify do
 | 
			
		||||
					type: \read
 | 
			
		||||
					id: message.id
 | 
			
		||||
			@update!
 | 
			
		||||
 | 
			
		||||
			if is-bottom
 | 
			
		||||
				# Scroll to bottom
 | 
			
		||||
				@scroll-to-bottom!
 | 
			
		||||
			else if message.user_id != @I.id
 | 
			
		||||
				# Notify
 | 
			
		||||
				@notify '新しいメッセージがあります'
 | 
			
		||||
 | 
			
		||||
		@on-read = (ids) ~>
 | 
			
		||||
			if not Array.isArray ids then ids = [ids]
 | 
			
		||||
			ids.for-each (id) ~>
 | 
			
		||||
				if (@messages.some (x) ~> x.id == id)
 | 
			
		||||
					exist = (@messages.map (x) -> x.id).index-of id
 | 
			
		||||
					@messages[exist].is_read = true
 | 
			
		||||
					@update!
 | 
			
		||||
 | 
			
		||||
		@is-bottom = ~>
 | 
			
		||||
			current = @refs.stream.scroll-top + @refs.stream.offset-height
 | 
			
		||||
			max = @refs.stream.scroll-height
 | 
			
		||||
			current > (max - 32)
 | 
			
		||||
 | 
			
		||||
		@scroll-to-bottom = ~>
 | 
			
		||||
			@refs.stream.scroll-top = @refs.stream.scroll-height
 | 
			
		||||
 | 
			
		||||
		@notify = (message) ~>
 | 
			
		||||
			n = document.create-element \p
 | 
			
		||||
			n.inner-HTML = '<i class="fa fa-arrow-circle-down"></i>' + message
 | 
			
		||||
			n.onclick = ~>
 | 
			
		||||
				@scroll-to-bottom!
 | 
			
		||||
				n.parent-node.remove-child n
 | 
			
		||||
			@refs.notifications.append-child n
 | 
			
		||||
 | 
			
		||||
			set-timeout ~>
 | 
			
		||||
				n.style.opacity = 0
 | 
			
		||||
				set-timeout ~>
 | 
			
		||||
					n.parent-node.remove-child n
 | 
			
		||||
				, 1000ms
 | 
			
		||||
			, 4000ms
 | 
			
		||||
 | 
			
		||||
		@on-visibilitychange = ~>
 | 
			
		||||
			if document.hidden then return
 | 
			
		||||
			@messages.for-each (message) ~>
 | 
			
		||||
				if message.user_id != @I.id and not message.is_read
 | 
			
		||||
					@connection.socket.send JSON.stringify do
 | 
			
		||||
						type: \read
 | 
			
		||||
						id: message.id
 | 
			
		||||
	</script>
 | 
			
		||||
</mk-messaging-room>
 | 
			
		||||
		Reference in New Issue
	
	Block a user