order menu feature first try. see #680
This commit is contained in:
parent
4ab2af98a3
commit
14868d4fd4
8 changed files with 740 additions and 24 deletions
|
|
@ -36,11 +36,11 @@
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"prompt_summarize_this": {
|
"prompt_summarize_this": {
|
||||||
"message": "Summarize this",
|
"message": "Summarize",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"prompt_translate_this": {
|
"prompt_translate_this": {
|
||||||
"message": "Translate this",
|
"message": "Translate",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"prompt_this": {
|
"prompt_this": {
|
||||||
|
|
@ -853,7 +853,7 @@
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"prompt_add_tags": {
|
"prompt_add_tags": {
|
||||||
"message": "Add tags to this email",
|
"message": "Add tags",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"prompt_add_tags_full_text": {
|
"prompt_add_tags_full_text": {
|
||||||
|
|
@ -1393,7 +1393,7 @@
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"prompt_spamfilter" : {
|
"prompt_spamfilter" : {
|
||||||
"message" : "Detect spam emails",
|
"message" : "Analyze for spam",
|
||||||
"description" : ""
|
"description" : ""
|
||||||
},
|
},
|
||||||
"prompt_spamfilter_full_text" : {
|
"prompt_spamfilter_full_text" : {
|
||||||
|
|
@ -1500,22 +1500,6 @@
|
||||||
"message": "No",
|
"message": "No",
|
||||||
"description": ""
|
"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": {
|
"noActiveCalendar": {
|
||||||
"message": "No editable calendar found!",
|
"message": "No editable calendar found!",
|
||||||
"description": ""
|
"description": ""
|
||||||
|
|
@ -2274,5 +2258,73 @@
|
||||||
"placeholder_string": {
|
"placeholder_string": {
|
||||||
"message": "Placeholder",
|
"message": "Placeholder",
|
||||||
"description": ""
|
"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_save": {
|
||||||
|
"message": "Save",
|
||||||
|
"description": "Save button label in 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": "Menu Order",
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -488,6 +488,7 @@ export class mzta_Menus {
|
||||||
is_special: prompt.is_special,
|
is_special: prompt.is_special,
|
||||||
position_display: prompt.position_display,
|
position_display: prompt.position_display,
|
||||||
position_compose: prompt.position_compose,
|
position_compose: prompt.position_compose,
|
||||||
|
position_context: prompt.position_context,
|
||||||
};
|
};
|
||||||
this.shortcutMenu.push(curr_menu_entry);
|
this.shortcutMenu.push(curr_menu_entry);
|
||||||
}
|
}
|
||||||
|
|
@ -514,8 +515,11 @@ export class mzta_Menus {
|
||||||
}, resolve)
|
}, resolve)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Sort alphabetically for context menu
|
// Sort by position_context if available, otherwise alphabetically
|
||||||
contextPrompts.sort((a, b) => {
|
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 nameA = i18nConditionalGet(a.name);
|
||||||
const nameB = i18nConditionalGet(b.name);
|
const nameB = i18nConditionalGet(b.name);
|
||||||
return nameA.localeCompare(nameB);
|
return nameA.localeCompare(nameB);
|
||||||
|
|
|
||||||
|
|
@ -383,7 +383,7 @@ const specialPrompts = [
|
||||||
api_model: '',
|
api_model: '',
|
||||||
is_default: "1",
|
is_default: "1",
|
||||||
is_special: "1",
|
is_special: "1",
|
||||||
show_in: "both",
|
show_in: "none",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'prompt_summarize_email_separator',
|
id: 'prompt_summarize_email_separator',
|
||||||
|
|
@ -400,7 +400,7 @@ const specialPrompts = [
|
||||||
api_model: '',
|
api_model: '',
|
||||||
is_default: "1",
|
is_default: "1",
|
||||||
is_special: "1",
|
is_special: "1",
|
||||||
show_in: "both",
|
show_in: "none",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'prompt_translate_this',
|
id: 'prompt_translate_this',
|
||||||
|
|
@ -488,7 +488,7 @@ export function preparePromptsForExport(prompts, include_api_settings = false){
|
||||||
}
|
}
|
||||||
|
|
||||||
if(prompt.is_default == 1){
|
if(prompt.is_default == 1){
|
||||||
let allowedKeys = ['id', 'enabled', 'position_compose', 'position_display', 'need_custom_text', 'show_in'];
|
let allowedKeys = ['id', 'enabled', 'position_compose', 'position_display', 'position_context', 'need_custom_text', 'show_in'];
|
||||||
if(include_api_settings){
|
if(include_api_settings){
|
||||||
allowedKeys.push('api_type');
|
allowedKeys.push('api_type');
|
||||||
for (const [integration, options] of Object.entries(integration_options_config)) {
|
for (const [integration, options] of Object.entries(integration_options_config)) {
|
||||||
|
|
@ -541,6 +541,7 @@ async function getDefaultPrompts_withProps() {
|
||||||
prompt.text = browser.i18n.getMessage(prompt.text);
|
prompt.text = browser.i18n.getMessage(prompt.text);
|
||||||
prompt.position_display = pos;
|
prompt.position_display = pos;
|
||||||
prompt.position_compose = pos;
|
prompt.position_compose = pos;
|
||||||
|
prompt.position_context = pos;
|
||||||
prompt.enabled = 1;
|
prompt.enabled = 1;
|
||||||
pos++;
|
pos++;
|
||||||
})
|
})
|
||||||
|
|
@ -552,6 +553,7 @@ async function getDefaultPrompts_withProps() {
|
||||||
if(prefs._default_prompts_properties?.[prompt.id]){
|
if(prefs._default_prompts_properties?.[prompt.id]){
|
||||||
prompt.position_compose = prefs._default_prompts_properties[prompt.id].position_compose;
|
prompt.position_compose = prefs._default_prompts_properties[prompt.id].position_compose;
|
||||||
prompt.position_display = prefs._default_prompts_properties[prompt.id].position_display;
|
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.enabled = prefs._default_prompts_properties[prompt.id].enabled;
|
||||||
prompt.need_custom_text = prefs._default_prompts_properties[prompt.id].need_custom_text;
|
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_model = prefs._default_prompts_properties[prompt.id].chatgpt_web_model;
|
||||||
|
|
@ -562,6 +564,7 @@ async function getDefaultPrompts_withProps() {
|
||||||
}else{
|
}else{
|
||||||
prompt.position_display = pos;
|
prompt.position_display = pos;
|
||||||
prompt.position_compose = pos;
|
prompt.position_compose = pos;
|
||||||
|
prompt.position_context = pos;
|
||||||
prompt.enabled = 1;
|
prompt.enabled = 1;
|
||||||
pos++;
|
pos++;
|
||||||
}
|
}
|
||||||
|
|
@ -608,6 +611,7 @@ export async function setDefaultPromptsProperties(prompts) {
|
||||||
default_prompts_properties[prompt.id] = {
|
default_prompts_properties[prompt.id] = {
|
||||||
position_compose: (prompt.position_compose === undefined || prompt.position_compose === "undefined") ? "" : prompt.position_compose,
|
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_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,
|
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,
|
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_model: (prompt.chatgpt_web_model === undefined || prompt.chatgpt_web_model === "undefined") ? "" : prompt.chatgpt_web_model,
|
||||||
|
|
@ -666,6 +670,10 @@ export async function setSpecialPrompts(prompts) {
|
||||||
await browser.storage.local.set({_special_prompts: prompts});
|
await browser.storage.local.set({_special_prompts: prompts});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getHiddenSpecialPromptIds() {
|
||||||
|
return specialPrompts.filter(p => p.show_in === "none").map(p => p.id);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getSpamFilterPrompt(){
|
export async function getSpamFilterPrompt(){
|
||||||
return (await getSpecialPrompts()).find(prompt => prompt.id == 'prompt_spamfilter');
|
return (await getSpecialPrompts()).find(prompt => prompt.id == 'prompt_spamfilter');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -271,6 +271,7 @@
|
||||||
<button id="btnManagePrompts">__MSG_prefs_OptionText_btnManagePrompts__</button>
|
<button id="btnManagePrompts">__MSG_prefs_OptionText_btnManagePrompts__</button>
|
||||||
<br>__MSG_prefs_OptionText_btnManagePrompts_infoline__ <a href="https://micz.it/thunderbird-addon-thunderai/data-placeholders/">__MSG_more_info_string__</a>
|
<br>__MSG_prefs_OptionText_btnManagePrompts_infoline__ <a href="https://micz.it/thunderbird-addon-thunderai/data-placeholders/">__MSG_more_info_string__</a>
|
||||||
<br><button id="btnManageCustomDataPH">__MSG_prefs_OptionText_btnManageCustomDataPH__</button>
|
<br><button id="btnManageCustomDataPH">__MSG_prefs_OptionText_btnManageCustomDataPH__</button>
|
||||||
|
<br><button id="btnMenuOrder">__MSG_menu_order_btn_label__</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="miczDescription">
|
<div id="miczDescription">
|
||||||
<h1>__MSG_prefsInfoTitle__</h1>
|
<h1>__MSG_prefsInfoTitle__</h1>
|
||||||
|
|
|
||||||
|
|
@ -412,6 +412,10 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
openTab('/pages/customdataplaceholders/mzta-custom-dataplaceholders.html');
|
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', () => {
|
document.getElementById('btnManageTagsInfo').addEventListener('click', () => {
|
||||||
openTab('/pages/addtags/mzta-add-tags.html');
|
openTab('/pages/addtags/mzta-add-tags.html');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
291
pages/menu_order/mzta-menu-order.css
Normal file
291
pages/menu_order/mzta-menu-order.css
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
margin: 20px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page_title {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info_text {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
#command_palette {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnSave {
|
||||||
|
padding: 6px 20px;
|
||||||
|
font-size: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnSave:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
#msgDisplay {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #2a7e2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub_tab {
|
||||||
|
padding: 4px 14px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background: #f5f5f5;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info_text {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
#msgDisplay {
|
||||||
|
color: #5cb85c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu_list_panel {
|
||||||
|
border-color: #444;
|
||||||
|
background: #2a2a30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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; }
|
||||||
|
}
|
||||||
39
pages/menu_order/mzta-menu-order.html
Normal file
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 class="info_text">__MSG_menu_order_info__</p>
|
||||||
|
<div id="command_palette">
|
||||||
|
<button id="btnSave" disabled>__MSG_menu_order_save__</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>
|
||||||
317
pages/menu_order/mzta-menu-order.js
Normal file
317
pages/menu_order/mzta-menu-order.js
Normal file
|
|
@ -0,0 +1,317 @@
|
||||||
|
/*
|
||||||
|
* 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 } from '../../js/mzta-utils.js';
|
||||||
|
|
||||||
|
let allPrompts = [];
|
||||||
|
let currentPopupView = 'display'; // 'display' or 'compose'
|
||||||
|
let hasUnsavedChanges = false;
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
allPrompts = await getPrompts(false, [], true);
|
||||||
|
|
||||||
|
// Exclude special prompts that are defined with show_in: "none" (internal prompts, not user-toggleable)
|
||||||
|
const hiddenSpecialIds = getHiddenSpecialPromptIds();
|
||||||
|
allPrompts = allPrompts.filter(p => !hiddenSpecialIds.includes(p.id));
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
initSubTabs();
|
||||||
|
|
||||||
|
document.getElementById('btnSave').addEventListener('click', saveAll);
|
||||||
|
|
||||||
|
i18n.updateDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 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';
|
||||||
|
|
||||||
|
const activeItems = allPrompts.filter(p => {
|
||||||
|
const showIn = p.show_in || 'popup';
|
||||||
|
return showIn === 'popup' || showIn === 'both';
|
||||||
|
});
|
||||||
|
const hiddenItems = allPrompts.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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 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 btnSave = document.getElementById('btnSave');
|
||||||
|
const msgDisplay = document.getElementById('msgDisplay');
|
||||||
|
btnSave.disabled = true;
|
||||||
|
|
||||||
|
const defaultPromptsToSave = allPrompts.filter(p => String(p.is_default) === '1' && String(p.is_special) !== '1');
|
||||||
|
const customPromptsToSave = allPrompts.filter(p => String(p.is_default) === '0' && String(p.is_special) !== '1');
|
||||||
|
const specialPromptsToSave = allPrompts.filter(p => String(p.is_special) === '1');
|
||||||
|
|
||||||
|
await setDefaultPromptsProperties(defaultPromptsToSave);
|
||||||
|
await setCustomPrompts(customPromptsToSave);
|
||||||
|
await setSpecialPrompts(specialPromptsToSave);
|
||||||
|
|
||||||
|
await browser.runtime.sendMessage({ command: "reload_menus" });
|
||||||
|
|
||||||
|
hasUnsavedChanges = false;
|
||||||
|
msgDisplay.textContent = browser.i18n.getMessage('menu_order_saved');
|
||||||
|
setTimeout(() => { msgDisplay.textContent = ''; }, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function markUnsaved() {
|
||||||
|
hasUnsavedChanges = true;
|
||||||
|
document.getElementById('btnSave').disabled = false;
|
||||||
|
document.getElementById('msgDisplay').textContent = '';
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue