diff --git a/SQL/mysql.initial.sql b/SQL/mysql.initial.sql index 4cc5f1d..57e9d03 100644 --- a/SQL/mysql.initial.sql +++ b/SQL/mysql.initial.sql @@ -46,6 +46,22 @@ CREATE TABLE IF NOT EXISTS `ident_switch` smallint NOT NULL DEFAULT 1, + `notify_check` + smallint + NOT NULL + DEFAULT 1, + `notify_basic` + smallint + DEFAULT NULL, + `notify_sound` + smallint + DEFAULT NULL, + `notify_desktop` + smallint + DEFAULT NULL, + `notify_sound_url` + varchar(255) + DEFAULT NULL, `drafts_mbox` varchar(64), `sent_mbox` diff --git a/SQL/mysql/2026021002.sql b/SQL/mysql/2026021002.sql new file mode 100644 index 0000000..196d232 --- /dev/null +++ b/SQL/mysql/2026021002.sql @@ -0,0 +1,30 @@ +ALTER TABLE `ident_switch` + ADD `notify_check` + smallint + NOT NULL + DEFAULT 1 + AFTER `sieve_auth`; + +ALTER TABLE `ident_switch` + ADD `notify_basic` + smallint + DEFAULT NULL + AFTER `notify_check`; + +ALTER TABLE `ident_switch` + ADD `notify_sound` + smallint + DEFAULT NULL + AFTER `notify_basic`; + +ALTER TABLE `ident_switch` + ADD `notify_desktop` + smallint + DEFAULT NULL + AFTER `notify_sound`; + +ALTER TABLE `ident_switch` + ADD `notify_sound_url` + varchar(255) + DEFAULT NULL + AFTER `notify_desktop`; diff --git a/SQL/postgres.initial.sql b/SQL/postgres.initial.sql index 6e5f36b..6e330ec 100644 --- a/SQL/postgres.initial.sql +++ b/SQL/postgres.initial.sql @@ -47,6 +47,22 @@ CREATE TABLE ident_switch smallint NOT NULL DEFAULT(1), + notify_check + smallint + NOT NULL + DEFAULT(1), + notify_basic + smallint + DEFAULT NULL, + notify_sound + smallint + DEFAULT NULL, + notify_desktop + smallint + DEFAULT NULL, + notify_sound_url + varchar(255) + DEFAULT NULL, drafts_mbox varchar(64), sent_mbox diff --git a/SQL/postgres/2026021002.sql b/SQL/postgres/2026021002.sql new file mode 100644 index 0000000..6a9ddd3 --- /dev/null +++ b/SQL/postgres/2026021002.sql @@ -0,0 +1,25 @@ +ALTER TABLE ident_switch + ADD notify_check + smallint + NOT NULL + DEFAULT 1; + +ALTER TABLE ident_switch + ADD notify_basic + smallint + DEFAULT NULL; + +ALTER TABLE ident_switch + ADD notify_sound + smallint + DEFAULT NULL; + +ALTER TABLE ident_switch + ADD notify_desktop + smallint + DEFAULT NULL; + +ALTER TABLE ident_switch + ADD notify_sound_url + varchar(255) + DEFAULT NULL; diff --git a/SQL/sqlite.initial.sql b/SQL/sqlite.initial.sql index 168c0e1..1e8d9fe 100644 --- a/SQL/sqlite.initial.sql +++ b/SQL/sqlite.initial.sql @@ -47,6 +47,22 @@ CREATE TABLE ident_switch smallint NOT NULL DEFAULT 1, + notify_check + smallint + NOT NULL + DEFAULT 1, + notify_basic + smallint + DEFAULT NULL, + notify_sound + smallint + DEFAULT NULL, + notify_desktop + smallint + DEFAULT NULL, + notify_sound_url + varchar(255) + DEFAULT NULL, drafts_mbox varchar(64), sent_mbox diff --git a/SQL/sqlite/2026021002.sql b/SQL/sqlite/2026021002.sql new file mode 100644 index 0000000..6a9ddd3 --- /dev/null +++ b/SQL/sqlite/2026021002.sql @@ -0,0 +1,25 @@ +ALTER TABLE ident_switch + ADD notify_check + smallint + NOT NULL + DEFAULT 1; + +ALTER TABLE ident_switch + ADD notify_basic + smallint + DEFAULT NULL; + +ALTER TABLE ident_switch + ADD notify_sound + smallint + DEFAULT NULL; + +ALTER TABLE ident_switch + ADD notify_desktop + smallint + DEFAULT NULL; + +ALTER TABLE ident_switch + ADD notify_sound_url + varchar(255) + DEFAULT NULL; diff --git a/config.inc.php.dist b/config.inc.php.dist index 1305586..9ea52bf 100644 --- a/config.inc.php.dist +++ b/config.inc.php.dist @@ -50,3 +50,23 @@ $config['ident_switch.preconfig'] = [ 'user' => 'mbox', ], ]; + +/* + * Enable background mail checking across secondary accounts. + * Shows unread counts in the account switcher and a badge for new messages. + * Default: true. + */ +$config['ident_switch.check_mail'] = true; + +/* + * Round-robin mode: check one identity per refresh cycle instead of all. + * Useful with many accounts to reduce IMAP connections per refresh. + * Default: false (all identities checked every cycle). + */ +$config['ident_switch.round_robin'] = false; + +/* + * Hide the warning shown to users when the newmail_notifier plugin is not active. + * Default: false (warning is displayed). + */ +$config['ident_switch.hide_notifier_warning'] = false; diff --git a/ident_switch-switch.js b/ident_switch-switch.js index b8f9123..3908f54 100644 --- a/ident_switch-switch.js +++ b/ident_switch-switch.js @@ -1,7 +1,12 @@ /** - * ident_switch - Account switcher UI component. + * ident_switch - Account switcher UI and new mail notifications. + * + * Places the hidden onchange). + */ +function plugin_switchIdent_switch(val) { + rcmail.env.unread_counts = {}; + rcmail.http_post('plugin.ident_switch.switch', { + '_ident-id': val, + '_mbox': rcmail.env.mailbox + }); +} + +/** + * Update per-account counts in select options and total badge. + * + * Each entry in data is {unseen: N, baseline: B}. + * - Option text: "account (baseline+delta)" when delta > 0, else "account (unseen)" + * - Badge: sum of deltas across all accounts (new messages not yet visited). + * + * @param {Object} data - Map of iid to {unseen, baseline}. + */ +function ident_switch_updateCounts(data) { + var map = rcmail.env.ident_switch_iid_map || {}; + var $select = $('#plugin-ident_switch-account'); + var totalDelta = 0; + + // Reset all options to original text + $select.find('option').each(function() { + var orig = $(this).data('orig-text'); + if (orig) { + $(this).text(orig); + } + }); + + // On mail task, skip count for the active account (already shown in folder list) + var selectedVal = rcmail.env.task === 'mail' ? $select.val() : null; + + // Update each option with its count + for (var iid in data) { + if (!data.hasOwnProperty(iid)) continue; + var info = data[iid]; + var unseen = parseInt(info.unseen) || 0; + var baseline = parseInt(info.baseline) || 0; + var delta = Math.max(0, unseen - baseline); + + totalDelta += delta; + + if (map[iid] === undefined) continue; + var selectVal = '' + map[iid]; + var $opt = $select.find('option[value="' + selectVal + '"]'); + if (!$opt.length) continue; + + // Skip active account on mail task + if (selectVal === selectedVal) continue; + + var suffix; + if (delta > 0) { + suffix = ' (' + baseline + '+' + delta + ')'; + } else if (unseen > 0) { + suffix = ' (' + unseen + ')'; + } else { + suffix = ''; + } + + if (suffix) { + $opt.text($opt.data('orig-text') + suffix); } } - return false; + // Update total badge (only new messages not yet visited) + var $badge = $('#ident-switch-badge'); + if (totalDelta > 0) { + $badge.text(totalDelta).show(); + } else { + $badge.hide().text(''); + } } -function plugin_switchIdent_addCbClassic($sw) { - var $taskBar = $('#taskbar'); - if ($taskBar.length > 0) { - $taskBar.prepend($sw); - return true; +/** + * Handle new mail notification from server. + * @param {Object} data - {iid, label, count, basic, sound, desktop} + */ +function ident_switch_onNotify(data) { + if (data.basic) { + ident_switch_notifyBasic(); + } + if (data.sound) { + ident_switch_notifySound(); + } + if (data.desktop) { + ident_switch_notifyDesktop(data.label, data.count); + } +} + +/** + * Basic notification: change page title. + */ +function ident_switch_notifyBasic() { + var marker = '(*) '; + if (document.title.indexOf(marker) !== 0) { + document.title = marker + document.title; + } +} + +/** + * Sound notification: play newmail_notifier sound. + */ +function ident_switch_notifySound() { + var src = rcmail.assets_path('plugins/newmail_notifier/sound'); + try { + new Audio(src + '.mp3').play().catch(function() { + new Audio(src + '.wav').play().catch(function() {}); + }); + } catch(e) {} +} + +/** + * Desktop notification via Notification API. + */ +function ident_switch_notifyDesktop(label, count) { + if (!('Notification' in window)) { + return; } - return false; + if (Notification.permission === 'default') { + Notification.requestPermission(); + return; + } + + if (Notification.permission !== 'granted') { + return; + } + + var body = count + ' unread message' + (count > 1 ? 's' : ''); + var popup = new Notification('New mail — ' + label, { + body: body, + tag: 'ident_switch_' + label, + icon: rcmail.assets_path('plugins/ident_switch/mail.png') + }); + + var timeout = (rcmail.env.newmail_notifier_timeout || 10) * 1000; + setTimeout(function() { popup.close(); }, timeout); } -function plugin_switchIdent_addCbElastic($sw) { - var $taskBar = $('.header-title.username'); - $sw.css("background-color", "transparent").css("border","none"); - $sw.css("background-position-x","left 0.75rem").css("padding","0 0 0 2rem"); - $sw.css("font-weight","bold").css("box-shadow","none"); - if ($taskBar.length > 0) { - $taskBar.prepend($sw); - return true; - } - - return false; -} - -function plugin_switchIdent_switch(val) { - rcmail.env.unread_counts = {}; - console.log(rcmail.env.unread_counts); - rcmail.http_post('plugin.ident_switch.switch', { '_ident-id': val, '_mbox': rcmail.env.mailbox }); -} - -function plugin_switchIdent_fixIdent(iid) { - if (parseInt(iid) > 0) - $("#_from").val(iid); +/** + * Fix identity selection in compose view when impersonating. + */ +function plugin_switchIdent_fixIdent(iid) { + if (parseInt(iid) > 0) { + $('#_from').val(iid); + } } diff --git a/ident_switch.css b/ident_switch.css new file mode 100644 index 0000000..a8895e2 --- /dev/null +++ b/ident_switch.css @@ -0,0 +1,32 @@ +/** + * ident_switch - Account switcher styles. + * + * Copyright (C) 2026 Gecka + * + * Licensed under AGPL-3.0+. + * + * @url https://github.com/Gecka-apps/ident_switch + */ + +/* Wrapper around