Search refactor (#2983)
* [start] basic sidebar function * remove flicker on fetch * use promises for global and help results * add summary view * single modal, sidebar keyboard selection * Placeholder and no_results state, result render prep * Specific empty states, results template no longer special: thus remove reset(), forum results trial * Add loading state * more results working * commonify more results, added images * All nav results in modal, with recents * use common utils in awesome bar * UI fixes * Remove forum results * frappe/erpnext#8222 frappe/erpnext#8264
This commit is contained in:
parent
eb6bd89ce0
commit
f2df24803c
10 changed files with 1221 additions and 1380 deletions
|
|
@ -139,6 +139,7 @@
|
|||
"public/js/frappe/ui/toolbar/search.js",
|
||||
"public/js/frappe/ui/toolbar/search.html",
|
||||
"public/js/frappe/ui/toolbar/search_header.html",
|
||||
"public/js/frappe/ui/toolbar/search_utils.js",
|
||||
"public/js/frappe/ui/toolbar/about.js",
|
||||
"public/js/frappe/ui/toolbar/navbar.html",
|
||||
"public/js/frappe/ui/toolbar/toolbar.js",
|
||||
|
|
@ -213,7 +214,7 @@
|
|||
],
|
||||
"js/list.min.js": [
|
||||
"public/js/frappe/ui/listing.html",
|
||||
|
||||
|
||||
"public/js/frappe/ui/base_list.js",
|
||||
|
||||
"public/js/frappe/model/indicator.js",
|
||||
|
|
@ -238,7 +239,7 @@
|
|||
"public/js/frappe/list/list_item_row_head.html",
|
||||
"public/js/frappe/list/list_item_subject.html",
|
||||
"public/js/frappe/list/list_permission_footer.html",
|
||||
|
||||
|
||||
"public/js/frappe/list/list_renderer.js",
|
||||
"public/js/frappe/views/gantt/gantt_view.js",
|
||||
"public/js/frappe/views/calendar/calendar.js",
|
||||
|
|
@ -256,7 +257,7 @@
|
|||
"public/js/frappe/views/inbox/inbox_no_result.html",
|
||||
"public/js/frappe/views/inbox/inbox_view_item_row.html",
|
||||
"public/js/frappe/views/inbox/inbox_view_item_main_head.html",
|
||||
|
||||
|
||||
"public/js/frappe/views/kanban/kanban_board.html",
|
||||
"public/js/frappe/views/kanban/kanban_column.html",
|
||||
"public/js/frappe/views/kanban/kanban_card.html"
|
||||
|
|
|
|||
|
|
@ -677,13 +677,100 @@ fieldset[disabled] .form-control {
|
|||
}
|
||||
.search-dialog .modal-dialog {
|
||||
width: 768px;
|
||||
height: 500px;
|
||||
}
|
||||
.search-dialog .search-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
}
|
||||
.search-dialog .modal-body {
|
||||
padding: 0px 15px;
|
||||
}
|
||||
.search-dialog input.form-control,
|
||||
.search-dialog .input-group-addon {
|
||||
.search-dialog .empty-state {
|
||||
color: #d4d9dd;
|
||||
height: 500px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
.search-dialog .empty-state .status-icon {
|
||||
font-size: 40px;
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.search-dialog .empty-state p {
|
||||
font-size: 15px;
|
||||
display: block;
|
||||
}
|
||||
.search-dialog .empty-state .cover {
|
||||
color: white;
|
||||
font-size: 6px;
|
||||
position: absolute;
|
||||
}
|
||||
@keyframes twinkle {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@-o-keyframes twinkle {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@-moz-keyframes twinkle {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes twinkle {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.search-dialog .twinkle-one {
|
||||
-webkit-animation: twinkle 1.5s ease infinite;
|
||||
-moz-animation: twinkle 1.5s ease infinite;
|
||||
-o-animation: twinkle 1.5s ease infinite;
|
||||
animation: twinkle 1.5s ease infinite;
|
||||
}
|
||||
.search-dialog .twinkle-two {
|
||||
-webkit-animation: twinkle 1.5s ease infinite 0.5s;
|
||||
-moz-animation: twinkle 1.5s ease infinite 0.5s;
|
||||
-o-animation: twinkle 1.5s ease infinite 0.5s;
|
||||
animation: twinkle 1.5s ease infinite 0.5s;
|
||||
}
|
||||
.search-dialog .twinkle-three {
|
||||
-webkit-animation: twinkle 1.5s ease infinite 1s;
|
||||
-moz-animation: twinkle 1.5s ease infinite 1s;
|
||||
-o-animation: twinkle 1.5s ease infinite 1s;
|
||||
animation: twinkle 1.5s ease infinite 1s;
|
||||
}
|
||||
.search-dialog input.form-control {
|
||||
border: none;
|
||||
border-left-style: none;
|
||||
}
|
||||
|
|
@ -691,9 +778,6 @@ fieldset[disabled] .form-control {
|
|||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.search-dialog .input-group-addon {
|
||||
background-color: #FFF;
|
||||
}
|
||||
.search-dialog .layout-side-section,
|
||||
.search-dialog .layout-main-section {
|
||||
height: 500px;
|
||||
|
|
@ -712,6 +796,7 @@ fieldset[disabled] .form-control {
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-left: 20px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.search-dialog .layout-side-section .nav li a i {
|
||||
visibility: hidden;
|
||||
|
|
@ -719,23 +804,9 @@ fieldset[disabled] .form-control {
|
|||
.search-dialog .layout-side-section .nav .active i {
|
||||
visibility: visible;
|
||||
}
|
||||
.search-dialog .results-area .search-intro-placeholder {
|
||||
color: #d4d9dd;
|
||||
height: inherit;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.search-dialog .results-area .search-intro-placeholder span {
|
||||
text-align: center;
|
||||
}
|
||||
.search-dialog .results-area .search-intro-placeholder span i {
|
||||
font-size: 64px;
|
||||
display: block;
|
||||
}
|
||||
.search-dialog .results-area .search-intro-placeholder span p {
|
||||
font-size: 15px;
|
||||
display: block;
|
||||
.search-dialog .layout-side-section .nav .select a,
|
||||
.search-dialog .layout-side-section .nav a:hover {
|
||||
background-color: #f7fafc;
|
||||
}
|
||||
.search-dialog .results-area .single-link a {
|
||||
color: #36414c;
|
||||
|
|
@ -748,6 +819,9 @@ fieldset[disabled] .form-control {
|
|||
font-family: 'Octicons';
|
||||
content: '\f0a4';
|
||||
}
|
||||
.search-dialog .module-section .result {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.search-dialog .full-list .result {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
|
@ -772,6 +846,27 @@ fieldset[disabled] .form-control {
|
|||
.search-dialog .more-results {
|
||||
display: none;
|
||||
}
|
||||
.search-dialog .result p {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.search-dialog .result .result-image {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
.search-dialog .result .result-image .flex-text {
|
||||
display: flex;
|
||||
height: 60px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.search-dialog .result .result-image span {
|
||||
font-size: 30px;
|
||||
color: #d1d8dd;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.search-dialog .modal-dialog {
|
||||
width: auto;
|
||||
|
|
@ -785,21 +880,12 @@ fieldset[disabled] .form-control {
|
|||
margin: 0px;
|
||||
border-top: none;
|
||||
}
|
||||
.search-dialog .layout-side-section .sidebar-menu {
|
||||
margin: 30px 0px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 600px) {
|
||||
.search-dialog .results-area .back-link {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.result p {
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
.search-result {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.note-editor.note-frame .note-editing-area .note-editable {
|
||||
color: #36414C;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,6 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
var $input = $(element);
|
||||
var input = $input.get(0);
|
||||
|
||||
this.search = new frappe.search.UnifiedSearch();
|
||||
this.global = new frappe.search.GlobalSearch();
|
||||
this.nav = new frappe.search.NavSearch();
|
||||
this.help = new frappe.search.HelpSearch();
|
||||
|
||||
this.options = [];
|
||||
this.global_results = [];
|
||||
|
||||
|
|
@ -23,32 +18,22 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
autoFirst: true,
|
||||
list: [],
|
||||
filter: function (text, term) {
|
||||
this.get_item(text.value).boo = "foo";
|
||||
return true;
|
||||
},
|
||||
data: function (item, input) {
|
||||
var label = item.label + "%%%" + item.value + "%%%" +
|
||||
(item.description || "") + "%%%" + (item.index || "")
|
||||
+ "%%%" + (item.type || "") + "%%%" + (item.prefix || "");
|
||||
return {
|
||||
label: label,
|
||||
label: (item.index || ""),
|
||||
value: item.value
|
||||
};
|
||||
},
|
||||
item: function(item, term) {
|
||||
var d = item;
|
||||
var parts = item.split("%%%"),
|
||||
d = { label: parts[0], value: parts[1], description: parts[2],
|
||||
type: parts[4], prefix: parts[5]};
|
||||
|
||||
if(d.prefix) {
|
||||
var html = "<span>" + __((d.prefix + ' ' + d.label)) + "</span>";
|
||||
} else if(d.type) {
|
||||
var html = "<span>" + __((d.label + ' ' + d.type)) + "</span>";
|
||||
} else {
|
||||
var html = "<span>" + __(d.label || d.value) + "</span>";
|
||||
}
|
||||
var d = this.get_item(item.value);
|
||||
var name = d.prefix ? __(d.prefix + ' ' + (d.label || d.value)) :
|
||||
__(d.label || d.value);
|
||||
var html = '<span>' + name + '</span>';
|
||||
if(d.description && d.value!==d.description) {
|
||||
html += '<br><span class="text-muted">' + __(d.description) + '</span>';
|
||||
html += '<br><span class="text-muted ellipsis">' + __(d.description) + '</span>';
|
||||
}
|
||||
return $('<li></li>')
|
||||
.data('item.autocomplete', d)
|
||||
|
|
@ -56,9 +41,7 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
.get(0);
|
||||
},
|
||||
sort: function(a, b) {
|
||||
var a_index = a.split("%%%")[3];
|
||||
var b_index = b.split("%%%")[3];
|
||||
return (a_index - b_index);
|
||||
return (b.label - a.label);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -67,53 +50,29 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
var txt = value.trim().replace(/\s\s+/g, ' ');
|
||||
var last_space = txt.lastIndexOf(' ');
|
||||
me.global_results = [];
|
||||
|
||||
if(txt && txt.length > 2) {
|
||||
me.global.get_awesome_bar_options(txt.toLowerCase(), me);
|
||||
}
|
||||
// if(txt && txt.length > 1) {
|
||||
// me.global.get_awesome_bar_options(txt.toLowerCase(), me);
|
||||
// }
|
||||
|
||||
var $this = $(this);
|
||||
clearTimeout($this.data('timeout'));
|
||||
|
||||
$this.data('timeout', setTimeout(function(){
|
||||
me.options = [];
|
||||
if(txt && txt.length > 2) {
|
||||
if(txt && txt.length > 1) {
|
||||
if(last_space !== -1) {
|
||||
me.set_specifics(txt.slice(0,last_space), txt.slice(last_space+1));
|
||||
}
|
||||
me.add_defaults(txt);
|
||||
me.options = me.options.concat(me.build_options(txt));
|
||||
me.build_defaults(txt);
|
||||
me.options = me.options.concat(me.global_results);
|
||||
} else {
|
||||
me.options = me.options.concat(
|
||||
me.deduplicate(frappe.search.utils.get_recent_pages(txt || "")));
|
||||
}
|
||||
|
||||
|
||||
me.make_calculator(txt);
|
||||
me.add_recent(txt || "");
|
||||
me.add_help();
|
||||
|
||||
// de-duplicate
|
||||
var out = [], routes = [];
|
||||
me.options.forEach(function(option) {
|
||||
if(option.route) {
|
||||
if(option.route[0] === "List" && option.route[2]) {
|
||||
option.route.splice(2);
|
||||
}
|
||||
var str_route = (typeof option.route==='string') ?
|
||||
option.route : option.route.join('/');
|
||||
if(routes.indexOf(str_route)===-1) {
|
||||
out.push(option);
|
||||
routes.push(str_route);
|
||||
} else {
|
||||
var old = routes.indexOf(str_route);
|
||||
if(out[old].index > option.index) {
|
||||
out[old] = option;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.push(option);
|
||||
}
|
||||
});
|
||||
awesomplete.list = out;
|
||||
awesomplete.list = me.options;
|
||||
}, 100));
|
||||
|
||||
});
|
||||
|
|
@ -153,20 +112,20 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
frappe.route();
|
||||
}
|
||||
}
|
||||
$input.val("");
|
||||
});
|
||||
|
||||
$input.on("awesomplete-selectcomplete", function(e) {
|
||||
$input.val("");
|
||||
});
|
||||
this.setup_recent();
|
||||
this.search.setup();
|
||||
frappe.search.utils.setup_recent();
|
||||
},
|
||||
|
||||
add_help: function() {
|
||||
this.options.push({
|
||||
label: __("Help on Search"),
|
||||
value: "Help on Search",
|
||||
index: 100,
|
||||
index: -10,
|
||||
default: "Help",
|
||||
onclick: function() {
|
||||
var txt = '<table class="table table-bordered">\
|
||||
|
|
@ -186,142 +145,6 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
});
|
||||
},
|
||||
|
||||
add_recent: function(txt) {
|
||||
var me = this;
|
||||
values = [];
|
||||
$.each(me.recent, function(i, doctype) {
|
||||
values.push([doctype[1], ['Form', doctype[0], doctype[1]]]);
|
||||
});
|
||||
|
||||
values = values.reverse();
|
||||
|
||||
$.each(frappe.route_history, function(i, route) {
|
||||
if(route[0]==='Form') {
|
||||
values.push([route[2], route]);
|
||||
}
|
||||
else if(in_list(['List', 'Report', 'Tree', 'modules', 'query-report'], route[0])) {
|
||||
if(route[1]) {
|
||||
values.push([route[1], route]);
|
||||
}
|
||||
}
|
||||
else if(route[0]) {
|
||||
values.push([frappe.route_titles[route[0]] || route[0], route]);
|
||||
}
|
||||
});
|
||||
|
||||
this.find(values, txt, function(match) {
|
||||
out = {
|
||||
route: match[1]
|
||||
}
|
||||
if(match[1][0]==='Form') {
|
||||
if(match[1][1] !== match[1][2]) {
|
||||
out.label = __(match[1][1]) + " " + match[1][2].bold();
|
||||
out.value = __(match[1][1]) + " " + match[1][2];
|
||||
} else {
|
||||
out.label = __(match[1][1]).bold();
|
||||
out.value = __(match[1][1]);
|
||||
}
|
||||
} else if(in_list(['List', 'Report', 'Tree', 'modules', 'query-report'], match[1][0])) {
|
||||
var type = match[1][0], label = type;
|
||||
if(type==='modules') label = 'Module';
|
||||
else if(type==='query-report') label = 'Report';
|
||||
out.label = __(match[1][1]).bold() + " " + __(label);
|
||||
out.value = __(match[1][1]) + " " + __(label);
|
||||
} else {
|
||||
out.label = match[0].bold();
|
||||
out.value = match[0];
|
||||
}
|
||||
out.index = 80;
|
||||
out.default = "Recent";
|
||||
return out;
|
||||
}, true);
|
||||
},
|
||||
|
||||
find: function(list, txt, process, prepend) {
|
||||
var me = this;
|
||||
$.each(list, function(i, item) {
|
||||
if($.isArray(item)) {
|
||||
_item = item[0];
|
||||
} else {
|
||||
_item = item;
|
||||
}
|
||||
_item = __(_item || '').toLowerCase().replace(/-/g, " ");
|
||||
if(txt===_item || _item.indexOf(txt) !== -1) {
|
||||
var option = process(item);
|
||||
|
||||
if(option) {
|
||||
if($.isPlainObject(option)) {
|
||||
option = [option];
|
||||
}
|
||||
|
||||
option.forEach(function(o) { o.match = item; });
|
||||
|
||||
if(prepend) {
|
||||
me.options = option.concat(me.options);
|
||||
} else {
|
||||
me.options = me.options.concat(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setup_recent: function() {
|
||||
this.recent = JSON.parse(frappe.boot.user.recent || "[]") || [];
|
||||
},
|
||||
|
||||
fuzzy_search: function(_txt, _item) {
|
||||
parsed_item = __(_item || '').replace(/-|_/g, " ");
|
||||
item = parsed_item.toLowerCase();
|
||||
txt = _txt.toLowerCase();
|
||||
|
||||
var ilen = item.length;
|
||||
var tlen = txt.length;
|
||||
var match_level = tlen/ilen;
|
||||
var rendered_label = "";
|
||||
var i, j, skips = 0, mismatches = 0;
|
||||
|
||||
if(tlen > ilen) {
|
||||
return [];
|
||||
}
|
||||
if(parsed_item.indexOf(_txt) !== -1 && txt !== _txt) {
|
||||
var regEx = new RegExp("("+ txt +")", "ig");
|
||||
rendered_label = parsed_item.replace(regEx, '<b>$1</b>');
|
||||
return [parsed_item, (ilen + 10), rendered_label];
|
||||
}
|
||||
if(item.indexOf(txt) !== -1) {
|
||||
var regEx = new RegExp("("+ txt +")", "ig");
|
||||
rendered_label = parsed_item.replace(regEx, '<b>$1</b>');
|
||||
return [parsed_item, 20 + (ilen + 10), rendered_label];
|
||||
}
|
||||
outer: for (i = 0, j = 0; i < tlen; i++) {
|
||||
var t_ch = txt.charCodeAt(i);
|
||||
if(mismatches !== 0) skips++;
|
||||
if(skips > 3) return [];
|
||||
mismatches = 0;
|
||||
while (j < ilen) {
|
||||
var i_ch = item.charCodeAt(j);
|
||||
if (i_ch === t_ch) {
|
||||
var item_char = parsed_item.charAt(j);
|
||||
if(item_char === item_char.toLowerCase()){
|
||||
rendered_label += '<b>' + txt.charAt(i) + '</b>';
|
||||
} else {
|
||||
rendered_label += '<b>' + txt.charAt(i).toUpperCase() + '</b>';
|
||||
}
|
||||
j++;
|
||||
continue outer;
|
||||
}
|
||||
mismatches++;
|
||||
if(mismatches > 2) return [];
|
||||
rendered_label += parsed_item.charAt(j);
|
||||
j++;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
rendered_label += parsed_item.slice(j);
|
||||
return [parsed_item, 40 + (ilen + 10), rendered_label];
|
||||
},
|
||||
|
||||
set_specifics: function(txt, end_txt) {
|
||||
var me = this;
|
||||
var results = this.build_options(txt);
|
||||
|
|
@ -332,19 +155,50 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
});
|
||||
},
|
||||
|
||||
build_defaults: function(txt) {
|
||||
add_defaults: function(txt) {
|
||||
this.make_global_search(txt);
|
||||
this.make_search_in_current(txt);
|
||||
this.options = this.options.concat(this.make_search_in_list(txt));
|
||||
this.make_calculator(txt);
|
||||
},
|
||||
|
||||
build_options: function(txt) {
|
||||
return this.make_new_doc(txt).concat(
|
||||
this.get_doctypes(txt),
|
||||
this.get_reports(txt),
|
||||
this.get_pages(txt),
|
||||
this.get_modules(txt)
|
||||
var options = frappe.search.utils.get_creatables(txt).concat(
|
||||
frappe.search.utils.get_search_in_list(txt),
|
||||
frappe.search.utils.get_doctypes(txt),
|
||||
frappe.search.utils.get_reports(txt),
|
||||
frappe.search.utils.get_pages(txt),
|
||||
frappe.search.utils.get_modules(txt),
|
||||
frappe.search.utils.get_recent_pages(txt || "")
|
||||
);
|
||||
var out = this.deduplicate(options);
|
||||
return out.sort(function(a, b) {
|
||||
return b.index - a.index;
|
||||
});
|
||||
},
|
||||
|
||||
deduplicate: function(options) {
|
||||
var out = [], routes = [];
|
||||
options.forEach(function(option) {
|
||||
if(option.route) {
|
||||
if(option.route[0] === "List" && option.route[2]) {
|
||||
option.route.splice(2);
|
||||
}
|
||||
var str_route = (typeof option.route==='string') ?
|
||||
option.route : option.route.join('/');
|
||||
if(routes.indexOf(str_route)===-1) {
|
||||
out.push(option);
|
||||
routes.push(str_route);
|
||||
} else {
|
||||
var old = routes.indexOf(str_route);
|
||||
if(out[old].index > option.index) {
|
||||
out[old] = option;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.push(option);
|
||||
}
|
||||
});
|
||||
return out;
|
||||
},
|
||||
|
||||
set_global_results: function(global_results, txt){
|
||||
|
|
@ -357,11 +211,10 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
label: __("Search for '" + txt.bold() + "'"),
|
||||
value: __("Search for '" + txt + "'"),
|
||||
match: txt,
|
||||
index: 1,
|
||||
index: 100,
|
||||
default: "Search",
|
||||
onclick: function() {
|
||||
me.search.search_dialog.show();
|
||||
me.search.setup_search(txt, [me.nav, me.global, me.help]);
|
||||
frappe.searchdialog.search.init_search(txt, "global_search");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
@ -378,10 +231,10 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
label: __('Find {0} in {1}', [txt.bold(), route[1].bold()]),
|
||||
value: __('Find {0} in {1}', [txt, route[1]]),
|
||||
route_options: options,
|
||||
index: 2,
|
||||
onclick: function() {
|
||||
cur_list.refresh();
|
||||
},
|
||||
index: 90,
|
||||
default: "Current",
|
||||
match: txt
|
||||
});
|
||||
|
|
@ -401,7 +254,7 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
label: formatted_value,
|
||||
value: __('{0} = {1}', [txt, val]),
|
||||
match: val,
|
||||
index: 3,
|
||||
index: 80,
|
||||
default: "Calculator",
|
||||
onclick: function() {
|
||||
msgprint(formatted_value, "Result");
|
||||
|
|
@ -412,208 +265,4 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
make_search_in_list: function(txt) {
|
||||
var me = this;
|
||||
var out = [];
|
||||
if(in_list(txt.split(" "), "in") && (txt.slice(-2) !== "in")) {
|
||||
parts = txt.split(" in ");
|
||||
frappe.boot.user.can_read.forEach(function (item) {
|
||||
var target = me.fuzzy_search(parts[1], item)[0];
|
||||
if(target) {
|
||||
out.push({
|
||||
label: __('Find {0} in {1}', [__(parts[0]).bold(), __(target).bold()]),
|
||||
value: __('Find {0} in {1}', [__(parts[0]), __(target)]),
|
||||
route_options: {"name": ["like", "%" + parts[0] + "%"]},
|
||||
index: 4,
|
||||
default: "In List",
|
||||
route: ["List", item]
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
make_new_doc: function(txt) {
|
||||
var me = this;
|
||||
var out = [];
|
||||
if(txt.split(" ")[0]==="new") {
|
||||
frappe.boot.user.can_create.forEach(function (item) {
|
||||
var result = me.fuzzy_search(txt.substr(4), item);
|
||||
var target = result[0];
|
||||
var index = result[1];
|
||||
var rendered_label = result[2];
|
||||
if(target) {
|
||||
out.push({
|
||||
label: rendered_label,
|
||||
value: __("New {0}", [target]),
|
||||
index: index,
|
||||
type: "New",
|
||||
prefix: "New",
|
||||
match: item,
|
||||
onclick: function() { frappe.new_doc(item, true); }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
get_doctypes: function(txt) {
|
||||
var me = this;
|
||||
var out = [];
|
||||
|
||||
var result, target, index, rendered_label;
|
||||
var option = function(type, route, order) {
|
||||
return {
|
||||
label: rendered_label,
|
||||
value: __(target + " " + type),
|
||||
route: route,
|
||||
index: index + order,
|
||||
match: target,
|
||||
type: type
|
||||
}
|
||||
};
|
||||
frappe.boot.user.can_read.forEach(function (item) {
|
||||
result = me.fuzzy_search(txt, item);
|
||||
target = result[0];
|
||||
index = result[1];
|
||||
rendered_label = result[2];
|
||||
if(target) {
|
||||
// include 'making new' option
|
||||
if(in_list(frappe.boot.user.can_create, item)) {
|
||||
var match = item;
|
||||
out.push({
|
||||
label: rendered_label,
|
||||
value: __("New {0}", [target]),
|
||||
index: index + 0.4,
|
||||
type: "New",
|
||||
prefix: "New",
|
||||
match: item,
|
||||
onclick: function() { frappe.new_doc(match, true); }
|
||||
});
|
||||
}
|
||||
if(in_list(frappe.boot.single_types, target)) {
|
||||
out.push(option("", ["Form", target, target], 0));
|
||||
|
||||
} else if(in_list(frappe.boot.treeviews, target)) {
|
||||
out.push(option("Tree", ["Tree", target], 0));
|
||||
|
||||
} else {
|
||||
out.push(option("List", ["List", target], 0));
|
||||
if(frappe.model.can_get_report(target)) {
|
||||
out.push(option("Report", ["Report", target], 0.1));
|
||||
}
|
||||
if(frappe.boot.calendars.indexOf(target) !== -1) {
|
||||
out.push(option("Calendar", ["List", target, "Calendar"], 0.2));
|
||||
out.push(option("Gantt", ["List", target, "Gantt"], 0.3));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return out;
|
||||
},
|
||||
|
||||
get_reports: function(txt) {
|
||||
var me = this;
|
||||
var out = [];
|
||||
Object.keys(frappe.boot.user.all_reports).forEach(function(item) {
|
||||
var result = me.fuzzy_search(txt, item);
|
||||
var target = result[0];
|
||||
var index = result[1];
|
||||
var rendered_label = result[2];
|
||||
if(target) {
|
||||
var report = frappe.boot.user.all_reports[item];
|
||||
var route = [];
|
||||
if(report.report_type == "Report Builder")
|
||||
route = ["Report", report.ref_doctype, item];
|
||||
else
|
||||
route = ["query-report", item];
|
||||
|
||||
out.push({
|
||||
label: rendered_label,
|
||||
value: __("Report {0}" , [__(target)]),
|
||||
match: txt,
|
||||
index: index,
|
||||
type: "Report",
|
||||
prefix: "Report",
|
||||
route: route
|
||||
});
|
||||
}
|
||||
});
|
||||
return out;
|
||||
},
|
||||
|
||||
get_pages: function(txt) {
|
||||
var me = this;
|
||||
var out = [];
|
||||
this.pages = {};
|
||||
$.each(frappe.boot.page_info, function(name, p) {
|
||||
me.pages[p.title] = p;
|
||||
p.name = name;
|
||||
});
|
||||
Object.keys(this.pages).forEach(function(item) {
|
||||
var result = me.fuzzy_search(txt, item);
|
||||
var target = result[0];
|
||||
var index = result[1];
|
||||
var rendered_label = result[2];
|
||||
if(target) {
|
||||
var page = me.pages[item];
|
||||
out.push({
|
||||
label: rendered_label,
|
||||
value: __("Open {0}", [__(target)]),
|
||||
match: txt,
|
||||
index: index,
|
||||
type: "Page",
|
||||
prefix: "Open",
|
||||
route: [page.route || page.name]
|
||||
});
|
||||
}
|
||||
});
|
||||
// calendar
|
||||
var target = 'Calendar';
|
||||
if(__('calendar').indexOf(txt.toLowerCase()) === 0) {
|
||||
out.push({
|
||||
label: __(target),
|
||||
value: __("Open {0}", [__(target)]),
|
||||
route: [target, 'Event'],
|
||||
index: 5,
|
||||
type: "Calendar",
|
||||
prefix: "Open",
|
||||
match: target
|
||||
});
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
get_modules: function(txt) {
|
||||
var me = this;
|
||||
var out = [];
|
||||
Object.keys(frappe.modules).forEach(function(item) {
|
||||
var result = me.fuzzy_search(txt, item);
|
||||
var target = result[0];
|
||||
var index = result[1];
|
||||
var rendered_label = result[2];
|
||||
if(target) {
|
||||
var module = frappe.modules[item];
|
||||
if(module._doctype) return;
|
||||
ret = {
|
||||
label: rendered_label,
|
||||
value: __("Open {0}", [__(target)]),
|
||||
match: txt,
|
||||
index: index,
|
||||
type: "Module",
|
||||
prefix: "Open"
|
||||
}
|
||||
if(module.link) {
|
||||
ret.route = [module.link];
|
||||
} else {
|
||||
ret.route = ["Module", item];
|
||||
}
|
||||
out.push(ret);
|
||||
}
|
||||
});
|
||||
return out;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
<div class="row">
|
||||
<div class="col-md-2 col-sm-2 hidden-xs layout-side-section search-sidebar">
|
||||
|
||||
</div>
|
||||
<div class="col-md-10 col-sm-10 layout-main-section results-area">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="row search-results">
|
||||
<div class="col-md-2 col-sm-2 hidden-xs layout-side-section">
|
||||
<ul class="module-sidebar-nav overlay-sidebar nav nav-pills nav-stacked search-sidebar"></ul>
|
||||
</div>
|
||||
<div class="col-md-10 col-sm-10 layout-main-section results-area"></div>
|
||||
</div>
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,6 @@
|
|||
<div class="input-group has-feedback search-header" style="margin:5px -5px;">
|
||||
<span class="input-group-addon">
|
||||
<i class="octicon octicon-search"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control search-input" placeholder="Search for ...">
|
||||
<span class="input-group-addon"><a type="button" class="close" data-dismiss="modal" aria-hidden="true">×</a></span>
|
||||
<div class="search-header">
|
||||
<i class="octicon octicon-search"></i>
|
||||
<input type="text" class="form-control search-input" style="padding: 15px">
|
||||
<p class="loading-state hide" style="margin: 0px 20px; color:#d4d9dd">{%= __("Searching")%} ...</p>
|
||||
<a type="button" class="close" data-dismiss="modal" aria-hidden="true">×</a>
|
||||
</div>
|
||||
553
frappe/public/js/frappe/ui/toolbar/search_utils.js
Normal file
553
frappe/public/js/frappe/ui/toolbar/search_utils.js
Normal file
|
|
@ -0,0 +1,553 @@
|
|||
frappe.provide('frappe.search');
|
||||
|
||||
frappe.search.utils = {
|
||||
setup_recent: function() {
|
||||
this.recent = JSON.parse(frappe.boot.user.recent || "[]") || [];
|
||||
},
|
||||
|
||||
get_recent_pages: function(keywords) {
|
||||
var me = this;
|
||||
values = [], options = [];
|
||||
function find(list, keywords, process) {
|
||||
list.forEach(function(item, i) {
|
||||
_item = ($.isArray(item)) ? item[0] : item;
|
||||
_item = __(_item || '').toLowerCase().replace(/-/g, " ");
|
||||
|
||||
if(keywords===_item || _item.indexOf(keywords) !== -1) {
|
||||
var option = process(item);
|
||||
|
||||
if(option) {
|
||||
if($.isPlainObject(option)) {
|
||||
option = [option];
|
||||
}
|
||||
option.forEach(function(o) { o.match = item; });
|
||||
options = option.concat(options);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
me.recent.forEach(function(doctype, i) {
|
||||
values.push([doctype[1], ['Form', doctype[0], doctype[1]]]);
|
||||
});
|
||||
|
||||
values = values.reverse();
|
||||
|
||||
frappe.route_history.forEach(function(route, i) {
|
||||
if(route[0]==='Form') {
|
||||
values.push([route[2], route]);
|
||||
}
|
||||
else if(in_list(['List', 'Report', 'Tree', 'modules', 'query-report'], route[0])) {
|
||||
if(route[1]) {
|
||||
values.push([route[1], route]);
|
||||
}
|
||||
}
|
||||
else if(route[0]) {
|
||||
values.push([frappe.route_titles[route[0]] || route[0], route]);
|
||||
}
|
||||
});
|
||||
|
||||
find(values, keywords, function(match) {
|
||||
out = {
|
||||
route: match[1]
|
||||
}
|
||||
if(match[1][0]==='Form') {
|
||||
if(match[1][1] !== match[1][2]) {
|
||||
out.label = __(match[1][1]) + " " + match[1][2].bold();
|
||||
out.value = __(match[1][1]) + " " + match[1][2];
|
||||
} else {
|
||||
out.label = __(match[1][1]).bold();
|
||||
out.value = __(match[1][1]);
|
||||
}
|
||||
} else if(in_list(['List', 'Report', 'Tree', 'modules', 'query-report'], match[1][0])) {
|
||||
var type = match[1][0], label = type;
|
||||
if(type==='modules') label = 'Module';
|
||||
else if(type==='query-report') label = 'Report';
|
||||
out.label = __(match[1][1]).bold() + " " + __(label);
|
||||
out.value = __(match[1][1]) + " " + __(label);
|
||||
} else {
|
||||
out.label = match[0].bold();
|
||||
out.value = match[0];
|
||||
}
|
||||
out.index = 80;
|
||||
return out;
|
||||
});
|
||||
|
||||
return options;
|
||||
},
|
||||
|
||||
get_search_in_list: function(keywords) {
|
||||
var me = this;
|
||||
var out = [];
|
||||
if(in_list(keywords.split(" "), "in") && (keywords.slice(-2) !== "in")) {
|
||||
parts = keywords.split(" in ");
|
||||
frappe.boot.user.can_read.forEach(function (item) {
|
||||
if(frappe.boot.user.can_search.includes(item)) {
|
||||
var level = me.fuzzy_search(parts[1], item);
|
||||
if(level) {
|
||||
out.push({
|
||||
type: "In List",
|
||||
prefix: "Find '" + __(parts[0]).bold() + "' in ",
|
||||
label: __(me.bolden_match_part(item, parts[1])),
|
||||
value: __('Find {0} in {1}', [__(parts[0]), __(item)]),
|
||||
route_options: {"name": ["like", "%" + parts[0] + "%"]},
|
||||
index: 1 + level,
|
||||
route: ["List", item]
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
get_creatables: function(keywords) {
|
||||
var me = this;
|
||||
var out = [];
|
||||
if(keywords.split(" ")[0]==="new") {
|
||||
frappe.boot.user.can_create.forEach(function (item) {
|
||||
var level = me.fuzzy_search(keywords.substr(4), item);
|
||||
if(level) {
|
||||
out.push({
|
||||
type: "New",
|
||||
prefix: "New ",
|
||||
label: __(me.bolden_match_part(item, keywords.substr(4))),
|
||||
value: __("New {0}", [item]),
|
||||
index: 1 + level,
|
||||
match: item,
|
||||
onclick: function() { frappe.new_doc(item, true); }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
get_doctypes: function(keywords) {
|
||||
var me = this;
|
||||
var out = [];
|
||||
|
||||
var level, target;
|
||||
var option = function(type, route, order) {
|
||||
return {
|
||||
type: type,
|
||||
label: __("{0}" + " " + type, [__(me.bolden_match_part(target, keywords))]),
|
||||
value: __(target + " " + type),
|
||||
index: level + order,
|
||||
match: target,
|
||||
route: route,
|
||||
}
|
||||
};
|
||||
frappe.boot.user.can_read.forEach(function(item) {
|
||||
if(frappe.boot.user.can_search.includes(item)) {
|
||||
level = me.fuzzy_search(keywords, item);
|
||||
if(level) {
|
||||
target = item;
|
||||
// include 'making new' option
|
||||
if(in_list(frappe.boot.user.can_create, item)) {
|
||||
var match = item;
|
||||
out.push({
|
||||
type: "New",
|
||||
label: __("New {0}", [__(me.bolden_match_part(item, keywords))]),
|
||||
value: __("New {0}", [__(item)]),
|
||||
index: level + 0.01,
|
||||
match: item,
|
||||
onclick: function() { frappe.new_doc(match, true); }
|
||||
});
|
||||
}
|
||||
if(in_list(frappe.boot.single_types, item)) {
|
||||
out.push(option("", ["Form", item, item], 0.05));
|
||||
|
||||
} else if(in_list(frappe.boot.treeviews, item)) {
|
||||
out.push(option("Tree", ["Tree", item], 0.05));
|
||||
|
||||
} else {
|
||||
out.push(option("List", ["List", item], 0.05));
|
||||
if(frappe.model.can_get_report(item)) {
|
||||
out.push(option("Report", ["Report", item], 0.04));
|
||||
}
|
||||
if(frappe.boot.calendars.indexOf(item) !== -1) {
|
||||
out.push(option("Calendar", ["List", item, "Calendar"], 0.03));
|
||||
out.push(option("Gantt", ["List", item, "Gantt"], 0.02));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return out;
|
||||
},
|
||||
|
||||
get_reports: function(keywords) {
|
||||
var me = this;
|
||||
var out = [];
|
||||
Object.keys(frappe.boot.user.all_reports).forEach(function(item) {
|
||||
var level = me.fuzzy_search(keywords, item);
|
||||
if(level > 0) {
|
||||
var report = frappe.boot.user.all_reports[item];
|
||||
if(report.report_type == "Report Builder")
|
||||
route = ["Report", report.ref_doctype, item];
|
||||
else
|
||||
route = ["query-report", item];
|
||||
out.push({
|
||||
type: "Report",
|
||||
prefix: "Report ",
|
||||
label: __(me.bolden_match_part(item, keywords)),
|
||||
value: __("Report {0}" , [item]),
|
||||
index: level,
|
||||
route: route
|
||||
});
|
||||
}
|
||||
});
|
||||
return out;
|
||||
},
|
||||
|
||||
get_pages: function(keywords) {
|
||||
var me = this;
|
||||
var out = [];
|
||||
this.pages = {};
|
||||
$.each(frappe.boot.page_info, function(name, p) {
|
||||
me.pages[p.title] = p;
|
||||
p.name = name;
|
||||
});
|
||||
Object.keys(this.pages).forEach(function(item) {
|
||||
var level = me.fuzzy_search(keywords, item);
|
||||
if(level) {
|
||||
var page = me.pages[item];
|
||||
out.push({
|
||||
type: "Page",
|
||||
prefix: "Open ",
|
||||
label: __(me.bolden_match_part(item, keywords)),
|
||||
value: __("Open {0}", [__(item)]),
|
||||
match: item,
|
||||
index: level,
|
||||
route: [page.route || page.name]
|
||||
});
|
||||
}
|
||||
});
|
||||
var target = 'Calendar';
|
||||
if(__('calendar').indexOf(keywords.toLowerCase()) === 0) {
|
||||
out.push({
|
||||
type: "Calendar",
|
||||
prefix: "Open ",
|
||||
label: __('Calendar'),
|
||||
value: __("Open {0}", [__(target)]),
|
||||
index: me.fuzzy_search(keywords, 'Calendar'),
|
||||
match: target,
|
||||
route: [target, 'Event'],
|
||||
});
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
get_modules: function(keywords) {
|
||||
var me = this;
|
||||
var out = [];
|
||||
Object.keys(frappe.modules).forEach(function(item) {
|
||||
var level = me.fuzzy_search(keywords, item);
|
||||
if(level > 0) {
|
||||
var module = frappe.modules[item];
|
||||
if(module._doctype) return;
|
||||
ret = {
|
||||
type: "Module",
|
||||
prefix: "Open ",
|
||||
label: __(me.bolden_match_part(item, keywords)),
|
||||
value: __("Open {0}", [__(item)]),
|
||||
index: level,
|
||||
}
|
||||
if(module.link) {
|
||||
ret.route = [module.link];
|
||||
} else {
|
||||
ret.route = ["Module", item];
|
||||
}
|
||||
out.push(ret);
|
||||
}
|
||||
});
|
||||
return out;
|
||||
},
|
||||
|
||||
get_global_results: function (keywords, start, limit, doctype = "") {
|
||||
var me = this;
|
||||
function get_results_sets(data) {
|
||||
var results_sets = [], result, set;
|
||||
function get_existing_set (doctype) {
|
||||
return results_sets.find(function(set) {
|
||||
return set.title === doctype;
|
||||
});
|
||||
}
|
||||
|
||||
function make_description(content, doc_name) {
|
||||
parts = content.split("|||");
|
||||
content_length = 300;
|
||||
fields = [];
|
||||
current_length = 0;
|
||||
var field_text = "";
|
||||
for(var i = 0; i < parts.length; i++) {
|
||||
part = parts[i];
|
||||
if(part.toLowerCase().indexOf(keywords) !== -1) {
|
||||
if(part.indexOf('&&&') !== -1) {
|
||||
var colon_index = part.indexOf('&&&');
|
||||
var field_value = part.slice(colon_index + 3);
|
||||
} else {
|
||||
var colon_index = part.indexOf(':');
|
||||
var field_value = part.slice(colon_index + 1);
|
||||
}
|
||||
var field_name = part.slice(0, colon_index);
|
||||
|
||||
var remaining_length = content_length - current_length;
|
||||
current_length += field_name.length + field_value.length + 2;
|
||||
if(current_length < content_length) {
|
||||
field_text = '<span class="field-name text-muted">' +
|
||||
me.bolden_match_part(field_name, keywords) + ':' + '</span>' +
|
||||
me.bolden_match_part(field_value, keywords);
|
||||
if(fields.indexOf(field_text) === -1 && doc_name !== field_value) {
|
||||
fields.push(field_text);
|
||||
}
|
||||
} else {
|
||||
if(field_name.length < remaining_length){
|
||||
remaining_length -= field_name.length;
|
||||
field_text = '<span class="field-name text-muted">' +
|
||||
me.bolden_match_part(field_name, keywords) + ':' + '</span>';
|
||||
field_value = field_value.slice(0, remaining_length);
|
||||
field_value = field_value.slice(0, field_value.lastIndexOf(' ')) + ' ...';
|
||||
field_text += me.bolden_match_part(field_value, keywords);
|
||||
fields.push(field_text);
|
||||
} else {
|
||||
fields.push('...');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return fields.join(', ');
|
||||
}
|
||||
|
||||
data.forEach(function(d) {
|
||||
// more properties
|
||||
result = {
|
||||
label: d.name,
|
||||
value: d.name,
|
||||
description: make_description(d.content, d.name),
|
||||
route: ['Form', d.doctype, d.name],
|
||||
}
|
||||
if(d.image || d.image === null){
|
||||
result.image = d.image;
|
||||
}
|
||||
set = get_existing_set(d.doctype);
|
||||
if(set) {
|
||||
set.results.push(result);
|
||||
} else {
|
||||
set = {
|
||||
title: d.doctype,
|
||||
results: [result],
|
||||
fetch_type: "Global"
|
||||
}
|
||||
results_sets.push(set);
|
||||
}
|
||||
|
||||
});
|
||||
return results_sets;
|
||||
}
|
||||
return new Promise(function(resolve, reject) {
|
||||
frappe.call({
|
||||
method: "frappe.utils.global_search.search",
|
||||
args: {
|
||||
text: keywords,
|
||||
start: start,
|
||||
limit: limit,
|
||||
doctype: doctype
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
resolve(get_results_sets(r.message));
|
||||
} else {
|
||||
resolve([]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
get_help_results: function(keywords) {
|
||||
function get_results_set(data) {
|
||||
var result;
|
||||
var set = {
|
||||
title: "Help",
|
||||
fetch_type: "Help",
|
||||
results: []
|
||||
}
|
||||
data.forEach(function(d) {
|
||||
// more properties
|
||||
result = {
|
||||
label: d[0],
|
||||
value: d[0],
|
||||
description: d[1],
|
||||
data_path: d[2],
|
||||
onclick: function() {
|
||||
|
||||
}
|
||||
}
|
||||
set.results.push(result);
|
||||
});
|
||||
return [set];
|
||||
}
|
||||
return new Promise(function(resolve, reject) {
|
||||
frappe.call({
|
||||
method: "frappe.utils.help.get_help",
|
||||
args: {
|
||||
text: keywords
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
resolve(get_results_set(r.message));
|
||||
} else {
|
||||
resolve([]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
get_nav_results: function(keywords) {
|
||||
function sort_uniques(array) {
|
||||
var routes = [], out = [];
|
||||
array.forEach(function(d) {
|
||||
if(d.route) {
|
||||
if(d.route[0] === "List" && d.route[2]) {
|
||||
d.route.splice(2);
|
||||
}
|
||||
var str_route = d.route.join('/');
|
||||
if(routes.indexOf(str_route) === -1) {
|
||||
routes.push(str_route);
|
||||
out.push(d);
|
||||
} else {
|
||||
var old = routes.indexOf(str_route);
|
||||
if(out[old].index > d.index) {
|
||||
out[old] = d;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.push(d);
|
||||
}
|
||||
});
|
||||
return out.sort(function(a, b) {
|
||||
return b.index - a.index;
|
||||
});
|
||||
}
|
||||
var lists = [], setup = [];
|
||||
var all_doctypes = sort_uniques(this.get_doctypes(keywords));
|
||||
all_doctypes.forEach(function(d) {
|
||||
if(d.type === "") {
|
||||
setup.push(d);
|
||||
} else {
|
||||
lists.push(d);
|
||||
}
|
||||
});
|
||||
var in_keyword = keywords.split(" in ")[0];
|
||||
return [
|
||||
{title: "Recents", fetch_type: "Nav", results: sort_uniques(this.get_recent_pages(keywords))},
|
||||
{title: "Create a new ...", fetch_type: "Nav", results: sort_uniques(this.get_creatables(keywords))},
|
||||
{title: "Find '" + in_keyword + "' in ... ", fetch_type: "Nav", results: sort_uniques(this.get_search_in_list(keywords))},
|
||||
{title: "Lists", fetch_type: "Nav", results: lists},
|
||||
{title: "Reports", fetch_type: "Nav", results: sort_uniques(this.get_reports(keywords))},
|
||||
{title: "Administration", fetch_type: "Nav", results: sort_uniques(this.get_pages(keywords))},
|
||||
{title: "Modules", fetch_type: "Nav", results: sort_uniques(this.get_modules(keywords))},
|
||||
{title: "Setup", fetch_type: "Nav", results: setup},
|
||||
]
|
||||
},
|
||||
|
||||
fuzzy_search: function(keywords, _item) {
|
||||
// Returns 10 for case-perfect contain, 0 for not found
|
||||
// 9 for perfect contain,
|
||||
// 0 - 6 for fuzzy contain
|
||||
|
||||
// **Specific use-case step**
|
||||
item = __(_item || '').replace(/-/g, " ");
|
||||
|
||||
var ilen = item.length;
|
||||
var klen = keywords.length;
|
||||
var length_ratio = klen/ilen;
|
||||
var max_skips = 3, max_mismatch_len = 2;
|
||||
|
||||
if (klen > ilen) { return 0; }
|
||||
|
||||
if(keywords === item || item.toLowerCase().indexOf(keywords) === 0) {
|
||||
return 10 + length_ratio;
|
||||
}
|
||||
|
||||
if (item.indexOf(keywords) !== -1 && keywords !== keywords.toLowerCase()) {
|
||||
return 9 + length_ratio;
|
||||
}
|
||||
|
||||
item = item.toLowerCase();
|
||||
keywords = keywords.toLowerCase();
|
||||
|
||||
if (item.indexOf(keywords) !== -1) {
|
||||
return 8 + length_ratio;
|
||||
}
|
||||
|
||||
var skips = 0, mismatches = 0;
|
||||
outer: for (var i = 0, j = 0; i < klen; i++) {
|
||||
if(mismatches !== 0) skips++;
|
||||
if(skips > max_skips) return 0;
|
||||
var k_ch = keywords.charCodeAt(i);
|
||||
mismatches = 0;
|
||||
while (j < ilen) {
|
||||
if (item.charCodeAt(j++) === k_ch) {
|
||||
continue outer;
|
||||
}
|
||||
if(++mismatches > max_mismatch_len) return 0 ;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Since indexOf didn't pass, there will be atleast 1 skip
|
||||
// hence no divide by zero, but just to be safe
|
||||
if((skips + mismatches) > 0) {
|
||||
return (5 + length_ratio)/(skips + mismatches);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
|
||||
bolden_match_part: function(str, subseq) {
|
||||
var rendered = "";
|
||||
if(this.fuzzy_search(subseq, str) === 0) {
|
||||
return str;
|
||||
} else if(this.fuzzy_search(subseq, str) > 6) {
|
||||
var regEx = new RegExp("("+ subseq +")", "ig");
|
||||
return str.replace(regEx, '<b>$1</b>');
|
||||
} else {
|
||||
var str_orig = str;
|
||||
var str = str.toLowerCase();
|
||||
var str_len = str.length;
|
||||
var subseq = subseq.toLowerCase();
|
||||
|
||||
outer: for(var i = 0, j = 0; i < subseq.length; i++) {
|
||||
var sub_ch = subseq.charCodeAt(i);
|
||||
while(j < str_len) {
|
||||
if(str.charCodeAt(j) === sub_ch) {
|
||||
var str_char = str_orig.charAt(j);
|
||||
if(str_char === str_char.toLowerCase()) {
|
||||
rendered += '<b>' + subseq.charAt(i) + '</b>';
|
||||
} else {
|
||||
rendered += '<b>' + subseq.charAt(i).toUpperCase() + '</b>';
|
||||
}
|
||||
j++;
|
||||
continue outer;
|
||||
}
|
||||
rendered += str_orig.charAt(j);
|
||||
j++;
|
||||
}
|
||||
return str_orig;
|
||||
}
|
||||
rendered += str_orig.slice(j);
|
||||
return rendered;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
unscrub_and_titlecase: function(str) {
|
||||
return __(str || '').replace(/-|_/g, " ").replace(/\w*/g,
|
||||
function(keywords){return keywords.charAt(0).toUpperCase() + keywords.substr(1).toLowerCase();});
|
||||
},
|
||||
}
|
||||
|
|
@ -12,12 +12,11 @@ frappe.ui.toolbar.Toolbar = Class.extend({
|
|||
|
||||
this.setup_sidebar();
|
||||
|
||||
this.awesome_bar = new frappe.search.AwesomeBar();
|
||||
this.awesome_bar.setup("#navbar-search");
|
||||
this.awesome_bar.setup("#modal-search");
|
||||
var awesome_bar = new frappe.search.AwesomeBar();
|
||||
awesome_bar.setup("#navbar-search");
|
||||
awesome_bar.setup("#modal-search");
|
||||
|
||||
this.search = this.awesome_bar.search;
|
||||
this.help = this.awesome_bar.help;
|
||||
this.setup_help();
|
||||
|
||||
$(document).on("notification-update", function() {
|
||||
frappe.ui.notifications.update_notifications();
|
||||
|
|
@ -25,8 +24,6 @@ frappe.ui.toolbar.Toolbar = Class.extend({
|
|||
|
||||
$('.dropdown-toggle').dropdown();
|
||||
|
||||
this.setup_help();
|
||||
|
||||
$(document).trigger('toolbar_setup');
|
||||
|
||||
// clear all custom menus on page change
|
||||
|
|
@ -81,6 +78,12 @@ frappe.ui.toolbar.Toolbar = Class.extend({
|
|||
},
|
||||
|
||||
setup_help: function () {
|
||||
frappe.provide('frappe.help');
|
||||
frappe.help.show_results = show_results;
|
||||
|
||||
this.search = new frappe.search.SearchDialog();
|
||||
frappe.provide('frappe.searchdialog');
|
||||
frappe.searchdialog.search = this.search;
|
||||
|
||||
$(".dropdown-help .dropdown-toggle").on("click", function () {
|
||||
$(".dropdown-help input").focus();
|
||||
|
|
@ -154,13 +157,9 @@ frappe.ui.toolbar.Toolbar = Class.extend({
|
|||
|
||||
var me = this;
|
||||
function show_help_results(keywords) {
|
||||
me.search.search_dialog.show();
|
||||
me.search.setup_search(keywords, [me.help]);
|
||||
me.search.init_search(keywords, "help");
|
||||
}
|
||||
|
||||
frappe.provide('frappe.help');
|
||||
frappe.help.show_results = show_results;
|
||||
|
||||
function show_results(e) {
|
||||
//edit links
|
||||
href = e.target.href;
|
||||
|
|
|
|||
|
|
@ -550,14 +550,85 @@ textarea.form-control {
|
|||
.search-dialog {
|
||||
.modal-dialog {
|
||||
width: 768px;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.search-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0px 15px;
|
||||
|
||||
}
|
||||
|
||||
input.form-control, .input-group-addon {
|
||||
.empty-state {
|
||||
color: #d4d9dd;
|
||||
height: 500px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
.status-icon {
|
||||
font-size: 40px;
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cover {
|
||||
color: white;
|
||||
font-size: 6px;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes twinkle {
|
||||
0% { opacity:1; }
|
||||
50% { opacity:0; }
|
||||
100% { opacity:1; }
|
||||
}
|
||||
@-o-keyframes twinkle {
|
||||
0% { opacity:1; }
|
||||
50% { opacity:0; }
|
||||
100% { opacity:1; }
|
||||
}
|
||||
@-moz-keyframes twinkle {
|
||||
0% { opacity:1; }
|
||||
50% { opacity:0; }
|
||||
100% { opacity:1; }
|
||||
}
|
||||
@-webkit-keyframes twinkle {
|
||||
0% { opacity:1; }
|
||||
50% { opacity:0; }
|
||||
100% { opacity:1; }
|
||||
}
|
||||
.twinkle-one {
|
||||
-webkit-animation: twinkle 1.5s ease infinite;
|
||||
-moz-animation: twinkle 1.5s ease infinite;
|
||||
-o-animation: twinkle 1.5s ease infinite;
|
||||
animation: twinkle 1.5s ease infinite;
|
||||
}
|
||||
.twinkle-two {
|
||||
-webkit-animation: twinkle 1.5s ease infinite 0.5s;
|
||||
-moz-animation: twinkle 1.5s ease infinite 0.5s;
|
||||
-o-animation: twinkle 1.5s ease infinite 0.5s;
|
||||
animation: twinkle 1.5s ease infinite 0.5s;
|
||||
}
|
||||
.twinkle-three {
|
||||
-webkit-animation: twinkle 1.5s ease infinite 1s;
|
||||
-moz-animation: twinkle 1.5s ease infinite 1s;
|
||||
-o-animation: twinkle 1.5s ease infinite 1s;
|
||||
animation: twinkle 1.5s ease infinite 1s;
|
||||
}
|
||||
|
||||
input.form-control {
|
||||
border: none;
|
||||
border-left-style:none;
|
||||
}
|
||||
|
|
@ -567,10 +638,6 @@ textarea.form-control {
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
.input-group-addon{
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.layout-side-section,
|
||||
.layout-main-section {
|
||||
height: 500px;
|
||||
|
|
@ -593,6 +660,7 @@ textarea.form-control {
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-left: 20px;
|
||||
background-color: #ffffff;
|
||||
|
||||
i {
|
||||
visibility: hidden;
|
||||
|
|
@ -602,36 +670,14 @@ textarea.form-control {
|
|||
.active i {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.select a, a:hover {
|
||||
background-color: #f7fafc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.results-area {
|
||||
.search-intro-placeholder {
|
||||
color: #d4d9dd;
|
||||
height: inherit;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
text-align: center;
|
||||
|
||||
i {
|
||||
font-size: 64px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a {
|
||||
// color: #5E64FF;
|
||||
// }
|
||||
|
||||
.single-link a {
|
||||
color: #36414c;
|
||||
}
|
||||
|
|
@ -647,6 +693,10 @@ textarea.form-control {
|
|||
font-family: 'Octicons';
|
||||
content: '\f0a4';
|
||||
}
|
||||
|
||||
.result {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.full-list {
|
||||
|
|
@ -684,6 +734,34 @@ textarea.form-control {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.result {
|
||||
p {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.result-image {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
background-color: #fafbfc;
|
||||
|
||||
.flex-text {
|
||||
display: flex;
|
||||
height: 60px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 30px;
|
||||
color: @text-extra-muted;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: @screen-xs) {
|
||||
.modal-dialog {
|
||||
width: auto;
|
||||
|
|
@ -699,10 +777,6 @@ textarea.form-control {
|
|||
margin: 0px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.layout-side-section .sidebar-menu {
|
||||
margin: 30px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
|
|
@ -712,16 +786,6 @@ textarea.form-control {
|
|||
}
|
||||
}
|
||||
|
||||
.result {
|
||||
p {
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.search-result {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.note-editor.note-frame .note-editing-area .note-editable {
|
||||
color: @text-color;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ def delete_for_document(doc):
|
|||
name = %s''', (doc.doctype, doc.name), as_dict=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
def search(text, start=0, limit=20):
|
||||
def search(text, start=0, limit=20, doctype=""):
|
||||
'''Search for given text in __global_search
|
||||
:param text: phrase to be searched
|
||||
:param start: start results at, default 0
|
||||
|
|
@ -118,14 +118,31 @@ def search(text, start=0, limit=20):
|
|||
:return: Array of result objects'''
|
||||
|
||||
text = "+" + text + "*"
|
||||
results = frappe.db.sql('''
|
||||
select
|
||||
doctype, name, content
|
||||
from
|
||||
__global_search
|
||||
where
|
||||
match(content) against (%s IN BOOLEAN MODE)
|
||||
limit {start}, {limit}'''.format(start=start, limit=limit), text, as_dict=True)
|
||||
if not doctype:
|
||||
results = frappe.db.sql('''
|
||||
select
|
||||
doctype, name, content
|
||||
from
|
||||
__global_search
|
||||
where
|
||||
match(content) against (%s IN BOOLEAN MODE)
|
||||
limit {start}, {limit}'''.format(start=start, limit=limit), text+"*", as_dict=True)
|
||||
else:
|
||||
results = frappe.db.sql('''
|
||||
select
|
||||
doctype, name, content
|
||||
from
|
||||
__global_search
|
||||
where
|
||||
doctype = %s AND
|
||||
match(content) against (%s IN BOOLEAN MODE)
|
||||
limit {start}, {limit}'''.format(start=start, limit=limit), (doctype, text), as_dict=True)
|
||||
|
||||
for r in results:
|
||||
if frappe.get_meta(r.doctype).image_field:
|
||||
doc = frappe.get_doc(r.doctype, r.name)
|
||||
r.image = doc.get(doc.meta.image_field)
|
||||
|
||||
return results
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
|
|
@ -148,45 +165,3 @@ def web_search(text, start=0, limit=20):
|
|||
limit {start}, {limit}'''.format(start=start, limit=limit),
|
||||
text, as_dict=True)
|
||||
return results
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_search_doctypes(text):
|
||||
'''Search for all t
|
||||
:param text: phrase to be searched
|
||||
:return: Array of result objects'''
|
||||
|
||||
text = "+" + text + "*"
|
||||
results = frappe.db.sql('''
|
||||
select
|
||||
doctype
|
||||
from
|
||||
__global_search
|
||||
where
|
||||
match(content) against (%s IN BOOLEAN MODE)
|
||||
group by
|
||||
doctype
|
||||
order by
|
||||
count(doctype) desc limit 0, 80''', text, as_dict=True)
|
||||
return results
|
||||
|
||||
@frappe.whitelist()
|
||||
def search_in_doctype(doctype, text, start, limit):
|
||||
'''Search for given text in given doctype in __global_search
|
||||
:param doctype: doctype to be searched in
|
||||
:param text: phrase to be searched
|
||||
:param start: start results at, default 0
|
||||
:param limit: number of results to return, default 20
|
||||
:return: Array of result objects'''
|
||||
|
||||
text = "+" + text + "*"
|
||||
results = frappe.db.sql('''
|
||||
select
|
||||
doctype, name, content
|
||||
from
|
||||
__global_search
|
||||
where
|
||||
doctype = %s AND
|
||||
match(content) against (%s IN BOOLEAN MODE)
|
||||
limit {start}, {limit}'''.format(start=start, limit=limit), (doctype, text), as_dict=True)
|
||||
|
||||
return results
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue