New Chat RoomView Layout

This commit is contained in:
Achilles Rasquinha 2018-01-22 18:35:25 +05:30
parent 27e5cf6967
commit b04e3cce42
10 changed files with 591 additions and 415 deletions

View file

@ -18,7 +18,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "type",
"fieldname": "room_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
@ -27,7 +27,7 @@
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Type",
"label": "Room Type",
"length": 0,
"no_copy": 0,
"options": "Direct\nGroup\nVisitor",
@ -206,7 +206,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-01-18 05:22:09.458705",
"modified": "2018-01-21 12:35:12.069954",
"modified_by": "faris@erpnext.com",
"module": "Chat",
"name": "Chat Message",

View file

@ -17,7 +17,8 @@ from frappe.chat.util import (
dictify,
get_emojis,
safe_json_loads,
get_user_doc
get_user_doc,
squashify
)
session = frappe.session
@ -91,13 +92,13 @@ def get_new_chat_message_doc(user, room, content, link = True):
meta = get_message_meta(content)
mess = frappe.new_doc('Chat Message')
mess.type = room.type
mess.room = room.name
mess.content = sanitize_message_content(content)
mess.user = user.name
mess.room = room.name
mess.room_type = room.type
mess.content = sanitize_message_content(content)
mess.user = user.name
mess.mentions = json.dumps(meta.mentions)
mess.urls = ','.join(meta.urls)
mess.mentions = json.dumps(meta.mentions)
mess.urls = ','.join(meta.urls)
mess.save(ignore_permissions = True)
if link:
@ -112,14 +113,15 @@ def get_new_chat_message(user, room, content):
mess = get_new_chat_message_doc(user, room, content)
resp = dict(
name = mess.name,
user = mess.user,
room = mess.room,
content = mess.content,
urls = mess.urls,
mentions = json.loads(mess.mentions),
creation = mess.creation,
seen = json.loads(mess._seen) if mess._seen else [ ],
name = mess.name,
user = mess.user,
room = mess.room,
room_type = mess.room_type,
content = mess.content,
urls = mess.urls,
mentions = json.loads(mess.mentions),
creation = mess.creation,
seen = json.loads(mess._seen) if mess._seen else [ ],
)
return resp
@ -145,11 +147,11 @@ def history(room, fields = None, limit = 10, start = None, end = None):
room = frappe.get_doc('Chat Room', room)
mess = frappe.get_all('Chat Message',
filters = [
('Chat Message', 'room', '=', room.name),
('Chat Message', 'type', '=', room.type)
('Chat Message', 'room', '=', room.name),
('Chat Message', 'room_type', '=', room.type)
],
fields = fields if fields else [
'name', 'type', 'room', 'content', 'user', 'mentions', 'urls', 'creation', '_seen'
'name', 'room_type', 'room', 'content', 'user', 'mentions', 'urls', 'creation', '_seen'
],
order_by = 'creation'
)
@ -167,14 +169,15 @@ def get(name, rooms = None, fields = None):
dmess = frappe.get_doc('Chat Message', name)
data = dict(
name = dmess.name,
user = dmess.user,
room = dmess.room,
content = dmess.content,
urls = dmess.urls,
mentions = dmess.mentions,
creation = dmess.creation,
seen = assign_if_empty(dmess._seen, [ ])
name = dmess.name,
user = dmess.user,
room = dmess.room,
room_type = dmess.room_type,
content = dmess.content,
urls = dmess.urls,
mentions = dmess.mentions,
creation = dmess.creation,
seen = assign_if_empty(dmess._seen, [ ])
)
return data

View file

@ -3,9 +3,7 @@
"public/css/font-awesome.css",
"public/css/octicons/octicons.css",
"public/css/website.css",
"public/css/avatar.css",
"public/css/chat.css"
"public/css/avatar.css"
],
"js/frappe-web.min.js": [
"public/js/frappe/class.js",
@ -22,11 +20,7 @@
"public/js/lib/microtemplate.js",
"public/js/frappe/query_string.js",
"website/js/website.js",
"public/js/frappe/misc/rating_icons.html",
"public/js/lib/hyper.min.js",
"public/js/lib/fuse.min.js",
"public/js/frappe/chat.js"
"public/js/frappe/misc/rating_icons.html"
],
"js/control.min.js": [
"public/js/frappe/ui/capture.js",
@ -146,7 +140,7 @@
"public/js/lib/moment/moment-with-locales.min.js",
"public/js/lib/moment/moment-timezone-with-data.min.js",
"public/js/lib/socket.io.min.js",
"public/js/lib/markdown.js",
"public/js/lib/showdown.js",
"public/js/lib/jSignature.min.js",
"public/js/frappe/translate.js",
"public/js/lib/datepicker/datepicker.min.js",

View file

@ -314,21 +314,8 @@ a.no-decoration:active {
vertical-align: middle;
max-width: 180px;
}
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel .frappe-chat-room-list .message-count {
background: #ff5858;
display: inline-block;
padding: 5px;
color: white;
border-radius: 50%;
font-size: 12px;
height: 20px;
line-height: 10px;
text-align: center;
min-width: 20px;
margin-top: 4px;
}
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel .chat-list.list-group {
height: 390px;
height: 387px;
overflow-y: scroll;
}
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel .frappe-chat-room-footer {
@ -354,40 +341,99 @@ a.no-decoration:active {
.frappe-chat .panel {
margin-bottom: 0px !important;
}
.frappe-chat .panel .frappe-chat-form {
margin: 1px;
}
.frappe-chat .panel .frappe-chat-form .form-control {
.frappe-chat .panel .chat-form .form-control {
font-size: 12px;
}
.frappe-chat .panel .frappe-chat-form .dropdown-menu {
.frappe-chat .panel .chat-form .dropdown-menu {
border-radius: 4px;
}
.frappe-chat .panel .frappe-chat-form .btn {
border-radius: 0px !important;
}
.frappe-chat .panel .frappe-chat-form .hint-list.list-group {
.frappe-chat .panel .chat-form .hint-list.list-group {
margin: 0px;
max-height: 150px;
overflow-y: auto;
}
.frappe-chat .panel .frappe-chat-form .hint-list.list-group .hint-list-item.list-group-item:first-child,
.frappe-chat .panel .frappe-chat-form .hint-list.list-group .hint-list-item.list-group-item:last-child {
.frappe-chat .panel .chat-form .hint-list.list-group .hint-list-item.list-group-item:first-child,
.frappe-chat .panel .chat-form .hint-list.list-group .hint-list-item.list-group-item:last-child {
border-radius: 0px !important;
}
.frappe-chat .panel .frappe-chat-form .hint-list.list-group .hint-list-item.list-group-item:first-child a,
.frappe-chat .panel .frappe-chat-form .hint-list.list-group .hint-list-item.list-group-item:last-child a {
.frappe-chat .panel .chat-form .hint-list.list-group .hint-list-item.list-group-item:first-child a,
.frappe-chat .panel .chat-form .hint-list.list-group .hint-list-item.list-group-item:last-child a {
text-decoration: none;
}
.chat-message {
background: #E8DDFF;
padding: 5px 15px;
margin: 5px;
border-radius: 5px;
display: inline-block;
.chat-list {
padding-bottom: 3px;
}
.seen-check {
font-size: 12px;
.chat-form {
border-top: 1px solid #D1D8DD;
}
.chat-form .input-group-btn .btn {
background: white;
}
.chat-form .form-control {
line-height: 27px;
border: none;
box-shadow: none;
resize: none;
padding-left: 0px;
padding-right: 0px;
}
.chat-form .fa {
font-size: 14px;
}
.chat-list {
background: #FAFBFC;
}
.chat-list .chat-list-item {
cursor: pointer;
border: none !important;
padding: 5px 10px;
background: transparent;
}
.chat-list .chat-list-item .avatar {
vertical-align: top;
}
.chat-list .chat-list-item .chat-bubble {
min-width: 20%;
max-width: 75%;
display: inline-block;
padding: 5px 10px;
border-radius: 5px;
-webkit-box-shadow: 0px 0.1px 0.5px 0px rgba(0, 0, 0, 0.5);
-moz-box-shadow: 0px 0.1px 0.5px 0px rgba(0, 0, 0, 0.5);
box-shadow: 0px 0.1px 0.5px 0px rgba(0, 0, 0, 0.5);
}
.chat-list .chat-list-item .chat-bubble.chat-bubble-l {
background-color: #EBEFF2;
}
.chat-list .chat-list-item .chat-bubble.chat-bubble-l .chat-bubble-meta > .chat-bubble-creation,
.chat-list .chat-list-item .chat-bubble.chat-bubble-l .chat-bubble-meta > .chat-bubble-check i {
color: #577287 !important;
}
.chat-list .chat-list-item .chat-bubble.chat-bubble-r {
text-align: right;
background-color: #EBF7CF;
}
.chat-list .chat-list-item .chat-bubble.chat-bubble-r .chat-bubble-meta > .chat-bubble-creation,
.chat-list .chat-list-item .chat-bubble.chat-bubble-r .chat-bubble-meta > .chat-bubble-check i {
color: #80ab1c !important;
}
.chat-list .chat-list-item .chat-bubble .chat-bubble-author {
font-size: 12px;
}
.chat-list .chat-list-item .chat-bubble .chat-bubble-author a {
font-weight: 700;
text-decoration: none !important;
}
.chat-list .chat-list-item .chat-bubble .chat-bubble-content {
margin-bottom: 5px;
word-wrap: break-word;
}
.chat-list .chat-list-item .chat-bubble .chat-bubble-meta {
font-size: 10px;
}
.chat-list .chat-list-item .chat-bubble .chat-bubble-meta > .chat-bubble-check {
margin-left: 5px;
}
.chat-list .chat-list-item .chat-bubble .chat-bubble-meta > .chat-bubble-check i {
font-size: 12px;
}

View file

@ -1,6 +1,12 @@
// Frappe Chat
// Author - Achilles Rasquinha <achilles@frappe.io>
/**
* --------------------------------------------------------------------------------
* Developer Notes
* --------------------------------------------------------------------------------
*/
/* eslint semi: "never" */
// Fuck semicolons - https://mislav.net/2010/05/semicolons
@ -1064,6 +1070,8 @@ const { h, Component } = hyper
// frappe's component namespace.
frappe.provide('frappe.components')
frappe.provide('frappe.chat.component')
/**
* @description Button Component
*
@ -1171,7 +1179,7 @@ class extends Component
{
const { props } = this
return props.type ? h("i", { ...props, class: `fa ${props.fixed ? "fa-fw" : ""} fa-${props.type}` }) : null
return props.type ? h("i", { ...props, class: `fa ${props.fixed ? "fa-fw" : ""} fa-${props.type} ${props.class}` }) : null
}
}
frappe.components.FontAwesome.defaultProps
@ -1396,7 +1404,7 @@ class extends Component
const state = [ ]
for (const room of rooms)
if ( room.type === "Group" || room.last_message )
if ( room.type === "Group" || room.owner === frappe.session.user || room.last_message )
{
frappe.log.info(`Adding ${room.name} to component.`)
state.push(room)
@ -2091,7 +2099,8 @@ class extends Component
{
icon: "camera",
label: "Camera",
click: ( ) => {
on_click: ( ) =>
{
const capture = new frappe.ui.Capture({
animate: false,
error: true
@ -2107,7 +2116,8 @@ class extends Component
{
icon: "file",
label: "File",
click: ( ) => {
on_click: ( ) =>
{
}
}
@ -2127,7 +2137,7 @@ class extends Component
h("div", { class: `panel panel-default ${frappe._.is_mobile() ? "panel-span" : ""}` },
h(frappe.Chat.Widget.Room.Header, { ...props, back: props.destroy }),
!frappe._.is_empty(props.messages) ?
h(frappe.Chat.Widget.ChatList, {
h(frappe.chat.component.ChatList, {
messages: props.messages
})
:
@ -2140,11 +2150,11 @@ class extends Component
)
),
h("div", { class: "frappe-chat-room-footer" },
h(frappe.Chat.Widget.ChatForm, { actions: actions,
change: () => {
h(frappe.chat.component.ChatForm, { actions: actions,
on_change: () => {
frappe.chat.message.typing(props.name)
},
submit: (message) => {
on_submit: (message) => {
frappe.chat.message.send(props.name, message)
},
hint: hints
@ -2220,23 +2230,141 @@ class extends Component
}
/**
* @description Chat Form Component
* @description ChatList Component
*
* @prop {array} messages - ChatMessage(s)
*/
frappe.Chat.Widget.ChatForm
frappe.chat.component.ChatList
=
class extends Component {
constructor (props) {
super (props)
this.change = this.change.bind(this)
this.submit = this.submit.bind(this)
this.hint = this.hint.bind(this)
this.state = frappe.Chat.Widget.ChatForm.defaultState
class extends Component
{
on_mounted ( )
{
this.$element = $('.frappe-chat').find('.chat-list')
this.$element.scrollTop(this.$element[0].scrollHeight)
}
change (e)
on_updated ( )
{
this.$element.scrollTop(this.$element[0].scrollHeight)
}
render ( )
{
const { props } = this
return !frappe._.is_empty(props.messages) ? (
h("div",{class:"chat-list list-group"},
props.messages.map(m => h(frappe.chat.component.ChatList.Item, {...m}))
)
) : null
}
}
/**
* @description ChatList.Item Component
*
* @prop {string} name - ChatMessage name
* @prop {string} user - ChatMessage user
* @prop {string} room - ChatMessage room
* @prop {string} room_type - ChatMessage Room Type ("Direct", "Group" or "Visitor")
* @prop {string} content - ChatMessage content
* @prop {frappe.datetime.datetime} creation - Chat Message creation
*/
frappe.chat.component.ChatList.Item
=
class extends Component
{
render ( )
{
const { props } = this
const me = props.user === frappe.session.user
return (
h("div",{class: "chat-list-item list-group-item"},
h("div",{class:`${me ? "text-right" : ""}`},
props.room_type === "Group" && !me?
h(frappe.components.Avatar,
{
title: frappe.user.full_name(props.user),
image: frappe.user.image(props.user)
}) : null,
h(frappe.chat.component.ChatBubble, props)
)
)
)
}
}
/**
* @description ChatBubble Component
*
* @prop {string} name - ChatMessage name
* @prop {string} user - ChatMessage user
* @prop {string} room - ChatMessage room
* @prop {string} room_type - ChatMessage room_type ("Direct", "Group" or "Visitor")
* @prop {string} content - ChatMessage content
* @prop {frappe.datetime.datetime} creation - ChatMessage creation
*/
frappe.chat.component.ChatBubble
=
class extends Component
{
render ( )
{
const { props } = this
const creation = props.creation.format('hh:mm A')
const me = props.user === frappe.session.user
const read = !frappe._.is_empty(props.seen) && !props.seen.includes(frappe.session.user)
const content = props.content
return (
h("div",{class:`chat-bubble chat-bubble-${me ? "r" : "l"}`},
props.room_type === "Group" && !me?
h("div",{class:"chat-bubble-author"},
h("a", { onclick: () => { frappe.set_route(`Form/User/${props.user}`) } },
frappe.user.full_name(props.user)
)
) : null,
h("div",{class:"chat-bubble-content"},
h("small","",content)
),
h("div",{class:"chat-bubble-meta"},
h("span",{class:"chat-bubble-creation"},creation),
me && read ?
h("span",{class:"chat-bubble-check"},
h(frappe.components.Octicon,{type:"check"})
) : null
)
)
)
}
}
/**
* @description ChatForm Component
*/
frappe.chat.component.ChatForm
=
class extends Component
{
constructor (props)
{
super (props)
this.on_change = this.on_change.bind(this)
this.on_submit = this.on_submit.bind(this)
this.hint = this.hint.bind(this)
this.state = frappe.chat.component.ChatForm.defaultState
}
on_change (e)
{
const { props, state } = this
const value = e.target.value
@ -2245,7 +2373,7 @@ class extends Component {
[e.target.name]: value
})
props.change(state)
props.on_change(state)
this.hint(value)
}
@ -2290,23 +2418,24 @@ class extends Component {
}
}
submit (e)
on_submit (e)
{
e.preventDefault()
if ( this.state.content )
{
this.props.submit(this.state.content)
this.props.on_submit(this.state.content)
this.set_state({ content: null })
}
}
render ( ) {
render ( )
{
const { props, state } = this
return (
h("div", { class: "frappe-chat-form" },
h("div",{class:"chat-form"},
state.hints.length ?
h("ul", { class: "hint-list list-group" },
state.hints.map((item) =>
@ -2323,25 +2452,27 @@ class extends Component {
)
})
) : null,
h("form", { oninput: this.change, onsubmit: this.submit },
h("div", { class: "input-group input-group-lg" },
h("div", { class: "input-group-btn dropup" },
h(frappe.components.Button, { class: "dropdown-toggle", "data-toggle": "dropdown" },
h(frappe.components.FontAwesome, { type: "paperclip", fixed: true, style: { "font-size": "14px" } })
),
h("div", { class: "dropdown-menu dropdown-menu-left", onclick: e => e.stopPropagation() },
!frappe._.is_empty(props.actions) && props.actions.map((action) => {
return (
h("li", null,
h("a", { onclick: action.click },
h(frappe.components.FontAwesome, { type: action.icon, fixed: true }), ` ${action.label}`,
h("form", { oninput: this.on_change, onsubmit: this.on_submit },
h("div",{class:"input-group input-group-lg"},
!frappe._.is_empty(props.actions) ?
h("div",{class:"input-group-btn dropup"},
h(frappe.components.Button,{ class: "dropdown-toggle", "data-toggle": "dropdown"},
h(frappe.components.FontAwesome, { class: "text-muted", type: "paperclip", fixed: true })
),
h("div",{ class:"dropdown-menu dropdown-menu-left", onclick: e => e.stopPropagation() },
!frappe._.is_empty(props.actions) && props.actions.map((action) =>
{
return (
h("li", null,
h("a",{onclick:action.on_click},
h(frappe.components.FontAwesome,{type:action.icon,fixed:true}), ` ${action.label}`,
)
)
)
)
})
)
),
h("input",
})
)
) : null,
h("textarea",
{
class: "form-control",
name: "content",
@ -2351,12 +2482,12 @@ class extends Component {
onkeypress: (e) =>
{
if ( e.which === frappe.ui.keycode.RETURN && !e.shiftKey )
this.submit(e)
this.on_submit(e)
}
}),
h("div", { class: "input-group-btn" },
h(frappe.components.Button, { type: "primary", class: "dropdown-toggle", "data-toggle": "dropdown", onclick: this.submit },
h(frappe.components.FontAwesome, { type: "send", fixed: true, style: { "font-size": "14px" } })
h("div",{class:"input-group-btn"},
h(frappe.components.Button, { onclick: this.on_submit },
h(frappe.components.FontAwesome, { class: !frappe._.is_empty(state.content) ? "text-primary" : "text-muted", type: "send", fixed: true })
),
)
)
@ -2365,14 +2496,20 @@ class extends Component {
)
}
}
frappe.Chat.Widget.ChatForm.defaultState
frappe.chat.component.ChatForm.defaultState
=
{
content: null,
hints: [ ],
}
frappe.Chat.Widget.EmojiPicker
/**
* @description EmojiPicker Component
*
* @todo Under Development
*/
frappe.chat.component.EmojiPicker
=
class extends Component
{
@ -2387,14 +2524,14 @@ class extends Component
),
h("div", { class: "dropdown-menu dropdown-menu-right", onclick: e => e.stopPropagation() },
h("div", { class: "panel panel-default" },
h(frappe.Chat.Widget.EmojiPicker.List)
h(frappe.chat.component.EmojiPicker.List)
)
)
)
)
}
}
frappe.Chat.Widget.EmojiPicker.List
frappe.chat.component.EmojiPicker.List
=
class extends Component
{
@ -2408,72 +2545,4 @@ class extends Component
)
)
}
}
/**
* @description Chat List HOC
*/
frappe.Chat.Widget.ChatList
=
class extends Component {
on_mounted ( )
{
const $element = $('.frappe-chat').find('.chat-list')
$element.scrollTop($element[0].scrollHeight)
}
on_updated ( )
{
const $element = $('.frappe-chat').find('.chat-list')
$element.scrollTop($element[0].scrollHeight)
}
render ( ) {
const { props } = this
return !frappe._.is_empty(props.messages) ? (
h("ul", { class: "chat-list list-group" },
props.messages.map(m => h(frappe.Chat.Widget.ChatList.Item, {
...m
}))
)
) : null
}
}
frappe.Chat.Widget.ChatList.Item
=
class extends Component {
render ( ) {
const { props } = this
return (
h("li", { class: "list-group-item", style: "border: none !important" },
h(frappe.Chat.Widget.ChatList.Bubble, props)
)
)
}
}
frappe.Chat.Widget.ChatList.Bubble
=
class extends Component {
render ( ) {
const { props } = this
return (
h(frappe.Chat.Widget.MediaProfile, {
title: frappe.user.full_name(props.user),
subtitle: `${frappe.chat.pretty_datetime(props.creation)}`,
content: props.content,
image: frappe.user.image(props.user),
width_title: "100%",
position: frappe.user.full_name(props.user) === "You" ? "right" : "left"
})
)
}
}
frappe.Chat.Widget.ChatList.Bubble.defaultState =
{
creation: ""
}

View file

@ -34,7 +34,7 @@ frappe.tools.downloadify = function(data, roles, title) {
frappe.markdown = function(txt) {
if(!frappe.md2html) {
frappe.md2html = new Showdown.converter();
frappe.md2html = new showdown.Converter();
}
while(txt.substr(0,1)==="\n") {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -41,184 +41,309 @@
.navbar
{
.frappe-chat-toggle
{
height: @frappe-chat-toggle-height;
text-align: center;
}
.frappe-chat-toggle
{
height: @frappe-chat-toggle-height;
text-align: center;
}
.octicon { margin-top: 5px; } // Hack, somewhat.
.octicon { margin-top: 5px; } // Hack, somewhat.
}
.frappe-chat
{
& > .frappe-chat-popper
{
position: fixed;
bottom: 0px;
right: 0px;
margin: @frappe-chat-popper-margin;
z-index: @frappe-chat-popper-z-index;
& > .frappe-chat-popper-collapse
{
& > .panel
{
position: relative;
display: flex;
flex-direction: column;
width: @frappe-chat-popper-panel-width;
height: @frappe-chat-popper-panel-height;
box-shadow: @frappe-chat-popper-panel-box-shadow;
& > .frappe-chat-popper
{
position: fixed;
bottom: 0px;
right: 0px;
margin: @frappe-chat-popper-margin;
z-index: @frappe-chat-popper-z-index;
& > .frappe-chat-popper-collapse
{
& > .panel
{
position: relative;
display: flex;
flex-direction: column;
width: @frappe-chat-popper-panel-width;
height: @frappe-chat-popper-panel-height;
box-shadow: @frappe-chat-popper-panel-box-shadow;
.vcenter
{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.vcenter
{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.panel-heading
{
.frappe-chat-action-bar
{
form
{
width: 100%;
}
.panel-heading
{
.frappe-chat-action-bar
{
form
{
width: 100%;
}
.btn-action
{
margin-left: 5px !important;
}
}
}
.btn-action
{
margin-left: 5px !important;
}
}
}
.frappe-chat-room-list
{
height: 100%;
overflow-y: auto;
padding: 0 1px 0 1px;
.frappe-chat-room-list
{
height: 100%;
overflow-y: auto;
padding: 0 1px 0 1px;
& > li > a
{
border-radius: 0px !important;
}
& > li > a
{
border-radius: 0px !important;
}
.media
{
.media-heading, .media-subtitle
{
.ellipsis;
max-width: @frappe-chat-room-list-content-max-width;
}
}
.media
{
.media-heading, .media-subtitle
{
.ellipsis;
max-width: @frappe-chat-room-list-content-max-width;
}
}
}
.message-count
{
background: #ff5858;
display: inline-block;
padding: 5px;
color: white;
border-radius: 50%;
font-size: 12px;
height: 20px;
line-height: 10px;
text-align: center;
min-width: 20px;
margin-top: 4px;
}
}
.chat-list.list-group
{
height: 387px;
overflow-y: scroll;
}
.chat-list.list-group
{
height: 390px;
overflow-y: scroll;
}
.frappe-chat-room-footer
{
position: absolute;
left: 0px;
right: 0px;
bottom: 0px;
}
}
.frappe-chat-room-footer
{
position: absolute;
left: 0px;
right: 0px;
bottom: 0px;
}
}
& > .panel.panel-span
{
position: fixed;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
bottom: 0px;
right: 0px;
overflow: auto;
border-radius: 0px;
.panel-heading
{
border-radius: 0px;
}
}
}
}
& > .panel.panel-span
{
position: fixed;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
bottom: 0px;
right: 0px;
overflow: auto;
border-radius: 0px;
.panel-heading
{
border-radius: 0px;
}
}
}
}
.panel
{
margin-bottom: 0px !important;
.panel
{
margin-bottom: 0px !important;
.chat-form
{
.form-control
{
font-size: @frappe-chat-form-font-size;
}
.dropdown-menu
{
border-radius: @frappe-chat-form-menu-border-radius;
}
// Hints
.hint-list.list-group
{
margin: 0px;
max-height: @frappe-chat-form-list-group-height;
overflow-y: auto;
.hint-list-item.list-group-item:first-child, .hint-list-item.list-group-item:last-child
{
border-radius: 0px !important;
.frappe-chat-form
{
// HACK: Wraps the ChatForm within the panel.
margin: 1px;
.form-control
{
font-size: @frappe-chat-form-font-size;
}
.dropdown-menu
{
border-radius: @frappe-chat-form-menu-border-radius;
}
.btn
{
border-radius: 0px !important;
}
// Hints
.hint-list.list-group
{
margin: 0px;
max-height: @frappe-chat-form-list-group-height;
overflow-y: auto;
.hint-list-item.list-group-item:first-child, .hint-list-item.list-group-item:last-child
{
border-radius: 0px !important;
a { text-decoration: none }
}
}
}
}
a { text-decoration: none }
}
}
}
}
}
//
.chat-message {
background: #E8DDFF;
padding: 5px 15px;
margin: 5px;
border-radius: 5px;
display: inline-block;
// hacks
.chat-list
{
padding-bottom: 3px;
}
.seen-check {
font-size: 12px;
display: inline-block;
margin-left: 5px;
@frappe-chat-color-grey: #8D99A6;
@frappe-chat-base-font-size: 12px;
@frappe-chat-base-font-size-lg: 14px;
@frappe-chat-base-spacing: 5px;
// ChatForm
@frappe-chat-form-border: 1px solid #D1D8DD;
// ChatList
@frappe-chat-list-bg-color: #FAFBFC;
// ChatList.Item
@frappe-chat-list-item-padding: @frappe-chat-base-spacing @frappe-chat-base-spacing * 2;
// ChatBubble
@frappe-chat-bubble-padding: @frappe-chat-base-spacing @frappe-chat-base-spacing * 2;
@frappe-chat-bubble-min-width: 20%;
@frappe-chat-bubble-max-width: 75%;
@frappe-chat-bubble-box-shadow: 0px 0.1px 0.5px 0px rgba(0,0,0,0.5);
@frappe-chat-bubble-border-size: 1px;
@frappe-chat-bubble-border-radius: @frappe-chat-base-spacing;
@frappe-chat-bubble-l-color: #EBEFF2;
@frappe-chat-bubble-r-color: #EBF7CF;
@frappe-chat-bubble-author-font-size: @frappe-chat-base-font-size;
@frappe-chat-bubble-content-margin-bottom: @frappe-chat-base-spacing;
@frappe-chat-bubble-meta-font-size: @frappe-chat-base-spacing * 2;
@frappe-chat-bubble-check-font-size: @frappe-chat-base-font-size;
.chat-form
{
border-top: @frappe-chat-form-border;
.input-group-btn
{
.btn
{
background: white;
}
}
.form-control
{
line-height: 27px; // HACK: Makes input and placeholder centered within textarea. Also takes care of the input-btn
border: none;
box-shadow: none;
resize: none;
padding-left: 0px;
padding-right: 0px;
}
.fa
{
font-size: @frappe-chat-base-font-size-lg;
}
}
.chat-list
{
background: @frappe-chat-list-bg-color;
.chat-list-item
{
.avatar
{
vertical-align: top;
}
.cursor-pointer;
border: none !important;
padding: @frappe-chat-list-item-padding;
background: transparent;
.chat-bubble
{
min-width: @frappe-chat-bubble-min-width;
max-width: @frappe-chat-bubble-max-width;
display: inline-block;
padding: @frappe-chat-bubble-padding;
border-radius: @frappe-chat-bubble-border-radius;
-webkit-box-shadow: @frappe-chat-bubble-box-shadow;
-moz-box-shadow: @frappe-chat-bubble-box-shadow;
box-shadow: @frappe-chat-bubble-box-shadow;
&.chat-bubble-l
{
background-color: @frappe-chat-bubble-l-color;
.chat-bubble-meta
{
& > .chat-bubble-creation, & > .chat-bubble-check i
{
color: darken(@frappe-chat-bubble-l-color, 50%) !important;
}
}
}
&.chat-bubble-r
{
text-align: right;
background-color: @frappe-chat-bubble-r-color;
.chat-bubble-meta
{
& > .chat-bubble-creation, & > .chat-bubble-check i
{
color: darken(@frappe-chat-bubble-r-color, 50%) !important;
}
}
}
.chat-bubble-author
{
font-size: @frappe-chat-bubble-author-font-size;
a
{
.font-bold;
text-decoration: none !important;
}
}
.chat-bubble-content
{
margin-bottom: @frappe-chat-bubble-content-margin-bottom;
word-wrap: break-word;
}
.chat-bubble-meta
{
font-size: @frappe-chat-bubble-meta-font-size;
& > .chat-bubble-check
{
margin-left: @frappe-chat-base-spacing;
i
{
font-size: @frappe-chat-bubble-check-font-size;
}
}
}
}
}
}