Merge pull request #96 from wagesj45/codex/add-enabled-checkbox-to-rules-ui

Add enabled toggle for rules
This commit is contained in:
Jordan Wages 2025-07-15 23:08:37 -05:00 committed by GitHub
commit 39ffa288fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 25 additions and 5 deletions

View file

@ -19,6 +19,7 @@ message meets a specified criterion.
- **Light/Dark themes** automatically match Thunderbird's appearance with optional manual override. - **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. - **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 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. - **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. - **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. - **Status icons** toolbar icons show when classification is in progress and briefly display success or error states.
@ -75,7 +76,7 @@ Sortana is implemented entirely with standard WebExtension scripts—no custom e
deleting or archiving a message when it matches. Drag rules to deleting or archiving a message when it matches. Drag rules to
reorder them, check *Only apply to unread messages* to skip read mail, reorder them, check *Only apply to unread messages* to skip read mail,
set optional minimum or maximum message age limits, select the accounts or set optional minimum or maximum message age limits, select the accounts or
folders a rule should apply to, and 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 check *Stop after match* to halt further processing. Forward and reply actions
open a compose window using the account that received the message. open a compose window using the account that received the message.
3. Save your settings. New mail will be evaluated automatically using the 3. Save your settings. New mail will be evaluated automatically using the

View file

@ -36,6 +36,7 @@ function normalizeRules(rules) {
if (r.actions) { if (r.actions) {
if (!Array.isArray(r.accounts)) r.accounts = []; if (!Array.isArray(r.accounts)) r.accounts = [];
if (!Array.isArray(r.folders)) r.folders = []; if (!Array.isArray(r.folders)) r.folders = [];
r.enabled = r.enabled !== false;
return r; return r;
} }
const actions = []; const actions = [];
@ -49,6 +50,7 @@ function normalizeRules(rules) {
if (typeof r.maxAgeDays === 'number') rule.maxAgeDays = r.maxAgeDays; if (typeof r.maxAgeDays === 'number') rule.maxAgeDays = r.maxAgeDays;
if (Array.isArray(r.accounts)) rule.accounts = r.accounts; if (Array.isArray(r.accounts)) rule.accounts = r.accounts;
if (Array.isArray(r.folders)) rule.folders = r.folders; if (Array.isArray(r.folders)) rule.folders = r.folders;
rule.enabled = r.enabled !== false;
return rule; return rule;
}) : []; }) : [];
} }
@ -234,6 +236,9 @@ async function processMessage(id) {
} }
for (const rule of aiRules) { for (const rule of aiRules) {
if (rule.enabled === false) {
continue;
}
if (hdr && Array.isArray(rule.accounts) && rule.accounts.length && if (hdr && Array.isArray(rule.accounts) && rule.accounts.length &&
!rule.accounts.includes(hdr.folder.accountId)) { !rule.accounts.includes(hdr.folder.accountId)) {
continue; continue;

View file

@ -326,6 +326,16 @@ document.addEventListener('DOMContentLoaded', async () => {
}); });
header.appendChild(delBtn); 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'); const actionsContainer = document.createElement('div');
actionsContainer.className = 'rule-actions mb-2'; actionsContainer.className = 'rule-actions mb-2';
@ -469,18 +479,19 @@ document.addEventListener('DOMContentLoaded', async () => {
}); });
const stopProcessing = ruleEl.querySelector('.stop-processing')?.checked; const stopProcessing = ruleEl.querySelector('.stop-processing')?.checked;
const unreadOnly = ruleEl.querySelector('.unread-only')?.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 minAgeDays = parseFloat(ruleEl.querySelector('.min-age')?.value);
const maxAgeDays = parseFloat(ruleEl.querySelector('.max-age')?.value); const maxAgeDays = parseFloat(ruleEl.querySelector('.max-age')?.value);
const accounts = [...(ruleEl.querySelector('.account-select')?.selectedOptions || [])].map(o => o.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 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(minAgeDays)) rule.minAgeDays = minAgeDays;
if (!isNaN(maxAgeDays)) rule.maxAgeDays = maxAgeDays; if (!isNaN(maxAgeDays)) rule.maxAgeDays = maxAgeDays;
if (accounts.length) rule.accounts = accounts; if (accounts.length) rule.accounts = accounts;
if (folders.length) rule.folders = folders; if (folders.length) rule.folders = folders;
return rule; 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); renderRules(data);
}); });
@ -488,6 +499,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (r.actions) { if (r.actions) {
if (!Array.isArray(r.accounts)) r.accounts = []; if (!Array.isArray(r.accounts)) r.accounts = [];
if (!Array.isArray(r.folders)) r.folders = []; if (!Array.isArray(r.folders)) r.folders = [];
if (r.enabled !== false) r.enabled = true; else r.enabled = false;
return r; return r;
} }
const actions = []; const actions = [];
@ -501,6 +513,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (typeof r.maxAgeDays === 'number') rule.maxAgeDays = r.maxAgeDays; if (typeof r.maxAgeDays === 'number') rule.maxAgeDays = r.maxAgeDays;
if (Array.isArray(r.accounts)) rule.accounts = r.accounts; if (Array.isArray(r.accounts)) rule.accounts = r.accounts;
if (Array.isArray(r.folders)) rule.folders = r.folders; if (Array.isArray(r.folders)) rule.folders = r.folders;
rule.enabled = r.enabled !== false;
return rule; return rule;
})); }));
@ -644,11 +657,12 @@ document.addEventListener('DOMContentLoaded', async () => {
}); });
const stopProcessing = ruleEl.querySelector('.stop-processing')?.checked; const stopProcessing = ruleEl.querySelector('.stop-processing')?.checked;
const unreadOnly = ruleEl.querySelector('.unread-only')?.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 minAgeDays = parseFloat(ruleEl.querySelector('.min-age')?.value);
const maxAgeDays = parseFloat(ruleEl.querySelector('.max-age')?.value); const maxAgeDays = parseFloat(ruleEl.querySelector('.max-age')?.value);
const accounts = [...(ruleEl.querySelector('.account-select')?.selectedOptions || [])].map(o => o.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 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(minAgeDays)) rule.minAgeDays = minAgeDays;
if (!isNaN(maxAgeDays)) rule.maxAgeDays = maxAgeDays; if (!isNaN(maxAgeDays)) rule.maxAgeDays = maxAgeDays;
if (accounts.length) rule.accounts = accounts; if (accounts.length) rule.accounts = accounts;