🎨 Add site and rename app
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
<template src="./template.html"></template>
|
||||
<script src="./script.js"></script>
|
||||
<style src="./style.styl" lang="stylus" scoped></style>
|
||||
@@ -0,0 +1,223 @@
|
||||
import Tooltip from '@/components/tooltip'
|
||||
|
||||
export default
|
||||
{
|
||||
name: 'chat',
|
||||
|
||||
components:
|
||||
{
|
||||
Tooltip
|
||||
},
|
||||
|
||||
data()
|
||||
{
|
||||
return {
|
||||
userName: '',
|
||||
messageText: ''
|
||||
}
|
||||
},
|
||||
|
||||
computed:
|
||||
{
|
||||
open()
|
||||
{
|
||||
return this.$store.state.chat.open
|
||||
},
|
||||
|
||||
question()
|
||||
{
|
||||
return this.$store.state.chat.question
|
||||
},
|
||||
|
||||
messages()
|
||||
{
|
||||
return this.$store.state.chat.messages
|
||||
},
|
||||
|
||||
user()
|
||||
{
|
||||
return this.$store.state.chat.user
|
||||
},
|
||||
|
||||
scrollbarWidth()
|
||||
{
|
||||
return this.$store.state.scrollbarWidth
|
||||
},
|
||||
|
||||
unreadCount()
|
||||
{
|
||||
return this.$store.state.chat.unreadCount
|
||||
}
|
||||
},
|
||||
|
||||
watch:
|
||||
{
|
||||
user(value)
|
||||
{
|
||||
this.userName = value.name
|
||||
},
|
||||
|
||||
messages()
|
||||
{
|
||||
window.requestAnimationFrame(() =>
|
||||
{
|
||||
if(this.atBottom)
|
||||
{
|
||||
this.scrollToBottom()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
open(value)
|
||||
{
|
||||
if(value)
|
||||
{
|
||||
window.requestAnimationFrame(() =>
|
||||
{
|
||||
// Focus on textearea
|
||||
this.$textarea.focus()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
question(value)
|
||||
{
|
||||
if(value)
|
||||
{
|
||||
window.requestAnimationFrame(() =>
|
||||
{
|
||||
// Focus on textearea
|
||||
this.$textarea.focus()
|
||||
|
||||
// Scroll to bottom
|
||||
this.scrollToBottom()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created()
|
||||
{
|
||||
this.atBottom = true
|
||||
},
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.$messages = this.$el.querySelector('.messages')
|
||||
this.$textarea = this.$el.querySelector('.textarea')
|
||||
this.$innerMessages = this.$messages.querySelector('.inner-messages')
|
||||
},
|
||||
|
||||
methods:
|
||||
{
|
||||
onHeaderClick()
|
||||
{
|
||||
this.$store.commit('toggleChat')
|
||||
},
|
||||
|
||||
onUserNameChange()
|
||||
{
|
||||
this.$store.commit('setPendingUser', { name: this.userName })
|
||||
},
|
||||
|
||||
onUserNameKeyDown(event)
|
||||
{
|
||||
// Blur if enter pressed
|
||||
if(event.keyCode === 13)
|
||||
{
|
||||
// Focus on textearea
|
||||
this.$textarea.focus()
|
||||
}
|
||||
},
|
||||
|
||||
onMessageTextKeyDown(event)
|
||||
{
|
||||
// Enter key pressed but without shift key
|
||||
if(event.keyCode === 13 && !event.shiftKey)
|
||||
{
|
||||
event.preventDefault()
|
||||
|
||||
const text = this.messageText.trim()
|
||||
|
||||
if(text)
|
||||
{
|
||||
// Create and send message
|
||||
const message = {}
|
||||
message.text = text
|
||||
|
||||
if(this.question)
|
||||
{
|
||||
message.file = this.question.file
|
||||
message.line = this.question.line
|
||||
message.version = this.question.version
|
||||
}
|
||||
|
||||
this.$store.commit('setPendingMessage', message)
|
||||
this.messageText = ''
|
||||
|
||||
// Reset question
|
||||
this.$store.commit('setQuestion', null)
|
||||
|
||||
// Scroll to bottom
|
||||
this.scrollToBottom()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onMessagesScroll()
|
||||
{
|
||||
this.atBottom = this.$messages.scrollTop + this.$messages.offsetHeight >= this.$innerMessages.offsetHeight - 15
|
||||
},
|
||||
|
||||
onQuestionRemoveClick()
|
||||
{
|
||||
// Reset question
|
||||
this.$store.commit('setQuestion', null)
|
||||
|
||||
// Focus on textearea
|
||||
this.$textarea.focus()
|
||||
},
|
||||
|
||||
onFileClick(file, version, line)
|
||||
{
|
||||
// Set file, line and version
|
||||
this.$store.commit('setFile', file)
|
||||
this.$store.commit('setLine', line)
|
||||
|
||||
window.requestAnimationFrame(() =>
|
||||
{
|
||||
this.$store.commit('setVersion', version)
|
||||
})
|
||||
|
||||
// Focus on textearea
|
||||
this.$textarea.focus()
|
||||
|
||||
// Scroll to bottom
|
||||
this.scrollToBottom()
|
||||
},
|
||||
|
||||
scrollToBottom()
|
||||
{
|
||||
this.$messages.scrollTop = this.$innerMessages.offsetHeight - this.$messages.offsetHeight + 15
|
||||
},
|
||||
|
||||
formatMinutes(time)
|
||||
{
|
||||
const date = new Date(time)
|
||||
|
||||
let minutes = '' + date.getMinutes()
|
||||
if(minutes.length === 1)
|
||||
{
|
||||
minutes = `0${minutes}`
|
||||
}
|
||||
|
||||
let hours = '' + date.getHours()
|
||||
if(hours.length === 1)
|
||||
{
|
||||
hours = `0${hours}`
|
||||
}
|
||||
|
||||
return `${hours}:${minutes}`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
.chat
|
||||
position fixed
|
||||
bottom 0
|
||||
right 123px
|
||||
color #fff
|
||||
|
||||
.trigger
|
||||
position absolute
|
||||
bottom 0
|
||||
right 0
|
||||
height 40px
|
||||
padding-left 14px
|
||||
padding-right 18px
|
||||
line-height 40px
|
||||
background #24263A
|
||||
font-size 14px
|
||||
white-space nowrap
|
||||
user-select none
|
||||
|
||||
.stroke
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
right 0
|
||||
height 1px
|
||||
background #4BD1C5
|
||||
display none
|
||||
|
||||
.icon
|
||||
margin-left 4px
|
||||
|
||||
.unread
|
||||
display inline-block
|
||||
position absolute
|
||||
top 7px
|
||||
left calc(100% - 25px)
|
||||
width 20px
|
||||
text-align center
|
||||
font-size 9px
|
||||
line-height 9px
|
||||
color #4BD1C5
|
||||
|
||||
&:hover
|
||||
.stroke
|
||||
display block
|
||||
|
||||
.content
|
||||
position relative
|
||||
background #24263A
|
||||
height 450px
|
||||
width 300px
|
||||
overflow hidden
|
||||
|
||||
.header
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 40px
|
||||
padding-left 14px
|
||||
line-height 40px
|
||||
font-size 14px
|
||||
user-select none
|
||||
|
||||
.stroke
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
right 0
|
||||
height 1px
|
||||
background #4BD1C5
|
||||
display none
|
||||
|
||||
.dash
|
||||
position absolute
|
||||
top calc(50% - 1px)
|
||||
right 14px
|
||||
height 2px
|
||||
width 12px
|
||||
background #fff
|
||||
|
||||
&:hover
|
||||
.stroke
|
||||
display block
|
||||
|
||||
.messages
|
||||
position absolute
|
||||
top 40px
|
||||
left 0
|
||||
right 0
|
||||
height 330px
|
||||
padding-top 4px
|
||||
overflow-y scroll
|
||||
background #393855
|
||||
font-size 12px
|
||||
|
||||
.message
|
||||
position relative
|
||||
padding-left 21px
|
||||
margin-bottom 10px
|
||||
|
||||
.stroke
|
||||
position absolute
|
||||
top 0px
|
||||
left 10px
|
||||
width 2px
|
||||
bottom 2px
|
||||
|
||||
.message-user
|
||||
padding-bottom 3px
|
||||
|
||||
.message-date
|
||||
display inline-block
|
||||
opacity 0
|
||||
padding-left 4px
|
||||
padding-bottom 3px
|
||||
|
||||
.message-file
|
||||
display block
|
||||
padding-bottom 3px
|
||||
opacity 0.7
|
||||
|
||||
&:hover
|
||||
opacity 1
|
||||
color #4BD1C5
|
||||
|
||||
&:hover
|
||||
.message-date
|
||||
opacity 0.4
|
||||
|
||||
|
||||
.user
|
||||
position absolute
|
||||
top 374px
|
||||
left 0
|
||||
font-size 12px
|
||||
padding-left 8px
|
||||
|
||||
.user-label
|
||||
opacity 0.7
|
||||
pointer-events none
|
||||
user-select none
|
||||
|
||||
.user-name
|
||||
width 185px
|
||||
padding-left 1px
|
||||
outline none
|
||||
border none
|
||||
|
||||
&:hover
|
||||
background rgba(255, 255, 255, 0.075)
|
||||
|
||||
.question
|
||||
position absolute
|
||||
bottom 0
|
||||
right 0
|
||||
padding 4px
|
||||
font-size 12px
|
||||
|
||||
.question-file
|
||||
position absolute
|
||||
bottom 5px
|
||||
right 22px
|
||||
opacity 0.7
|
||||
white-space nowrap
|
||||
pointer-events none
|
||||
|
||||
.remove
|
||||
position absolute
|
||||
bottom 0
|
||||
right 0
|
||||
width 20px
|
||||
height 20px
|
||||
padding-top 4px
|
||||
padding-left 3px
|
||||
|
||||
.icon
|
||||
&.default
|
||||
display block
|
||||
|
||||
&.hover
|
||||
display none
|
||||
|
||||
&:hover
|
||||
.icon
|
||||
&.default
|
||||
display none
|
||||
|
||||
&.hover
|
||||
display block
|
||||
|
||||
textarea
|
||||
position absolute
|
||||
bottom 0
|
||||
left 0
|
||||
font-size 12px
|
||||
width 100%
|
||||
height 58px
|
||||
padding-left 8px
|
||||
resize none
|
||||
// opacity 0.8
|
||||
@@ -0,0 +1,45 @@
|
||||
<div class="chat">
|
||||
<a href="#" v-show="!open" class="trigger" @click="onHeaderClick">
|
||||
<span class="stroke"></span>
|
||||
Discussions
|
||||
<img v-if="unreadCount === 0" class="icon default" src="../../../static/svg/chat-star.svg" alt="">
|
||||
<img v-if="unreadCount > 0" class="icon hover" src="../../../static/svg/chat-star-hover.svg" alt="">
|
||||
<span v-if="unreadCount > 0" class="unread">{{ unreadCount > 99 ? '99+' : unreadCount }}</span>
|
||||
<tooltip position="top-left">Chat with other person on the project</tooltip>
|
||||
</a>
|
||||
<div class="content" v-show="open">
|
||||
<a href="#" class="header" @click="onHeaderClick">
|
||||
<span class="stroke"></span>
|
||||
<div class="dash"></div>
|
||||
Discussions
|
||||
</a>
|
||||
<div class="messages" :style="`right: -${scrollbarWidth}px;`" @scroll="onMessagesScroll">
|
||||
<div class="inner-messages">
|
||||
<div class="message" v-for="message of messages" :key="message.date">
|
||||
<div class="stroke" :style="`background: ${message.user.color};`"></div>
|
||||
<span class="message-user" :style="`color: ${message.user.color};`">
|
||||
{{ message.user.name }}
|
||||
</span>
|
||||
<span class="message-date">
|
||||
{{ formatMinutes(message.time) }}
|
||||
</span>
|
||||
<a href="#" class="message-file" v-if="message.file && message.line" @click.prevent="onFileClick(message.file, message.version, message.line)">
|
||||
{{ message.file }}:{{ message.line }}
|
||||
</a>
|
||||
<div class="message-text" v-html="message.text.replace(/\n/g, '<br>')"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user" v-if="user">
|
||||
<span class="user-label">Your nickname is</span> <input maxlength="22" class="user-name" :style="`color: ${user.color};`" v-model="userName" @change="onUserNameChange($event)" @keydown="onUserNameKeyDown($event)"/>
|
||||
</div>
|
||||
<textarea class="textarea" :style="`width:calc(100% + ${scrollbarWidth}px);padding-right:${scrollbarWidth}px;`" v-model="messageText" @keydown="onMessageTextKeyDown($event)" placeholder="Type you message here..."></textarea>
|
||||
<div class="question" v-if="question">
|
||||
<span class="question-file">{{ question.file }}:{{ question.line }}</span>
|
||||
<a href="#" class="remove" @click.prevent="onQuestionRemoveClick">
|
||||
<img class="icon default" src="../../../static/svg/cross.svg" alt="">
|
||||
<img class="icon hover" src="../../../static/svg/cross-hover.svg" alt="">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user