Add messaging widget
This commit is contained in:
		
							parent
							
								
									0a994e5b98
								
							
						
					
					
						commit
						ab2293aa4c
					
				
					 14 changed files with 186 additions and 7 deletions
				
			
		|  | @ -393,6 +393,9 @@ desktop: | |||
|     mk-access-log-home-widget: | ||||
|       title: "Access log" | ||||
| 
 | ||||
|     mk-messaging-home-widget: | ||||
|       title: "Messaging" | ||||
| 
 | ||||
|     mk-repost-form: | ||||
|       quote: "Quote..." | ||||
|       cancel: "Cancel" | ||||
|  |  | |||
|  | @ -393,6 +393,9 @@ desktop: | |||
|     mk-access-log-home-widget: | ||||
|       title: "アクセスログ" | ||||
| 
 | ||||
|     mk-messaging-home-widget: | ||||
|       title: "メッセージ" | ||||
| 
 | ||||
|     mk-repost-form: | ||||
|       quote: "引用する..." | ||||
|       cancel: "キャンセル" | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import Message from '../models/messaging-message'; | |||
| import { IMessagingMessage as IMessage } from '../models/messaging-message'; | ||||
| import publishUserStream from '../event'; | ||||
| import { publishMessagingStream } from '../event'; | ||||
| import { publishMessagingIndexStream } from '../event'; | ||||
| 
 | ||||
| /** | ||||
|  * Mark as read message(s) | ||||
|  | @ -49,6 +50,7 @@ export default ( | |||
| 
 | ||||
| 	// Publish event
 | ||||
| 	publishMessagingStream(otherpartyId, userId, 'read', ids.map(id => id.toString())); | ||||
| 	publishMessagingIndexStream(userId, 'read', ids.map(id => id.toString())); | ||||
| 
 | ||||
| 	// Calc count of my unread messages
 | ||||
| 	const count = await Message | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import DriveFile from '../../../models/drive-file'; | |||
| import serialize from '../../../serializers/messaging-message'; | ||||
| import publishUserStream from '../../../event'; | ||||
| import { publishMessagingStream } from '../../../event'; | ||||
| import { publishMessagingIndexStream } from '../../../event'; | ||||
| import config from '../../../../conf'; | ||||
| 
 | ||||
| /** | ||||
|  | @ -85,10 +86,12 @@ module.exports = (params, user) => new Promise(async (res, rej) => { | |||
| 
 | ||||
| 	// 自分のストリーム
 | ||||
| 	publishMessagingStream(message.user_id, message.recipient_id, 'message', messageObj); | ||||
| 	publishMessagingIndexStream(message.user_id, 'message', messageObj); | ||||
| 	publishUserStream(message.user_id, 'messaging_message', messageObj); | ||||
| 
 | ||||
| 	// 相手のストリーム
 | ||||
| 	publishMessagingStream(message.recipient_id, message.user_id, 'message', messageObj); | ||||
| 	publishMessagingIndexStream(message.recipient_id, 'message', messageObj); | ||||
| 	publishUserStream(message.recipient_id, 'messaging_message', messageObj); | ||||
| 
 | ||||
| 	// 3秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
 | ||||
|  |  | |||
|  | @ -25,6 +25,10 @@ class MisskeyEvent { | |||
| 		this.publish(`messaging-stream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value); | ||||
| 	} | ||||
| 
 | ||||
| 	public publishMessagingIndexStream(userId: ID, type: string, value?: any): void { | ||||
| 		this.publish(`messaging-index-stream:${userId}`, type, typeof value === 'undefined' ? null : value); | ||||
| 	} | ||||
| 
 | ||||
| 	public publishChannelStream(channelId: ID, type: string, value?: any): void { | ||||
| 		this.publish(`channel-stream:${channelId}`, type, typeof value === 'undefined' ? null : value); | ||||
| 	} | ||||
|  | @ -46,4 +50,6 @@ export const publishPostStream = ev.publishPostStream.bind(ev); | |||
| 
 | ||||
| export const publishMessagingStream = ev.publishMessagingStream.bind(ev); | ||||
| 
 | ||||
| export const publishMessagingIndexStream = ev.publishMessagingIndexStream.bind(ev); | ||||
| 
 | ||||
| export const publishChannelStream = ev.publishChannelStream.bind(ev); | ||||
|  |  | |||
							
								
								
									
										10
									
								
								src/api/stream/messaging-index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/api/stream/messaging-index.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| import * as websocket from 'websocket'; | ||||
| import * as redis from 'redis'; | ||||
| 
 | ||||
| export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void { | ||||
| 	// Subscribe messaging index stream
 | ||||
| 	subscriber.subscribe(`misskey:messaging-index-stream:${user._id}`); | ||||
| 	subscriber.on('message', (_, data) => { | ||||
| 		connection.send(data); | ||||
| 	}); | ||||
| } | ||||
|  | @ -8,6 +8,7 @@ import isNativeToken from './common/is-native-token'; | |||
| 
 | ||||
| import homeStream from './stream/home'; | ||||
| import messagingStream from './stream/messaging'; | ||||
| import messagingIndexStream from './stream/messaging-index'; | ||||
| import serverStream from './stream/server'; | ||||
| import requestsStream from './stream/requests'; | ||||
| import channelStream from './stream/channel'; | ||||
|  | @ -58,6 +59,7 @@ module.exports = (server: http.Server) => { | |||
| 		const channel = | ||||
| 			request.resourceURL.pathname === '/' ? homeStream : | ||||
| 			request.resourceURL.pathname === '/messaging' ? messagingStream : | ||||
| 			request.resourceURL.pathname === '/messaging-index' ? messagingIndexStream : | ||||
| 			null; | ||||
| 
 | ||||
| 		if (channel !== null) { | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import activateMe from './i'; | |||
| import activateApi from './api'; | ||||
| import ServerStreamManager from '../scripts/server-stream-manager'; | ||||
| import RequestsStreamManager from '../scripts/requests-stream-manager'; | ||||
| import MessagingIndexStream from '../scripts/messaging-index-stream-manager'; | ||||
| 
 | ||||
| export default (me, stream) => { | ||||
| 	activateMe(me); | ||||
|  | @ -13,4 +14,5 @@ export default (me, stream) => { | |||
| 
 | ||||
| 	(riot as any).mixin('server-stream', { serverStream: new ServerStreamManager() }); | ||||
| 	(riot as any).mixin('requests-stream', { requestsStream: new RequestsStreamManager() }); | ||||
| 	(riot as any).mixin('messaging-index-stream', { messagingIndexStream: new MessagingIndexStream(me) }); | ||||
| }; | ||||
|  |  | |||
							
								
								
									
										20
									
								
								src/web/app/common/scripts/messaging-index-stream-manager.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/web/app/common/scripts/messaging-index-stream-manager.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| import StreamManager from './stream-manager'; | ||||
| import Connection from './messaging-index-stream'; | ||||
| 
 | ||||
| export default class ServerStreamManager extends StreamManager<Connection> { | ||||
| 	private me; | ||||
| 
 | ||||
| 	constructor(me) { | ||||
| 		super(); | ||||
| 
 | ||||
| 		this.me = me; | ||||
| 	} | ||||
| 
 | ||||
| 	public getConnection() { | ||||
| 		if (this.connection == null) { | ||||
| 			this.connection = new Connection(this.me); | ||||
| 		} | ||||
| 
 | ||||
| 		return this.connection; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/web/app/common/scripts/messaging-index-stream.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/web/app/common/scripts/messaging-index-stream.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| import Stream from './stream'; | ||||
| 
 | ||||
| /** | ||||
|  * Messaging index stream connection | ||||
|  */ | ||||
| class Connection extends Stream { | ||||
| 	constructor(me) { | ||||
| 		super('messaging-index', { | ||||
| 			i: me.token | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| export default Connection; | ||||
|  | @ -1,5 +1,5 @@ | |||
| <mk-messaging> | ||||
| 	<div class="search"> | ||||
| <mk-messaging data-compact={ opts.compact }> | ||||
| 	<div class="search" if={ !opts.compact }> | ||||
| 		<div class="form"> | ||||
| 			<label for="search-input"><i class="fa fa-search"></i></label> | ||||
| 			<input ref="search" type="search" oninput={ search } onkeydown={ onSearchKeydown } placeholder="%i18n:common.tags.mk-messaging.search-user%"/> | ||||
|  | @ -36,6 +36,31 @@ | |||
| 		: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 | ||||
|  | @ -75,7 +100,7 @@ | |||
| 
 | ||||
| 					> input | ||||
| 						margin 0 | ||||
| 						padding 0 12px 0 38px | ||||
| 						padding 0 0 0 38px | ||||
| 						width 100% | ||||
| 						font-size 1em | ||||
| 						line-height 38px | ||||
|  | @ -299,22 +324,59 @@ | |||
| 		this.mixin('i'); | ||||
| 		this.mixin('api'); | ||||
| 
 | ||||
| 		this.mixin('messaging-index-stream'); | ||||
| 		this.connection = this.messagingIndexStream.getConnection(); | ||||
| 		this.connectionId = this.messagingIndexStream.use(); | ||||
| 
 | ||||
| 		this.searchResult = []; | ||||
| 
 | ||||
| 		this.on('mount', () => { | ||||
| 			this.api('messaging/history').then(history => { | ||||
| 				this.isLoading = false; | ||||
| 				history.forEach(message => { | ||||
| 					message.is_me = message.user_id == this.I.id | ||||
| 		this.registerMessage = message => { | ||||
| 			message.is_me = message.user_id == this.I.id; | ||||
| 			message._click = () => { | ||||
| 				this.trigger('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.isLoading = 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 == '') { | ||||
|  |  | |||
							
								
								
									
										50
									
								
								src/web/app/desktop/tags/home-widgets/messaging.tag
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/web/app/desktop/tags/home-widgets/messaging.tag
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | |||
| <mk-messaging-home-widget> | ||||
| 	<virtual if={ data.design == 0 }> | ||||
| 		<p class="title"><i class="fa fa-comments"></i>%i18n:desktop.tags.mk-messaging-home-widget.title%</p> | ||||
| 	</virtual> | ||||
| 	<mk-messaging ref="index" compact={ true }/> | ||||
| 	<style> | ||||
| 		:scope | ||||
| 			display block | ||||
| 			overflow hidden | ||||
| 			background #fff | ||||
| 
 | ||||
| 			> .title | ||||
| 				z-index 2 | ||||
| 				margin 0 | ||||
| 				padding 0 16px | ||||
| 				line-height 42px | ||||
| 				font-size 0.9em | ||||
| 				font-weight bold | ||||
| 				color #888 | ||||
| 				box-shadow 0 1px rgba(0, 0, 0, 0.07) | ||||
| 
 | ||||
| 				> i | ||||
| 					margin-right 4px | ||||
| 
 | ||||
| 			> mk-messaging | ||||
| 				max-height 250px | ||||
| 				overflow auto | ||||
| 
 | ||||
| 	</style> | ||||
| 	<script> | ||||
| 		this.data = { | ||||
| 			design: 0 | ||||
| 		}; | ||||
| 
 | ||||
| 		this.mixin('widget'); | ||||
| 
 | ||||
| 		this.on('mount', () => { | ||||
| 			this.refs.index.on('navigate-user', user => { | ||||
| 				riot.mount(document.body.appendChild(document.createElement('mk-messaging-room-window')), { | ||||
| 					user: user | ||||
| 				}); | ||||
| 			}); | ||||
| 		}); | ||||
| 
 | ||||
| 		this.func = () => { | ||||
| 			if (++this.data.design == 2) this.data.design = 0; | ||||
| 			this.save(); | ||||
| 		}; | ||||
| 	</script> | ||||
| </mk-messaging-home-widget> | ||||
|  | @ -19,6 +19,7 @@ | |||
| 					<option value="user-recommendation">おすすめユーザー</option> | ||||
| 					<option value="recommended-polls">投票</option> | ||||
| 					<option value="post-form">投稿フォーム</option> | ||||
| 					<option value="messaging">メッセージ</option> | ||||
| 					<option value="channel">チャンネル</option> | ||||
| 					<option value="access-log">アクセスログ</option> | ||||
| 					<option value="server">サーバー情報</option> | ||||
|  |  | |||
|  | @ -44,6 +44,7 @@ require('./home-widgets/channel.tag'); | |||
| require('./home-widgets/timemachine.tag'); | ||||
| require('./home-widgets/post-form.tag'); | ||||
| require('./home-widgets/access-log.tag'); | ||||
| require('./home-widgets/messaging.tag'); | ||||
| require('./timeline.tag'); | ||||
| require('./messaging/window.tag'); | ||||
| require('./messaging/room-window.tag'); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue