@@ -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', | |||
@@ -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): | |||
@@ -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 |
@@ -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); | |||
} | |||
} | |||
}); |
@@ -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 | |||
} |
@@ -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 |
@@ -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] | |||
] | |||
}; |
@@ -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()? | |||
} | |||
} | |||
}); |
@@ -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 | |||
} |
@@ -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) |
@@ -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}`] | |||
} | |||
}; |
@@ -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) { | |||
} | |||
}); |
@@ -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 | |||
} |
@@ -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) |
@@ -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] | |||
] | |||
}; |
@@ -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 | |||
} |
@@ -1,8 +0,0 @@ | |||
# imports - module imports | |||
from frappe.model.document import Document | |||
import frappe | |||
session = frappe.session | |||
class ChatRoomUser(Document): | |||
pass |
@@ -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) { | |||
} | |||
}); |
@@ -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 | |||
} |
@@ -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 |
@@ -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 | |||
) |
@@ -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) |
@@ -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) |
@@ -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 |
@@ -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": [ | |||
{ | |||
@@ -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) |
@@ -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 | |||
} | |||
} |
@@ -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 | |||
} | |||
} |
@@ -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) |
@@ -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) | |||
@@ -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 = [ | |||
@@ -9,7 +9,6 @@ Integrations | |||
Printing | |||
Contacts | |||
Data Migration | |||
Chat | |||
Social | |||
Automation | |||
Event Streaming | |||
Event Streaming |
@@ -1 +0,0 @@ | |||
import "./frappe/chat"; |
@@ -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"; | |||
@@ -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; | |||
} | |||
} |
@@ -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 ''}}"> | |||
@@ -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 |
@@ -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 | |||
} | |||
} |
@@ -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) | |||
@@ -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); | |||
}); |