Merge pull request #91 from wagesj45/codex/add-copy-option-to-action-selector
Enable copy rule action
This commit is contained in:
commit
d992ad9c55
4 changed files with 18 additions and 7 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.
|
- **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.
|
- **Debug logging** – optional colorized logs help troubleshoot interactions with the AI service.
|
||||||
- **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, mark read/unread or flag/unflag messages based on AI classification. Rules can optionally apply only to unread messages.
|
- **Automatic rules** – create rules that tag, move, copy, 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.
|
- **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.
|
- **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.
|
||||||
|
@ -102,7 +102,7 @@ Here are some useful and fun example criteria you can use in your filters. Filte
|
||||||
For when you're ready to filter based on vibes.
|
For when you're ready to filter based on vibes.
|
||||||
|
|
||||||
You can define as many filters as you'd like, each using a different prompt and
|
You can define as many filters as you'd like, each using a different prompt and
|
||||||
triggering tags, moves, read/unread changes or flag updates based on the model's classification.
|
triggering tags, moves, copies, read/unread changes or flag updates based on the model's classification.
|
||||||
|
|
||||||
## Required Permissions
|
## Required Permissions
|
||||||
|
|
||||||
|
@ -110,10 +110,10 @@ Sortana requests the following Thunderbird permissions:
|
||||||
|
|
||||||
- `storage` – store configuration and cached classification results.
|
- `storage` – store configuration and cached classification results.
|
||||||
- `messagesRead` – read message contents for classification.
|
- `messagesRead` – read message contents for classification.
|
||||||
- `messagesMove` – move messages when a rule specifies a target folder.
|
- `messagesMove` – move or copy messages when a rule specifies a target folder.
|
||||||
- `messagesUpdate` – change message properties such as tags, junk status, read/unread state and flags.
|
- `messagesUpdate` – change message properties such as tags, junk status, read/unread state and flags.
|
||||||
- `messagesTagsList` – retrieve existing message tags for rule actions.
|
- `messagesTagsList` – retrieve existing message tags for rule actions.
|
||||||
- `accountsRead` – list accounts and folders for move actions.
|
- `accountsRead` – list accounts and folders for move or copy actions.
|
||||||
- `menus` – add context menu commands.
|
- `menus` – add context menu commands.
|
||||||
- `tabs` – open new tabs and query the active tab.
|
- `tabs` – open new tabs and query the active tab.
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"options.collapseWhitespace": { "message": "Collapse long whitespace" }
|
"options.collapseWhitespace": { "message": "Collapse long whitespace" }
|
||||||
,"action.read": { "message": "read" }
|
,"action.read": { "message": "read" }
|
||||||
,"action.flag": { "message": "flag" }
|
,"action.flag": { "message": "flag" }
|
||||||
|
,"action.copy": { "message": "copy" }
|
||||||
,"param.markRead": { "message": "mark read" }
|
,"param.markRead": { "message": "mark read" }
|
||||||
,"param.markUnread": { "message": "mark unread" }
|
,"param.markUnread": { "message": "mark unread" }
|
||||||
,"param.flag": { "message": "flag" }
|
,"param.flag": { "message": "flag" }
|
||||||
|
|
|
@ -37,6 +37,7 @@ function normalizeRules(rules) {
|
||||||
const actions = [];
|
const actions = [];
|
||||||
if (r.tag) actions.push({ type: 'tag', tagKey: r.tag });
|
if (r.tag) actions.push({ type: 'tag', tagKey: r.tag });
|
||||||
if (r.moveTo) actions.push({ type: 'move', folder: r.moveTo });
|
if (r.moveTo) actions.push({ type: 'move', folder: r.moveTo });
|
||||||
|
if (r.copyTarget || r.copyTo) actions.push({ type: 'copy', copyTarget: r.copyTarget || r.copyTo });
|
||||||
const rule = { criterion: r.criterion, actions };
|
const rule = { criterion: r.criterion, actions };
|
||||||
if (r.stopProcessing) rule.stopProcessing = true;
|
if (r.stopProcessing) rule.stopProcessing = true;
|
||||||
if (r.unreadOnly) rule.unreadOnly = true;
|
if (r.unreadOnly) rule.unreadOnly = true;
|
||||||
|
@ -234,6 +235,8 @@ async function processMessage(id) {
|
||||||
}
|
}
|
||||||
} else if (act.type === 'move' && act.folder) {
|
} else if (act.type === 'move' && act.folder) {
|
||||||
await messenger.messages.move([id], act.folder);
|
await messenger.messages.move([id], act.folder);
|
||||||
|
} else if (act.type === 'copy' && act.copyTarget) {
|
||||||
|
await messenger.messages.copy([id], act.copyTarget);
|
||||||
} else if (act.type === 'junk') {
|
} else if (act.type === 'junk') {
|
||||||
await messenger.messages.update(id, { junk: !!act.junk });
|
await messenger.messages.update(id, { junk: !!act.junk });
|
||||||
} else if (act.type === 'read') {
|
} else if (act.type === 'read') {
|
||||||
|
|
|
@ -171,7 +171,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const typeWrapper = document.createElement('div');
|
const typeWrapper = document.createElement('div');
|
||||||
typeWrapper.className = 'select is-small mr-2';
|
typeWrapper.className = 'select is-small mr-2';
|
||||||
const typeSelect = document.createElement('select');
|
const typeSelect = document.createElement('select');
|
||||||
['tag','move','junk','read','flag'].forEach(t => {
|
['tag','move','copy','junk','read','flag'].forEach(t => {
|
||||||
const opt = document.createElement('option');
|
const opt = document.createElement('option');
|
||||||
opt.value = t;
|
opt.value = t;
|
||||||
opt.textContent = t;
|
opt.textContent = t;
|
||||||
|
@ -198,7 +198,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
sel.value = action.tagKey || '';
|
sel.value = action.tagKey || '';
|
||||||
wrap.appendChild(sel);
|
wrap.appendChild(sel);
|
||||||
paramSpan.appendChild(wrap);
|
paramSpan.appendChild(wrap);
|
||||||
} else if (typeSelect.value === 'move') {
|
} else if (typeSelect.value === 'move' || typeSelect.value === 'copy') {
|
||||||
const wrap = document.createElement('div');
|
const wrap = document.createElement('div');
|
||||||
wrap.className = 'select is-small';
|
wrap.className = 'select is-small';
|
||||||
const sel = document.createElement('select');
|
const sel = document.createElement('select');
|
||||||
|
@ -209,7 +209,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
opt.textContent = f.name;
|
opt.textContent = f.name;
|
||||||
sel.appendChild(opt);
|
sel.appendChild(opt);
|
||||||
}
|
}
|
||||||
sel.value = action.folder || '';
|
sel.value = action.folder || action.copyTarget || '';
|
||||||
wrap.appendChild(sel);
|
wrap.appendChild(sel);
|
||||||
paramSpan.appendChild(wrap);
|
paramSpan.appendChild(wrap);
|
||||||
} else if (typeSelect.value === 'junk') {
|
} else if (typeSelect.value === 'junk') {
|
||||||
|
@ -361,6 +361,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
if (type === 'move') {
|
if (type === 'move') {
|
||||||
return { type, folder: row.querySelector('.folder-select').value };
|
return { type, folder: row.querySelector('.folder-select').value };
|
||||||
}
|
}
|
||||||
|
if (type === 'copy') {
|
||||||
|
return { type, copyTarget: row.querySelector('.folder-select').value };
|
||||||
|
}
|
||||||
if (type === 'junk') {
|
if (type === 'junk') {
|
||||||
return { type, junk: row.querySelector('.junk-select').value === 'true' };
|
return { type, junk: row.querySelector('.junk-select').value === 'true' };
|
||||||
}
|
}
|
||||||
|
@ -385,6 +388,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const actions = [];
|
const actions = [];
|
||||||
if (r.tag) actions.push({ type: 'tag', tagKey: r.tag });
|
if (r.tag) actions.push({ type: 'tag', tagKey: r.tag });
|
||||||
if (r.moveTo) actions.push({ type: 'move', folder: r.moveTo });
|
if (r.moveTo) actions.push({ type: 'move', folder: r.moveTo });
|
||||||
|
if (r.copyTarget || r.copyTo) actions.push({ type: 'copy', copyTarget: r.copyTarget || r.copyTo });
|
||||||
const rule = { criterion: r.criterion, actions };
|
const rule = { criterion: r.criterion, actions };
|
||||||
if (r.stopProcessing) rule.stopProcessing = true;
|
if (r.stopProcessing) rule.stopProcessing = true;
|
||||||
if (r.unreadOnly) rule.unreadOnly = true;
|
if (r.unreadOnly) rule.unreadOnly = true;
|
||||||
|
@ -509,6 +513,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
if (type === 'move') {
|
if (type === 'move') {
|
||||||
return { type, folder: row.querySelector('.folder-select').value };
|
return { type, folder: row.querySelector('.folder-select').value };
|
||||||
}
|
}
|
||||||
|
if (type === 'copy') {
|
||||||
|
return { type, copyTarget: row.querySelector('.folder-select').value };
|
||||||
|
}
|
||||||
if (type === 'junk') {
|
if (type === 'junk') {
|
||||||
return { type, junk: row.querySelector('.junk-select').value === 'true' };
|
return { type, junk: row.querySelector('.junk-select').value === 'true' };
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue