[Client] Messagingをいろいろ
This commit is contained in:
parent
4c498320c2
commit
3d38356a91
14 changed files with 223 additions and 132 deletions
|
|
@ -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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue