Files
server/usr/share/psa-horde/imp/js/search.js
2026-01-07 20:52:11 +01:00

709 lines
22 KiB
JavaScript

/**
* Advanced search page.
*
* @author Michael Slusarz <slusarz@horde.org>
* @copyright 2014-2015 Horde LLC
* @license GPL-2 (http://www.horde.org/licenses/gpl)
*/
var ImpSearch = {
// The following variables are defined in PHP code:
// data, i_criteria, i_mboxes, i_recent, text
criteria: {},
mboxes: $H(),
saved_searches: {},
updateRecentSearches: function(searches)
{
var fragment = document.createDocumentFragment(),
node = new Element('OPTION');
$('recent_searches_div').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"]');
if (show) {
or.show().next().show();
} else {
or.hide().next().hide();
}
},
updateCriteria: function(criteria)
{
this.resetCriteria();
criteria.each(function(c) {
var crit = c.criteria;
switch (c.element) {
case 'IMP_Search_Element_Attachment':
this.insertFilter('attach', crit);
break;
case 'IMP_Search_Element_Bulk':
this.insertFilter('bulk', crit);
break;
case 'IMP_Search_Element_Daterange':
// JS Date() requires timestamp in ms; PHP value is in secs
this.insertDate(crit.b ? new Date(crit.b * 1000) : 0, crit.e ? new Date(crit.e * 1000) : 0, crit.n);
break;
case 'IMP_Search_Element_Flag':
this.insertFlag(encodeURIComponent(decodeURIComponent(crit.f)), !crit.s);
break;
case 'IMP_Search_Element_Header':
switch (crit.h) {
case 'from':
case 'to':
case 'cc':
case 'bcc':
case 'subject':
this.insertText(crit.h, crit.t, crit.n);
break;
default:
this.insertCustomHdr({ h: crit.h.capitalize(), s: crit.t }, crit.n);
break;
}
break;
case 'IMP_Search_Element_Mailinglist':
this.insertFilter('mailinglist', crit);
break;
case 'IMP_Search_Element_Or':
this.insertOr();
break;
case 'IMP_Search_Element_Personal':
this.insertFilter('personal', crit);
break;
case 'IMP_Search_Element_Recipient':
this.insertText('recip', crit.t, crit.n);
break;
case 'IMP_Search_Element_Size':
this.insertSize(crit.l ? 'size_larger' : 'size_smaller', crit.s);
break;
case 'IMP_Search_Element_Text':
this.insertText(crit.b ? 'body' : 'text', crit.t, crit.n);
break;
case 'IMP_Search_Element_Within':
this.insertWithin(crit.o ? 'older' : 'younger', { l: this.data.constants.within.index(crit.t), v: crit.v });
break;
}
}, this);
if ($('search_criteria').childElements().size()) {
if ($('no_search_criteria').visible()) {
$('no_search_criteria', 'search_criteria').invoke('toggle');
}
this.showOr(true);
}
},
getCriteriaLabel: function(id, nocolon)
{
var sca = $('search_criteria_add').down('[value="' + RegExp.escape(id) + '"]');
return (sca.textContent || sca.innerText) + (nocolon ? ' ' : ': ');
},
deleteCriteria: function(div)
{
var first, keys;
delete this.criteria[div.identify()];
div.remove();
keys = $('search_criteria').childElements().pluck('id');
if (keys.size()) {
first = keys.first();
if (this.criteria[first].t && this.criteria[first].t == 'or') {
$(first).remove();
delete this.criteria[first];
keys = [];
} else if ($(first).down().hasClassName('join')) {
$(first).down().remove();
}
}
if (!keys.size()) {
this.showOr(false);
$('no_search_criteria', 'search_criteria').invoke('toggle');
}
},
resetCriteria: function()
{
var elts = $('search_criteria').childElements();
if (elts.size()) {
elts.invoke('remove');
$('no_search_criteria', 'search_criteria').invoke('toggle');
this.criteria = {};
this.showOr(false);
}
},
insertCriteria: function(tds)
{
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);
}
div.insert(div2);
tds.each(function(node) {
div2.insert(node);
});
div2.insert(new Element('A', { href: '#', className: 'iconImg searchuiImg searchuiDelete' }));
$('search_criteria_add').clear();
$('search_criteria').insert(div);
return div.identify();
},
insertOr: function()
{
var div = new Element('DIV').insert(new Element('EM', { className: 'join joinOr' }).insert('--&nbsp;' + this.text.or + '&nbsp;--'));
$('search_criteria_add').clear();
$('search_criteria').insert(div);
this.criteria[div.identify()] = { t: 'or' };
},
insertText: function(id, text, not)
{
var tmp = [
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', { className: 'checkbox', type: 'checkbox' }).setValue(not)).insert(this.text.not_match)
];
this.criteria[this.insertCriteria(tmp)] = { t: id };
tmp[1].activate();
},
insertCustomHdr: function(text, not)
{
text = text || { h: '', s: '' };
var tmp = [
new Element('EM').insert(this.text.customhdr),
new Element('INPUT', { type: 'text', size: 25 }).setValue(text.h),
new Element('SPAN').insert(new Element('EM').insert(this.text.search_term + ' ')).insert(new Element('INPUT', { type: 'text', size: 25 }).setValue(text.s)),
new Element('SPAN', { className: 'notMatch' }).insert(new Element('INPUT', { className: 'checkbox', type: 'checkbox' }).setValue(not)).insert(this.text.not_match)
];
this.criteria[this.insertCriteria(tmp)] = { t: 'customhdr' };
tmp[1].activate();
},
insertSize: function(id, size)
{
var tmp = [
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) : '')
];
this.criteria[this.insertCriteria(tmp)] = { t: id };
tmp[1].activate();
},
insertDate: function(begin, end, not)
{
var elt1, elt2, tmp, tmp2;
elt1 = new Element('SPAN').insert(
new Element('SPAN')
).insert(
new Element('A', { href: '#', className: 'dateReset', title: this.text.datereset }).insert(
new Element('SPAN', { className: 'iconImg searchuiImg closeImg' })
)
).insert(
new Element('A', { href: '#', className: 'calendarPopup', title: this.text.dateselection }).insert(
new Element('SPAN', { className: 'iconImg searchuiImg calendarImg' })
)
);
elt2 = elt1.clone(true);
tmp = [
new Element('EM').insert(this.getCriteriaLabel('date_range')),
elt1.addClassName('beginDate'),
new Element('SPAN').insert(this.text.to),
elt2.addClassName('endDate'),
new Element('SPAN', { className: 'notMatch' }).insert(new Element('INPUT', { className: 'checkbox', type: 'checkbox' }).setValue(not)).insert(this.text.not_match)
];
tmp2 = this.insertCriteria(tmp);
this.updateDate(tmp2, elt1, begin);
this.updateDate(tmp2, elt2, end);
},
updateDate: function(id, elt, data)
{
if (data) {
elt.down('SPAN').update(this.data.months[data.getMonth()] + ' ' + data.getDate() + ', ' + data.getFullYear());
elt.down('A.dateReset').show();
// Convert Date object to a UTC object, since JSON outputs in UTC.
data = new Date(Date.UTC(data.getFullYear(), data.getMonth(), data.getDate()));
} else {
elt.down('SPAN').update('-----');
elt.down('A.dateReset').hide();
}
// Need to store date information at all times in criteria, since
// there is no other way to track this information (there is no
// form field for this type). Also, convert Date object to a UTC
// object, since JSON outputs in UTC.
if (!this.criteria[id]) {
this.criteria[id] = { t: 'date_range' };
}
this.criteria[id][elt.hasClassName('beginDate') ? 'b' : 'e'] = data;
},
insertWithin: function(id, data)
{
data = data || { l: '', v: '' };
var tmp = [
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 };
tmp[1].down().activate();
},
insertFilter: function(id, not)
{
var tmp = [
new Element('EM').insert(this.getCriteriaLabel(id, true)),
new Element('SPAN', { className: 'notMatch' }).insert(new Element('INPUT', { className: 'checkbox', type: 'checkbox' }).setValue(not)).insert(this.text.not_match)
];
this.criteria[this.insertCriteria(tmp)] = { t: id };
},
insertFlag: function(id, not)
{
var tmp = [
new Element('EM').insert(this.text.flag),
new Element('SPAN', { className: 'searchFlag' }).insert(this.getCriteriaLabel(id).slice(0, -2).escapeHTML()),
new Element('SPAN', { className: 'notMatch' }).insert(new Element('INPUT', { className: 'checkbox', type: 'checkbox' }).setValue(not)).insert(this.text.not_match)
];
this.criteria[this.insertCriteria(tmp)] = { t: id };
},
// Mailbox actions
// mboxes = (object) m: mailboxes, s: subfolders
updateMailboxes: function(mboxes)
{
this.resetMailboxes();
mboxes.m.each(function(f) {
this.insertMailbox(f, false);
}, this);
mboxes.s.each(function(f) {
this.insertMailbox(f, true);
}, this);
},
deleteMailbox: function(div)
{
var first, keys,
id = div.identify();
this.disableMailbox(false, this.mboxes.get(id));
this.mboxes.unset(id);
div.remove();
keys = $('search_mboxes').childElements().pluck('id');
if (keys.size()) {
first = keys.first();
if ($(first).down().hasClassName('join')) {
$(first).down().remove();
}
}
if (!keys.size()) {
$('no_search_mboxes', 'search_mboxes').invoke('toggle');
$('search_mboxes_add').up().show();
}
},
resetMailboxes: function()
{
elts = $('search_mboxes').childElements();
if (elts.size()) {
this.mboxes.values().each(this.disableMailbox.bind(this, false));
elts.invoke('remove');
$('no_search_mboxes', 'search_mboxes').invoke('toggle');
$('search_mboxes_add').clear().up().show();
this.mboxes = $H();
}
},
insertMailbox: function(mbox, checked)
{
var div = new Element('DIV', { className: 'searchId' }),
div2 = new Element('DIV', { className: 'searchElement' });
if (mbox == this.allsearch) {
this.resetMailboxes();
[ $('no_search_mboxes'), $('search_mboxes_add').up() ].invoke('hide');
$('search_mboxes').show();
div2.insert(
new Element('EM').insert(ImpSearch.text.search_all.escapeHTML())
).insert(
new Element('A', { href: '#', className: 'iconImg searchuiImg searchuiDelete' })
);
} else {
if ($('search_mboxes').childElements().size()) {
div.insert(new Element('EM', { className: 'join' }).insert(this.text.and));
} else {
$('no_search_mboxes', 'search_mboxes').invoke('toggle');
}
div2.insert(
new Element('EM').insert(this.getMailboxLabel(mbox).escapeHTML())
).insert(
new Element('SPAN', { className: 'subfolders' }).insert(new Element('INPUT', { className: 'checkbox', type: 'checkbox' }).setValue(checked)).insert(this.text.subfolder_search).setStyle(mbox == this.data.inbox ? { display: 'none' } : {})
).insert(
new Element('A', { href: '#', className: 'iconImg searchuiImg searchuiDelete' })
);
this.disableMailbox(true, mbox);
$('search_mboxes_add').clear();
}
div.insert(div2);
$('search_mboxes').insert(div);
this.mboxes.set(div.identify(), mbox);
},
getMailboxLabel: function(mbox)
{
return this.data.mbox_list[mbox];
},
disableMailbox: function(disable, mbox)
{
$('search_mboxes_add').down('[value="' + mbox + '"]').writeAttribute({ disabled: disable });
},
// Miscellaneous actions
submit: function()
{
var criteria,
data = [],
f_out = { mbox: [], subfolder: [] },
type = $F('search_type');
if (type && !$('search_label').present()) {
alert(this.text.need_label);
return;
}
if (type != 'filter' && !this.mboxes.size()) {
alert(this.text.need_mbox);
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 = ~~(!!$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].n = ~~(!!$F($(c).down('INPUT[type=checkbox]')));
this.criteria[c].v = { h: $F($(c).down('INPUT')), s: $F($(c).down('INPUT', 1)) };
data.push(this.criteria[c]);
break;
case 'size':
tmp = Number($F($(c).down('INPUT')));
if (!isNaN(tmp)) {
// Convert KB to bytes
this.criteria[c].v = tmp * 1024;
data.push(this.criteria[c]);
}
break;
case 'date':
if (!this.criteria[c].b && !this.criteria[c].e) {
alert(this.text.need_date);
return;
}
this.criteria[c].n = ~~(!!$F($(c).down('INPUT[type=checkbox]')));
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 = ~~(!!$F($(c).down('INPUT[type=checkbox]')));
data.push(this.criteria[c]);
break;
case 'flag':
this.criteria[c].n = ~~(!!$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));
if ($('search_mboxes_add').up().visible()) {
this.mboxes.each(function(f) {
var type = $F($(f.key).down('INPUT[type=checkbox]'))
? 'subfolder'
: 'mbox';
f_out[type].push(f.value);
});
} else {
f_out.mbox.push(this.allsearch);
}
$('mboxes_form').setValue(Object.toJSON(f_out));
$('search_form').submit();
},
clickHandler: function(e)
{
var elt = e.element();
switch (elt.readAttribute('id')) {
case 'search_submit':
this.submit();
e.memo.stop();
break;
case 'search_reset':
this.resetCriteria();
this.resetMailboxes();
return;
case 'search_dynamic_return':
e.memo.hordecore_stop = true;
window.parent.DimpBase.go('mbox', this.data.searchmbox);
break;
case 'search_edit_query_cancel':
e.memo.hordecore_stop = true;
if (this.data.dynamic_view) {
window.parent.DimpBase.go();
} else {
document.location.href = this.prefsurl;
}
break;
case 'show_unsub':
HordeCore.doAction('searchMailboxList', {
unsub: 1
}, {
callback: this.showUnsubCallback.bind(this)
});
elt.remove();
e.memo.stop();
break;
default:
if (elt.hasClassName('searchuiDelete')) {
if (elt.up('#search_criteria')) {
this.deleteCriteria(elt.up('DIV.searchId'));
} else {
this.deleteMailbox(elt.up('DIV.searchId'));
}
e.memo.stop();
} else if (elt.hasClassName('calendarImg')) {
Horde_Calendar.open(elt.identify(), this.criteria[elt.up('DIV.searchId').identify()].v);
e.memo.stop();
} else if (elt.hasClassName('closeImg') &&
(elt.up('SPAN.beginDate') || elt.up('SPAN.endDate'))) {
this.updateDate(
elt.up('DIV.searchId').identify(),
elt.up('SPAN'),
0
);
e.memo.stop();
}
break;
}
},
changeHandler: function(e)
{
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.updateMailboxes(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();
break;
case 'within':
this.insertWithin(val);
break;
case 'filter':
this.insertFilter(val);
break;
case 'flag':
this.insertFlag(val);
break;
}
break;
case 'search_mboxes_add':
this.insertMailbox($F('search_mboxes_add'));
break;
}
e.stop();
},
calendarSelectHandler: function(e)
{
this.updateDate(
e.findElement('DIV.searchId').identify(),
e.element().up('SPAN'),
e.memo
);
},
showUnsubCallback: function(r)
{
var sfa = $('search_mboxes_add'),
vals = sfa.select('[disabled]').pluck('value');
this.data.mbox_list = r.mbox_list;
sfa.update(r.tree);
vals.each(function(v) {
if (v.length) {
this.disableMailbox(true, v);
}
}, this);
},
onDomLoad: function()
{
if (!this.data) {
this.onDomLoad.bind(this).delay(0.1);
return;
}
HordeCore.initHandler('click');
if (Prototype.Browser.IE) {
$('recent_searches', 'search_criteria_add', 'search_mboxes_add').compact().invoke('observe', 'change', ImpSearch.changeHandler.bindAsEventListener(ImpSearch));
} else {
document.observe('change', ImpSearch.changeHandler.bindAsEventListener(ImpSearch));
}
this.data.constants.within = $H(this.data.constants.within);
if (this.i_recent) {
this.updateRecentSearches(this.i_recent);
delete this.i_recent;
}
if (this.i_criteria) {
this.updateCriteria(this.i_criteria);
delete this.i_criteria;
}
if (this.i_mboxes) {
this.updateMailboxes(this.i_mboxes);
delete this.i_mboxes;
}
}
};
document.observe('dom:loaded', ImpSearch.onDomLoad.bindAsEventListener(ImpSearch));
document.observe('HordeCore:click', ImpSearch.clickHandler.bindAsEventListener(ImpSearch));
document.observe('Horde_Calendar:select', ImpSearch.calendarSelectHandler.bindAsEventListener(ImpSearch));