diff --git a/README.md b/README.md index adebc99..60cbe13 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ message meets a specified criterion. - **Light/Dark themes** – automatically match Thunderbird's appearance with optional manual override. - **Automatic rules** – create rules that tag, move, copy, forward, reply, delete, archive, mark read/unread or flag/unflag messages based on AI classification. Rules can optionally apply only to unread messages and can ignore messages outside a chosen age range. - **Rule ordering** – drag rules to prioritize them and optionally stop processing after a match. +- **Rule enable/disable** – temporarily turn a rule off without removing it. - **Account & folder filters** – limit rules to specific accounts or folders. - **Context menu** – apply AI rules from the message list or the message display action button. - **Status icons** – toolbar icons show when classification is in progress and briefly display success or error states. @@ -75,8 +76,8 @@ Sortana is implemented entirely with standard WebExtension scripts—no custom e deleting or archiving a message when it matches. Drag rules to reorder them, check *Only apply to unread messages* to skip read mail, set optional minimum or maximum message age limits, select the accounts or - folders a rule should apply to, and - check *Stop after match* to halt further processing. Forward and reply actions + folders a rule should apply to, uncheck *Enabled* to temporarily disable a rule, and + check *Stop after match* to halt further processing. Forward and reply actions open a compose window using the account that received the message. 3. Save your settings. New mail will be evaluated automatically using the configured rules. diff --git a/background.js b/background.js index fb820bb..b0db614 100644 --- a/background.js +++ b/background.js @@ -36,6 +36,7 @@ function normalizeRules(rules) { if (r.actions) { if (!Array.isArray(r.accounts)) r.accounts = []; if (!Array.isArray(r.folders)) r.folders = []; + r.enabled = r.enabled !== false; return r; } const actions = []; @@ -49,6 +50,7 @@ function normalizeRules(rules) { if (typeof r.maxAgeDays === 'number') rule.maxAgeDays = r.maxAgeDays; if (Array.isArray(r.accounts)) rule.accounts = r.accounts; if (Array.isArray(r.folders)) rule.folders = r.folders; + rule.enabled = r.enabled !== false; return rule; }) : []; } @@ -234,6 +236,9 @@ async function processMessage(id) { } for (const rule of aiRules) { + if (rule.enabled === false) { + continue; + } if (hdr && Array.isArray(rule.accounts) && rule.accounts.length && !rule.accounts.includes(hdr.folder.accountId)) { continue; diff --git a/options/options.js b/options/options.js index 0042004..3d02639 100644 --- a/options/options.js +++ b/options/options.js @@ -326,6 +326,16 @@ document.addEventListener('DOMContentLoaded', async () => { }); header.appendChild(delBtn); + const enabledLabel = document.createElement('label'); + enabledLabel.className = 'checkbox ml-2'; + const enabledCheck = document.createElement('input'); + enabledCheck.type = 'checkbox'; + enabledCheck.className = 'rule-enabled'; + enabledCheck.checked = rule.enabled !== false; + enabledLabel.appendChild(enabledCheck); + enabledLabel.append(' Enabled'); + header.appendChild(enabledLabel); + const actionsContainer = document.createElement('div'); actionsContainer.className = 'rule-actions mb-2'; @@ -469,18 +479,19 @@ document.addEventListener('DOMContentLoaded', async () => { }); const stopProcessing = ruleEl.querySelector('.stop-processing')?.checked; const unreadOnly = ruleEl.querySelector('.unread-only')?.checked; + const enabled = ruleEl.querySelector('.rule-enabled')?.checked !== false; const minAgeDays = parseFloat(ruleEl.querySelector('.min-age')?.value); const maxAgeDays = parseFloat(ruleEl.querySelector('.max-age')?.value); const accounts = [...(ruleEl.querySelector('.account-select')?.selectedOptions || [])].map(o => o.value); const folders = [...(ruleEl.querySelector('.folder-filter-select')?.selectedOptions || [])].map(o => o.value); - const rule = { criterion, actions, unreadOnly, stopProcessing }; + const rule = { criterion, actions, unreadOnly, stopProcessing, enabled }; if (!isNaN(minAgeDays)) rule.minAgeDays = minAgeDays; if (!isNaN(maxAgeDays)) rule.maxAgeDays = maxAgeDays; if (accounts.length) rule.accounts = accounts; if (folders.length) rule.folders = folders; return rule; }); - data.push({ criterion: '', actions: [], unreadOnly: false, stopProcessing: false, accounts: [], folders: [] }); + data.push({ criterion: '', actions: [], unreadOnly: false, stopProcessing: false, enabled: true, accounts: [], folders: [] }); renderRules(data); }); @@ -488,6 +499,7 @@ document.addEventListener('DOMContentLoaded', async () => { if (r.actions) { if (!Array.isArray(r.accounts)) r.accounts = []; if (!Array.isArray(r.folders)) r.folders = []; + if (r.enabled !== false) r.enabled = true; else r.enabled = false; return r; } const actions = []; @@ -501,6 +513,7 @@ document.addEventListener('DOMContentLoaded', async () => { if (typeof r.maxAgeDays === 'number') rule.maxAgeDays = r.maxAgeDays; if (Array.isArray(r.accounts)) rule.accounts = r.accounts; if (Array.isArray(r.folders)) rule.folders = r.folders; + rule.enabled = r.enabled !== false; return rule; })); @@ -644,11 +657,12 @@ document.addEventListener('DOMContentLoaded', async () => { }); const stopProcessing = ruleEl.querySelector('.stop-processing')?.checked; const unreadOnly = ruleEl.querySelector('.unread-only')?.checked; + const enabled = ruleEl.querySelector('.rule-enabled')?.checked !== false; const minAgeDays = parseFloat(ruleEl.querySelector('.min-age')?.value); const maxAgeDays = parseFloat(ruleEl.querySelector('.max-age')?.value); const accounts = [...(ruleEl.querySelector('.account-select')?.selectedOptions || [])].map(o => o.value); const folders = [...(ruleEl.querySelector('.folder-filter-select')?.selectedOptions || [])].map(o => o.value); - const rule = { criterion, actions, unreadOnly, stopProcessing }; + const rule = { criterion, actions, unreadOnly, stopProcessing, enabled }; if (!isNaN(minAgeDays)) rule.minAgeDays = minAgeDays; if (!isNaN(maxAgeDays)) rule.maxAgeDays = maxAgeDays; if (accounts.length) rule.accounts = accounts;