Skip to content

Commit 43ebfd0

Browse files
authored
Merge pull request #1792 from mercihabam/harmonize-adv-search-special-folders-selection
refactor(frontend): harmonize special folders selection on the advanced search page, allowing to select all folders or each individually
2 parents 6d5fd2c + 09fcf42 commit 43ebfd0

File tree

7 files changed

+102
-52
lines changed

7 files changed

+102
-52
lines changed

modules/advanced_search/js_modules/route_handlers.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,78 @@ function applyAdvancedSearchPageHandlers() {
6262
}
6363
}
6464
Hm_Message_List.check_empty_list();
65+
66+
$('body').on("click", ".pick_special_folders", function(e) {
67+
e.preventDefault();
68+
69+
const modal = new Hm_Modal({
70+
modalId: 'pick_special_folders_modal',
71+
title: 'Pick Special Folders',
72+
size: 'lg',
73+
});
74+
75+
const serverId = $(this).closest('li').data('serverId');
76+
const specialFolders = [];
77+
['archive', 'draft', 'junk', 'sent', 'trash'].forEach(folderType => {
78+
const folders = hm_special_folders()?.[serverId];
79+
const folder = folders?.find(f => f.type === folderType);
80+
if (folder) {
81+
specialFolders.push(folder);
82+
}
83+
});
84+
85+
const account = $(this).closest('li').find('.adv_folder_link');
86+
const accountLabel = account.text();
87+
const accountId = account.data('target');
88+
89+
const isFolderSelected = (folder) => get_adv_sources().find(source => source.label === getFolderLabel(folder));
90+
91+
const getFolderLabel = (folder) => accountLabel + ' > ' + folder;
92+
93+
modal.setContent(`
94+
<div class="d-flex gap-3 flex-wrap">
95+
<div class="form-check form-switch">
96+
<input class="form-check-input" type="checkbox" id="all-special-folders">
97+
<label class="form-check-label" for="all-special-folders">All Folders</label>
98+
</div>
99+
${specialFolders.map(folder => `
100+
<div class="form-check form-switch">
101+
<input class="form-check-input special_folder_checkbox" type="checkbox" id="${folder.id}" ${isFolderSelected(folder.label) ? 'checked' : ''}>
102+
<label class="form-check-label" for="${folder.id}">${folder.label}</label>
103+
</div>
104+
`).join('')}
105+
</div>
106+
`);
107+
108+
modal.addFooterBtn('Pick', 'btn-primary', function() {
109+
const selectedFolders = [];
110+
$('.special_folder_checkbox:checked').each(function() {
111+
selectedFolders.push(specialFolders.find(folder => folder.id === $(this).attr('id')));
112+
});
113+
selectedFolders.forEach(folder => {
114+
add_source_to_list(accountId + folder.id, getFolderLabel(folder.label), true);
115+
});
116+
modal.hide();
117+
});
118+
119+
modal.open();
120+
121+
$('#all-special-folders').on('change', function() {
122+
const checked = $(this).is(':checked');
123+
$('.special_folder_checkbox').prop('checked', checked);
124+
});
125+
126+
$('.special_folder_checkbox').on('change', function() {
127+
const allChecked = $('.special_folder_checkbox').length === $('.special_folder_checkbox:checked').length;
128+
$('#all-special-folders').prop('checked', allChecked);
129+
});
130+
131+
if (specialFolders.every(folder => isFolderSelected(folder.label))) {
132+
$('#all-special-folders').prop('checked', true);
133+
}
134+
});
135+
136+
return () => {
137+
$('body').off("click", ".pick_special_folders");
138+
}
65139
}

modules/advanced_search/modules.php

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,9 @@ public function process() {
9191
}
9292

