diff --git a/.eslintrc b/.eslintrc index 05a5930db6..ec8d486c1e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -56,6 +56,7 @@ "root": true, "globals": { "frappe": true, + "Vue": true, "__": true, "_p": true, "_f": true, @@ -112,11 +113,9 @@ "getCookie": true, "getCookies": true, "get_url_arg": true, - "md5": true, "$": true, "jQuery": true, - "Vue": true, "moment": true, "hljs": true, "Awesomplete": true, diff --git a/frappe/api.py b/frappe/api.py index 13123c742e..e5dfae186f 100644 --- a/frappe/api.py +++ b/frappe/api.py @@ -53,6 +53,7 @@ def handle(): if call=="method": frappe.local.form_dict.cmd = doctype + frappe.local.form_dict.pop("data", None) return frappe.handler.handle() elif call=="resource": diff --git a/frappe/boot.py b/frappe/boot.py index 043c1b0361..05117acb7a 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -98,6 +98,7 @@ def load_conf_settings(bootinfo): def load_desktop_icons(bootinfo): from frappe.config import get_modules_from_all_apps_for_user bootinfo.allowed_modules = get_modules_from_all_apps_for_user() + bootinfo.home_settings = frappe.db.get_value("User", frappe.session.user, 'home_settings','') def get_allowed_pages(): return get_user_pages_or_reports('Page') diff --git a/frappe/client.py b/frappe/client.py index 39f0fd8516..fb2d47925b 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -7,6 +7,7 @@ from frappe import _ import frappe.model import frappe.utils import json, os +from frappe.utils import get_safe_filters from six import iteritems, string_types, integer_types @@ -370,17 +371,4 @@ def check_parent_permission(parent, child_doctype): if frappe.permissions.has_permission(parent): return # Either parent not passed or the user doesn't have permission on parent doctype of child table! - raise frappe.PermissionError - -def get_safe_filters(filters): - try: - filters = json.loads(filters) - - if isinstance(filters, (integer_types, float)): - filters = frappe.as_unicode(filters) - - except (TypeError, ValueError): - # filters are not passesd, not json - pass - - return filters + raise frappe.PermissionError \ No newline at end of file diff --git a/frappe/config/__init__.py b/frappe/config/__init__.py index 506c59eb58..eaacf26442 100644 --- a/frappe/config/__init__.py +++ b/frappe/config/__init__.py @@ -11,28 +11,22 @@ def get_modules_from_all_apps_for_user(user=None): all_modules = get_modules_from_all_apps() user_blocked_modules = frappe.get_doc('User', user).get_blocked_modules() - allowed_modules_list = [m for m in all_modules if m.get("module_name") not in user_blocked_modules] empty_tables_by_module = get_all_empty_tables_by_module() - home_settings = frappe.db.get_value("User", frappe.session.user, 'home_settings') - if home_settings: - home_settings = json.loads(home_settings) - for module in allowed_modules_list: - module_name = module["module_name"] + module_name = module.get("module_name") + + # Apply onboarding status if module_name in empty_tables_by_module: module["onboard_present"] = 1 - if home_settings: - category_settings = home_settings.get(module.get("category"), {}) if module.get("category") else {} - if module_name not in category_settings: - module["hidden"] = 1 - else: - links = category_settings[module_name]["links"] - if links: - module["links"] = get_module_link_items_from_list(module["app"], module_name, links.split(",")) + + + + # Set defaults links + module["links"] = get_onboard_items(module["app"], frappe.scrub(module_name))[:5] else: module["links"] = get_onboard_items(module["app"], frappe.scrub(module_name))[:6] diff --git a/frappe/core/doctype/view_log/view_log.json b/frappe/core/doctype/view_log/view_log.json index cf9d2eb746..af192cf32b 100644 --- a/frappe/core/doctype/view_log/view_log.json +++ b/frappe/core/doctype/view_log/view_log.json @@ -20,6 +20,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "viewed_by", "fieldtype": "Data", "hidden": 0, @@ -40,7 +41,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, - "search_index": 0, + "search_index": 1, "set_only_once": 1, "translatable": 0, "unique": 0 @@ -52,6 +53,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "reference_doctype", "fieldtype": "Link", "hidden": 0, @@ -73,7 +75,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, - "search_index": 0, + "search_index": 1, "set_only_once": 1, "translatable": 0, "unique": 0 @@ -85,6 +87,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "reference_name", "fieldtype": "Dynamic Link", "hidden": 0, @@ -106,7 +109,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, - "search_index": 0, + "search_index": 1, "set_only_once": 1, "translatable": 0, "unique": 0 @@ -122,7 +125,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-01-03 13:04:31.389182", + "modified": "2019-03-11 18:28:33.277683", "modified_by": "Administrator", "module": "Core", "name": "View Log", diff --git a/frappe/core/notifications.py b/frappe/core/notifications.py index 0f932d9f67..26e2049273 100644 --- a/frappe/core/notifications.py +++ b/frappe/core/notifications.py @@ -17,6 +17,9 @@ def get_notification_config(): "for_other": { "Likes": "frappe.core.notifications.get_unseen_likes", "Email": "frappe.core.notifications.get_unread_emails", + }, + "for_module": { + "Social": "frappe.social.doctype.post.post.get_unseen_post_count" } } diff --git a/frappe/desk/doctype/todo/todo_list.js b/frappe/desk/doctype/todo/todo_list.js index 47cb7bd1f6..6200e85dca 100644 --- a/frappe/desk/doctype/todo/todo_list.js +++ b/frappe/desk/doctype/todo/todo_list.js @@ -10,21 +10,14 @@ frappe.listview_settings['ToDo'] = { }, hide_name_column: true, refresh: function(me) { - // override assigned to me by owner if (me.todo_sidebar_setup) return; - me.page.sidebar.find(".assigned-to-me a").off("click").on("click", function() { - me.filter_area.remove("assigned_by"); - me.filter_area.add([[me.doctype, "owner", '=', frappe.session.user]]); - }); - // add assigned by me me.page.add_sidebar_item(__("Assigned By Me"), function() { - me.filter_area.remove("owner"); me.filter_area.add([[me.doctype, "assigned_by", '=', frappe.session.user]]); - }, ".assigned-to-me"); + }, ('.list-link[data-view="Kanban"]')); me.todo_sidebar_setup = true; }, add_fields: ["reference_type", "reference_name"], -} +} \ No newline at end of file diff --git a/frappe/desk/listview.py b/frappe/desk/listview.py index 8d439f228a..a05a3e7f04 100644 --- a/frappe/desk/listview.py +++ b/frappe/desk/listview.py @@ -24,3 +24,10 @@ def set_list_settings(doctype, values): frappe.clear_messages() doc.update(json.loads(values)) doc.save() + +@frappe.whitelist() +def get_user_assignments_and_count(): + user_list = frappe.get_list("User", filters={"user_type": "System User"}) + assignment_data = sorted([{"count":frappe.db.count('ToDo', filters = {'reference_type': 'Issue', 'owner': user['name'], 'status': 'Open'}), + "name": user['name']} for user in user_list], key=lambda k: k['count'], reverse = True) + return assignment_data \ No newline at end of file diff --git a/frappe/desk/moduleview.py b/frappe/desk/moduleview.py index d5b62d1406..5978bc7ef0 100644 --- a/frappe/desk/moduleview.py +++ b/frappe/desk/moduleview.py @@ -300,18 +300,32 @@ def get_links(app, module): for section in sections: for item in section["items"]: link_names.append(item.get("label")) - print(link_names) return link_names @frappe.whitelist() -def get_module_link_items_from_dict(module_link_list_map): - module_link_list_map = json.loads(module_link_list_map) - module_links = {} - for module, data in module_link_list_map.items(): - print(data) - module_links[module] = get_module_link_items_from_list(data["app"], module, data["links"]) - return module_links +def update_desk_section_settings(desk_section, new_settings): + home_settings = frappe.db.get_value("User", frappe.session.user, 'home_settings') + if home_settings: + home_settings = json.loads(home_settings) + else: + return {} + + new_settings = json.loads(new_settings) + + for module, data in new_settings.items(): + if data.get("links"): + data["links"] = get_module_link_items_from_list(data["app"], module, data.get("links")) + data.pop("app", None) + + home_settings[desk_section] = new_settings + settings_json_str = json.dumps(home_settings) + # # This didn't work + # frappe.db.set_value("User", frappe.session.user, 'home_settings', json.dumps(home_settings)) + frappe.db.sql("""update tabUser set home_settings = %s""", (settings_json_str), debug=True) + frappe.db.commit() + + return new_settings def get_module_link_items_from_list(app, module, list_of_link_names): diff --git a/frappe/model/document.py b/frappe/model/document.py index 4542cb03cb..697278f4bd 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -1154,7 +1154,7 @@ class Document(BaseDocument): frappe.local.flags.commit = True def add_viewed(self, user=None): - '''add log to communication when a user viewes a document''' + '''add log to communication when a user views a document''' if not user: user = frappe.session.user diff --git a/frappe/patches.txt b/frappe/patches.txt index 487aabc21f..467efe6a62 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -14,6 +14,7 @@ execute:frappe.reload_doc('core', 'doctype', 'comment') frappe.patches.v8_0.drop_is_custom_from_docperm execute:frappe.reload_doc('core', 'doctype', 'module_def') #2017-09-22 execute:frappe.reload_doc('core', 'doctype', 'version') #2017-04-01 +execute:frappe.reload_doc('email', 'doctype', 'document_follow') frappe.patches.v11_0.replicate_old_user_permissions frappe.patches.v11_0.reload_and_rename_view_log #2019-01-03 frappe.patches.v7_1.rename_scheduler_log_to_error_log @@ -200,7 +201,6 @@ frappe.patches.v9_1.move_feed_to_activity_log execute:frappe.delete_doc('Page', 'data-import-tool', ignore_missing=True) frappe.patches.v10_0.reload_countries_and_currencies frappe.patches.v10_0.refactor_social_login_keys -execute:frappe.reload_doc('email', 'doctype', 'document_follow') frappe.patches.v10_0.enable_chat_by_default_within_system_settings frappe.patches.v10_0.remove_custom_field_for_disabled_domain execute:frappe.delete_doc("Page", "chat") @@ -232,8 +232,7 @@ frappe.patches.v11_0.set_missing_creation_and_modified_value_for_user_permission frappe.patches.v11_0.remove_doctype_user_permissions_for_page_and_report frappe.patches.v11_0.set_default_letter_head_source frappe.patches.v12_0.set_primary_key_in_series -execute:frappe.reload_doc('email', 'doctype', 'document_follow') execute:frappe.delete_doc("Page", "modules", ignore_missing=True) frappe.patches.v11_0.set_default_letter_head_source frappe.patches.v12_0.setup_comments_from_communications -frappe.patches.v12_0.init_desk_settings +frappe.patches.v12_0.init_desk_settings #11-03-2019 diff --git a/frappe/patches/v12_0/init_desk_settings.py b/frappe/patches/v12_0/init_desk_settings.py index aa1060b906..782ced8a26 100644 --- a/frappe/patches/v12_0/init_desk_settings.py +++ b/frappe/patches/v12_0/init_desk_settings.py @@ -6,29 +6,6 @@ from frappe.config import get_modules_from_all_apps_for_user from frappe.desk.moduleview import get_onboard_items def execute(): - """Set the initial customizations for desk, with modules, indices and links.""" + """Reset the initial customizations for desk, with modules, indices and links.""" frappe.reload_doc("core", "doctype", "user") - all_modules = get_modules_from_all_apps_for_user() - - settings = {} - - for module in all_modules: - if not module.get("app"): continue - - links = get_onboard_items(module["app"], frappe.scrub(module["module_name"]))[:5] - module_settings = { - "links": ",".join([d["label"] for d in links]) - } - category_dict = settings.get(module.get("category", ""), None) - if category_dict: - module_settings["index"] = len(category_dict) - category_dict[module.get("module_name")] = module_settings - else: - module_settings["index"] = 0 - settings[module.get("category", "")] = { - module.get("module_name"): module_settings - } - - settings_json_str = json.dumps(settings) - - frappe.db.sql("""update tabUser set home_settings = %s""", (settings_json_str), debug=True) + frappe.db.sql("""update tabUser set home_settings = %s""", (''), debug=True) diff --git a/frappe/public/build.json b/frappe/public/build.json index 682cc6f3f1..73a9fae189 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -22,9 +22,6 @@ "js/chat.js": [ "public/js/frappe/chat.js" ], - "js/frappe-vue.min.js": [ - "public/js/frappe_vue.js" - ], "js/frappe-recorder.min.js": [ "public/js/frappe/recorder/recorder.js" ], diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index d0464d46cd..87538b287a 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -39,6 +39,7 @@ frappe.Application = Class.extend({ throw 'boot failed'; } + this.setup_frappe_vue(); this.load_bootinfo(); this.load_user_permissions(); this.make_nav_bar(); @@ -106,6 +107,8 @@ frappe.Application = Class.extend({ dialog.get_close_btn().toggle(false); }); + this.setup_social_listeners(); + // listen to build errors this.setup_build_error_listener(); @@ -119,6 +122,12 @@ frappe.Application = Class.extend({ } }, + + setup_frappe_vue() { + Vue.prototype.__ = window.__; + Vue.prototype.frappe = window.frappe; + }, + set_password: function(user) { var me=this; frappe.call({ @@ -530,6 +539,14 @@ frappe.Application = Class.extend({ console.log(data); }); } + }, + + setup_social_listeners() { + frappe.realtime.on('mention', (message) => { + if (frappe.get_route()[0] !== 'social') { + frappe.show_alert(message); + } + }); } }); diff --git a/frappe/public/js/frappe/dom.js b/frappe/public/js/frappe/dom.js index 39e576ebf8..830ed0a5ce 100644 --- a/frappe/public/js/frappe/dom.js +++ b/frappe/public/js/frappe/dom.js @@ -68,7 +68,7 @@ frappe.dom = { return txt; } }, - is_element_in_viewport: function (el) { + is_element_in_viewport: function (el, tolerance=0) { //special bonus for those using jQuery if (typeof jQuery === "function" && el instanceof jQuery) { @@ -78,10 +78,10 @@ frappe.dom = { var rect = el.getBoundingClientRect(); return ( - rect.top >= 0 - && rect.left >= 0 - // && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */ - // && rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */ + rect.top + tolerance >= 0 + && rect.left + tolerance >= 0 + && rect.bottom - tolerance <= $(window).height() + && rect.right - tolerance <= $(window).width() ); }, diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js index 4508760631..b2926b31d8 100644 --- a/frappe/public/js/frappe/form/footer/timeline.js +++ b/frappe/public/js/frappe/form/footer/timeline.js @@ -368,7 +368,6 @@ frappe.ui.form.Timeline = class Timeline { } else { c.content_html = c.content; c.content_html = frappe.utils.strip_whitespace(c.content_html); - c.content_html = c.content_html.replace(/</g,"<").replace(/>/g,">"); } // bold @mentions diff --git a/frappe/public/js/frappe/list/list_sidebar.html b/frappe/public/js/frappe/list/list_sidebar.html index 48ed21765b..619fa66277 100644 --- a/frappe/public/js/frappe/list/list_sidebar.html +++ b/frappe/public/js/frappe/list/list_sidebar.html @@ -45,8 +45,12 @@ -
  • - {%= __("Assigned To Me") %} +
  • + +
  • {% if(frappe.help.has_help(doctype)) { %}
  • {{ __("Help") }}
  • diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index 20f1f030d4..9f5ebfb2a7 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -26,7 +26,7 @@ frappe.views.ListSidebar = class ListSidebar { this.setup_reports(); this.setup_list_filter(); - this.setup_assigned_to_me(); + this.setup_assigned_to(); this.setup_views(); this.setup_kanban_boards(); this.setup_calendar_view(); @@ -216,12 +216,35 @@ frappe.views.ListSidebar = class ListSidebar { }); } - setup_assigned_to_me() { - this.page.sidebar.find(".assigned-to-me a").on("click", () => { - this.list_view.filter_area.add(this.list_view.doctype, "_assign", "like", `%${frappe.session.user}%`); + setup_assigned_to() { + let dropdown = this.page.sidebar.find('.assigned-dropdown'); + if(this.doctype === 'ToDo') { + $('.assigned-to').remove(); + } + frappe.call('frappe.desk.listview.get_user_assignments_and_count').then((data) => { + let current_user_count = data.message.find(user => user.name === frappe.session.user).count; + this.get_html_for_assigned(frappe.session.user, current_user_count).appendTo(dropdown); + let user_list = data.message.filter(user => !['Guest', frappe.session.user, 'Administrator'].includes(user.name)); + user_list.forEach((user) => { + this.get_html_for_assigned(user.name, user.count).appendTo(dropdown); + }); + $(".assigned-dropdown li a").on("click", (e) => { + let assigned_user = $(e.currentTarget).find($('.assigned-user')).text(); + if(assigned_user === 'Me') assigned_user = frappe.session.user; + this.list_view.filter_area.remove('_assign'); + this.list_view.filter_area.add(this.list_view.doctype, "_assign", "like", `%${assigned_user}%`); + }); }); } + get_html_for_assigned(name, count) { + if (name === frappe.session.user) name='Me'; + if (count > 99) count='99+'; + let html = $('
  • ' + + name + '' + count + '
  • '); + return html; + } + setup_upgrade_box() { let upgrade_list = $(``).appendTo(this.sidebar); diff --git a/frappe/public/js/frappe/social/Home.vue b/frappe/public/js/frappe/social/Home.vue index 5b0297b725..55397de76b 100644 --- a/frappe/public/js/frappe/social/Home.vue +++ b/frappe/public/js/frappe/social/Home.vue @@ -11,6 +11,7 @@ import Wall from './pages/Wall.vue'; import Profile from './pages/Profile.vue'; +import UserList from './pages/UserList.vue'; import NotFound from './components/NotFound.vue'; import ImageViewer from './components/ImageViewer.vue'; @@ -27,6 +28,10 @@ function get_route_map() { 'key': frappe.get_route()[2] } }, + 'social/users': { + 'component': UserList, + 'props': {} + }, 'not_found': { 'component': NotFound, } @@ -58,11 +63,14 @@ export default { frappe.app_updates.on('user_image_updated', () => { this.$root.$emit('user_image_updated') }) + + this.update_primary_action(frappe.get_route()[1]) }, mounted() { frappe.route.on('change', () => { if (frappe.get_route()[0] === 'social') { this.set_current_page(); + this.update_primary_action(frappe.get_route()[1]) frappe.utils.scroll_to(0); $("body").attr("data-route", frappe.get_route_str()); } @@ -73,6 +81,15 @@ export default { set_current_page() { this.current_page = this.get_current_page(); }, + update_primary_action(current_route) { + if (current_route === 'home') { + this.$root.page.set_primary_action(__('Post'), () => { + frappe.social.post_dialog.show(); + }); + } else { + this.$root.page.clear_primary_action() + } + }, get_current_page() { const route_map = get_route_map(); const route = frappe.get_route_str(); diff --git a/frappe/public/js/frappe/social/components/Post.vue b/frappe/public/js/frappe/social/components/Post.vue index 71afccfa1f..297ffa702f 100644 --- a/frappe/public/js/frappe/social/components/Post.vue +++ b/frappe/public/js/frappe/social/components/Post.vue @@ -12,6 +12,12 @@ v-if="options.length" :options="options" /> + + + +
    {{ user_name }} @@ -170,6 +176,11 @@ export default { }).then(frappe.dom.unfreeze) }) }, + update_seen() { + frappe.xcall('frappe.social.doctype.post.post.set_seen', { + post_name: this.post.name + }).then(() => this.post.seen = true) + }, generate_preview(link_element) { // TODO: move the code to separate component frappe.xcall('frappe.social.doctype.post.post.get_link_info', { @@ -203,5 +214,14 @@ export default { padding-top: 0px; background: #F6F6F6; } +.indicator { + margin-left: 15px; +} +.fade-enter-active, .fade-leave-active { + transition: opacity .8s; +} +.fade-enter, .fade-leave-to { + opacity: 0; +} diff --git a/frappe/public/js/frappe/social/components/PostAction.vue b/frappe/public/js/frappe/social/components/PostAction.vue index 491238210d..3c506dc198 100644 --- a/frappe/public/js/frappe/social/components/PostAction.vue +++ b/frappe/public/js/frappe/social/components/PostAction.vue @@ -44,7 +44,7 @@ export default { cursor: pointer; color: #8d99a6; span { - padding-left: 5px; + padding: 5px; } &:hover { color: darken(#8d99a6, 10%); diff --git a/frappe/public/js/frappe/social/components/PostComment.vue b/frappe/public/js/frappe/social/components/PostComment.vue index 2d41a0a839..b102fba5b5 100644 --- a/frappe/public/js/frappe/social/components/PostComment.vue +++ b/frappe/public/js/frappe/social/components/PostComment.vue @@ -2,26 +2,30 @@
    {{ __('Add a comment') }}
    - - +
    +
    +
    + {{ __("Ctrl+Enter to add comment") }} +
    + +
    -
    +
    - - - {{ comment.content }} + + +
    @@ -29,10 +33,9 @@ + + + + diff --git a/frappe/public/js/frappe/social/social_home.js b/frappe/public/js/frappe/social/social_home.js index ccddbb580a..26e4542d88 100644 --- a/frappe/public/js/frappe/social/social_home.js +++ b/frappe/public/js/frappe/social/social_home.js @@ -8,25 +8,20 @@ frappe.social.Home = class SocialHome { this.page = parent.page; this.setup_header(); this.make_body(); - this.set_primary_action(); } make_body() { this.$social_container = this.$parent.find('.layout-main'); - frappe.require('/assets/js/frappe-vue.min.js', () => { - new Vue({ - el: this.$social_container[0], - render: h => h(Home) - }); + new Vue({ + el: this.$social_container[0], + render: h => h(Home), + data: { + 'page': this.page + } }); } setup_header() { this.page.set_title(__('Social')); } - set_primary_action() { - this.page.set_primary_action(__('Post'), () => { - frappe.social.post_dialog.show(); - }); - } }; frappe.social.post_dialog = new frappe.ui.Dialog({ diff --git a/frappe/public/js/frappe/ui/like.js b/frappe/public/js/frappe/ui/like.js index c99f62dee6..8bed02119f 100644 --- a/frappe/public/js/frappe/ui/like.js +++ b/frappe/public/js/frappe/ui/like.js @@ -85,26 +85,30 @@ frappe.ui.click_toggle_like = function() { return false; } -frappe.ui.setup_like_popover = function($parent, selector, check_not_liked=true) { +frappe.ui.setup_like_popover = ($parent, selector, check_not_liked=true) => { if (frappe.dom.is_touchscreen()) { return; } - $parent.on("mouseover", selector, function() { - var $wrapper = $(this); - - $wrapper.popover({ + $parent.on('mouseover', selector, function() { + const target_element = $(this); + target_element.popover({ animation: true, - placement: "right", - content: function() { - var liked_by = $wrapper.attr('data-liked-by'); + placement: 'right', + trigger: 'manual', + template:`
    +
    +
    +
    `, + content: () => { + let liked_by = target_element.attr('data-liked-by'); liked_by = liked_by ? decodeURI(liked_by) : '[]'; liked_by = JSON.parse(liked_by); - var user = frappe.session.user; + const user = frappe.session.user; // hack if (check_not_liked) { - if ($wrapper.find(".not-liked").length) { + if (target_element.find(".not-liked").length) { if (liked_by.indexOf(user)!==-1) { liked_by.splice(liked_by.indexOf(user), 1); } @@ -118,16 +122,46 @@ frappe.ui.setup_like_popover = function($parent, selector, check_not_liked=true) if (!liked_by.length) { return ""; } - return frappe.render_template("liked_by", {"liked_by": liked_by}); + + let liked_by_list = $(``); + + // to show social profile of the user + let link_base = '#social/profile/'; + + liked_by.forEach(user => { + // append user list item + liked_by_list.append(` +
  • ${frappe.avatar(user)} + ${frappe.user.full_name(user)} +
  • + `); + }); + + liked_by_list.children('li').click(ev => { + let user = ev.currentTarget.dataset.user; + target_element.popover('hide'); + frappe.set_route(link_base + user); + }); + + return liked_by_list; }, html: true, container: 'body' }); - $wrapper.popover('show'); + target_element.popover('show'); + + $(".popover").on("mouseleave", () => { + target_element.popover('hide'); + }); + + target_element.on('mouseout', () => { + setTimeout(() => { + if (!$('.popover:hover').length) { + target_element.popover('hide'); + } + }, 100); + }); }); - $parent.on("mouseout", selector, function() { - $(this).popover('destroy'); - }); -} +}; diff --git a/frappe/public/js/frappe/ui/liked_by.html b/frappe/public/js/frappe/ui/liked_by.html deleted file mode 100644 index 0b836c5561..0000000000 --- a/frappe/public/js/frappe/ui/liked_by.html +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/frappe/public/js/frappe/views/components/DeskSection.vue b/frappe/public/js/frappe/views/components/DeskSection.vue index 0718f05d02..3a9ec45e9c 100644 --- a/frappe/public/js/frappe/views/components/DeskSection.vue +++ b/frappe/public/js/frappe/views/components/DeskSection.vue @@ -28,192 +28,216 @@ import DeskModuleBox from "./DeskModuleBox.vue"; import { generate_route } from "./utils.js"; export default { - props: ["category", "all_modules"], - components: { - DeskModuleBox - }, - data() { - let template_modules = this.all_modules; - template_modules.forEach(module => { - if (module.links) { - module.links.forEach(link => { - link.route = generate_route(link); - }); - } - }); + props: ['category', 'all_modules', 'customization_settings'], + components: { + DeskModuleBox + }, + data() { + let default_modules = this.all_modules; + let modules = this.get_customized_modules(default_modules, this.customization_settings); - return { - template_modules: template_modules, - modules: template_modules.slice(), - settings: {}, - all_settings: {}, - dragged_index: -1, - hovered_index: -1 - }; - }, - methods: { - show_customize_dialog() { - if (!this.dialog) { - this.get_settings().then(() => { - const fields = this.make_fields(); - this.make_and_show_dialog(fields); - }); - } else { - this.dialog.show(); - } - }, - get_settings() { - return frappe.db.get_value("User", user, "home_settings").then(resp => { - this.all_settings = JSON.parse(resp.message["home_settings"]); - this.settings = this.all_settings[this.category]; - }); - }, - make_fields() { - let fields = []; - let template_modules = this.template_modules; - let selected_modules = Object.keys(this.settings); + return { + default_modules: default_modules, + modules: modules, + new_settings: {}, + dragged_index: -1, + hovered_index: -1, + } + }, + methods: { + show_customize_dialog() { + if(!this.dialog) { + const fields = this.make_fields(); + this.make_and_show_dialog(fields); + } else { + this.dialog.show(); + } + }, + make_fields() { + let fields = []; + this.modules.forEach(module => { + fields.push(this.get_module_select_field(module)); - template_modules.forEach(module => { - fields.push(this.get_module_select_field(module, selected_modules)); + if(module.links) { + fields.push(this.get_links_multiselect_field(module)); + } + }); + return fields; + }, + make_and_show_dialog(fields) { + this.dialog = new frappe.ui.Dialog({ + title: __("Customize " + this.category), + fields: fields, + primary_action_label: __("Update"), + primary_action: (values) => { + this.update_settings(values); + } + }); - if (module.links) { - fields.push(this.get_links_multiselect_field(module)); - } - }); + this.dialog.modal_body.find('.clearfix').css({'display': 'none'}); + this.dialog.modal_body.find('.frappe-control*[data-fieldtype="MultiSelect"]').css({'margin-bottom': '30px'}); - return fields; - }, - make_and_show_dialog(fields) { - this.dialog = new frappe.ui.Dialog({ - title: __("Customize " + this.category), - fields: fields, - primary_action_label: __("Update"), - primary_action: values => { - let module_link_list_map = {}; + this.dialog.show(); + }, - Object.keys(values).forEach(module_name => { - if (!module_name.includes("links") && values[module_name]) { - const links_str = values[module_name + "_links"] || ""; - this.settings[module_name]["links"] = links_str; - if (values[module_name]) { - module_link_list_map[module_name] = { - links: links_str.split(","), - app: this.template_modules.filter( - m => m.module_name === module_name - )[0].app - }; - } - } - }); + update_settings(values) { + // Figure out the diff from the default settings known from modules + let new_settings = {}; + const checkbox_fields = Object.keys(values).filter(f => !f.includes('links')); - frappe.db - .set_value("User", user, "home_settings", this.all_settings) - .then(resp => { - this.update_modules(module_link_list_map); - this.dialog.hide(); - }) - .fail(err => { - frappe.msgprint(err); - }); - } - }); + checkbox_fields.forEach(module_name => { + const default_module = this.default_modules.filter(f => f.module_name === module_name)[0]; - this.dialog.modal_body.find(".clearfix").css({ display: "none" }); - this.dialog.modal_body - .find('.frappe-control*[data-fieldtype="MultiSelect"]') - .css({ "margin-bottom": "30px" }); + // Check if hidden changed + const default_hidden = default_module.hidden ? 1 : 0; + const new_hidden = !values[module_name] ? 1 : 0; + const hidden_changed = new_hidden != default_hidden; - this.dialog.show(); - }, + // Check if links changed + let links_changed = 0; + let new_links = []; - update_modules(module_link_list_map) { - frappe.call({ - type: "GET", - method: "frappe.desk.moduleview.get_module_link_items_from_dict", - freeze: true, - args: { - module_link_list_map: module_link_list_map - }, - callback: r => { - const module_links_dict = r.message; - this.template_modules.map((m, i) => { - let raw_links = module_links_dict[m.module_name]; - raw_links.forEach(link => { - link.route = generate_route(link); - }); - if (Object.keys(module_link_list_map).includes(m.module_name)) { - m.hidden = 0; - m.links = raw_links; - } else { - m.hidden = 1; - } - }); + if(!new_hidden) { + const default_links = default_module.links.map(l => (l.name || l.label)); + const new_links_str = values[module_name + '_links'] || ''; + new_links = new_links_str ? new_links_str.split(",") : []; + links_changed = !this.are_arrays_equal(new_links, default_links); + } - this.modules = this.template_modules.filter(m => !m.hidden); - } - }); - }, + // Make new settings + let new_module_settings; - get_module_select_field(module, selected_modules) { - return { - label: __(module.module_name), - fieldname: module.module_name, - fieldtype: "Check", - default: selected_modules.includes(module.module_name) ? 1 : 0 - }; - }, + if(hidden_changed || links_changed) { + new_module_settings = {}; + if(hidden_changed) { + new_module_settings.hidden = new_hidden; + } + if(links_changed) { + new_module_settings.links = new_links; + } + } - get_links_multiselect_field(module) { - return { - label: __(""), - fieldname: module.module_name + "_links", - fieldtype: "MultiSelect", - get_data: function() { - let data = []; + if(new_module_settings) { + new_module_settings.app = this.default_modules.filter(m => m.module_name === module_name)[0].app; + new_settings[module_name] = new_module_settings; + } + }); - frappe.call({ - type: "GET", - method: "frappe.desk.moduleview.get_links", - async: false, - no_spinner: true, - args: { - app: module.app, - module: module.module_name - }, - callback: function(r) { - data = r.message; - } - }); - return data; - }, - default: module.links.map(m => m.name || m.label), - depends_on: module.module_name - }; - }, + if(Object.keys(new_settings)) { + frappe.call({ + type: "GET", + method:'frappe.desk.moduleview.update_desk_section_settings', + freeze: true, + args: { + desk_section: this.category, + new_settings: new_settings + }, + callback: (r) => { + let new_settings_with_link_objects = r.message; + let home_settings = JSON.parse(frappe.boot.home_settings); + home_settings[this.category] = new_settings_with_link_objects; + frappe.boot.home_settings = JSON.stringify(home_settings); - box_dragstart(index) { - this.dragged_index = index; - }, + this.modules = this.get_customized_modules(this.default_modules, new_settings_with_link_objects); + this.dialog.hide(); + } + }); + } else { + this.dialog.hide(); + }; + }, - box_dragend(index) { - this.dragged_index = -1; - this.hovered_index = -1; - }, + get_customized_modules(default_modules, customization_settings={}) { + return default_modules.map(module => { + let customized_module = JSON.parse(JSON.stringify(module)); - box_enter(index) { - this.hovered_index = index; - }, + const module_settings = customization_settings[module.module_name]; + if(module_settings) { + if(module_settings.links) { + customized_module.links = module_settings.links; + } + customized_module.hidden = module_settings ? module_settings.hidden : 0; + } - box_drop(index) { - let d = this.dragged_index; - let h = this.hovered_index; - if (d < h) { - this.modules.splice(h, 0, this.modules[d]); - this.modules.splice(d, 1); - } - } - } -}; + if(customized_module.links) { + customized_module.links.forEach(link => { + link.route = generate_route(link); + }); + } + + return customized_module; + }); + }, + + get_module_select_field(module) { + return { + label: __(module.module_name), + fieldname: module.module_name, + fieldtype: "Check", + default: module.hidden ? 0 : 1 + } + }, + + get_links_multiselect_field(module) { + return { + label: __(""), + fieldname: module.module_name + "_links", + fieldtype: "MultiSelect", + get_data: function() { + let data = []; + + frappe.call({ + type: "GET", + method:'frappe.desk.moduleview.get_links', + async: false, + no_spinner: true, + args: { + app: module.app, + module: module.module_name, + }, + callback: function(r) { + data = r.message; + } + }); + return data; + }, + default: module.links.map(l => (l.name || l.label)), + depends_on: module.module_name + }; + }, + + are_arrays_equal(arr1, arr2) { + if(arr1.length !== arr2.length) return false; + let areEqual = true; + arr1.map((d, i) => { + if(arr2[i] !== d) areEqual = false; + }); + return areEqual; + }, + + box_dragstart(index) { + this.dragged_index = index; + }, + + box_dragend(index) { + this.dragged_index = -1; + this.hovered_index = -1; + }, + + box_enter(index) { + this.hovered_index = index; + }, + + box_drop(index) { + let d = this.dragged_index; + let h = this.hovered_index; + if (d < h) { + this.modules.splice(h, 0, this.modules[d]); + this.modules.splice(d, 1); + } + }, + } +}