From 06f78d041ffce73ae93327773219860826f6d3a3 Mon Sep 17 00:00:00 2001 From: Michael M Slusarz Date: Thu, 18 Nov 2010 16:54:41 -0700 Subject: [PATCH] Add subfolder searching to search UI For now, remove Tree display of mailboxes in UI. It might be a bit easier to add mailboxes using a tree, but it is more difficult to visualize the current list of search mailboxes (and I can't figure out a clean way to allow subfolder searches to be defined). --- imp/docs/CHANGES | 1 + imp/js/search.js | 497 ++++++++++++++++++++--------------- imp/lib/Imap/Tree.php | 10 +- imp/lib/Search/Query.php | 9 +- imp/search.php | 53 +++- imp/templates/imp/search/search.html | 41 ++- imp/themes/ie6_or_less.css | 4 +- imp/themes/ie7.css | 4 +- imp/themes/screen.css | 54 ++-- 9 files changed, 402 insertions(+), 271 deletions(-) diff --git a/imp/docs/CHANGES b/imp/docs/CHANGES index cb782c638..fc110e643 100644 --- a/imp/docs/CHANGES +++ b/imp/docs/CHANGES @@ -2,6 +2,7 @@ v5.0-git -------- +[mms] Add subfolder searching. [mms] Refactor inline message image blocking to operate on all messages, not just HTML messages. [mms] Add attachment message filter. diff --git a/imp/js/search.js b/imp/js/search.js index ed6a67154..59eac48f5 100644 --- a/imp/js/search.js +++ b/imp/js/search.js @@ -7,24 +7,28 @@ var ImpSearch = { // The following variables are defined in search.php: - // data, i_criteria, recent, selected, text + // data, i_criteria, i_folders, i_recent, text criteria: {}, + folders: $H(), saved_searches: {}, - getAll: function() + updateRecentSearches: function(searches) { - return $('search_form').getInputs(null, 'folder_list[]'); - }, + var fragment = document.createDocumentFragment(), + node = new Element('OPTION'); - selectFolders: function(checked) - { - this.getAll().each(function(e) { - if (!e.disabled) { - e.checked = Boolean(checked); - } - }); + $('recent_searches_div').show().next().show(); + + this.saved_searches = $H(searches); + this.saved_searches.each(function(s) { + fragment.appendChild($(node.clone(false)).writeAttribute({ value: s.key.escapeHTML() }).update(s.value.l.escapeHTML())); + }, this); + + $('recent_searches').appendChild(fragment); }, + // Criteria actions + showOr: function(show) { var or = $('search_criteria_add').down('[value="or"]'); @@ -35,22 +39,7 @@ var ImpSearch = { } }, - updateRecentSearches: function(searches) - { - var fragment = document.createDocumentFragment(), - node = new Element('OPTION'); - - $('recent_searches_div').show(); - - $H(searches).each(function(s) { - fragment.appendChild($(node.clone(false)).writeAttribute({ value: s.value.v.escapeHTML() }).update(s.value.l.escapeHTML())); - this.saved_searches[s.key] = s.value.c; - }, this); - - $('recent_searches').appendChild(fragment); - }, - - updateSearchCriteria: function(criteria) + updateCriteria: function(criteria) { this.resetCriteria(); @@ -77,7 +66,7 @@ var ImpSearch = { case 'cc': case 'bcc': case 'subject': - this.insertText(crit.h.capitalize(), crit.t, crit.n); + this.insertText(crit.h, crit.t, crit.n); break; default: @@ -115,82 +104,14 @@ var ImpSearch = { break; } }, this); - }, - - updateSelectedFolders: function(folders) - { - var tmp = $('search_folders_hdr'); - if (!tmp) { - return; - } - - tmp = tmp.next(); - this.selectFolders(false); - folders.each(function(f) { - var i = tmp.down('INPUT[value=' + f + ']'); - if (i) { - i.checked = true; - } - }); - }, - - changeHandler: function(e) - { - var elt = e.element(), val = $F(elt); - - switch (elt.readAttribute('id')) { - case 'recent_searches': - this.updateSearchCriteria(this.saved_searches[$F(elt)]); - if (!$('search_criteria').up().visible()) { - this.toggleHeader($('search_criteria').up().previous()); - } - elt.clear(); - break; - - case 'search_criteria_add': - if (val == 'or') { - this.insertOr(); - break; - } - - switch (this.data.types[val]) { - case 'header': - case 'text': - this.insertText(val); - break; - - case 'customhdr': - this.insertCustomHdr(); - break; - - case 'size': - this.insertSize(val); - break; - - case 'date': - this.insertDate(val); - break; - - case 'within': - this.insertWithin(val); - break; - - case 'filter': - this.insertFilter(val); - break; - - case 'flag': - this.insertFlag(val); - break; - } - break; + if ($('search_criteria').childElements().size()) { + $('no_search_criteria', 'search_criteria').invoke('toggle'); + this.showOr(true); } - - e.stop(); }, - getLabel: function(id) + getCriteriaLabel: function(id) { return $('search_criteria_add').down('[value="' + RegExp.escape(id) + '"]').getText() + ': '; }, @@ -217,26 +138,32 @@ var ImpSearch = { if (!keys.size()) { this.showOr(false); + $('no_search_criteria', 'search_criteria').invoke('toggle'); } }, resetCriteria: function() { - $('search_criteria').childElements().invoke('remove'); - this.criteria = {}; - this.showOr(false); + var elts = $('search_criteria').childElements(); + if (elts) { + elts.invoke('remove'); + $('no_search_criteria', 'search_criteria').invoke('toggle'); + this.criteria = {}; + this.showOr(false); + } }, insertCriteria: function(tds) { - var div = new Element('DIV', { className: 'searchCriteriaId' }), - div2 = new Element('DIV', { className: 'searchCriteriaElement' }); + var div = new Element('DIV', { className: 'searchId' }), + div2 = new Element('DIV', { className: 'searchElement' }); if ($('search_criteria').childElements().size()) { if (this.criteria[$('search_criteria').childElements().last().readAttribute('id')].t != 'or') { div.insert(new Element('EM', { className: 'join' }).insert(this.text.and)); } } else { + $('no_search_criteria', 'search_criteria').invoke('toggle'); this.showOr(true); } @@ -265,7 +192,7 @@ var ImpSearch = { insertText: function(id, text, not) { var tmp = [ - new Element('EM').insert(this.getLabel(id)), + new Element('EM').insert(this.getCriteriaLabel(id)), new Element('INPUT', { type: 'text', size: 25 }).setValue(text), new Element('SPAN', { className: 'notMatch' }).insert(new Element('INPUT', { checked: Boolean(not), className: 'checkbox', type: 'checkbox' })).insert(this.text.not_match) ]; @@ -290,7 +217,7 @@ var ImpSearch = { insertSize: function(id, size) { var tmp = [ - new Element('EM').insert(this.getLabel(id)), + new Element('EM').insert(this.getCriteriaLabel(id)), // Convert from bytes to KB new Element('INPUT', { type: 'text', size: 10 }).setValue(Object.isNumber(size) ? Math.round(size / 1024) : '') ]; @@ -305,7 +232,7 @@ var ImpSearch = { } var tmp = [ - new Element('EM').insert(this.getLabel(id)), + new Element('EM').insert(this.getCriteriaLabel(id)), new Element('SPAN').insert(new Element('SPAN')).insert(new Element('A', { href: '#', className: 'calendarPopup', title: this.text.dateselection }).insert(new Element('SPAN', { className: 'iconImg searchuiImg searchuiCalendar' }))) ]; this.replaceDate(this.insertCriteria(tmp), id, data); @@ -313,7 +240,7 @@ var ImpSearch = { replaceDate: function(id, type, d) { - $(id).down('TD SPAN SPAN').update(this.data.months[d.getMonth()] + ' ' + d.getDate() + ', ' + (d.getYear() + 1900)); + $(id).down('SPAN SPAN').update(this.data.months[d.getMonth()] + ' ' + d.getDate() + ', ' + (d.getYear() + 1900)); // Need to store date information at all times in criteria, since we // have no other way to track this information (there is not form // field for this type). @@ -325,7 +252,7 @@ var ImpSearch = { data = data || { l: '', v: '' }; var tmp = [ - new Element('EM').insert(this.getLabel(id)), + new Element('EM').insert(this.getCriteriaLabel(id)), new Element('SPAN').insert(new Element('INPUT', { type: 'text', size: 8 }).setValue(data.v)).insert(' ').insert($($('within_criteria').clone(true)).writeAttribute({ id: null }).show().setValue(data.l)) ]; this.criteria[this.insertCriteria(tmp)] = { t: id }; @@ -335,7 +262,7 @@ var ImpSearch = { insertFilter: function(id, not) { var tmp = [ - new Element('EM').insert(this.getLabel(id)), + new Element('EM').insert(this.getCriteriaLabel(id)), new Element('SPAN').insert(new Element('INPUT', { checked: Boolean(not), className: 'checkbox', type: 'checkbox' })).insert(this.text.not_match) ]; this.criteria[this.insertCriteria(tmp)] = { t: id }; @@ -345,84 +272,195 @@ var ImpSearch = { { var tmp = [ new Element('EM').insert(this.text.flag), - this.getLabel(id).slice(0, -2), + this.getCriteriaLabel(id).slice(0, -2), new Element('SPAN').insert(new Element('INPUT', { checked: Boolean(not), className: 'checkbox', type: 'checkbox' })).insert(this.text.not_match) ]; this.criteria[this.insertCriteria(tmp)] = { t: id }; }, + // Folder actions + + // folders = (object) m: mailboxes, s: subfolders + updateFolders: function(folders) + { + this.resetFolders(); + folders.m.each(function(f) { + this.insertFolder(f, false); + }, this); + folders.s.each(function(f) { + this.insertFolder(f, true); + }, this); + }, + + deleteFolder: function(div) + { + var first, keys, + id = div.identify() + + this.disableFolder(false, this.folders.get(id)); + this.folders.unset(id); + div.remove(); + + keys = $('search_folders').childElements().pluck('id'); + + if (keys.size()) { + first = keys.first(); + if ($(first).down().hasClassName('join')) { + $(first).down().remove(); + } + } + + if (!keys.size()) { + $('no_search_folders', 'search_folders').invoke('toggle'); + } + }, + + resetFolders: function() + { + elts = $('search_folders').childElements(); + + if (elts.size()) { + this.folders.values().each(this.disableFolder.bind(this, false)); + elts.invoke('remove'); + $('no_search_folders', 'search_folders').invoke('toggle'); + this.folders = $H(); + } + }, + + insertFolder: function(folder, checked) + { + var id, + div = new Element('DIV', { className: 'searchId' }), + div2 = new Element('DIV', { className: 'searchElement' }); + + if ($('search_folders').childElements().size()) { + div.insert(new Element('EM', { className: 'join' }).insert(this.text.and)); + } else { + $('no_search_folders', 'search_folders').invoke('toggle'); + } + + div.insert(div2); + + div2.insert( + new Element('EM').insert(this.getFolderLabel(folder).escapeHTML()) + ).insert( + new Element('SPAN', { className: 'subfolders' }).insert(new Element('INPUT', { checked: checked, className: 'checkbox', type: 'checkbox' })).insert(this.text.subfolder_search) + ).insert( + new Element('A', { href: '#', className: 'iconImg searchuiImg searchuiDelete' }) + ); + + + this.disableFolder(true, folder); + $('search_folders_add').clear(); + $('search_folders').insert(div); + + id = div.identify(); + this.folders.set(id, folder); + + return id; + }, + + getFolderLabel: function(folder) + { + return this.data.folder_list[folder]; + }, + + disableFolder: function(disable, folder) + { + $('search_folders_add').down('[value="' + escape(folder) + '"]').writeAttribute({ disabled: disable }); + }, + + // Miscellaneous actions + submit: function() { - var data = [], tmp; + var criteria, + data = [], + f_out = { mbox: [], subfolder: [] }, + sflist = []; - if ($('search_folders_hdr') && - !this.getAll().findAll(function(i) { return i.checked; }).size()) { - alert(this.text.need_folder); - } else if ($F('search_type') && !$('search_label').present()) { + if ($F('search_type') && !$('search_label').present()) { alert(this.text.need_label); - } else { - tmp = $('search_criteria').childElements().pluck('id'); - if (tmp.size()) { - tmp.each(function(c) { - var tmp2; - - if (this.criteria[c].t == 'or') { - data.push(this.criteria[c]); - return; - } + return; + } - switch (this.data.types[this.criteria[c].t]) { - case 'header': - case 'text': - this.criteria[c].n = Number(Boolean($F($(c).down('INPUT[type=checkbox]')))); - this.criteria[c].v = $F($(c).down('INPUT[type=text]')); - data.push(this.criteria[c]); - break; - - case 'customhdr': - this.criteria[c].v = { h: $F($(c).down('INPUT')), s: $F($(c).down('INPUT', 1)) }; - data.push(this.criteria[c]); - break; - - case 'size': - tmp2 = Number($F($(c).down('INPUT'))); - if (!isNaN(tmp2)) { - // Convert KB to bytes - this.criteria[c].v = tmp2 * 1024; - data.push(this.criteria[c]); - } - break; - - case 'date': - data.push(this.criteria[c]); - break; - - case 'within': - this.criteria[c].v = { l: $F($(c).down('SELECT')), v: parseInt($F($(c).down('INPUT')), 10) }; - data.push(this.criteria[c]); - break; - - case 'filter': - this.criteria[c].n = Number(Boolean($F($(c).down('INPUT[type=checkbox]')))); - data.push(this.criteria[c]); - break; - - case 'flag': - this.criteria[c].n = Number(Boolean($F($(c).down('INPUT[type=checkbox]')))); - data.push({ - n: this.criteria[c].n, - t: 'flag', - v: this.criteria[c].t - }); - break; - } - }, this); - $('criteria_form').setValue(Object.toJSON(data)); - $('search_form').submit(); - } else { - alert(this.text.need_criteria); - } + if (!this.folders.size()) { + alert(this.text.need_folder); + return; + } + + criteria = $('search_criteria').childElements().pluck('id'); + if (!criteria.size()) { + alert(this.text.need_criteria); + return; } + + criteria.each(function(c) { + var tmp; + + if (this.criteria[c].t == 'or') { + data.push(this.criteria[c]); + return; + } + + switch (this.data.types[this.criteria[c].t]) { + case 'header': + case 'text': + this.criteria[c].n = Number(Boolean($F($(c).down('INPUT[type=checkbox]')))); + this.criteria[c].v = $F($(c).down('INPUT[type=text]')); + data.push(this.criteria[c]); + break; + + case 'customhdr': + this.criteria[c].v = { h: $F($(c).down('INPUT')), s: $F($(c).down('INPUT', 1)) }; + data.push(this.criteria[c]); + break; + + case 'size': + tmp2 = Number($F($(c).down('INPUT'))); + if (!isNaN(tmp2)) { + // Convert KB to bytes + this.criteria[c].v = tmp2 * 1024; + data.push(this.criteria[c]); + } + break; + + case 'date': + data.push(this.criteria[c]); + break; + + case 'within': + this.criteria[c].v = { l: $F($(c).down('SELECT')), v: parseInt($F($(c).down('INPUT')), 10) }; + data.push(this.criteria[c]); + break; + + case 'filter': + this.criteria[c].n = Number(Boolean($F($(c).down('INPUT[type=checkbox]')))); + data.push(this.criteria[c]); + break; + + case 'flag': + this.criteria[c].n = Number(Boolean($F($(c).down('INPUT[type=checkbox]')))); + data.push({ + n: this.criteria[c].n, + t: 'flag', + v: this.criteria[c].t + }); + break; + } + }, this); + + $('criteria_form').setValue(Object.toJSON(data)); + + this.folders.each(function(f) { + var type = $F($(f.key).down('INPUT[type=checkbox]')) + ? 'subfolder' + : 'mbox'; + f_out[type].push(f.value); + }); + $('folders_form').setValue(Object.toJSON(f_out)); + + $('search_form').submit(); }, clickHandler: function(e) @@ -445,7 +483,7 @@ var ImpSearch = { case 'search_reset': this.resetCriteria(); - this.selectFolders(false); + this.resetFolders(); return; case 'search_dimp_return': @@ -453,12 +491,6 @@ var ImpSearch = { window.parent.DimpBase.go('folder:' + this.data.searchmbox); return; - case 'link_sel_all': - case 'link_sel_none': - this.selectFolders(id == 'link_sel_all'); - e.stop(); - return; - case 'search_edit_query_cancel': e.stop(); if (this.data.dimp) { @@ -469,15 +501,16 @@ var ImpSearch = { return; default: - if (elt.hasClassName('arrowExpanded') || - elt.hasClassName('arrowCollapsed')) { - this.toggleHeader(elt.up()); - } else if (elt.hasClassName('searchuiDelete')) { - this.deleteCriteria(elt.up('DIV.searchCriteriaId')); + if (elt.hasClassName('searchuiDelete')) { + if (elt.up('#search_criteria')) { + this.deleteCriteria(elt.up('DIV.searchId')); + } else { + this.deleteFolder(elt.up('DIV.searchId')); + } e.stop(); return; } else if (elt.hasClassName('searchuiCalendar')) { - Horde_Calendar.open(elt.identify(), this.criteria[elt.up('DIV.searchCriteriaId').identify()].v); + Horde_Calendar.open(elt.identify(), this.criteria[elt.up('DIV.searchId').identify()].v); e.stop(); return; } @@ -488,20 +521,70 @@ var ImpSearch = { } }, - toggleHeader: function(elt) + changeHandler: function(e) { - elt.down().toggle().next().toggle().up().next().toggle(); - if (elt.readAttribute('id') == 'search_folders_hdr') { - elt.down('SPAN.searchuiFoldersActions').toggle(); - if (window.imp_search && elt.next().visible()) { - window.imp_search.stripe(); + var tmp, + elt = e.element(), + val = $F(elt); + + switch (elt.readAttribute('id')) { + case 'recent_searches': + tmp = this.saved_searches.get($F(elt)); + this.updateCriteria(tmp.c); + this.updateFolders(tmp.f); + elt.clear(); + break; + + case 'search_criteria_add': + if (val == 'or') { + this.insertOr(); + break; + } + + switch (this.data.types[val]) { + case 'header': + case 'text': + this.insertText(val); + break; + + case 'customhdr': + this.insertCustomHdr(); + break; + + case 'size': + this.insertSize(val); + break; + + case 'date': + this.insertDate(val); + break; + + case 'within': + this.insertWithin(val); + break; + + case 'filter': + this.insertFilter(val); + break; + + case 'flag': + this.insertFlag(val); + break; } + break; + + case 'search_folders_add': + this.insertFolder(unescape($F('search_folders_add'))); + break; } + + e.stop(); }, + calendarSelectHandler: function(e) { - var id = e.findElement('DIV.searchCriteriaId').identify(); + var id = e.findElement('DIV.searchId').identify(); this.replaceDate(id, this.criteria[id].t, e.memo); }, @@ -515,20 +598,20 @@ var ImpSearch = { this.data.constants.date = $H(this.data.constants.date); this.data.constants.within = $H(this.data.constants.within); - if (this.recent) { - this.updateRecentSearches(this.recent); - this.recent = null; - } - - if (this.selected) { - this.updateSelectedFolders(this.selected); - this.selected = null; + if (this.i_recent) { + this.updateRecentSearches(this.i_recent); + this.i_recent = null; } if (this.i_criteria) { - this.updateSearchCriteria(this.i_criteria); + this.updateCriteria(this.i_criteria); this.i_criteria = null; } + + if (this.i_folders) { + this.updateFolders(this.i_folders); + this.i_folders = null; + } } }; diff --git a/imp/lib/Imap/Tree.php b/imp/lib/Imap/Tree.php index 2be5e56fa..51a0a2ce4 100644 --- a/imp/lib/Imap/Tree.php +++ b/imp/lib/Imap/Tree.php @@ -1464,6 +1464,10 @@ class IMP_Imap_Tree implements ArrayAccess, Iterator, Serializable * DEFAULT: null (add to base level) * 'poll_info' - (boolean) Include poll information? * DEFAULT: false + * 'render_params' - (array) List of params to pass to renderer if + * auto-creating. + * DEFAULT: 'alternate', 'lines', 'lines_base', and + * 'nosession' are passed in with true values * 'render_type' - (string) The renderer name. * DEFAULT: Javascript * @@ -1473,6 +1477,8 @@ class IMP_Imap_Tree implements ArrayAccess, Iterator, Serializable public function createTree($name, array $opts = array()) { $opts = array_merge(array( + 'parent' => null, + 'render_params' => array(), 'render_type' => 'Javascript' ), $opts); @@ -1483,12 +1489,12 @@ class IMP_Imap_Tree implements ArrayAccess, Iterator, Serializable $tree = $name; $parent = $opts['parent']; } else { - $tree = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Tree')->create($name, $opts['render_type'], array( + $tree = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Tree')->create($name, $opts['render_type'], array_merge(array( 'alternate' => true, 'lines' => true, 'lines_base' => true, 'nosession' => true - )); + ), $opts['render_params'])); $parent = null; } diff --git a/imp/lib/Search/Query.php b/imp/lib/Search/Query.php index faf1badb3..474614f50 100644 --- a/imp/lib/Search/Query.php +++ b/imp/lib/Search/Query.php @@ -91,7 +91,8 @@ class IMP_Search_Query implements Serializable * DEFAULT: Search Results * mboxes - (array) The list of mailboxes to search. * DEFAULT: None - * subfolders - (array) The list of mailboxes to do subfolder searces for. + * subfolders - (array) The list of mailboxes to do subfolder searches + * for. * DEFAULT: None * */ @@ -121,6 +122,8 @@ class IMP_Search_Query implements Serializable $this->_mboxes[] = self::SUBFOLDER . $val; } } + + natsort($this->_mboxes); } /** @@ -133,11 +136,15 @@ class IMP_Search_Query implements Serializable * 'label' - (string) The query label. * 'mboxes' - (array) The list of mailboxes to query. This list * automatically expands subfolder searches. + * 'mbox_list' - (array) The list of individual mailboxes to query (no + * subfolder mailboxes). * 'mid' - (string) The query ID with the search mailbox prefix. * 'query' - (array) The list of IMAP queries that comprise this search. * Keys are mailbox names, values are * Horde_Imap_Client_Search_Query objects. * 'querytext' - (string) The textual representation of the query. + * 'subfolder_list' - (array) The list of mailboxes to do subfolder + * queries for. The subfolders are not expanded. * * * @return mixed Property value. diff --git a/imp/search.php b/imp/search.php index 1d781c928..02126b771 100644 --- a/imp/search.php +++ b/imp/search.php @@ -11,7 +11,8 @@ * 'edit_query_filter' - (string) The name of the filter being edited. * 'edit_query_vfolder' - (string) The name of the virtual folder being * edited. - * 'folder_list' - (array) The list of folders to add to the query. + * 'folders_form' - (string) JSON representation of the list of mailboxes for + * the query. Hash containing 2 keys: mbox & subfolder. * 'search_label' - (string) The label to use when saving the search. * 'search_mailbox' - (string) Use this mailbox as the default value. * DEFAULT: INBOX @@ -286,10 +287,12 @@ if ($vars->criteria_form) { break; case 'vfolder': + $folders_form = Horde_Serialize::unserialize($vars->folders_form, Horde_Serialize::JSON); $q_ob = $imp_search->createQuery($c_list, array( 'id' => IMP::formMbox($vars->edit_query_vfolder, false), 'label' => $vars->search_label, - 'mboxes' => Horde_Serialize::unserialize($vars->folders_form, Horde_Serialize::JSON), + 'mboxes' => $folders_form->mbox, + 'subfolders' => $folders_form->subfolder, 'type' => IMP_Search::CREATE_VFOLDER )); @@ -303,8 +306,10 @@ if ($vars->criteria_form) { break; default: + $folders_form = Horde_Serialize::unserialize($vars->folders_form, Horde_Serialize::JSON); $q_ob = $imp_search->createQuery($c_list, array( - 'mboxes' => Horde_Serialize::unserialize($vars->folders_form, Horde_Serialize::JSON) + 'mboxes' => $folders_form->mbox, + 'subfolders' => $folders_form->subfolder )); $redirect_target = 'mailbox'; break; @@ -331,9 +336,6 @@ if ($vars->criteria_form) { } } -/* Preselect mailboxes. */ -$js_vars['ImpSearch.selected'] = array($search_mailbox); - /* Prepare the search template. */ $t = $injector->createInstance('Horde_Template'); $t->setOption('gettext', true); @@ -365,6 +367,10 @@ if ($vars->edit_query && $imp_search->isSearchMbox($vars->edit_query)) { } $js_vars['ImpSearch.i_criteria'] = $q_ob->criteria; + $js_vars['ImpSearch.i_folders'] = array( + 'm' => $q_ob->mbox_list, + 's' => $q_ob->subfolder_list + ); } else { /* Process list of recent searches. */ $rs = array(); @@ -372,14 +378,22 @@ if ($vars->edit_query && $imp_search->isSearchMbox($vars->edit_query)) { foreach ($imp_search as $val) { $rs[$val->id] = array( 'c' => $val->criteria, - 'l' => Horde_String::truncate($val->querytext), - 'v' => $val->id + 'f' => array( + 'm' => $val->mbox_list, + 's' => $val->subfolder_list + ), + 'l' => Horde_String::truncate($val->querytext) ); } if (!empty($rs)) { - $js_vars['ImpSearch.recent'] = $rs; + $js_vars['ImpSearch.i_recent'] = $rs; } + + $js_vars['ImpSearch.i_folders'] = array( + 'm' => array($search_mailbox), + 's' => array() + ); } /* Create the criteria list. */ @@ -416,16 +430,27 @@ foreach ($flist['set'] as $val) { $t->set('flist', $flag_set); /* Generate master folder list. */ +$folder_list = array(); if (!$t->get('edit_query_filter')) { - $tree = $injector->getInstance('IMP_Imap_Tree')->createTree('imp_search', array( - 'checkbox' => true, + $imap_tree = $injector->getInstance('IMP_Imap_Tree'); + $imap_tree->setIteratorFilter(); + + $tree = $imap_tree->createTree('imp_search', array( + 'render_params' => array( + 'abbrev' => 0, + 'heading' => _("Add search folder:") + ), + 'render_type' => 'IMP_Tree_Flist' )); $t->set('tree', $tree->getTree()); + + foreach ($imap_tree as $val) { + $folder_list[$val->value] = $val->display; + } } Horde_Core_Ui_JsCalendar::init(); Horde::addScriptFile('horde.js', 'horde'); -Horde::addScriptFile('stripe.js', 'horde'); Horde::addScriptFile('search.js', 'imp'); Horde::addInlineJsVars(array_merge($js_vars, array( @@ -433,6 +458,7 @@ Horde::addInlineJsVars(array_merge($js_vars, array( 'ImpSearch.data' => array( 'constants' => $constants, 'dimp' => $dimp_view, + 'folder_list' => $folder_list, 'months' => Horde_Core_Ui_JsCalendar::months(), 'searchmbox' => $search_mailbox, 'types' => $types @@ -449,7 +475,8 @@ Horde::addInlineJsVars(array_merge($js_vars, array( 'need_label' => _("Saved searches require a label."), 'not_match' => _("Do NOT Match"), 'or' => _("OR"), - 'search_term' => _("Search Term:") + 'search_term' => _("Search Term:"), + 'subfolder_search' => _("Search all subfolders?") ) )), array('onload' => 'dom')); diff --git a/imp/templates/imp/search/search.html b/imp/templates/imp/search/search.html index 44a8cef41..923ec8004 100644 --- a/imp/templates/imp/search/search.html +++ b/imp/templates/imp/search/search.html @@ -1,5 +1,6 @@
+

@@ -16,8 +17,6 @@

@@ -29,15 +28,14 @@
- - - Search Criteria + Search Criteria
-
-
+
+
No Search Criteria
+ -
+
+ + +
- - Saved Searches
-
+
@@ -120,10 +117,6 @@
- -