Merge pull request #90 from wagesj45/codex/add-unread-message-option-to-rules
Add unread-only rule option
This commit is contained in:
commit
23c067e2e7
3 changed files with 34 additions and 13 deletions
|
@ -17,7 +17,7 @@ message meets a specified criterion.
|
|||
- **Markdown conversion** – optionally convert HTML bodies to Markdown before sending them to the AI service.
|
||||
- **Debug logging** – optional colorized logs help troubleshoot interactions with the AI service.
|
||||
- **Light/Dark themes** – automatically match Thunderbird's appearance with optional manual override.
|
||||
- **Automatic rules** – create rules that tag, move, mark read/unread or flag/unflag messages based on AI classification.
|
||||
- **Automatic rules** – create rules that tag, move, mark read/unread or flag/unflag messages based on AI classification. Rules can optionally apply only to unread messages.
|
||||
- **Rule ordering** – drag rules to prioritize them and optionally stop processing after a match.
|
||||
- **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.
|
||||
|
@ -71,7 +71,8 @@ Sortana is implemented entirely with standard WebExtension scripts—no custom e
|
|||
1. Open the add-on's options and set the URL of your classification service.
|
||||
2. Use the **Classification Rules** section to add a criterion and optional
|
||||
actions such as tagging or moving a message when it matches. Drag rules to
|
||||
reorder them and check *Stop after match* to halt further processing.
|
||||
reorder them, check *Only apply to unread messages* to skip read mail, and
|
||||
check *Stop after match* to halt further processing.
|
||||
3. Save your settings. New mail will be evaluated automatically using the
|
||||
configured rules.
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ function normalizeRules(rules) {
|
|||
if (r.moveTo) actions.push({ type: 'move', folder: r.moveTo });
|
||||
const rule = { criterion: r.criterion, actions };
|
||||
if (r.stopProcessing) rule.stopProcessing = true;
|
||||
if (r.unreadOnly) rule.unreadOnly = true;
|
||||
return rule;
|
||||
}) : [];
|
||||
}
|
||||
|
@ -208,14 +209,20 @@ async function processMessage(id) {
|
|||
const full = await messenger.messages.getFull(id);
|
||||
const text = buildEmailText(full);
|
||||
let currentTags = [];
|
||||
let alreadyRead = false;
|
||||
try {
|
||||
const hdr = await messenger.messages.get(id);
|
||||
currentTags = Array.isArray(hdr.tags) ? [...hdr.tags] : [];
|
||||
alreadyRead = hdr.read === true;
|
||||
} catch (e) {
|
||||
currentTags = [];
|
||||
alreadyRead = false;
|
||||
}
|
||||
|
||||
for (const rule of aiRules) {
|
||||
if (rule.unreadOnly && alreadyRead) {
|
||||
continue;
|
||||
}
|
||||
const cacheKey = await AiClassifier.buildCacheKey(id, rule.criterion);
|
||||
const matched = await AiClassifier.classifyText(text, rule.criterion, cacheKey);
|
||||
if (matched) {
|
||||
|
|
|
@ -318,20 +318,30 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
addAction.className = 'button is-small mb-2';
|
||||
addAction.addEventListener('click', () => actionsContainer.appendChild(createActionRow()));
|
||||
|
||||
const stopLabel = document.createElement('label');
|
||||
stopLabel.className = 'checkbox mt-2';
|
||||
const stopCheck = document.createElement('input');
|
||||
stopCheck.type = 'checkbox';
|
||||
stopCheck.className = 'stop-processing';
|
||||
stopCheck.checked = rule.stopProcessing === true;
|
||||
stopLabel.appendChild(stopCheck);
|
||||
stopLabel.append(' Stop after match');
|
||||
const stopLabel = document.createElement('label');
|
||||
stopLabel.className = 'checkbox mt-2';
|
||||
const stopCheck = document.createElement('input');
|
||||
stopCheck.type = 'checkbox';
|
||||
stopCheck.className = 'stop-processing';
|
||||
stopCheck.checked = rule.stopProcessing === true;
|
||||
stopLabel.appendChild(stopCheck);
|
||||
stopLabel.append(' Stop after match');
|
||||
|
||||
const unreadLabel = document.createElement('label');
|
||||
unreadLabel.className = 'checkbox mt-2 ml-4';
|
||||
const unreadCheck = document.createElement('input');
|
||||
unreadCheck.type = 'checkbox';
|
||||
unreadCheck.className = 'unread-only';
|
||||
unreadCheck.checked = rule.unreadOnly === true;
|
||||
unreadLabel.appendChild(unreadCheck);
|
||||
unreadLabel.append(' Only apply to unread messages');
|
||||
|
||||
const body = document.createElement('div');
|
||||
body.className = 'message-body';
|
||||
body.appendChild(actionsContainer);
|
||||
body.appendChild(addAction);
|
||||
body.appendChild(stopLabel);
|
||||
body.appendChild(unreadLabel);
|
||||
|
||||
article.appendChild(header);
|
||||
article.appendChild(body);
|
||||
|
@ -363,9 +373,10 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
return { type };
|
||||
});
|
||||
const stopProcessing = ruleEl.querySelector('.stop-processing')?.checked;
|
||||
return { criterion, actions, stopProcessing };
|
||||
const unreadOnly = ruleEl.querySelector('.unread-only')?.checked;
|
||||
return { criterion, actions, unreadOnly, stopProcessing };
|
||||
});
|
||||
data.push({ criterion: '', actions: [], stopProcessing: false });
|
||||
data.push({ criterion: '', actions: [], unreadOnly: false, stopProcessing: false });
|
||||
renderRules(data);
|
||||
});
|
||||
|
||||
|
@ -376,6 +387,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
if (r.moveTo) actions.push({ type: 'move', folder: r.moveTo });
|
||||
const rule = { criterion: r.criterion, actions };
|
||||
if (r.stopProcessing) rule.stopProcessing = true;
|
||||
if (r.unreadOnly) rule.unreadOnly = true;
|
||||
return rule;
|
||||
}));
|
||||
|
||||
|
@ -509,7 +521,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
return { type };
|
||||
});
|
||||
const stopProcessing = ruleEl.querySelector('.stop-processing')?.checked;
|
||||
return { criterion, actions, stopProcessing };
|
||||
const unreadOnly = ruleEl.querySelector('.unread-only')?.checked;
|
||||
return { criterion, actions, unreadOnly, stopProcessing };
|
||||
}).filter(r => r.criterion);
|
||||
const stripUrlParams = stripUrlToggle.checked;
|
||||
const altTextImages = altTextToggle.checked;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue