diff options
| author | syuilo <syuilotan@yahoo.co.jp> | 2017-02-15 15:45:01 +0900 |
|---|---|---|
| committer | syuilo <syuilotan@yahoo.co.jp> | 2017-02-15 15:45:01 +0900 |
| commit | 3d38356a91dfbbb57b7feb4a0c069accb98dbb19 (patch) | |
| tree | 0b957876e63222a006f2b5e8a0061596400bbbd6 /src/web/app/common | |
| parent | [Client] 文言を修正 (diff) | |
| download | misskey-3d38356a91dfbbb57b7feb4a0c069accb98dbb19.tar.gz misskey-3d38356a91dfbbb57b7feb4a0c069accb98dbb19.tar.bz2 misskey-3d38356a91dfbbb57b7feb4a0c069accb98dbb19.zip | |
[Client] Messagingをいろいろ
Diffstat (limited to 'src/web/app/common')
| -rw-r--r-- | src/web/app/common/tags/index.js | 4 | ||||
| -rw-r--r-- | src/web/app/common/tags/messaging/form.tag | 159 | ||||
| -rw-r--r-- | src/web/app/common/tags/messaging/index.tag | 328 | ||||
| -rw-r--r-- | src/web/app/common/tags/messaging/message.tag | 230 | ||||
| -rw-r--r-- | src/web/app/common/tags/messaging/room.tag | 224 |
5 files changed, 945 insertions, 0 deletions
diff --git a/src/web/app/common/tags/index.js b/src/web/app/common/tags/index.js index 692a7070a4..21d817dbd2 100644 --- a/src/web/app/common/tags/index.js +++ b/src/web/app/common/tags/index.js @@ -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'); diff --git a/src/web/app/common/tags/messaging/form.tag b/src/web/app/common/tags/messaging/form.tag new file mode 100644 index 0000000000..8979edf288 --- /dev/null +++ b/src/web/app/common/tags/messaging/form.tag @@ -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> diff --git a/src/web/app/common/tags/messaging/index.tag b/src/web/app/common/tags/messaging/index.tag new file mode 100644 index 0000000000..2950f2a88f --- /dev/null +++ b/src/web/app/common/tags/messaging/index.tag @@ -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> diff --git a/src/web/app/common/tags/messaging/message.tag b/src/web/app/common/tags/messaging/message.tag new file mode 100644 index 0000000000..375a2fefcc --- /dev/null +++ b/src/web/app/common/tags/messaging/message.tag @@ -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> diff --git a/src/web/app/common/tags/messaging/room.tag b/src/web/app/common/tags/messaging/room.tag new file mode 100644 index 0000000000..3acfc14b0e --- /dev/null +++ b/src/web/app/common/tags/messaging/room.tag @@ -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> |