wip
This commit is contained in:
		
							parent
							
								
									e8affdc730
								
							
						
					
					
						commit
						b62f01b2f7
					
				
					 2 changed files with 448 additions and 456 deletions
				
			
		| 
						 | 
				
			
			@ -1,456 +0,0 @@
 | 
			
		|||
<mk-messaging data-compact={ opts.compact }>
 | 
			
		||||
	<div class="search" v-if="!opts.compact">
 | 
			
		||||
		<div class="form">
 | 
			
		||||
			<label for="search-input">%fa:search%</label>
 | 
			
		||||
			<input ref="search" type="search" oninput={ search } onkeydown={ onSearchKeydown } placeholder="%i18n:common.tags.mk-messaging.search-user%"/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="result">
 | 
			
		||||
			<ol class="users" v-if="searchResult.length > 0" ref="searchResult">
 | 
			
		||||
				<li each={ user, i in searchResult } onkeydown={ parent.onSearchResultKeydown.bind(null, i) } @click="user._click" tabindex="-1">
 | 
			
		||||
					<img class="avatar" src={ user.avatar_url + '?thumbnail&size=32' } alt=""/>
 | 
			
		||||
					<span class="name">{ user.name }</span>
 | 
			
		||||
					<span class="username">@{ user.username }</span>
 | 
			
		||||
				</li>
 | 
			
		||||
			</ol>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="history" v-if="history.length > 0">
 | 
			
		||||
		<template each={ history }>
 | 
			
		||||
			<a class="user" data-is-me={ is_me } data-is-read={ is_read } @click="_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 }/>
 | 
			
		||||
					</header>
 | 
			
		||||
					<div class="body">
 | 
			
		||||
						<p class="text"><span class="me" v-if="is_me">%i18n:common.tags.mk-messaging.you%:</span>{ text }</p>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</a>
 | 
			
		||||
		</template>
 | 
			
		||||
	</div>
 | 
			
		||||
	<p class="no-history" v-if="!fetching && history.length == 0">%i18n:common.tags.mk-messaging.no-history%</p>
 | 
			
		||||
	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
 | 
			
		||||
	<style lang="stylus" scoped>
 | 
			
		||||
		:scope
 | 
			
		||||
			display block
 | 
			
		||||
 | 
			
		||||
			&[data-compact]
 | 
			
		||||
				font-size 0.8em
 | 
			
		||||
 | 
			
		||||
				> .history
 | 
			
		||||
					> a
 | 
			
		||||
						&:last-child
 | 
			
		||||
							border-bottom none
 | 
			
		||||
 | 
			
		||||
						&:not([data-is-me]):not([data-is-read])
 | 
			
		||||
							> div
 | 
			
		||||
								background-image none
 | 
			
		||||
								border-left solid 4px #3aa2dc
 | 
			
		||||
 | 
			
		||||
						> div
 | 
			
		||||
							padding 16px
 | 
			
		||||
 | 
			
		||||
							> header
 | 
			
		||||
								> mk-time
 | 
			
		||||
									font-size 1em
 | 
			
		||||
 | 
			
		||||
							> .avatar
 | 
			
		||||
								width 42px
 | 
			
		||||
								height 42px
 | 
			
		||||
								margin 0 12px 0 0
 | 
			
		||||
 | 
			
		||||
			> .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
 | 
			
		||||
 | 
			
		||||
						> [data-fa]
 | 
			
		||||
							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 0 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
 | 
			
		||||
							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
 | 
			
		||||
							cursor pointer
 | 
			
		||||
 | 
			
		||||
							&:hover
 | 
			
		||||
							&:focus
 | 
			
		||||
								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
 | 
			
		||||
					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])
 | 
			
		||||
						> div
 | 
			
		||||
							background-image url("/assets/unread.svg")
 | 
			
		||||
							background-repeat no-repeat
 | 
			
		||||
							background-position 0 center
 | 
			
		||||
 | 
			
		||||
					&:after
 | 
			
		||||
						content ""
 | 
			
		||||
						display block
 | 
			
		||||
						clear both
 | 
			
		||||
 | 
			
		||||
					> div
 | 
			
		||||
						max-width 500px
 | 
			
		||||
						margin 0 auto
 | 
			
		||||
						padding 20px 30px
 | 
			
		||||
 | 
			
		||||
						&:after
 | 
			
		||||
							content ""
 | 
			
		||||
							display block
 | 
			
		||||
							clear both
 | 
			
		||||
 | 
			
		||||
						> 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
 | 
			
		||||
 | 
			
		||||
			> .fetching
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 16px
 | 
			
		||||
				text-align center
 | 
			
		||||
				color #aaa
 | 
			
		||||
 | 
			
		||||
				> [data-fa]
 | 
			
		||||
					margin-right 4px
 | 
			
		||||
 | 
			
		||||
			// TODO: element base media query
 | 
			
		||||
			@media (max-width 400px)
 | 
			
		||||
				> .search
 | 
			
		||||
					> .result
 | 
			
		||||
						> .users
 | 
			
		||||
							> li
 | 
			
		||||
								padding 8px 16px
 | 
			
		||||
 | 
			
		||||
				> .history
 | 
			
		||||
					> a
 | 
			
		||||
						&:not([data-is-me]):not([data-is-read])
 | 
			
		||||
							> div
 | 
			
		||||
								background-image none
 | 
			
		||||
								border-left solid 4px #3aa2dc
 | 
			
		||||
 | 
			
		||||
						> div
 | 
			
		||||
							padding 16px
 | 
			
		||||
							font-size 14px
 | 
			
		||||
 | 
			
		||||
							> .avatar
 | 
			
		||||
								margin 0 12px 0 0
 | 
			
		||||
 | 
			
		||||
	</style>
 | 
			
		||||
	<script lang="typescript">
 | 
			
		||||
		this.mixin('i');
 | 
			
		||||
		this.mixin('api');
 | 
			
		||||
 | 
			
		||||
		this.mixin('messaging-index-stream');
 | 
			
		||||
		this.connection = this.messagingIndexStream.getConnection();
 | 
			
		||||
		this.connectionId = this.messagingIndexStream.use();
 | 
			
		||||
 | 
			
		||||
		this.searchResult = [];
 | 
			
		||||
		this.history = [];
 | 
			
		||||
		this.fetching = true;
 | 
			
		||||
 | 
			
		||||
		this.registerMessage = message => {
 | 
			
		||||
			message.is_me = message.user_id == this.I.id;
 | 
			
		||||
			message._click = () => {
 | 
			
		||||
				this.$emit('navigate-user', message.is_me ? message.recipient : message.user);
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.on('mount', () => {
 | 
			
		||||
			this.connection.on('message', this.onMessage);
 | 
			
		||||
			this.connection.on('read', this.onRead);
 | 
			
		||||
 | 
			
		||||
			this.api('messaging/history').then(history => {
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
				history.forEach(message => {
 | 
			
		||||
					this.registerMessage(message);
 | 
			
		||||
				});
 | 
			
		||||
				this.history = history;
 | 
			
		||||
				this.update();
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.on('unmount', () => {
 | 
			
		||||
			this.connection.off('message', this.onMessage);
 | 
			
		||||
			this.connection.off('read', this.onRead);
 | 
			
		||||
			this.messagingIndexStream.dispose(this.connectionId);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.onMessage = message => {
 | 
			
		||||
			this.history = this.history.filter(m => !(
 | 
			
		||||
				(m.recipient_id == message.recipient_id && m.user_id == message.user_id) ||
 | 
			
		||||
				(m.recipient_id == message.user_id && m.user_id == message.recipient_id)));
 | 
			
		||||
 | 
			
		||||
			this.registerMessage(message);
 | 
			
		||||
 | 
			
		||||
			this.history.unshift(message);
 | 
			
		||||
			this.update();
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.onRead = ids => {
 | 
			
		||||
			ids.forEach(id => {
 | 
			
		||||
				const found = this.history.find(m => m.id == id);
 | 
			
		||||
				if (found) found.is_read = true;
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.update();
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.search = () => {
 | 
			
		||||
			const q = this.$refs.search.value;
 | 
			
		||||
			if (q == '') {
 | 
			
		||||
				this.searchResult = [];
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			this.api('users/search', {
 | 
			
		||||
				query: q,
 | 
			
		||||
				max: 5
 | 
			
		||||
			}).then(users => {
 | 
			
		||||
				users.forEach(user => {
 | 
			
		||||
					user._click = () => {
 | 
			
		||||
						this.$emit('navigate-user', user);
 | 
			
		||||
						this.searchResult = [];
 | 
			
		||||
					};
 | 
			
		||||
				});
 | 
			
		||||
				this.update({
 | 
			
		||||
					searchResult: users
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.onSearchKeydown = e => {
 | 
			
		||||
			switch (e.which) {
 | 
			
		||||
				case 9: // [TAB]
 | 
			
		||||
				case 40: // [↓]
 | 
			
		||||
					e.preventDefault();
 | 
			
		||||
					e.stopPropagation();
 | 
			
		||||
					this.$refs.searchResult.childNodes[0].focus();
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.onSearchResultKeydown = (i, e) => {
 | 
			
		||||
			const cancel = () => {
 | 
			
		||||
				e.preventDefault();
 | 
			
		||||
				e.stopPropagation();
 | 
			
		||||
			};
 | 
			
		||||
			switch (true) {
 | 
			
		||||
				case e.which == 10: // [ENTER]
 | 
			
		||||
				case e.which == 13: // [ENTER]
 | 
			
		||||
					cancel();
 | 
			
		||||
					this.searchResult[i]._click();
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				case e.which == 27: // [ESC]
 | 
			
		||||
					cancel();
 | 
			
		||||
					this.$refs.search.focus();
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				case e.which == 9 && e.shiftKey: // [TAB] + [Shift]
 | 
			
		||||
				case e.which == 38: // [↑]
 | 
			
		||||
					cancel();
 | 
			
		||||
					(this.$refs.searchResult.childNodes[i].previousElementSibling || this.$refs.searchResult.childNodes[this.searchResult.length - 1]).focus();
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				case e.which == 9: // [TAB]
 | 
			
		||||
				case e.which == 40: // [↓]
 | 
			
		||||
					cancel();
 | 
			
		||||
					(this.$refs.searchResult.childNodes[i].nextElementSibling || this.$refs.searchResult.childNodes[0]).focus();
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
	</script>
 | 
			
		||||
</mk-messaging>
 | 
			
		||||
							
								
								
									
										448
									
								
								src/web/app/common/views/components/messaging.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										448
									
								
								src/web/app/common/views/components/messaging.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,448 @@
 | 
			
		|||
<template>
 | 
			
		||||
<div class="mk-messaging" :data-compact="compact">
 | 
			
		||||
	<div class="search" v-if="!opts.compact">
 | 
			
		||||
		<div class="form">
 | 
			
		||||
			<label for="search-input">%fa:search%</label>
 | 
			
		||||
			<input v-model="q" type="search" @input="search" @keydown="onSearchKeydown" placeholder="%i18n:common.tags.mk-messaging.search-user%"/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="result">
 | 
			
		||||
			<ol class="users" v-if="searchResult.length > 0" ref="searchResult">
 | 
			
		||||
				<li each={ user, i in searchResult }
 | 
			
		||||
					@keydown.enter="navigate(user)"
 | 
			
		||||
					onkeydown={ parent.onSearchResultKeydown.bind(null, i) }
 | 
			
		||||
					@click="user._click"
 | 
			
		||||
					tabindex="-1"
 | 
			
		||||
				>
 | 
			
		||||
					<img class="avatar" src={ user.avatar_url + '?thumbnail&size=32' } alt=""/>
 | 
			
		||||
					<span class="name">{ user.name }</span>
 | 
			
		||||
					<span class="username">@{ user.username }</span>
 | 
			
		||||
				</li>
 | 
			
		||||
			</ol>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="history" v-if="history.length > 0">
 | 
			
		||||
		<template each={ history }>
 | 
			
		||||
			<a class="user" data-is-me={ is_me } data-is-read={ is_read } @click="navigate(isMe(message) ? message.recipient : message.user)">
 | 
			
		||||
				<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 }/>
 | 
			
		||||
					</header>
 | 
			
		||||
					<div class="body">
 | 
			
		||||
						<p class="text"><span class="me" v-if="is_me">%i18n:common.tags.mk-messaging.you%:</span>{ text }</p>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</a>
 | 
			
		||||
		</template>
 | 
			
		||||
	</div>
 | 
			
		||||
	<p class="no-history" v-if="!fetching && history.length == 0">%i18n:common.tags.mk-messaging.no-history%</p>
 | 
			
		||||
	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: {
 | 
			
		||||
		compact: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			default: false
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			moreFetching: false,
 | 
			
		||||
			messages: [],
 | 
			
		||||
			q: null,
 | 
			
		||||
			result: [],
 | 
			
		||||
			connection: null,
 | 
			
		||||
			connectionId: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.connection = this.$root.$data.os.streams.messagingIndexStream.getConnection();
 | 
			
		||||
		this.connectionId = this.$root.$data.os.streams.messagingIndexStream.use();
 | 
			
		||||
 | 
			
		||||
		this.connection.on('message', this.onMessage);
 | 
			
		||||
		this.connection.on('read', this.onRead);
 | 
			
		||||
 | 
			
		||||
		this.$root.$data.os.api('messaging/history').then(messages => {
 | 
			
		||||
			this.fetching = false;
 | 
			
		||||
			this.messages = messages;
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.off('message', this.onMessage);
 | 
			
		||||
		this.connection.off('read', this.onRead);
 | 
			
		||||
		this.$root.$data.os.stream.dispose(this.connectionId);
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		isMe(message) {
 | 
			
		||||
			return message.user_id == this.$root.$data.os.i.id;
 | 
			
		||||
		},
 | 
			
		||||
		onMessage(message) {
 | 
			
		||||
			this.messages = this.messages.filter(m => !(
 | 
			
		||||
				(m.recipient_id == message.recipient_id && m.user_id == message.user_id) ||
 | 
			
		||||
				(m.recipient_id == message.user_id && m.user_id == message.recipient_id)));
 | 
			
		||||
 | 
			
		||||
			this.messages.unshift(message);
 | 
			
		||||
		},
 | 
			
		||||
		onRead(ids) {
 | 
			
		||||
			ids.forEach(id => {
 | 
			
		||||
				const found = this.messages.find(m => m.id == id);
 | 
			
		||||
				if (found) found.is_read = true;
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		search() {
 | 
			
		||||
			if (this.q == '') {
 | 
			
		||||
				this.result = [];
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			this.$root.$data.os.api('users/search', {
 | 
			
		||||
				query: this.q,
 | 
			
		||||
				max: 5
 | 
			
		||||
			}).then(users => {
 | 
			
		||||
				this.result = users;
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		navigate(user) {
 | 
			
		||||
			this.$emit('navigate', user);
 | 
			
		||||
		},
 | 
			
		||||
		onSearchKeydown(e) {
 | 
			
		||||
			switch (e.which) {
 | 
			
		||||
				case 9: // [TAB]
 | 
			
		||||
				case 40: // [↓]
 | 
			
		||||
					e.preventDefault();
 | 
			
		||||
					e.stopPropagation();
 | 
			
		||||
					(this.$refs.searchResult as any).childNodes[0].focus();
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		onSearchResultKeydown(i, e) {
 | 
			
		||||
			const cancel = () => {
 | 
			
		||||
				e.preventDefault();
 | 
			
		||||
				e.stopPropagation();
 | 
			
		||||
			};
 | 
			
		||||
			switch (true) {
 | 
			
		||||
				case e.which == 27: // [ESC]
 | 
			
		||||
					cancel();
 | 
			
		||||
					this.$refs.search.focus();
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				case e.which == 9 && e.shiftKey: // [TAB] + [Shift]
 | 
			
		||||
				case e.which == 38: // [↑]
 | 
			
		||||
					cancel();
 | 
			
		||||
					(this.$refs.searchResult.childNodes[i].previousElementSibling || this.$refs.searchResult.childNodes[this.searchResult.length - 1]).focus();
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				case e.which == 9: // [TAB]
 | 
			
		||||
				case e.which == 40: // [↓]
 | 
			
		||||
					cancel();
 | 
			
		||||
					(this.$refs.searchResult.childNodes[i].nextElementSibling || this.$refs.searchResult.childNodes[0]).focus();
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.mk-messaging
 | 
			
		||||
 | 
			
		||||
	&[data-compact]
 | 
			
		||||
		font-size 0.8em
 | 
			
		||||
 | 
			
		||||
		> .history
 | 
			
		||||
			> a
 | 
			
		||||
				&:last-child
 | 
			
		||||
					border-bottom none
 | 
			
		||||
 | 
			
		||||
				&:not([data-is-me]):not([data-is-read])
 | 
			
		||||
					> div
 | 
			
		||||
						background-image none
 | 
			
		||||
						border-left solid 4px #3aa2dc
 | 
			
		||||
 | 
			
		||||
				> div
 | 
			
		||||
					padding 16px
 | 
			
		||||
 | 
			
		||||
					> header
 | 
			
		||||
						> mk-time
 | 
			
		||||
							font-size 1em
 | 
			
		||||
 | 
			
		||||
					> .avatar
 | 
			
		||||
						width 42px
 | 
			
		||||
						height 42px
 | 
			
		||||
						margin 0 12px 0 0
 | 
			
		||||
 | 
			
		||||
	> .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
 | 
			
		||||
 | 
			
		||||
				> [data-fa]
 | 
			
		||||
					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 0 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
 | 
			
		||||
					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
 | 
			
		||||
					cursor pointer
 | 
			
		||||
 | 
			
		||||
					&:hover
 | 
			
		||||
					&:focus
 | 
			
		||||
						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
 | 
			
		||||
			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])
 | 
			
		||||
				> div
 | 
			
		||||
					background-image url("/assets/unread.svg")
 | 
			
		||||
					background-repeat no-repeat
 | 
			
		||||
					background-position 0 center
 | 
			
		||||
 | 
			
		||||
			&:after
 | 
			
		||||
				content ""
 | 
			
		||||
				display block
 | 
			
		||||
				clear both
 | 
			
		||||
 | 
			
		||||
			> div
 | 
			
		||||
				max-width 500px
 | 
			
		||||
				margin 0 auto
 | 
			
		||||
				padding 20px 30px
 | 
			
		||||
 | 
			
		||||
				&:after
 | 
			
		||||
					content ""
 | 
			
		||||
					display block
 | 
			
		||||
					clear both
 | 
			
		||||
 | 
			
		||||
				> 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
 | 
			
		||||
 | 
			
		||||
	> .fetching
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
 | 
			
		||||
		> [data-fa]
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 | 
			
		||||
	// TODO: element base media query
 | 
			
		||||
	@media (max-width 400px)
 | 
			
		||||
		> .search
 | 
			
		||||
			> .result
 | 
			
		||||
				> .users
 | 
			
		||||
					> li
 | 
			
		||||
						padding 8px 16px
 | 
			
		||||
 | 
			
		||||
		> .history
 | 
			
		||||
			> a
 | 
			
		||||
				&:not([data-is-me]):not([data-is-read])
 | 
			
		||||
					> div
 | 
			
		||||
						background-image none
 | 
			
		||||
						border-left solid 4px #3aa2dc
 | 
			
		||||
 | 
			
		||||
				> div
 | 
			
		||||
					padding 16px
 | 
			
		||||
					font-size 14px
 | 
			
		||||
 | 
			
		||||
					> .avatar
 | 
			
		||||
						margin 0 12px 0 0
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue