Browse Source

chore: Remove Chat Module

version-14
codescientist703 3 years ago
parent
commit
ed13182573
49 changed files with 56 additions and 4604 deletions
  1. +0
    -5
      cypress/fixtures/doctype_with_tab_break.js
  2. +2
    -2
      frappe/__init__.py
  3. +0
    -23
      frappe/chat/__init__.py
  4. +0
    -0
      frappe/chat/doctype/__init__.py
  5. +0
    -0
      frappe/chat/doctype/chat_message/__init__.py
  6. +0
    -10
      frappe/chat/doctype/chat_message/chat_message.js
  7. +0
    -91
      frappe/chat/doctype/chat_message/chat_message.json
  8. +0
    -215
      frappe/chat/doctype/chat_message/chat_message.py
  9. +0
    -8
      frappe/chat/doctype/chat_message/chat_message_list.js
  10. +0
    -0
      frappe/chat/doctype/chat_profile/__init__.py
  11. +0
    -10
      frappe/chat/doctype/chat_profile/chat_profile.js
  12. +0
    -98
      frappe/chat/doctype/chat_profile/chat_profile.json
  13. +0
    -98
      frappe/chat/doctype/chat_profile/chat_profile.py
  14. +0
    -11
      frappe/chat/doctype/chat_profile/chat_profile_list.js
  15. +0
    -0
      frappe/chat/doctype/chat_room/__init__.py
  16. +0
    -8
      frappe/chat/doctype/chat_room/chat_room.js
  17. +0
    -100
      frappe/chat/doctype/chat_room/chat_room.json
  18. +0
    -227
      frappe/chat/doctype/chat_room/chat_room.py
  19. +0
    -6
      frappe/chat/doctype/chat_room/chat_room_list.js
  20. +0
    -0
      frappe/chat/doctype/chat_room_user/__init__.py
  21. +0
    -40
      frappe/chat/doctype/chat_room_user/chat_room_user.json
  22. +0
    -8
      frappe/chat/doctype/chat_room_user/chat_room_user.py
  23. +0
    -0
      frappe/chat/doctype/chat_token/__init__.py
  24. +0
    -8
      frappe/chat/doctype/chat_token/chat_token.js
  25. +0
    -57
      frappe/chat/doctype/chat_token/chat_token.json
  26. +0
    -9
      frappe/chat/doctype/chat_token/chat_token.py
  27. +0
    -13
      frappe/chat/util/__init__.py
  28. +0
    -35
      frappe/chat/util/test_util.py
  29. +0
    -108
      frappe/chat/util/util.py
  30. +0
    -42
      frappe/chat/website/__init__.py
  31. +2
    -8
      frappe/core/doctype/role/role.json
  32. +2
    -2
      frappe/core/doctype/role/role.py
  33. +3
    -17
      frappe/core/doctype/system_settings/system_settings.json
  34. +1
    -6
      frappe/core/doctype/user/user.json
  35. +1
    -4
      frappe/core/doctype/user/user.py
  36. +6
    -6
      frappe/core/doctype/user_permission/test_user_permission.py
  37. +0
    -7
      frappe/hooks.py
  38. +1
    -2
      frappe/modules.txt
  39. +0
    -1
      frappe/public/js/chat.bundle.js
  40. +0
    -1
      frappe/public/js/desk.bundle.js
  41. +0
    -2788
      frappe/public/js/frappe/chat.js
  42. +0
    -461
      frappe/public/less/chat.bundle.less
  43. BIN
      frappe/public/sounds/chat-message.mp3
  44. BIN
      frappe/public/sounds/chat-notification.mp3
  45. +0
    -1
      frappe/templates/base.html
  46. +29
    -0
      frappe/utils/__init__.py
  47. +2
    -54
      frappe/website/doctype/website_settings/website_settings.json
  48. +1
    -2
      frappe/website/doctype/website_settings/website_settings.py
  49. +6
    -12
      frappe/website/js/website.js

+ 0
- 5
cypress/fixtures/doctype_with_tab_break.js View File

@@ -30,11 +30,6 @@ export default {
"link_doctype": "Contact",
"link_fieldname": "user"
},
{
"group": "Profile",
"link_doctype": "Chat Profile",
"link_fieldname": "user"
},
],
modified_by: 'Administrator',
module: 'Custom',


+ 2
- 2
frappe/__init__.py View File

@@ -1798,7 +1798,7 @@ def get_version(doctype, name, limit=None, head=False, raise_err=True):
'limit': limit
}, as_list=1)

from frappe.chat.util import squashify, dictify, safe_json_loads
from frappe.utils import squashify, dictify, safe_json_loads

versions = []

@@ -1856,7 +1856,7 @@ def mock(type, size=1, locale='en'):
data = getattr(fake, type)()
results.append(data)

from frappe.chat.util import squashify
from frappe.utils import squashify
return squashify(results)

def validate_and_sanitize_search_inputs(fn):


+ 0
- 23
frappe/chat/__init__.py View File

@@ -1,23 +0,0 @@

import frappe
from frappe import _

session = frappe.session

def authenticate(user, raise_err = True):
if session.user == 'Guest':
if not frappe.db.exists('Chat Token', user):
if raise_err:
frappe.throw(_("Sorry, you're not authorized."))
else:
return False
else:
return True
else:
if user != session.user:
if raise_err:
frappe.throw(_("Sorry, you're not authorized."))
else:
return False
else:
return True

+ 0
- 0
frappe/chat/doctype/__init__.py View File


+ 0
- 0
frappe/chat/doctype/chat_message/__init__.py View File


+ 0
- 10
frappe/chat/doctype/chat_message/chat_message.js View File

@@ -1,10 +0,0 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on('Chat Message', {
onload: function(frm) {
if(frm.doc.type == 'File') {
frm.set_df_property('content', 'read_only', 1);
}
}
});

+ 0
- 91
frappe/chat/doctype/chat_message/chat_message.json View File

@@ -1,91 +0,0 @@
{
"beta": 1,
"creation": "2017-11-10 11:10:40.011099",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"room_type",
"type",
"user",
"room",
"content",
"mentions",
"urls"
],
"fields": [
{
"fieldname": "room_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Room Type",
"options": "Direct\nGroup\nVisitor",
"reqd": 1
},
{
"fieldname": "type",
"fieldtype": "Data",
"label": "Type",
"options": "Content\nFile"
},
{
"fieldname": "user",
"fieldtype": "Link",
"hidden": 1,
"label": "User",
"options": "User",
"read_only": 1
},
{
"fieldname": "room",
"fieldtype": "Link",
"label": "Room",
"options": "Chat Room",
"reqd": 1
},
{
"fieldname": "content",
"fieldtype": "Text",
"label": "Content",
"reqd": 1
},
{
"fieldname": "mentions",
"fieldtype": "Code",
"hidden": 1,
"label": "Mentions"
},
{
"fieldname": "urls",
"fieldtype": "Data",
"hidden": 1,
"label": "URLs"
}
],
"modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Chat",
"name": "Chat Message",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"search_fields": "content, user",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "content",
"track_changes": 1,
"track_seen": 1
}

+ 0
- 215
frappe/chat/doctype/chat_message/chat_message.py View File

@@ -1,215 +0,0 @@
# imports - standard imports
import json

# imports - third-party imports
import requests
from bs4 import BeautifulSoup as Soup

# imports - module imports
from frappe.model.document import Document
from frappe import _, _dict
import frappe

# imports - frappe module imports
from frappe.chat import authenticate
from frappe.chat.util import (
get_if_empty,
check_url,
dictify,
get_emojis,
safe_json_loads,
get_user_doc,
squashify
)

session = frappe.session

class ChatMessage(Document):
pass

def get_message_urls(content):
soup = Soup(content, 'html.parser')
anchors = soup.find_all('a')
urls = [ ]

for anchor in anchors:
text = anchor.text

if check_url(text):
urls.append(text)

return urls

def get_message_mentions(content):
mentions = [ ]
tokens = content.split(' ')

for token in tokens:
if token.startswith('@'):
what = token[1:]
if frappe.db.exists('User', what):
mentions.append(what)
else:
if frappe.db.exists('User', token):
mentions.append(token)

return mentions

def get_message_meta(content):
'''
Assumes content to be HTML. Sanitizes the content
into a dict of metadata values.
'''
meta = _dict(
links = [ ],
mentions = [ ]
)

meta.content = content
meta.urls = get_message_urls(content)
meta.mentions = get_message_mentions(content)

return meta

def sanitize_message_content(content):
emojis = get_emojis()

tokens = content.split(' ')
for token in tokens:
if token.startswith(':') and token.endswith(':'):
what = token[1:-1]

# Expensive, I know.
for emoji in emojis:
for alias in emoji.aliases:
if what == alias:
content = content.replace(token, emoji.emoji)

return content

def get_new_chat_message_doc(user, room, content, type = "Content", link = True):
user = get_user_doc(user)
room = frappe.get_doc('Chat Room', room)

meta = get_message_meta(content)
mess = frappe.new_doc('Chat Message')
mess.room = room.name
mess.room_type = room.type
mess.content = sanitize_message_content(content)
mess.type = type
mess.user = user.name

mess.mentions = json.dumps(meta.mentions)
mess.urls = ','.join(meta.urls)
mess.save(ignore_permissions = True)

if link:
room.update(dict(
last_message = mess.name
))
room.save(ignore_permissions = True)

return mess

def get_new_chat_message(user, room, content, type = "Content"):
mess = get_new_chat_message_doc(user, room, content, type)

resp = dict(
name = mess.name,
user = mess.user,
room = mess.room,
room_type = mess.room_type,
content = json.loads(mess.content) if mess.type in ["File"] else mess.content,
urls = mess.urls,
mentions = json.loads(mess.mentions),
creation = mess.creation,
seen = json.loads(mess._seen) if mess._seen else [ ],
)

return resp

@frappe.whitelist(allow_guest = True)
def send(user, room, content, type = "Content"):
mess = get_new_chat_message(user, room, content, type)

frappe.publish_realtime('frappe.chat.message:create', mess, room = room,
after_commit = True)

@frappe.whitelist(allow_guest = True)
def seen(message, user = None):
authenticate(user)

has_message = frappe.db.exists('Chat Message', message)

if has_message:
mess = frappe.get_doc('Chat Message', message)
mess.add_seen(user)
mess.load_from_db()
room = mess.room
resp = dict(message = message, data = dict(seen = json.loads(mess._seen) if mess._seen else []))

frappe.publish_realtime('frappe.chat.message:update', resp, room = room, after_commit = True)

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', 'room_type', '=', room.type)
],
fields = fields if fields else [
'name', 'room_type', 'room', 'content', 'type', 'user', 'mentions', 'urls', 'creation', '_seen'
],
order_by = 'creation'
)

if not fields or 'seen' in fields:
for m in mess:
m['seen'] = json.loads(m._seen) if m._seen else [ ]
del m['_seen']
if not fields or 'content' in fields:
for m in mess:
m['content'] = json.loads(m.content) if m.type in ["File"] else m.content

frappe.enqueue('frappe.chat.doctype.chat_message.chat_message.mark_messages_as_seen',
message_names=[m.name for m in mess], user=frappe.session.user)

return mess

def mark_messages_as_seen(message_names, user):
'''
Marks chat messages as seen, updates the _seen for each message
(should be run in background process)
'''
for name in message_names:
seen = frappe.db.get_value('Chat Message', name, '_seen') or '[]'
seen = json.loads(seen)
seen.append(user)
seen = json.dumps(seen)
frappe.db.set_value('Chat Message', name, '_seen', seen, update_modified=False)

frappe.db.commit()


@frappe.whitelist()
def get(name, rooms = None, fields = None):
rooms, fields = safe_json_loads(rooms, fields)

has_message = frappe.db.exists('Chat Message', name)

if has_message:
dmess = frappe.get_doc('Chat Message', name)
data = dict(
name = dmess.name,
user = dmess.user,
room = dmess.room,
room_type = dmess.room_type,
content = json.loads(dmess.content) if dmess.type in ["File"] else dmess.content,
type = dmess.type,
urls = dmess.urls,
mentions = dmess.mentions,
creation = dmess.creation,
seen = get_if_empty(dmess._seen, [ ])
)

return data

+ 0
- 8
frappe/chat/doctype/chat_message/chat_message_list.js View File

@@ -1,8 +0,0 @@
frappe.listview_settings['Chat Message'] = {
filters: [
['Chat Message', 'user', '==', frappe.session.user, true]
// I need an or_filter here.
// ['Chat Room', 'owner', '==', frappe.session.user, true],
// ['Chat Room', frappe.session.user, 'in', 'users', true]
]
};

+ 0
- 0
frappe/chat/doctype/chat_profile/__init__.py View File


+ 0
- 10
frappe/chat/doctype/chat_profile/chat_profile.js View File

@@ -1,10 +0,0 @@
/* eslint semi: "never" */
frappe.ui.form.on('Chat Profile', {
refresh: function (form) {
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()?
}
}
});

+ 0
- 98
frappe/chat/doctype/chat_profile/chat_profile.json View File

@@ -1,98 +0,0 @@
{
"autoname": "field:user",
"beta": 1,
"creation": "2017-11-13 18:26:57.943027",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"user",
"status",
"chat_background",
"notifications",
"message_preview",
"notification_tones",
"conversation_tones",
"settings",
"enable_chat"
],
"fields": [
{
"fieldname": "user",
"fieldtype": "Link",
"label": "User",
"options": "User",
"reqd": 1
},
{
"default": "Online",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"options": "Online\nAway\nBusy\nOffline"
},
{
"fieldname": "chat_background",
"fieldtype": "Attach Image",
"label": "Chat Background"
},
{
"fieldname": "notifications",
"fieldtype": "Section Break",
"label": "Notifications"
},
{
"default": "1",
"fieldname": "message_preview",
"fieldtype": "Check",
"label": "Message Preview"
},
{
"default": "1",
"fieldname": "notification_tones",
"fieldtype": "Check",
"label": "Notification Tones"
},
{
"default": "1",
"fieldname": "conversation_tones",
"fieldtype": "Check",
"label": "Conversation Tones"
},
{
"fieldname": "settings",
"fieldtype": "Section Break",
"label": "Settings"
},
{
"default": "1",
"fieldname": "enable_chat",
"fieldtype": "Check",
"label": "Enable Chat"
}
],
"in_create": 1,
"modified": "2019-11-07 13:21:36.414961",
"modified_by": "Administrator",
"module": "Chat",
"name": "Chat Profile",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

+ 0
- 98
frappe/chat/doctype/chat_profile/chat_profile.py View File

@@ -1,98 +0,0 @@
# imports - module imports
from frappe.model.document import Document
from frappe import _
import frappe

# imports - frappe module imports
from frappe.core.doctype.version.version import get_diff
from frappe.chat.doctype.chat_room import chat_room
from frappe.chat.util import (
safe_json_loads,
filter_dict,
dictify
)

session = frappe.session

class ChatProfile(Document):
def on_update(self):
if not self.is_new():
b, a = self.get_doc_before_save(), self
diff = dictify(get_diff(a, b))
if diff:
user = session.user

fields = [changed[0] for changed in diff.changed]

if 'status' in fields:
rooms = chat_room.get(user, filters = ['Chat Room', 'type', '=', 'Direct'])
update = dict(user = user, data = dict(status = self.status))

for room in rooms:
frappe.publish_realtime('frappe.chat.profile:update', update, room = room.name, after_commit = True)

if 'enable_chat' in fields:
update = dict(user = user, data = dict(enable_chat = bool(self.enable_chat)))
frappe.publish_realtime('frappe.chat.profile:update', update, user = user, after_commit = True)

def authenticate(user):
if user != session.user:
frappe.throw(_("Sorry, you're not authorized."))

@frappe.whitelist()
def get(user, fields = None):
duser = frappe.get_doc('User', user)

if frappe.db.exists('Chat Profile', user):
dprof = frappe.get_doc('Chat Profile', user)

# If you're adding something here, make sure the client recieves it.
profile = dict(
# User
name = duser.name,
email = duser.email,
first_name = duser.first_name,
last_name = duser.last_name,
username = duser.username,
avatar = duser.user_image,
bio = duser.bio,
# Chat Profile
status = dprof.status,
chat_background = dprof.chat_background,
message_preview = bool(dprof.message_preview),
notification_tones = bool(dprof.notification_tones),
conversation_tones = bool(dprof.conversation_tones),
enable_chat = bool(dprof.enable_chat)
)
profile = filter_dict(profile, fields)

return dictify(profile)

@frappe.whitelist()
def create(user, exists_ok = False, fields = None):
authenticate(user)

exists_ok, fields = safe_json_loads(exists_ok, fields)

try:
dprof = frappe.new_doc('Chat Profile')
dprof.user = user
dprof.save(ignore_permissions = True)
except frappe.DuplicateEntryError:
frappe.clear_messages()
if not exists_ok:
frappe.throw(_('Chat Profile for User {0} exists.').format(user))

profile = get(user, fields = fields)

return profile

@frappe.whitelist()
def update(user, data):
authenticate(user)

data = safe_json_loads(data)

dprof = frappe.get_doc('Chat Profile', user)
dprof.update(data)
dprof.save(ignore_permissions = True)

+ 0
- 11
frappe/chat/doctype/chat_profile/chat_profile_list.js View File

@@ -1,11 +0,0 @@
frappe.listview_settings['Chat Profile'] =
{
get_indicator: function (doc)
{
const status = frappe.utils.squash(frappe.chat.profile.STATUSES.filter(
s => s.name === doc.status
));

return [__(status.name), status.color, `status,=,${status.name}`]
}
};

+ 0
- 0
frappe/chat/doctype/chat_room/__init__.py View File


+ 0
- 8
frappe/chat/doctype/chat_room/chat_room.js View File

@@ -1,8 +0,0 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on('Chat Room', {
refresh: function (form) {

}
});

+ 0
- 100
frappe/chat/doctype/chat_room/chat_room.json View File

@@ -1,100 +0,0 @@
{
"autoname": "CR.#####",
"beta": 1,
"creation": "2017-11-08 15:27:21.156667",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"type",
"room_name",
"avatar",
"last_message",
"message_count",
"owner",
"user_list",
"users"
],
"fields": [
{
"default": "Direct",
"fieldname": "type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Type",
"options": "Direct\nGroup\nVisitor",
"reqd": 1,
"set_only_once": 1
},
{
"depends_on": "eval:doc.type==\"Group\"",
"fieldname": "room_name",
"fieldtype": "Data",
"label": "Name"
},
{
"depends_on": "eval:doc.type==\"Group\"",
"fieldname": "avatar",
"fieldtype": "Attach Image",
"hidden": 1,
"label": "Avatar"
},
{
"fieldname": "last_message",
"fieldtype": "Data",
"hidden": 1,
"label": "Last Message"
},
{
"fieldname": "message_count",
"fieldtype": "Int",
"hidden": 1,
"label": "Message Count"
},
{
"fieldname": "owner",
"fieldtype": "Data",
"hidden": 1,
"label": "Owner",
"read_only": 1
},
{
"fieldname": "user_list",
"fieldtype": "Section Break",
"label": "Users"
},
{
"fieldname": "users",
"fieldtype": "Table",
"label": "Users",
"options": "Chat Room User"
}
],
"image_field": "avatar",
"modified": "2019-11-07 13:20:24.625329",
"modified_by": "Administrator",
"module": "Chat",
"name": "Chat Room",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 1,
"share": 1,
"write": 1
}
],
"search_fields": "room_name",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "room_name",
"track_changes": 1
}

+ 0
- 227
frappe/chat/doctype/chat_room/chat_room.py View File

@@ -1,227 +0,0 @@
# imports - module imports
from frappe.model.document import Document
from frappe import _
import frappe

# imports - frappe module imports
from frappe.chat import authenticate
from frappe.core.doctype.version.version import get_diff
from frappe.chat.doctype.chat_message import chat_message
from frappe.chat.util import (
safe_json_loads,
dictify,
listify,
squashify,
get_if_empty
)

session = frappe.session


def is_direct(owner, other, bidirectional=False):
def get_room(owner, other):
room = frappe.get_all('Chat Room', filters=[
['Chat Room', 'type', 'in', ('Direct', 'Visitor')],
['Chat Room', 'owner', '=', owner],
['Chat Room User', 'user', '=', other]
], distinct=True)

return room

exists = len(get_room(owner, other)) == 1
if bidirectional:
exists = exists or len(get_room(other, owner)) == 1

return exists


def get_chat_room_user_set(users, filter_=None):
seen, uset = set(), list()

for u in users:
if filter_(u) and u.user not in seen:
uset.append(u)
seen.add(u.user)

return uset


class ChatRoom(Document):
def validate(self):
if self.is_new():
users = get_chat_room_user_set(self.users, filter_=lambda u: u.user != session.user)
self.update(dict(
users=users
))

if self.type == "Direct":
if len(self.users) != 1:
frappe.throw(_('{0} room must have atmost one user.').format(self.type))

other = squashify(self.users)

if self.is_new():
if is_direct(self.owner, other.user, bidirectional=True):
frappe.throw(_('Direct room with {0} already exists.').format(other.user))

if self.type == "Group" and not self.room_name:
frappe.throw(_('Group name cannot be empty.'))

def on_update(self):
if not self.is_new():
before = self.get_doc_before_save()
if not before: return

after = self
diff = dictify(get_diff(before, after))
if diff:
update = {}
for changed in diff.changed:
field, old, new = changed

if field == 'last_message':
new = chat_message.get(new)

update.update({field: new})

if diff.added or diff.removed:
update.update(dict(users=[u.user for u in self.users]))

update = dict(room=self.name, data=update)

frappe.publish_realtime('frappe.chat.room:update', update, room=self.name,
after_commit=True)


@frappe.whitelist(allow_guest=True)
def get(user=None, token=None, rooms=None, fields=None, filters=None):
# There is this horrible bug out here.
# Looks like if frappe.call sends optional arguments (not in right order),
# the argument turns to an empty string.
# I'm not even going to think searching for it.
# Hence, the hack was get_if_empty (previous assign_if_none)
# - Achilles Rasquinha achilles@frappe.io
data = user or token
authenticate(data)

rooms, fields, filters = safe_json_loads(rooms, fields, filters)

rooms = listify(get_if_empty(rooms, []))
fields = listify(get_if_empty(fields, []))

const = [] # constraints
if rooms:
const.append(['Chat Room', 'name', 'in', rooms])
if filters:
if isinstance(filters[0], list):
const = const + filters
else:
const.append(filters)

default = ['name', 'type', 'room_name', 'creation', 'owner', 'avatar']
handle = ['users', 'last_message']

param = [f for f in fields if f not in handle]

rooms = frappe.get_all('Chat Room',
or_filters=[
['Chat Room', 'owner', '=', frappe.session.user],
['Chat Room User', 'user', '=', frappe.session.user]
],
filters=const,
fields=param + ['name'] if param else default,
distinct=True
)

if not fields or 'users' in fields:
for i, r in enumerate(rooms):
droom = frappe.get_doc('Chat Room', r.name)
rooms[i]['users'] = []

for duser in droom.users:
rooms[i]['users'].append(duser.user)

if not fields or 'last_message' in fields:
for i, r in enumerate(rooms):
droom = frappe.get_doc('Chat Room', r.name)
if droom.last_message:
rooms[i]['last_message'] = chat_message.get(droom.last_message)
else:
rooms[i]['last_message'] = None

rooms = squashify(dictify(rooms))

return rooms


@frappe.whitelist(allow_guest=True)
def create(kind, token, users=None, name=None):
authenticate(token)

users = safe_json_loads(users)
create = True

if kind == 'Visitor':
room = squashify(frappe.db.sql("""
SELECT name
FROM `tabChat Room`
WHERE owner=%s
""", (frappe.session.user), as_dict=True))

if room:
room = frappe.get_doc('Chat Room', room.name)
create = False

if create:
room = frappe.new_doc('Chat Room')
room.type = kind
room.owner = frappe.session.user
room.room_name = name

dusers = []

if kind != 'Visitor':
if users:
users = listify(users)
for user in users:
duser = frappe.new_doc('Chat Room User')
duser.user = user
dusers.append(duser)

room.users = dusers
else:
dsettings = frappe.get_single('Website Settings')
room.room_name = dsettings.chat_room_name

users = [user for user in room.users] if hasattr(room, 'users') else []

for user in dsettings.chat_operators:
if user.user not in users:
# appending user to room.users will remove the user from chat_operators
# this is undesirable, create a new Chat Room User instead
chat_room_user = {"doctype": "Chat Room User", "user": user.user}
room.append('users', chat_room_user)

room.save(ignore_permissions=True)

room = get(token=token, rooms=room.name)
if room:
users = [room.owner] + [u for u in room.users]

for user in users:
frappe.publish_realtime('frappe.chat.room:create', room, user=user, after_commit=True)

return room


@frappe.whitelist(allow_guest=True)
def history(room, user, fields=None, limit=10, start=None, end=None):
if frappe.get_doc('Chat Room', room).type != 'Visitor':
authenticate(user)

fields = safe_json_loads(fields)

mess = chat_message.history(room, limit=limit, start=start, end=end)
mess = squashify(mess)

return dictify(mess)

+ 0
- 6
frappe/chat/doctype/chat_room/chat_room_list.js View File

@@ -1,6 +0,0 @@
frappe.listview_settings['Chat Room'] = {
filters: [
['Chat Room', 'owner', '=', frappe.session.user, true],
['Chat Room User', 'user', '=', frappe.session.user, true]
]
};

+ 0
- 0
frappe/chat/doctype/chat_room_user/__init__.py View File


+ 0
- 40
frappe/chat/doctype/chat_room_user/chat_room_user.json View File

@@ -1,40 +0,0 @@
{
"beta": 1,
"creation": "2017-11-08 15:24:21.029314",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"user",
"is_admin"
],
"fields": [
{
"fieldname": "user",
"fieldtype": "Link",
"in_list_view": 1,
"label": "User",
"options": "User",
"reqd": 1
},
{
"default": "0",
"fieldname": "is_admin",
"fieldtype": "Check",
"label": "Admin"
}
],
"in_create": 1,
"istable": 1,
"modified": "2019-11-07 13:21:05.297337",
"modified_by": "Administrator",
"module": "Chat",
"name": "Chat Room User",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

+ 0
- 8
frappe/chat/doctype/chat_room_user/chat_room_user.py View File

@@ -1,8 +0,0 @@
# imports - module imports
from frappe.model.document import Document
import frappe

session = frappe.session

class ChatRoomUser(Document):
pass

+ 0
- 0
frappe/chat/doctype/chat_token/__init__.py View File


+ 0
- 8
frappe/chat/doctype/chat_token/chat_token.js View File

@@ -1,8 +0,0 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on('Chat Token', {
refresh: function(frm) {

}
});

+ 0
- 57
frappe/chat/doctype/chat_token/chat_token.json View File

@@ -1,57 +0,0 @@
{
"autoname": "field:token",
"beta": 1,
"creation": "2018-03-26 18:20:13.825652",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"token",
"ip_address",
"country"
],
"fields": [
{
"fieldname": "token",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Token",
"reqd": 1
},
{
"fieldname": "ip_address",
"fieldtype": "Data",
"label": "IP Address"
},
{
"fieldname": "country",
"fieldtype": "Data",
"label": "Country"
}
],
"in_create": 1,
"modified": "2019-11-07 13:21:24.514558",
"modified_by": "Administrator",
"module": "Chat",
"name": "Chat Token",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

+ 0
- 9
frappe/chat/doctype/chat_token/chat_token.py View File

@@ -1,9 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies and contributors
# License: MIT. See LICENSE

import frappe
from frappe.model.document import Document

class ChatToken(Document):
pass

+ 0
- 13
frappe/chat/util/__init__.py View File

@@ -1,13 +0,0 @@
# imports - module imports
from frappe.chat.util.util import (
get_user_doc,
squashify,
safe_json_loads,
filter_dict,
get_if_empty,
listify,
dictify,
check_url,
create_test_user,
get_emojis
)

+ 0
- 35
frappe/chat/util/test_util.py View File

@@ -1,35 +0,0 @@
# imports - standard imports
import unittest

# imports - module imports
from frappe.chat.util import (
get_user_doc,
safe_json_loads
)
import frappe

class TestChatUtil(unittest.TestCase):
def test_safe_json_loads(self):
number = safe_json_loads("1")
self.assertEqual(type(number), int)

number = safe_json_loads("1.0")
self.assertEqual(type(number), float)

string = safe_json_loads("foobar")
self.assertEqual(type(string), str)

array = safe_json_loads('[{ "foo": "bar" }]')
self.assertEqual(type(array), list)

objekt = safe_json_loads('{ "foo": "bar" }')
self.assertEqual(type(objekt), dict)

true, null = safe_json_loads("true", "null")
self.assertEqual(true, True)
self.assertEqual(null, None)

def test_get_user_doc(self):
# Needs more test cases.
user = get_user_doc()
self.assertEqual(user.name, frappe.session.user)

+ 0
- 108
frappe/chat/util/util.py View File

@@ -1,108 +0,0 @@
# imports - standard imports
import json
from collections.abc import MutableMapping, MutableSequence, Sequence

# imports - third-party imports
import requests
from urllib.parse import urlparse

# imports - module imports
import frappe
from frappe.exceptions import DuplicateEntryError
from frappe.model.document import Document

session = frappe.session


def get_user_doc(user = None):
if isinstance(user, Document):
return user

user = user or session.user
user = frappe.get_doc('User', user)

return user

def squashify(what):
if isinstance(what, Sequence) and len(what) == 1:
return what[0]

return what

def safe_json_loads(*args):
results = []

for arg in args:
try:
arg = json.loads(arg)
except Exception:
pass

results.append(arg)

return squashify(results)

def filter_dict(what, keys, ignore = False):
copy = dict()

if keys:
for k in keys:
if k not in what and not ignore:
raise KeyError('{key} not in dict.'.format(key = k))
else:
copy.update({
k: what[k]
})
else:
copy = what.copy()

return copy

def get_if_empty(a, b):
if not a:
a = b
return a

def listify(arg):
if not isinstance(arg, list):
arg = [arg]
return arg

def dictify(arg):
if isinstance(arg, MutableSequence):
for i, a in enumerate(arg):
arg[i] = dictify(a)
elif isinstance(arg, MutableMapping):
arg = frappe._dict(arg)

return arg

def check_url(what, raise_err = False):
if not urlparse(what).scheme:
if raise_err:
raise ValueError('{what} not a valid URL.')
else:
return False

return True

def create_test_user(module):
try:
test_user = frappe.new_doc('User')
test_user.first_name = '{module}'.format(module = module)
test_user.email = 'testuser.{module}@example.com'.format(module = module)
test_user.save()
except DuplicateEntryError:
frappe.log('Test User Chat Profile exists.')

def get_emojis():
redis = frappe.cache()
emojis = redis.hget('frappe_emojis', 'emojis')

if not emojis:
resp = requests.get('http://git.io/frappe-emoji')
if resp.ok:
emojis = resp.json()
redis.hset('frappe_emojis', 'emojis', emojis)

return dictify(emojis)

+ 0
- 42
frappe/chat/website/__init__.py View File

@@ -1,42 +0,0 @@

import frappe
from frappe.chat.util import filter_dict, safe_json_loads

from frappe.sessions import get_geo_ip_country

@frappe.whitelist(allow_guest = True)
def settings(fields = None):
fields = safe_json_loads(fields)

dsettings = frappe.get_single('Website Settings')
response = dict(
socketio = dict(
port = frappe.conf.socketio_port
),
enable = bool(dsettings.chat_enable),
enable_from = dsettings.chat_enable_from,
enable_to = dsettings.chat_enable_to,
room_name = dsettings.chat_room_name,
welcome_message = dsettings.chat_welcome_message,
operators = [
duser.user for duser in dsettings.chat_operators
]
)

if fields:
response = filter_dict(response, fields)

return response

@frappe.whitelist(allow_guest = True)
def token():
dtoken = frappe.new_doc('Chat Token')

dtoken.token = frappe.generate_hash()
dtoken.ip_address = frappe.local.request_ip
country = get_geo_ip_country(dtoken.ip_address)
if country:
dtoken.country = country['iso_code']
dtoken.save(ignore_permissions = True)

return dtoken.token

+ 2
- 8
frappe/core/doctype/role/role.json View File

@@ -17,7 +17,6 @@
"navigation_settings_section",
"search_bar",
"notifications",
"chat",
"list_settings_section",
"list_sidebar",
"bulk_actions",
@@ -85,12 +84,6 @@
"fieldtype": "Check",
"label": "Search Bar"
},
{
"default": "1",
"fieldname": "chat",
"fieldtype": "Check",
"label": "Chat"
},
{
"fieldname": "list_settings_section",
"fieldtype": "Section Break",
@@ -155,10 +148,11 @@
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-01-27 10:35:37.638350",
"modified": "2021-10-08 14:06:55.729364",
"modified_by": "Administrator",
"module": "Core",
"name": "Role",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{


+ 2
- 2
frappe/core/doctype/role/role.py View File

@@ -5,7 +5,7 @@ import frappe

from frappe.model.document import Document

desk_properties = ("search_bar", "notifications", "chat", "list_sidebar",
desk_properties = ("search_bar", "notifications", "list_sidebar",
"bulk_actions", "view_switcher", "form_sidebar", "timeline", "dashboard")

class Role(Document):
@@ -82,4 +82,4 @@ def role_query(doctype, txt, searchfield, start, page_len, filters):
report_filters.extend(filters)

return frappe.get_all('Role', limit_start=start, limit_page_length=page_len,
filters=report_filters, as_list=1)
filters=report_filters, as_list=1)

+ 3
- 17
frappe/core/doctype/system_settings/system_settings.json View File

@@ -65,9 +65,7 @@
"attach_view_link",
"prepared_report_section",
"enable_prepared_report_auto_deletion",
"prepared_report_expiry_period",
"chat",
"enable_chat"
"prepared_report_expiry_period"
],
"fields": [
{
@@ -381,18 +379,6 @@
"fieldtype": "Check",
"label": "Hide footer in auto email reports"
},
{
"collapsible": 1,
"fieldname": "chat",
"fieldtype": "Section Break",
"label": "Chat"
},
{
"default": "1",
"fieldname": "enable_chat",
"fieldtype": "Check",
"label": "Enable Chat"
},
{
"fieldname": "column_break_21",
"fieldtype": "Column Break"
@@ -474,7 +460,7 @@
"icon": "fa fa-cog",
"issingle": 1,
"links": [],
"modified": "2021-03-30 11:47:47.330437",
"modified": "2021-10-08 14:04:11.406725",
"modified_by": "Administrator",
"module": "Core",
"name": "System Settings",
@@ -492,4 +478,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}
}

+ 1
- 6
frappe/core/doctype/user/user.json View File

@@ -617,11 +617,6 @@
"link_doctype": "Contact",
"link_fieldname": "user"
},
{
"group": "Profile",
"link_doctype": "Chat Profile",
"link_fieldname": "user"
},
{
"group": "Profile",
"link_doctype": "Blogger",
@@ -709,4 +704,4 @@
"sort_order": "DESC",
"title_field": "full_name",
"track_changes": 1
}
}

+ 1
- 4
frappe/core/doctype/user/user.py View File

@@ -421,9 +421,6 @@ class User(Document):
WHERE `%s` = %s""" %
(tab, field, '%s', field, '%s'), (new_name, old_name))

if frappe.db.exists("Chat Profile", old_name):
frappe.rename_doc("Chat Profile", old_name, new_name, force=True, show_alert=False)

if frappe.db.exists("Notification Settings", old_name):
frappe.rename_doc("Notification Settings", old_name, new_name, force=True, show_alert=False)

@@ -1057,4 +1054,4 @@ def get_enabled_users():
enabled_users = frappe.get_all("User", filters={"enabled": "1"}, pluck="name")
return enabled_users

return frappe.cache().get_value("enabled_users", _get_enabled_users)
return frappe.cache().get_value("enabled_users", _get_enabled_users)

+ 6
- 6
frappe/core/doctype/user_permission/test_user_permission.py View File

@@ -73,7 +73,7 @@ class TestUserPermission(unittest.TestCase):
def test_for_applicable_on_update_from_apply_to_all(self):
''' Update User Permission from all to some applicable Doctypes'''
user = create_user('test_bulk_creation_update@example.com')
param = get_params(user,'User', user.name, applicable = ["Chat Room", "Chat Message"])
param = get_params(user,'User', user.name, applicable = ["Comment", "Contact"])

# Initially create User Permission document with apply_to_all checked
is_created = add_user_permissions(get_params(user, 'User', user.name))
@@ -84,8 +84,8 @@ class TestUserPermission(unittest.TestCase):
frappe.db.commit()

removed_apply_to_all = frappe.db.exists("User Permission", get_exists_param(user))
is_created_applicable_first = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Room"))
is_created_applicable_second = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Message"))
is_created_applicable_first = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Comment"))
is_created_applicable_second = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Contact"))

# Check that apply_to_all is removed
self.assertIsNone(removed_apply_to_all)
@@ -101,14 +101,14 @@ class TestUserPermission(unittest.TestCase):
param = get_params(user, 'User', user.name)

# create User permissions that with applicable
is_created = add_user_permissions(get_params(user, 'User', user.name, applicable = ["Chat Room", "Chat Message"]))
is_created = add_user_permissions(get_params(user, 'User', user.name, applicable = ["Comment", "Contact"]))

self.assertEqual(is_created, 1)

is_created = add_user_permissions(param)
is_created_apply_to_all = frappe.db.exists("User Permission", get_exists_param(user))
removed_applicable_first = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Room"))
removed_applicable_second = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Message"))
removed_applicable_first = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Comment"))
removed_applicable_second = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Contact"))

# To check that a User permission with apply_to_all exists
self.assertIsNotNone(is_created_apply_to_all)


+ 0
- 7
frappe/hooks.py View File

@@ -76,8 +76,6 @@ before_tests = "frappe.utils.install.before_tests"

email_append_to = ["Event", "ToDo", "Communication"]

get_rooms = 'frappe.chat.doctype.chat_room.chat_room.get_rooms'

calendars = ["Event"]

leaderboards = "frappe.desk.leaderboard.get_leaderboards"
@@ -281,11 +279,6 @@ sounds = [
{"name": "error", "src": "/assets/frappe/sounds/error.mp3", "volume": 0.1},
{"name": "alert", "src": "/assets/frappe/sounds/alert.mp3", "volume": 0.2},
# {"name": "chime", "src": "/assets/frappe/sounds/chime.mp3"},

# frappe.chat sounds
{ "name": "chat-message", "src": "/assets/frappe/sounds/chat-message.mp3", "volume": 0.1 },
{ "name": "chat-notification", "src": "/assets/frappe/sounds/chat-notification.mp3", "volume": 0.1 }
# frappe.chat sounds
]

bot_parsers = [


+ 1
- 2
frappe/modules.txt View File

@@ -9,7 +9,6 @@ Integrations
Printing
Contacts
Data Migration
Chat
Social
Automation
Event Streaming
Event Streaming

+ 0
- 1
frappe/public/js/chat.bundle.js View File

@@ -1 +0,0 @@
import "./frappe/chat";

+ 0
- 1
frappe/public/js/desk.bundle.js View File

@@ -99,7 +99,6 @@ import "./frappe/query_string.js";

// import "./frappe/ui/comment.js";

import "./frappe/chat.js";
import "./frappe/utils/energy_point_utils.js";
import "./frappe/utils/dashboard_utils.js";
import "./frappe/ui/chart.js";


+ 0
- 2788
frappe/public/js/frappe/chat.js
File diff suppressed because it is too large
View File


+ 0
- 461
frappe/public/less/chat.bundle.less View File

@@ -1,461 +0,0 @@
// Author - Achilles Rasquinha <achilles@frappe.io>
// http://codeguide.co - @mdo (Author of Bootstrap)

@import "../css/font-awesome.css";
@import "../css/octicons/octicons.css";

// Typography
@font-weight-bold: 700;
@font-weight-heavy: 900;

@chat-toggle-height: 40px;

@fab-box-shadow: 0 5px 15px rgba(0, 0, 0, .25);
@fab-size: 48px;
@fab-size-lg: 56px;
@fab-margin: 20px;

@chat-popper-margin: @fab-margin;
@chat-popper-panel-width: 350px;
@chat-popper-panel-height: 500px;
// z-index greater than FAB, lesser than modal.
@chat-popper-z-index: 1035;

// BS modal's box-shadow
@chat-popper-panel-box-shadow: @fab-box-shadow;

// https://github.com/twbs/bootstrap/blob/v3.3.7/less/variables.less#L278
// Keep z-index of the ChatPopper higher than others, lower than modal background.

@chat-room-list-content-max-width: 180px;

@chat-form-font-size: 12px;
@chat-form-menu-border-radius: 4px;
@chat-form-list-group-height: 150px; // Hints

// Typography
.font-bold { font-weight: @font-weight-bold; }
.font-heavy { font-weight: @font-weight-heavy; }

// Utilities
.cursor-pointer { cursor: pointer; }

// Hacks and Fixes
// suggested by rushabh@frappe.io. Thanks, Rushabh!
// .avatar { padding: 2px; }

.frappe-fab
{
position: fixed;
bottom: 0;
right: 0;
border-radius: 50%;
box-shadow: @fab-box-shadow;
margin: @fab-margin;
width: @fab-size;
height: @fab-size;

&.frappe-fab-lg
{
width: @fab-size-lg;
height: @fab-size-lg;
}
}

.navbar
{
.frappe-chat-toggle
{
height: @chat-toggle-height;
text-align: center;
}

.octicon { margin-top: 3px; } // Hack, somewhat.
}

.frappe-chat
{
& > .frappe-chat-popper
{
position: fixed;
bottom: 0px;
right: 0px;
margin: @chat-popper-margin;
z-index: @chat-popper-z-index;

& > .frappe-chat-popper-collapse
{
& > .panel
{
position: relative;
display: flex;
flex-direction: column;
width: @chat-popper-panel-width;
height: @chat-popper-panel-height;
box-shadow: @chat-popper-panel-box-shadow;

.vcenter
{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

.panel-heading
{
.panel-title
{
.media-heading
{
font-size: 12px;
margin: 0px;
padding: 0px;
}

.media-left {
padding-right: 10px;
}

.media-subtitle
{
font-size: 12px;
}
}

.frappe-chat-action-bar
{
form
{
width: 100%;
}

.btn-action
{
margin-left: 5px !important;
}
}
}

.frappe-chat-room-list
{
height: 100%;
overflow-y: auto;
padding: 0 1px 0 1px;

& > li > a
{
border-radius: 0px !important;
}

.media
{
.media-heading, .media-subtitle
{
max-width: @chat-room-list-content-max-width;
}
}
}
}

& > .panel.panel-bg
{
background-size: 350px 500px;
background-image: url(/assets/frappe/images/chat/wallpaper-default.jpg);
}

& > .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;

.chat-form
{
.form-control
{
font-size: @chat-form-font-size;
}

.dropdown-menu
{
border-radius: @chat-form-menu-border-radius;
}

// Hints
.hint-list.list-group
{
margin: 0px;
max-height: @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 }
}
}
}
}
}

@chat-color-grey: #8D99A6;

@chat-base-font-size: 12px;
@chat-base-font-size-lg: 14px;

@chat-base-spacing: 5px;

// ChatForm
@chat-form-border: 1px solid #D1D8DD;

// ChatList
@chat-list-bg-color: #FAFBFC;

// ChatList.Item
@chat-list-item-padding: @chat-base-spacing @chat-base-spacing * 2;

// ChatBubble
@chat-bubble-padding: @chat-base-spacing @chat-base-spacing * 2;
@chat-bubble-min-width: 25%;
@chat-bubble-max-width: 75%;

@chat-bubble-box-shadow: 0px 0.1px 0.5px 0px rgba(0,0,0,0.5);

@chat-bubble-border-size: 1px;
@chat-bubble-border-radius: @chat-base-spacing;

@chat-bubble-l-color: #EBEFF2;
@chat-bubble-r-color: #EBF7CF;

@chat-bubble-l-groupable-margin-left: 40px;

@chat-bubble-author-font-size: @chat-base-font-size;

@chat-bubble-content-margin-bottom: @chat-base-spacing;

@chat-bubble-meta-font-size: @chat-base-spacing * 2;

@chat-bubble-check-font-size: @chat-base-font-size;

.frappe-chat-popper-collapse
{
& > .panel
{
& > .panel-heading
{
padding: @chat-base-spacing @chat-base-spacing * 2;

.btn-back
{
margin-right: @chat-base-spacing;
}

.avatar
{
width: 32px; height: 32px;
}
}
}

}


.chat-room-footer
{
.chat-form
{
border-top: @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;
overflow: hidden;
}

.fa
{
font-size: @chat-base-font-size-lg;
transition: color 0.5s; // Change, with grace. :)
}
}
}


.chat-list
{
height: 100%;
// background: @chat-list-bg-color;
overflow-y: scroll;

.chat-list-item
{
.avatar
{
vertical-align: top;

.standard-image
{
background-color: white;
}
}

.cursor-pointer;

border: none !important;
padding: @chat-list-item-padding;
background: transparent;

.chat-bubble
{
max-width: @chat-bubble-max-width;
display: inline-block;
padding: @chat-bubble-padding;
border-radius: @chat-bubble-border-radius;

-webkit-box-shadow: @chat-bubble-box-shadow;
-moz-box-shadow: @chat-bubble-box-shadow;
box-shadow: @chat-bubble-box-shadow;

@media (max-width : 768px) {
min-width: @chat-bubble-min-width;
}

&.chat-bubble-l
{
&.chat-groupable
{
margin-left: @chat-bubble-l-groupable-margin-left;
}

// background-color: @chat-bubble-l-color;
background-color: white;

.chat-bubble-meta
{
& > .chat-bubble-creation, & > .chat-bubble-check i
{
color: darken(@chat-bubble-l-color, 50%) !important;
}
}
}

&.chat-bubble-r
{
text-align: right;
background-color: @chat-bubble-r-color;

.chat-bubble-meta
{
& > .chat-bubble-creation, & > .chat-bubble-check i
{
color: darken(@chat-bubble-r-color, 50%) !important;
}
}
}

.chat-bubble-author
{
font-size: @chat-bubble-author-font-size;

a
{
.font-bold;

text-decoration: none !important;
}
}

.chat-bubble-content
{
margin-bottom: @chat-bubble-content-margin-bottom;
word-wrap: break-word;
}

.chat-bubble-meta
{
font-size: @chat-bubble-meta-font-size;

& > .chat-bubble-check
{
margin-left: @chat-base-spacing;

i
{
font-size: @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;
}

// v12 fixes for visitor chat
.panel-default > .panel-heading {
background-color: #f7fafc;
border-color: #ced5db;
display: flex;
}

textarea.form-control {
resize: none;
height: 3.2em;
}

@media (max-width: 767px) {
.nav-stacked > li + li {
margin-top: 0px;
margin-left: 2px;
}
}

BIN
frappe/public/sounds/chat-message.mp3 View File


BIN
frappe/public/sounds/chat-notification.mp3 View File


+ 0
- 1
frappe/templates/base.html View File

@@ -57,7 +57,6 @@
window.dev_server = {{ dev_server }};
window.socketio_port = {{ (frappe.socketio_port or 'null') }};
window.show_language_picker = {{ show_language_picker or 'false' }};
window.is_chat_enabled = {{ chat_enable }};
</script>
</head>
<body frappe-session-status="{{ 'logged-in' if frappe.session.user != 'Guest' else 'logged-out'}}" data-path="{{ path | e }}" {%- if template and template.endswith('.md') %} frappe-content-type="markdown" {%- endif %} class="{{ body_class or ''}}">


+ 29
- 0
frappe/utils/__init__.py View File

@@ -17,6 +17,7 @@ from typing import Generator, Iterable
from urllib.parse import quote, urlparse
from werkzeug.test import Client
from redis.exceptions import ConnectionError
from collections.abc import MutableMapping, MutableSequence, Sequence

import frappe
# utility functions like cint, int, flt, etc.
@@ -861,3 +862,31 @@ def groupby_metric(iterable: typing.Dict[str, list], key: str):

def get_table_name(table_name: str) -> str:
return f"tab{table_name}" if not table_name.startswith("__") else table_name

def squashify(what):
if isinstance(what, Sequence) and len(what) == 1:
return what[0]

return what

def safe_json_loads(*args):
results = []

for arg in args:
try:
arg = json.loads(arg)
except Exception:
pass

results.append(arg)

return squashify(results)

def dictify(arg):
if isinstance(arg, MutableSequence):
for i, a in enumerate(arg):
arg[i] = dictify(a)
elif isinstance(arg, MutableMapping):
arg = frappe._dict(arg)

return arg

+ 2
- 54
frappe/website/doctype/website_settings/website_settings.json View File

@@ -63,14 +63,7 @@
"subdomain",
"head_html",
"robots_txt",
"route_redirects",
"section_break_39",
"chat_enable",
"chat_enable_from",
"chat_enable_to",
"chat_room_name",
"chat_welcome_message",
"chat_operators"
"route_redirects"
],
"fields": [
{
@@ -266,51 +259,6 @@
"fieldtype": "Code",
"label": "Robots.txt"
},
{
"collapsible": 1,
"fieldname": "section_break_39",
"fieldtype": "Section Break",
"label": "Chat"
},
{
"default": "0",
"fieldname": "chat_enable",
"fieldtype": "Check",
"label": "Enable Chat"
},
{
"depends_on": "chat_enable",
"fieldname": "chat_enable_from",
"fieldtype": "Time",
"label": "From"
},
{
"depends_on": "chat_enable",
"fieldname": "chat_enable_to",
"fieldtype": "Time",
"label": "To"
},
{
"default": "Support",
"depends_on": "chat_enable",
"fieldname": "chat_room_name",
"fieldtype": "Data",
"label": "Chat Room Name"
},
{
"default": "Hi, how may I help you?",
"depends_on": "chat_enable",
"fieldname": "chat_welcome_message",
"fieldtype": "Data",
"label": "Welcome Message"
},
{
"depends_on": "chat_enable",
"fieldname": "chat_operators",
"fieldtype": "Table",
"label": "Chat Operators",
"options": "Chat Room User"
},
{
"fieldname": "route_redirects",
"fieldtype": "Table",
@@ -470,4 +418,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}
}

+ 1
- 2
frappe/website/doctype/website_settings/website_settings.py View File

@@ -120,8 +120,7 @@ def get_website_settings(context=None):
"facebook_share", "google_plus_one", "twitter_share", "linked_in_share",
"disable_signup", "hide_footer_signup", "head_html", "title_prefix",
"navbar_template", "footer_template", "navbar_search", "enable_view_tracking",
"footer_logo", "call_to_action", "call_to_action_url", "show_language_picker",
"chat_enable"]:
"footer_logo", "call_to_action", "call_to_action_url", "show_language_picker"]:
if hasattr(settings, k):
context[k] = settings.get(k)



+ 6
- 12
frappe/website/js/website.js View File

@@ -633,17 +633,11 @@ $(document).on("page-change", function() {

frappe.ready(function() {
frappe.show_language_picker();
if (window.is_chat_enabled) {
frappe.require([
"/assets/frappe/node_modules/moment/min/moment-with-locales.min.js",
"/assets/frappe/node_modules/moment-timezone/builds/moment-timezone-with-data.min.js",
"chat.bundle.css",
"/assets/frappe/js/lib/socket.io.min.js"
], () => {
frappe.require('chat.bundle.js', () => {
frappe.chat.setup();
});
});
}

frappe.require([
"/assets/frappe/node_modules/moment/min/moment-with-locales.min.js",
"/assets/frappe/node_modules/moment-timezone/builds/moment-timezone-with-data.min.js",
"/assets/frappe/js/lib/socket.io.min.js"
]);
frappe.socketio.init(window.socketio_port);
});

Loading…
Cancel
Save