9393
$searchInAllFolders = $this->request->post['all_folders'] ?? false;
94-
$searchInSpecialFolders = $this->request->post['all_special_folders'] ?? false;
9594
$includeSubfolders = $this->request->post['include_subfolders'] ?? false;
9695
if ($searchInAllFolders) {
9796
$msg_list = $this->all_folders_search($mailbox, $flags, $params, $limit);
98-
} elseif ($searchInSpecialFolders) {
99-
$msg_list = $this->special_folders_search($mailbox, $flags, $params, $limit);
10097
} else if ($includeSubfolders) {
10198
$msg_list = $this->all_folders_search($mailbox, $flags, $params, $limit, $this->folder);
10299
} else if (! $mailbox->select_folder($this->folder)) {
@@ -111,7 +108,8 @@ public function process() {
111108

112109
private function all_folders_search($mailbox, $flags, $params, $limit, $parent = '') {
113110
if ($parent) {
114-
$folders = $mailbox->get_subfolders($parent);
111+
$folders = [['name' => $parent]];
112+
$folders = array_merge($folders, $mailbox->get_subfolders($parent));
115113
} else {
116114
$folders = $mailbox->get_folders();
117115
}
@@ -124,20 +122,6 @@ private function all_folders_search($mailbox, $flags, $params, $limit, $parent =
124122
return $msg_list;
125123
}
126124

127-
private function special_folders_search($mailbox, $flags, $params, $limit) {
128-
$specials = $this->user_config->get('special_imap_folders', array());
129-
$folders = $specials[$this->imap_id] ?? [];
130-
131-
$msg_list = array();
132-
foreach ($folders as $folder) {
133-
$this->folder = $folder;
134-
$mailbox->select_folder($this->folder);
135-
$msgs = $this->imap_search($flags, $mailbox, $params, $limit);
136-
$msg_list = array_merge($msg_list, $msgs);
137-
}
138-
return $msg_list;
139-
}
140-
141125
private function imap_search($flags, $mailbox, $params, $limit) {
142126
$msg_list = array();
143127
$exclude_deleted = true;
@@ -176,6 +160,7 @@ private function validate_source($val) {
176160
}
177161
$source_parts = explode('_', $val);
178162
$this->imap_id = $source_parts[1];
163+
179164
$this->folder = hex2bin($source_parts[2]);
180165
return true;
181166
}

modules/advanced_search/setup.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
'adv_terms' => array('filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_REQUIRE_ARRAY),
4444
'adv_targets' => array('filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_REQUIRE_ARRAY),
4545
'all_folders' => FILTER_VALIDATE_BOOLEAN,
46-
'all_special_folders' => FILTER_VALIDATE_BOOLEAN,
4746
'include_subfolders' => FILTER_VALIDATE_BOOLEAN,
4847
)
4948
);

modules/advanced_search/site.js

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -87,18 +87,13 @@ var expand_adv_folder = function(res) {
8787
}
8888
};
8989

90-
$(document).on("change", "input[name='all_folders'],input[name='all_special_folders']", function() {
90+
$(document).on("change", "input[name='all_folders']", function() {
9191
const folderLi = $(this).closest('li');
92-
const divergentCheckboxName = this.name === 'all_folders' ? 'all_special_folders' : 'all_folders';
93-
const divergentCheckbox = $(this).closest('div').find(`input[name='${divergentCheckboxName}']`)
9492

9593
if ($(this).is(':checked')) {
9694
folderLi.find('a').attr('disabled', 'disabled');
97-
divergentCheckbox.prop('checked', false);
98-
divergentCheckbox.attr('disabled', 'disabled');
9995
} else {
10096
folderLi.find('a').removeAttr('disabled');
101-
divergentCheckbox.removeAttr('disabled');
10297
}
10398
});
10499

@@ -117,11 +112,8 @@ var adv_select_imap_folder = function(el) {
117112
folders.find('li').each(function(index) {
118113
const wrapper = $('<div class="d-flex justify-content-between wrapper"></div>');
119114
$(this).wrapInner(wrapper);
120-
const allSpecialFoldersCheckbox = `
121-
<span class="form-check">
122-
<label class="form-check-label" for="all_special_folders-${index}">All special folders</label>
123-
<input class="form-check-input" type="checkbox" name="all_special_folders" id="all_special_folders-${index}">
124-
</span>
115+
const pickSpecialFoldersButton = `
116+
<a href="#" class="btn btn-light btn-sm pick_special_folders">Pick special folders</a>
125117
`;
126118
const allFoldersCheckbox = `
127119
<span class="form-check">
@@ -130,7 +122,7 @@ var adv_select_imap_folder = function(el) {
130122
</span>
131123
`;
132124
const checkboxesWrapper = $('<div class="d-flex gap-3"></div>');
133-
checkboxesWrapper.append(allSpecialFoldersCheckbox);
125+
checkboxesWrapper.append(pickSpecialFoldersButton);
134126
checkboxesWrapper.append(allFoldersCheckbox);
135127
$(this).find('.wrapper').append(checkboxesWrapper);
136128
});
@@ -141,8 +133,8 @@ var adv_select_imap_folder = function(el) {
141133
$('.adv_folder_list').html(folders.html());
142134

143135
$('.adv_folder_link', list_container).on("click", function() { return expand_adv_folder_list($(this).data('target')); });
144-
$('a', list_container).not('.adv_folder_link').not('.close_adv_folders').off('click');
145-
$('a', list_container).not('.adv_folder_link').not('.close_adv_folders').on("click", function() { adv_folder_select($(this).data('id')); return false; });
136+
$('a', list_container).not('.adv_folder_link').not('.close_adv_folders').not('.pick_special_folders').off('click');
137+
$('a', list_container).not('.adv_folder_link').not('.close_adv_folders').not('.pick_special_folders').on("click", function() { adv_folder_select($(this).data('id')); return false; });
146138
$('.close_adv_folders').on("click", function() {
147139
$('.adv_folder_list').html('');
148140
$('.adv_folder_list').hide();
@@ -191,6 +183,8 @@ var adv_folder_select = function(id) {
191183
};
192184

193185
var add_source_to_list = function(id, label, includeSubfolders) {
186+
if (get_adv_sources().find(source => source.label === label)) return;
187+
194188
var close = $(globals.close_html);
195189
close.addClass('adv_remove_source');
196190
close.attr('data-target', id);
@@ -250,13 +244,7 @@ var get_adv_sources = function() {
250244
const searchInAllFolders = $('.adv_folder_list li input[name="all_folders"]:checked');
251245
searchInAllFolders.each(function() {
252246
const li = $(this).closest('li');
253-
sources.push({'source': li.attr('class'), 'label': li.find('a').text(), allFolders: true});
254-
});
255-
256-
const searchInSpecialFolders = $('.adv_folder_list li input[name="all_special_folders"]:checked');
257-
searchInSpecialFolders.each(function() {
258-
const li = $(this).closest('li');
259-
sources.push({'source': li.attr('class'), 'label': li.find('a').text(), specialFolders: true});
247+
sources.push({'source': li.attr('class'), 'label': li.find('a').first().text(), allFolders: true});
260248
});
261249

262250
const selected_sources = $('div', $('.adv_source_list'));
@@ -265,9 +253,8 @@ var get_adv_sources = function() {
265253
}
266254
selected_sources.each(function() {
267255
const source = this.className;
268-
const mailboxSource = source.split('_').slice(0, 2).join('_');
269-
if (!sources.find(s => s.source.indexOf(mailboxSource) > -1)) {
270-
sources.push({'source': source, 'label': $('a', $(this)).text(), subFolders: $(this).data('subfolders')});
256+
if (!sources.find(s => s.source === source)) {
257+
sources.push({'source': source, 'label': $(this).text(), subFolders: $(this).data('subfolders')});
271258
}
272259
});
273260
return sources;
@@ -443,8 +430,6 @@ var send_requests = function(requests) {
443430

444431
if (request['all_folders']) {
445432
params.push({name: 'all_folders', value: true});
446-
} else if (request['all_special_folders']) {
447-
params.push({name: 'all_special_folders', value: true});
448433
} else if (request['sub_folders']) {
449434
params.push({name: 'include_subfolders', value: true});
450435
}
@@ -507,10 +492,8 @@ var build_adv_search_requests = function(terms, sources, targets, times, other)
507492
time = times[ti];
508493
const config = {'source': source.source, 'time': time, 'other': other,
509494
'targets': target_vals, 'terms': term_vals};
510-
if (source.allFolders) {
495+
if (source.allFolders || source.source.split('_').filter(part => part.trim() !== '').length === 2) {
511496
config['all_folders'] = true;
512-
} else if (source.specialFolders) {
513-
config['all_special_folders'] = true;
514497
} else if (source.subFolders) {
515498
config['sub_folders'] = true;
516499
}
@@ -567,7 +550,7 @@ var apply_saved_search = function() {
567550
}
568551
}
569552
for (var i=0, len=details['sources'].length; i < len; i++) {
570-
add_source_to_list(details['sources'][i]['source'], details['sources'][i]['label']);
553+
add_source_to_list(details['sources'][i]['source'], details['sources'][i]['label'], details['sources'][i]['subFolders'] || false);
571554
}
572555
for (var i=0, len=details['targets'].length; i < len; i++) {
573556
if (i == 0) {

modules/core/output_modules.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,14 @@ protected function output() {
616616
$settings = $this->get('user_settings', array());
617617
$enable_snooze = $settings['enable_snooze'] ?? DEFAULT_ENABLE_SNOOZE;
618618
$enable_collect_address_on_send = $settings['enable_collect_address_on_send_setting'] ?? DEFAULT_ENABLE_COLLECT_ADDRESS_ON_SEND;
619+
$specialFolders = $settings['special_imap_folders'];
620+
$formattedSpecialFolders = [];
621+
foreach ($specialFolders as $serverId => $folders) {
622+
$formattedSpecialFolders[$serverId] = [];
623+
foreach ($folders as $type => $folder) {
624+
$formattedSpecialFolders[$serverId][] = ['type' => $type, 'label' => $folder, 'id' => bin2hex($folder)];
625+
}
626+
}
619627
$res = '<script type="text/javascript" id="data-store">'.
620628
'var globals = {};'.
621629
'var hm_is_logged = function () { return '.($this->get('is_logged') ? '1' : '0').'; };'.
@@ -633,6 +641,7 @@ protected function output() {
633641
'var hm_web_root_path = function() { return "'.WEB_ROOT.'"; };'.
634642
'var hm_flag_image_src = function() { return "<i class=\"bi bi-star-half\"></i>"; };'.
635643
'var hm_check_dirty_flag = function() { return '.($this->get('warn_for_unsaved_changes', '') ? '1' : '0').'; };'.
644+
'var hm_special_folders = function() { return '.json_encode($formattedSpecialFolders).'; };'.
636645
format_data_sources($this->get('data_sources', array()), $this);
637646

638647
if (!$this->get('disable_delete_prompt', DEFAULT_DISABLE_DELETE_PROMPT)) {

modules/imap/output_modules.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -880,7 +880,7 @@ protected function output() {
880880
$res = '';
881881
if ($this->get('imap_folders')) {
882882
foreach ($this->get('imap_folders', array()) as $id => $folder) {
883-
$res .= '<li class="imap_'.$id.'_"><a href="#" class="imap_folder_link" data-target="imap_'.$id.'_">';
883+
$res .= '<li class="imap_'.$id.'_" data-server-id="' . $id . '"><a href="#" class="imap_folder_link" data-target="imap_'.$id.'_">';
884884
if (!$this->get('hide_folder_icons')) {
885885
$res .= '<i class="bi bi-folder me-2"></i>';
886886
}

tests/phpunit/modules/core/output_modules.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,10 +426,10 @@ public function test_js_data() {
426426
$test = new Output_Test('js_data', 'core');
427427
$test->handler_response = array('disable_delete_prompt' => true);
428428
$res = $test->run();
429-
$this->assertStringStartsWith('<script type="text/javascript" id="data-store">var globals = {};var hm_is_logged = function () { return 0; };var hm_empty_folder = function() { return "So alone"; };var hm_mobile = function() { return 0; };var hm_debug = function() { return "0"; };var hm_mailto = function() { return 0; };var hm_page_name = function() { return ""; };var hm_language_direction = function() { return "ltr"; };var hm_list_path = function() { return ""; };var hm_list_parent = function() { return ""; };var hm_msg_uid = function() { return Hm_Utils.get_from_global("msg_uid", ""); };var hm_encrypt_ajax_requests = function() { return ""; };var hm_encrypt_local_storage = function() { return ""; };var hm_web_root_path = function() { return ""; };var hm_flag_image_src = function() { return "<i class=\\"bi bi-star-half\\"></i>"; };var hm_check_dirty_flag = function() { return 0; };var hm_data_sources = function() { return []; };var hm_delete_prompt = function() { return true; };', implode($res->output_response));
429+
$this->assertStringStartsWith('<script type="text/javascript" id="data-store">var globals = {};var hm_is_logged = function () { return 0; };var hm_empty_folder = function() { return "So alone"; };var hm_mobile = function() { return 0; };var hm_debug = function() { return "0"; };var hm_mailto = function() { return 0; };var hm_page_name = function() { return ""; };var hm_language_direction = function() { return "ltr"; };var hm_list_path = function() { return ""; };var hm_list_parent = function() { return ""; };var hm_msg_uid = function() { return Hm_Utils.get_from_global("msg_uid", ""); };var hm_encrypt_ajax_requests = function() { return ""; };var hm_encrypt_local_storage = function() { return ""; };var hm_web_root_path = function() { return ""; };var hm_flag_image_src = function() { return "<i class=\\"bi bi-star-half\\"></i>"; };var hm_check_dirty_flag = function() { return 0; };var hm_special_folders = function() { return []; };var hm_data_sources = function() { return []; };var hm_delete_prompt = function() { return true; };', implode($res->output_response));
430430
$test->handler_response = array();
431431
$res = $test->run();
432-
$this->assertStringStartsWith('<script type="text/javascript" id="data-store">var globals = {};var hm_is_logged = function () { return 0; };var hm_empty_folder = function() { return "So alone"; };var hm_mobile = function() { return 0; };var hm_debug = function() { return "0"; };var hm_mailto = function() { return 0; };var hm_page_name = function() { return ""; };var hm_language_direction = function() { return "ltr"; };var hm_list_path = function() { return ""; };var hm_list_parent = function() { return ""; };var hm_msg_uid = function() { return Hm_Utils.get_from_global("msg_uid", ""); };var hm_encrypt_ajax_requests = function() { return ""; };var hm_encrypt_local_storage = function() { return ""; };var hm_web_root_path = function() { return ""; };var hm_flag_image_src = function() { return "<i class=\\"bi bi-star-half\\"></i>"; };var hm_check_dirty_flag = function() { return 0; };var hm_data_sources = function() { return []; };var hm_delete_prompt = function() { return confirm("This action cannot be undone. Are you sure you want to delete this?"); };', implode($res->output_response));
432+
$this->assertStringStartsWith('<script type="text/javascript" id="data-store">var globals = {};var hm_is_logged = function () { return 0; };var hm_empty_folder = function() { return "So alone"; };var hm_mobile = function() { return 0; };var hm_debug = function() { return "0"; };var hm_mailto = function() { return 0; };var hm_page_name = function() { return ""; };var hm_language_direction = function() { return "ltr"; };var hm_list_path = function() { return ""; };var hm_list_parent = function() { return ""; };var hm_msg_uid = function() { return Hm_Utils.get_from_global("msg_uid", ""); };var hm_encrypt_ajax_requests = function() { return ""; };var hm_encrypt_local_storage = function() { return ""; };var hm_web_root_path = function() { return ""; };var hm_flag_image_src = function() { return "<i class=\\"bi bi-star-half\\"></i>"; };var hm_check_dirty_flag = function() { return 0; };var hm_special_folders = function() { return []; };var hm_data_sources = function() { return []; };var hm_delete_prompt = function() { return confirm("This action cannot be undone. Are you sure you want to delete this?"); };', implode($res->output_response));
433433
}
434434
/**
435435
* @preserveGlobalState disabled

0 commit comments

Comments
 (0)