diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index 0666926685..5e1f345d72 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -173,10 +173,11 @@ frappe.views.BaseList = class BaseList { } this.menu_items.map(item => { + // console.log('item', item) if (item.condition && item.condition() === false) { return; } - const $item = this.page.add_menu_item(item.label, item.action, item.standard); + const $item = this.page.add_menu_item(item.label, item.action, item.standard, item.shortcut); if (item.class) { $item && $item.addClass(item.class); } diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 47a9ee4b65..453f1be687 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -202,7 +202,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } else { this.make_new_doc(); } - }, 'octicon octicon-plus'); + }, 'octicon octicon-plus','ctrl+b'); } else { this.page.clear_primary_action(); } @@ -226,6 +226,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { this.setup_events(); this.settings.onload && this.settings.onload(this); this.show_restricted_list_indicator_if_applicable(); + } setup_freeze_area() { @@ -753,8 +754,93 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { this.setup_like(); this.setup_realtime_updates(); this.setup_action_handler(); + this.setup_keyboard_nav(); } + setup_keyboard_nav() { + let selected, next, new_class, is_nav; + let index = 0; + $(document).on('keydown',null, (e)=> { + if ($('[role="listbox"]').is(":visible")) { + console.log('true'); + is_nav = true; + } + else { + is_nav = false; + let list = this.$result.find('.list-row-container'); + list = list.add($('.list-paging-area').find('.btn')); + if(e.which === 40) { + if(selected) { + new_class = list.eq(index).is('button') ? 'btn-selected': 'selected'; + // console.log('presseddd') + selected.removeClass(new_class); + index++; + next = list.eq(index); + if(next.length > 0) { + new_class = list.eq(index).is('button') ? 'btn-selected': 'selected'; + selected = next.addClass(new_class); + } else { + new_class = list.eq(0).is('button') ? 'btn-selected': 'selected'; + selected = list.eq(0).addClass(new_class); + index = 0; + } + } else { + new_class = list.eq(0).is('button') ? 'btn-selected': 'selected'; + selected = list.eq(0).addClass(new_class); + } + } else if(e.which === 38) { + if(selected) { + new_class = list.eq(index).is('button') ? 'btn-selected': 'selected'; + selected.removeClass(new_class); + if(index>0) index--; + next = list.eq(index); + if(next.length > 0) { + new_class = list.eq(index).is('button') ? 'btn-selected': 'selected'; + selected = next.addClass(new_class); + } else { + new_class = list.eq(list.length-1).is('button') ? 'btn-selected': 'selected'; + selected = list.eq(list.length - 1).addClass(new_class); + index = list.length - 1; + } + } else { + new_class = list.eq(list.length-1).is('button') ? 'btn-selected': 'selected'; + selected = list.eq(list.length - 1).addClass(new_class); + } + } else if(e.which === 13) { + console.log('is nav', is_nav) + if(!is_nav) { + if(selected.is('button') ) { + selected.click(); + selected.removeClass('btn-selected'); + index = 0; + } + else selected.find('.list-row').click(); + index = 0; + } + } + + } + }); + } + + // down_navigation(list) { + // let selected_element; + // if(selected_element) { + // console.log('presseddd') + // selected_element.removeClass('selected'); + // next = selected_element.next(); + // console.log('next', next) + // if(next.length > 0){ + // selected_element = next.addClass('selected'); + // } else { + // selected_element = list.eq(0).addClass('selected'); + // } + // } else{ + // selected_element = list.eq(0).addClass('selected'); + // console.log('liSelected', liSelected); + // } + // } + setup_filterable() { // filterable events this.$result.on('click', '.filterable', e => { @@ -1030,6 +1116,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { if (frappe.model.can_import(doctype)) { items.push({ label: __('Import'), + // shortcut: 'Ctrl + I', action: () => frappe.set_route('List', 'Data Import', { reference_doctype: doctype }), @@ -1040,6 +1127,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { if (frappe.model.can_set_user_permissions(doctype)) { items.push({ label: __('User Permissions'), + // shortcut: 'Ctrl + U', action: () => frappe.set_route('List', 'User Permission', { allow: doctype }), @@ -1050,6 +1138,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { if (frappe.user_roles.includes('System Manager')) { items.push({ label: __('Role Permissions Manager'), + // shortcut: 'Ctrl + M', action: () => frappe.set_route('permission-manager', { doctype }), @@ -1068,6 +1157,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { }); } }, + // shortcut: 'Alt + C', standard: true }); } @@ -1075,12 +1165,14 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { items.push({ label: __('Toggle Sidebar'), action: () => this.toggle_side_bar(), + // shortcut: 'Ctrl + T', standard: true }); items.push({ label: __('Share URL'), action: () => this.share_url(), + // shortcut: 'Alt + S', standard: true }); @@ -1088,6 +1180,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { // edit doctype items.push({ label: __('Edit DocType'), + // shortcut: 'Ctrl + E', action: () => frappe.set_route('Form', 'DocType', doctype), standard: true }); @@ -1096,6 +1189,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { if (frappe.user.has_role('System Manager')) { items.push({ label: __('Settings'), + // shortcut: 'Ctrl + Shift + S', action: () => this.show_list_settings(), standard: true }); diff --git a/frappe/public/js/frappe/ui/page.js b/frappe/public/js/frappe/ui/page.js index db7cf83090..e580c5ff80 100644 --- a/frappe/public/js/frappe/ui/page.js +++ b/frappe/public/js/frappe/ui/page.js @@ -42,6 +42,193 @@ frappe.ui.Page = Class.extend({ make: function() { this.wrapper = $(this.parent); this.add_main_section(); + this.setup_nav(); + }, + + keyboard_shortcut(shortcut, element) { + let shortcut_key = shortcut.replace(/ /g,''); + // console.log('true', shortcut); + let namespace = 'keyup.'+ shortcut.charAt(shortcut.length-1); + $(document).on(namespace,null, shortcut_key, (e)=> { + console.log('heree'); + // e.stopImmediatePropagation(); + // console.log($li, shortcut); + element.click() + }) + }, + + + setup_nav() { + console.log('MENU', this.menu); + let menu_el = $('.menu-btn-group'); + console.log('menu', menu_el); + let buttons = $('.page-actions').find('button'), menu_list, actions_list, el_list; + let timer = false; + $(document).on('keydown', null, 'alt', ()=> { + menu_list = $('.menu-btn-group ul li a').filter(':visible'); + actions_list = $('.actions-btn-group ul li a').filter(':visible'); + console.log('menu list', menu_list); + clearTimeout(timer); + timer=false; + if(!timer) timer = setTimeout(()=> { + if(menu_list.length) { + el_list = this.underline_alt_elements(menu_list); + } else if(actions_list.length) { + el_list = this.underline_alt_elements(actions_list); + } + else el_list = this.underline_alt_elements(buttons); + },1000); + }); + + $(document).on('keyup',null, (e)=> { + let index = 0, liSelected, next; + clearTimeout(timer); + timer = false; + console.log('el list', el_list); + if(el_list) { + this.remove_alt_elements(el_list); + } + menu_list = $('.menu-btn-group ul li a').filter(':visible'); + console.log('prssanna', menu_list); + if(menu_list.length) { + console.log('yasss'); + let li = menu_list; + console.log('list', li); + if(e.which === 40) { + console.log('down'); + if(liSelected) { + liSelected.removeClass('selected'); + next = liSelected.next(); + if(next.length > 0) { + liSelected = next.addClass('selected'); + } else { + liSelected = li.eq(0).addClass('selected'); + } + } else { + liSelected = li.eq(0).addClass('selected'); + } + } else if(e.which === 38) { + console.log('up'); + if(liSelected) { + liSelected.removeClass('selected'); + next = liSelected.prev(); + if(next.length > 0) { + liSelected = next.addClass('selected'); + } else { + liSelected = li.last().addClass('selected'); + } + } else { + liSelected = li.last().addClass('selected'); + } + } + } + }) + + // $(document).on('keydown',null,(e)=> { + // console.log('hereee'); + // let menu = $('.menu-btn-group.open') + // console.log('menu', menu); + // if(menu.length) { + // console.log('yasss'); + // let li = menu_list; + // console.log('list', li); + // if(e.which === 40) { + // if(liSelected) { + // liSelected.removeClass('selected'); + // next = liSelected.next(); + // if(next.length > 0) { + // liSelected = next.addClass('selected'); + // } else { + // liSelected = li.eq(0).addClass('selected'); + // } + // } else { + // liSelected = li.eq(0).addClass('selected'); + // } + // } else if(e.which === 38) { + // if(liSelected) { + // liSelected.removeClass('selected'); + // next = liSelected.prev(); + // if(next.length > 0) { + // liSelected = next.addClass('selected'); + // } else { + // liSelected = li.last().addClass('selected'); + // } + // } else { + // liSelected = li.last().addClass('selected'); + // } + // } + // } + // }) + }, + + underline_alt_elements($list) { + console.log('list', $list); + let char_list = []; + let el_list = []; + $.each($list,(i,v)=> { + let $el = $(v); + if($el.is(':visible')) { + let el_obj={}; + el_obj.$el = $el; + let text = $el.text().trim(); + let i = 0; + let char = text.charAt(0); + console.log(char_list); + while(char_list.includes(char.toUpperCase())) { + i++; + char = text.charAt(i); + } + char_list.push(char.toUpperCase()); + console.log(char); + let new_text = this.underline_character(text,i); + let new_html = $el.html().replace(text, new_text); + let shortcut = 'alt+'+text.charAt(i); + el_obj.shortcut = shortcut; + el_list.push(el_obj); + // char_list.push(text.charAt(0)); + $el.html(new_html); + } + }); + console.log('element list', el_list); + el_list.forEach((element)=> { + console.log('here'); + let namespace = 'keyup.'+ element.shortcut.charAt(element.shortcut.length-1); + $(document).off(namespace); + if(element.$el.is(':visible')) { + console.log('visible'); + this.keyboard_shortcut(element.shortcut, element.$el); + } + }) + return el_list; + }, + + remove_alt_elements($list) { + $.each($list,(i,v)=> { + // let $el = $(v); + if(v.$el.is(':visible')) { + // let text = $el.text().trim(); + // let new_text = this.remove_underline(text); + // console.log('new text', new_text); + let new_html = this.remove_underline(v.$el.html()); + console.log(new_html); + v.$el.html(new_html); + let namespace = 'keyup.'+ v.shortcut.charAt(v.shortcut.length-1); + $(document).off(namespace); + } + }); + }, + + underline_character(str, n) { + console.log('str', str); + let new_str; + new_str = str.slice(0,n) + ""+str.charAt(n)+"" + str.slice(n+1); + console.log('new str', new_str); + return new_str; + }, + + remove_underline(str) { + let new_str = str.replace('','').replace('',''); + return new_str; }, get_empty_state: function(title, message, primary_action) { @@ -166,14 +353,17 @@ frappe.ui.Page = Class.extend({ } }, - set_primary_action: function(label, click, icon, working_label) { + set_primary_action: function(label, click, icon, shortcut, working_label) { this.set_action(this.btn_primary, { label: label, click: click, icon: icon, - working_label: working_label + working_label: working_label, + shortcut: shortcut }); - + if(shortcut) { + this.keyboard_shortcut(shortcut,this.btn_primary); + } return this.btn_primary; }, @@ -212,8 +402,9 @@ frappe.ui.Page = Class.extend({ //--- Menu --// - add_menu_item: function(label, click, standard) { - return this.add_dropdown_item(label, click, standard, this.menu); + add_menu_item: function(label, click, standard, shortcut) { + // console.log('shortcut', shortcut) + return this.add_dropdown_item(label, click, standard, this.menu, shortcut); }, clear_menu: function() { @@ -271,15 +462,14 @@ frappe.ui.Page = Class.extend({ * @param {object} parent - DOM object representing the parent of the drop down item lists * @param {Boolean} show_parent - Whether to show the dropdown button if dropdown item is added */ - add_dropdown_item: function(label, click, standard, parent, show_parent=true) { + add_dropdown_item: function(label, click, standard, parent, shortcut, show_parent=true) { let item_selector = 'li > a.grey-link'; if(show_parent) { parent.parent().removeClass("hide"); } - - var $li = $('
  • '+ label +'
  • '), - $link = $li.find("a").on("click", click); - + if(shortcut) var $li = $('
  • '+ label + ' ('+shortcut+')
  • '); + else var $li = $('
  • '+ label +'
  • '); + var $link = $li.find("a").on("click", click); if (this.is_in_group_button_dropdown(parent, item_selector, label)) return; if(standard) { @@ -291,7 +481,10 @@ frappe.ui.Page = Class.extend({ } $li.addClass("user-action").insertBefore(this.divider); } - + // console.log('parent', parent) + if(shortcut) { + this.keyboard_shortcut(shortcut, $li.find('a')); + } return $link; }, diff --git a/frappe/public/js/frappe/views/formview.js b/frappe/public/js/frappe/views/formview.js index 033569a03c..46b1533233 100644 --- a/frappe/public/js/frappe/views/formview.js +++ b/frappe/public/js/frappe/views/formview.js @@ -82,5 +82,82 @@ frappe.views.FormFactory = class FormFactory extends frappe.views.Factory { load(dt, dn) { frappe.container.change_to("Form/" + dt); frappe.views.formview[dt].frm.refresh(dn); + // console.log('this', this); + this.setup_modal(); + } + + setup_modal() { + $(document).on('keydown',null, 'ctrl+shift+m', (e)=> { + // e.stopImmediatePropagation(); + if($(this.page).is(':hidden')) { + console.log('yesss'); + return; + } + console.log('this', this.page); + console.log('modal'); + let fields = this.get_section_fields(); + let field_labels = []; + fields.forEach((f)=> {field_labels.push(f.label)}); + console.log('labels', field_labels); + const d = new frappe.ui.Dialog({ + title: __('Go to Field'), + fields: [ + { + fieldtype: 'Select', + fieldname: 'go_to_field', + label: 'Select Field', + options: field_labels, + reqd: 1 + } + ] + }); + d.set_primary_action("Go", () => { + let field = d.get_values().go_to_field; + console.log('field', field); + let element = fields.find( f=> f.label == field).section_body; + console.log('element', element); + if(element.hasClass('hide')) { + console.log('true'); + element.removeClass('hide'); + } + frappe.utils.scroll_to(element); + // this.group_by = this.meta.fields.find( f => f.label == d.get_values().group_by).fieldname; + // this.aggregate_on = this.meta.fields.find( f => f.label == d.get_values().aggregate_on).fieldname; + + // let sql_aggregate_function = { + // Count: "count", + // Sum: "sum", + // Average: "avg", + // } + // this.aggregate_function = sql_aggregate_function[d.get_values().aggregate_function]; + d.hide(); + }); + d.show(); + }) + } + + get_section_fields() { + let fields = []; + // this.page.frm.fields.forEach((f) => { + // if(f.df.fieldtype!=='Section Break' && !f.df.hidden && f.disp_status!=='None' && f.df.label) { + // fields.push(f.df.label); + // } + // }); + // console.log('fields', fields); + // return fields; + + this.page.frm.layout.sections.forEach((section)=> { + section.fields_list.forEach((f)=> { + if(f.df.fieldtype!=='Section Break' && !f.df.hidden && f.disp_status!=='None' && f.df.label) { + let field = {}; + field.label = f.df.label; + field.section_body = section.body; + field.section = section.df.label; + fields.push(field); + } + }) + }) + console.log('fields', fields); + return fields; } } diff --git a/frappe/public/less/list.less b/frappe/public/less/list.less index 31984f08c9..a70f53f38b 100644 --- a/frappe/public/less/list.less +++ b/frappe/public/less/list.less @@ -90,6 +90,14 @@ } } +.selected { + background-color: @panel-bg; +} + +.btn-selected { + background: #cfdce5; +} + .list-row-head { background-color: @panel-bg; border-bottom: 1px solid @border-color !important;