feat: use search bar for tags too

This commit is contained in:
Himanshu Warekar 2019-09-25 17:48:36 +05:30
parent b7196f124c
commit abb477a1c0
5 changed files with 181 additions and 122 deletions

View file

@ -4,8 +4,7 @@
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"description",
"count"
"description"
],
"fields": [
{
@ -13,15 +12,9 @@
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Description"
},
{
"fieldname": "count",
"fieldtype": "Int",
"label": "Count",
"read_only": 1
}
],
"modified": "2019-09-24 00:53:13.488147",
"modified": "2019-09-25 17:47:41.712237",
"modified_by": "Administrator",
"module": "Desk",
"name": "Tag",

View file

@ -2,109 +2,14 @@
// MIT License. See license.txt
frappe.provide("frappe.global_tags");
frappe.provide("locals.global_tags");
frappe.global_tags.GlobalTagsDialog = class GlobalTags {
constructor(opts) {
$.extend(this, opts);
this.show();
}
show() {
if (!this.dialog) {
this.make_dialog();
}
$(this.dialog.body).html(
`<div class="text-muted text-center" style="padding: 30px 0px">
${__("Loading")}...
</div>`);
this.dialog.show();
}
make_dialog() {
let title = __("Tag {0}", ["#".concat(this.tag)]);
this.dialog = new frappe.ui.Dialog({
hide_on_page_refresh: true,
minimizable: true,
title: title
});
this.dialog.on_page_show = () => {
this.get_documents_for_tag()
.then(() => this.make_html());
};
}
make_html() {
const results = this.results;
let html = '';
const linked_doctypes = Object.keys(results);
if (linked_doctypes.length === 0) {
html = __("Not Linked to any record");
} else {
html = linked_doctypes.map(doctype => {
const docs = results[doctype];
return `
<div class="list-item-table margin-bottom">
${this.make_doc_head(doctype)}
${docs.map(doc => this.make_doc_row(doc.dn, doctype, doc.title)).join('')}
</div>
`;
}).join('');
}
$(this.dialog.body).html(html);
}
get_documents_for_tag() {
return new Promise((resolve) => {
frappe.call({
method: "frappe.utils.global_tags.get_documents_for_tag",
args: {
tag: this.tag
},
callback: (r) => {
this.results = r.message;
resolve();
}
});
});
}
make_doc_head(heading) {
return `
<header class="level list-row list-row-head text-muted small">
<div>${__(heading)}</div>
</header>
`;
}
make_doc_row(docname, doctype, title) {
return `<div class="list-row-container">
<div class="level list-row small">
<div class="level-left bold">
<a href="#Form/${doctype}/${docname}">${docname}</a>
</div>
<div class="level-left">
<p>${title}</p>
</div>
</div>
</div>`;
}
};
frappe.global_tags.utils = {
get_tags: function(txt) {
txt = txt.slice(1);
let out = [];
for (let i in locals.global_tags) {
let tag = locals.global_tags[i];
for (let i in frappe.global_tags.tags) {
let tag = frappe.global_tags.tags[i];
let level = frappe.search.utils.fuzzy_search(txt, tag);
if (level) {
out.push({
@ -113,8 +18,9 @@ frappe.global_tags.utils = {
value: __("#{0}", [__(tag)]),
index: 1 + level,
match: tag,
onclick: function() {
new frappe.global_tags.GlobalTagsDialog({"tag": tag});
onclick() {
// Use Global Search Dialog for tag search too.
frappe.searchdialog.search.init_search("#".concat(tag), "global_tag")
}
});
}
@ -123,14 +29,130 @@ frappe.global_tags.utils = {
return out;
},
set_tags: function() {
set_tags() {
frappe.call({
method: "frappe.utils.global_tags.get_tags_list_for_awesomebar",
callback: function(r) {
if (r && r.message) {
locals.global_tags = $.extend([], r.message);
frappe.global_tags.tags = $.extend([], r.message);
}
}
});
}
}
},
get_tag_results: function(tag) {
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) {
var parts = content.split(" ||| ");
var result_max_length = 300;
var field_length = 120;
var fields = [];
var result_current_length = 0;
var field_text = "";
for(var i = 0; i < parts.length; i++) {
var part = parts[i];
if(part.toLowerCase().indexOf(tag) !== -1) {
// If the field contains the keyword
if(part.indexOf(' &&& ') !== -1) {
var colon_index = part.indexOf(' &&& ');
var field_value = part.slice(colon_index + 5);
} else {
var colon_index = part.indexOf(' : ');
var field_value = part.slice(colon_index + 3);
}
if(field_value.length > field_length) {
// If field value exceeds field_length, find the keyword in it
// and trim field value by half the field_length at both sides
// ellipsify if necessary
var field_data = "";
var index = field_value.indexOf(tag);
field_data += index < field_length/2 ? field_value.slice(0, index)
: '...' + field_value.slice(index - field_length/2, index);
field_data += field_value.slice(index, index + field_length/2);
field_data += index + field_length/2 < field_value.length ? "..." : "";
field_value = field_data;
}
var field_name = part.slice(0, colon_index);
// Find remaining result_length and add field length to result_current_length
var remaining_length = result_max_length - result_current_length;
result_current_length += field_name.length + field_value.length + 2;
if(result_current_length < result_max_length) {
// We have room, push the entire field
field_text = '<span class="field-name text-muted">' +
me.bolden_match_part(field_name, tag) + ': </span> ' +
me.bolden_match_part(field_value, tag);
if(fields.indexOf(field_text) === -1 && doc_name !== field_value) {
fields.push(field_text);
}
} else {
// Not enough room
if(field_name.length < remaining_length){
// Ellipsify (trim at word end) and push
remaining_length -= field_name.length;
field_text = '<span class="field-name text-muted">' +
me.bolden_match_part(field_name, tag) + ': </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, tag);
fields.push(field_text);
} else {
// No room for even the field name, skip
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],
};
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_tags.get_documents_for_tag",
args: {
tag: tag
},
callback: function(r) {
if(r.message) {
resolve(get_results_sets(r.message));
} else {
resolve([]);
}
}
});
});
},
}

View file

@ -124,6 +124,10 @@ frappe.search.SearchDialog = Class.extend({
// Help results
// this.$modal_body.on('click', 'a[data-path]', frappe.help.show_results);
this.bind_keyboard_events();
// Setup Minimizable functionality
this.search_dialog.minimizable = true;
this.search_dialog.$wrapper.find('.btn-modal-minimize').click(() => this.toggle_minimize());
},
bind_keyboard_events: function() {
@ -308,7 +312,7 @@ frappe.search.SearchDialog = Class.extend({
frappe.route_options = result.route_options;
}
$result.on('click', (e) => {
this.search_dialog.hide();
this.toggle_minimize();
if(result.onclick) {
result.onclick(result.match);
} else {
@ -353,6 +357,19 @@ frappe.search.SearchDialog = Class.extend({
this.$modal_body.find('.more-results.last').slideDown(200, function() {});
},
get_minimize_btn: function() {
return this.search_dialog.$wrapper.find(".modal-header .btn-modal-minimize");
},
toggle_minimize: function() {
let modal = this.search_dialog.$wrapper.closest('.modal').toggleClass('modal-minimize');
modal.attr('tabindex') ? modal.removeAttr('tabindex') : modal.attr('tabindex', -1);
this.get_minimize_btn().find('i').toggleClass('octicon-chevron-down').toggleClass('octicon-chevron-up');
this.search_dialog.is_minimized = !this.search_dialog.is_minimized;
this.on_minimize_toggle && this.on_minimize_toggle(this.search_dialog.is_minimized);
this.search_dialog.header.find('.modal-title').toggleClass('cursor-pointer');
},
// Search objects
searches: {
global_search: {
@ -372,6 +389,22 @@ frappe.search.SearchDialog = Class.extend({
});
}
},
global_tag: {
input_placeholder: __("Global Tags"),
empty_state_text: __("Search for Tags"),
no_results_status: (keyword) => __("<p>No results found for '" + keyword + "' in Global Tags</p>"),
get_results: function(keywords, callback) {
var results = frappe.search.utils.get_nav_results(keywords);
frappe.global_tags.utils.get_tag_results(keywords)
.then(function(global_results) {
results = results.concat(global_results);
callback(results, keywords);
}, function (err) {
console.error(err);
});
}
},
},
});

View file

@ -1,6 +1,13 @@
<div class="search-header">
<i class="octicon octicon-search"></i>
<input type="text" class="form-control search-input" style="padding-left: 15px">
<p class="loading-state hide" style="margin: 0px 20px; color:#d4d9dd">{%= __("Searching")%}&nbsp...</p>
<a type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</a>
<i class="octicon octicon-search"></i>
<input type="text" class="form-control search-input" style="padding-left: 15px">
<p class="loading-state hide" style="margin: 0px 20px; color:#d4d9dd">{%= __("Searching")%}&nbsp...</p>
<!--a type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</a -->
<a type="button" class="btn btn-default btn-sm btn-modal-minimize" style="margin-right: 2px;">
<i class="octicon octicon-chevron-down" style="padding: 1px 0px;"></i>
</a>
<a type="button" class="btn btn-default btn-sm btn-modal-close" data-dismiss="modal" aria-hidden="true">
<i class="octicon octicon-x visible-xs" style="padding: 1px 0px;"></i>
<span class="hidden-xs">Close</span>
</a>
</div>

View file

@ -48,7 +48,10 @@ def get_documents_for_tag(tag):
:param tag: tag to be searched
"""
# remove hastag `#` from tag
results = {}
tag = tag[1:]
results = []
tag = frappe.db.escape('%{0}%'.format(tag.lower()), False)
result = frappe.db.sql('''
@ -58,10 +61,11 @@ def get_documents_for_tag(tag):
'''.format(tag), as_dict=True)
for res in result:
if res.dt in results.keys():
results[res.dt].append(res)
else:
results[res.dt] = [res]
results.append({
"doctype": res.dt,
"name": res.dn,
"content": res.title
})
return results