diff --git a/modules/core/js_modules/utils/loaders.js b/modules/core/js_modules/utils/loaders.js new file mode 100644 index 0000000000..c7eacf07c9 --- /dev/null +++ b/modules/core/js_modules/utils/loaders.js @@ -0,0 +1,22 @@ +function showLoaderToast(text = 'Loading...') { + const uniqueId = Math.random().toString(36).substring(7); + const toastHTML = ` +
+ +
+ ` + + document.body.insertAdjacentHTML('beforeend', toastHTML) + + const instance = bootstrap.Toast.getOrCreateInstance(document.getElementById(uniqueId)); + instance.show(); + + return instance; +} \ No newline at end of file diff --git a/modules/core/navigation/navigation.js b/modules/core/navigation/navigation.js index d07533057c..d5532bcc52 100644 --- a/modules/core/navigation/navigation.js +++ b/modules/core/navigation/navigation.js @@ -54,7 +54,9 @@ async function navigate(url) { const html = await response.text(); const main = html.match(/]*>((.|[\n\r])*)<\/main>/i)[0]; + const title = html.match(/]*>((.|[\n\r])*)<\/title>/i)[0]; $('main').replaceWith(main); + document.title = title.replace(/<[^>]*>/g, ''); window.location.next = url; diff --git a/modules/core/navigation/utils.js b/modules/core/navigation/utils.js index 30781d63b3..fb15f57418 100644 --- a/modules/core/navigation/utils.js +++ b/modules/core/navigation/utils.js @@ -1,23 +1,9 @@ -const toastHTML = ` -
- -
-` -document.body.insertAdjacentHTML('beforeend', toastHTML) - function showRoutingToast() { - bootstrap.Toast.getOrCreateInstance(document.getElementById('routing-toast')).show() + window.routingToast = showLoaderToast('Redirecting...'); } function hideRoutingToast() { - bootstrap.Toast.getOrCreateInstance(document.getElementById('routing-toast')).hide() + window.routingToast.hide(); } function getListPathParam() { diff --git a/modules/core/site.css b/modules/core/site.css index 9cd5d27de7..1eba77fca2 100644 --- a/modules/core/site.css +++ b/modules/core/site.css @@ -10,7 +10,6 @@ body { .compose_page, .message_list, .msg_text, -.selected_part, .server_content, .profile_content, .user_settings, diff --git a/modules/core/site.js b/modules/core/site.js index e3be6dc3dc..e914aff6de 100644 --- a/modules/core/site.js +++ b/modules/core/site.js @@ -917,27 +917,27 @@ function Message_List() { }; /* TODO: remove module specific refs */ - this.update_title = function() { + this.update_title = function(list_path = getListPathParam()) { var count = 0; var rows = Hm_Utils.rows(); var tbody = Hm_Utils.tbody(); - if (getListPathParam() == 'unread') { + if (list_path == 'unread') { count = rows.length; document.title = count+' '+hm_trans('Unread'); } - else if (getListPathParam() == 'flagged') { + else if (list_path == 'flagged') { count = rows.length; document.title = count+' '+hm_trans('Flagged'); } - else if (getListPathParam() == 'combined_inbox') { + else if (list_path == 'combined_inbox') { count = $('tr .unseen', tbody).length; document.title = count+' '+hm_trans('Unread in Everything'); } - else if (getListPathParam() == 'email') { + else if (list_path == 'email') { count = $('tr .unseen', tbody).length; document.title = count+' '+hm_trans('Unread in Email'); } - else if (getListPathParam() == 'feeds') { + else if (list_path == 'feeds') { count = $('tr .unseen', tbody).length; document.title = count+' '+hm_trans('Unread in Feeds'); } diff --git a/modules/imap/functions.php b/modules/imap/functions.php index e68c0156a0..13bef44782 100644 --- a/modules/imap/functions.php +++ b/modules/imap/functions.php @@ -468,7 +468,7 @@ function format_msg_part_row($id, $vals, $output_mod, $level, $part, $dl_args, $ if ($mobile) { $res .= '
'.$output_mod->html_safe($size); $res .= '
'.$output_mod->html_safe(decode_fld($desc)).'
'; - $res .= ''; + $res .= ''; } else { $res .= ''.$output_mod->html_safe($size); @@ -477,10 +477,10 @@ function format_msg_part_row($id, $vals, $output_mod, $level, $part, $dl_args, $ ''.(isset($vals['attributes']['charset']) && trim($vals['attributes']['charset']) ? $output_mod->html_safe(mb_strtolower($vals['attributes']['charset'])) : ''); } $res .= ''.$output_mod->html_safe(decode_fld($desc)).''; - $res .= ''.$output_mod->trans('Download').''; + $res .= ''.$output_mod->trans('Download').''; } if ($output_mod->get('allow_delete_attachment') && isset($vals['file_attributes']['attachment'])) { - $res .= ''.$output_mod->trans('Remove').''; + $res .= ''.$output_mod->trans('Remove').''; } $res .= ''; return $res; @@ -641,7 +641,7 @@ function format_attachment($struct, $output_mod, $part, $dl_args, $at_args) { $res .= ''.$output_mod->html_safe(decode_fld($desc)).''; $res .= ''.$output_mod->html_safe($size).''; - $res .= ''.$output_mod->trans('Download').''; + $res .= ''.$output_mod->trans('Download').''; if ($output_mod->get('allow_delete_attachment') && isset($vals['file_attributes']['attachment'])) { $res .= ''.$output_mod->trans('Remove').''; } diff --git a/modules/imap/js_modules/route_handlers.js b/modules/imap/js_modules/route_handlers.js index a449a53e66..3908bc9b89 100644 --- a/modules/imap/js_modules/route_handlers.js +++ b/modules/imap/js_modules/route_handlers.js @@ -6,6 +6,17 @@ function applyImapMessageListPageHandlers(routeParams) { imap_setup_snooze(); imap_setup_tags(); + Hm_Message_List.set_row_events(); + + $('.core_msg_control').on("click", function(e) { + e.preventDefault(); + Hm_Message_List.message_action($(this).data('action')); + }); + $('.toggle_link').on("click", function(e) { + e.preventDefault(); + Hm_Message_List.toggle_rows(); + }); + if (window.githubMessageListPageHandler) githubMessageListPageHandler(routeParams); if (window.inlineMessageMessageListAndSearchPageHandler) inlineMessageMessageListAndSearchPageHandler(routeParams); if (window.wpMessageListPageHandler) wpMessageListPageHandler(routeParams); diff --git a/modules/imap/js_modules/utils/attachements.js b/modules/imap/js_modules/utils/attachements.js new file mode 100644 index 0000000000..bfa99d1a19 --- /dev/null +++ b/modules/imap/js_modules/utils/attachements.js @@ -0,0 +1,21 @@ +function handleAttachementDownload() { + $('.download_link a').on("click", async function(e) { + e.preventDefault(); + const loaderInstance = showLoaderToast("Downloading attachment..."); + const href = $(this).data('src'); + try { + await fetch(href).then(res => res.blob()).then(blob => { + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'attachment'; + a.click(); + URL.revokeObjectURL(url); + }); + } catch (error) { + Hm_Notices.show([`ERR${error.message}`]); + } finally { + loaderInstance.hide(); + } + }); +} \ No newline at end of file diff --git a/modules/imap/js_modules/utils/messageParts.js b/modules/imap/js_modules/utils/messageParts.js new file mode 100644 index 0000000000..1e8578a151 --- /dev/null +++ b/modules/imap/js_modules/utils/messageParts.js @@ -0,0 +1,7 @@ +function handleViewMessagePart() { + $('.msg_part_link').on("click", function(e) { + e.preventDefault(); + const messagePart = $(this).data('messagePart'); + get_message_content(messagePart, getMessageUidParam() ?? inline_msg_uid, getListPathParam()); + }); +} \ No newline at end of file diff --git a/modules/imap/site.js b/modules/imap/site.js index b6d888f091..5d0ce57b28 100644 --- a/modules/imap/site.js +++ b/modules/imap/site.js @@ -467,6 +467,9 @@ var setup_imap_folder_page = async function(listPath) { await select_imap_folder(listPath, true, true) } + // Update browser title + Hm_Message_List.update_title(listPath); + // Refresh in the background each 30 seconds and abort any pending request when the page unmounts const backgroundAbortController = new AbortController(); const interval = setInterval(async () => { @@ -684,7 +687,7 @@ var get_message_content = function(msg_part, uid, list_path, detail, callback, n {'name': 'folder', 'value': detail.folder}], function(res) { onSuccess(res); - if (!noupdate) { + if (!noupdate && !msg_part) { Hm_Utils.save_to_local_storage(getMessageStorageKey(uid), JSON.stringify(res)); } }, @@ -846,6 +849,9 @@ var imap_message_view_finished = function(msg_uid, detail, skip_links) { return block_unblock_sender(msg_uid, detail, $(this).data('target'), 'unblock', sender); }); fixLtrInRtl(); + + handleAttachementDownload(); + handleViewMessagePart(); }; var get_local_message_content = function(msg_uid, path) { diff --git a/modules/inline_message/site.js b/modules/inline_message/site.js index 682034816f..a412581c22 100644 --- a/modules/inline_message/site.js +++ b/modules/inline_message/site.js @@ -94,7 +94,6 @@ var get_inline_msg_details = function(link) { }; var msg_inline_close = function() { - $('.refresh_link').trigger('click'); if (inline_msg_style() == 'right') { $('.msg_text').remove(); $('.message_table').css('width', '100%'); @@ -126,7 +125,6 @@ var capture_subject_click = function() { var inline_msg_loaded_callback = function() { $('.header_subject th').append(''); $('.close_inline_msg').on("click", function() { msg_inline_close(); }); - $('.msg_part_link').on("click", function() { return get_message_content($(this).data('messagePart'), uid, list_path, details, inline_msg_loaded_callback, false, $(this).data('allowImages')); }); update_imap_links(uid, details); }; diff --git a/scripts/config_gen.php b/scripts/config_gen.php index 1fb595ad7b..b4c98770cc 100644 --- a/scripts/config_gen.php +++ b/scripts/config_gen.php @@ -188,7 +188,8 @@ function get_module_assignments($settings) { $js .= file_get_contents($js_module); } } - foreach (glob('modules' . DIRECTORY_SEPARATOR . $mod . DIRECTORY_SEPARATOR . 'js_modules' . DIRECTORY_SEPARATOR . '*.js') as $js_module) { + $directoriesPattern = str_replace('/', DIRECTORY_SEPARATOR, "{*,*/*}"); + foreach (glob('modules' . DIRECTORY_SEPARATOR . $mod . DIRECTORY_SEPARATOR . 'js_modules' . DIRECTORY_SEPARATOR . $directoriesPattern . '*.js', GLOB_BRACE) as $js_module) { $js .= file_get_contents($js_module); } if (is_readable(sprintf("modules/%s/site.js", $mod))) { diff --git a/tests/selenium/folder_list.py b/tests/selenium/folder_list.py index ccd03a63fc..e020a6d30d 100644 --- a/tests/selenium/folder_list.py +++ b/tests/selenium/folder_list.py @@ -53,7 +53,7 @@ def hide_folders(self): self.driver.execute_script("arguments[0].click();", hide_button) assert self.by_class('folder_toggle').text.startswith('Show folders') list_item = self.by_class('menu_home') - link = list_item.find_element(By.TAG_NAME, 'a'); + link = list_item.find_element(By.TAG_NAME, 'a') assert link.is_displayed() == False def show_folders(self): diff --git a/tests/selenium/pages.py b/tests/selenium/pages.py index b42fc5061d..a5663315a1 100644 --- a/tests/selenium/pages.py +++ b/tests/selenium/pages.py @@ -126,7 +126,7 @@ def folders(self): list_item.find_element(By.TAG_NAME, 'a').click() self.wait_with_folder_list() self.safari_workaround() - self.wait(By.CLASS_NAME, 'content_title') + self.wait_for_navigation_to_complete() assert self.by_class('content_title').text == 'Folders' def save(self): @@ -156,6 +156,7 @@ def profiles(self): list_item.find_element(By.TAG_NAME, 'a').click() self.wait_with_folder_list() self.safari_workaround() + self.wait_for_navigation_to_complete() assert self.by_class('profile_content_title').text == 'Profiles' if __name__ == '__main__': diff --git a/tests/selenium/settings.py b/tests/selenium/settings.py index 4029b72eff..0a8f20c008 100644 --- a/tests/selenium/settings.py +++ b/tests/selenium/settings.py @@ -84,6 +84,7 @@ def __init__(self): self.wait() def load_settings_page(self): + self.wait_on_class('main_menu') self.by_css('[data-source=".settings"]').click() list_item = self.by_class('menu_settings') list_item.find_element(By.TAG_NAME, 'a').click()