Merge pull request #4889 from achillesrasquinha/chat-fixes

New Chat Room View 🎉🎉🎉
This commit is contained in:
Achilles Rasquinha 2018-01-23 14:36:49 +05:30 committed by GitHub
commit b97a006f28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 715 additions and 396 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

@ -1,7 +1,7 @@
/* eslint semi: "never" */
frappe.ui.form.on('Chat Profile', {
refresh: function (form) {
if ( form.doc.user !== frappe.session.user ) {
if ( form.doc.name !== frappe.session.user ) {
form.disable_save()
form.set_read_only(true)
// There's one more that faris@frappe.io told me to add here. form.refresh_fields()?

View file

@ -273,8 +273,8 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-12-17 15:53:24.103274",
"modified_by": "achilles@erpnext.com",
"modified": "2018-01-20 18:14:02.384039",
"modified_by": "faris@erpnext.com",
"module": "Chat",
"name": "Chat Room",
"name_case": "",

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",

View file

@ -314,29 +314,6 @@ 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;
overflow-y: scroll;
}
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel .frappe-chat-room-footer {
position: absolute;
left: 0px;
right: 0px;
bottom: 0px;
}
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel.panel-span {
position: fixed;
width: 100%;
@ -354,40 +331,133 @@ 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;
.frappe-chat-popper-collapse > .panel > .panel-heading {
padding: 5px 10px;
}
.seen-check {
font-size: 12px;
.frappe-chat-popper-collapse > .panel > .panel-heading .btn-back {
margin-right: 5px;
}
.frappe-chat-popper-collapse > .panel > .panel-heading .avatar {
width: 32px;
height: 32px;
}
.chat-room-footer {
position: absolute;
bottom: 0;
}
.chat-room-footer .chat-form {
border-top: 1px solid #D1D8DD;
}
.chat-room-footer .chat-form .input-group-btn .btn {
background: white;
border-radius: 0px;
}
.chat-room-footer .chat-form .form-control {
line-height: 27px;
border: none;
box-shadow: none;
resize: none;
padding-left: 0px;
padding-right: 0px;
}
.chat-room-footer .chat-form .fa {
font-size: 14px;
transition: color 0.5s;
}
.chat-list {
padding-bottom: 50px;
height: 100%;
background: #FAFBFC;
background-size: 350px 500px;
background-image: url(/assets/frappe/images/chat/wallpaper-default.jpg);
overflow-y: scroll;
}
.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: white;
}
.chat-list .chat-list-item .chat-bubble.chat-bubble-l.chat-groupable {
margin-left: 40px;
}
.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;
}
.chat-list-notification {
text-align: center;
}
.chat-list-notification-content {
color: white;
background-color: #8D99A6;
display: inline-block;
/* padding: 5px; */
border-radius: 20px;
opacity: 0.5;
font-size: 10px;
padding: 5px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 KiB

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
@ -133,7 +139,17 @@ frappe.datetime.datetime = class
* @example
* const datetime = new frappe.datetime.now()
*/
frappe.datetime.now = () => new frappe.datetime.datetime()
frappe.datetime.now = () => new frappe.datetime.datetime()
frappe.datetime.equal = (a, b, type) =>
{
a = a.moment
b = b.moment
const equal = a.isSame(b, type)
return equal
}
/**
* @description Compares two frappe.datetime.datetime objects.
@ -208,7 +224,7 @@ frappe._.format = (string, object) =>
*/
frappe._.fuzzy_search = (query, dataset, options) =>
{
const DEFAULT =
const DEFAULT =
{
shouldSort: true,
threshold: 0.6,
@ -1064,6 +1080,8 @@ const { h, Component } = hyper
// frappe's component namespace.
frappe.provide('frappe.components')
frappe.provide('frappe.chat.component')
/**
* @description Button Component
*
@ -1171,7 +1189,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
@ -1483,7 +1501,6 @@ class extends Component
"status", "message_preview", "notification_tones", "conversation_tones"
]).then(profile =>
{
frappe.log.info(`Chat Profile created for User ${frappe.session.user} - ${JSON.stringify(profile)}.`)
this.set_state({ profile })
frappe.chat.room.get(rooms =>
@ -1866,7 +1883,7 @@ class extends Component
return (
h("div", { class: `frappe-chat-action-bar ${props.class ? props.class : ""}` },
h("form", { oninput: this.change, onsubmit: this.submit },
h("input", { class: "form-control input-sm", name: "query", value: state.query, placeholder: props.placeholder || "Search" }),
h("input", { autocomplete: "off", class: "form-control input-sm", name: "query", value: state.query, placeholder: props.placeholder || "Search" }),
),
!frappe._.is_empty(actions) ?
actions.map(action => h(frappe.Chat.Widget.ActionBar.Action, { ...action })) : null
@ -1961,8 +1978,6 @@ class extends Component
if ( props.last_message )
item.timestamp = frappe.chat.pretty_datetime(props.last_message.creation)
console.log(props)
return (
h("li", null,
h("a", { class: props.active ? "active": "", onclick: () => props.click(props) },
@ -2094,7 +2109,8 @@ class extends Component
{
icon: "camera",
label: "Camera",
click: ( ) => {
on_click: ( ) =>
{
const capture = new frappe.ui.Capture({
animate: false,
error: true
@ -2110,7 +2126,8 @@ class extends Component
{
icon: "file",
label: "File",
click: ( ) => {
on_click: ( ) =>
{
}
}
@ -2128,9 +2145,9 @@ class extends Component
return (
h("div", { class: `panel panel-default ${frappe._.is_mobile() ? "panel-span" : ""}` },
h(frappe.Chat.Widget.Room.Header, { ...props, back: props.destroy }),
h(frappe.Chat.Widget.Room.Header, { ...props, on_back: props.destroy }),
!frappe._.is_empty(props.messages) ?
h(frappe.Chat.Widget.ChatList, {
h(frappe.chat.component.ChatList, {
messages: props.messages
})
:
@ -2142,12 +2159,12 @@ class extends Component
)
)
),
h("div", { class: "frappe-chat-room-footer" },
h(frappe.Chat.Widget.ChatForm, { actions: actions,
change: () => {
h("div", { class: "chat-room-footer" },
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
@ -2201,8 +2218,8 @@ class extends Component
h("div", { class: "panel-heading" },
h("div", { class: "level" },
popper ?
h("div", { style: { "padding-right": "15px" } }, // sins of mine.
h("a", { onclick: props.back }, h(frappe.components.Octicon, { type: "chevron-left" }))
h(frappe.components.Button,{class:"btn-back",onclick:props.on_back},
h(frappe.components.Octicon, { type: "chevron-left" })
) : null,
h("div","",
h("div", { class: "panel-title" },
@ -2223,23 +2240,162 @@ 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 ( )
{
var messages = [ ]
for (var i = 0 ; i < this.props.messages.length ; ++i)
{
var message = this.props.messages[i]
const me = message.user === frappe.session.user
if ( i === 0 || !frappe.datetime.equal(message.creation, this.props.messages[i - 1].creation, 'day') )
messages.push({ type: "Notification", content: message.creation.format('MMMM DD') })
messages.push(message)
}
return !frappe._.is_empty(messages) ? (
h("div",{class:"chat-list list-group"},
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 - ChatMessage creation
*
* @prop {boolean} groupable - Whether the ChatMessage is groupable.
*/
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"},
props.type === "Notification" ?
h("div",{class:"chat-list-notification"},
h("div",{class:"chat-list-notification-content"},
props.content
)
)
:
h("div",{class:`${me ? "text-right" : ""}`},
!me && !props.groupable && !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
*
* @prop {boolean} groupable - Whether the ChatMessage is groupable.
*/
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 ${props.groupable ? "chat-groupable" : ""} 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
@ -2248,7 +2404,7 @@ class extends Component {
[e.target.name]: value
})
props.change(state)
props.on_change(state)
this.hint(value)
}
@ -2293,23 +2449,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) =>
@ -2326,25 +2483,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",
@ -2354,12 +2513,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 })
),
)
)
@ -2368,14 +2527,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
{
@ -2390,14 +2555,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
{
@ -2411,72 +2576,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

@ -1,15 +0,0 @@
frappe.Peer = class
{
constructor ( )
{
this.peer = new Peer()
this.peer.on('open', (ID) => {
console.log(`A new peer connection has occured with ID: ${ID}`)
})
}
}
frappe.Peer.boot = ( ) =>
{
const client = new frappe.Peer()
}

View file

@ -70,7 +70,7 @@
</li>
<!-- frappe.chat -->
<!-- "Placeholder, to display or not to display." -->
<!-- "placeholder, to display or not to display." -->
<li class="frappe-chat-dropdown"></li>
<!-- end frappe.chat -->

View file

@ -34,13 +34,15 @@ frappe.ui.toolbar.Toolbar = Class.extend({
setup_frappe_chat ( ) {
frappe.log = frappe.Logger.get('frappe.chat');
frappe.log.info('Setting up frappe.chat');
// TODO: frappe.chat: Handle realtime System Settings update.
// TODO: frappe.chat: frappe.chat.<object> requires a storage.
frappe.log.warn('TODO: Handle realtime System Settings update.');
frappe.log.warn('TODO: frappe.chat.<object> requires a storage.');
// Create/Get Chat Profile for session User, retrieve enable_chat
frappe.log.info(`Creating a Chat Profile.`);
frappe.chat.profile.create("enable_chat").then(({ enable_chat }) => {
frappe.log.info('Creating a Chat Profile.');
frappe.chat.profile.create('enable_chat').then(({ enable_chat }) => {
frappe.log.info(`Chat Profile created for User ${frappe.session.user}.`)
const should_render = frappe.sys_defaults.enable_chat && enable_chat;
this.render_frappe_chat(should_render);
});
@ -49,7 +51,7 @@ frappe.ui.toolbar.Toolbar = Class.extend({
// Don't worry, enable_chat is broadcasted to this user only. No overhead. :)
frappe.chat.profile.on.update((user, profile) => {
if ( user === frappe.session.user && 'enable_chat' in profile ) {
frappe.log.warn(`Chat Profile update (Enable Chat - ${Boolean(profile.enable_chat)}.`);
frappe.log.warn(`Chat Profile update (Enable Chat - ${Boolean(profile.enable_chat)})`);
const should_render = frappe.sys_defaults.enable_chat && profile.enable_chat;
this.render_frappe_chat(should_render);
}

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,352 @@
.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;
}
}
& > .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;
}
}
}
}
.chat-list.list-group
{
height: 390px;
overflow-y: scroll;
}
.panel
{
margin-bottom: 0px !important;
.frappe-chat-room-footer
{
position: absolute;
left: 0px;
right: 0px;
bottom: 0px;
}
}
.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;
& > .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;
.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;
@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-l-groupable-margin-left: 40px;
@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;
.frappe-chat-popper-collapse
{
& > .panel
{
& > .panel-heading
{
padding: @frappe-chat-base-spacing @frappe-chat-base-spacing * 2;
.btn-back
{
margin-right: @frappe-chat-base-spacing;
}
.avatar
{
width: 32px; height: 32px;
}
}
}
}
.seen-check {
font-size: 12px;
display: inline-block;
margin-left: 5px;
.chat-room-footer
{
position: absolute;
bottom: 0;
.chat-form
{
border-top: @frappe-chat-form-border;
.input-group-btn
{
.btn
{
background: white;
border-radius: 0px;
}
}
.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;
transition: color 0.5s; // Change, with grace. :)
}
}
}
.chat-list
{
padding-bottom: 50px;
height: 100%;
background: @frappe-chat-list-bg-color;
background-size: 350px 500px;
background-image: url(/assets/frappe/images/chat/wallpaper-default.jpg);
overflow-y: scroll;
.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
{
&.chat-groupable
{
margin-left: @frappe-chat-bubble-l-groupable-margin-left;
}
// background-color: @frappe-chat-bubble-l-color;
background-color: white;
.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;
}
}
}
}
}
}
.chat-list-notification
{
text-align: center;
}
.chat-list-notification-content
{
color: white;
background-color: #8D99A6;
display: inline-block;
/* padding: 5px; */
border-radius: 20px;
opacity: 0.5;
font-size: 10px;
padding: 5px;
// background-color: white;
}