@@ -13,7 +13,7 @@ import os, sys, importlib, inspect, json | |||
from .exceptions import * | |||
from .utils.jinja import get_jenv, get_template, render_template | |||
__version__ = '8.0.5' | |||
__version__ = '8.0.6' | |||
__title__ = "Frappe Framework" | |||
local = Local() | |||
@@ -1,5 +1,6 @@ | |||
{ | |||
"allow_copy": 0, | |||
"allow_guest_to_view": 0, | |||
"allow_import": 1, | |||
"allow_rename": 0, | |||
"autoname": "CustomScript.####", | |||
@@ -24,6 +25,7 @@ | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 1, | |||
"in_standard_filter": 1, | |||
"label": "DocType", | |||
@@ -55,6 +57,7 @@ | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 1, | |||
"in_standard_filter": 1, | |||
"label": "Script Type", | |||
@@ -84,8 +87,9 @@ | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Script", | |||
"length": 0, | |||
@@ -115,6 +119,7 @@ | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Sample", | |||
@@ -133,19 +138,19 @@ | |||
"unique": 0 | |||
} | |||
], | |||
"has_web_view": 0, | |||
"hide_heading": 0, | |||
"hide_toolbar": 0, | |||
"icon": "fa fa-glass", | |||
"idx": 1, | |||
"image_view": 0, | |||
"in_create": 0, | |||
"in_dialog": 0, | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2016-12-29 14:40:24.037012", | |||
"modified_by": "Administrator", | |||
"modified": "2017-04-03 18:07:28.138437", | |||
"modified_by": "faris@erpnext.com", | |||
"module": "Custom", | |||
"name": "Custom Script", | |||
"owner": "Administrator", | |||
@@ -160,7 +165,6 @@ | |||
"export": 0, | |||
"if_owner": 0, | |||
"import": 0, | |||
"is_custom": 0, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
@@ -181,7 +185,6 @@ | |||
"export": 0, | |||
"if_owner": 0, | |||
"import": 0, | |||
"is_custom": 0, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
@@ -196,7 +199,8 @@ | |||
"quick_entry": 0, | |||
"read_only": 0, | |||
"read_only_onload": 0, | |||
"show_name_in_global_search": 0, | |||
"sort_order": "ASC", | |||
"track_changes": 1, | |||
"track_seen": 0 | |||
} | |||
} |
@@ -846,7 +846,7 @@ | |||
"currency_fraction": "Cent", | |||
"currency_fraction_units": 100, | |||
"currency_symbol": "\u20ac", | |||
"number_format": "#,###.##", | |||
"number_format": "# ###,##", | |||
"timezones": [ | |||
"Europe/Paris" | |||
] | |||
@@ -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; | |||
} | |||
@@ -420,6 +420,7 @@ | |||
.list-item input[type=checkbox] { | |||
margin: 0; | |||
margin-right: 5px; | |||
flex: 0 0 12px; | |||
} | |||
.list-item .liked-by, | |||
.list-item .liked-by-filter-button { | |||
@@ -11,7 +11,7 @@ | |||
</i> | |||
<span class="likes-count">{{ (_liked_by.length > 99 ? "99+" : _liked_by.length) || "" }}</span> | |||
</span> | |||
<a class="grey list-id {{ css_seen }}" | |||
<a class="grey list-id {{ css_seen }} ellipsis" | |||
data-name="{{ _name }}" | |||
href="#Form/{{ _doctype_encoded }}/{{ _name_encoded }}" | |||
title="{{ _full_title }}">{{ strip_html(_title) }}</a> | |||
@@ -447,9 +447,6 @@ frappe.views.ListRenderer = Class.extend({ | |||
data._title = strip_html(data[title_field] || data.name); | |||
data._full_title = data._title; | |||
if (data._title.length > 35) { | |||
data._title = data._title.slice(0, 35) + '...'; | |||
} | |||
data._workflow = null; | |||
if (this.workflow_state_fieldname) { | |||
@@ -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> |
@@ -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> |
@@ -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; | |||
} | |||
.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, .input-group-addon { | |||
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; | |||
} | |||
} | |||
} | |||
.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; | |||
} | |||
.select a, a:hover { | |||
background-color: #f7fafc; | |||
} | |||
} | |||
} | |||
// a { | |||
// color: #5E64FF; | |||
// } | |||
.results-area { | |||
.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; | |||
} | |||
@@ -514,6 +514,7 @@ | |||
input[type=checkbox] { | |||
margin: 0; | |||
margin-right: 5px; | |||
flex: 0 0 12px; | |||
} | |||
.liked-by, .liked-by-filter-button { | |||
@@ -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 |