|
|
@ -18,6 +18,8 @@
|
|||
<li>Added the <i>{%mail_full_headers%}</i> placeholder to retrieve all the email headers at once [<a href="https://github.com/micz/ThunderAI/issues/713">#713</a>].</li>
|
||||
<li><i>[All APIs]</i> In the API webchat the status messages have different colors [<a href="https://github.com/micz/ThunderAI/issues/3">#3</a>].</li>
|
||||
<li>Account exclusion lists for add tags and antispam are enforced only for automatic analysis of incoming emails and not for the context menu action that is always executed [<a href="https://github.com/micz/ThunderAI/issues/749">#749</a>].</li>
|
||||
<li><i>[All APIs]</i> Inline auto translation for emails added [<a href="https://github.com/micz/ThunderAI/issues/247">#247</a>].</li>
|
||||
<li>Custom menus configuration added. Now it's possibile to define which prompts show in the ThunderAI menu, which ones in the context menu and in which order [<a href="https://github.com/micz/ThunderAI/issues/49">#49</a>, <a href="https://github.com/micz/ThunderAI/issues/184">#184</a>, <a href="https://github.com/micz/ThunderAI/issues/680">#680</a>].</li>
|
||||
<li>...</li>
|
||||
</ul>
|
||||
<h2>Version 4.0.3 - 20/03/2026</h2>
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ _The language status represents the percentage of translated strings in the late
|
|||
- [Roundicons](https://www.flaticon.com/authors/roundicons) for the summarize context menu icon
|
||||
- [HideMau](https://www.flaticon.com/authors/hidemaru) for the ai summarize icon
|
||||
- [Hilmy Abiyyu A.](https://www.flaticon.com/authors/hilmy-abiyyu-a) for the ai translate and context menu icons
|
||||
- [bearicons](https://www.flaticon.com/authors/bearicons) for the empty context menu icon
|
||||
- [meaicon](https://www.flaticon.com/authors/meaicon) for the add task context menu icon
|
||||
|
||||
|
||||
<br>
|
||||
|
|
|
|||
|
|
@ -35,12 +35,8 @@
|
|||
"message": "Classify",
|
||||
"description": ""
|
||||
},
|
||||
"prompt_summarize_this": {
|
||||
"message": "Summarize this",
|
||||
"description": ""
|
||||
},
|
||||
"prompt_translate_this": {
|
||||
"message": "Translate this",
|
||||
"message": "Translate",
|
||||
"description": ""
|
||||
},
|
||||
"prompt_this": {
|
||||
|
|
@ -203,6 +199,22 @@
|
|||
"message": "Composing an email",
|
||||
"description": ""
|
||||
},
|
||||
"show_in": {
|
||||
"message": "Show in",
|
||||
"description": ""
|
||||
},
|
||||
"show_in_popup": {
|
||||
"message": "Popup only",
|
||||
"description": ""
|
||||
},
|
||||
"show_in_context": {
|
||||
"message": "Context menu only",
|
||||
"description": ""
|
||||
},
|
||||
"show_in_both": {
|
||||
"message": "Both",
|
||||
"description": ""
|
||||
},
|
||||
"customPrompts_close_button": {
|
||||
"message": "Close button",
|
||||
"description": ""
|
||||
|
|
@ -612,14 +624,6 @@
|
|||
"message": "If checked, using the keyboard shortcut CTRL+ALT+A will automatically send the highlighted prompt from the menu. Otherwise, the prompt name will be displayed to the user, requiring another press of the Enter key to send it.",
|
||||
"description": ""
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_order_alphabet": {
|
||||
"message": "Menu: order alphabetically",
|
||||
"description": ""
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_order_alphabet_info": {
|
||||
"message": "If checked, the prompts in the menu will be ordered alphabetically.",
|
||||
"description": ""
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_dims_info": {
|
||||
"message": "Set to 0 if you don't want to specify the window size.",
|
||||
"description": ""
|
||||
|
|
@ -816,10 +820,6 @@
|
|||
"message": "Classify the following text in terms of Politeness, Warmth, Formality, Assertiveness, Offensiveness giving a percentage for each category. Reply with only the category and score with no extra comments or other text.",
|
||||
"description": ""
|
||||
},
|
||||
"prompt_summarize_this_full_text": {
|
||||
"message": "Summarize the following email into a bullet point list.",
|
||||
"description": ""
|
||||
},
|
||||
"prompt_translate_this_full_text": {
|
||||
"message": "Translate the email below into {%thunderai_translate_lang%}.\n\nRules:\n- Translate both the subject and the body.\n- Return the result as a JSON object with three fields: \"subject\", \"body\" and \"status\".\n- If the translation has been done the status is equal to 1.\n- If the email is written in one of these languages \"{%thunderai_translate_exclude_lang%}\" or in the {%thunderai_translate_lang%} language, return an empty string for the body and the subject and set the status to -1.\n- Do not add explanations, notes, or any text outside the JSON.\n\nMail subject: {%mail_subject%}\n\nMail body: {%mail_html_body%}\n\nGenerate a response in JSON format only. The output should be only a JSON object. Here is an example of the JSON format to be used:\n{\n\"subject\": \"subject translation\",\n\"body\": \"body translation\",\n\"status\": \"status result\"\n}",
|
||||
"description": ""
|
||||
|
|
@ -837,7 +837,7 @@
|
|||
"description": ""
|
||||
},
|
||||
"prompt_add_tags": {
|
||||
"message": "Add tags to this email",
|
||||
"message": "Add tags",
|
||||
"description": ""
|
||||
},
|
||||
"prompt_add_tags_full_text": {
|
||||
|
|
@ -1105,7 +1105,7 @@
|
|||
"description": ""
|
||||
},
|
||||
"prompt_summarize": {
|
||||
"message": "Summarize this email or these emails",
|
||||
"message": "Summarize",
|
||||
"description": ""
|
||||
},
|
||||
"prompt_summarize_full_text": {
|
||||
|
|
@ -1377,7 +1377,7 @@
|
|||
"description": ""
|
||||
},
|
||||
"prompt_spamfilter" : {
|
||||
"message" : "Detect spam emails",
|
||||
"message" : "Analyze for spam",
|
||||
"description" : ""
|
||||
},
|
||||
"prompt_spamfilter_full_text" : {
|
||||
|
|
@ -1520,22 +1520,6 @@
|
|||
"message": "No",
|
||||
"description": ""
|
||||
},
|
||||
"context_menu_mzta-add-tags": {
|
||||
"message": "Add tags",
|
||||
"description": ""
|
||||
},
|
||||
"context_menu_mzta-spamfilter": {
|
||||
"message": "Analyze for spam",
|
||||
"description": ""
|
||||
},
|
||||
"context_menu_mzta-summarize": {
|
||||
"message": "Summarize",
|
||||
"description": ""
|
||||
},
|
||||
"context_menu_mzta-translate": {
|
||||
"message": "Translate",
|
||||
"description": ""
|
||||
},
|
||||
"noActiveCalendar": {
|
||||
"message": "No editable calendar found!",
|
||||
"description": ""
|
||||
|
|
@ -2306,5 +2290,77 @@
|
|||
"placeholder_string": {
|
||||
"message": "Placeholder",
|
||||
"description": ""
|
||||
},
|
||||
"menu_order_title": {
|
||||
"message": "Menu Order",
|
||||
"description": "Title for the menu order page"
|
||||
},
|
||||
"menu_order_popup_list_title": {
|
||||
"message": "Popup Menu",
|
||||
"description": "Header for the popup menu list in the menu order page"
|
||||
},
|
||||
"menu_order_context_list_title": {
|
||||
"message": "Context Menu",
|
||||
"description": "Header for the context menu list in the menu order page"
|
||||
},
|
||||
"menu_order_saved": {
|
||||
"message": "Menu order saved!",
|
||||
"description": "Message shown after saving menu order"
|
||||
},
|
||||
"menu_order_tab_reading": {
|
||||
"message": "Reading",
|
||||
"description": "Sub-tab label for reading view ordering"
|
||||
},
|
||||
"menu_order_tab_composing": {
|
||||
"message": "Composing",
|
||||
"description": "Sub-tab label for composing view ordering"
|
||||
},
|
||||
"menu_order_badge_default": {
|
||||
"message": "Default",
|
||||
"description": "Badge label for default prompts"
|
||||
},
|
||||
"menu_order_badge_special": {
|
||||
"message": "Special",
|
||||
"description": "Badge label for special prompts"
|
||||
},
|
||||
"menu_order_badge_custom": {
|
||||
"message": "Custom",
|
||||
"description": "Badge label for custom prompts"
|
||||
},
|
||||
"menu_order_type_reading": {
|
||||
"message": "Reading",
|
||||
"description": "Badge label for reading-only prompts"
|
||||
},
|
||||
"menu_order_type_composing": {
|
||||
"message": "Composing",
|
||||
"description": "Badge label for composing-only prompts"
|
||||
},
|
||||
"menu_order_type_always": {
|
||||
"message": "Always",
|
||||
"description": "Badge label for prompts shown in all contexts"
|
||||
},
|
||||
"menu_order_btn_label": {
|
||||
"message": "Manage menu order settings",
|
||||
"description": "Button label to open the menu order page from options"
|
||||
},
|
||||
"menu_order_info": {
|
||||
"message": "Drag and drop items to reorder them. Use the toggle to show or hide items in each menu.",
|
||||
"description": "Info text for the menu order page"
|
||||
},
|
||||
"menu_order_active_items": {
|
||||
"message": "Visible items",
|
||||
"description": "Section header for active/visible menu items"
|
||||
},
|
||||
"menu_order_hidden_items": {
|
||||
"message": "Hidden items",
|
||||
"description": "Section header for hidden menu items"
|
||||
},
|
||||
"menu_order_icon_label": {
|
||||
"message": "Choose an icon",
|
||||
"description": "Label/tooltip for the context menu icon picker on the menu order page"
|
||||
},
|
||||
"menu_order_icon_none": {
|
||||
"message": "(none)",
|
||||
"description": "Dropdown option meaning no custom icon is selected for the context menu item"
|
||||
}
|
||||
}
|
||||
|
|
@ -210,6 +210,7 @@ Each subdirectory is a self-contained settings/UI page for a specific feature:
|
|||
| `customdataplaceholders/` | Custom placeholder editor |
|
||||
| `get-calendar-event/` | Calendar event extraction settings |
|
||||
| `get-task/` | Task creation settings |
|
||||
| `menu_order/` | Drag-and-drop reordering and visibility control for popup and context menus |
|
||||
| `spamfilter/` | Spam filter settings |
|
||||
| `summarize/` | Email summarization settings |
|
||||
| `translate/` | Email translation settings |
|
||||
|
|
|
|||
|
|
@ -28,9 +28,12 @@ Prompts are the core user-facing feature of ThunderAI. Each prompt defines an AI
|
|||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `enabled` | number | `0` = hidden, `1` = shown in popup |
|
||||
| `position_display` | number | Sort order in reading view |
|
||||
| `position_compose` | number | Sort order in compose view |
|
||||
| `enabled` | number | `0` = hidden, `1` = shown in menus |
|
||||
| `position_display` | number | Sort order for the popup menu in reading view |
|
||||
| `position_compose` | number | Sort order for the popup menu in compose view |
|
||||
| `position_context` | number | Sort order for the context menu |
|
||||
| `show_in` | string | `"popup"` = popup only, `"context"` = context menu only, `"both"` = both, `"none"` = hidden from all menus. Default: `"popup"` for default/custom prompts, `"both"` for special prompts |
|
||||
| `custom_icon` | string | Filename (with extension) of an icon in `images/context_menu/custom/` used as the context-menu icon. Empty string = no icon. Only used for non-special prompts (special prompts use their hard-coded icons in `specialPromptToContextMenuID`). Selectable from a dropdown on the Menu Order page, context-menu tab. |
|
||||
|
||||
### Per-Prompt API Override Properties
|
||||
|
||||
|
|
@ -59,6 +62,70 @@ Some prompts trigger additional Thunderbird actions beyond just sending text to
|
|||
|
||||
These special prompts can have their own dedicated API integration settings (configured in the Options page). The list of these special prompts is in `options/mzta-options-default.js` as `special_prompts_with_integration`.
|
||||
|
||||
## Menu System
|
||||
|
||||
### Popup Menu
|
||||
- Displays prompts filtered by `show_in` (`"popup"` or `"both"`) and by tab context (`type` property: reading view shows types `0`+`1`, compose view shows types `0`+`2`)
|
||||
- Ordering: always position-based using `position_display` (reading view) or `position_compose` (compose view). Alphabetical ordering has been removed
|
||||
- Special prompts retain their colored background (CSS class `special_prompt`) in the popup based on `is_special == "1"`
|
||||
|
||||
### Context Menu
|
||||
- Dynamically built from all prompts with `show_in` set to `"context"` or `"both"`, filtered to reading types only (`type` 0 or 1)
|
||||
- Appears as a "ThunderAI" submenu in the `message_list` context
|
||||
- Ordering: position-based using `position_context` (fallback to alphabetical only when positions are equal)
|
||||
- Special prompts (add_tags, spamfilter, summarize, translate) route through `processEmails()` for batch processing; regular prompts execute via `menus.executeMenuAction()`
|
||||
- Icons: special prompts use dedicated icons (defined in `contextMenuIconsPath`); all other prompts use the addon icon (`images/icon-32.png`)
|
||||
- Add Tags in context menu assigns tags automatically (`addTagsAuto: true`), while in the popup it shows the interactive tag selection form
|
||||
|
||||
### Menu Order Page (`pages/menu_order/`)
|
||||
|
||||
Dedicated page for reordering, enabling, and disabling menu items across both the popup and the context menu. Opened from the options page via the "Menu Order" button.
|
||||
|
||||
**UI layout** — two side-by-side panels:
|
||||
- **Popup Menu panel**: sub-tabs for "Reading" / "Composing" switch the list between `position_display` / `position_compose` ordering and between the allowed types (`0`+`1` vs `0`+`2`)
|
||||
- **Context Menu panel**: single list ordered by `position_context`. Items with `type: "2"` (composing-only) are never shown here
|
||||
|
||||
Each list has two sections:
|
||||
- **Visible items**: active for the menu (`show_in` includes the menu), draggable to reorder
|
||||
- **Hidden items**: inactive for the menu (`show_in` excludes the menu), sorted alphabetically, not draggable
|
||||
|
||||
**Toggle coordination** — flipping the checkbox updates the prompt's `show_in` with four-state logic:
|
||||
- Popup ON: `"none"` → `"popup"`, `"context"` → `"both"`
|
||||
- Popup OFF: `"popup"` → `"none"`, `"both"` → `"context"`
|
||||
- Context ON: `"none"` → `"context"`, `"popup"` → `"both"`
|
||||
- Context OFF: `"context"` → `"none"`, `"both"` → `"popup"`
|
||||
|
||||
**Drag and drop** — native HTML5 DnD assigns sequential position numbers (1, 2, 3, ...) to `position_display`, `position_compose`, or `position_context` depending on which list is being sorted.
|
||||
|
||||
**Exclusions from the UI** (preserved on save so data is not lost):
|
||||
- Prompts with `enabled === 0` (disabled)
|
||||
- Special prompts whose base definition has `show_in: "none"` (internal prompts like `prompt_summarize_email_template` and `prompt_summarize_email_separator`) — retrieved via `getHiddenSpecialPromptIds()`
|
||||
- Special prompts whose feature is not active — retrieved from background via `get_active_special_ids` message, which calls `getActiveSpecialPromptsIDs()` with current prefs and `_sparks_presence`
|
||||
|
||||
**Cross-tab reload** — the page listens on `browser.storage.onChanged` for changes to `_default_prompts_properties`, `_custom_prompt`, or `_special_prompts`. When one of those keys changes (e.g. user saves from the Custom Prompts page in another tab), the page reloads its data with a 200ms debounce. Any unsaved local changes are discarded to avoid overwriting the other page's work.
|
||||
|
||||
**Save flow**:
|
||||
1. Re-concat preserved prompts (disabled + hidden-specials + inactive-feature specials) with the UI-visible prompts
|
||||
2. Split by `is_default` / `is_special` and call `setDefaultPromptsProperties()`, `setCustomPrompts()`, `setSpecialPrompts()`
|
||||
3. Send `reload_menus` to the background to rebuild both menus
|
||||
|
||||
### Alphabetic-to-Position Migration
|
||||
|
||||
The `dynamic_menu_order_alphabet` preference (previously a user-facing option) has been retired and removed from the UI, but the key still exists in storage as a one-shot migration flag. At every background startup, `migrateMenuOrderAlphabetic()` in `js/mzta-prompts.js` runs:
|
||||
|
||||
1. Reads `dynamic_menu_order_alphabet` (defaults to `true` if unset)
|
||||
2. If `true`: sorts all visible prompts with special prompts first (alphabetically), then the rest (alphabetically), and assigns sequential `position_display` = `position_compose` = `position_context` numbers. Hidden special prompts are preserved untouched.
|
||||
3. Persists the new positions via `setDefaultPromptsProperties` / `setCustomPrompts` / `setSpecialPrompts`
|
||||
4. Sets `dynamic_menu_order_alphabet = false` in sync storage so the migration does not run again
|
||||
|
||||
This ensures existing users upgrading from the previous alphabetical-default behaviour get the same visible ordering on first run, while subsequent launches keep whatever custom ordering the user has set.
|
||||
|
||||
### Special Prompt Visibility Dependencies
|
||||
|
||||
`getActiveSpecialPromptsIDs()` in `js/mzta-utils.js` maps feature prefs to active special prompt IDs. Notable dependency:
|
||||
|
||||
- `prompt_get_calendar_event_from_clipboard` is emitted only if **both** `get_calendar_event` and `get_calendar_event_from_clipboard` are active. If `get_calendar_event` is off, neither calendar prompt is shown regardless of the clipboard pref.
|
||||
|
||||
### Summarize: Dual-Mode Prompt System
|
||||
|
||||
The summarize feature uses two distinct prompt pathways:
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ These are generated programmatically at the bottom of `mzta-options-default.js`
|
|||
| `chatgpt_web_custom_gpt` | `''` | Custom GPT URL |
|
||||
| `chatgpt_web_load_wait_time` | `1000` | Wait time (ms) for ChatGPT page |
|
||||
| `dynamic_menu_force_enter` | `false` | Force Enter to submit in popup |
|
||||
| `dynamic_menu_order_alphabet` | `true` | Sort prompts alphabetically |
|
||||
| `dynamic_menu_order_alphabet` | `true` | Internal migration flag only; no UI. Set to `false` by `migrateMenuOrderAlphabetic()` on first boot after upgrade to bootstrap position-based ordering. See `claude-spec/02-prompts.md` for details. |
|
||||
| `placeholders_use_default_value` | `false` | Use placeholder defaults when empty |
|
||||
| `max_prompt_length` | `30000` | Max prompt string length |
|
||||
|
||||
|
|
@ -126,6 +126,10 @@ The summarize settings page provides:
|
|||
- Each has Save/Reset buttons and placeholder autocomplete
|
||||
- Default text comes from i18n strings (`prompt_summarize_full_text`, etc.)
|
||||
|
||||
### Menu Order Page (`pages/menu_order/`)
|
||||
|
||||
Entry point from the options page via the "Menu Order" button (next to "Manage your prompts"). Provides drag-and-drop reordering and toggle-based visibility control for both the popup and the context menu. See `claude-spec/02-prompts.md` ("Menu Order Page") for the full behaviour, data flow, and exclusion rules.
|
||||
|
||||
### Translate Settings Page (`pages/translate/`)
|
||||
|
||||
The translate settings page provides:
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
BIN
images/context_menu/custom/adventure-game.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
images/context_menu/custom/airport.png
Normal file
|
After Width: | Height: | Size: 994 B |
BIN
images/context_menu/custom/alert-sign.png
Normal file
|
After Width: | Height: | Size: 974 B |
BIN
images/context_menu/custom/calendar.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
images/context_menu/custom/cash.png
Normal file
|
After Width: | Height: | Size: 971 B |
BIN
images/context_menu/custom/clapboard.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
images/context_menu/custom/clock.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
images/context_menu/custom/coins.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
images/context_menu/custom/copywriting.png
Normal file
|
After Width: | Height: | Size: 954 B |
BIN
images/context_menu/custom/customer-service.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
images/context_menu/custom/deadline.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
images/context_menu/custom/discount.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
images/context_menu/custom/earth.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
images/context_menu/custom/electric-car.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
images/context_menu/custom/empty_icon.png
Normal file
|
After Width: | Height: | Size: 705 B |
BIN
images/context_menu/custom/experiment.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
images/context_menu/custom/express-delivery.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
images/context_menu/custom/flammable.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
images/context_menu/custom/home.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
images/context_menu/custom/idea.png
Normal file
|
After Width: | Height: | Size: 793 B |
BIN
images/context_menu/custom/info.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
images/context_menu/custom/invoice.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
images/context_menu/custom/justice-scale.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
images/context_menu/custom/like.png
Normal file
|
After Width: | Height: | Size: 733 B |
BIN
images/context_menu/custom/love-letter.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
images/context_menu/custom/policeman.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
images/context_menu/custom/printer.png
Normal file
|
After Width: | Height: | Size: 677 B |
BIN
images/context_menu/custom/profit.png
Normal file
|
After Width: | Height: | Size: 664 B |
BIN
images/context_menu/custom/puzzle-game.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
images/context_menu/custom/radiation-hazard.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
images/context_menu/custom/receipt.png
Normal file
|
After Width: | Height: | Size: 838 B |
BIN
images/context_menu/custom/recycle-sign.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
images/context_menu/custom/science.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
images/context_menu/custom/scissors.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
images/context_menu/custom/star.png
Normal file
|
After Width: | Height: | Size: 925 B |
BIN
images/context_menu/custom/start-up.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
images/context_menu/custom/sticky-notes.png
Normal file
|
After Width: | Height: | Size: 604 B |
BIN
images/context_menu/custom/target.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
images/context_menu/custom/thermometer.png
Normal file
|
After Width: | Height: | Size: 795 B |
BIN
images/context_menu/custom/trophy.png
Normal file
|
After Width: | Height: | Size: 990 B |
BIN
images/context_menu/getcalendarevent.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
images/context_menu/getcalendareventfromclipboard.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
images/context_menu/gettask.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
|
@ -36,6 +36,7 @@ import {
|
|||
cleanupNewlines,
|
||||
checkIfTagLabelExists,
|
||||
getConnectionType,
|
||||
getContextMenuIcon,
|
||||
} from './mzta-utils.js'
|
||||
import { taPromptUtils } from './mzta-utils-prompt.js';
|
||||
import { taLogger } from './mzta-logger.js';
|
||||
|
|
@ -76,7 +77,6 @@ export class mzta_Menus {
|
|||
this.shortcutMenu = [];
|
||||
this.menu_listeners = {};
|
||||
this.allPrompts = await getPrompts(true,also_special);
|
||||
this.allPrompts.sort((a, b) => a.name.localeCompare(b.name));
|
||||
this.allPrompts.forEach((prompt) => {
|
||||
this.addAction(prompt)
|
||||
});
|
||||
|
|
@ -219,7 +219,7 @@ export class mzta_Menus {
|
|||
|
||||
// const tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
// add custom text if needed
|
||||
//browser.runtime.sendMessage({command: "chatgpt_open", prompt: fullPrompt, action: curr_prompt.action, tabId: tabs[0].id});
|
||||
// browser.runtime.sendMessage({command: "chatgpt_open", prompt: fullPrompt, action: curr_prompt.action, tabId: tabs[0].id});
|
||||
if(curr_prompt.is_special == '1'){ // Special prompts
|
||||
switch(curr_prompt.id){
|
||||
case 'prompt_add_tags': { // Add tags to the email
|
||||
|
|
@ -495,15 +495,77 @@ export class mzta_Menus {
|
|||
}
|
||||
|
||||
addShortcutMenu(prompt) {
|
||||
let curr_menu_entry = {id: prompt.id, label: i18nConditionalGet(prompt.name), type: prompt.type};
|
||||
let curr_menu_entry = {
|
||||
id: prompt.id,
|
||||
label: i18nConditionalGet(prompt.name),
|
||||
type: prompt.type,
|
||||
show_in: prompt.show_in || "popup",
|
||||
is_special: prompt.is_special,
|
||||
position_display: prompt.position_display,
|
||||
position_compose: prompt.position_compose,
|
||||
position_context: prompt.position_context,
|
||||
};
|
||||
this.shortcutMenu.push(curr_menu_entry);
|
||||
}
|
||||
|
||||
async loadContextMenus() {
|
||||
await browser.menus.removeAll();
|
||||
const contextPrompts = this.allPrompts.filter(p => {
|
||||
const showIn = p.show_in || "popup";
|
||||
// Only show prompts that should appear in context menu and are for reading context (type 0 or 1)
|
||||
return (showIn === "context" || showIn === "both") && (String(p.type) === "0" || String(p.type) === "1");
|
||||
});
|
||||
|
||||
if (contextPrompts.length === 0) {
|
||||
this.logger.log("No prompts for context menu");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create parent menu
|
||||
await new Promise(resolve =>
|
||||
browser.menus.create({
|
||||
id: 'mzta-context-parent',
|
||||
title: 'ThunderAI',
|
||||
contexts: ["message_list"],
|
||||
}, resolve)
|
||||
);
|
||||
|
||||
// Sort by position_context if available, otherwise alphabetically
|
||||
contextPrompts.sort((a, b) => {
|
||||
const posA = a.position_context || 9999;
|
||||
const posB = b.position_context || 9999;
|
||||
if (posA !== posB) return posA - posB;
|
||||
const nameA = i18nConditionalGet(a.name);
|
||||
const nameB = i18nConditionalGet(b.name);
|
||||
return nameA.localeCompare(nameB);
|
||||
});
|
||||
|
||||
// Create child menu items
|
||||
for (const prompt of contextPrompts) {
|
||||
const title = i18nConditionalGet(prompt.name);
|
||||
const iconPath = getContextMenuIcon(prompt);
|
||||
const menuOpts = {
|
||||
id: 'mzta-ctx-' + prompt.id,
|
||||
title: title,
|
||||
contexts: ["message_list"],
|
||||
parentId: 'mzta-context-parent',
|
||||
};
|
||||
if (iconPath) {
|
||||
menuOpts.icons = iconPath;
|
||||
}
|
||||
await new Promise(resolve =>
|
||||
browser.menus.create(menuOpts, resolve)
|
||||
);
|
||||
}
|
||||
this.logger.log("Context menus loaded: " + contextPrompts.length + " items");
|
||||
}
|
||||
|
||||
async loadMenus(also_special = []) {
|
||||
await this.initialize(also_special);
|
||||
await this.addMenu(this.rootMenu);
|
||||
this.addClickListener();
|
||||
this.loadShortcutMenu();
|
||||
await this.loadContextMenus();
|
||||
this.logger.log("Menus loaded");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,12 @@
|
|||
summaryTabId (set by _openSummaryWebchat in mzta-background.js):
|
||||
The tab ID of the message display tab to update with the saved summary.
|
||||
|
||||
Show in menu (show_in attribute):
|
||||
"popup": Show only in the popup menu
|
||||
"context": Show only in the context menu
|
||||
"both": Show in both popup and context menus
|
||||
"none": Do not show in any menu
|
||||
|
||||
================ USER PROPERTIES
|
||||
Enabled (enabled attribute):
|
||||
0: Disabled
|
||||
|
|
@ -108,6 +114,7 @@ const defaultPrompts = [
|
|||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "0",
|
||||
show_in: "popup",
|
||||
},
|
||||
{
|
||||
id: 'prompt_reply_advanced',
|
||||
|
|
@ -126,6 +133,7 @@ const defaultPrompts = [
|
|||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "0",
|
||||
show_in: "popup",
|
||||
},
|
||||
{
|
||||
id: 'prompt_reply_custom_command',
|
||||
|
|
@ -144,6 +152,7 @@ const defaultPrompts = [
|
|||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "0",
|
||||
show_in: "popup",
|
||||
},
|
||||
{
|
||||
id: 'prompt_rewrite_polite',
|
||||
|
|
@ -162,6 +171,7 @@ const defaultPrompts = [
|
|||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "0",
|
||||
show_in: "popup",
|
||||
},
|
||||
{
|
||||
id: 'prompt_rewrite_formal',
|
||||
|
|
@ -180,6 +190,7 @@ const defaultPrompts = [
|
|||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "0",
|
||||
show_in: "popup",
|
||||
},
|
||||
{
|
||||
id: 'prompt_classify',
|
||||
|
|
@ -198,24 +209,7 @@ const defaultPrompts = [
|
|||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "0",
|
||||
},
|
||||
{
|
||||
id: 'prompt_summarize_this',
|
||||
name: "__MSG_prompt_summarize_this__",
|
||||
text: "prompt_summarize_this_full_text",
|
||||
type: "0",
|
||||
action: "0",
|
||||
need_selected: "0",
|
||||
need_signature: "0",
|
||||
need_custom_text: "0",
|
||||
define_response_lang: "1",
|
||||
use_diff_viewer: "0",
|
||||
chatgpt_web_model: '',
|
||||
chatgpt_web_project: '',
|
||||
chatgpt_web_custom_gpt: '',
|
||||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "0",
|
||||
show_in: "popup",
|
||||
},
|
||||
{
|
||||
id: 'prompt_proofread_this',
|
||||
|
|
@ -234,6 +228,7 @@ const defaultPrompts = [
|
|||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "0",
|
||||
show_in: "popup",
|
||||
},
|
||||
{
|
||||
id: 'prompt_this',
|
||||
|
|
@ -252,6 +247,7 @@ const defaultPrompts = [
|
|||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "0",
|
||||
show_in: "popup",
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -270,6 +266,7 @@ const specialPrompts = [
|
|||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "1",
|
||||
show_in: "both",
|
||||
},
|
||||
{
|
||||
id: 'prompt_get_calendar_event',
|
||||
|
|
@ -285,6 +282,7 @@ const specialPrompts = [
|
|||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "1",
|
||||
show_in: "popup",
|
||||
},
|
||||
{
|
||||
id: 'prompt_get_calendar_event_from_clipboard',
|
||||
|
|
@ -300,6 +298,7 @@ const specialPrompts = [
|
|||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "1",
|
||||
show_in: "popup",
|
||||
},
|
||||
{
|
||||
id: 'prompt_get_task',
|
||||
|
|
@ -315,6 +314,7 @@ const specialPrompts = [
|
|||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "1",
|
||||
show_in: "popup",
|
||||
},
|
||||
{
|
||||
id: 'prompt_spamfilter',
|
||||
|
|
@ -330,6 +330,7 @@ const specialPrompts = [
|
|||
api_type: '',
|
||||
is_default: "1",
|
||||
is_special: "1",
|
||||
show_in: "context",
|
||||
},
|
||||
{
|
||||
id: 'prompt_summarize',
|
||||
|
|
@ -346,6 +347,7 @@ const specialPrompts = [
|
|||
api_model: '',
|
||||
is_default: "1",
|
||||
is_special: "1",
|
||||
show_in: "context",
|
||||
},
|
||||
{
|
||||
id: 'prompt_summarize_email_template',
|
||||
|
|
@ -362,6 +364,7 @@ const specialPrompts = [
|
|||
api_model: '',
|
||||
is_default: "1",
|
||||
is_special: "1",
|
||||
show_in: "none",
|
||||
},
|
||||
{
|
||||
id: 'prompt_summarize_email_separator',
|
||||
|
|
@ -378,6 +381,7 @@ const specialPrompts = [
|
|||
api_model: '',
|
||||
is_default: "1",
|
||||
is_special: "1",
|
||||
show_in: "none",
|
||||
},
|
||||
{
|
||||
id: 'prompt_translate_this',
|
||||
|
|
@ -394,6 +398,7 @@ const specialPrompts = [
|
|||
api_model: '',
|
||||
is_default: "1",
|
||||
is_special: "1",
|
||||
show_in: "context",
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -464,7 +469,7 @@ export function preparePromptsForExport(prompts, include_api_settings = false){
|
|||
}
|
||||
|
||||
if(prompt.is_default == 1){
|
||||
let allowedKeys = ['id', 'enabled', 'position_compose', 'position_display', 'need_custom_text'];
|
||||
let allowedKeys = ['id', 'enabled', 'position_compose', 'position_display', 'position_context', 'need_custom_text', 'show_in', 'custom_icon'];
|
||||
if(include_api_settings){
|
||||
allowedKeys.push('api_type');
|
||||
for (const [integration, options] of Object.entries(integration_options_config)) {
|
||||
|
|
@ -517,6 +522,7 @@ async function getDefaultPrompts_withProps() {
|
|||
prompt.text = browser.i18n.getMessage(prompt.text);
|
||||
prompt.position_display = pos;
|
||||
prompt.position_compose = pos;
|
||||
prompt.position_context = pos;
|
||||
prompt.enabled = 1;
|
||||
pos++;
|
||||
})
|
||||
|
|
@ -528,15 +534,19 @@ async function getDefaultPrompts_withProps() {
|
|||
if(prefs._default_prompts_properties?.[prompt.id]){
|
||||
prompt.position_compose = prefs._default_prompts_properties[prompt.id].position_compose;
|
||||
prompt.position_display = prefs._default_prompts_properties[prompt.id].position_display;
|
||||
prompt.position_context = prefs._default_prompts_properties[prompt.id]?.position_context || prompt.position_display;
|
||||
prompt.enabled = prefs._default_prompts_properties[prompt.id].enabled;
|
||||
prompt.need_custom_text = prefs._default_prompts_properties[prompt.id].need_custom_text;
|
||||
prompt.chatgpt_web_model = prefs._default_prompts_properties[prompt.id].chatgpt_web_model;
|
||||
prompt.chatgpt_web_project = prefs._default_prompts_properties[prompt.id].chatgpt_web_project;
|
||||
prompt.chatgpt_web_custom_gpt = (prefs._default_prompts_properties[prompt.id]?.chatgpt_web_custom_gpt || '').trim();
|
||||
prompt.api_type = (prefs._default_prompts_properties[prompt.id]?.api_type || '').trim();
|
||||
prompt.show_in = prefs._default_prompts_properties[prompt.id]?.show_in || prompt.show_in;
|
||||
prompt.custom_icon = prefs._default_prompts_properties[prompt.id]?.custom_icon || "";
|
||||
}else{
|
||||
prompt.position_display = pos;
|
||||
prompt.position_compose = pos;
|
||||
prompt.position_context = pos;
|
||||
prompt.enabled = 1;
|
||||
pos++;
|
||||
}
|
||||
|
|
@ -569,6 +579,12 @@ async function getCustomPrompts() {
|
|||
if(prompt.api_type === undefined){
|
||||
prompt.api_type = "";
|
||||
}
|
||||
if(prompt.show_in === undefined){
|
||||
prompt.show_in = "popup";
|
||||
}
|
||||
if(prompt.custom_icon === undefined){
|
||||
prompt.custom_icon = "";
|
||||
}
|
||||
});
|
||||
return prefs._custom_prompt;
|
||||
}
|
||||
|
|
@ -580,12 +596,15 @@ export async function setDefaultPromptsProperties(prompts) {
|
|||
default_prompts_properties[prompt.id] = {
|
||||
position_compose: (prompt.position_compose === undefined || prompt.position_compose === "undefined") ? "" : prompt.position_compose,
|
||||
position_display: (prompt.position_display === undefined || prompt.position_display === "undefined") ? "" : prompt.position_display,
|
||||
position_context: (prompt.position_context === undefined || prompt.position_context === "undefined") ? "" : prompt.position_context,
|
||||
enabled: (prompt.enabled === undefined || prompt.enabled === "undefined") ? "" : prompt.enabled,
|
||||
need_custom_text: (prompt.need_custom_text === undefined || prompt.need_custom_text === "undefined") ? "" : prompt.need_custom_text,
|
||||
chatgpt_web_model: (prompt.chatgpt_web_model === undefined || prompt.chatgpt_web_model === "undefined") ? "" : prompt.chatgpt_web_model,
|
||||
chatgpt_web_project: (prompt.chatgpt_web_project === undefined || prompt.chatgpt_web_project === "undefined") ? "" : prompt.chatgpt_web_project,
|
||||
chatgpt_web_custom_gpt: (prompt.chatgpt_web_custom_gpt === undefined || prompt.chatgpt_web_custom_gpt === "undefined") ? "" : prompt.chatgpt_web_custom_gpt,
|
||||
api_type: (prompt.api_type === undefined || prompt.api_type === "undefined") ? "" : prompt.api_type,
|
||||
show_in: (prompt.show_in === undefined || prompt.show_in === "undefined") ? "popup" : prompt.show_in,
|
||||
custom_icon: (prompt.custom_icon === undefined || prompt.custom_icon === "undefined") ? "" : prompt.custom_icon,
|
||||
};
|
||||
});
|
||||
//console.log('>>>>>>>>>>>>>> default_prompts_properties: ' + JSON.stringify(default_prompts_properties));
|
||||
|
|
@ -616,6 +635,13 @@ export async function getSpecialPrompts(){
|
|||
updatedPrompts.push(newPrompt);
|
||||
}
|
||||
});
|
||||
// Migrate: add show_in if missing from saved special prompts
|
||||
updatedPrompts.forEach((prompt) => {
|
||||
if (prompt.show_in === undefined) {
|
||||
prompt.show_in = "both";
|
||||
}
|
||||
});
|
||||
|
||||
// console.log(">>>>>>>>>>>>> getSpecialPrompts updatedPrompts: " + JSON.stringify(updatedPrompts));
|
||||
if (updatedPrompts.length !== prefs._special_prompts.length) {
|
||||
await browser.storage.local.set({ _special_prompts: updatedPrompts });
|
||||
|
|
@ -630,6 +656,56 @@ export async function setSpecialPrompts(prompts) {
|
|||
await browser.storage.local.set({_special_prompts: prompts});
|
||||
}
|
||||
|
||||
export function getHiddenSpecialPromptIds() {
|
||||
return specialPrompts.filter(p => p.show_in === "none").map(p => p.id);
|
||||
}
|
||||
|
||||
// Migration: if dynamic_menu_order_alphabet was true (or unset), assign initial positions
|
||||
// so that prompts appear alphabetically with special prompts first, then disable the flag
|
||||
// to switch to position-based ordering permanently.
|
||||
export async function migrateMenuOrderAlphabetic() {
|
||||
const prefs = await browser.storage.sync.get({ dynamic_menu_order_alphabet: true });
|
||||
if (!prefs.dynamic_menu_order_alphabet) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allPrompts = await getPrompts(false, [], true);
|
||||
const hiddenSpecialIds = getHiddenSpecialPromptIds();
|
||||
const visiblePrompts = allPrompts.filter(p => !hiddenSpecialIds.includes(p.id));
|
||||
|
||||
const resolveName = (p) => {
|
||||
const n = p.name || '';
|
||||
if (n.startsWith('__MSG_') && n.endsWith('__')) {
|
||||
return browser.i18n.getMessage(n.substring(6, n.length - 2));
|
||||
}
|
||||
return n;
|
||||
};
|
||||
|
||||
const specials = visiblePrompts.filter(p => String(p.is_special) === '1')
|
||||
.sort((a, b) => resolveName(a).localeCompare(resolveName(b)));
|
||||
const others = visiblePrompts.filter(p => String(p.is_special) !== '1')
|
||||
.sort((a, b) => resolveName(a).localeCompare(resolveName(b)));
|
||||
const ordered = specials.concat(others);
|
||||
|
||||
ordered.forEach((prompt, idx) => {
|
||||
const pos = idx + 1;
|
||||
prompt.position_display = pos;
|
||||
prompt.position_compose = pos;
|
||||
prompt.position_context = pos;
|
||||
});
|
||||
|
||||
const defaultPromptsToSave = ordered.filter(p => String(p.is_default) === '1' && String(p.is_special) !== '1');
|
||||
const customPromptsToSave = ordered.filter(p => String(p.is_default) === '0' && String(p.is_special) !== '1');
|
||||
const visibleSpecialsToSave = ordered.filter(p => String(p.is_special) === '1');
|
||||
const hiddenSpecialsToPreserve = allPrompts.filter(p => hiddenSpecialIds.includes(p.id));
|
||||
|
||||
await setDefaultPromptsProperties(defaultPromptsToSave);
|
||||
await setCustomPrompts(customPromptsToSave);
|
||||
await setSpecialPrompts(visibleSpecialsToSave.concat(hiddenSpecialsToPreserve));
|
||||
|
||||
await browser.storage.sync.set({ dynamic_menu_order_alphabet: false });
|
||||
}
|
||||
|
||||
export async function getSpamFilterPrompt(){
|
||||
return (await getSpecialPrompts()).find(prompt => prompt.id == 'prompt_spamfilter');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,24 +16,69 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { prefs_default, getDynamicSettingValue } from '../options/mzta-options-default.js';
|
||||
import {
|
||||
prefs_default,
|
||||
getDynamicSettingValue
|
||||
} from '../options/mzta-options-default.js';
|
||||
|
||||
import { customMenuIconsPath } from '../pages/menu_order/mzta-custom-menu-icons.js'
|
||||
|
||||
const sparks_min = '1.2.0'; // Minimum version of ThunderAI-Sparks required for the add-on to work
|
||||
const MICZ_IT_LOCALIZED_LANGS = ['es', 'de', 'fr', 'it'];
|
||||
|
||||
export const getMenuContextCompose = () => 'compose_action_menu';
|
||||
export const getMenuContextDisplay = () => 'message_display_action_menu';
|
||||
|
||||
export const contextMenuID_GetCalendarEvent = 'mzta-get-calendar-event';
|
||||
export const contextMenuID_GetCalendarEventFromClipboard = 'mzta-get-calendar-event-from-clipboard';
|
||||
export const contextMenuID_GetTask = 'mzta-get-task';
|
||||
export const contextMenuID_AddTags = 'mzta-add-tags';
|
||||
export const contextMenuID_Spamfilter = 'mzta-spamfilter';
|
||||
export const contextMenuID_Summarize = 'mzta-summarize';
|
||||
export const contextMenuID_Translate = 'mzta-translate';
|
||||
export const contextMenuIconsPath = {
|
||||
[contextMenuID_AddTags]: 'moz-extension:images/menu_autotags.png',
|
||||
[contextMenuID_Spamfilter]: 'moz-extension:images/menu_spamfilter.png',
|
||||
[contextMenuID_Summarize]: 'moz-extension:images/menu_summarize.png',
|
||||
[contextMenuID_Translate]: 'moz-extension:images/menu_translate.png',
|
||||
[contextMenuID_GetCalendarEvent]: 'moz-extension:images/context_menu/getcalendarevent.png',
|
||||
[contextMenuID_GetCalendarEventFromClipboard]: 'moz-extension:images/context_menu/getcalendareventfromclipboard.png',
|
||||
[contextMenuID_GetTask]: 'moz-extension:images/context_menu/gettask.png',
|
||||
[contextMenuID_AddTags]: 'moz-extension:images/context_menu/autotags.png',
|
||||
[contextMenuID_Spamfilter]: 'moz-extension:images/context_menu/spamfilter.png',
|
||||
[contextMenuID_Summarize]: 'moz-extension:images/context_menu/summarize.png',
|
||||
[contextMenuID_Translate]: 'moz-extension:images/context_menu/translate.png',
|
||||
};
|
||||
|
||||
// Map from special prompt IDs to context menu IDs
|
||||
export const specialPromptToContextMenuID = {
|
||||
'prompt_get_calendar_event': contextMenuID_GetCalendarEvent,
|
||||
'prompt_get_calendar_event_from_clipboard': contextMenuID_GetCalendarEventFromClipboard,
|
||||
'prompt_get_task': contextMenuID_GetTask,
|
||||
'prompt_add_tags': contextMenuID_AddTags,
|
||||
'prompt_spamfilter': contextMenuID_Spamfilter,
|
||||
'prompt_summarize': contextMenuID_Summarize,
|
||||
'prompt_translate_this': contextMenuID_Translate,
|
||||
};
|
||||
|
||||
// const defaultContextMenuIcon = 'moz-extension:images/icon-32px.png';
|
||||
const defaultContextMenuIcon = '';
|
||||
|
||||
export function getContextMenuIcon(prompt) {
|
||||
// Back-compat: accept a plain id string too
|
||||
const promptId = (typeof prompt === 'string') ? prompt : prompt?.id;
|
||||
const isSpecial = (typeof prompt === 'object' && prompt !== null) ? String(prompt.is_special) === '1' : true;
|
||||
|
||||
if (isSpecial) {
|
||||
const contextMenuId = specialPromptToContextMenuID[promptId];
|
||||
if (contextMenuId && contextMenuIconsPath[contextMenuId]) {
|
||||
return contextMenuIconsPath[contextMenuId];
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof prompt === 'object' && prompt !== null && prompt.custom_icon) {
|
||||
return 'moz-extension:' + customMenuIconsPath + prompt.custom_icon;
|
||||
}
|
||||
|
||||
return defaultContextMenuIcon;
|
||||
}
|
||||
|
||||
export function getLanguageDisplayName(languageCode) {
|
||||
const languageDisplay = new Intl.DisplayNames([languageCode], {type: 'language'});
|
||||
let lang_string = languageDisplay.of(languageCode);
|
||||
|
|
@ -612,10 +657,11 @@ export function getActiveSpecialPromptsIDs(args = {}) {
|
|||
get_calendar_event = false,
|
||||
get_calendar_event_from_clipboard = false,
|
||||
get_task = false,
|
||||
spamfilter = false,
|
||||
summarize = false,
|
||||
translate = false,
|
||||
is_chatgpt_web = false
|
||||
} = args;
|
||||
// The Antispam filter is not here, because this method is used only
|
||||
// to reload the ThunderAI button menu, not the context menu
|
||||
let output = [];
|
||||
// console.log(">>>>>>>>>> getActiveSpecialPromptsIDs args: " + JSON.stringify(args));
|
||||
if (is_chatgpt_web) {
|
||||
|
|
@ -629,13 +675,22 @@ export function getActiveSpecialPromptsIDs(args = {}) {
|
|||
}
|
||||
if (get_calendar_event) {
|
||||
output.push('prompt_get_calendar_event');
|
||||
}
|
||||
if (get_calendar_event_from_clipboard) {
|
||||
output.push('prompt_get_calendar_event_from_clipboard');
|
||||
}
|
||||
}
|
||||
if (get_task) {
|
||||
output.push('prompt_get_task');
|
||||
}
|
||||
if (spamfilter) {
|
||||
output.push('prompt_spamfilter');
|
||||
}
|
||||
if (summarize) {
|
||||
output.push('prompt_summarize');
|
||||
}
|
||||
if (translate) {
|
||||
output.push('prompt_translate_this');
|
||||
}
|
||||
// console.log(">>>>>>>>>> getActiveSpecialPromptsIDs output: " + JSON.stringify(output));
|
||||
return output;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,24 +42,19 @@ import {
|
|||
getMessages,
|
||||
getMailBody,
|
||||
extractJsonObject,
|
||||
contextMenuID_AddTags,
|
||||
contextMenuID_Spamfilter,
|
||||
contextMenuID_Summarize,
|
||||
contextMenuID_Translate,
|
||||
contextMenuIconsPath,
|
||||
sanitizeChatGPTModelData,
|
||||
sanitizeChatGPTWebCustomData,
|
||||
stripHtmlKeepLines,
|
||||
htmlBodyToPlainText,
|
||||
convertNewlinesToParagraphs,
|
||||
getConnectionType,
|
||||
checkAPIIntegration,
|
||||
hasSpecificIntegration,
|
||||
} from './js/mzta-utils.js';
|
||||
import { taPromptUtils } from './js/mzta-utils-prompt.js';
|
||||
import { mzta_specialCommand } from './js/mzta-special-commands.js';
|
||||
import {
|
||||
getSpamFilterPrompt
|
||||
getSpamFilterPrompt,
|
||||
migrateMenuOrderAlphabetic
|
||||
} from './js/mzta-prompts.js';
|
||||
import { taSpamReport } from './js/mzta-spamreport.js';
|
||||
import { taSummaryStore } from './js/mzta-summarystore.js';
|
||||
|
|
@ -105,6 +100,9 @@ let special_prompts_ids = getActiveSpecialPromptsIDs({
|
|||
get_calendar_event: doGetSparkFeature(prefs_init.get_calendar_event),
|
||||
get_calendar_event_from_clipboard: doGetSparkFeature(prefs_init.get_calendar_event_from_clipboard),
|
||||
get_task: doGetSparkFeature(prefs_init.get_task),
|
||||
spamfilter: prefs_init.spamfilter,
|
||||
summarize: prefs_init.summarize,
|
||||
translate: prefs_init.translate,
|
||||
is_chatgpt_web: (prefs_init.connection_type === "chatgpt_web")
|
||||
});
|
||||
|
||||
|
|
@ -169,7 +167,7 @@ function preparePopupMenu(tab) {
|
|||
}
|
||||
|
||||
async function _reload_menus() {
|
||||
let prefs_reload = await browser.storage.sync.get({add_tags: prefs_default.add_tags, get_calendar_event: prefs_default.get_calendar_event, get_calendar_event_from_clipboard: prefs_default.get_calendar_event_from_clipboard, get_task: prefs_default.get_task, connection_type: prefs_default.connection_type});
|
||||
let prefs_reload = await browser.storage.sync.get({add_tags: prefs_default.add_tags, get_calendar_event: prefs_default.get_calendar_event, get_calendar_event_from_clipboard: prefs_default.get_calendar_event_from_clipboard, get_task: prefs_default.get_task, connection_type: prefs_default.connection_type, spamfilter: prefs_default.spamfilter, summarize: prefs_default.summarize, translate: prefs_default.translate});
|
||||
let getCalendarEvent = doGetSparkFeature(prefs_reload.get_calendar_event);
|
||||
let getCalendarEventFromClipboard = doGetSparkFeature(prefs_reload.get_calendar_event_from_clipboard);
|
||||
let getTask = doGetSparkFeature(prefs_reload.get_task);
|
||||
|
|
@ -179,6 +177,9 @@ async function _reload_menus() {
|
|||
get_calendar_event: getCalendarEvent,
|
||||
get_calendar_event_from_clipboard: getCalendarEventFromClipboard,
|
||||
get_task: getTask,
|
||||
spamfilter: prefs_reload.spamfilter,
|
||||
summarize: prefs_reload.summarize,
|
||||
translate: prefs_reload.translate,
|
||||
is_chatgpt_web: (prefs_reload.connection_type === "chatgpt_web")
|
||||
});
|
||||
menus.reload(special_prompts_ids);
|
||||
|
|
@ -186,6 +187,24 @@ async function _reload_menus() {
|
|||
return true;
|
||||
}
|
||||
|
||||
async function _getActiveSpecialIds() {
|
||||
let prefs_reload = await browser.storage.sync.get({add_tags: prefs_default.add_tags, get_calendar_event: prefs_default.get_calendar_event, get_calendar_event_from_clipboard: prefs_default.get_calendar_event_from_clipboard, get_task: prefs_default.get_task, connection_type: prefs_default.connection_type, spamfilter: prefs_default.spamfilter, summarize: prefs_default.summarize, translate: prefs_default.translate});
|
||||
let getCalendarEvent = doGetSparkFeature(prefs_reload.get_calendar_event);
|
||||
let getCalendarEventFromClipboard = doGetSparkFeature(prefs_reload.get_calendar_event_from_clipboard);
|
||||
let getTask = doGetSparkFeature(prefs_reload.get_task);
|
||||
return getActiveSpecialPromptsIDs({
|
||||
addtags: prefs_reload.add_tags,
|
||||
addtags_api: hasSpecificIntegration(prefs_init.add_tags_use_specific_integration, prefs_init.add_tags_connection_type),
|
||||
get_calendar_event: getCalendarEvent,
|
||||
get_calendar_event_from_clipboard: getCalendarEventFromClipboard,
|
||||
get_task: getTask,
|
||||
spamfilter: prefs_reload.spamfilter,
|
||||
summarize: prefs_reload.summarize,
|
||||
translate: prefs_reload.translate,
|
||||
is_chatgpt_web: (prefs_reload.connection_type === "chatgpt_web")
|
||||
});
|
||||
}
|
||||
|
||||
async function _assign_tags(_data, create_new_tags = true, exclusions_exact_match = false) {
|
||||
let all_tags_list = await getTagsList();
|
||||
all_tags_list = all_tags_list[1];
|
||||
|
|
@ -507,8 +526,26 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|||
case 'reload_menus':
|
||||
return _reload_menus();
|
||||
break;
|
||||
case 'get_active_special_ids':
|
||||
return _getActiveSpecialIds();
|
||||
break;
|
||||
case 'shortcut_do_prompt':
|
||||
taLog.log("Executing shortcut, promptId: " + message.promptId);
|
||||
if (message.promptId !== 'prompt_add_tags' && specialContextMenuActions[message.promptId]) { //TODO Add an option here if you want the user to decide to use the autotagging also in the popup menu
|
||||
async function _shortcut_special() {
|
||||
let tabId = message.tabId;
|
||||
if (!tabId) {
|
||||
let tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
if (tabs.length === 0) return false;
|
||||
tabId = tabs[0].id;
|
||||
}
|
||||
let displayedMessage = await browser.messageDisplay.getDisplayedMessage(tabId);
|
||||
if (!displayedMessage) return false;
|
||||
taLog.log("Displayed message found.");
|
||||
return specialContextMenuActions[message.promptId]([displayedMessage]);
|
||||
}
|
||||
return _shortcut_special();
|
||||
}
|
||||
return menus.executeMenuAction(message.promptId);
|
||||
break;
|
||||
case 'popup_menu_ready':
|
||||
|
|
@ -1436,6 +1473,9 @@ function setupStorageChangeListener() {
|
|||
get_calendar_event: getCalendarEvent,
|
||||
get_calendar_event_from_clipboard: getCalendarEventFromClipboard,
|
||||
get_task: getTask,
|
||||
spamfilter: prefs_init.spamfilter,
|
||||
summarize: prefs_init.summarize,
|
||||
translate: prefs_init.translate,
|
||||
is_chatgpt_web: (prefs_init.connection_type === "chatgpt_web")
|
||||
});
|
||||
menus.reload(special_prompts_ids);
|
||||
|
|
@ -1453,6 +1493,9 @@ function setupStorageChangeListener() {
|
|||
get_calendar_event: getCalendarEvent,
|
||||
get_calendar_event_from_clipboard: getCalendarEventFromClipboard,
|
||||
get_task: getTask,
|
||||
spamfilter: prefs_init.spamfilter,
|
||||
summarize: prefs_init.summarize,
|
||||
translate: prefs_init.translate,
|
||||
is_chatgpt_web: (prefs_init.connection_type === "chatgpt_web")
|
||||
});
|
||||
menus.reload(special_prompts_ids);
|
||||
|
|
@ -1470,6 +1513,9 @@ function setupStorageChangeListener() {
|
|||
get_calendar_event: getCalendarEvent,
|
||||
get_calendar_event_from_clipboard: getCalendarEventFromClipboard,
|
||||
get_task: getTask,
|
||||
spamfilter: prefs_init.spamfilter,
|
||||
summarize: prefs_init.summarize,
|
||||
translate: prefs_init.translate,
|
||||
is_chatgpt_web: (prefs_init.connection_type === "chatgpt_web")
|
||||
});
|
||||
menus.reload(special_prompts_ids);
|
||||
|
|
@ -1487,6 +1533,69 @@ function setupStorageChangeListener() {
|
|||
get_calendar_event: getCalendarEvent,
|
||||
get_calendar_event_from_clipboard: getCalendarEventFromClipboard,
|
||||
get_task: getTask,
|
||||
spamfilter: prefs_init.spamfilter,
|
||||
summarize: prefs_init.summarize,
|
||||
translate: prefs_init.translate,
|
||||
is_chatgpt_web: (prefs_init.connection_type === "chatgpt_web")
|
||||
});
|
||||
menus.reload(special_prompts_ids);
|
||||
}
|
||||
|
||||
// Process 'spamfilter' changes
|
||||
if (changes.spamfilter) {
|
||||
const newSpamfilter = changes.spamfilter.newValue;
|
||||
let getCalendarEvent = doGetSparkFeature(prefs_init.get_calendar_event);
|
||||
let getCalendarEventFromClipboard = doGetSparkFeature(prefs_init.get_calendar_event_from_clipboard);
|
||||
let getTask = doGetSparkFeature(prefs_init.get_task);
|
||||
const special_prompts_ids = getActiveSpecialPromptsIDs({
|
||||
addtags: prefs_init.add_tags,
|
||||
addtags_api: hasSpecificIntegration(prefs_init.add_tags_use_specific_integration, prefs_init.add_tags_connection_type),
|
||||
get_calendar_event: getCalendarEvent,
|
||||
get_calendar_event_from_clipboard: getCalendarEventFromClipboard,
|
||||
get_task: getTask,
|
||||
spamfilter: newSpamfilter,
|
||||
summarize: prefs_init.summarize,
|
||||
translate: prefs_init.translate,
|
||||
is_chatgpt_web: (prefs_init.connection_type === "chatgpt_web")
|
||||
});
|
||||
menus.reload(special_prompts_ids);
|
||||
}
|
||||
|
||||
// Process 'summarize' changes
|
||||
if (changes.summarize) {
|
||||
const newSummarize = changes.summarize.newValue;
|
||||
let getCalendarEvent = doGetSparkFeature(prefs_init.get_calendar_event);
|
||||
let getCalendarEventFromClipboard = doGetSparkFeature(prefs_init.get_calendar_event_from_clipboard);
|
||||
let getTask = doGetSparkFeature(prefs_init.get_task);
|
||||
const special_prompts_ids = getActiveSpecialPromptsIDs({
|
||||
addtags: prefs_init.add_tags,
|
||||
addtags_api: hasSpecificIntegration(prefs_init.add_tags_use_specific_integration, prefs_init.add_tags_connection_type),
|
||||
get_calendar_event: getCalendarEvent,
|
||||
get_calendar_event_from_clipboard: getCalendarEventFromClipboard,
|
||||
get_task: getTask,
|
||||
spamfilter: prefs_init.spamfilter,
|
||||
summarize: newSummarize,
|
||||
translate: prefs_init.translate,
|
||||
is_chatgpt_web: (prefs_init.connection_type === "chatgpt_web")
|
||||
});
|
||||
menus.reload(special_prompts_ids);
|
||||
}
|
||||
|
||||
// Process 'translate' changes
|
||||
if (changes.translate) {
|
||||
const newTranslate = changes.translate.newValue;
|
||||
let getCalendarEvent = doGetSparkFeature(prefs_init.get_calendar_event);
|
||||
let getCalendarEventFromClipboard = doGetSparkFeature(prefs_init.get_calendar_event_from_clipboard);
|
||||
let getTask = doGetSparkFeature(prefs_init.get_task);
|
||||
const special_prompts_ids = getActiveSpecialPromptsIDs({
|
||||
addtags: prefs_init.add_tags,
|
||||
addtags_api: hasSpecificIntegration(prefs_init.add_tags_use_specific_integration, prefs_init.add_tags_connection_type),
|
||||
get_calendar_event: getCalendarEvent,
|
||||
get_calendar_event_from_clipboard: getCalendarEventFromClipboard,
|
||||
get_task: getTask,
|
||||
spamfilter: prefs_init.spamfilter,
|
||||
summarize: prefs_init.summarize,
|
||||
translate: newTranslate,
|
||||
is_chatgpt_web: (prefs_init.connection_type === "chatgpt_web")
|
||||
});
|
||||
menus.reload(special_prompts_ids);
|
||||
|
|
@ -1504,14 +1613,15 @@ function setupStorageChangeListener() {
|
|||
get_calendar_event: getCalendarEvent,
|
||||
get_calendar_event_from_clipboard: getCalendarEventFromClipboard,
|
||||
get_task: getTask,
|
||||
spamfilter: prefs_init.spamfilter,
|
||||
summarize: prefs_init.summarize,
|
||||
translate: prefs_init.translate,
|
||||
is_chatgpt_web: (newConnectionType === "chatgpt_web")
|
||||
});
|
||||
menus.reload(special_prompts_ids);
|
||||
}
|
||||
|
||||
reload_pref_init().then(() => {
|
||||
addContextMenuItems();
|
||||
});
|
||||
reload_pref_init();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1540,97 +1650,34 @@ function setupPermissionsRemovedListener() {
|
|||
setupPermissionsRemovedListener();
|
||||
|
||||
// Menus handling
|
||||
await migrateMenuOrderAlphabetic();
|
||||
const menus = new mzta_Menus(openChatGPT, prefs_init.do_debug);
|
||||
menus.loadMenus(special_prompts_ids);
|
||||
|
||||
// Context Menus
|
||||
function addContextMenu(menu_id) {
|
||||
browser.menus.remove(menu_id);
|
||||
browser.menus.create({
|
||||
id: menu_id,
|
||||
title: browser.i18n.getMessage("context_menu_" + menu_id),
|
||||
contexts: ["message_list"],
|
||||
icons: contextMenuIconsPath[menu_id],
|
||||
});
|
||||
taLog.log("Context menu added: " + menu_id);
|
||||
// console.log(">>>>>>> contextMenuIconsPath[menu_id]: " + contextMenuIconsPath[menu_id]);
|
||||
}
|
||||
// Context menu click handling
|
||||
// Context menus are now created dynamically by mzta_Menus.loadContextMenus()
|
||||
// based on each prompt's show_in property. The menu item IDs use the format 'mzta-ctx-<prompt_id>'.
|
||||
// Special prompts (add_tags, spamfilter, summarize, translate) are routed to processEmails()
|
||||
// for batch processing. Regular prompts are executed via menus.executeMenuAction().
|
||||
|
||||
function removeContextMenu(menu_id) {
|
||||
browser.menus.remove(menu_id);
|
||||
taLog.log("Context menu removed: " + menu_id);
|
||||
}
|
||||
const specialContextMenuActions = {
|
||||
'prompt_add_tags': (messages) => processEmails({ messages, addTagsAuto: true }),
|
||||
'prompt_spamfilter': (messages) => processEmails({ messages, spamFilter: true }),
|
||||
'prompt_summarize': (messages) => processEmails({ messages, summarize: true }),
|
||||
'prompt_translate_this': (messages) => processEmails({ messages, translate: true }),
|
||||
};
|
||||
|
||||
function addContextMenuItems() {
|
||||
let itemsToAdd = [];
|
||||
browser.menus.onClicked.addListener((info, tab) => {
|
||||
const menuItemId = info.menuItemId;
|
||||
if (typeof menuItemId !== 'string' || !menuItemId.startsWith('mzta-ctx-')) {
|
||||
return;
|
||||
}
|
||||
const promptId = menuItemId.replace('mzta-ctx-', '');
|
||||
|
||||
// Add Context menu: Add tags
|
||||
if(prefs_init.add_tags && checkAPIIntegration(prefs_init.connection_type, prefs_init.add_tags_use_specific_integration,prefs_init.add_tags_connection_type)){
|
||||
itemsToAdd.push(contextMenuID_AddTags);
|
||||
if (specialContextMenuActions[promptId]) {
|
||||
specialContextMenuActions[promptId](getMessages(info.selectedMessages));
|
||||
} else {
|
||||
removeContextMenu(contextMenuID_AddTags);
|
||||
}
|
||||
|
||||
// Add Context menu: Spamfilter
|
||||
if(prefs_init.spamfilter && checkAPIIntegration(prefs_init.connection_type, prefs_init.spamfilter_use_specific_integration,prefs_init.spamfilter_connection_type)){
|
||||
itemsToAdd.push(contextMenuID_Spamfilter);
|
||||
} else {
|
||||
removeContextMenu(contextMenuID_Spamfilter);
|
||||
}
|
||||
|
||||
// Add Context menu: Summarize
|
||||
if(prefs_init.summarize && checkAPIIntegration(prefs_init.connection_type, prefs_init.summarize_use_specific_integration, prefs_init.summarize_connection_type)) {
|
||||
itemsToAdd.push(contextMenuID_Summarize);
|
||||
} else {
|
||||
removeContextMenu(contextMenuID_Summarize);
|
||||
}
|
||||
|
||||
// Add Context menu: Translate
|
||||
if(prefs_init.translate && checkAPIIntegration(prefs_init.connection_type, prefs_init.translate_use_specific_integration, prefs_init.translate_connection_type)){
|
||||
itemsToAdd.push(contextMenuID_Translate);
|
||||
} else {
|
||||
removeContextMenu(contextMenuID_Translate);
|
||||
}
|
||||
|
||||
itemsToAdd.sort((a, b) => {
|
||||
let titleA = browser.i18n.getMessage("context_menu_" + a);
|
||||
let titleB = browser.i18n.getMessage("context_menu_" + b);
|
||||
return titleA.localeCompare(titleB);
|
||||
});
|
||||
|
||||
itemsToAdd.forEach(menu_id => {
|
||||
addContextMenu(menu_id);
|
||||
});
|
||||
}
|
||||
|
||||
addContextMenuItems();
|
||||
|
||||
// Listen for context menu item clicks
|
||||
browser.menus.onClicked.addListener( (info, tab) => {
|
||||
let _add_tags = false
|
||||
let _spamfilter = false
|
||||
let _summarize = false;
|
||||
let _translate = false;
|
||||
if(info.menuItemId === contextMenuID_AddTags){
|
||||
_add_tags = true;
|
||||
}
|
||||
if(info.menuItemId === contextMenuID_Spamfilter){
|
||||
_spamfilter = true;
|
||||
}
|
||||
if(info.menuItemId === contextMenuID_Summarize) {
|
||||
_summarize = true;
|
||||
}
|
||||
if(info.menuItemId === contextMenuID_Translate) {
|
||||
_translate = true;
|
||||
}
|
||||
if(_add_tags || _spamfilter || _summarize || _translate){
|
||||
processEmails({
|
||||
messages: getMessages(info.selectedMessages),
|
||||
addTagsAuto: _add_tags,
|
||||
spamFilter: _spamfilter,
|
||||
summarize: _summarize,
|
||||
translate: _translate
|
||||
});
|
||||
menus.executeMenuAction(promptId);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ export const prefs_default = {
|
|||
chatgpt_web_custom_gpt: '',
|
||||
chatgpt_web_load_wait_time: 1000,
|
||||
dynamic_menu_force_enter: false,
|
||||
dynamic_menu_order_alphabet: true,
|
||||
placeholders_use_default_value: false,
|
||||
max_prompt_length: 30000, // max string length for prompt
|
||||
add_tags: false,
|
||||
|
|
|
|||
|
|
@ -117,12 +117,11 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td><label>
|
||||
<span class="opt_title">__MSG_prefs_OptionText_dynamic_menu_order_alphabet__</span>
|
||||
<span class="opt_title">__MSG_menu_order_title__</span>
|
||||
</label></td>
|
||||
<td>
|
||||
<label>
|
||||
<input type="checkbox" id="dynamic_menu_order_alphabet" name="dynamic_menu_order_alphabet" class="option-input" />
|
||||
<span>__MSG_prefs_OptionText_dynamic_menu_order_alphabet_info__</span>
|
||||
<button id="btnMenuOrder">__MSG_menu_order_btn_label__</button>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -410,6 +410,10 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
openTab('/pages/customdataplaceholders/mzta-custom-dataplaceholders.html');
|
||||
});
|
||||
|
||||
document.getElementById('btnMenuOrder').addEventListener('click', () => {
|
||||
openTab('/pages/menu_order/mzta-menu-order.html');
|
||||
});
|
||||
|
||||
document.getElementById('btnManageTagsInfo').addEventListener('click', () => {
|
||||
openTab('/pages/addtags/mzta-add-tags.html');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
<li>Added the <i>{%mail_full_headers%}</i> placeholder to retrieve all the email headers at once [<a href="https://github.com/micz/ThunderAI/issues/713">#713</a>].</li>
|
||||
<li><i>[All APIs]</i> In the API webchat the status messages have different colors [<a href="https://github.com/micz/ThunderAI/issues/3">#3</a>].</li>
|
||||
<li>Account exclusion lists for add tags and antispam are enforced only for automatic analysis of incoming emails and not for the context menu action that is always executed [<a href="https://github.com/micz/ThunderAI/issues/749">#749</a>].</li>
|
||||
<li><i>[All APIs]</i> Inline auto translation for emails added [<a href="https://github.com/micz/ThunderAI/issues/247">#247</a>].</li>
|
||||
<li>Custom menus configuration added. Now it's possibile to define which prompts show in the ThunderAI menu, which ones in the context menu and in which order [<a href="https://github.com/micz/ThunderAI/issues/49">#49</a>, <a href="https://github.com/micz/ThunderAI/issues/184">#184</a>, <a href="https://github.com/micz/ThunderAI/issues/680">#680</a>].</li>
|
||||
<li>...</li>
|
||||
</ul>
|
||||
<h2>Version 4.0.3 - 20/03/2026</h2>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,14 @@
|
|||
<option value="2">__MSG_customPrompts_add_to_menu_composing__</option>
|
||||
</select>
|
||||
<br><br>
|
||||
<label for="selectShowInNew" class="field_title">__MSG_show_in__:</label>
|
||||
<br>
|
||||
<select id="selectShowInNew" name="show_in" tabindex="4">
|
||||
<option value="popup">__MSG_show_in_popup__</option>
|
||||
<option value="context">__MSG_show_in_context__</option>
|
||||
<option value="both">__MSG_show_in_both__</option>
|
||||
</select>
|
||||
<br><br>
|
||||
<label for="selectActionNew" class="field_title">__MSG_customPrompts_form_label_Action__:</label>
|
||||
<br>
|
||||
<select id="selectActionNew" name="action" tabindex="5">
|
||||
|
|
|
|||
|
|
@ -288,7 +288,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
position_display: positionMax_display + 1,
|
||||
is_default: 0,
|
||||
idnum: idnumMax + 1,
|
||||
api_type: document.getElementById('new_prompt_api_type').value
|
||||
api_type: document.getElementById('new_prompt_api_type').value,
|
||||
show_in: document.getElementById('selectShowInNew').value,
|
||||
};
|
||||
|
||||
switch(prefs.connection_type) {
|
||||
|
|
@ -663,6 +664,8 @@ function showItemRowEditor(tr) {
|
|||
tr.querySelector('.api_additional_info_show').style.display = 'none';
|
||||
tr.querySelector('.type_output').style.display = 'inline';
|
||||
tr.querySelector('.type_show').style.display = 'none';
|
||||
tr.querySelector('.show_in_output').style.display = 'inline';
|
||||
tr.querySelector('.show_in_show').style.display = 'none';
|
||||
const action_output = tr.querySelector('.action_output')
|
||||
action_output.style.display = 'inline';
|
||||
action_output.addEventListener('change', toggleDiffviewer);
|
||||
|
|
@ -688,6 +691,8 @@ function hideItemRowEditor(tr) {
|
|||
toggleAdditionalPropertiesShow(tr);
|
||||
tr.querySelector('.type_output').style.display = 'none';
|
||||
tr.querySelector('.type_show').style.display = 'inline';
|
||||
tr.querySelector('.show_in_output').style.display = 'none';
|
||||
tr.querySelector('.show_in_show').style.display = 'inline';
|
||||
const action_output = tr.querySelector('.action_output')
|
||||
action_output.style.display = 'none';
|
||||
action_output.addEventListener('change', toggleDiffviewer);
|
||||
|
|
@ -824,6 +829,7 @@ function handleCancelClick(e) {
|
|||
tr.querySelector('.text_output').value = sanitizeHtml(tr.querySelector('.text_show').innerHTML).replace(/<br\s*\/?>/gi, "\n");
|
||||
tr.querySelector('.type_output').value = tr.querySelector('.type').innerText;
|
||||
// tr.querySelector('.type_output').selectedOptions[0].text = tr.querySelector('.type_show').innerText;
|
||||
tr.querySelector('.show_in_output').value = tr.querySelector('.show_in').innerText || 'popup';
|
||||
tr.querySelector('.action_output').value = tr.querySelector('.action').innerText;
|
||||
// tr.querySelector('.action_output').selectedOptions[0].text = tr.querySelector('.action_show').innerText;
|
||||
tr.querySelector('.chatgpt_web_model_output').value = tr.querySelector('.chatgpt_web_model_show').innerText;
|
||||
|
|
@ -850,6 +856,7 @@ function handleConfirmClick(e) {
|
|||
newValues.name = tr.querySelector('.name_output').value.trim();
|
||||
newValues.text = tr.querySelector('.text_output').value;
|
||||
newValues.type = tr.querySelector('.type_output').value;
|
||||
newValues.show_in = tr.querySelector('.show_in_output').value;
|
||||
newValues.action = tr.querySelector('.action_output').value;
|
||||
newValues.need_selected = tr.querySelector('.need_selected').checked ? 1 : 0;
|
||||
newValues.need_signature = tr.querySelector('.need_signature').checked ? 1 : 0;
|
||||
|
|
@ -876,6 +883,8 @@ function handleConfirmClick(e) {
|
|||
// Update item data
|
||||
tr.querySelector('.type').innerText = tr.querySelector('.type_output').value;
|
||||
tr.querySelector('.type_show').innerText = tr.querySelector('.type_output').selectedOptions[0].text;
|
||||
tr.querySelector('.show_in').innerText = tr.querySelector('.show_in_output').value;
|
||||
tr.querySelector('.show_in_show').innerText = tr.querySelector('.show_in_output').selectedOptions[0].text;
|
||||
tr.querySelector('.action').innerText = tr.querySelector('.action_output').value;
|
||||
tr.querySelector('.action_show').innerText = tr.querySelector('.action_output').selectedOptions[0].text;
|
||||
if (newValues.api_type !== '') {
|
||||
|
|
@ -951,6 +960,7 @@ function handleCopyClick(e) {
|
|||
document.getElementById('txtNameNew').value = name + ' (' + browser.i18n.getMessage("copy_text") + ')';
|
||||
document.getElementById('txtTextNew').value = text;
|
||||
document.getElementById('selectTypeNew').value = type;
|
||||
document.getElementById('selectShowInNew').value = tr.querySelector('.show_in_output').value || 'popup';
|
||||
document.getElementById('selectActionNew').value = action;
|
||||
|
||||
document.getElementById('checkboxNeedSelectedNew').checked = need_selected;
|
||||
|
|
@ -1019,7 +1029,7 @@ function loadPromptsList(values){
|
|||
}
|
||||
|
||||
let options = {
|
||||
valueNames: [ { data: ['idnum'] }, 'is_default', 'id', 'name', 'text', 'type', 'action', 'position_compose', 'position_display', { name: 'need_selected', attr: 'checked_val'}, { name: 'need_signature', attr: 'checked_val'}, { name: 'need_custom_text', attr: 'checked_val'}, { name: 'define_response_lang', attr: 'checked_val'}, { name: 'use_diff_viewer', attr: 'checked_val'}, { name: 'enabled', attr: 'checked_val'}, 'api_type', ...api_fields ],
|
||||
valueNames: [ { data: ['idnum'] }, 'is_default', 'id', 'name', 'text', 'type', 'action', 'position_compose', 'position_display', 'show_in', { name: 'need_selected', attr: 'checked_val'}, { name: 'need_signature', attr: 'checked_val'}, { name: 'need_custom_text', attr: 'checked_val'}, { name: 'define_response_lang', attr: 'checked_val'}, { name: 'use_diff_viewer', attr: 'checked_val'}, { name: 'enabled', attr: 'checked_val'}, 'api_type', ...api_fields ],
|
||||
item: function(values) {
|
||||
let type_output = '';
|
||||
switch(String(values.type)){
|
||||
|
|
@ -1046,7 +1056,18 @@ function loadPromptsList(values){
|
|||
action_output = `__MSG_customPrompts_substitute_text__`;
|
||||
break;
|
||||
}
|
||||
//console.log('>>>>>>>>>>>>> action_output: ' + JSON.stringify(action_output));
|
||||
let show_in_output = '';
|
||||
switch(String(values.show_in || 'popup')){
|
||||
case "popup":
|
||||
show_in_output = `__MSG_show_in_popup__`;
|
||||
break;
|
||||
case "context":
|
||||
show_in_output = `__MSG_show_in_context__`;
|
||||
break;
|
||||
case "both":
|
||||
show_in_output = `__MSG_show_in_both__`;
|
||||
break;
|
||||
}
|
||||
|
||||
let output = `<tr ` + ((values.is_default == 1) ? 'class="is_default"':'') + `>
|
||||
<td class="w08"><span class="id id_show"></span><input type="text" class="hiddendata id_output" value="` + values.id + `" /></td>
|
||||
|
|
@ -1095,6 +1116,16 @@ function loadPromptsList(values){
|
|||
</select>` +
|
||||
`<span class="type hiddendata"></span>
|
||||
<br><br>
|
||||
<span class="field_title_s">__MSG_show_in__:</span>
|
||||
<br>
|
||||
<span class="show_in_show">` + show_in_output + `</span>
|
||||
<select class="show_in_output hiddendata input_mod">
|
||||
<option value="popup"` + ((values.show_in == "popup" || !values.show_in) ? ' selected':'') + `>__MSG_show_in_popup__</option>
|
||||
<option value="context"` + ((values.show_in == "context") ? ' selected':'') + `>__MSG_show_in_context__</option>
|
||||
<option value="both"` + ((values.show_in == "both") ? ' selected':'') + `>__MSG_show_in_both__</option>
|
||||
</select>` +
|
||||
`<span class="show_in hiddendata"></span>
|
||||
<br><br>
|
||||
<span class="field_title_s">__MSG_customPrompts_form_label_Action__:</span>
|
||||
<br><span class="action_show">` + action_output + `</span>
|
||||
<select class="action_output hiddendata">
|
||||
|
|
@ -1251,6 +1282,7 @@ function clearFields() {
|
|||
document.getElementById('chatGPTWebProjectNew').value = '';
|
||||
document.getElementById('chatGPTWebCustomGPTNew').value = '';
|
||||
document.getElementById('selectTypeNew').value = '0';
|
||||
document.getElementById('selectShowInNew').value = 'popup';
|
||||
document.getElementById('selectActionNew').value = '0';
|
||||
document.getElementById('checkboxNeedSelectedNew').value = '0';
|
||||
document.getElementById('checkboxNeedSignatureNew').value = '0';
|
||||
|
|
|
|||
61
pages/menu_order/mzta-custom-menu-icons.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
|
||||
* Copyright (C) 2024 - 2026 Mic (m@micz.it)
|
||||
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export const customMenuIconsPath = 'images/context_menu/custom/';
|
||||
|
||||
export const customMenuIcons = [
|
||||
'adventure-game.png',
|
||||
'airport.png',
|
||||
'alert-sign.png',
|
||||
'calendar.png',
|
||||
'cash.png',
|
||||
'clapboard.png',
|
||||
'clock.png',
|
||||
'coins.png',
|
||||
'copywriting.png',
|
||||
'customer-service.png',
|
||||
'deadline.png',
|
||||
'discount.png',
|
||||
'earth.png',
|
||||
'electric-car.png',
|
||||
'experiment.png',
|
||||
'express-delivery.png',
|
||||
'flammable.png',
|
||||
'home.png',
|
||||
'idea.png',
|
||||
'info.png',
|
||||
'invoice.png',
|
||||
'justice-scale.png',
|
||||
'like.png',
|
||||
'love-letter.png',
|
||||
'policeman.png',
|
||||
'printer.png',
|
||||
'profit.png',
|
||||
'puzzle-game.png',
|
||||
'radiation-hazard.png',
|
||||
'receipt.png',
|
||||
'recycle-sign.png',
|
||||
'science.png',
|
||||
'scissors.png',
|
||||
'star.png',
|
||||
'start-up.png',
|
||||
'sticky-notes.png',
|
||||
'target.png',
|
||||
'thermometer.png',
|
||||
'trophy.png',
|
||||
];
|
||||
402
pages/menu_order/mzta-menu-order.css
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
.page_title {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
#command_palette {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding-top: 1px;
|
||||
height: 26px;
|
||||
width: 100%;
|
||||
z-index: 90;
|
||||
margin-bottom: 16px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
#btnSaveAll {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#msgDisplay {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
#lists_container {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.menu_list_panel {
|
||||
flex: 1;
|
||||
min-width: 320px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.menu_list_panel h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.sub_tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-bottom: 0;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.sub_tab {
|
||||
padding: 4px 14px;
|
||||
border: 1px solid #ccc;
|
||||
background: #f5f5f5;
|
||||
cursor: pointer;
|
||||
border-radius: 4px 4px 0 0;
|
||||
font-size: 0.9em;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.sub_tab.active {
|
||||
background: #fff;
|
||||
border-bottom-color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.section_label {
|
||||
font-size: 0.85em;
|
||||
color: #666;
|
||||
margin: 8px 0 4px 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.hidden_section_label {
|
||||
border-top: 1px dashed #ccc;
|
||||
padding-top: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.sortable_list,
|
||||
.hidden_list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.sortable_item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
margin: 3px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
user-select: none;
|
||||
transition: background-color 0.15s;
|
||||
}
|
||||
|
||||
.sortable_item:hover {
|
||||
background: #f0f4ff;
|
||||
}
|
||||
|
||||
.sortable_item.dragging {
|
||||
opacity: 0.4;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.sortable_item.drag-over {
|
||||
border-top: 2px solid #409df3;
|
||||
}
|
||||
|
||||
.drag_handle {
|
||||
cursor: grab;
|
||||
color: #999;
|
||||
font-size: 1.1em;
|
||||
padding: 0 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.drag_handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.item_toggle {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item_name {
|
||||
flex: 1;
|
||||
font-size: 0.92em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.72em;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.badge_default {
|
||||
background: #e0e0e0;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.badge_special {
|
||||
background: #d4eaff;
|
||||
color: #1a5fa0;
|
||||
}
|
||||
|
||||
.badge_custom {
|
||||
background: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.badge_type {
|
||||
background: #fff3e0;
|
||||
color: #e65100;
|
||||
}
|
||||
|
||||
.item_icon_preview {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
padding: 1px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.item_icon_preview:hover {
|
||||
border-color: #409df3;
|
||||
background: #eef5ff;
|
||||
}
|
||||
|
||||
.item_icon_preview_empty {
|
||||
display: inline-block;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.item_icon_preview_editable {
|
||||
border-color: #b0c4de;
|
||||
}
|
||||
|
||||
.item_icon_preview_special {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.item_icon_preview_special:hover {
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.icon_picker_popover {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
background: #fff;
|
||||
border: 1px solid #bbb;
|
||||
border-radius: 6px;
|
||||
padding: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 28px);
|
||||
gap: 4px;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.icon_picker_cell {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 2px;
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.icon_picker_cell img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.icon_picker_cell:hover {
|
||||
background: #eef5ff;
|
||||
border-color: #409df3;
|
||||
}
|
||||
|
||||
.icon_picker_cell.selected {
|
||||
background: #d4eaff;
|
||||
border-color: #409df3;
|
||||
}
|
||||
|
||||
.icon_picker_cell_none {
|
||||
font-size: 1.2em;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
/* Hidden list items */
|
||||
.hidden_list .sortable_item {
|
||||
opacity: 0.6;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.hidden_list .sortable_item .drag_handle {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.hidden_list .sortable_item:hover {
|
||||
opacity: 0.8;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
/* Dark mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #1c1b22;
|
||||
color: rgb(251, 251, 254);
|
||||
}
|
||||
|
||||
#command_palette {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
#msgDisplay {
|
||||
color: #5cb85c;
|
||||
}
|
||||
|
||||
.menu_list_panel {
|
||||
border-color: #444;
|
||||
background: #2a2a30;
|
||||
}
|
||||
|
||||
.sub_tabs {
|
||||
border-bottom-color: #555;
|
||||
}
|
||||
|
||||
.sub_tab {
|
||||
border-color: #555;
|
||||
background: #333;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.sub_tab.active {
|
||||
background: #2a2a30;
|
||||
border-bottom-color: #2a2a30;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.section_label {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.hidden_section_label {
|
||||
border-top-color: #555;
|
||||
}
|
||||
|
||||
.sortable_item {
|
||||
border-color: #444;
|
||||
background: #333;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.sortable_item:hover {
|
||||
background: #3a3a44;
|
||||
}
|
||||
|
||||
.sortable_item.drag-over {
|
||||
border-top-color: #409df3;
|
||||
}
|
||||
|
||||
.drag_handle {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.badge_default {
|
||||
background: #444;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.badge_special {
|
||||
background: #1a3a5c;
|
||||
color: #7ab8f5;
|
||||
}
|
||||
|
||||
.badge_custom {
|
||||
background: #1b3a1e;
|
||||
color: #81c784;
|
||||
}
|
||||
|
||||
.badge_type {
|
||||
background: #3d2800;
|
||||
color: #ffb74d;
|
||||
}
|
||||
|
||||
.item_icon_preview_editable {
|
||||
border-color: #556;
|
||||
}
|
||||
|
||||
.item_icon_preview:hover {
|
||||
background: #3a3a44;
|
||||
border-color: #409df3;
|
||||
}
|
||||
|
||||
.icon_picker_popover {
|
||||
background: #2a2a30;
|
||||
border-color: #555;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.icon_picker_cell:hover {
|
||||
background: #3a3a44;
|
||||
}
|
||||
|
||||
.icon_picker_cell.selected {
|
||||
background: #1a3a5c;
|
||||
}
|
||||
|
||||
.icon_picker_cell_none {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.hidden_list .sortable_item {
|
||||
background: #2a2a2e;
|
||||
}
|
||||
|
||||
.hidden_list .sortable_item:hover {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
a:link { color: #409eff; }
|
||||
a:visited { color: #409eff; }
|
||||
a:hover { color: #66b1ff; }
|
||||
a:active { color: #66b1ff; }
|
||||
|
||||
.item_icon_preview_empty {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.icon_picker_cell_none img {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
39
pages/menu_order/mzta-menu-order.html
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>ThunderAI - __MSG_menu_order_title__</title>
|
||||
<link rel="stylesheet" href="mzta-menu-order.css" />
|
||||
<link rel="icon" href="../../images/icon-16px.png" />
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="page_title">__MSG_menu_order_title__</h1>
|
||||
<p>__MSG_menu_order_info__</p>
|
||||
<div id="command_palette">
|
||||
<button id="btnSaveAll" disabled>__MSG_btnSaveAll_string__</button>
|
||||
<span id="msgDisplay"></span>
|
||||
</div>
|
||||
<div id="lists_container">
|
||||
<div class="menu_list_panel" id="popup_panel">
|
||||
<h2>__MSG_menu_order_popup_list_title__</h2>
|
||||
<div class="sub_tabs">
|
||||
<button class="sub_tab active" data-view="display">__MSG_menu_order_tab_reading__</button>
|
||||
<button class="sub_tab" data-view="compose">__MSG_menu_order_tab_composing__</button>
|
||||
</div>
|
||||
<h3 class="section_label">__MSG_menu_order_active_items__</h3>
|
||||
<ul id="popup_list" class="sortable_list"></ul>
|
||||
<h3 class="section_label hidden_section_label">__MSG_menu_order_hidden_items__</h3>
|
||||
<ul id="popup_list_hidden" class="hidden_list"></ul>
|
||||
</div>
|
||||
<div class="menu_list_panel" id="context_panel">
|
||||
<h2>__MSG_menu_order_context_list_title__</h2>
|
||||
<h3 class="section_label">__MSG_menu_order_active_items__</h3>
|
||||
<ul id="context_list" class="sortable_list"></ul>
|
||||
<h3 class="section_label hidden_section_label">__MSG_menu_order_hidden_items__</h3>
|
||||
<ul id="context_list_hidden" class="hidden_list"></ul>
|
||||
</div>
|
||||
</div>
|
||||
<script src="mzta-menu-order.js" type="module"></script>
|
||||
<script src="../../js/mzta-i18n.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
523
pages/menu_order/mzta-menu-order.js
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
/*
|
||||
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
|
||||
* Copyright (C) 2024 - 2026 Mic (m@micz.it)
|
||||
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
getPrompts,
|
||||
setDefaultPromptsProperties,
|
||||
setCustomPrompts,
|
||||
setSpecialPrompts,
|
||||
getHiddenSpecialPromptIds
|
||||
} from '../../js/mzta-prompts.js';
|
||||
import {
|
||||
i18nConditionalGet,
|
||||
specialPromptToContextMenuID,
|
||||
contextMenuIconsPath
|
||||
} from '../../js/mzta-utils.js';
|
||||
import {
|
||||
customMenuIcons,
|
||||
customMenuIconsPath
|
||||
} from './mzta-custom-menu-icons.js';
|
||||
|
||||
// Convert "moz-extension:images/foo.png" to a relative path usable from this page
|
||||
function resolveSpecialIconPath(promptId) {
|
||||
const ctxId = specialPromptToContextMenuID[promptId];
|
||||
if (!ctxId) return '';
|
||||
const raw = contextMenuIconsPath[ctxId];
|
||||
if (!raw) return '';
|
||||
return '../../' + raw.replace(/^moz-extension:/, '');
|
||||
}
|
||||
|
||||
let allPrompts = [];
|
||||
let allExcludedSpecialPrompts = []; // special prompts excluded from UI (hidden + inactive features), preserved on save
|
||||
let allDisabledPrompts = []; // default/custom prompts disabled (enabled=0), excluded from UI, preserved on save
|
||||
let currentPopupView = 'display'; // 'display' or 'compose'
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await loadAndRender();
|
||||
initSubTabs();
|
||||
|
||||
document.getElementById('btnSaveAll').addEventListener('click', saveAll);
|
||||
|
||||
// If prompts are modified elsewhere (e.g. custom prompts page saving), reload this page's data.
|
||||
// Any unsaved changes on this page are discarded to avoid overwriting the other page's changes.
|
||||
let reloadDebounce = null;
|
||||
browser.storage.onChanged.addListener((changes, areaName) => {
|
||||
if (areaName !== 'local') return;
|
||||
if (!(changes._default_prompts_properties || changes._custom_prompt || changes._special_prompts)) return;
|
||||
clearTimeout(reloadDebounce);
|
||||
reloadDebounce = setTimeout(() => {
|
||||
document.getElementById('btnSaveAll').disabled = true;
|
||||
const msgDisplay = document.getElementById('msgDisplay');
|
||||
msgDisplay.textContent = '';
|
||||
msgDisplay.style.display = 'none';
|
||||
loadAndRender();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
i18n.updateDocument();
|
||||
});
|
||||
|
||||
async function loadAndRender() {
|
||||
allPrompts = await getPrompts(false, [], true);
|
||||
|
||||
// Exclude special prompts that are defined with show_in: "none" (internal prompts, not user-toggleable)
|
||||
// and special prompts whose feature is not active (e.g. add_tags disabled, sparks not present)
|
||||
const hiddenSpecialIds = getHiddenSpecialPromptIds();
|
||||
const activeSpecialIds = await browser.runtime.sendMessage({ command: "get_active_special_ids" });
|
||||
allExcludedSpecialPrompts = allPrompts.filter(p =>
|
||||
hiddenSpecialIds.includes(p.id) ||
|
||||
(String(p.is_special) === '1' && !activeSpecialIds.includes(p.id))
|
||||
);
|
||||
allPrompts = allPrompts.filter(p => !allExcludedSpecialPrompts.some(e => e.id === p.id));
|
||||
|
||||
// Exclude disabled prompts (enabled=0) from the UI, preserve them for save
|
||||
allDisabledPrompts = allPrompts.filter(p => String(p.enabled) === '0');
|
||||
allPrompts = allPrompts.filter(p => String(p.enabled) !== '0');
|
||||
|
||||
// Resolve i18n names and assign initial position_context if missing
|
||||
let contextPos = 1;
|
||||
const sortedForContext = [...allPrompts].sort((a, b) => {
|
||||
const nameA = i18nConditionalGet(a.name);
|
||||
const nameB = i18nConditionalGet(b.name);
|
||||
return nameA.localeCompare(nameB);
|
||||
});
|
||||
sortedForContext.forEach(p => {
|
||||
if (p.position_context === undefined || p.position_context === '' || p.position_context === 'undefined') {
|
||||
p.position_context = contextPos;
|
||||
}
|
||||
contextPos++;
|
||||
});
|
||||
|
||||
// Resolve display names
|
||||
allPrompts.forEach(p => {
|
||||
p._displayName = i18nConditionalGet(p.name);
|
||||
});
|
||||
|
||||
renderPopupList();
|
||||
renderContextList();
|
||||
}
|
||||
|
||||
// ==================== Sub-tabs ====================
|
||||
|
||||
function initSubTabs() {
|
||||
document.querySelectorAll('.sub_tab').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.sub_tab').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
currentPopupView = btn.dataset.view;
|
||||
renderPopupList();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== Render Popup List ====================
|
||||
|
||||
function renderPopupList() {
|
||||
const posKey = currentPopupView === 'display' ? 'position_display' : 'position_compose';
|
||||
// Filter by type: reading view shows type 0+1, composing view shows type 0+2
|
||||
const allowedTypes = currentPopupView === 'display' ? ['0', '1'] : ['0', '2'];
|
||||
const typeFiltered = allPrompts.filter(p => allowedTypes.includes(String(p.type)));
|
||||
|
||||
const activeItems = typeFiltered.filter(p => {
|
||||
const showIn = p.show_in || 'popup';
|
||||
return showIn === 'popup' || showIn === 'both';
|
||||
});
|
||||
const hiddenItems = typeFiltered.filter(p => {
|
||||
const showIn = p.show_in || 'popup';
|
||||
return showIn !== 'popup' && showIn !== 'both';
|
||||
});
|
||||
|
||||
activeItems.sort((a, b) => (a[posKey] || 9999) - (b[posKey] || 9999));
|
||||
hiddenItems.sort((a, b) => a._displayName.localeCompare(b._displayName));
|
||||
|
||||
const activeList = document.getElementById('popup_list');
|
||||
const hiddenList = document.getElementById('popup_list_hidden');
|
||||
|
||||
renderListItems(activeList, activeItems, 'popup', true);
|
||||
renderListItems(hiddenList, hiddenItems, 'popup', false);
|
||||
|
||||
initDragAndDrop(activeList, posKey);
|
||||
}
|
||||
|
||||
// ==================== Render Context List ====================
|
||||
|
||||
function renderContextList() {
|
||||
// Only show items with type 0 or 1 (not composing-only)
|
||||
const contextEligible = allPrompts.filter(p => String(p.type) === '0' || String(p.type) === '1');
|
||||
|
||||
const activeItems = contextEligible.filter(p => {
|
||||
const showIn = p.show_in || 'popup';
|
||||
return showIn === 'context' || showIn === 'both';
|
||||
});
|
||||
const hiddenItems = contextEligible.filter(p => {
|
||||
const showIn = p.show_in || 'popup';
|
||||
return showIn !== 'context' && showIn !== 'both';
|
||||
});
|
||||
|
||||
activeItems.sort((a, b) => (a.position_context || 9999) - (b.position_context || 9999));
|
||||
hiddenItems.sort((a, b) => a._displayName.localeCompare(b._displayName));
|
||||
|
||||
const activeList = document.getElementById('context_list');
|
||||
const hiddenList = document.getElementById('context_list_hidden');
|
||||
|
||||
renderListItems(activeList, activeItems, 'context', true);
|
||||
renderListItems(hiddenList, hiddenItems, 'context', false);
|
||||
|
||||
initDragAndDrop(activeList, 'position_context');
|
||||
}
|
||||
|
||||
// ==================== Render List Items ====================
|
||||
|
||||
function renderListItems(listEl, items, menuType, isActive) {
|
||||
listEl.innerHTML = '';
|
||||
items.forEach(prompt => {
|
||||
const li = document.createElement('li');
|
||||
li.classList.add('sortable_item');
|
||||
li.dataset.id = prompt.id;
|
||||
if (isActive) {
|
||||
li.draggable = true;
|
||||
}
|
||||
|
||||
// Drag handle
|
||||
const handle = document.createElement('span');
|
||||
handle.classList.add('drag_handle');
|
||||
handle.textContent = '\u2630';
|
||||
li.appendChild(handle);
|
||||
|
||||
// Icon slot (context menu only) - between handle and toggle, to keep rows aligned
|
||||
if (menuType === 'context') {
|
||||
if (String(prompt.is_special) === '1') {
|
||||
li.appendChild(buildSpecialIconDisplay(prompt));
|
||||
} else {
|
||||
li.appendChild(buildIconPicker(prompt));
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle checkbox
|
||||
const toggle = document.createElement('input');
|
||||
toggle.type = 'checkbox';
|
||||
toggle.classList.add('item_toggle');
|
||||
toggle.checked = isActive;
|
||||
toggle.addEventListener('change', () => {
|
||||
toggleShowIn(prompt, menuType, toggle.checked);
|
||||
});
|
||||
li.appendChild(toggle);
|
||||
|
||||
// Name
|
||||
const nameSpan = document.createElement('span');
|
||||
nameSpan.classList.add('item_name');
|
||||
nameSpan.textContent = prompt._displayName;
|
||||
li.appendChild(nameSpan);
|
||||
|
||||
// Type badge
|
||||
const typeBadge = document.createElement('span');
|
||||
typeBadge.classList.add('badge', 'badge_type');
|
||||
if (String(prompt.type) === '1') {
|
||||
typeBadge.textContent = browser.i18n.getMessage('menu_order_type_reading');
|
||||
} else if (String(prompt.type) === '2') {
|
||||
typeBadge.textContent = browser.i18n.getMessage('menu_order_type_composing');
|
||||
} else {
|
||||
typeBadge.textContent = browser.i18n.getMessage('menu_order_type_always');
|
||||
}
|
||||
li.appendChild(typeBadge);
|
||||
|
||||
// Source badge
|
||||
const sourceBadge = document.createElement('span');
|
||||
sourceBadge.classList.add('badge');
|
||||
if (String(prompt.is_special) === '1') {
|
||||
sourceBadge.classList.add('badge_special');
|
||||
sourceBadge.textContent = browser.i18n.getMessage('menu_order_badge_special');
|
||||
} else if (String(prompt.is_default) === '1') {
|
||||
sourceBadge.classList.add('badge_default');
|
||||
sourceBadge.textContent = browser.i18n.getMessage('menu_order_badge_default');
|
||||
} else {
|
||||
sourceBadge.classList.add('badge_custom');
|
||||
sourceBadge.textContent = browser.i18n.getMessage('menu_order_badge_custom');
|
||||
}
|
||||
li.appendChild(sourceBadge);
|
||||
|
||||
listEl.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== Custom icon picker ====================
|
||||
|
||||
let activeIconPopover = null;
|
||||
|
||||
function closeIconPopover() {
|
||||
if (activeIconPopover) {
|
||||
activeIconPopover.remove();
|
||||
activeIconPopover = null;
|
||||
document.removeEventListener('mousedown', onDocMouseDownForPopover, true);
|
||||
document.removeEventListener('keydown', onDocKeyDownForPopover, true);
|
||||
}
|
||||
}
|
||||
|
||||
function onDocMouseDownForPopover(e) {
|
||||
if (activeIconPopover && !activeIconPopover.contains(e.target) && !e.target.classList.contains('item_icon_preview')) {
|
||||
closeIconPopover();
|
||||
}
|
||||
}
|
||||
|
||||
function onDocKeyDownForPopover(e) {
|
||||
if (e.key === 'Escape') closeIconPopover();
|
||||
}
|
||||
|
||||
function applyIconToPreview(preview, filename) {
|
||||
if (filename) {
|
||||
preview.src = '../../' + customMenuIconsPath + filename;
|
||||
preview.classList.remove('item_icon_preview_empty');
|
||||
} else {
|
||||
preview.src = '../../' + customMenuIconsPath + 'empty_icon.png';
|
||||
preview.classList.add('item_icon_preview_empty');
|
||||
}
|
||||
}
|
||||
|
||||
function buildSpecialIconDisplay(prompt) {
|
||||
const img = document.createElement('img');
|
||||
img.classList.add('item_icon_preview', 'item_icon_preview_special');
|
||||
img.alt = '';
|
||||
const path = resolveSpecialIconPath(prompt.id);
|
||||
if (path) {
|
||||
img.src = path;
|
||||
} else {
|
||||
img.src = '../../' + customMenuIconsPath + 'empty_icon.png';
|
||||
img.classList.add('item_icon_preview_empty');
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
function buildIconPicker(prompt) {
|
||||
const preview = document.createElement('img');
|
||||
preview.classList.add('item_icon_preview', 'item_icon_preview_editable');
|
||||
preview.alt = '';
|
||||
preview.title = browser.i18n.getMessage('menu_order_icon_label');
|
||||
applyIconToPreview(preview, prompt.custom_icon || '');
|
||||
|
||||
preview.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
if (activeIconPopover && activeIconPopover.dataset.forId === prompt.id) {
|
||||
closeIconPopover();
|
||||
return;
|
||||
}
|
||||
closeIconPopover();
|
||||
openIconPopover(preview, prompt);
|
||||
});
|
||||
|
||||
return preview;
|
||||
}
|
||||
|
||||
function openIconPopover(anchorEl, prompt) {
|
||||
const popover = document.createElement('div');
|
||||
popover.classList.add('icon_picker_popover');
|
||||
popover.dataset.forId = prompt.id;
|
||||
|
||||
// "None" option
|
||||
const noneBtn = document.createElement('button');
|
||||
noneBtn.type = 'button';
|
||||
noneBtn.classList.add('icon_picker_cell', 'icon_picker_cell_none');
|
||||
noneBtn.title = browser.i18n.getMessage('menu_order_icon_none');
|
||||
const noneImg = document.createElement('img');
|
||||
noneImg.src = '../../' + customMenuIconsPath + 'empty_icon.png';
|
||||
noneImg.alt = '';
|
||||
noneBtn.appendChild(noneImg);
|
||||
if (!prompt.custom_icon) noneBtn.classList.add('selected');
|
||||
noneBtn.addEventListener('click', () => {
|
||||
prompt.custom_icon = '';
|
||||
applyIconToPreview(anchorEl, '');
|
||||
markUnsaved();
|
||||
closeIconPopover();
|
||||
});
|
||||
popover.appendChild(noneBtn);
|
||||
|
||||
customMenuIcons.forEach(filename => {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.classList.add('icon_picker_cell');
|
||||
btn.title = filename.replace(/\.[^.]+$/, '');
|
||||
if (filename === prompt.custom_icon) btn.classList.add('selected');
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = '../../' + customMenuIconsPath + filename;
|
||||
img.alt = '';
|
||||
btn.appendChild(img);
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
prompt.custom_icon = filename;
|
||||
applyIconToPreview(anchorEl, filename);
|
||||
markUnsaved();
|
||||
closeIconPopover();
|
||||
});
|
||||
popover.appendChild(btn);
|
||||
});
|
||||
|
||||
document.body.appendChild(popover);
|
||||
activeIconPopover = popover;
|
||||
|
||||
// Position popover below the anchor
|
||||
const rect = anchorEl.getBoundingClientRect();
|
||||
const popRect = popover.getBoundingClientRect();
|
||||
let left = rect.left + window.scrollX;
|
||||
let top = rect.bottom + window.scrollY + 4;
|
||||
if (left + popRect.width > window.scrollX + document.documentElement.clientWidth - 8) {
|
||||
left = window.scrollX + document.documentElement.clientWidth - popRect.width - 8;
|
||||
}
|
||||
if (left < window.scrollX + 4) left = window.scrollX + 4;
|
||||
popover.style.left = left + 'px';
|
||||
popover.style.top = top + 'px';
|
||||
|
||||
document.addEventListener('mousedown', onDocMouseDownForPopover, true);
|
||||
document.addEventListener('keydown', onDocKeyDownForPopover, true);
|
||||
}
|
||||
|
||||
// ==================== Toggle show_in ====================
|
||||
|
||||
function toggleShowIn(prompt, menuType, isOn) {
|
||||
const current = prompt.show_in || 'popup';
|
||||
|
||||
if (menuType === 'popup') {
|
||||
if (isOn) {
|
||||
prompt.show_in = (current === 'none') ? 'popup' : (current === 'context') ? 'both' : current;
|
||||
} else {
|
||||
prompt.show_in = (current === 'popup') ? 'none' : (current === 'both') ? 'context' : current;
|
||||
}
|
||||
} else { // context
|
||||
if (isOn) {
|
||||
prompt.show_in = (current === 'none') ? 'context' : (current === 'popup') ? 'both' : current;
|
||||
} else {
|
||||
prompt.show_in = (current === 'context') ? 'none' : (current === 'both') ? 'popup' : current;
|
||||
}
|
||||
}
|
||||
|
||||
markUnsaved();
|
||||
renderPopupList();
|
||||
renderContextList();
|
||||
}
|
||||
|
||||
// ==================== Drag and Drop ====================
|
||||
|
||||
function initDragAndDrop(listEl, positionKey) {
|
||||
let draggedItem = null;
|
||||
|
||||
listEl.addEventListener('dragstart', (e) => {
|
||||
const li = e.target.closest('.sortable_item');
|
||||
if (!li) return;
|
||||
draggedItem = li;
|
||||
draggedItem.classList.add('dragging');
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/plain', li.dataset.id);
|
||||
});
|
||||
|
||||
listEl.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
if (!draggedItem) return;
|
||||
|
||||
// Remove previous drag-over indicators
|
||||
listEl.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
|
||||
|
||||
const afterElement = getDragAfterElement(listEl, e.clientY);
|
||||
if (afterElement) {
|
||||
afterElement.classList.add('drag-over');
|
||||
listEl.insertBefore(draggedItem, afterElement);
|
||||
} else {
|
||||
listEl.appendChild(draggedItem);
|
||||
}
|
||||
});
|
||||
|
||||
listEl.addEventListener('dragleave', (e) => {
|
||||
if (e.target.classList) {
|
||||
e.target.classList.remove('drag-over');
|
||||
}
|
||||
});
|
||||
|
||||
listEl.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
listEl.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
|
||||
});
|
||||
|
||||
listEl.addEventListener('dragend', () => {
|
||||
if (draggedItem) {
|
||||
draggedItem.classList.remove('dragging');
|
||||
updatePositionsFromDOM(listEl, positionKey);
|
||||
draggedItem = null;
|
||||
markUnsaved();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getDragAfterElement(container, y) {
|
||||
const draggableElements = [...container.querySelectorAll('.sortable_item:not(.dragging)')];
|
||||
return draggableElements.reduce((closest, child) => {
|
||||
const box = child.getBoundingClientRect();
|
||||
const offset = y - box.top - box.height / 2;
|
||||
if (offset < 0 && offset > closest.offset) {
|
||||
return { offset: offset, element: child };
|
||||
}
|
||||
return closest;
|
||||
}, { offset: Number.NEGATIVE_INFINITY }).element;
|
||||
}
|
||||
|
||||
function updatePositionsFromDOM(listEl, positionKey) {
|
||||
const items = listEl.querySelectorAll('.sortable_item');
|
||||
items.forEach((li, index) => {
|
||||
const promptId = li.dataset.id;
|
||||
const prompt = allPrompts.find(p => p.id === promptId);
|
||||
if (prompt) {
|
||||
prompt[positionKey] = index + 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== Save ====================
|
||||
|
||||
async function saveAll() {
|
||||
const btnSaveAll = document.getElementById('btnSaveAll');
|
||||
const msgDisplay = document.getElementById('msgDisplay');
|
||||
btnSaveAll.disabled = true;
|
||||
|
||||
const disabledDefaults = allDisabledPrompts.filter(p => String(p.is_default) === '1' && String(p.is_special) !== '1');
|
||||
const disabledCustoms = allDisabledPrompts.filter(p => String(p.is_default) === '0' && String(p.is_special) !== '1');
|
||||
const disabledSpecials = allDisabledPrompts.filter(p => String(p.is_special) === '1');
|
||||
|
||||
const defaultPromptsToSave = allPrompts.filter(p => String(p.is_default) === '1' && String(p.is_special) !== '1').concat(disabledDefaults);
|
||||
const customPromptsToSave = allPrompts.filter(p => String(p.is_default) === '0' && String(p.is_special) !== '1').concat(disabledCustoms);
|
||||
const specialPromptsToSave = allPrompts.filter(p => String(p.is_special) === '1').concat(disabledSpecials).concat(allExcludedSpecialPrompts);
|
||||
|
||||
await setDefaultPromptsProperties(defaultPromptsToSave);
|
||||
await setCustomPrompts(customPromptsToSave);
|
||||
await setSpecialPrompts(specialPromptsToSave);
|
||||
|
||||
await browser.runtime.sendMessage({ command: "reload_menus" });
|
||||
|
||||
msgDisplay.textContent = browser.i18n.getMessage('menu_order_saved');
|
||||
msgDisplay.style.display = 'inline';
|
||||
msgDisplay.style.color = 'green';
|
||||
setTimeout(() => {
|
||||
msgDisplay.textContent = '';
|
||||
msgDisplay.style.display = 'none';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function markUnsaved() {
|
||||
document.getElementById('btnSaveAll').disabled = false;
|
||||
const msgDisplay = document.getElementById('msgDisplay');
|
||||
msgDisplay.textContent = browser.i18n.getMessage('customPrompts_unsaved_changes');
|
||||
msgDisplay.style.display = 'inline';
|
||||
msgDisplay.style.color = 'red';
|
||||
}
|
||||
|
|
@ -18,34 +18,15 @@
|
|||
|
||||
import { prefs_default } from "../options/mzta-options-default.js";
|
||||
import { taLogger } from "../js/mzta-logger.js";
|
||||
import {
|
||||
checkSparksPresence,
|
||||
checkAPIIntegration,
|
||||
} from "../js/mzta-utils.js";
|
||||
|
||||
let menuSendImmediately = false;
|
||||
let taLog = console;
|
||||
let connection_type = 'chatgpt_web';
|
||||
let add_tags = false;
|
||||
let add_tags_use_specific_integration = false;
|
||||
let add_tags_connection_type = '';
|
||||
let get_calendar_event = false;
|
||||
let get_calendar_event_from_clipboard = false;
|
||||
let get_task = false;
|
||||
let _ok_sparks = false;
|
||||
let tabType;
|
||||
let num_special_menu_items = 0;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
let prefs = await browser.storage.sync.get({
|
||||
do_debug: prefs_default.do_debug,
|
||||
dynamic_menu_force_enter: prefs_default.dynamic_menu_force_enter,
|
||||
add_tags: prefs_default.add_tags,
|
||||
add_tags_use_specific_integration: prefs_default.add_tags_use_specific_integration,
|
||||
add_tags_connection_type: prefs_default.add_tags_connection_type,
|
||||
get_calendar_event: prefs_default.get_calendar_event,
|
||||
get_calendar_event_from_clipboard: prefs_default.get_calendar_event_from_clipboard,
|
||||
get_task: prefs_default.get_task,
|
||||
connection_type: prefs_default.connection_type
|
||||
});
|
||||
taLog = new taLogger("mzta-popup",prefs.do_debug);
|
||||
|
|
@ -63,19 +44,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
});
|
||||
taLog.log("active_prompts: " + JSON.stringify(active_prompts));
|
||||
menuSendImmediately = prefs.dynamic_menu_force_enter;
|
||||
connection_type = prefs.connection_type;
|
||||
add_tags = prefs.add_tags;
|
||||
add_tags_use_specific_integration = prefs.add_tags_use_specific_integration;
|
||||
add_tags_connection_type = prefs.add_tags_connection_type;
|
||||
get_calendar_event = prefs.get_calendar_event;
|
||||
get_calendar_event_from_clipboard = prefs.get_calendar_event_from_clipboard;
|
||||
get_task = prefs.get_task;
|
||||
_ok_sparks = await checkSparksPresence() == 1;
|
||||
// console.log(">>>>>>>>>>>>>>>>> add_tags: " + add_tags);
|
||||
// console.log(">>>>>>>>>>>>>>>>> get_calendar_event: " + get_calendar_event);
|
||||
// console.log(">>>>>>>>>>>>>>>>> get_task: " + get_task);
|
||||
// console.log(">>>>>>>>>>>>>>>>> _ok_sparks: " + _ok_sparks);
|
||||
searchPrompt(active_prompts, tabId, tabType);
|
||||
searchPrompt(active_prompts, tabId, tabType, filtering);
|
||||
i18n.updateDocument();
|
||||
|
||||
if(prefs.connection_type === 'chatgpt_web'){
|
||||
|
|
@ -110,14 +79,12 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
}
|
||||
}, { once: true });
|
||||
|
||||
async function searchPrompt(allPrompts, tabId, tabType){
|
||||
async function searchPrompt(allPrompts, tabId, tabType, filtering){
|
||||
taLog.log("tabType: " + tabType);
|
||||
|
||||
let prefs_order = await browser.storage.sync.get({dynamic_menu_order_alphabet: true});
|
||||
|
||||
if(prefs_order.dynamic_menu_order_alphabet){
|
||||
allPrompts.sort((a, b) => a.label.localeCompare(b.label));
|
||||
}
|
||||
// Sort by position: use position_display for reading (filtering=1), position_compose for composing (filtering=2)
|
||||
const posKey = filtering === 2 ? 'position_compose' : 'position_display';
|
||||
allPrompts.sort((a, b) => (a[posKey] || 9999) - (b[posKey] || 9999));
|
||||
|
||||
// console.log(">>>>>>>>> allPrompts: " + JSON.stringify(allPrompts));
|
||||
|
||||
|
|
@ -161,68 +128,10 @@ async function searchPrompt(allPrompts, tabId, tabType){
|
|||
|
||||
|
||||
// Prepend numbers to the first 10 items
|
||||
// If add_tags is true and connection_type is not 'chatgpt_web' reserve 0 position for prompt_add_tags and 1 for prompt_get_calendar_event (0, if no prompt_add_tags is disabled)
|
||||
let max_num_el = 10
|
||||
let first_num_el = 0;
|
||||
|
||||
let do_add_tags = checkDoAddTags();
|
||||
let do_get_calendar_event = checkDoCalendarEvent();
|
||||
let do_get_calendar_event_from_clipboard = checkDoCalendarEventFromClipboard();
|
||||
let do_get_task = checkDoTask();
|
||||
|
||||
// console.log(">>>>>>>>>>> do_add_tags: " + do_add_tags);
|
||||
// console.log(">>>>>>>>>>> do_get_calendar_event: " + do_get_calendar_event);
|
||||
// console.log(">>>>>>>>>>> do_get_task: " + do_get_task);
|
||||
// console.log(">>>>>>>>>>> filteredData: " + JSON.stringify(filteredData));
|
||||
|
||||
num_special_menu_items = (do_add_tags ? 1 : 0) + (do_get_calendar_event ? 1 : 0) + (do_get_calendar_event_from_clipboard ? 1 : 0) + (do_get_task ? 1 : 0);
|
||||
// console.log(">>>>>>>>>>>> num_special_menu_items: " + num_special_menu_items);
|
||||
if(num_special_menu_items > 0){
|
||||
max_num_el -= num_special_menu_items;
|
||||
first_num_el = num_special_menu_items;
|
||||
// console.log(">>>>>>>>>>>>> max_num_el: " + max_num_el);
|
||||
// console.log(">>>>>>>>>>>>> first_num_el: " + first_num_el);
|
||||
if(do_add_tags){
|
||||
filteredData = ensurePromptAddTagsFirst(filteredData);
|
||||
if (!filteredData[0].numberPrepended) {
|
||||
filteredData[0].numberPrepended = 'true';
|
||||
filteredData[0].label = '0. ' + filteredData[0].label;
|
||||
}
|
||||
}
|
||||
if(do_get_calendar_event){
|
||||
filteredData = ensurePromptGetCalendarEventFirst(filteredData, do_add_tags);
|
||||
let gce_curr_pos = do_add_tags ? 1 : 0;
|
||||
if (!filteredData[gce_curr_pos].numberPrepended) {
|
||||
filteredData[gce_curr_pos].numberPrepended = 'true';
|
||||
filteredData[gce_curr_pos].label = gce_curr_pos + '. ' + filteredData[gce_curr_pos].label;
|
||||
}
|
||||
}
|
||||
if(do_get_calendar_event_from_clipboard){
|
||||
filteredData = ensurePromptGetCalendarEventFromClipboardFirst(filteredData, do_add_tags, do_get_calendar_event);
|
||||
let gcefc_curr_pos = (do_add_tags ? 1 : 0) + (do_get_calendar_event ? 1 : 0);
|
||||
if (!filteredData[gcefc_curr_pos].numberPrepended) {
|
||||
filteredData[gcefc_curr_pos].numberPrepended = 'true';
|
||||
filteredData[gcefc_curr_pos].label = gcefc_curr_pos + '. ' + filteredData[gcefc_curr_pos].label;
|
||||
}
|
||||
}
|
||||
if(do_get_task){
|
||||
filteredData = ensurePromptGetTaskFirst(filteredData, do_add_tags, do_get_calendar_event, do_get_calendar_event_from_clipboard);
|
||||
let gtask_curr_pos = (do_add_tags ? 1 : 0) + (do_get_calendar_event ? 1 : 0) + (do_get_calendar_event_from_clipboard ? 1 : 0);
|
||||
if (!filteredData[gtask_curr_pos].numberPrepended) {
|
||||
filteredData[gtask_curr_pos].numberPrepended = 'true';
|
||||
filteredData[gtask_curr_pos].label = gtask_curr_pos + '. ' + filteredData[gtask_curr_pos].label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(">>>>>>>>>>> filteredData after special items check: " + JSON.stringify(filteredData));
|
||||
|
||||
Array.from(filteredData).slice(first_num_el, max_num_el).forEach((item, index) => {
|
||||
let number = (index + first_num_el).toString();
|
||||
// Check if the number is already prepended to avoid duplication
|
||||
Array.from(filteredData).slice(0, 10).forEach((item, index) => {
|
||||
if (!item.numberPrepended) {
|
||||
item.label = `${number}. ${item.label}`;
|
||||
item.numberPrepended = 'true'; // Mark as prepended
|
||||
item.label = `${index}. ${item.label}`;
|
||||
item.numberPrepended = 'true';
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -234,7 +143,7 @@ async function searchPrompt(allPrompts, tabId, tabType){
|
|||
itemDiv.classList.add('mzta_autocomplete-item');
|
||||
itemDiv.textContent = item.label;
|
||||
itemDiv.setAttribute('data-id', item.id);
|
||||
if((item.id === 'prompt_add_tags')||(item.id === 'prompt_get_calendar_event')||(item.id === 'prompt_get_calendar_event_from_clipboard')||(item.id === 'prompt_get_task')){
|
||||
if(item.is_special == "1"){
|
||||
itemDiv.className += ' special_prompt';
|
||||
}
|
||||
|
||||
|
|
@ -367,9 +276,15 @@ async function sendPrompt(prompt_id, tabId){
|
|||
}
|
||||
|
||||
function filterPromptsForTab(prompts_data, filtering){
|
||||
// If filtering is 0, return the original array without any filters (btw it should not happen)
|
||||
// Filter by show_in: only show prompts visible in the popup
|
||||
let filtered = prompts_data.filter(prompt => {
|
||||
const showIn = prompt.show_in || "popup";
|
||||
return showIn === "popup" || showIn === "both";
|
||||
});
|
||||
|
||||
// If filtering is 0, return without type filter (should not happen)
|
||||
if (filtering === 0) {
|
||||
return prompts_data;
|
||||
return filtered;
|
||||
}
|
||||
|
||||
// Define the types to include based on the value of filtering
|
||||
|
|
@ -379,95 +294,10 @@ function filterPromptsForTab(prompts_data, filtering){
|
|||
} else if (filtering === 2) {
|
||||
allowedTypes = ["0", "2"];
|
||||
} else {
|
||||
// If filtering has an unexpected value, return the original data
|
||||
return prompts_data;
|
||||
return filtered;
|
||||
}
|
||||
|
||||
// Filter the array based on the allowed types
|
||||
return prompts_data.filter(prompt => allowedTypes.includes(prompt.type));
|
||||
return filtered.filter(prompt => allowedTypes.includes(prompt.type));
|
||||
}
|
||||
|
||||
function checkDoAddTags(){
|
||||
return add_tags && checkAPIIntegration(connection_type, add_tags_use_specific_integration,add_tags_connection_type) && (tabType !== 'messageCompose');
|
||||
}
|
||||
|
||||
function checkDoCalendarEvent(){
|
||||
return get_calendar_event && (connection_type !== "chatgpt_web" && tabType !== 'messageCompose') && _ok_sparks;
|
||||
}
|
||||
|
||||
function checkDoCalendarEventFromClipboard(){
|
||||
return get_calendar_event_from_clipboard && (connection_type !== "chatgpt_web" && tabType !== 'messageCompose') && _ok_sparks;
|
||||
}
|
||||
|
||||
function checkDoTask(){
|
||||
return get_task && (connection_type !== "chatgpt_web" && tabType !== 'messageCompose') && _ok_sparks;
|
||||
}
|
||||
|
||||
function ensurePromptAddTagsFirst(arr) {
|
||||
// Find the index of the object with id "prompt_add_tags"
|
||||
const index = arr.findIndex(item => item.id === "prompt_add_tags");
|
||||
|
||||
// If found and not already the first element
|
||||
if (index !== -1 && index !== 0) {
|
||||
// Remove it from its current position
|
||||
const [promptAddTags] = arr.splice(index, 1);
|
||||
// Add it to the beginning of the array
|
||||
arr.unshift(promptAddTags);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
function ensurePromptGetCalendarEventFirst(arr, do_add_tags) {
|
||||
// Find the index of the object with id "prompt_get_calendar_event"
|
||||
const index = arr.findIndex(item => item.id === "prompt_get_calendar_event");
|
||||
|
||||
// If found and needs repositioning
|
||||
if (index !== -1 && (do_add_tags ? index !== 1 : index !== 0)) {
|
||||
// Remove it from its current position
|
||||
const [promptAddTags] = arr.splice(index, 1);
|
||||
|
||||
// Add it to the specified position
|
||||
const targetPosition = do_add_tags ? 1 : 0;
|
||||
arr.splice(targetPosition, 0, promptAddTags);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
function ensurePromptGetCalendarEventFromClipboardFirst(arr, do_add_tags, do_get_calendar_event) {
|
||||
// Find the index of the object with id "prompt_get_calendar_event_from_clipboard"
|
||||
const index = arr.findIndex(item => item.id === "prompt_get_calendar_event_from_clipboard");
|
||||
|
||||
const targetPosition = (do_add_tags ? 1 : 0) + (do_get_calendar_event ? 1 : 0);
|
||||
|
||||
// If found and needs repositioning
|
||||
if (index !== -1 && index !== targetPosition) {
|
||||
// Remove it from its current position
|
||||
const [promptAddTags] = arr.splice(index, 1);
|
||||
|
||||
// Add it to the specified position
|
||||
arr.splice(targetPosition, 0, promptAddTags);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
function ensurePromptGetTaskFirst(arr, do_add_tags, do_get_calendar_event, do_get_calendar_event_from_clipboard) {
|
||||
// Find the index of the object with id "prompt_get_task"
|
||||
const index = arr.findIndex(item => item.id === "prompt_get_task");
|
||||
|
||||
// Determine the target position to insert "prompt_get_task" after calendar
|
||||
const targetPosition = (do_add_tags ? 1 : 0) + (do_get_calendar_event ? 1 : 0) + (do_get_calendar_event_from_clipboard ? 1 : 0);
|
||||
|
||||
// If found and needs repositioning
|
||||
if (index !== -1 && index !== targetPosition) {
|
||||
// Remove it from its current position
|
||||
const [promptGetTask] = arr.splice(index, 1);
|
||||
|
||||
// Add it to the specified position
|
||||
arr.splice(targetPosition, 0, promptGetTask);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
|
|
|||