您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enhances Google Images search tools with more filter options (Exact Size, Aspect Ratio, File Type, expanded Region/Site Search) and customization.
当前为
// ==UserScript== // @name Google Images Tools Enhanced // @name:en Google Images Tools Enhanced // @name:zh-TW Google 圖片工具強化版 // @name:ja Google 画像検索ツール拡張 // @namespace https://greasyfork.org/en/users/1467948-stonedkhajiit // @version 0.1.0 // @description Enhances Google Images search tools with more filter options (Exact Size, Aspect Ratio, File Type, expanded Region/Site Search) and customization. // @description:en Enhances Google Images search tools with more filter options (Exact Size, Aspect Ratio, File Type, expanded Region/Site Search) and customization. // @description:zh-TW 大幅強化 Google 圖片搜尋工具,提供更多篩選選項(例如精確尺寸、長寬比、檔案類型、擴展的地區/站內搜尋)以及高度自訂功能。 // @description:ja Google 画像検索ツールを大幅に強化し、より多くのフィルターオプション(正確なサイズ、アスペクト比、ファイル形式、拡張された地域/サイト内検索など)とカスタマイズ機能を提供します。 // @author StonedKhajiit // @license MIT // @include http*://*.google.tld/search*tbm=isch* // @include http*://*.google.tld/search*udm=2* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant GM_info // ==/UserScript== (function () { 'use strict'; // --- Configuration --- const DEBUG_MODE = false; // Set to false for production to reduce console output // Check for essential Greasemonkey functions at the beginning if (typeof GM_getValue !== 'function' || typeof GM_setValue !== 'function') { console.error('[GITE] CRITICAL: GM_getValue or GM_setValue is not available. Settings cannot be loaded or saved. The script may not function correctly.'); } if (typeof GM_registerMenuCommand !== 'function' && DEBUG_MODE) { console.warn('[GITE] GM_registerMenuCommand is not available. Greasemonkey menu items will not be registered.'); } // --- Selectors (Constants for DOM elements) --- const MENU_SELECTOR = 'g-menu[jsname="xl07Ob"]'; const MENU_ITEM_SELECTOR = 'g-menu-item'; const CUSTOM_SIZER_ATTR = 'data-custom-sizer'; const SIZER_VALUE_ATTR = 'data-sizer-value'; const CUSTOM_FILTER_ITEM_ATTR = 'data-custom-filter-item'; const FILTER_PARAM_ATTR = 'data-filter-param'; const FILTER_VALUE_ATTR = 'data-filter-value'; const TBS_PREFIX_ATTR = 'data-tbs-prefix'; const TOOLS_BUTTON_SELECTOR = 'div#hdtb-tls[role="button"][jsname="bVqjv"]'; const FILTER_BUTTONS_CONTAINER_SELECTOR = 'div#hdtbMenus div.BfdGL.ZkEmPc'; const FILTER_SPAN_TEMPLATE_SELECTOR = FILTER_BUTTONS_CONTAINER_SELECTOR + ' > span[jscontroller="nabPbb"]:first-of-type'; const FILTER_BUTTON_TEXT_CLASS = '.KTBKoe'; const FILTER_BUTTON_WRAPPER_CLASS = '.AozSsc'; const NATIVE_CLEAR_BUTTON_SELECTOR = 'a.EvhmG[href*="tbas=0"], a.AozSsc.UbBYac[href*="tbas=0"]'; const GITE_CLEAR_BUTTON_ID = 'gite-clear-filters-button'; const CLEAR_BUTTON_TARGET_DISPLAY_STYLE = 'inline-flex'; const GITE_SETTINGS_BUTTON_ID = 'gite-settings-toolbar-button'; const OBSERVER_TARGET_SELECTOR_ID = '#hdtb-msb'; // --- Timing & Limits (Constants for delays, retries, etc.) --- const MAX_OBSERVER_TRIES = 15; const MAX_TOOLBAR_BUTTON_OBSERVER_TRIES = 15; const OBSERVER_RETRY_DELAY_MS = 500; const BUTTON_CLICK_RETRY_DELAY_MS = 200; const BUTTON_CLICK_MAX_ATTEMPTS = 20; const POST_CLICK_DELAY_MS = 300; // Slightly increased const POPSTATE_UPDATE_DELAY_MS = 150; const FINAL_UI_UPDATE_DELAY_MS = 350; const GITE_SETTINGS_GM_KEY = 'GITE_USER_SETTINGS'; const GITE_SCRIPT_VERSION = typeof GM_info !== 'undefined' && GM_info.script ? GM_info.script.version : 'N/A_GM_info_NA'; const GITE_LANG_KEY_ATTR = 'data-gite-lang-key'; let CURRENT_LANGUAGE = 'en'; // --- I18N Strings (Localization data for English, Traditional Chinese, and Japanese) --- const GITE_I18N_STRINGS = { 'en': { // Filter Titles 'filter_title_size': "Size", 'filter_title_exact_size': "Exact size", 'filter_title_aspect_ratio': "Aspect ratio", 'filter_title_color': "Color", 'filter_title_type': "Type", 'filter_title_time': "Time", 'filter_title_usage_rights': "Usage rights", 'filter_title_file_type': "File type", 'filter_title_region': "Region", 'filter_title_site_search': "Site Search", // General Buttons & Labels 'btn_clear': "Clear", 'btn_apply': "Apply", 'btn_cancel': "Cancel", 'btn_close': "Close", 'btn_save_and_close': "Save & Close", 'btn_delete': "Delete", 'btn_edit_label': "Edit Label", 'btn_save_changes': "Save", 'btn_add_new_exact_size': "Add Size", 'btn_add_new_site': "Add Site", // Size Options 'option_text_size_any': "Any size", 'option_text_size_large': "Large", 'option_text_size_medium': "Medium", 'option_text_size_icon': "Icon", 'option_text_size_qsvga': "Larger than 400×300", 'option_text_size_vga': "Larger than 640×480", 'option_text_size_svga': "Larger than 800×600", 'option_text_size_xga': "Larger than 1024×768", 'option_text_size_2mp': "Larger than 2MP", 'option_text_size_4mp': "Larger than 4MP", 'option_text_size_6mp': "Larger than 6MP", 'option_text_size_8mp': "Larger than 8MP", 'option_text_size_10mp': "Larger than 10MP", 'option_text_size_12mp': "Larger than 12MP", 'option_text_size_15mp': "Larger than 15MP", 'option_text_size_20mp': "Larger than 20MP", 'option_text_size_40mp': "Larger than 40MP", 'option_text_size_70mp': "Larger than 70MP", // Exact Size Options 'option_text_exact_size_any': "Any exact size", 'option_text_exact_size_1024x768': "1024×768 (XGA)", 'option_text_exact_size_1280x720': "1280×720 (HD)", 'option_text_exact_size_1366x768': "1366×768 (Laptop)", 'option_text_exact_size_1600x900': "1600×900 (HD+)", 'option_text_exact_size_1920x1080': "1920×1080 (FHD)", 'option_text_exact_size_2560x1080': "2560×1080 (Ultrawide FHD)", 'option_text_exact_size_2560x1440': "2560×1440 (QHD)", 'option_text_exact_size_3440x1440': "3440×1440 (Ultrawide QHD)", 'option_text_exact_size_3840x2160': "3840×2160 (4K UHD)", 'option_text_exact_size_1080x1920': "1080×1920 (FHD Port.)", 'option_text_exact_size_768x1024': "768×1024 (XGA Port.)", 'exact_size_placeholder_width': "Width", 'exact_size_placeholder_height': "Height", // Aspect Ratio Options 'option_text_ar_any': "Any aspect ratio", 'option_text_ar_tall': "Tall", 'option_text_ar_square': "Square", 'option_text_ar_wide': "Wide", 'option_text_ar_panoramic': "Panoramic", // Color Options 'option_text_color_any': "Any color", 'option_text_color_full': "Full color", 'option_text_color_bw': "Black and white", 'option_text_color_transparent': "Transparent", 'option_text_color_palette_red': "Red", 'option_text_color_palette_orange': "Orange", 'option_text_color_palette_yellow': "Yellow", 'option_text_color_palette_green': "Green", 'option_text_color_palette_teal': "Teal", 'option_text_color_palette_blue': "Blue", 'option_text_color_palette_purple': "Purple", 'option_text_color_palette_pink': "Pink", 'option_text_color_palette_white': "White", 'option_text_color_palette_gray': "Gray", 'option_text_color_palette_black': "Black", 'option_text_color_palette_brown': "Brown", // Type Options 'option_text_type_any': "Any type", 'option_text_type_face': "Face", 'option_text_type_photo': "Photo", 'option_text_type_clipart': "Clip art", 'option_text_type_lineart': "Line drawing", 'option_text_type_gif': "GIF", // Time Options 'option_text_time_any': "Any time", 'option_text_time_past_hour': "Past hour", 'option_text_time_past_24h': "Past 24 hours", 'option_text_time_past_3d': "Past 3 days", 'option_text_time_past_week': "Past week", 'option_text_time_past_month': "Past month", 'option_text_time_past_3m': "Past 3 months", 'option_text_time_past_6m': "Past 6 months", 'option_text_time_past_9m': "Past 9 months", 'option_text_time_past_year': "Past year", 'option_text_time_custom_range': "Custom range...", 'datepicker_label_from': "From:", 'datepicker_label_to': "To:", // Usage Rights Options 'option_text_rights_any': "Not filtered by license", 'option_text_rights_cc': "Creative Commons licenses", 'option_text_rights_commercial': "Commercial & other licenses", // File Type Options 'option_text_filetype_any': "Any format", 'option_text_filetype_jpg': "JPG files", 'option_text_filetype_gif': "GIF files", 'option_text_filetype_png': "PNG files", 'option_text_filetype_bmp': "BMP files", 'option_text_filetype_svg': "SVG files", 'option_text_filetype_webp': "WEBP files", 'option_text_filetype_avif': "AVIF files", 'option_text_filetype_ico': "ICO files", 'option_text_filetype_raw': "RAW files", // Region Options 'option_text_region_any': "Any region", 'option_text_region_ca': "Canada", 'option_text_region_us': "United States", 'option_text_region_mx': "Mexico", 'option_text_region_ar': "Argentina", 'option_text_region_br': "Brazil", 'option_text_region_cl': "Chile", 'option_text_region_co': "Colombia", 'option_text_region_pe': "Peru", 'option_text_region_gb': "United Kingdom", 'option_text_region_fr': "France", 'option_text_region_de': "Germany", 'option_text_region_it': "Italy", 'option_text_region_es': "Spain", 'option_text_region_al': "Albania", 'option_text_region_at': "Austria", 'option_text_region_by': "Belarus", 'option_text_region_be': "Belgium", 'option_text_region_ba': "Bosnia and Herzegovina", 'option_text_region_bg': "Bulgaria", 'option_text_region_hr': "Croatia", 'option_text_region_cz': "Czech Republic", 'option_text_region_dk': "Denmark", 'option_text_region_ee': "Estonia", 'option_text_region_fi': "Finland", 'option_text_region_gr': "Greece", 'option_text_region_hu': "Hungary", 'option_text_region_is': "Iceland", 'option_text_region_ie': "Ireland", 'option_text_region_lv': "Latvia", 'option_text_region_lt': "Lithuania", 'option_text_region_lu': "Luxembourg", 'option_text_region_nl': "Netherlands", 'option_text_region_no': "Norway", 'option_text_region_pl': "Poland", 'option_text_region_pt': "Portugal", 'option_text_region_ro': "Romania", 'option_text_region_ru': "Russia", 'option_text_region_rs': "Serbia", 'option_text_region_sk': "Slovakia", 'option_text_region_si': "Slovenia", 'option_text_region_se': "Sweden", 'option_text_region_ch': "Switzerland", 'option_text_region_tr': "Turkey", 'option_text_region_ua': "Ukraine", 'option_text_region_jp': "Japan", 'option_text_region_kr': "South Korea", 'option_text_region_tw': "Taiwan", 'option_text_region_cn': "China", 'option_text_region_hk': "Hong Kong", 'option_text_region_in': "India", 'option_text_region_id': "Indonesia", 'option_text_region_il': "Israel", 'option_text_region_my': "Malaysia", 'option_text_region_ph': "Philippines", 'option_text_region_sa': "Saudi Arabia", 'option_text_region_sg': "Singapore", 'option_text_region_th': "Thailand", 'option_text_region_ae': "United Arab Emirates", 'option_text_region_vn': "Vietnam", 'option_text_region_au': "Australia", 'option_text_region_nz': "New Zealand", 'option_text_region_eg': "Egypt", 'option_text_region_ng': "Nigeria", 'option_text_region_za': "South Africa", // Site Search Specific 'option_text_site_any': "Any site", // Alerts & Messages 'alert_size_already_saved': "This size is already saved.", 'alert_custom_size_deleted': "Custom size deleted: ", 'alert_custom_size_saved': "Custom size saved: ", 'alert_invalid_domain': "Invalid domain format.", 'alert_datepicker_select_dates': "Please select a start and end date.", 'alert_datepicker_end_before_start': "End date cannot be earlier than start date.", 'alert_datepicker_invalid_date': "Invalid date selected or formatting failed. Please choose again.", 'alert_exact_size_invalid_input': "Please enter valid width and height (positive integers).", 'alert_confirm_delete_option_prefix': "Delete \"", 'alert_label_updated': "Label updated.", 'alert_exact_size_added': "New exact size added.", 'alert_exact_size_deleted': "Saved size deleted.", 'alert_generic_error_saving': "Error saving settings.", 'alert_gm_setvalue_unavailable': "Error: GM_setValue is not available. Settings cannot be saved.", 'alert_site_label_empty': "Site label cannot be empty.", 'alert_site_domain_empty': "Site domain cannot be empty.", 'alert_site_domain_invalid': "Invalid domain format. Use example.com or .domain.", 'alert_site_already_saved': "This site/domain is already saved (check label or domain).", 'alert_site_added': "New site added: ", 'alert_site_deleted': "Saved site deleted.", 'alert_site_label_updated': "Site label updated.", 'alert_site_value_updated': "Site domain updated.", // Text Prefixes/Suffixes 'text_larger_than_prefix': "Larger than ", 'text_site_search_active_prefix': "Site: ", // Greasemonkey Menu 'gm_menu_gite_settings': "GITE Settings", 'gm_menu_reset_all_gite_settings': "Reset All GITE Settings", 'gm_please_reload': "Please reload the page for the changes to be fully applied.", 'gm_settings_updated_no_reload': "GITE settings updated.", // Settings Panel 'settings_panel_title': "GITE Settings", 'settings_tab_general': "General", 'settings_tab_exact_size': "Exact Size", 'settings_tab_size': "Size", 'settings_tab_time': "Time", 'settings_tab_region': "Region", 'settings_tab_site_search': "Site Search", 'settings_label_language': "Script Language:", 'settings_lang_auto': "Auto-detect", 'settings_lang_en': "English", 'settings_lang_zh_TW': "Traditional Chinese (繁體中文)", 'settings_lang_ja': "Japanese (日本語)", 'settings_label_autoexpand': "Auto-expand Google 'Tools' bar:", 'settings_label_showtoolbarbutton': "Show settings button on filter bar:", 'btn_reset_all_settings': "Reset All to Defaults", 'alert_confirm_reset_all_settings': "Are you sure you want to reset ALL GITE settings to their defaults? This cannot be undone.", 'alert_settings_reset_to_default': "All settings have been reset to default. Please save if you wish to keep these defaults, or reload for full effect if saved from GM menu.", 'alert_settings_saved_reload_required': "Settings saved. Some changes may require a page reload to take full effect.", 'tooltip_gite_settings_button': "GITE Settings", 'settings_enable_filter_category_prefix': "Enable \"", 'settings_enable_filter_category_suffix': "\" filter", 'settings_options_for_category_prefix': "Options for \"", 'settings_options_for_category_suffix': "\"", 'settings_size_options_note': "Manage visibility of default size options. Two-column layout in main menu is preserved.", 'btn_reset_options_for_category_prefix': "Reset \"", 'btn_reset_options_for_category_suffix': "\" Options to Defaults", 'settings_section_predefined_options': "Predefined Options:", 'settings_section_your_saved_sizes': "Your Saved Sizes:", 'settings_label_add_new_exact_size': "Add New Exact Size:", 'settings_placeholder_label_optional': "Label (optional)", 'settings_region_options_note': "Manage visibility of region options. All listed regions will be available in the filter menu if this category is enabled.", 'settings_label_enable_site_search_filter': "Enable \"Site Search\" filter", 'settings_section_your_saved_sites': "Your Saved Sites:", 'settings_label_add_new_site': "Add New Site:", 'settings_placeholder_site_label': "Label (e.g., Official Website)", 'settings_placeholder_site_domain': "Domain (e.g., example.com or .gov)", 'settings_no_saved_items_placeholder': "No saved items yet." }, 'zh-TW': { //繁體中文翻譯 'filter_title_size': "大小", 'filter_title_exact_size': "精確尺寸", 'filter_title_aspect_ratio': "長寬比", 'filter_title_color': "顏色", 'filter_title_type': "類型", 'filter_title_time': "時間", 'filter_title_usage_rights': "使用權限", 'filter_title_file_type': "檔案類型", 'filter_title_region': "地區", 'filter_title_site_search': "站內搜尋", 'btn_clear': "清除", 'btn_apply': "套用", 'btn_cancel': "取消", 'btn_close': "關閉", 'btn_save_and_close': "儲存並關閉", 'btn_delete': "刪除", 'btn_edit_label': "編輯標籤", 'btn_save_changes': "儲存", 'btn_add_new_exact_size': "新增尺寸", 'btn_add_new_site': "新增網站", 'option_text_size_any': "任何大小", 'option_text_size_large': "大型", 'option_text_size_medium': "中型", 'option_text_size_icon': "圖示", 'option_text_size_qsvga': "大於 400×300", 'option_text_size_vga': "大於 640×480", 'option_text_size_svga': "大於 800×600", 'option_text_size_xga': "大於 1024×768", 'option_text_size_2mp': "大於 2MP", 'option_text_size_4mp': "大於 4MP", 'option_text_size_6mp': "大於 6MP", 'option_text_size_8mp': "大於 8MP", 'option_text_size_10mp': "大於 10MP", 'option_text_size_12mp': "大於 12MP", 'option_text_size_15mp': "大於 15MP", 'option_text_size_20mp': "大於 20MP", 'option_text_size_40mp': "大於 40MP", 'option_text_size_70mp': "大於 70MP", 'option_text_exact_size_any': "任何精確尺寸", 'option_text_exact_size_1024x768': "1024×768 (XGA)", 'option_text_exact_size_1280x720': "1280×720 (HD)", 'option_text_exact_size_1366x768': "1366×768 (筆記型電腦)", 'option_text_exact_size_1600x900': "1600×900 (HD+)", 'option_text_exact_size_1920x1080': "1920×1080 (FHD)", 'option_text_exact_size_2560x1080': "2560×1080 (超寬 FHD)", 'option_text_exact_size_2560x1440': "2560×1440 (QHD)", 'option_text_exact_size_3440x1440': "3440×1440 (超寬 QHD)", 'option_text_exact_size_3840x2160': "3840×2160 (4K UHD)", 'option_text_exact_size_1080x1920': "1080×1920 (FHD 縱向)", 'option_text_exact_size_768x1024': "768×1024 (XGA 縱向)", 'exact_size_placeholder_width': "寬度", 'exact_size_placeholder_height': "高度", 'option_text_ar_any': "任何長寬比", 'option_text_ar_tall': "高", 'option_text_ar_square': "正方形", 'option_text_ar_wide': "寬幅", 'option_text_ar_panoramic': "全景", 'option_text_color_any': "任何色彩", 'option_text_color_full': "全彩", 'option_text_color_bw': "黑白", 'option_text_color_transparent': "透明背景", 'option_text_color_palette_red': "紅色", 'option_text_color_palette_orange': "橘色", 'option_text_color_palette_yellow': "黃色", 'option_text_color_palette_green': "綠色", 'option_text_color_palette_teal': "藍綠色", 'option_text_color_palette_blue': "藍色", 'option_text_color_palette_purple': "紫色", 'option_text_color_palette_pink': "粉紅色", 'option_text_color_palette_white': "白色", 'option_text_color_palette_gray': "灰色", 'option_text_color_palette_black': "黑色", 'option_text_color_palette_brown': "棕色", 'option_text_type_any': "任何類型", 'option_text_type_face': "臉部特寫", 'option_text_type_photo': "相片", 'option_text_type_clipart': "美工圖案", 'option_text_type_lineart': "線條藝術畫", 'option_text_type_gif': "GIF 動畫", 'option_text_time_any': "不限時間", 'option_text_time_past_hour': "過去 1 小時", 'option_text_time_past_24h': "過去 24 小時", 'option_text_time_past_3d': "過去 3 天", 'option_text_time_past_week': "過去 1 週", 'option_text_time_past_month': "過去 1 個月", 'option_text_time_past_3m': "過去 3 個月", 'option_text_time_past_6m': "過去 6 個月", 'option_text_time_past_9m': "過去 9 個月", 'option_text_time_past_year': "過去 1 年", 'option_text_time_custom_range': "自訂日期範圍...", 'datepicker_label_from': "開始日期:", 'datepicker_label_to': "結束日期:", 'option_text_rights_any': "不限使用權", 'option_text_rights_cc': "創用 CC 授權", 'option_text_rights_commercial': "商業和其他授權", 'option_text_filetype_any': "任何格式", 'option_text_filetype_jpg': "JPG", 'option_text_filetype_gif': "GIF", 'option_text_filetype_png': "PNG", 'option_text_filetype_bmp': "BMP", 'option_text_filetype_svg': "SVG", 'option_text_filetype_webp': "WEBP", 'option_text_filetype_avif': "AVIF", 'option_text_filetype_ico': "ICO", 'option_text_filetype_raw': "RAW", // Region Options 'option_text_region_any': "任何地區", 'option_text_region_ca': "加拿大", 'option_text_region_us': "美國", 'option_text_region_mx': "墨西哥", 'option_text_region_ar': "阿根廷", 'option_text_region_br': "巴西", 'option_text_region_cl': "智利", 'option_text_region_co': "哥倫比亞", 'option_text_region_pe': "秘魯", 'option_text_region_gb': "英國", 'option_text_region_fr': "法國", 'option_text_region_de': "德國", 'option_text_region_it': "義大利", 'option_text_region_es': "西班牙", 'option_text_region_al': "阿爾巴尼亞", 'option_text_region_at': "奧地利", 'option_text_region_by': "白俄羅斯", 'option_text_region_be': "比利時", 'option_text_region_ba': "波士尼亞與赫塞哥維納", 'option_text_region_bg': "保加利亞", 'option_text_region_hr': "克羅埃西亞", 'option_text_region_cz': "捷克", 'option_text_region_dk': "丹麥", 'option_text_region_ee': "愛沙尼亞", 'option_text_region_fi': "芬蘭", 'option_text_region_gr': "希臘", 'option_text_region_hu': "匈牙利", 'option_text_region_is': "冰島", 'option_text_region_ie': "愛爾蘭", 'option_text_region_lv': "拉脫維亞", 'option_text_region_lt': "立陶宛", 'option_text_region_lu': "盧森堡", 'option_text_region_nl': "荷蘭", 'option_text_region_no': "挪威", 'option_text_region_pl': "波蘭", 'option_text_region_pt': "葡萄牙", 'option_text_region_ro': "羅馬尼亞", 'option_text_region_ru': "俄羅斯", 'option_text_region_rs': "塞爾維亞", 'option_text_region_sk': "斯洛伐克", 'option_text_region_si': "斯洛維尼亞", 'option_text_region_se': "瑞典", 'option_text_region_ch': "瑞士", 'option_text_region_tr': "土耳其", 'option_text_region_ua': "烏克蘭", 'option_text_region_jp': "日本", 'option_text_region_kr': "韓國", 'option_text_region_tw': "台灣", 'option_text_region_cn': "中國", 'option_text_region_hk': "香港", 'option_text_region_in': "印度", 'option_text_region_id': "印尼", 'option_text_region_il': "以色列", 'option_text_region_my': "馬來西亞", 'option_text_region_ph': "菲律賓", 'option_text_region_sa': "沙烏地阿拉伯", 'option_text_region_sg': "新加坡", 'option_text_region_th': "泰國", 'option_text_region_ae': "阿拉伯聯合大公國", 'option_text_region_vn': "越南", 'option_text_region_au': "澳洲", 'option_text_region_nz': "紐西蘭", 'option_text_region_eg': "埃及", 'option_text_region_ng': "奈及利亞", 'option_text_region_za': "南非", 'option_text_site_any': "任何網站", 'alert_size_already_saved': "此尺寸已儲存。", 'alert_custom_size_deleted': "已刪除自訂尺寸: ", 'alert_custom_size_saved': "已儲存自訂尺寸: ", 'alert_invalid_domain': "網域格式無效。", 'alert_datepicker_select_dates': "請選擇開始和結束日期。", 'alert_datepicker_end_before_start': "結束日期不能早於開始日期。", 'alert_datepicker_invalid_date': "選擇的日期無效或格式化失敗,請重新選擇。", 'alert_exact_size_invalid_input': "請輸入有效的寬度和高度 (正整數)。", 'alert_confirm_delete_option_prefix': "刪除「", 'alert_label_updated': "標籤已更新。", 'alert_exact_size_added': "新的精確尺寸已新增。", 'alert_exact_size_deleted': "已儲存的尺寸已刪除。", 'alert_generic_error_saving': "儲存設定時發生錯誤。", 'alert_gm_setvalue_unavailable': "錯誤:GM_setValue 功能不可用,無法儲存設定。", 'alert_site_label_empty': "網站標籤不得為空。", 'alert_site_domain_empty': "網站網域不得為空。", 'alert_site_domain_invalid': "網域格式無效。請使用 example.com 或 .domain 格式。", 'alert_site_already_saved': "此網站/網域已儲存 (請檢查標籤或網域)。", 'alert_site_added': "新網站已新增: ", 'alert_site_deleted': "已儲存的網站已刪除。", 'alert_site_label_updated': "網站標籤已更新。", 'alert_site_value_updated': "網站網域已更新。", 'text_larger_than_prefix': "大於 ", 'text_site_search_active_prefix': "搜尋網站: ", 'gm_menu_gite_settings': "GITE 設定", 'gm_menu_reset_all_gite_settings': "重設所有 GITE 設定", 'gm_please_reload': "請重新載入頁面以套用變更。", 'gm_settings_updated_no_reload': "GITE 設定已更新。", 'settings_panel_title': "GITE 設定", 'settings_tab_general': "一般", 'settings_tab_exact_size': "精確尺寸", 'settings_tab_size': "大小", 'settings_tab_time': "時間", 'settings_tab_region': "地區", 'settings_tab_site_search': "站內搜尋", 'settings_label_language': "腳本語言:", 'settings_lang_auto': "自動偵測", 'settings_lang_en': "English (英文)", 'settings_lang_zh_TW': "Traditional Chinese (繁體中文)", 'settings_lang_ja': "日文 (日本語)", 'settings_label_autoexpand': "自動展開 Google「工具」列:", 'settings_label_showtoolbarbutton': "在篩選器列顯示設定按鈕:", 'btn_reset_all_settings': "全部重設為預設", 'alert_confirm_reset_all_settings': "您確定要將所有 GITE 設定重設為預設值嗎?此操作無法復原。", 'alert_settings_reset_to_default': "所有設定已重設為預設值。如果您希望保留這些預設值,請儲存設定,或於 GM 選單重設後重新載入頁面。", 'alert_settings_saved_reload_required': "設定已儲存。部分變更可能需要重新載入頁面才能完全生效。", 'tooltip_gite_settings_button': "GITE 設定", 'settings_enable_filter_category_prefix': "啟用「", 'settings_enable_filter_category_suffix': "」篩選器", 'settings_options_for_category_prefix': "「", 'settings_options_for_category_suffix': "」的選項", 'settings_size_options_note': "管理預設尺寸選項的可見性。主選單中的兩欄佈局將被保留。", 'btn_reset_options_for_category_prefix': "重設「", 'btn_reset_options_for_category_suffix': "」選項為預設值", 'settings_section_predefined_options': "預定義選項:", 'settings_section_your_saved_sizes': "您儲存的尺寸:", 'settings_label_add_new_exact_size': "新增精確尺寸:", 'settings_placeholder_label_optional': "標籤 (選填)", 'settings_region_options_note': "管理地區選項的可見性。所有列出的地區選項,若此分類已啟用,即可在篩選器選單中使用。", 'settings_label_enable_site_search_filter': "啟用「站內搜尋」篩選器", 'settings_section_your_saved_sites': "您儲存的網站:", 'settings_label_add_new_site': "新增網站:", 'settings_placeholder_site_label': "標籤 (例如:官方網站)", 'settings_placeholder_site_domain': "網域 (例如:example.com 或 .gov)", 'settings_no_saved_items_placeholder': "尚無儲存的項目。" }, 'ja': { // 日本語翻訳 // Filter Titles 'filter_title_size': "サイズ", 'filter_title_exact_size': "正確なサイズ", 'filter_title_aspect_ratio': "アスペクト比", 'filter_title_color': "色", 'filter_title_type': "種類", 'filter_title_time': "期間", 'filter_title_usage_rights': "ライセンス", 'filter_title_file_type': "ファイル形式", 'filter_title_region': "地域", 'filter_title_site_search': "サイト内検索", // General Buttons & Labels 'btn_clear': "クリア", 'btn_apply': "適用", // Settings panel: Apply. Date picker: 選択 (Select) 'btn_cancel': "キャンセル", 'btn_close': "閉じる", 'btn_save_and_close': "保存して閉じる", 'btn_delete': "削除", 'btn_edit_label': "ラベルを編集", 'btn_save_changes': "保存", 'btn_add_new_exact_size': "サイズを追加", 'btn_add_new_site': "サイトを追加", // Size Options 'option_text_size_any': "すべてのサイズ", 'option_text_size_large': "大", 'option_text_size_medium': "中", 'option_text_size_icon': "アイコンサイズ", 'option_text_size_qsvga': "400×300 以上", 'option_text_size_vga': "640×480 以上", 'option_text_size_svga': "800×600 以上", 'option_text_size_xga': "1024×768 以上", 'option_text_size_2mp': "200 万画素以上", 'option_text_size_4mp': "400 万画素以上", 'option_text_size_6mp': "600 万画素以上", 'option_text_size_8mp': "800 万画素以上", 'option_text_size_10mp': "1000 万画素以上", 'option_text_size_12mp': "1200 万画素以上", 'option_text_size_15mp': "1500 万画素以上", 'option_text_size_20mp': "2000 万画素以上", 'option_text_size_40mp': "4000 万画素以上", 'option_text_size_70mp': "7000 万画素以上", // Exact Size Options 'option_text_exact_size_any': "すべての正確なサイズ", 'option_text_exact_size_1024x768': "1024×768 (XGA)", 'option_text_exact_size_1280x720': "1280×720 (HD)", 'option_text_exact_size_1366x768': "1366×768 (ラップトップ)", 'option_text_exact_size_1600x900': "1600×900 (HD+)", 'option_text_exact_size_1920x1080': "1920×1080 (FHD)", 'option_text_exact_size_2560x1080': "2560×1080 (ウルトラワイド FHD)", 'option_text_exact_size_2560x1440': "2560×1440 (QHD)", 'option_text_exact_size_3440x1440': "3440×1440 (ウルトラワイド QHD)", 'option_text_exact_size_3840x2160': "3840×2160 (4K UHD)", 'option_text_exact_size_1080x1920': "1080×1920 (FHD 縦長)", 'option_text_exact_size_768x1024': "768×1024 (XGA 縦長)", 'exact_size_placeholder_width': "幅", 'exact_size_placeholder_height': "高さ", // Aspect Ratio Options 'option_text_ar_any': "全アスペクト比", 'option_text_ar_tall': "縦長表示", 'option_text_ar_square': "正方形", 'option_text_ar_wide': "横長表示", 'option_text_ar_panoramic': "パノラマ表示", // Color Options 'option_text_color_any': "すべての色", 'option_text_color_full': "フルカラー", 'option_text_color_bw': "白黒", 'option_text_color_transparent': "透明", 'option_text_color_palette_red': "赤", 'option_text_color_palette_orange': "オレンジ", 'option_text_color_palette_yellow': "黄", 'option_text_color_palette_green': "緑", 'option_text_color_palette_teal': "青緑", 'option_text_color_palette_blue': "青", 'option_text_color_palette_purple': "紫", 'option_text_color_palette_pink': "ピンク", 'option_text_color_palette_white': "白", 'option_text_color_palette_gray': "グレー", 'option_text_color_palette_black': "黒", 'option_text_color_palette_brown': "茶", // Type Options 'option_text_type_any': "すべての種類", 'option_text_type_face': "顔", 'option_text_type_photo': "写真", 'option_text_type_clipart': "クリップアート", 'option_text_type_lineart': "線画", 'option_text_type_gif': "GIF", // Time Options 'option_text_time_any': "期間指定なし", 'option_text_time_past_hour': "1 時間以内", 'option_text_time_past_24h': "24 時間以内", 'option_text_time_past_3d': "3 日以内", 'option_text_time_past_week': "1 週間以内", 'option_text_time_past_month': "1 か月以内", 'option_text_time_past_3m': "3 か月以内", 'option_text_time_past_6m': "6 か月以内", 'option_text_time_past_9m': "9 か月以内", 'option_text_time_past_year': "1 年以内", 'option_text_time_custom_range': "期間を指定...", 'datepicker_label_from': "開始日:", 'datepicker_label_to': "終了日:", // Usage Rights Options 'option_text_rights_any': "すべて", 'option_text_rights_cc': "クリエイティブ・コモンズ ライセンス", 'option_text_rights_commercial': "商用およびその他のライセンス", // File Type Options 'option_text_filetype_any': "すべての形式", 'option_text_filetype_jpg': "JPG ファイル", 'option_text_filetype_gif': "GIF ファイル", 'option_text_filetype_png': "PNG ファイル", 'option_text_filetype_bmp': "BMP ファイル", 'option_text_filetype_svg': "SVG ファイル", 'option_text_filetype_webp': "WEBP ファイル", 'option_text_filetype_avif': "AVIF ファイル", 'option_text_filetype_ico': "ICO ファイル", 'option_text_filetype_raw': "RAW ファイル", // Region Options 'option_text_region_any': "すべての地域", 'option_text_region_ca': "カナダ", 'option_text_region_us': "アメリカ合衆国", 'option_text_region_mx': "メキシコ", 'option_text_region_ar': "アルゼンチン", 'option_text_region_br': "ブラジル", 'option_text_region_cl': "チリ", 'option_text_region_co': "コロンビア", 'option_text_region_pe': "ペルー", 'option_text_region_gb': "イギリス", 'option_text_region_fr': "フランス", 'option_text_region_de': "ドイツ", 'option_text_region_it': "イタリア", 'option_text_region_es': "スペイン", 'option_text_region_al': "アルバニア", 'option_text_region_at': "オーストリア", 'option_text_region_by': "ベラルーシ", 'option_text_region_be': "ベルギー", 'option_text_region_ba': "ボスニア・ヘルツェゴビナ", 'option_text_region_bg': "ブルガリア", 'option_text_region_hr': "クロアチア", 'option_text_region_cz': "チェコ", 'option_text_region_dk': "デンマーク", 'option_text_region_ee': "エストニア", 'option_text_region_fi': "フィンランド", 'option_text_region_gr': "ギリシャ", 'option_text_region_hu': "ハンガリー", 'option_text_region_is': "アイスランド", 'option_text_region_ie': "アイルランド", 'option_text_region_lv': "ラトビア", 'option_text_region_lt': "リトアニア", 'option_text_region_lu': "ルクセンブルク", 'option_text_region_nl': "オランダ", 'option_text_region_no': "ノルウェー", 'option_text_region_pl': "ポーランド", 'option_text_region_pt': "ポルトガル", 'option_text_region_ro': "ルーマニア", 'option_text_region_ru': "ロシア", 'option_text_region_rs': "セルビア", 'option_text_region_sk': "スロバキア", 'option_text_region_si': "スロベニア", 'option_text_region_se': "スウェーデン", 'option_text_region_ch': "スイス", 'option_text_region_tr': "トルコ", 'option_text_region_ua': "ウクライナ", 'option_text_region_jp': "日本", 'option_text_region_kr': "韓国", 'option_text_region_tw': "台湾", 'option_text_region_cn': "中国", 'option_text_region_hk': "香港", 'option_text_region_in': "インド", 'option_text_region_id': "インドネシア", 'option_text_region_il': "イスラエル", 'option_text_region_my': "マレーシア", 'option_text_region_ph': "フィリピン", 'option_text_region_sa': "サウジアラビア", 'option_text_region_sg': "シンガポール", 'option_text_region_th': "タイ", 'option_text_region_ae': "アラブ首長国連邦", 'option_text_region_vn': "ベトナム", 'option_text_region_au': "オーストラリア", 'option_text_region_nz': "ニュージーランド", 'option_text_region_eg': "エジプト", 'option_text_region_ng': "ナイジェリア", 'option_text_region_za': "南アフリカ", // Site Search Specific 'option_text_site_any': "すべてのサイト", // Alerts & Messages 'alert_size_already_saved': "このサイズは既に保存されています。", 'alert_custom_size_deleted': "カスタムサイズを削除しました: ", 'alert_custom_size_saved': "カスタムサイズを保存しました: ", 'alert_invalid_domain': "無効なドメイン形式です。", 'alert_datepicker_select_dates': "開始日と終了日を選択してください。", 'alert_datepicker_end_before_start': "終了日を開始日より前にすることはできません。", 'alert_datepicker_invalid_date': "無効な日付が選択されたか、フォーマットに失敗しました。もう一度選択してください。", 'alert_exact_size_invalid_input': "有効な幅と高さを入力してください(正の整数)。", 'alert_confirm_delete_option_prefix': "「", // Example: 「Option Name」を削除しますか? (Will need to append "」を削除しますか?" in code or make a full sentence key) 'alert_label_updated': "ラベルが更新されました。", 'alert_exact_size_added': "新しい正確なサイズが追加されました。", 'alert_exact_size_deleted': "保存されたサイズが削除されました。", 'alert_generic_error_saving': "設定の保存中にエラーが発生しました。", 'alert_gm_setvalue_unavailable': "エラー: GM_setValue が利用できません。設定を保存できません。", 'alert_site_label_empty': "サイトラベルは空にできません。", 'alert_site_domain_empty': "サイトドメインは空にできません。", 'alert_site_domain_invalid': "無効なドメイン形式です。example.com または .gov の形式を使用してください。", 'alert_site_already_saved': "このサイト/ドメインは既に保存されています(ラベルまたはドメインを確認してください)。", 'alert_site_added': "新しいサイトが追加されました: ", 'alert_site_deleted': "保存されたサイトが削除されました。", 'alert_site_label_updated': "サイトラベルが更新されました。", 'alert_site_value_updated': "サイトドメインが更新されました。", // Text Prefixes/Suffixes 'text_larger_than_prefix': "次より大きい: ", 'text_site_search_active_prefix': "サイト: ", // Greasemonkey Menu 'gm_menu_gite_settings': "GITE 設定", 'gm_menu_reset_all_gite_settings': "すべての GITE 設定をリセット", 'gm_please_reload': "変更を完全に適用するには、ページをリロードしてください。", 'gm_settings_updated_no_reload': "GITE 設定が更新されました。", // Settings Panel 'settings_panel_title': "GITE 設定", 'settings_tab_general': "一般", 'settings_tab_exact_size': "正確なサイズ", 'settings_tab_size': "サイズ", 'settings_tab_time': "期間", 'settings_tab_region': "地域", 'settings_tab_site_search': "サイト内検索", 'settings_label_language': "スクリプト言語:", 'settings_lang_auto': "自動検出", 'settings_lang_en': "English (英語)", 'settings_lang_zh_TW': "繁體中文 (Traditional Chinese)", 'settings_lang_ja': "日本語 (Japanese)", 'settings_label_autoexpand': "Google「ツール」バーを自動展開:", 'settings_label_showtoolbarbutton': "フィルターバーに設定ボタンを表示:", 'btn_reset_all_settings': "すべてデフォルトにリセット", 'alert_confirm_reset_all_settings': "すべてのGITE設定をデフォルトにリセットしてもよろしいですか?この操作は元に戻せません。", 'alert_settings_reset_to_default': "すべての設定がデフォルトにリセットされました。これらのデフォルトを保持する場合は保存してください。GMメニューからリセットした場合は、完全に適用するためにページをリロードしてください。", 'alert_settings_saved_reload_required': "設定が保存されました。一部の変更は、完全に有効にするためにページのリロードが必要な場合があります。", 'tooltip_gite_settings_button': "GITE 設定", 'settings_enable_filter_category_prefix': "「", 'settings_enable_filter_category_suffix': "」フィルターを有効にする", 'settings_options_for_category_prefix': "「", 'settings_options_for_category_suffix': "」のオプション", 'settings_size_options_note': "デフォルトのサイズオプションの表示を管理します。メインメニューの2列レイアウトは保持されます。", 'btn_reset_options_for_category_prefix': "「", 'btn_reset_options_for_category_suffix': "」オプションをデフォルトにリセット", 'settings_section_predefined_options': "事前定義オプション:", 'settings_section_your_saved_sizes': "保存したサイズ:", 'settings_label_add_new_exact_size': "新しい正確なサイズを追加:", 'settings_placeholder_label_optional': "ラベル (オプション)", 'settings_region_options_note': "地域オプションの表示を管理します。このカテゴリが有効な場合、リストされているすべての地域オプションがフィルターメニューで利用可能になります。", 'settings_label_enable_site_search_filter': "「サイト内検索」フィルターを有効にする", 'settings_section_your_saved_sites': "保存したサイト:", 'settings_label_add_new_site': "新しいサイトを追加:", 'settings_placeholder_site_label': "ラベル (例: 公式サイト)", 'settings_placeholder_site_domain': "ドメイン (例: example.com または .gov)", 'settings_no_saved_items_placeholder': "まだ保存されたアイテムはありません。" } }; // --- Option Definitions (Language Agnostic Base for Filters) --- const GITE_OPTION_DEFINITIONS = { size: [ { id: "gite_size_any", value: "", type: "native", textKey: "option_text_size_any", column: 1, defaultEnabled: true }, { id: "gite_size_large", value: "l", type: "native", textKey: "option_text_size_large", column: 1, defaultEnabled: true }, { id: "gite_size_medium", value: "m", type: "native", textKey: "option_text_size_medium", column: 1, defaultEnabled: true }, { id: "gite_size_icon", value: "i", type: "native", textKey: "option_text_size_icon", column: 1, defaultEnabled: true }, { id: "gite_size_sep1_col1", type: "separator", textKey: null, column: 1, defaultEnabled: true }, { id: "gite_size_qsvga", value: "qsvga", type: "pixel", textKey: "option_text_size_qsvga", column: 1, defaultEnabled: true }, { id: "gite_size_vga", value: "vga", type: "pixel", textKey: "option_text_size_vga", column: 1, defaultEnabled: true }, { id: "gite_size_svga", value: "svga", type: "pixel", textKey: "option_text_size_svga", column: 1, defaultEnabled: true }, { id: "gite_size_xga", value: "xga", type: "pixel", textKey: "option_text_size_xga", column: 1, defaultEnabled: true }, { id: "gite_size_2mp", value: "2mp", type: "megapixel", textKey: "option_text_size_2mp", column: 2, defaultEnabled: true }, { id: "gite_size_4mp", value: "4mp", type: "megapixel", textKey: "option_text_size_4mp", column: 2, defaultEnabled: true }, { id: "gite_size_6mp", value: "6mp", type: "megapixel", textKey: "option_text_size_6mp", column: 2, defaultEnabled: true }, { id: "gite_size_8mp", value: "8mp", type: "megapixel", textKey: "option_text_size_8mp", column: 2, defaultEnabled: true }, { id: "gite_size_10mp", value: "10mp", type: "megapixel", textKey: "option_text_size_10mp", column: 2, defaultEnabled: true }, { id: "gite_size_12mp", value: "12mp", type: "megapixel", textKey: "option_text_size_12mp", column: 2, defaultEnabled: true }, { id: "gite_size_15mp", value: "15mp", type: "megapixel", textKey: "option_text_size_15mp", column: 2, defaultEnabled: true }, { id: "gite_size_20mp", value: "20mp", type: "megapixel", textKey: "option_text_size_20mp", column: 2, defaultEnabled: true }, { id: "gite_size_40mp", value: "40mp", type: "megapixel", textKey: "option_text_size_40mp", column: 2, defaultEnabled: true }, { id: "gite_size_70mp", value: "70mp", type: "megapixel", textKey: "option_text_size_70mp", column: 2, defaultEnabled: true }, ], exactSize: [ { id: "gite_exact_any", value: "", type: "imagesize_clear", textKey: "option_text_exact_size_any", defaultEnabled: true }, { id: "gite_exact_sep1", type: "separator", textKey: null, defaultEnabled: true }, // Visual separator in menu { id: "gite_exact_1024x768", value: "1024x768", type: "imagesize", textKey: "option_text_exact_size_1024x768", defaultEnabled: true }, { id: "gite_exact_1280x720", value: "1280x720", type: "imagesize", textKey: "option_text_exact_size_1280x720", defaultEnabled: true }, { id: "gite_exact_1366x768", value: "1366x768", type: "imagesize", textKey: "option_text_exact_size_1366x768", defaultEnabled: true }, { id: "gite_exact_1600x900", value: "1600x900", type: "imagesize", textKey: "option_text_exact_size_1600x900", defaultEnabled: true }, { id: "gite_exact_1920x1080", value: "1920x1080", type: "imagesize", textKey: "option_text_exact_size_1920x1080", defaultEnabled: true }, { id: "gite_exact_2560x1080", value: "2560x1080", type: "imagesize", textKey: "option_text_exact_size_2560x1080", defaultEnabled: true }, { id: "gite_exact_2560x1440", value: "2560x1440", type: "imagesize", textKey: "option_text_exact_size_2560x1440", defaultEnabled: true }, { id: "gite_exact_3440x1440", value: "3440x1440", type: "imagesize", textKey: "option_text_exact_size_3440x1440", defaultEnabled: true }, { id: "gite_exact_3840x2160", value: "3840x2160", type: "imagesize", textKey: "option_text_exact_size_3840x2160", defaultEnabled: true }, { id: "gite_exact_1080x1920", value: "1080x1920", type: "imagesize", textKey: "option_text_exact_size_1080x1920", defaultEnabled: true }, { id: "gite_exact_768x1024", value: "768x1024", type: "imagesize", textKey: "option_text_exact_size_768x1024", defaultEnabled: true }, // No gite_exact_sep2 here, as the menu structure for exactSize has changed ], aspectRatio: [ { id: "ar_any", value: "", textKey: "option_text_ar_any" }, { id: "ar_tall", value: "t", textKey: "option_text_ar_tall" }, { id: "ar_square", value: "s", textKey: "option_text_ar_square" }, { id: "ar_wide", value: "w", textKey: "option_text_ar_wide" }, { id: "ar_panoramic", value: "xw", textKey: "option_text_ar_panoramic" } ], color: { prefixOptions: [ { id: "color_prefix_any", value: "", paramNameOverride: "tbs", tbsValue: "", textKey: "option_text_color_any" }, { id: "color_prefix_full", value: "color", paramNameOverride: "tbs", tbsValue: "ic:color", textKey: "option_text_color_full" }, { id: "color_prefix_bw", value: "gray", paramNameOverride: "tbs", tbsValue: "ic:gray", textKey: "option_text_color_bw" }, { id: "color_prefix_transparent", value: "trans", paramNameOverride: "tbs", tbsValue: "ic:trans", textKey: "option_text_color_transparent" }, { id: "color_prefix_sep1", type: "separator", textKey: null}, ], paletteColors: [ { id: "palette_red", nameKey: "option_text_color_palette_red", hex: "#c00", tbsValue: "ic:specific,isc:red", complement: "#00FFFF", type: "palette" }, { id: "palette_orange", nameKey: "option_text_color_palette_orange", hex: "#fb940b", tbsValue: "ic:specific,isc:orange", complement: "#0066FF", type: "palette" }, { id: "palette_yellow", nameKey: "option_text_color_palette_yellow", hex: "#ff0", tbsValue: "ic:specific,isc:yellow", complement: "#0000FF", type: "palette" }, { id: "palette_green", nameKey: "option_text_color_palette_green", hex: "#0c0", tbsValue: "ic:specific,isc:green", complement: "#FF00FF", type: "palette" }, { id: "palette_teal", nameKey: "option_text_color_palette_teal", hex: "#03c0c6", tbsValue: "ic:specific,isc:teal", complement: "#FF3F39", type: "palette" }, { id: "palette_blue", nameKey: "option_text_color_palette_blue", hex: "#00f", tbsValue: "ic:specific,isc:blue", complement: "#FFA500", type: "palette" }, { id: "palette_purple", nameKey: "option_text_color_palette_purple", hex: "#762ca7", tbsValue: "ic:specific,isc:purple", complement: "#89D258", type: "palette" }, { id: "palette_pink", nameKey: "option_text_color_palette_pink", hex: "#ff98bf", tbsValue: "ic:specific,isc:pink", complement: "#00CC41", type: "palette" }, { id: "palette_white", nameKey: "option_text_color_palette_white", hex: "#fff", tbsValue: "ic:specific,isc:white", complement: "#000000", type: "palette" }, { id: "palette_gray", nameKey: "option_text_color_palette_gray", hex: "#999", tbsValue: "ic:specific,isc:gray", complement: "#FFFFFF", type: "palette" }, { id: "palette_black", nameKey: "option_text_color_palette_black", hex: "#000", tbsValue: "ic:specific,isc:black", complement: "#FFFFFF", type: "palette" }, { id: "palette_brown", nameKey: "option_text_color_palette_brown", hex: "#885418", tbsValue: "ic:specific,isc:brown", complement: "#77ABEB", type: "palette" } ] }, type: [ { id: "type_any", tbsValue: "", textKey: "option_text_type_any" }, { id: "type_face", tbsValue: "itp:face", textKey: "option_text_type_face" }, { id: "type_photo", tbsValue: "itp:photo", textKey: "option_text_type_photo" }, { id: "type_clipart", tbsValue: "itp:clipart", textKey: "option_text_type_clipart" }, { id: "type_lineart", tbsValue: "itp:lineart", textKey: "option_text_type_lineart" }, { id: "type_gif", tbsValue: "itp:animated", textKey: "option_text_type_gif" } ], time: [ { id: "gite_time_any", tbsValue: "", textKey: "option_text_time_any", defaultEnabled: true }, { id: "gite_time_past_hour", tbsValue: "qdr:h", textKey: "option_text_time_past_hour", defaultEnabled: true }, { id: "gite_time_past_24h", tbsValue: "qdr:d", textKey: "option_text_time_past_24h", defaultEnabled: true }, { id: "gite_time_past_3d", tbsValue: "qdr:d3", textKey: "option_text_time_past_3d", defaultEnabled: false }, { id: "gite_time_past_week", tbsValue: "qdr:w", textKey: "option_text_time_past_week", defaultEnabled: true }, { id: "gite_time_past_month", tbsValue: "qdr:m", textKey: "option_text_time_past_month", defaultEnabled: true }, { id: "gite_time_past_3m", tbsValue: "qdr:m3", textKey: "option_text_time_past_3m", defaultEnabled: false }, { id: "gite_time_past_6m", tbsValue: "qdr:m6", textKey: "option_text_time_past_6m", defaultEnabled: false }, { id: "gite_time_past_9m", tbsValue: "qdr:m9", textKey: "option_text_time_past_9m", defaultEnabled: false }, { id: "gite_time_past_year", tbsValue: "qdr:y", textKey: "option_text_time_past_year", defaultEnabled: true }, { id: "gite_time_sep1", type: "separator", textKey: null, defaultEnabled: true }, { id: "gite_time_custom_range", type: "custom_date_trigger", tbsValue: null, textKey: "option_text_time_custom_range", defaultEnabled: true } ], usageRights: [ { id: "rights_any", tbsValue: "", textKey: "option_text_rights_any" }, { id: "rights_cc", tbsValue: "sur:cl", textKey: "option_text_rights_cc" }, { id: "rights_commercial", tbsValue: "sur:ol", textKey: "option_text_rights_commercial" } ], fileType: [ { id: "filetype_any", value: "", textKey: "option_text_filetype_any" }, { id: "filetype_jpg", value: "jpg", textKey: "option_text_filetype_jpg" }, { id: "filetype_gif", value: "gif", textKey: "option_text_filetype_gif" }, { id: "filetype_png", value: "png", textKey: "option_text_filetype_png" }, { id: "filetype_bmp", value: "bmp", textKey: "option_text_filetype_bmp" }, { id: "filetype_svg", value: "svg", textKey: "option_text_filetype_svg" }, { id: "filetype_webp", value: "webp", textKey: "option_text_filetype_webp" }, { id: "filetype_avif", value: "avif", textKey: "option_text_filetype_avif" }, { id: "filetype_ico", value: "ico", textKey: "option_text_filetype_ico" }, { id: "filetype_raw", value: "craw", textKey: "option_text_filetype_raw" } ], region: [ // Total 48 regions, 12 defaultEnabled: true. Sorted by geographic region. No 'column' attribute. // North America { id: "region_ca", value: "countryCA", paramName: "cr", textKey: "option_text_region_ca", defaultEnabled: true }, { id: "region_us", value: "countryUS", paramName: "cr", textKey: "option_text_region_us", defaultEnabled: true }, { id: "region_mx", value: "countryMX", paramName: "cr", textKey: "option_text_region_mx", defaultEnabled: false }, // South America { id: "region_ar", value: "countryAR", paramName: "cr", textKey: "option_text_region_ar", defaultEnabled: false }, { id: "region_br", value: "countryBR", paramName: "cr", textKey: "option_text_region_br", defaultEnabled: false }, { id: "region_cl", value: "countryCL", paramName: "cr", textKey: "option_text_region_cl", defaultEnabled: false }, { id: "region_co", value: "countryCO", paramName: "cr", textKey: "option_text_region_co", defaultEnabled: false }, { id: "region_pe", value: "countryPE", paramName: "cr", textKey: "option_text_region_pe", defaultEnabled: false }, // Europe { id: "region_al", value: "countryAL", paramName: "cr", textKey: "option_text_region_al", defaultEnabled: false }, // Albania { id: "region_at", value: "countryAT", paramName: "cr", textKey: "option_text_region_at", defaultEnabled: false }, // Austria { id: "region_by", value: "countryBY", paramName: "cr", textKey: "option_text_region_by", defaultEnabled: false }, // Belarus { id: "region_be", value: "countryBE", paramName: "cr", textKey: "option_text_region_be", defaultEnabled: false }, // Belgium { id: "region_ba", value: "countryBA", paramName: "cr", textKey: "option_text_region_ba", defaultEnabled: false }, // Bosnia and Herzegovina { id: "region_bg", value: "countryBG", paramName: "cr", textKey: "option_text_region_bg", defaultEnabled: false }, // Bulgaria { id: "region_hr", value: "countryHR", paramName: "cr", textKey: "option_text_region_hr", defaultEnabled: false }, // Croatia { id: "region_cz", value: "countryCZ", paramName: "cr", textKey: "option_text_region_cz", defaultEnabled: false }, // Czech Republic { id: "region_dk", value: "countryDK", paramName: "cr", textKey: "option_text_region_dk", defaultEnabled: false }, // Denmark { id: "region_ee", value: "countryEE", paramName: "cr", textKey: "option_text_region_ee", defaultEnabled: false }, // Estonia { id: "region_fi", value: "countryFI", paramName: "cr", textKey: "option_text_region_fi", defaultEnabled: false }, // Finland { id: "region_fr", value: "countryFR", paramName: "cr", textKey: "option_text_region_fr", defaultEnabled: true }, { id: "region_de", value: "countryDE", paramName: "cr", textKey: "option_text_region_de", defaultEnabled: true }, { id: "region_gr", value: "countryGR", paramName: "cr", textKey: "option_text_region_gr", defaultEnabled: false }, // Greece { id: "region_hu", value: "countryHU", paramName: "cr", textKey: "option_text_region_hu", defaultEnabled: false }, // Hungary { id: "region_is", value: "countryIS", paramName: "cr", textKey: "option_text_region_is", defaultEnabled: false }, // Iceland { id: "region_it", value: "countryIT", paramName: "cr", textKey: "option_text_region_it", defaultEnabled: true }, { id: "region_lv", value: "countryLV", paramName: "cr", textKey: "option_text_region_lv", defaultEnabled: false }, // Latvia { id: "region_lt", value: "countryLT", paramName: "cr", textKey: "option_text_region_lt", defaultEnabled: false }, // Lithuania { id: "region_lu", value: "countryLU", paramName: "cr", textKey: "option_text_region_lu", defaultEnabled: false }, // Luxembourg { id: "region_nl", value: "countryNL", paramName: "cr", textKey: "option_text_region_nl", defaultEnabled: false }, // Netherlands { id: "region_no", value: "countryNO", paramName: "cr", textKey: "option_text_region_no", defaultEnabled: false }, // Norway { id: "region_pl", value: "countryPL", paramName: "cr", textKey: "option_text_region_pl", defaultEnabled: false }, // Poland { id: "region_pt", value: "countryPT", paramName: "cr", textKey: "option_text_region_pt", defaultEnabled: false }, // Portugal { id: "region_ro", value: "countryRO", paramName: "cr", textKey: "option_text_region_ro", defaultEnabled: false }, // Romania { id: "region_ru", value: "countryRU", paramName: "cr", textKey: "option_text_region_ru", defaultEnabled: false }, // Russia { id: "region_rs", value: "countryRS", paramName: "cr", textKey: "option_text_region_rs", defaultEnabled: false }, // Serbia { id: "region_sk", value: "countrySK", paramName: "cr", textKey: "option_text_region_sk", defaultEnabled: false }, // Slovakia { id: "region_si", value: "countrySI", paramName: "cr", textKey: "option_text_region_si", defaultEnabled: false }, // Slovenia { id: "region_es", value: "countryES", paramName: "cr", textKey: "option_text_region_es", defaultEnabled: true }, { id: "region_se", value: "countrySE", paramName: "cr", textKey: "option_text_region_se", defaultEnabled: false }, // Sweden { id: "region_ch", value: "countryCH", paramName: "cr", textKey: "option_text_region_ch", defaultEnabled: false }, // Switzerland { id: "region_tr", value: "countryTR", paramName: "cr", textKey: "option_text_region_tr", defaultEnabled: false }, // Turkey { id: "region_ua", value: "countryUA", paramName: "cr", textKey: "option_text_region_ua", defaultEnabled: false }, // Ukraine { id: "region_gb", value: "countryGB", paramName: "cr", textKey: "option_text_region_gb", defaultEnabled: true }, // United Kingdom { id: "region_ie", value: "countryIE", paramName: "cr", textKey: "option_text_region_ie", defaultEnabled: false }, // Ireland (Moved for consistency) // Asia { id: "region_cn", value: "countryCN", paramName: "cr", textKey: "option_text_region_cn", defaultEnabled: false }, { id: "region_hk", value: "countryHK", paramName: "cr", textKey: "option_text_region_hk", defaultEnabled: false }, { id: "region_in", value: "countryIN", paramName: "cr", textKey: "option_text_region_in", defaultEnabled: false }, { id: "region_id", value: "countryID", paramName: "cr", textKey: "option_text_region_id", defaultEnabled: false }, { id: "region_il", value: "countryIL", paramName: "cr", textKey: "option_text_region_il", defaultEnabled: false }, { id: "region_jp", value: "countryJP", paramName: "cr", textKey: "option_text_region_jp", defaultEnabled: true }, { id: "region_my", value: "countryMY", paramName: "cr", textKey: "option_text_region_my", defaultEnabled: false }, { id: "region_ph", value: "countryPH", paramName: "cr", textKey: "option_text_region_ph", defaultEnabled: false }, { id: "region_sa", value: "countrySA", paramName: "cr", textKey: "option_text_region_sa", defaultEnabled: false }, { id: "region_sg", value: "countrySG", paramName: "cr", textKey: "option_text_region_sg", defaultEnabled: false }, { id: "region_kr", value: "countryKR", paramName: "cr", textKey: "option_text_region_kr", defaultEnabled: true }, { id: "region_tw", value: "countryTW", paramName: "cr", textKey: "option_text_region_tw", defaultEnabled: true }, { id: "region_th", value: "countryTH", paramName: "cr", textKey: "option_text_region_th", defaultEnabled: false }, { id: "region_ae", value: "countryAE", paramName: "cr", textKey: "option_text_region_ae", defaultEnabled: false }, { id: "region_vn", value: "countryVN", paramName: "cr", textKey: "option_text_region_vn", defaultEnabled: false }, // Oceania { id: "region_au", value: "countryAU", paramName: "cr", textKey: "option_text_region_au", defaultEnabled: true }, { id: "region_nz", value: "countryNZ", paramName: "cr", textKey: "option_text_region_nz", defaultEnabled: true }, // Africa { id: "region_eg", value: "countryEG", paramName: "cr", textKey: "option_text_region_eg", defaultEnabled: false }, { id: "region_ng", value: "countryNG", paramName: "cr", textKey: "option_text_region_ng", defaultEnabled: false }, { id: "region_za", value: "countryZA", paramName: "cr", textKey: "option_text_region_za", defaultEnabled: false }, ], site: [ { id: "gite_site_any", value: "", type: "site_clear", textKey: "option_text_site_any", defaultEnabled: true, isCustom: false } ] }; // --- Default Settings Structure --- // Helper function to generate default options for managed categories from GITE_OPTION_DEFINITIONS. function _generateDefaultOptionsFromDefinitions(categoryKey) { const definitions = GITE_OPTION_DEFINITIONS[categoryKey]; if (!definitions) { if (DEBUG_MODE) warn(`_generateDefaultOptionsFromDefinitions: No definitions found for categoryKey "${categoryKey}". Returning empty array.`); return []; } if (categoryKey === 'site') { // 'site' only has "Any site" in GITE_OPTION_DEFINITIONS return definitions.map(def => ({ id: def.id, ...(def.type && { type: def.type }), value: def.value, textKey: def.textKey, customText: null, isCustom: def.isCustom !== undefined ? def.isCustom : false, isEnabled: def.defaultEnabled !== undefined ? def.defaultEnabled : true, })); } return definitions.map(def => ({ // For size, exactSize predefined, time, region id: def.id, ...(def.type && { type: def.type }), value: def.value, tbsValue: def.tbsValue, textKey: def.textKey, customText: null, isCustom: false, isEnabled: def.defaultEnabled !== undefined ? def.defaultEnabled : true, ...(def.column && { column: def.column }), // 'column' is only relevant for old 'size' filter menu rendering })); } const GITE_DEFAULT_SETTINGS = { general: { selectedLanguage: "auto", autoExpandTools: true, showSettingsButtonOnToolbar: true }, filters: { size: { enabled: true, options: _generateDefaultOptionsFromDefinitions('size') }, exactSize: { enabled: true, predefinedOptions: _generateDefaultOptionsFromDefinitions('exactSize'), userDefinedOptions: [] }, time: { enabled: true, options: _generateDefaultOptionsFromDefinitions('time') }, region: { enabled: true, options: _generateDefaultOptionsFromDefinitions('region') }, site: { enabled: true, userDefinedOptions: [ // Bundled default sites for "Site Search" // --- Enabled by default (12 items) --- { id: "gite_defaultsite_wikipedia", label: "Wikipedia", value: "wikipedia.org", isEnabled: true, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_flickr", label: "Flickr", value: "flickr.com", isEnabled: true, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_artstation", label: "ArtStation", value: "artstation.com", isEnabled: true, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_behance", label: "Behance", value: "behance.net", isEnabled: true, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_unsplash", label: "Unsplash", value: "unsplash.com", isEnabled: true, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_pexels", label: "Pexels", value: "pexels.com", isEnabled: true, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_reddit", label: "Reddit", value: "reddit.com", isEnabled: true, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_twitter", label: "X (Twitter)", value: "twitter.com", isEnabled: true, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_commons_wikimedia", label: "Wikimedia Commons", value: "commons.wikimedia.org", isEnabled: true, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_500px", label: "500px", value: "500px.com", isEnabled: true, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_archive", label: "Internet Archive", value: "archive.org", isEnabled: true, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_cara", label: "Cara", value: "cara.app", isEnabled: true, isCustom: true, type: 'site_filter' }, // --- Disabled by default (20 items, for a total of 32 with enabled ones) --- { id: "gite_defaultsite_deviantart", label: "DeviantArt", value: "deviantart.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_nasa", label: "NASA", value: "nasa.gov", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_dribbble", label: "Dribbble", value: "dribbble.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_bluesky", label: "Bluesky", value: "bsky.app", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_mastodon_social", label: "Mastodon (social)", value: "mastodon.social", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_lemmy_world", label: "Lemmy (world)", value: "lemmy.world", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_pixabay", label: "Pixabay", value: "pixabay.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_tumblr", label: "Tumblr", value: "tumblr.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_steamcommunity", label: "Steam Community", value: "steamcommunity.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_natgeo", label: "National Geographic", value: "nationalgeographic.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_magnumphotos", label: "Magnum Photos", value: "magnumphotos.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_gettyimages", label: "Getty Images", value: "gettyimages.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_ign", label: "IGN", value: "ign.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_vogue", label: "Vogue", value: "vogue.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_archdigest", label: "Architectural Digest", value: "architecturaldigest.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_nature", label: "Nature Portfolio", value: "nature.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_cgmeetup", label: "CGMeetup", value: "cgmeetup.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_artfol", label: "Artfol", value: "artfol.me", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_facebook", label: "Facebook", value: "facebook.com", isEnabled: false, isCustom: true, type: 'site_filter' }, { id: "gite_defaultsite_metmuseum", label: "The Metropolitan Museum of Art", value: "metmuseum.org", isEnabled: false, isCustom: true, type: 'site_filter' }, ] }, aspectRatio: { enabled: true }, color: { enabled: true }, type: { enabled: true }, usageRights: { enabled: true }, fileType: { enabled: true }, } }; let giteSettings = {}; let giteSettingsPanelElement = null; let currentGiteSettingsForPanel = {}; let isSettingsPanelOpen = false; // --- Logging Utilities --- function log(...args) { if (DEBUG_MODE) console.log('[GITE]', ...args); } function error(...args) { console.error('[GITE]', ...args); } function warn(...args) { console.warn('[GITE]', ...args); } // --- Internationalization (I18N) Utilities --- function getLocalizedString(key, lang = CURRENT_LANGUAGE, fallbackText = null) { const effectiveLang = GITE_I18N_STRINGS[lang] ? lang : 'en'; const primaryText = GITE_I18N_STRINGS[effectiveLang]?.[key]; if (primaryText !== undefined) return primaryText; let returnedText = fallbackText !== null ? fallbackText : key; if (effectiveLang !== 'en') { const enText = GITE_I18N_STRINGS['en']?.[key]; if (enText !== undefined) { returnedText = enText; if (DEBUG_MODE) warn(`[i18n] Key "${key}" not found for lang "${lang}". Fell back to "en": "${enText}".`); } else if (DEBUG_MODE) { warn(`[i18n] Key "${key}" not found for lang "${lang}" or "en". Using ${fallbackText !== null ? 'provided fallback' : 'key as text'}: "${returnedText}".`); } } else if (DEBUG_MODE) { warn(`[i18n] Key "${key}" not found for lang "en". Using ${fallbackText !== null ? 'provided fallback' : 'key as text'}: "${returnedText}".`); } return returnedText; } function getLocalizedTextByPath(pathString, lang = CURRENT_LANGUAGE, fallbackText = '') { if (!pathString || typeof pathString !== 'string') { if (DEBUG_MODE && pathString !== null) warn(`getLocalizedTextByPath: non-string/null path: ${pathString}`); return fallbackText; } const keys = pathString.split('.'); if (keys.length === 1) return getLocalizedString(keys[0], lang, fallbackText || pathString); let primaryLangTranslations = GITE_I18N_STRINGS[lang] || GITE_I18N_STRINGS['en']; let current = primaryLangTranslations; for (let i = 0; i < keys.length; i++) { if (current && typeof current === 'object' && keys[i] in current) { current = current[keys[i]]; } else { if (lang !== 'en' && GITE_I18N_STRINGS['en']) { let enCurrent = GITE_I18N_STRINGS['en']; for (let j = 0; j < keys.length; j++) { if (enCurrent && typeof enCurrent === 'object' && keys[j] in enCurrent) enCurrent = enCurrent[keys[j]]; else return fallbackText || pathString; } return typeof enCurrent === 'string' ? enCurrent : (fallbackText || pathString); } return fallbackText || pathString; } } return typeof current === 'string' ? current : (fallbackText || pathString); } function initializeCurrentLanguage() { let preferredLang = 'auto'; if (giteSettings && giteSettings.general && giteSettings.general.selectedLanguage) { preferredLang = giteSettings.general.selectedLanguage; } if (preferredLang !== 'auto' && GITE_I18N_STRINGS[preferredLang]) { CURRENT_LANGUAGE = preferredLang; if (DEBUG_MODE) log(`Using preferred lang from settings: ${CURRENT_LANGUAGE}`); } else { const browserLangInit = (navigator.language || navigator.userLanguage || 'en').toLowerCase(); if (browserLangInit.startsWith('zh-tw') || browserLangInit.startsWith('zh-hk') || browserLangInit.startsWith('zh-hant')) CURRENT_LANGUAGE = 'zh-TW'; else if (browserLangInit.startsWith('ja')) CURRENT_LANGUAGE = 'ja'; // Added Japanese detection else CURRENT_LANGUAGE = 'en'; if (DEBUG_MODE) log(`Auto-detected browser lang: ${browserLangInit}, Using: ${CURRENT_LANGUAGE}`); if (preferredLang !== 'auto' && !GITE_I18N_STRINGS[preferredLang] && DEBUG_MODE) { warn(`Preferred lang "${preferredLang}" was specified but is not supported. Using auto-detected: ${CURRENT_LANGUAGE}.`); } } } function updateAllLocalizableElements(newLang, parentElement = document) { const langToUse = newLang || CURRENT_LANGUAGE; if (DEBUG_MODE) { const parentId = parentElement === document ? "document" : (parentElement.id || parentElement.tagName || "UnnamedElement"); log(`Updating localizable elements to lang: ${langToUse} within parent: ${parentId}`); } if (!parentElement || typeof parentElement.querySelectorAll !== 'function') { warn(`updateAllLocalizableElements: Invalid parentElement provided.`); return; } parentElement.querySelectorAll(`[${GITE_LANG_KEY_ATTR}]`).forEach(element => { const key = element.getAttribute(GITE_LANG_KEY_ATTR); const attributeTarget = element.getAttribute('data-gite-lang-target-attr'); if (key) { const localizedText = getLocalizedString(key, langToUse, key); if (attributeTarget && element.hasAttribute(attributeTarget)) element.setAttribute(attributeTarget, localizedText); else if (element.tagName === 'INPUT' && (element.type === 'button' || element.type === 'submit' || element.type === 'reset')) element.value = localizedText; else if (element.tagName === 'INPUT' && attributeTarget === 'placeholder') element.setAttribute('placeholder', localizedText); else element.textContent = localizedText; } }); } // --- Global State Variables for UI interaction --- let giteToolsExpandTimer = null; let enhancedFiltersInitializedThisInstance = false; let customFilterMenus = []; let activeDatePicker = null; let datePickerOverlayElement = null; // --- Settings Panel State Accessors (operate on currentGiteSettingsForPanel) --- function getSettingFromPanel(path, defaultValue = undefined) { let obj = currentGiteSettingsForPanel; const parts = path.split('.'); for (let i = 0; i < parts.length; i++) { if (!obj || typeof obj !== 'object' || !obj.hasOwnProperty(parts[i])) return defaultValue; obj = obj[parts[i]]; } return obj; } function setSettingInPanel(path, value) { let obj = currentGiteSettingsForPanel; const parts = path.split('.'); for (let i = 0; i < parts.length - 1; i++) { if (!obj[parts[i]] || typeof obj[parts[i]] !== 'object') obj[parts[i]] = {}; obj = obj[parts[i]]; } obj[parts[parts.length - 1]] = value; } // --- Date Picker Specific UI Function --- function closeDatePicker() { if (activeDatePicker && activeDatePicker.parentNode) { activeDatePicker.parentNode.removeChild(activeDatePicker); if (DEBUG_MODE) log("Custom date picker UI removed."); } activeDatePicker = null; if (datePickerOverlayElement && datePickerOverlayElement.parentNode) { datePickerOverlayElement.parentNode.removeChild(datePickerOverlayElement); if (DEBUG_MODE) log("Date picker overlay removed."); } datePickerOverlayElement = null; } // --- Color Utility Functions --- function hexToRgb(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } function getSimpleContrastColor(hexColor) { const rgb = hexToRgb(hexColor); if (!rgb) return '#000000'; const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255; return luminance > 0.5 ? '#000000' : '#FFFFFF'; } // --- Theme Detection for UI styling --- function detectAndApplyThemeClass() { const colorSchemeMeta = document.querySelector('meta[name="color-scheme"]'); let isDarkMode = false; if (colorSchemeMeta && colorSchemeMeta.content) { const content = colorSchemeMeta.content.toLowerCase(); isDarkMode = content.includes("dark"); } else { if (DEBUG_MODE) log("Color-scheme meta tag not found, trying prefers-color-scheme media query."); isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; } document.documentElement.classList.toggle('gite-detected-dark-theme', isDarkMode); document.documentElement.classList.toggle('gite-detected-light-theme', !isDarkMode); if (DEBUG_MODE) log(`Applied CSS theme class: .gite-detected-${isDarkMode ? 'dark' : 'light'}-theme.`); } // --- CSS Injection --- function injectCustomHoverStyles() { const styleId = 'gite-custom-hover-styles'; let styleElement = document.getElementById(styleId); if (styleElement && !DEBUG_MODE && document.querySelector('#gite-settings-panel')) return; const GOOGLE_DARK_THEME_SELECTOR = 'html[data-theme="dark"], body.dark, body.dark-mode, html.dark'; const GOOGLE_LIGHT_THEME_SELECTOR = 'html[data-theme="light"], body.light, body.light-mode, html.light'; const cssBaseVariables = ` :root { --gite-hover-bg-light: #ebebeb; --gite-hover-text-light: var(--uv-styles-color-on-tertiary, #202124); --gite-separator-light: var(--uv-styles-color-outline, #dadce0); --gite-hover-bg-dark: var(--uv-styles-color-outline, #3c4043); --gite-hover-text-dark: var(--YLNNHc, #e8eaed); --gite-separator-dark: var(--uv-styles-color-outline, #3c4043); --gite-hover-bg: var(--gite-hover-bg-light); --gite-hover-text: var(--gite-hover-text-light); --gite-separator-color: var(--gite-separator-light); --gite-color-swatch-border-light: #ccc; --gite-color-swatch-border-dark: #5f6368; --gite-color-swatch-border: var(--gite-color-swatch-border-light); --gite-datepicker-bg: var(--uv-styles-color-surface, #fff); --gite-datepicker-text: var(--uv-styles-color-on-surface, #202124); --gite-datepicker-border: var(--uv-styles-color-outline, #ccc); --gite-datepicker-button-bg: var(--uv-styles-color-primary, #1a73e8); --gite-datepicker-button-text: var(--uv-styles-color-on-primary, #fff); --gite-datepicker-button-cancel-bg: var(--uv-styles-color-tertiary, #f1f3f4); --gite-datepicker-button-cancel-text: var(--uv-styles-color-on-tertiary, #202124); --gite-custom-size-delete-bg-light: #f8d7da; --gite-custom-size-delete-text-light: #721c24; --gite-custom-size-delete-border-light: #f5c6cb; --gite-custom-size-delete-bg-dark: #5d383b; --gite-custom-size-delete-text-dark: #f5c6cb; --gite-custom-size-delete-border-dark: #8c3a41; --gite-custom-size-delete-bg: var(--gite-custom-size-delete-bg-light); --gite-custom-size-delete-text: var(--gite-custom-size-delete-text-light); --gite-custom-size-delete-border: var(--gite-custom-size-delete-border-light); --gite-modal-max-width: 580px; --gite-modal-max-height: 85vh; --gite-tab-nav-width: 120px; --gite-focus-ring-color: #4285f4; --gite-input-bg: var(--gite-datepicker-bg); --gite-input-text: var(--gite-datepicker-text); --gite-input-border: var(--gite-datepicker-border); }`; const cssThemeOverrides = ` html.gite-detected-light-theme { --gite-hover-bg: var(--gite-hover-bg-light) !important; --gite-hover-text: var(--gite-hover-text-light) !important; --gite-separator-color: var(--gite-separator-light) !important; --gite-color-swatch-border: var(--gite-color-swatch-border-light) !important; --gite-datepicker-bg: var(--uv-styles-color-surface, #fff) !important; --gite-datepicker-text: var(--uv-styles-color-on-surface, #202124) !important; --gite-datepicker-border: var(--uv-styles-color-outline, #ccc) !important; --gite-datepicker-button-bg: var(--uv-styles-color-primary, #1a73e8) !important; --gite-datepicker-button-text: var(--uv-styles-color-on-primary, #fff) !important; --gite-datepicker-button-cancel-bg: var(--uv-styles-color-tertiary, #f1f3f4) !important; --gite-datepicker-button-cancel-text: var(--uv-styles-color-on-tertiary, #202124) !important; --gite-custom-size-delete-bg: var(--gite-custom-size-delete-bg-light) !important; --gite-custom-size-delete-text: var(--gite-custom-size-delete-text-light) !important; --gite-custom-size-delete-border: var(--gite-custom-size-delete-border-light) !important; } html.gite-detected-dark-theme { --gite-hover-bg: var(--gite-hover-bg-dark) !important; --gite-hover-text: var(--gite-hover-text-dark) !important; --gite-separator-color: var(--gite-separator-dark) !important; --gite-color-swatch-border: var(--gite-color-swatch-border-dark) !important; --gite-datepicker-bg: #202124 !important; --gite-datepicker-text: #e8eaed !important; --gite-datepicker-border: #5f6368 !important; --gite-datepicker-button-bg: #8ab4f8 !important; --gite-datepicker-button-text: #202124 !important; --gite-datepicker-button-cancel-bg: #303134 !important; --gite-datepicker-button-cancel-text: #bdc1c6 !important; --gite-custom-size-delete-bg: var(--gite-custom-size-delete-bg-dark) !important; --gite-custom-size-delete-text: var(--gite-custom-size-delete-text-dark) !important; --gite-custom-size-delete-border: var(--gite-custom-size-delete-border-dark) !important; } @media (prefers-color-scheme: light) { :root:not(.gite-detected-dark-theme):not(${GOOGLE_DARK_THEME_SELECTOR}) { --gite-hover-bg: var(--gite-hover-bg-light); --gite-hover-text: var(--gite-hover-text-light); --gite-separator-color: var(--gite-separator-light); --gite-color-swatch-border: var(--gite-color-swatch-border-light); --gite-datepicker-bg: var(--uv-styles-color-surface, #fff); --gite-datepicker-text: var(--uv-styles-color-on-surface, #202124); --gite-datepicker-border: var(--uv-styles-color-outline, #ccc); --gite-datepicker-button-bg: var(--uv-styles-color-primary, #1a73e8); --gite-datepicker-button-text: var(--uv-styles-color-on-primary, #fff); --gite-datepicker-button-cancel-bg: var(--uv-styles-color-tertiary, #f1f3f4); --gite-datepicker-button-cancel-text: var(--uv-styles-color-on-tertiary, #202124); --gite-custom-size-delete-bg: var(--gite-custom-size-delete-bg-light); --gite-custom-size-delete-text: var(--gite-custom-size-delete-text-light); --gite-custom-size-delete-border: var(--gite-custom-size-delete-border-light); } } @media (prefers-color-scheme: dark) { :root:not(.gite-detected-light-theme):not(${GOOGLE_LIGHT_THEME_SELECTOR}) { --gite-hover-bg: var(--gite-hover-bg-dark); --gite-hover-text: var(--gite-hover-text-dark); --gite-separator-color: var(--gite-separator-dark); --gite-color-swatch-border: var(--gite-color-swatch-border-dark); --gite-datepicker-bg: #202124; --gite-datepicker-text: #e8eaed; --gite-datepicker-border: #5f6368; --gite-datepicker-button-bg: #8ab4f8; --gite-datepicker-button-text: #202124; --gite-datepicker-button-cancel-bg: #303134; --gite-datepicker-button-cancel-text: #bdc1c6; --gite-custom-size-delete-bg: var(--gite-custom-size-delete-bg-dark); --gite-custom-size-delete-text: var(--gite-custom-size-delete-text-dark); --gite-custom-size-delete-border: var(--gite-custom-size-delete-border-dark); } } ${GOOGLE_LIGHT_THEME_SELECTOR} { --gite-hover-bg: var(--gite-hover-bg-light) !important; --gite-hover-text: var(--gite-hover-text-light) !important; --gite-separator-color: var(--gite-separator-light) !important; --gite-color-swatch-border: var(--gite-color-swatch-border-light) !important; --gite-datepicker-bg: var(--uv-styles-color-surface, #fff) !important; --gite-datepicker-text: var(--uv-styles-color-on-surface, #202124) !important; --gite-datepicker-border: var(--uv-styles-color-outline, #ccc) !important; --gite-datepicker-button-bg: var(--uv-styles-color-primary, #1a73e8) !important; --gite-datepicker-button-text: var(--uv-styles-color-on-primary, #fff) !important; --gite-datepicker-button-cancel-bg: var(--uv-styles-color-tertiary, #f1f3f4) !important; --gite-datepicker-button-cancel-text: var(--uv-styles-color-on-tertiary, #202124) !important; --gite-custom-size-delete-bg: var(--gite-custom-size-delete-bg-light) !important; --gite-custom-size-delete-text: var(--gite-custom-size-delete-text-light) !important; --gite-custom-size-delete-border: var(--gite-custom-size-delete-border-light) !important; } ${GOOGLE_DARK_THEME_SELECTOR} { --gite-hover-bg: var(--gite-hover-bg-dark) !important; --gite-hover-text: var(--gite-hover-text-dark) !important; --gite-separator-color: var(--gite-separator-dark) !important; --gite-color-swatch-border: var(--gite-color-swatch-border-dark) !important; --gite-datepicker-bg: #202124 !important; --gite-datepicker-text: #e8eaed !important; --gite-datepicker-border: #5f6368 !important; --gite-datepicker-button-bg: #8ab4f8 !important; --gite-datepicker-button-text: #202124 !important; --gite-datepicker-button-cancel-bg: #303134 !important; --gite-datepicker-button-cancel-text: #bdc1c6 !important; --gite-custom-size-delete-bg: var(--gite-custom-size-delete-bg-dark) !important; --gite-custom-size-delete-text: var(--gite-custom-size-delete-text-dark) !important; --gite-custom-size-delete-border: var(--gite-custom-size-delete-border-dark) !important; } `; const cssComponentStyles = ` .gite-custom-menu-item { font-family: Arial, sans-serif; font-size: 14px; } .gite-custom-menu-item:hover { background-color: var(--gite-hover-bg) !important; color: var(--gite-hover-text) !important; } .gite-color-palette-container { display: grid; grid-template-columns: repeat(6, 1fr); gap: 4px; padding: 6px 16px; align-items: center; justify-items: center;} .gite-color-swatch-item { padding: 0 !important; margin:0; width: auto; height: auto; display: flex; align-items: center; justify-content: center;} .gite-color-swatch-item .YpcDnf { padding: 2px !important; display: flex; } .gite-color-swatch { display: block; width: 20px; height: 20px; border: 1px solid var(--gite-color-swatch-border); border-radius: 4px; cursor: pointer; box-sizing: border-box; } .gite-color-swatch:hover { border-color: var(--gite-hover-text); } .gite-color-swatch.gite-selected-color { border-width: 2px; } html.gite-detected-light-theme .gite-color-swatch[style*="background:#fff"], html.gite-detected-light-theme .gite-color-swatch[style*="background:white"] { border-color: #e0e0e0; } .gite-datepicker-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: rgba(0, 0, 0, 0.65); z-index: 20000; } .gite-custom-date-picker { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: var(--gite-datepicker-bg); color: var(--gite-datepicker-text); border: 1px solid var(--gite-datepicker-border); box-shadow: 0 4px 8px rgba(0,0,0,0.2); padding: 20px; z-index: 20001; border-radius: 8px; } .gite-custom-date-picker div { margin-bottom: 10px; } .gite-custom-date-picker label { margin-right: 5px; } .gite-custom-date-picker input[type="date"] { padding: 5px; border: 1px solid var(--gite-datepicker-border); border-radius: 4px; background-color: var(--gite-datepicker-bg); color: var(--gite-datepicker-text); } html.gite-detected-dark-theme .gite-custom-date-picker input[type="date"] { color-scheme: dark; } html.gite-detected-light-theme .gite-custom-date-picker input[type="date"] { color-scheme: light; } .gite-custom-date-picker button { padding: 8px 15px; border-radius: 4px; cursor: pointer; margin-right: 10px; border: 1px solid var(--gite-datepicker-button-bg); background-color: var(--gite-datepicker-button-bg); color: var(--gite-datepicker-button-text); } .gite-custom-date-picker button:hover { opacity: 0.9; } .gite-custom-date-picker button.gite-date-cancel { background-color: var(--gite-datepicker-button-cancel-bg); color: var(--gite-datepicker-button-cancel-text); border-color: var(--gite-datepicker-border); } .gite-active-color-indicator { display: inline-block; width: 36px; height: 16px; border: 1px solid var(--gite-color-swatch-border); border-radius: 3px; vertical-align: text-bottom; margin: 0 2px 0 0; } html.gite-detected-light-theme .gite-active-color-indicator[style*="background-color: rgb(255, 255, 255)"], html.gite-detected-light-theme .gite-active-color-indicator[style*="background-color:#fff"], html.gite-detected-light-theme .gite-active-color-indicator[style*="background-color:white"] { border-color: #bbb; } #${GITE_CLEAR_BUTTON_ID}${FILTER_BUTTON_WRAPPER_CLASS} { margin-left: 16px !important; white-space: nowrap; cursor: pointer; } .${FILTER_BUTTON_WRAPPER_CLASS}.EISXeb ${FILTER_BUTTON_TEXT_CLASS} { font-weight: bold !important; } .gite-custom-imagesize-input-area { padding: 8px 16px; margin-bottom: 8px; border-bottom: 1px solid var(--gite-separator-color); } .gite-imagesize-input-controls { display: flex; align-items: center; } .gite-imagesize-input-controls > span { color: var(--gite-datepicker-text); margin: 0 5px; } .gite-custom-imagesize-input-area .gite-imagesize-input-controls input[type="number"] { width: 55px; padding: 6px; border: 1px solid var(--gite-datepicker-border); border-radius: 4px; background-color: var(--gite-datepicker-bg); color: var(--gite-datepicker-text); text-align: center; -moz-appearance: textfield; } .gite-custom-imagesize-input-area input[type="number"]::-webkit-outer-spin-button, .gite-custom-imagesize-input-area input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .gite-custom-imagesize-input-area button { padding: 6px 0px; border-radius: 4px; cursor: pointer; border-width: 1px; border-style: solid; font-size: 16px; font-weight: bold; line-height: 1; min-width: 40px; text-align: center; } /* Specific ID for apply button in menu */ #gite-imagesize-apply-menu { margin-left: 8px; border-color: var(--gite-datepicker-button-bg); background-color: var(--gite-datepicker-button-bg); color: var(--gite-datepicker-button-text); padding: 6px 10px !important; font-size: 1em !important; font-weight: normal !important; min-width: auto !important; } #gite-imagesize-apply-menu:hover { opacity: 0.9; } html.gite-detected-dark-theme .gite-custom-imagesize-input-area .gite-imagesize-input-controls input[type="number"] { color-scheme: dark; } html.gite-detected-light-theme .gite-custom-imagesize-input-area .gite-imagesize-input-controls input[type="number"] { color-scheme: light; } `; const cssSettingsPanelStyles = ` .gite-modal { display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 20000 !important; font-family: Arial, sans-serif; font-size: 13px; align-items: center; justify-content: center; } .gite-modal-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.65); } .gite-modal-content { position: relative; background-color: var(--gite-datepicker-bg); color: var(--gite-datepicker-text); border: 1px solid var(--gite-datepicker-border); border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); width: 90%; max-width: var(--gite-modal-max-width, 580px); max-height: var(--gite-modal-max-height, 85vh); display: flex; flex-direction: column; overflow: hidden; } .gite-modal-header { padding: 10px 12px; border-bottom: 1px solid var(--gite-separator-color); display: flex; justify-content: space-between; align-items: center; } .gite-modal-header h2 { margin: 0; font-size: 1.1em; font-weight: normal; } .gite-modal-close-btn { background: none; border: none; font-size: 1.6em; line-height: 1; padding: 0 4px; color: var(--gite-datepicker-text); cursor: pointer; opacity: 0.7; } .gite-modal-close-btn:hover { opacity: 1; } .gite-modal-body { padding: 0; flex-grow: 1; overflow: hidden; display: flex; } .gite-tabs-navigation { flex: 0 0 var(--gite-tab-nav-width, 120px); margin-right: 0px; border-right: 1px solid var(--gite-separator-color); overflow-y: auto; padding: 6px; box-sizing: border-box; } .gite-tabs-navigation ul { list-style: none; padding: 0; margin: 0; } .gite-tabs-navigation button[role="tab"] { display: block; width: 100%; padding: 8px 10px; text-align: left; background: none; border: 1px solid transparent; border-radius: 4px; margin-bottom: 4px; cursor: pointer; color: var(--gite-datepicker-text); font-size: 0.9em; box-sizing: border-box; word-break: break-word; } .gite-tabs-navigation button[role="tab"]:hover:not([disabled]) { background-color: var(--gite-hover-bg); } .gite-tabs-navigation button[role="tab"][aria-selected="true"] { background-color: var(--gite-datepicker-button-bg); color: var(--gite-datepicker-button-text); font-weight: bold; } .gite-tabs-navigation button[role="tab"]:focus-visible:not([disabled]) { border-color: var(--gite-focus-ring-color, #4285f4); outline: none; box-shadow: 0 0 0 1px var(--gite-focus-ring-color, #4285f4) inset; } .gite-tabs-navigation button[role="tab"][disabled] { opacity: 0.5; cursor: not-allowed; } .gite-tabs-content { flex-grow: 1; overflow-y: auto; padding: 10px; box-sizing: border-box; } .gite-tab-panel { display: none; animation: giteFadeIn 0.3s ease-out; } .gite-tab-panel.gite-active-panel { display: block; } @keyframes giteFadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } .gite-setting-group { margin-bottom: 10px; display: flex; flex-direction: column; align-items: flex-start; gap: 4px; } .gite-setting-group label { font-weight: 500; } .gite-setting-group select { width: 100%; padding: 6px; border: 1px solid var(--gite-input-border); border-radius: 4px; background-color: var(--gite-input-bg); color: var(--gite-input-text); box-sizing: border-box; height: 30px;} .gite-setting-group input[type="checkbox"] { width: auto; margin-top: 2px; align-self: flex-start; } html.gite-detected-dark-theme .gite-setting-group select { color-scheme: dark; } .gite-modal-footer { padding: 8px 12px; border-top: 1px solid var(--gite-separator-color); display: flex; justify-content: space-between; align-items: center; background-color: rgba(0,0,0,0.03); } html.gite-detected-dark-theme .gite-modal-footer { background-color: rgba(255,255,255,0.03); } .gite-modal-footer button { padding: 7px 12px; border-radius: 4px; cursor: pointer; margin-left: 6px; border: 1px solid var(--gite-datepicker-border); background-color: var(--gite-datepicker-button-cancel-bg); color: var(--gite-datepicker-button-cancel-text); font-size: 0.85em; } .gite-modal-footer button:hover { opacity: 0.9; } .gite-modal-footer .gite-primary-btn { background-color: var(--gite-datepicker-button-bg); color: var(--gite-datepicker-button-text); border-color: var(--gite-datepicker-button-bg); font-weight: bold; } #${GITE_SETTINGS_BUTTON_ID}.BaegVc { margin-left: 8px !important; padding: 0 6px !important; cursor: pointer; display: inline-flex !important; align-items: center !important; font-size: 0.9em; line-height: normal !important; height: auto !important; } #${GITE_SETTINGS_BUTTON_ID}.BaegVc:hover { background-color: var(--gite-hover-bg) !important; } #${GITE_SETTINGS_BUTTON_ID}.BaegVc:focus-visible { outline: 1px solid var(--gite-focus-ring-color, #4285f4) !important; outline-offset: -1px !important; box-shadow: none !important; } .gite-exactsize-panel-section, .gite-options-panel-section { margin-bottom: 15px; padding-bottom:10px; border-bottom: 1px dashed var(--gite-separator-color); } .gite-exactsize-panel-section:last-child, .gite-options-panel-section:last-child { border-bottom: none; margin-bottom: 0; } .gite-exactsize-panel-section h3, .gite-options-panel-section h3 { font-size: 1em; margin-top: 0; margin-bottom: 8px; color: var(--gite-datepicker-text); opacity: 0.9; font-weight:500; } .gite-exactsize-options-list, .gite-options-list { list-style: none; padding: 0; margin: 0 0 10px 0; max-height: 120px; overflow-y:auto; border: 1px solid var(--gite-input-border); border-radius: 4px; } .gite-exactsize-options-list li, .gite-options-list li { display: flex; align-items: center; padding: 5px 8px; border-bottom: 1px solid var(--gite-separator-light); gap: 6px; font-size:0.9em; } .gite-exactsize-options-list li:last-child, .gite-options-list li:last-child { border-bottom: none; } .gite-exactsize-options-list li .gite-option-text, .gite-options-list li .gite-option-text { flex-grow: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .gite-exactsize-options-list li .gite-option-value-hint, .gite-options-list li .gite-option-value-hint { font-size: 0.8em; color: #777; margin-left: auto; flex-shrink: 0; padding-left: 4px; } html.gite-detected-dark-theme .gite-exactsize-options-list li .gite-option-value-hint, html.gite-detected-dark-theme .gite-options-list li .gite-option-value-hint { color: #aaa; } .gite-exactsize-options-list li input[type="checkbox"], .gite-options-list li input[type="checkbox"] { margin-right: 5px; flex-shrink:0; } .gite-exactsize-options-list li .gite-edit-label-input { flex-grow: 1; padding: 3px; font-size: 0.9em; border: 1px solid var(--gite-input-border); border-radius: 3px; background-color: var(--gite-input-bg); color: var(--gite-input-text); } .gite-exactsize-options-list li .gite-actions { margin-left:auto; display: flex; gap: 4px; flex-shrink:0; } .gite-exactsize-options-list li .gite-actions button { background: none; border: none; cursor: pointer; opacity: 0.7; padding: 2px 4px; font-size: 0.9em; color: var(--gite-datepicker-text); } .gite-exactsize-options-list li .gite-actions button:hover { opacity: 1; } .gite-add-exact-size-form { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; } .gite-add-exact-size-form .gite-form-row { display: flex; gap: 8px; align-items: flex-end; } .gite-add-exact-size-form div:not(.gite-form-row) { display: flex; flex-direction: column; } .gite-add-exact-size-form label { font-size: 0.8em; margin-bottom: 2px; opacity: 0.8; } .gite-add-exact-size-form input[type="number"], .gite-add-exact-size-form input[type="text"] { width:100%; padding: 5px; border: 1px solid var(--gite-input-border); border-radius: 4px; background-color: var(--gite-input-bg); color: var(--gite-input-text); box-sizing: border-box; height: 28px; font-size:0.9em; } .gite-add-exact-size-form .gite-form-row input[type="number"] { width: 60px; text-align: center; -moz-appearance: textfield; } .gite-add-exact-size-form input[type="number"]::-webkit-outer-spin-button, .gite-add-exact-size-form input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .gite-add-exact-size-form button[type="submit"] { width:100%; padding: 0 10px; font-size: 0.9em; height: 28px; background-color: var(--gite-datepicker-button-bg); color: var(--gite-datepicker-button-text); border: 1px solid var(--gite-datepicker-button-bg); margin-top: 8px; } html.gite-detected-dark-theme .gite-add-exact-size-form input { color-scheme: dark; } .gite-panel-category-footer { margin-top:10px; padding-top:8px; border-top: 1px dashed var(--gite-separator-color); text-align: right;} .gite-reset-category-btn { font-size: 0.85em !important; padding: 5px 10px !important; } `; const finalCss = `${cssBaseVariables}\n${cssThemeOverrides}\n${cssComponentStyles}\n${cssSettingsPanelStyles}`; if (!styleElement) { styleElement = document.createElement('style'); styleElement.id = styleId; styleElement.type = 'text/css'; if (document.head) document.head.appendChild(styleElement); else { error("Document head not found. Cannot inject GITE CSS."); return; } } if (styleElement.textContent !== finalCss || DEBUG_MODE) { styleElement.textContent = finalCss; if (DEBUG_MODE) log("Injected/Updated GITE CSS styles."); } } // Helper to get raw option definitions for a category, used by getFilterOptionsFromSettings. function _getRawDefinitionsForCategory(categoryKey) { if (categoryKey === 'color') { const prefixes = GITE_OPTION_DEFINITIONS.color.prefixOptions || []; const palettes = GITE_OPTION_DEFINITIONS.color.paletteColors || []; return [...prefixes, ...palettes]; } return GITE_OPTION_DEFINITIONS[categoryKey] || []; } // Retrieves and processes filter options based on current giteSettings. function getFilterOptionsFromSettings(categoryKey) { const managedOptionCategories = ['size', 'exactSize', 'time', 'region', 'site']; const categoryConfigFromSettings = giteSettings.filters ? giteSettings.filters[categoryKey] : undefined; if (categoryConfigFromSettings && categoryConfigFromSettings.enabled === false) { if (DEBUG_MODE) log(`Filter category '${categoryKey}' is disabled by top-level setting. No options will be shown.`); return []; } if (!categoryConfigFromSettings && !managedOptionCategories.includes(categoryKey) && DEBUG_MODE) { log(`Category '${categoryKey}' (non-managed) not found in giteSettings.filters. Assuming enabled and using GITE_OPTION_DEFINITIONS.`); } let optionsToProcess = []; if (managedOptionCategories.includes(categoryKey)) { const fallbackDefaultCatConf = GITE_DEFAULT_SETTINGS.filters[categoryKey]; if (!categoryConfigFromSettings) { if (DEBUG_MODE) warn(`Managed category '${categoryKey}' missing from giteSettings.filters. Using GITE_DEFAULT_SETTINGS for its structure.`); if (categoryKey === 'site') { const predefinedSiteOptions = GITE_OPTION_DEFINITIONS.site ? JSON.parse(JSON.stringify(GITE_OPTION_DEFINITIONS.site)) : []; const userDefinedSiteOptions = (fallbackDefaultCatConf && fallbackDefaultCatConf.userDefinedOptions) ? fallbackDefaultCatConf.userDefinedOptions : []; optionsToProcess = [...predefinedSiteOptions, ...userDefinedSiteOptions]; } else if (categoryKey === 'exactSize') { optionsToProcess = fallbackDefaultCatConf.predefinedOptions || []; optionsToProcess = [...optionsToProcess, ...( (fallbackDefaultCatConf && fallbackDefaultCatConf.userDefinedOptions) || [])]; } else { optionsToProcess = fallbackDefaultCatConf.options || []; } } else { if (categoryKey === 'exactSize') { const predefined = categoryConfigFromSettings.predefinedOptions || (fallbackDefaultCatConf? fallbackDefaultCatConf.predefinedOptions : []) || []; const userDefined = categoryConfigFromSettings.userDefinedOptions || []; optionsToProcess = [...predefined, ...userDefined]; } else if (categoryKey === 'site') { const predefinedSiteOptions = GITE_OPTION_DEFINITIONS.site ? JSON.parse(JSON.stringify(GITE_OPTION_DEFINITIONS.site)) : []; const anySiteOption = predefinedSiteOptions.find(opt => opt.id === "gite_site_any" && opt.defaultEnabled === true); const userDefinedSiteOptions = categoryConfigFromSettings.userDefinedOptions || []; optionsToProcess = []; if (anySiteOption) { optionsToProcess.push({ ...anySiteOption, isEnabled: anySiteOption.defaultEnabled !== undefined ? anySiteOption.defaultEnabled : true }); } optionsToProcess = [...optionsToProcess, ...userDefinedSiteOptions]; if(DEBUG_MODE) log(`[GITE DEBUG] Site optionsToProcess (merged with explicit Any site):`, JSON.parse(JSON.stringify(optionsToProcess))); } else { optionsToProcess = categoryConfigFromSettings.options || (fallbackDefaultCatConf ? fallbackDefaultCatConf.options : []) || []; } } } else { optionsToProcess = _getRawDefinitionsForCategory(categoryKey); } const processedOptions = []; let filterTitleKey; switch (categoryKey) { case 'exactSize': filterTitleKey = 'filter_title_exact_size'; break; case 'aspectRatio': filterTitleKey = 'filter_title_aspect_ratio'; break; case 'usageRights': filterTitleKey = 'filter_title_usage_rights'; break; case 'fileType': filterTitleKey = 'filter_title_file_type'; break; case 'site': filterTitleKey = 'filter_title_site_search'; break; default: filterTitleKey = `filter_title_${categoryKey.toLowerCase()}`; } const originalTitleEng = getLocalizedString(filterTitleKey, 'en', categoryKey); optionsToProcess.forEach(optDef => { let finalOptionIsEnabled; if (optDef.isCustom) finalOptionIsEnabled = optDef.isEnabled; else if (categoryKey === 'site' && optDef.id === 'gite_site_any') finalOptionIsEnabled = optDef.defaultEnabled !== undefined ? optDef.defaultEnabled : true; else if (managedOptionCategories.includes(categoryKey) && !optDef.isCustom && categoryConfigFromSettings) finalOptionIsEnabled = optDef.isEnabled; else if (optDef.type === 'separator') finalOptionIsEnabled = optDef.isEnabled !== undefined ? optDef.isEnabled : true; else finalOptionIsEnabled = optDef.defaultEnabled !== undefined ? optDef.defaultEnabled : true; if (finalOptionIsEnabled) { let displayText; let textSourceKey = optDef.textKey || optDef.nameKey; if ((categoryKey === 'exactSize' || categoryKey === 'site') && optDef.isCustom && optDef.label) displayText = optDef.label; else if (categoryConfigFromSettings && optDef.customText !== null && optDef.customText !== undefined) displayText = optDef.customText; else if (textSourceKey) displayText = getLocalizedString(textSourceKey, CURRENT_LANGUAGE, optDef.id); else if (optDef.type === 'separator') displayText = '---'; else { displayText = optDef.value || optDef.id || (optDef.type === 'palette' ? optDef.hex : `[${optDef.id || 'Option'}]`); if (DEBUG_MODE && optDef.type !== 'separator') warn(`Option id "${optDef.id || optDef.value || optDef.hex}" in category "${categoryKey}" is missing a textKey or custom label. Fallback: ${displayText}`); } processedOptions.push({ ...optDef, text: displayText, isEnabled: finalOptionIsEnabled, originalTitleEn: originalTitleEng, categoryKey: categoryKey, paramName: optDef.paramName || (categoryKey === 'site' || categoryKey === 'exactSize' ? 'q' : null) }); } }); if (DEBUG_MODE) { if (processedOptions.length > 0) log(`Processed ${processedOptions.length} displayable options for filter category '${categoryKey}'.`); else if (categoryConfigFromSettings && categoryConfigFromSettings.enabled === true && optionsToProcess.length > 0) { if (categoryKey === 'site' && optionsToProcess.some(opt => opt.id === 'gite_site_any')) log(`Filter category '${categoryKey}' is enabled. User-defined site options might all be disabled, or an issue prevented processing options other than 'Any site'.`); else log(`Filter category '${categoryKey}' is enabled, but all its defined sub-options appear to be disabled or were filtered out in settings. No options will be shown in its menu.`); } if (categoryKey === 'site') log(`[GITE DEBUG] Final processedOptions for site:`, JSON.parse(JSON.stringify(processedOptions))); } return processedOptions; } // --- Custom Exact Size Management (Interacts with giteSettings) --- // Note: With the change to remove add/delete of custom exact sizes from the filter menu, // addCustomExactSize, deleteCustomExactSize, and renderCustomExactSizesList are now primarily // used by the settings panel logic, not directly by the exactSize filter menu itself. // However, getCustomExactSizes might still be useful if other parts of the script need this info. // Retrieves user-defined exact sizes from giteSettings. function getCustomExactSizes() { if (giteSettings && giteSettings.filters && giteSettings.filters.exactSize && giteSettings.filters.exactSize.userDefinedOptions) { return giteSettings.filters.exactSize.userDefinedOptions.map(opt => ({ id: opt.id, text: opt.label || `${opt.width}x${opt.height}`, value: opt.value, width: opt.width, height: opt.height, isEnabled: opt.isEnabled, isCustom: true, type: 'imagesize' })); } return []; } // Saves an array of custom exact size objects back to giteSettings and GM storage. // Typically called from the settings panel logic. function saveCustomExactSizes(sizes) { // This function is primarily for the settings panel now if (!giteSettings || !giteSettings.filters || !giteSettings.filters.exactSize) { warn("giteSettings not properly initialized for saving exact sizes. Aborting saveCustomExactSizes."); return; } giteSettings.filters.exactSize.userDefinedOptions = sizes.map(s => ({ id: s.id || `user_exact_${s.width}x${s.height}_${Date.now()}`, width: parseInt(s.width, 10), height: parseInt(s.height, 10), label: s.label || s.text || `${s.width}x${s.height}`, value: s.value || `${s.width}x${s.height}`, type: "imagesize", isCustom: true, isEnabled: s.isEnabled !== undefined ? s.isEnabled : true, })); if (typeof GM_setValue === 'function') { try { GM_setValue(GITE_SETTINGS_GM_KEY, JSON.stringify(giteSettings)); if (DEBUG_MODE) log("Updated exact sizes in giteSettings via saveCustomExactSizes and persisted to GM storage."); } catch (e) { error("Error persisting giteSettings (during saveCustomExactSizes):", e); alert(getLocalizedString('alert_generic_error_saving')); } } else { warn("GM_setValue is not available. Custom exact sizes cannot be saved persistently."); } } // --- URL Construction Logic --- // Helper to update or remove a specific key-value pair within the 'tbs' URL parameter. function _updateSingleTbsParam(currentTbs, keyPrefix, newValue, isColorSpecificPalette = false) { let tbsParts = currentTbs ? currentTbs.split(',') : []; const fullPrefixToMatch = keyPrefix + ":"; tbsParts = tbsParts.filter(part => !part.startsWith(fullPrefixToMatch)); if (isColorSpecificPalette) { tbsParts = tbsParts.filter(part => part !== "ic:specific" && !part.startsWith("isc:")); if (newValue && newValue !== "") { tbsParts.push("ic:specific"); tbsParts.push(keyPrefix + ":" + newValue); } } else { if (newValue && newValue !== "") tbsParts.push(keyPrefix + ":" + newValue); } return tbsParts.filter(part => part && part.trim() !== "").join(','); } // Helper to remove all tbs parts starting with any of the given prefixes. function _clearTbsCategory(currentTbs, prefixesToClear) { if (!currentTbs) return ""; let tbsParts = currentTbs.split(','); prefixesToClear.forEach(prefix => { tbsParts = tbsParts.filter(part => !part.startsWith(prefix + ":")); }); if (prefixesToClear.some(p => p === 'ic' || p === 'isc')) tbsParts = tbsParts.filter(part => part !== "ic:specific"); return tbsParts.filter(part => part && part.trim() !== "").join(','); } // Constructs the 'tbs' part of the URL based on the selected option. function _buildTbsUrlPart(targetUrl, option) { let newTbsString = targetUrl.searchParams.get('tbs') || ""; const tbsValToProcess = option.tbsValue; const categoryKeyFromOption = option.categoryKey; if (tbsValToProcess === "" || tbsValToProcess === undefined || tbsValToProcess === null) { let prefixesToClear = []; if (categoryKeyFromOption === 'type') prefixesToClear = ["itp"]; else if (categoryKeyFromOption === 'time') prefixesToClear = ["qdr", "cdr"]; else if (categoryKeyFromOption === 'usageRights') prefixesToClear = ["sur"]; else if (categoryKeyFromOption === 'color') { prefixesToClear = ["ic", "isc"]; targetUrl.searchParams.delete('imgc'); } newTbsString = _clearTbsCategory(newTbsString, prefixesToClear); } else { if (categoryKeyFromOption === 'color') { targetUrl.searchParams.delete('imgc'); if (tbsValToProcess.startsWith("ic:specific,isc:")) { const colorName = tbsValToProcess.split(',isc:')[1]; newTbsString = _updateSingleTbsParam(newTbsString, "isc", colorName, true); } else { const [tbsKey, ...tbsValueParts] = tbsValToProcess.split(':'); newTbsString = _updateSingleTbsParam(newTbsString, tbsKey, tbsValueParts.join(':')); } } else if (categoryKeyFromOption === 'size' && tbsValToProcess && (tbsValToProcess.startsWith('isz:') || tbsValToProcess.startsWith('islt:') || tbsValToProcess.startsWith('isilu:'))) { const [tbsKey, ...tbsValueParts] = tbsValToProcess.split(':'); newTbsString = _updateSingleTbsParam(newTbsString, tbsKey, tbsValueParts.join(':')); targetUrl.searchParams.delete('imgsz'); let q = targetUrl.searchParams.get('q') || ""; q = q.replace(/imagesize:[\d]+x[\d]+/g, '').trim().replace(/\s\s+/g, ' '); if (q) targetUrl.searchParams.set('q', q); else targetUrl.searchParams.delete('q'); } else { if (typeof tbsValToProcess === 'string' && tbsValToProcess.includes(':')) { const [tbsKey, ...tbsValueParts] = tbsValToProcess.split(':'); let isValidTbsCat = (categoryKeyFromOption === 'type' && tbsKey === 'itp') || (categoryKeyFromOption === 'time' && (tbsKey === 'qdr' || tbsKey === 'cdr')) || (categoryKeyFromOption === 'usageRights' && tbsKey === 'sur'); if (isValidTbsCat) newTbsString = _updateSingleTbsParam(newTbsString, tbsKey, tbsValueParts.join(':')); else if (DEBUG_MODE) warn(`_buildTbsUrlPart: tbsVal '${tbsValToProcess}' for category '${categoryKeyFromOption}' has an unexpected tbsKey '${tbsKey}'.`); } else if (DEBUG_MODE && tbsValToProcess) warn(`_buildTbsUrlPart: tbsVal '${tbsValToProcess}' for category '${categoryKeyFromOption}' is not a processable standard tbs string.`); } } newTbsString = newTbsString.replace(/^,|,$/g, '').replace(/,,+/g, ','); if (newTbsString) targetUrl.searchParams.set('tbs', newTbsString); else targetUrl.searchParams.delete('tbs'); } // Modifies the 'q' (query) URL parameter for 'exactSize' and 'site' filters. function _buildQueryUrlPart(targetUrl, paramValueFromOption, option) { let currentQ = targetUrl.searchParams.get('q') || ""; let baseQ = currentQ; if (option.categoryKey === 'exactSize' || (option.type === 'imagesize' || option.type === 'imagesize_clear')) { baseQ = baseQ.replace(/imagesize:[\d]+x[\d]+/g, '').trim(); if (paramValueFromOption && paramValueFromOption !== "") targetUrl.searchParams.set('q', `${baseQ} imagesize:${paramValueFromOption}`.trim().replace(/\s\s+/g, ' ')); else { if (baseQ) targetUrl.searchParams.set('q', baseQ.trim()); else targetUrl.searchParams.delete('q'); } targetUrl.searchParams.delete('imgsz'); let tbs = targetUrl.searchParams.get('tbs') || ""; tbs = _clearTbsCategory(tbs, ['isz', 'islt', 'isilu']); if (tbs) targetUrl.searchParams.set('tbs', tbs); else targetUrl.searchParams.delete('tbs'); } else if (option.categoryKey === 'site') { baseQ = baseQ.replace(/site:[^\s]+/g, '').trim(); if (paramValueFromOption && paramValueFromOption !== "") { const cleanDomain = String(paramValueFromOption).replace(/\s/g, ''); if (cleanDomain) targetUrl.searchParams.set('q', `${baseQ} site:${cleanDomain}`.trim().replace(/\s\s+/g, ' ')); else { if (baseQ) targetUrl.searchParams.set('q', baseQ.trim()); else targetUrl.searchParams.delete('q');} } else { if (baseQ) targetUrl.searchParams.set('q', baseQ.trim()); else targetUrl.searchParams.delete('q');} } } // Handles simple URL parameters like 'imgsz', 'imgar', 'as_filetype', 'cr'. function _buildSimpleParamUrlPart(targetUrl, paramName, paramValueFromOption) { if (paramValueFromOption === "" || paramValueFromOption === undefined || paramValueFromOption === null) targetUrl.searchParams.delete(paramName); else { targetUrl.searchParams.set(paramName, String(paramValueFromOption)); if (paramName === 'imgsz') { let q = targetUrl.searchParams.get('q') || ""; q = q.replace(/imagesize:[\d]+x[\d]+/g, '').trim().replace(/\s\s+/g, ' '); if (q) targetUrl.searchParams.set('q', q); else targetUrl.searchParams.delete('q'); let tbs = targetUrl.searchParams.get('tbs') || ""; tbs = _clearTbsCategory(tbs, ['isz', 'islt', 'isilu']); if (tbs) targetUrl.searchParams.set('tbs', tbs); else targetUrl.searchParams.delete('tbs'); } } } // Main function to construct the new URL when a filter option is clicked. function buildNewUrl(paramNameFromMenuDefinition, paramValueFromOption, optionContext, clearAllGiteFilters = false) { const targetUrl = new URL(window.location.href); let currentQ = targetUrl.searchParams.get('q') || ""; if (clearAllGiteFilters) { targetUrl.searchParams.delete('imgsz'); targetUrl.searchParams.delete('imgar'); targetUrl.searchParams.delete('as_filetype'); targetUrl.searchParams.delete('imgc'); targetUrl.searchParams.delete('cr'); currentQ = currentQ.replace(/imagesize:[\d]+x[\d]+/g, '').replace(/site:[\w.-]+/g, '').trim().replace(/\s\s+/g, ' '); if (currentQ) targetUrl.searchParams.set('q', currentQ); else targetUrl.searchParams.delete('q'); let tbs = targetUrl.searchParams.get('tbs') || ""; const tbsKeysToClear = ['itp', 'ic', 'isc', 'sur', 'qdr', 'cdr', 'isz', 'islt', 'isilu']; tbs = _clearTbsCategory(tbs, tbsKeysToClear); if (tbs) targetUrl.searchParams.set('tbs', tbs); else targetUrl.searchParams.delete('tbs'); return targetUrl.toString(); } const currentOption = (optionContext && typeof optionContext === 'object') ? optionContext : { categoryKey: null, type: null, value: paramValueFromOption, tbsValue: null, paramNameOverride: null, originalTitleEn: '', paramName: null }; let actualParamName = paramNameFromMenuDefinition; if (currentOption.paramName) actualParamName = currentOption.paramName; else if (currentOption.paramNameOverride) actualParamName = currentOption.paramNameOverride; else if (currentOption.categoryKey === 'exactSize' || currentOption.categoryKey === 'site') actualParamName = 'q'; else if (currentOption.tbsValue !== undefined && currentOption.tbsValue !== null) actualParamName = 'tbs'; if (actualParamName === 'q') _buildQueryUrlPart(targetUrl, paramValueFromOption, currentOption); else if (actualParamName === 'tbs') _buildTbsUrlPart(targetUrl, currentOption); else { if (actualParamName) _buildSimpleParamUrlPart(targetUrl, actualParamName, paramValueFromOption); else if (DEBUG_MODE) warn(`buildNewUrl: Could not determine actualParamName. Initial: '${paramNameFromMenuDefinition}', Option:`, currentOption); } return targetUrl.toString(); } // --- UI Element Creation --- // Creates a GITE custom g-menu-item element. function createMenuItem(optionWithContext, paramNameForLinkGeneration, isCustomSizer = false /* Removed isCustomExactSizeItem, deleteCallback */ ) { const menuItem = document.createElement('g-menu-item'); menuItem.className = 'EpPYLd GZnQqe gite-custom-menu-item'; if (optionWithContext.type === "separator") { menuItem.setAttribute('role', 'separator'); const sepDiv = document.createElement('div'); sepDiv.style.cssText = 'margin-top: 5px; margin-bottom: 5px; border-top: 1px solid var(--gite-separator-color);'; menuItem.appendChild(sepDiv); return menuItem; } if (optionWithContext.type === 'palette') { menuItem.classList.add('gite-color-swatch-item'); menuItem.setAttribute('data-hex-color', optionWithContext.hex); if (optionWithContext.complement) menuItem.setAttribute('data-complement-color', optionWithContext.complement); } menuItem.setAttribute('jsname', 'NNJLud'); let valForAttr = (optionWithContext.categoryKey === 'color' && optionWithContext.type === 'palette') ? optionWithContext.tbsValue : (optionWithContext.tbsValue !== undefined && optionWithContext.tbsValue !== null) ? optionWithContext.tbsValue : optionWithContext.value; menuItem.setAttribute(FILTER_PARAM_ATTR, paramNameForLinkGeneration); menuItem.setAttribute(FILTER_VALUE_ATTR, String(valForAttr)); if (paramNameForLinkGeneration === 'tbs' && optionWithContext.tbsValue && typeof optionWithContext.tbsValue === 'string' && optionWithContext.tbsValue.includes(':')) { menuItem.setAttribute(TBS_PREFIX_ATTR, optionWithContext.tbsValue.split(':')[0] + ':'); } if (isCustomSizer) { menuItem.setAttribute(CUSTOM_SIZER_ATTR, "true"); menuItem.setAttribute(SIZER_VALUE_ATTR, String(optionWithContext.value).toUpperCase()); } else { menuItem.setAttribute(CUSTOM_FILTER_ITEM_ATTR, "true"); } // All other GITE items are marked this way now const innerDiv = document.createElement('div'); innerDiv.className = 'YpcDnf OSrXXb HG1dvd'; innerDiv.setAttribute('jsname', 'ibnC6b'); innerDiv.setAttribute('role', 'menuitemradio'); menuItem.appendChild(innerDiv); const anchor = document.createElement('a'); anchor.setAttribute('tabindex', '0'); const textSpan = document.createElement('span'); if (optionWithContext.textKey) textSpan.setAttribute(GITE_LANG_KEY_ATTR, optionWithContext.textKey); else if (optionWithContext.nameKey) textSpan.setAttribute(GITE_LANG_KEY_ATTR, optionWithContext.nameKey); if (optionWithContext.type === 'palette') { anchor.classList.add('gite-color-swatch'); anchor.style.backgroundColor = optionWithContext.hex; anchor.title = optionWithContext.text; anchor.setAttribute('aria-label', optionWithContext.text); if (optionWithContext.nameKey) { anchor.setAttribute(GITE_LANG_KEY_ATTR, optionWithContext.nameKey); anchor.setAttribute('data-gite-lang-target-attr', 'title');} } else { textSpan.textContent = optionWithContext.text; anchor.appendChild(textSpan); } if (optionWithContext.type === "custom_date_trigger") { anchor.href = "javascript:void(0);"; anchor.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); closeDatePicker(); datePickerOverlayElement = document.createElement('div'); datePickerOverlayElement.className = 'gite-datepicker-overlay'; datePickerOverlayElement.addEventListener('click', closeDatePicker); document.body.appendChild(datePickerOverlayElement); const pickerContainer = document.createElement('div'); pickerContainer.className = 'gite-custom-date-picker'; pickerContainer.innerHTML = `<div><label for="gite-start-date" ${GITE_LANG_KEY_ATTR}="datepicker_label_from">${getLocalizedString('datepicker_label_from')}</label><input type="date" id="gite-start-date"></div><div><label for="gite-end-date" ${GITE_LANG_KEY_ATTR}="datepicker_label_to">${getLocalizedString('datepicker_label_to')}</label><input type="date" id="gite-end-date"></div><div><button id="gite-date-apply" ${GITE_LANG_KEY_ATTR}="btn_apply">${getLocalizedString('btn_apply')}</button><button id="gite-date-cancel" class="gite-date-cancel" ${GITE_LANG_KEY_ATTR}="btn_cancel">${getLocalizedString('btn_cancel')}</button></div>`; document.body.appendChild(pickerContainer); activeDatePicker = pickerContainer; const parentMenu = menuItem.closest('g-menu'); const parentPopup = parentMenu ? parentMenu.closest('g-popup[jsname="V68bde"]') : null; if (parentPopup) { const trigger = parentPopup.querySelector('div[jsname="oYxtQd"][role="button"]'); if(trigger && trigger.getAttribute('aria-expanded') === 'true') trigger.click(); } const applyDateBtn = pickerContainer.querySelector('#gite-date-apply'); if(applyDateBtn) applyDateBtn.addEventListener('click', () => { const startInput = pickerContainer.querySelector('#gite-start-date'); const endInput = pickerContainer.querySelector('#gite-end-date'); if(!startInput || !endInput) { warn("Date picker inputs not found."); return; } const start = startInput.value; const end = endInput.value; if (!start || !end) { alert(getLocalizedString('alert_datepicker_select_dates')); return; } if (new Date(end) < new Date(start)) { alert(getLocalizedString('alert_datepicker_end_before_start')); return; } const formatDate = (d) => { if (!d) return ""; const o = new Date(d + 'T00:00:00'); if (isNaN(o.getTime())) return ""; return `${o.getMonth()+1}/${o.getDate()}/${o.getFullYear()}`; }; const fS = formatDate(start); const fE = formatDate(end); if (!fS || !fE) { alert(getLocalizedString('alert_datepicker_invalid_date')); return; } const tbsVal = `cdr:1,cd_min:${fS},cd_max:${fE}`; const dateOpt = { ...optionWithContext, tbsValue: tbsVal, categoryKey: 'time' }; const url = buildNewUrl('tbs', tbsVal, dateOpt); closeDatePicker(); window.location.href = url; }); const cancelDateBtn = pickerContainer.querySelector('#gite-date-cancel'); if(cancelDateBtn) cancelDateBtn.addEventListener('click', closeDatePicker); }); } else { let effectiveVal = (optionWithContext.tbsValue !== undefined) ? optionWithContext.tbsValue : optionWithContext.value; anchor.href = buildNewUrl(paramNameForLinkGeneration, effectiveVal, optionWithContext); } innerDiv.appendChild(anchor); // Removed the logic for adding delete button for exactSize items from menu return menuItem; } // --- Filter Menu Population Functions --- // Populates the "Size" filter menu with GITE's custom options. function addSizesToMenu(menuElement) { if (DEBUG_MODE) log("Populating GITE 'Size' filter options..."); const existingFlexContainer = menuElement.querySelector('div[style*="display: flex"]'); if (existingFlexContainer) existingFlexContainer.remove(); const originalMenuItems = menuElement.querySelectorAll(`${MENU_ITEM_SELECTOR}:not([${CUSTOM_SIZER_ATTR}])`); originalMenuItems.forEach(item => item.remove()); const containerDiv = document.createElement('div'); containerDiv.style.display = 'flex'; containerDiv.style.padding = '6px 0'; const column1 = document.createElement('div'); column1.style.paddingRight = '16px'; column1.style.minWidth = '150px'; column1.classList.add('gite-column-1'); const column2 = document.createElement('div'); column2.style.minWidth = '150px'; column2.classList.add('gite-column-2'); containerDiv.appendChild(column1); containerDiv.appendChild(column2); menuElement.appendChild(containerDiv); const sizeOptionsFromSettings = getFilterOptionsFromSettings('size'); if (sizeOptionsFromSettings.length === 0 && DEBUG_MODE) { warn("No 'Size' options to display after processing settings. Check GITE_OPTION_DEFINITIONS.size and current settings."); } sizeOptionsFromSettings.forEach(opt => { const targetColumn = (opt.column === 2) ? column2 : column1; // Use 'column' from optDef for 2-column layout const menuItem = createMenuItem(opt, 'imgsz', true); targetColumn.appendChild(menuItem); }); if (DEBUG_MODE) log(`Finished 'Size' options. ${sizeOptionsFromSettings.length} GITE options added.`); } // --- Settings Panel DOM & Logic --- // Basic cleaning for domain input for site search. function cleanDomainInput(domainStr) { if (!domainStr || typeof domainStr !== 'string') return ""; let cleaned = domainStr.trim().toLowerCase(); cleaned = cleaned.replace(/^https?:\/\//, '').replace(/^www\./, ''); const slashIndex = cleaned.indexOf('/'); if (slashIndex !== -1) cleaned = cleaned.substring(0, slashIndex); if (cleaned === "" || cleaned.includes(" ") || (cleaned.startsWith('.') && cleaned.length === 1) ) { if(DEBUG_MODE) warn(`Cleaned domain input "${domainStr}" resulted in invalid/empty: "${cleaned}"`); return ""; } return cleaned; } // Ensures the settings panel DOM is created and appended to the body. function ensureSettingsPanelDOM() { if (giteSettingsPanelElement && document.body.contains(giteSettingsPanelElement)) return; const panelHTML = ` <div id="gite-settings-panel" class="gite-modal"> <div class="gite-modal-overlay"></div> <div class="gite-modal-content"> <div class="gite-modal-header"> <h2 id="gite-settings-title" ${GITE_LANG_KEY_ATTR}="settings_panel_title">${getLocalizedString('settings_panel_title')}</h2> <button id="gite-settings-close-btn" class="gite-modal-close-btn" ${GITE_LANG_KEY_ATTR}="btn_close" data-gite-lang-target-attr="title" title="${getLocalizedString('btn_close')}">×</button> </div> <div class="gite-modal-body"> <div class="gite-tabs-navigation"> <ul role="tablist"> <li role="presentation"><button role="tab" id="gite-tab-general" aria-controls="gite-panel-general" aria-selected="true" ${GITE_LANG_KEY_ATTR}="settings_tab_general">${getLocalizedString('settings_tab_general')}</button></li> <li role="presentation"><button role="tab" id="gite-tab-exactsize" aria-controls="gite-panel-exactsize" ${GITE_LANG_KEY_ATTR}="settings_tab_exact_size">${getLocalizedString('settings_tab_exact_size')}</button></li> <li role="presentation"><button role="tab" id="gite-tab-size" aria-controls="gite-panel-size" ${GITE_LANG_KEY_ATTR}="settings_tab_size">${getLocalizedString('settings_tab_size')}</button></li> <li role="presentation"><button role="tab" id="gite-tab-time" aria-controls="gite-panel-time" ${GITE_LANG_KEY_ATTR}="settings_tab_time">${getLocalizedString('settings_tab_time')}</button></li> <li role="presentation"><button role="tab" id="gite-tab-region" aria-controls="gite-panel-region" ${GITE_LANG_KEY_ATTR}="settings_tab_region">${getLocalizedString('settings_tab_region')}</button></li> <li role="presentation"><button role="tab" id="gite-tab-sitesearch" aria-controls="gite-panel-sitesearch" ${GITE_LANG_KEY_ATTR}="settings_tab_site_search">${getLocalizedString('settings_tab_site_search')}</button></li> </ul> </div> <div class="gite-tabs-content"> <div role="tabpanel" id="gite-panel-general" aria-labelledby="gite-tab-general" class="gite-tab-panel gite-active-panel"> <div class="gite-setting-group"><label for="gite-setting-language" ${GITE_LANG_KEY_ATTR}="settings_label_language">${getLocalizedString('settings_label_language')}</label><select id="gite-setting-language"><option value="auto" ${GITE_LANG_KEY_ATTR}="settings_lang_auto">${getLocalizedString('settings_lang_auto')}</option><option value="en" ${GITE_LANG_KEY_ATTR}="settings_lang_en">${getLocalizedString('settings_lang_en')}</option><option value="zh-TW" ${GITE_LANG_KEY_ATTR}="settings_lang_zh_TW">${getLocalizedString('settings_lang_zh_TW')}</option><option value="ja" ${GITE_LANG_KEY_ATTR}="settings_lang_ja">${getLocalizedString('settings_lang_ja')}</option></select></div> <div class="gite-setting-group"><label for="gite-setting-autoexpand" ${GITE_LANG_KEY_ATTR}="settings_label_autoexpand">${getLocalizedString('settings_label_autoexpand')}</label><input type="checkbox" id="gite-setting-autoexpand"></div> <div class="gite-setting-group"><label for="gite-setting-showtoolbarbutton" ${GITE_LANG_KEY_ATTR}="settings_label_showtoolbarbutton">${getLocalizedString('settings_label_showtoolbarbutton')}</label><input type="checkbox" id="gite-setting-showtoolbarbutton"></div> </div> <div role="tabpanel" id="gite-panel-exactsize" aria-labelledby="gite-tab-exactsize" class="gite-tab-panel"> <div class="gite-setting-group gite-exactsize-panel-section"><label for="gite-filter-exactsize-cat-enabled"><span ${GITE_LANG_KEY_ATTR}="settings_enable_filter_category_prefix">${getLocalizedString('settings_enable_filter_category_prefix')}</span><span ${GITE_LANG_KEY_ATTR}="filter_title_exact_size">${getLocalizedString('filter_title_exact_size')}</span><span ${GITE_LANG_KEY_ATTR}="settings_enable_filter_category_suffix">${getLocalizedString('settings_enable_filter_category_suffix')}</span></label><input type="checkbox" id="gite-filter-exactsize-cat-enabled"></div> <div class="gite-exactsize-panel-section"><h3 ${GITE_LANG_KEY_ATTR}="settings_section_predefined_options">${getLocalizedString('settings_section_predefined_options')}</h3><ul class="gite-exactsize-options-list" id="gite-exactsize-predefined-list"></ul></div> <div class="gite-exactsize-panel-section"><h3 ${GITE_LANG_KEY_ATTR}="settings_section_your_saved_sizes">${getLocalizedString('settings_section_your_saved_sizes')}</h3><ul class="gite-exactsize-options-list" id="gite-exactsize-userdefined-list"></ul></div> <div class="gite-exactsize-panel-section"><h3 ${GITE_LANG_KEY_ATTR}="settings_label_add_new_exact_size">${getLocalizedString('settings_label_add_new_exact_size')}</h3><form class="gite-add-exact-size-form" id="gite-add-exact-size-form"><div class="gite-form-row"><div><label for="gite-new-exact-width">W:</label><input type="number" id="gite-new-exact-width" placeholder="${getLocalizedString('exact_size_placeholder_width')}" min="1" required ${GITE_LANG_KEY_ATTR}="exact_size_placeholder_width" data-gite-lang-target-attr="placeholder"></div><div><label for="gite-new-exact-height">H:</label><input type="number" id="gite-new-exact-height" placeholder="${getLocalizedString('exact_size_placeholder_height')}" min="1" required ${GITE_LANG_KEY_ATTR}="exact_size_placeholder_height" data-gite-lang-target-attr="placeholder"></div></div><div><label for="gite-new-exact-label">${getLocalizedString('settings_placeholder_label_optional')}:</label><input type="text" id="gite-new-exact-label" placeholder="${getLocalizedString('settings_placeholder_label_optional')}" ${GITE_LANG_KEY_ATTR}="settings_placeholder_label_optional" data-gite-lang-target-attr="placeholder"></div><button type="submit" class="gite-primary-btn" ${GITE_LANG_KEY_ATTR}="btn_add_new_exact_size">${getLocalizedString('btn_add_new_exact_size')}</button></form></div> </div> <div role="tabpanel" id="gite-panel-size" aria-labelledby="gite-tab-size" class="gite-tab-panel"><h3 style="margin-top:0;"><span ${GITE_LANG_KEY_ATTR}="settings_options_for_category_prefix">${getLocalizedString('settings_options_for_category_prefix')}</span><span ${GITE_LANG_KEY_ATTR}="filter_title_size">${getLocalizedString('filter_title_size')}</span><span ${GITE_LANG_KEY_ATTR}="settings_options_for_category_suffix">${getLocalizedString('settings_options_for_category_suffix')}</span></h3><p style="font-size:0.9em; opacity:0.8; margin-top:-5px; margin-bottom:10px;" ${GITE_LANG_KEY_ATTR}="settings_size_options_note">${getLocalizedString('settings_size_options_note')}</p><ul class="gite-options-list" id="gite-size-options-list"></ul><div class="gite-panel-category-footer"><button id="gite-size-reset-cat-btn" class="gite-reset-category-btn"><span ${GITE_LANG_KEY_ATTR}="btn_reset_options_for_category_prefix">${getLocalizedString('btn_reset_options_for_category_prefix')}</span><span ${GITE_LANG_KEY_ATTR}="filter_title_size">${getLocalizedString('filter_title_size')}</span><span ${GITE_LANG_KEY_ATTR}="btn_reset_options_for_category_suffix">${getLocalizedString('btn_reset_options_for_category_suffix')}</span></button></div></div> <div role="tabpanel" id="gite-panel-time" aria-labelledby="gite-tab-time" class="gite-tab-panel"><h3 style="margin-top:0;"><span ${GITE_LANG_KEY_ATTR}="settings_options_for_category_prefix">${getLocalizedString('settings_options_for_category_prefix')}</span><span ${GITE_LANG_KEY_ATTR}="filter_title_time">${getLocalizedString('filter_title_time')}</span><span ${GITE_LANG_KEY_ATTR}="settings_options_for_category_suffix">${getLocalizedString('settings_options_for_category_suffix')}</span></h3><ul class="gite-options-list" id="gite-time-options-list"></ul><div class="gite-panel-category-footer"><button id="gite-time-reset-cat-btn" class="gite-reset-category-btn"><span ${GITE_LANG_KEY_ATTR}="btn_reset_options_for_category_prefix">${getLocalizedString('btn_reset_options_for_category_prefix')}</span><span ${GITE_LANG_KEY_ATTR}="filter_title_time">${getLocalizedString('filter_title_time')}</span><span ${GITE_LANG_KEY_ATTR}="btn_reset_options_for_category_suffix">${getLocalizedString('btn_reset_options_for_category_suffix')}</span></button></div></div> <div role="tabpanel" id="gite-panel-region" aria-labelledby="gite-tab-region" class="gite-tab-panel"><div class="gite-setting-group" style="margin-bottom: 5px;"> <label for="gite-filter-region-cat-enabled"><span ${GITE_LANG_KEY_ATTR}="settings_enable_filter_category_prefix">${getLocalizedString('settings_enable_filter_category_prefix')}</span><span ${GITE_LANG_KEY_ATTR}="filter_title_region">${getLocalizedString('filter_title_region')}</span><span ${GITE_LANG_KEY_ATTR}="settings_enable_filter_category_suffix">${getLocalizedString('settings_enable_filter_category_suffix')}</span></label><input type="checkbox" id="gite-filter-region-cat-enabled"></div><h3 style="margin-top:10px;"> <span ${GITE_LANG_KEY_ATTR}="settings_options_for_category_prefix">${getLocalizedString('settings_options_for_category_prefix')}</span><span ${GITE_LANG_KEY_ATTR}="filter_title_region">${getLocalizedString('filter_title_region')}</span><span ${GITE_LANG_KEY_ATTR}="settings_options_for_category_suffix">${getLocalizedString('settings_options_for_category_suffix')}</span></h3><p style="font-size:0.9em; opacity:0.8; margin-top:-5px; margin-bottom:10px;" ${GITE_LANG_KEY_ATTR}="settings_region_options_note">${getLocalizedString('settings_region_options_note')}</p><ul class="gite-options-list" id="gite-region-options-list" style="max-height: 200px;"></ul> <div class="gite-panel-category-footer"><button id="gite-region-reset-cat-btn" class="gite-reset-category-btn"><span ${GITE_LANG_KEY_ATTR}="btn_reset_options_for_category_prefix">${getLocalizedString('btn_reset_options_for_category_prefix')}</span><span ${GITE_LANG_KEY_ATTR}="filter_title_region">${getLocalizedString('filter_title_region')}</span><span ${GITE_LANG_KEY_ATTR}="btn_reset_options_for_category_suffix">${getLocalizedString('btn_reset_options_for_category_suffix')}</span></button></div></div> <div role="tabpanel" id="gite-panel-sitesearch" aria-labelledby="gite-tab-sitesearch" class="gite-tab-panel"><div class="gite-setting-group" style="margin-bottom: 10px;"><label for="gite-filter-sitesearch-cat-enabled"><span ${GITE_LANG_KEY_ATTR}="settings_label_enable_site_search_filter">${getLocalizedString('settings_label_enable_site_search_filter')}</span></label><input type="checkbox" id="gite-filter-sitesearch-cat-enabled"></div><div class="gite-options-panel-section"><h3 ${GITE_LANG_KEY_ATTR}="settings_section_your_saved_sites">${getLocalizedString('settings_section_your_saved_sites')}</h3><ul class="gite-exactsize-options-list" id="gite-sitesearch-userdefined-list" style="max-height: 150px;"></ul></div><div class="gite-options-panel-section"><h3 ${GITE_LANG_KEY_ATTR}="settings_label_add_new_site">${getLocalizedString('settings_label_add_new_site')}</h3><form class="gite-add-exact-size-form" id="gite-add-new-site-form"><div><label for="gite-new-site-label" ${GITE_LANG_KEY_ATTR}="settings_placeholder_site_label" style="opacity:1; margin-bottom: 2px;">${getLocalizedString('settings_placeholder_site_label')}:</label><input type="text" id="gite-new-site-label" placeholder="${getLocalizedString('settings_placeholder_site_label')}" required ${GITE_LANG_KEY_ATTR}="settings_placeholder_site_label" data-gite-lang-target-attr="placeholder"></div><div style="margin-top: 8px;"><label for="gite-new-site-domain" ${GITE_LANG_KEY_ATTR}="settings_placeholder_site_domain" style="opacity:1; margin-bottom: 2px;">${getLocalizedString('settings_placeholder_site_domain')}:</label><input type="text" id="gite-new-site-domain" placeholder="${getLocalizedString('settings_placeholder_site_domain')}" required ${GITE_LANG_KEY_ATTR}="settings_placeholder_site_domain" data-gite-lang-target-attr="placeholder"></div><button type="submit" class="gite-primary-btn" ${GITE_LANG_KEY_ATTR}="btn_add_new_site" style="margin-top: 10px;">${getLocalizedString('btn_add_new_site')}</button></form></div></div> </div> </div> <div class="gite-modal-footer"> <button id="gite-settings-reset-all-btn" ${GITE_LANG_KEY_ATTR}="btn_reset_all_settings">${getLocalizedString('btn_reset_all_settings')}</button> <div><button id="gite-settings-cancel-btn" ${GITE_LANG_KEY_ATTR}="btn_cancel">${getLocalizedString('btn_cancel')}</button><button id="gite-settings-apply-btn" ${GITE_LANG_KEY_ATTR}="btn_apply">${getLocalizedString('btn_apply')}</button><button id="gite-settings-save-btn" class="gite-primary-btn" ${GITE_LANG_KEY_ATTR}="btn_save_and_close">${getLocalizedString('btn_save_and_close')}</button></div> </div> </div> </div>`; const tempDiv = document.createElement('div'); tempDiv.innerHTML = panelHTML.trim(); giteSettingsPanelElement = tempDiv.firstChild; if (document.body) { document.body.appendChild(giteSettingsPanelElement); setupPanelEventListeners(); } else error("document.body not found. Cannot append GITE settings panel."); } // Opens the GITE settings panel. function openSettingsPanel() { ensureSettingsPanelDOM(); if (!giteSettingsPanelElement) { error("Settings panel element could not be ensured/created. Aborting openSettingsPanel."); return; } currentGiteSettingsForPanel = JSON.parse(JSON.stringify(giteSettings)); updateAllLocalizableElements(CURRENT_LANGUAGE, giteSettingsPanelElement); loadGeneralSettingsToPanel(); loadExactSizeSettingsToPanel(); loadSizeOptionsSettingsList(); loadTimeOptionsSettingsList(); loadRegionSettingsToPanel(); loadRegionOptionsSettingsList(); loadSiteSearchSettingsToPanel(); const generalTabButton = giteSettingsPanelElement.querySelector('#gite-tab-general'); const generalPanel = giteSettingsPanelElement.querySelector('#gite-panel-general'); giteSettingsPanelElement.querySelectorAll('.gite-tabs-navigation button[role="tab"]').forEach(tb => tb.setAttribute('aria-selected', 'false')); giteSettingsPanelElement.querySelectorAll('.gite-tabs-content .gite-tab-panel').forEach(tp => tp.classList.remove('gite-active-panel')); if(generalTabButton) generalTabButton.setAttribute('aria-selected', 'true'); if(generalPanel) generalPanel.classList.add('gite-active-panel'); giteSettingsPanelElement.style.display = 'flex'; isSettingsPanelOpen = true; if (generalTabButton) generalTabButton.focus(); } // Closes the GITE settings panel. function closeSettingsPanel(forceClose = false) { if (!isSettingsPanelOpen && !forceClose) return; if (giteSettingsPanelElement) giteSettingsPanelElement.style.display = 'none'; isSettingsPanelOpen = false; } // Handles tab switching within the settings panel. function switchTabInPanel(event) { const newTabButton = event.target.closest('button[role="tab"]'); if (!newTabButton || newTabButton.disabled || newTabButton.getAttribute('aria-selected') === 'true') return; const nav = newTabButton.closest('.gite-tabs-navigation'); const contentContainer = giteSettingsPanelElement.querySelector('.gite-tabs-content'); if (!nav || !contentContainer) { warn("Tab navigation or content container not found in settings panel during tab switch."); return; } const oldActiveTab = nav.querySelector('[role="tab"][aria-selected="true"]'); if (oldActiveTab) { oldActiveTab.setAttribute('aria-selected', 'false'); const oldActivePanelId = oldActiveTab.getAttribute('aria-controls'); if (oldActivePanelId) { const oldActivePanel = contentContainer.querySelector(`#${oldActivePanelId}`); if (oldActivePanel) oldActivePanel.classList.remove('gite-active-panel');} } newTabButton.setAttribute('aria-selected', 'true'); const newPanelId = newTabButton.getAttribute('aria-controls'); const newPanel = contentContainer.querySelector(`#${newPanelId}`); if (newPanel) { newPanel.classList.add('gite-active-panel'); if (newPanelId === 'gite-panel-exactsize') loadExactSizeSettingsToPanel(); else if (newPanelId === 'gite-panel-size') loadSizeOptionsSettingsList(); else if (newPanelId === 'gite-panel-time') loadTimeOptionsSettingsList(); else if (newPanelId === 'gite-panel-region') { loadRegionSettingsToPanel(); loadRegionOptionsSettingsList(); } else if (newPanelId === 'gite-panel-sitesearch') { loadSiteSearchSettingsToPanel(); } } else warn(`Tab panel with ID "${newPanelId}" not found during tab switch.`); newTabButton.focus(); } // --- Functions to load data into specific settings panel tabs --- function loadGeneralSettingsToPanel() { if (!giteSettingsPanelElement || !currentGiteSettingsForPanel.general) { warn("Cannot load general settings to panel: Prerequisites not met."); return; } const langSelect = giteSettingsPanelElement.querySelector('#gite-setting-language'); if(langSelect) langSelect.value = getSettingFromPanel('general.selectedLanguage', 'auto'); else warn("Language select element not found in general settings panel."); const autoExpandCheck = giteSettingsPanelElement.querySelector('#gite-setting-autoexpand'); if(autoExpandCheck) autoExpandCheck.checked = getSettingFromPanel('general.autoExpandTools', true); else warn("Auto-expand checkbox not found in general settings panel."); const showToolbarBtnCheck = giteSettingsPanelElement.querySelector('#gite-setting-showtoolbarbutton'); if(showToolbarBtnCheck) showToolbarBtnCheck.checked = getSettingFromPanel('general.showSettingsButtonOnToolbar', true); else warn("Show toolbar button checkbox not found in general settings panel."); } function loadRegionSettingsToPanel() { // Loads the "Enable Region Filter" checkbox state if (!giteSettingsPanelElement || !currentGiteSettingsForPanel.filters || !currentGiteSettingsForPanel.filters.region) { warn("Cannot load region settings to panel: Prerequisites not met."); return; } const catEnabledCheckbox = giteSettingsPanelElement.querySelector('#gite-filter-region-cat-enabled'); if (catEnabledCheckbox) catEnabledCheckbox.checked = getSettingFromPanel('filters.region.enabled', true); else warn("Region category enable checkbox not found in settings panel."); } function loadRegionOptionsSettingsList() { // Renders the list of region options in settings renderSimpleOptionsListForCategory('region', 'gite-region-options-list', true); } function loadExactSizeSettingsToPanel() { if (!giteSettingsPanelElement || !currentGiteSettingsForPanel.filters || !currentGiteSettingsForPanel.filters.exactSize) { warn("Cannot load exact size settings to panel: Prerequisites not met."); return; } const esConfigInPanel = getSettingFromPanel('filters.exactSize', GITE_DEFAULT_SETTINGS.filters.exactSize); const catEnabledCheckbox = giteSettingsPanelElement.querySelector('#gite-filter-exactsize-cat-enabled'); if (catEnabledCheckbox) catEnabledCheckbox.checked = esConfigInPanel.enabled; else warn("Exact size category enable checkbox not found in settings panel."); renderExactSizeList('#gite-exactsize-predefined-list', esConfigInPanel.predefinedOptions || []); renderExactSizeList('#gite-exactsize-userdefined-list', esConfigInPanel.userDefinedOptions || [], true); } // Renders predefined or user-defined exact size options in the settings panel. function renderExactSizeList(listSelector, optionsFromPanelSettings, isUserDefined = false) { const listElement = giteSettingsPanelElement.querySelector(listSelector); if (!listElement) { if (DEBUG_MODE) warn(`Exact size list container ${listSelector} not found in settings panel.`); return; } listElement.innerHTML = ''; if (!optionsFromPanelSettings || optionsFromPanelSettings.length === 0) { const emptyMsgLi = document.createElement('li'); emptyMsgLi.textContent = getLocalizedString('settings_no_saved_items_placeholder'); emptyMsgLi.style.padding = "5px 8px"; emptyMsgLi.style.opacity = "0.7"; listElement.appendChild(emptyMsgLi); return; } optionsFromPanelSettings.forEach(opt => { if (opt.type === 'separator') { if (opt.isEnabled) { const sepLi = document.createElement('li'); sepLi.innerHTML = '<hr style="width:100%; margin: 3px 0; border-color: var(--gite-separator-color); opacity: 0.5;">'; listElement.appendChild(sepLi); } return; } const listItem = document.createElement('li'); listItem.dataset.id = opt.id; listItem.className = 'gite-exactsize-option-item'; let text = opt.customText || (isUserDefined && opt.label) || (opt.textKey ? getLocalizedString(opt.textKey, CURRENT_LANGUAGE, opt.id) : (isUserDefined ? `${opt.width}x${opt.height}` : opt.id)); const valueHint = isUserDefined ? `(${opt.width}x${opt.height})` : (opt.value ? `(${opt.value})` : ''); const deleteBtnTitle = getLocalizedString('btn_delete'); const editBtnTitle = getLocalizedString('btn_edit_label'); listItem.innerHTML = `<input type="checkbox" class="gite-option-enable-toggle" ${opt.isEnabled ? 'checked' : ''} data-opttype="${isUserDefined ? 'user' : 'predefined'}"> <span class="gite-option-text" title="${text}">${text}</span> <span class="gite-option-value-hint">${valueHint}</span> <div class="gite-actions"> ${isUserDefined ? `<button class="gite-edit-label-btn" ${GITE_LANG_KEY_ATTR}="btn_edit_label" data-gite-lang-target-attr="title" title="${editBtnTitle}">✏️</button>` : ''} ${isUserDefined ? `<button class="gite-delete-user-exact-btn" ${GITE_LANG_KEY_ATTR}="btn_delete" data-gite-lang-target-attr="title" title="${deleteBtnTitle}">🗑️</button>` : ''} </div>`; listElement.appendChild(listItem); }); } // Event handler for actions (enable/disable, edit, delete) on exact size items in the settings panel. function handleExactSizePanelActions(event) { const target = event.target; const listItem = target.closest('li.gite-exactsize-option-item'); if (!listItem) return; const optionId = listItem.dataset.id; const optTypeCheckbox = listItem.querySelector('.gite-option-enable-toggle'); const optType = optTypeCheckbox ? optTypeCheckbox.dataset.opttype : null; if (target.classList.contains('gite-option-enable-toggle')) { const isEnabled = target.checked; const optionListKey = optType === 'user' ? 'userDefinedOptions' : 'predefinedOptions'; const optionsListInPanel = getSettingFromPanel(`filters.exactSize.${optionListKey}`, []); const optionInPanel = optionsListInPanel.find(opt => opt.id === optionId); if (optionInPanel) optionInPanel.isEnabled = isEnabled; } else if (target.classList.contains('gite-edit-label-btn') && optType === 'user') { const userOptionsInPanel = getSettingFromPanel('filters.exactSize.userDefinedOptions', []); const optionInPanel = userOptionsInPanel.find(opt => opt.id === optionId); if (optionInPanel) { const currentLabel = optionInPanel.label || `${optionInPanel.width}x${optionInPanel.height}`; const newLabel = prompt(getLocalizedString('btn_edit_label') + ` for ${currentLabel}:`, currentLabel); if (newLabel !== null) { optionInPanel.label = newLabel.trim(); loadExactSizeSettingsToPanel(); alert(getLocalizedString('alert_label_updated'));} } } else if (target.classList.contains('gite-delete-user-exact-btn') && optType === 'user') { const optionTextElement = listItem.querySelector('.gite-option-text'); const optionDisplayText = optionTextElement ? optionTextElement.textContent : optionId; if (confirm(`${getLocalizedString('alert_confirm_delete_option_prefix')}${optionDisplayText}"?`)) { let userOptionsInPanel = getSettingFromPanel('filters.exactSize.userDefinedOptions', []); setSettingInPanel('filters.exactSize.userDefinedOptions', userOptionsInPanel.filter(opt => opt.id !== optionId)); loadExactSizeSettingsToPanel(); /* alert(getLocalizedString('alert_exact_size_deleted')); */ // Alert removed as per request } } } // Handles submission of the "Add New Exact Size" form in the settings panel. function handleAddNewExactSizeFromPanel(event) { event.preventDefault(); const form = event.target; const widthInput = form.querySelector('#gite-new-exact-width'); const heightInput = form.querySelector('#gite-new-exact-height'); const labelInput = form.querySelector('#gite-new-exact-label'); if(!widthInput || !heightInput || !labelInput) { warn("Add new exact size form inputs not found in settings panel."); return; } const width = parseInt(widthInput.value, 10); const height = parseInt(heightInput.value, 10); const label = labelInput.value.trim(); if (isNaN(width) || isNaN(height) || width <= 0 || height <= 0) { alert(getLocalizedString('alert_exact_size_invalid_input')); return; } const userOptionsInPanel = getSettingFromPanel('filters.exactSize.userDefinedOptions', []); const newValue = `${width}x${height}`; if (userOptionsInPanel.some(opt => opt.value === newValue)) { alert(getLocalizedString('alert_size_already_saved')); return; } const newId = `user_exact_${width}x${height}_${Date.now()}`; userOptionsInPanel.push({ id: newId, width, height, label: label || newValue, value: newValue, type: "imagesize", isCustom: true, isEnabled: true }); loadExactSizeSettingsToPanel(); /* alert(getLocalizedString('alert_exact_size_added')); */ // Alert removed as per request form.reset(); widthInput.focus(); } // --- Settings Panel Logic for Site Search --- function loadSiteSearchSettingsToPanel() { if (!giteSettingsPanelElement || !currentGiteSettingsForPanel.filters || !currentGiteSettingsForPanel.filters.site) { if (DEBUG_MODE) warn("Site search panel load prerequisites not met. Skipping loadSiteSearchSettingsToPanel."); return; } const siteConfigInPanel = getSettingFromPanel('filters.site', GITE_DEFAULT_SETTINGS.filters.site); const catEnabledCheckbox = giteSettingsPanelElement.querySelector('#gite-filter-sitesearch-cat-enabled'); const panelContentSections = giteSettingsPanelElement.querySelectorAll('#gite-panel-sitesearch .gite-options-panel-section'); if (catEnabledCheckbox) { catEnabledCheckbox.checked = siteConfigInPanel.enabled; panelContentSections.forEach(s => s.style.display = catEnabledCheckbox.checked ? '' : 'none'); } else if (DEBUG_MODE) warn("Site search category enable checkbox ('#gite-filter-sitesearch-cat-enabled') not found in panel."); renderSiteSearchList('#gite-sitesearch-userdefined-list', siteConfigInPanel.userDefinedOptions || []); } function renderSiteSearchList(listSelector, optionsFromPanelSettings) { const listElement = giteSettingsPanelElement.querySelector(listSelector); if (!listElement) { if (DEBUG_MODE) warn(`Site search list container ${listSelector} not found in settings panel.`); return; } listElement.innerHTML = ''; if (!optionsFromPanelSettings || optionsFromPanelSettings.length === 0) { const emptyMsgLi = document.createElement('li'); emptyMsgLi.textContent = getLocalizedString('settings_no_saved_items_placeholder'); emptyMsgLi.style.padding = "5px 8px"; emptyMsgLi.style.opacity = "0.7"; listElement.appendChild(emptyMsgLi); return; } optionsFromPanelSettings.forEach(opt => { const listItem = document.createElement('li'); listItem.dataset.id = opt.id; listItem.className = 'gite-exactsize-option-item gite-site-option-item'; const text = opt.label || opt.id; const valueHint = opt.value ? `(${opt.value})` : ''; const deleteBtnTitle = getLocalizedString('btn_delete'); const editBtnTitle = getLocalizedString('btn_edit_label'); listItem.innerHTML = `<input type="checkbox" class="gite-option-enable-toggle" ${opt.isEnabled ? 'checked' : ''} data-opttype="user-site" data-optionid="${opt.id}"><span class="gite-option-text" title="${text}">${text}</span><span class="gite-option-value-hint">${valueHint}</span><div class="gite-actions"><button class="gite-edit-site-btn" data-optionid="${opt.id}" ${GITE_LANG_KEY_ATTR}="btn_edit_label" data-gite-lang-target-attr="title" title="${editBtnTitle}">✏️</button><button class="gite-delete-user-site-btn" data-optionid="${opt.id}" ${GITE_LANG_KEY_ATTR}="btn_delete" data-gite-lang-target-attr="title" title="${deleteBtnTitle}">🗑️</button></div>`; listElement.appendChild(listItem); }); } function handleAddNewSiteFromPanel(event) { event.preventDefault(); const form = event.target; const labelInput = form.querySelector('#gite-new-site-label'); const domainInput = form.querySelector('#gite-new-site-domain'); if(!labelInput || !domainInput) { warn("Cannot add new site: Label or domain input element not found in the form."); return; } const label = labelInput.value.trim(); const rawDomain = domainInput.value.trim(); if (!label) { alert(getLocalizedString('alert_site_label_empty')); labelInput.focus(); return; } if (!rawDomain) { alert(getLocalizedString('alert_site_domain_empty')); domainInput.focus(); return; } const domain = cleanDomainInput(rawDomain); if (!domain) { alert(getLocalizedString('alert_site_domain_invalid')); domainInput.focus(); return; } const userOptionsInPanel = getSettingFromPanel('filters.site.userDefinedOptions', []); if (userOptionsInPanel.some(opt => opt.label.toLowerCase() === label.toLowerCase() || opt.value.toLowerCase() === domain.toLowerCase())) { alert(getLocalizedString('alert_site_already_saved')); return; } const newId = `user_site_${domain.replace(/[^a-z0-9]/gi, '')}_${Date.now()}`; const newSiteOption = { id: newId, label, value: domain, isEnabled: true, isCustom: true, type: 'site_filter' }; userOptionsInPanel.push(newSiteOption); renderSiteSearchList('#gite-sitesearch-userdefined-list', userOptionsInPanel); form.reset(); labelInput.focus(); } function handleSiteSearchPanelActions(event) { const target = event.target; const optionId = target.dataset.optionid; const listItem = target.closest('li.gite-site-option-item'); if (!optionId || !listItem) return; const userOptionsInPanel = getSettingFromPanel('filters.site.userDefinedOptions', []); const optionIndex = userOptionsInPanel.findIndex(opt => opt.id === optionId); if (optionIndex === -1) { if (DEBUG_MODE) warn(`Site option with ID ${optionId} not found in panel settings during action.`); return; } const optionInPanel = userOptionsInPanel[optionIndex]; if (target.classList.contains('gite-option-enable-toggle')) { optionInPanel.isEnabled = target.checked; if (DEBUG_MODE) log(`Site option "${optionInPanel.label}" isEnabled set to ${target.checked} in panel settings.`); } else if (target.classList.contains('gite-edit-site-btn')) { const currentLabel = optionInPanel.label; const currentValue = optionInPanel.value; const newLabel = prompt(getLocalizedString('btn_edit_label') + ` for "${currentLabel}" (Domain: ${currentValue}):`, currentLabel); if (newLabel === null) return; const trimmedNewLabel = newLabel.trim(); if (!trimmedNewLabel) { alert(getLocalizedString('alert_site_label_empty')); return; } const newValue = prompt(`Edit domain for "${trimmedNewLabel}" (Current: ${currentValue}):`, currentValue); if (newValue === null) return; const trimmedNewValue = newValue.trim(); if (!trimmedNewValue) { alert(getLocalizedString('alert_site_domain_empty')); return; } const cleanedDomain = cleanDomainInput(trimmedNewValue); if (!cleanedDomain) { alert(getLocalizedString('alert_site_domain_invalid')); return; } if (userOptionsInPanel.some(opt => opt.id !== optionId && (opt.label.toLowerCase() === trimmedNewLabel.toLowerCase() || opt.value.toLowerCase() === cleanedDomain.toLowerCase()))) { alert(getLocalizedString('alert_site_already_saved')); return; } optionInPanel.label = trimmedNewLabel; optionInPanel.value = cleanedDomain; renderSiteSearchList('#gite-sitesearch-userdefined-list', userOptionsInPanel); alert(getLocalizedString('alert_site_label_updated')); } else if (target.classList.contains('gite-delete-user-site-btn')) { if (confirm(`${getLocalizedString('alert_confirm_delete_option_prefix')}"${optionInPanel.label}"?`)) { userOptionsInPanel.splice(optionIndex, 1); renderSiteSearchList('#gite-sitesearch-userdefined-list', userOptionsInPanel); } } } // Renders a generic list of options (e.g., for Size, Time, Region) in the settings panel. function renderSimpleOptionsListForCategory(categoryKey, listElementId, includeValueHint = false) { if (!giteSettingsPanelElement || !currentGiteSettingsForPanel.filters || !currentGiteSettingsForPanel.filters[categoryKey]) { if (DEBUG_MODE) warn(`Cannot render simple options list for "${categoryKey}": Prerequisites not met.`); return; } const listElement = giteSettingsPanelElement.querySelector(`#${listElementId}`); if (!listElement) { if (DEBUG_MODE) warn(`Options list container #${listElementId} for category "${categoryKey}" not found.`); return; } listElement.innerHTML = ''; const optionsFromPanelSettings = getSettingFromPanel(`filters.${categoryKey}.options`, []); if (!optionsFromPanelSettings || optionsFromPanelSettings.length === 0) { const emptyMsgLi = document.createElement('li'); emptyMsgLi.textContent = getLocalizedString('settings_no_saved_items_placeholder'); emptyMsgLi.style.padding = "5px 8px"; emptyMsgLi.style.opacity = "0.7"; listElement.appendChild(emptyMsgLi); return; } optionsFromPanelSettings.forEach(opt => { if (opt.type === 'separator') { if (opt.isEnabled) { const sepLi = document.createElement('li'); sepLi.innerHTML = '<hr style="width:100%; margin: 3px 0; border-color: var(--gite-separator-color); opacity: 0.5;">'; listElement.appendChild(sepLi); } return; } const listItem = document.createElement('li'); listItem.dataset.id = opt.id; listItem.className = 'gite-option-item'; const text = opt.customText || getLocalizedString(opt.textKey, CURRENT_LANGUAGE, opt.id); let valueHintHtml = ''; if (includeValueHint) { const hintValue = opt.tbsValue || opt.value || (opt.paramName === 'cr' ? opt.value : ''); if (hintValue) valueHintHtml = `<span class="gite-option-value-hint">(${hintValue})</span>`; } let columnHintHtml = ''; // 'column' attribute is from GITE_OPTION_DEFINITIONS for Size filter if (categoryKey === 'size' && opt.column) { columnHintHtml = `<span class="gite-option-value-hint" style="margin-left: 5px; font-size:0.8em; opacity:0.7;">(Col ${opt.column})</span>`; } listItem.innerHTML = `<input type="checkbox" class="gite-option-enable-toggle" ${opt.isEnabled ? 'checked' : ''}><span class="gite-option-text" title="${text}">${text}</span>${columnHintHtml}${valueHintHtml}`; listElement.appendChild(listItem); }); } function loadTimeOptionsSettingsList() { renderSimpleOptionsListForCategory('time', 'gite-time-options-list', true); } function loadSizeOptionsSettingsList() { renderSimpleOptionsListForCategory('size', 'gite-size-options-list', true); } // Generic handler for enabling/disabling options in "simple lists" (Size, Time, Region) in settings panel. function handleGenericOptionEnableToggle(event, categoryKey) { const target = event.target; if (!target.classList.contains('gite-option-enable-toggle')) return; const listItem = target.closest('li.gite-option-item'); if (!listItem) return; const optionId = listItem.dataset.id; const isEnabled = target.checked; const optionsListInPanel = getSettingFromPanel(`filters.${categoryKey}.options`, []); const optionInPanel = optionsListInPanel.find(opt => opt.id === optionId); if (optionInPanel) { optionInPanel.isEnabled = isEnabled; if(DEBUG_MODE) log(`Option "${optionId}" in category "${categoryKey}" set to isEnabled: ${isEnabled} in panel settings.`); } else if (DEBUG_MODE) warn(`Option with ID "${optionId}" not found in panel settings for category "${categoryKey}" during toggle action.`); } // Resets options for a given category (Size, Time, Region) in the settings panel to their defaults. function handleResetCategoryOptions(categoryKey) { const defaultCategoryConfig = GITE_DEFAULT_SETTINGS.filters[categoryKey]; if (!defaultCategoryConfig || !defaultCategoryConfig.options) { warn(`No default options defined in GITE_DEFAULT_SETTINGS for category "${categoryKey}". Cannot reset.`); return; } const defaultCategoryOptions = defaultCategoryConfig.options; let localizedCategoryTitleKey; // Construct the correct I18N key for the category title switch (categoryKey) { case 'exactSize': localizedCategoryTitleKey = 'filter_title_exact_size'; break; case 'aspectRatio': localizedCategoryTitleKey = 'filter_title_aspect_ratio'; break; case 'usageRights': localizedCategoryTitleKey = 'filter_title_usage_rights'; break; case 'fileType': localizedCategoryTitleKey = 'filter_title_file_type'; break; case 'site': localizedCategoryTitleKey = 'filter_title_site_search'; break; default: localizedCategoryTitleKey = `filter_title_${categoryKey.toLowerCase()}`; } const localizedCategoryTitle = getLocalizedString(localizedCategoryTitleKey); if (confirm(`${getLocalizedString('btn_reset_options_for_category_prefix')}${localizedCategoryTitle}${getLocalizedString('btn_reset_options_for_category_suffix')}?`)) { setSettingInPanel(`filters.${categoryKey}.options`, JSON.parse(JSON.stringify(defaultCategoryOptions))); if (categoryKey === 'time') loadTimeOptionsSettingsList(); else if (categoryKey === 'size') loadSizeOptionsSettingsList(); else if (categoryKey === 'region') loadRegionOptionsSettingsList(); if(DEBUG_MODE) log(`Options for category "${categoryKey}" reset to defaults in panel settings.`); } } // Applies settings changes "live" from the panel to the main giteSettings and active UI. function applyLiveSettingChangesFromPanel() { if (DEBUG_MODE) log("Applying live setting changes from panel to main settings and UI..."); let languageActuallyChanged = false; const panelSelectedLang = getSettingFromPanel('general.selectedLanguage', 'auto'); const panelEffectiveLang = panelSelectedLang === 'auto' ? detectBrowserLanguageSafe() : panelSelectedLang; if (CURRENT_LANGUAGE !== panelEffectiveLang) { languageActuallyChanged = true; CURRENT_LANGUAGE = panelEffectiveLang; if(DEBUG_MODE) log(`Language changed to ${CURRENT_LANGUAGE} via panel "Apply" action.`); } const categoryEnableCheckboxes = { exactSize: '#gite-filter-exactsize-cat-enabled', region: '#gite-filter-region-cat-enabled', site: '#gite-filter-sitesearch-cat-enabled' }; for (const catKey in categoryEnableCheckboxes) { if(giteSettingsPanelElement) { const checkbox = giteSettingsPanelElement.querySelector(categoryEnableCheckboxes[catKey]); if (checkbox && currentGiteSettingsForPanel.filters && currentGiteSettingsForPanel.filters[catKey]) { setSettingInPanel(`filters.${catKey}.enabled`, checkbox.checked); } } } giteSettings = JSON.parse(JSON.stringify(currentGiteSettingsForPanel)); addToolbarSettingsButtonIfNeeded(true); if(languageActuallyChanged) { updateAllLocalizableElements(); setupGMMenus(); } initializeEnhancedFilters(true); if(DEBUG_MODE) log("Live changes applied. Filters re-initialized."); } // Safely detects browser language, defaulting to 'en'. function detectBrowserLanguageSafe() { const browserLangInit = (navigator.language || navigator.userLanguage || 'en').toLowerCase(); if (browserLangInit.startsWith('zh-tw') || browserLangInit.startsWith('zh-hk') || browserLangInit.startsWith('zh-hant')) return 'zh-TW'; if (browserLangInit.startsWith('ja')) return 'ja'; // Added Japanese return 'en'; } // Handles "Save & Close" or "Save" from the settings panel, persisting changes. function handleSaveSettingsFromPanel(andClose = true) { if (!giteSettingsPanelElement || !currentGiteSettingsForPanel.general) { warn("Cannot save settings: Panel element or general settings in panel's copy are missing."); return; } let needsReload = false; let languageActuallyChanged = false; const panelSelectedLang = getSettingFromPanel('general.selectedLanguage', 'auto'); const panelEffectiveLang = panelSelectedLang === 'auto' ? detectBrowserLanguageSafe() : panelSelectedLang; if (CURRENT_LANGUAGE !== panelEffectiveLang) languageActuallyChanged = true; let autoExpandChanged = false; if (giteSettings.general.autoExpandTools !== getSettingFromPanel('general.autoExpandTools', GITE_DEFAULT_SETTINGS.general.autoExpandTools)) autoExpandChanged = true; const categoryEnableCheckboxes = { exactSize: '#gite-filter-exactsize-cat-enabled', region: '#gite-filter-region-cat-enabled', site: '#gite-filter-sitesearch-cat-enabled' }; for (const catKey in categoryEnableCheckboxes) { if(giteSettingsPanelElement) { const checkbox = giteSettingsPanelElement.querySelector(categoryEnableCheckboxes[catKey]); if (checkbox && currentGiteSettingsForPanel.filters && currentGiteSettingsForPanel.filters[catKey]) setSettingInPanel(`filters.${catKey}.enabled`, checkbox.checked); } } if (languageActuallyChanged) { CURRENT_LANGUAGE = panelEffectiveLang; if(DEBUG_MODE) log(`Language set to ${CURRENT_LANGUAGE} and will be saved.`); } giteSettings = JSON.parse(JSON.stringify(currentGiteSettingsForPanel)); if (typeof GM_setValue === 'function') { try { GM_setValue(GITE_SETTINGS_GM_KEY, JSON.stringify(giteSettings)); if (DEBUG_MODE) log("GITE settings saved to GM storage."); let alertMsgKey = 'gm_settings_updated_no_reload'; if (languageActuallyChanged) { updateAllLocalizableElements(CURRENT_LANGUAGE); setupGMMenus(); } if (autoExpandChanged) { needsReload = true; alertMsgKey = 'alert_settings_saved_reload_required'; } if (!needsReload) { addToolbarSettingsButtonIfNeeded(true); initializeEnhancedFilters(true); } if (alertMsgKey !== 'gm_settings_updated_no_reload' || needsReload) alert(getLocalizedString(needsReload ? 'alert_settings_saved_reload_required' : alertMsgKey)); if (needsReload) { if (DEBUG_MODE) log("Reloading page due to a GITE setting change."); window.location.reload(); } } catch (e) { error("Error saving GITE settings via GM_setValue:", e); alert(getLocalizedString('alert_generic_error_saving')); } } else { warn("GM_setValue is not available. Settings cannot be saved."); alert(getLocalizedString('alert_gm_setvalue_unavailable')); } if (andClose && !needsReload) closeSettingsPanel(true); } // Resets all settings in the panel's temporary copy to defaults. function handleResetAllSettingsInPanel() { if (confirm(getLocalizedString('alert_confirm_reset_all_settings'))) { currentGiteSettingsForPanel = JSON.parse(JSON.stringify(GITE_DEFAULT_SETTINGS)); const newLangForPanel = currentGiteSettingsForPanel.general.selectedLanguage === 'auto' ? detectBrowserLanguageSafe() : currentGiteSettingsForPanel.general.selectedLanguage; updateAllLocalizableElements(newLangForPanel, giteSettingsPanelElement); loadGeneralSettingsToPanel(); loadExactSizeSettingsToPanel(); loadTimeOptionsSettingsList(); loadSizeOptionsSettingsList(); loadRegionSettingsToPanel(); loadRegionOptionsSettingsList(); loadSiteSearchSettingsToPanel(); alert(getLocalizedString('alert_settings_reset_to_default')); if (DEBUG_MODE) log("All settings in panel reset to defaults. Save to persist."); } } // Sets up all event listeners for the settings panel UI elements. function setupPanelEventListeners() { if (!giteSettingsPanelElement) { warn("Cannot setup panel event listeners: giteSettingsPanelElement is null."); return; } const q = (sel) => giteSettingsPanelElement.querySelector(sel); // Shorthand const qA = (sel) => giteSettingsPanelElement.querySelectorAll(sel); // Shorthand const closeBtn = q('#gite-settings-close-btn'); if(closeBtn) closeBtn.addEventListener('click', () => closeSettingsPanel()); else warn("Settings panel close button not found."); const overlay = q('.gite-modal-overlay'); if(overlay) overlay.addEventListener('click', () => closeSettingsPanel()); else warn("Settings panel overlay not found."); const tabNav = q('.gite-tabs-navigation ul'); if (tabNav) { tabNav.addEventListener('click', switchTabInPanel); tabNav.addEventListener('keydown', (e) => { if ((e.key === 'Enter' || e.key === ' ') && e.target.role === 'tab' && !e.target.disabled) { e.preventDefault(); switchTabInPanel(e); }}); } else warn("Settings panel tab navigation list not found."); const langSelect = q('#gite-setting-language'); if(langSelect) langSelect.addEventListener('change', (e) => { setSettingInPanel('general.selectedLanguage', e.target.value); const nl = e.target.value === 'auto' ? detectBrowserLanguageSafe() : e.target.value; updateAllLocalizableElements(nl, giteSettingsPanelElement);}); else warn("Language select dropdown not found."); const autoExpandCheck = q('#gite-setting-autoexpand'); if(autoExpandCheck) autoExpandCheck.addEventListener('change', (e) => setSettingInPanel('general.autoExpandTools', e.target.checked)); else warn("Auto-expand checkbox not found."); const showToolbarBtnCheck = q('#gite-setting-showtoolbarbutton'); if(showToolbarBtnCheck) showToolbarBtnCheck.addEventListener('change', (e) => setSettingInPanel('general.showSettingsButtonOnToolbar', e.target.checked)); else warn("Show toolbar button checkbox not found."); const saveBtn = q('#gite-settings-save-btn'); if(saveBtn) saveBtn.addEventListener('click', () => handleSaveSettingsFromPanel(true)); else warn("Save & Close button not found."); const applyBtn = q('#gite-settings-apply-btn'); if(applyBtn) applyBtn.addEventListener('click', () => { applyLiveSettingChangesFromPanel(); handleSaveSettingsFromPanel(false); }); else warn("Apply button not found."); const cancelBtn = q('#gite-settings-cancel-btn'); if(cancelBtn) cancelBtn.addEventListener('click', () => { closeSettingsPanel(); }); else warn("Cancel button not found."); const resetAllBtn = q('#gite-settings-reset-all-btn'); if(resetAllBtn) resetAllBtn.addEventListener('click', handleResetAllSettingsInPanel); else warn("Reset All button not found."); const esPanel = q('#gite-panel-exactsize'); if (esPanel) { const pList = esPanel.querySelector('#gite-exactsize-predefined-list'); if(pList) pList.addEventListener('change', handleExactSizePanelActions); const uList = esPanel.querySelector('#gite-exactsize-userdefined-list'); if(uList) { uList.addEventListener('click', handleExactSizePanelActions); uList.addEventListener('change', handleExactSizePanelActions); } const addForm = esPanel.querySelector('#gite-add-exact-size-form'); if (addForm) addForm.addEventListener('submit', handleAddNewExactSizeFromPanel); const esCatEnable = esPanel.querySelector('#gite-filter-exactsize-cat-enabled'); if(esCatEnable) esCatEnable.addEventListener('change', (e) => { setSettingInPanel('filters.exactSize.enabled', e.target.checked); esPanel.querySelectorAll('.gite-exactsize-panel-section > *:not(.gite-setting-group), .gite-exactsize-panel-section > h3, .gite-exactsize-panel-section > ul, .gite-exactsize-panel-section > form').forEach(s => s.style.opacity = e.target.checked ? '1' : '0.5'); }); } else warn("Exact Size panel tab content not found."); const timePanel = q('#gite-panel-time'); if (timePanel) { const timeOptionsList = timePanel.querySelector('#gite-time-options-list'); if(timeOptionsList) timeOptionsList.addEventListener('change', (e) => handleGenericOptionEnableToggle(e, 'time')); const timeResetBtn = timePanel.querySelector('#gite-time-reset-cat-btn'); if(timeResetBtn) timeResetBtn.addEventListener('click', () => handleResetCategoryOptions('time')); } else warn("Time panel tab content not found."); const sizePanel = q('#gite-panel-size'); if (sizePanel) { const sizeOptionsList = sizePanel.querySelector('#gite-size-options-list'); if(sizeOptionsList) sizeOptionsList.addEventListener('change', (e) => handleGenericOptionEnableToggle(e, 'size')); const sizeResetBtn = sizePanel.querySelector('#gite-size-reset-cat-btn'); if(sizeResetBtn) sizeResetBtn.addEventListener('click', () => handleResetCategoryOptions('size')); } else warn("Size panel tab content not found."); const regionPanel = q('#gite-panel-region'); if (regionPanel) { const regionCatEnable = regionPanel.querySelector('#gite-filter-region-cat-enabled'); if(regionCatEnable) regionCatEnable.addEventListener('change', (e) => { setSettingInPanel('filters.region.enabled', e.target.checked); regionPanel.querySelectorAll('.gite-options-list, .gite-panel-category-footer, p, h3:not(:first-of-type)').forEach(el => el.style.opacity = e.target.checked ? '1' : '0.5'); }); const regionOptionsList = regionPanel.querySelector('#gite-region-options-list'); if(regionOptionsList) regionOptionsList.addEventListener('change', (e) => handleGenericOptionEnableToggle(e, 'region')); const regionResetBtn = regionPanel.querySelector('#gite-region-reset-cat-btn'); if(regionResetBtn) regionResetBtn.addEventListener('click', () => handleResetCategoryOptions('region')); } else warn("Region panel tab content not found."); const siteSearchPanel = q('#gite-panel-sitesearch'); if (siteSearchPanel) { const siteCatEnable = siteSearchPanel.querySelector('#gite-filter-sitesearch-cat-enabled'); if (siteCatEnable) siteCatEnable.addEventListener('change', (e) => { setSettingInPanel('filters.site.enabled', e.target.checked); siteSearchPanel.querySelectorAll('.gite-options-panel-section').forEach(s => s.style.display = e.target.checked ? '' : 'none'); }); const addSiteForm = siteSearchPanel.querySelector('#gite-add-new-site-form'); if (addSiteForm) addSiteForm.addEventListener('submit', handleAddNewSiteFromPanel); const userSiteList = siteSearchPanel.querySelector('#gite-sitesearch-userdefined-list'); if (userSiteList) { userSiteList.addEventListener('click', handleSiteSearchPanelActions); userSiteList.addEventListener('change', handleSiteSearchPanelActions); } } else warn("Site Search panel tab content not found."); } // --- Toolbar Button & Filter State Updates --- // Creates the GITE settings button for the Google Images toolbar. function createGiteToolbarSettingsButton() { const button = document.createElement('div'); button.id = GITE_SETTINGS_BUTTON_ID; button.className = 'BaegVc YmvwI'; button.setAttribute('role', 'button'); button.setAttribute('tabindex', '0'); button.innerHTML = '⚙︎'; button.setAttribute(GITE_LANG_KEY_ATTR, 'tooltip_gite_settings_button'); button.setAttribute('data-gite-lang-target-attr', 'title'); button.title = getLocalizedString('tooltip_gite_settings_button'); button.style.cssText = "margin-left: 8px; padding: 0 6px; font-size: 0.9em; cursor: pointer; display: inline-flex; align-items: center; line-height: normal; height: auto;"; button.addEventListener('click', openSettingsPanel); button.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openSettingsPanel(); } }); return button; } let settingsButtonObserver = null; let settingsButtonObserverRetries = MAX_TOOLBAR_BUTTON_OBSERVER_TRIES; // Adds or removes the GITE settings button on the toolbar based on user settings. function addToolbarSettingsButtonIfNeeded(forceRecheck = false) { const existingButton = document.getElementById(GITE_SETTINGS_BUTTON_ID); const showButtonSetting = giteSettings && giteSettings.general ? giteSettings.general.showSettingsButtonOnToolbar : true; if (showButtonSetting) { if (!existingButton || forceRecheck) { if (existingButton && forceRecheck) existingButton.remove(); const toolsButton = document.querySelector(TOOLS_BUTTON_SELECTOR); if (toolsButton && toolsButton.parentElement && toolsButton.offsetParent !== null) { const giteSettingsBtn = createGiteToolbarSettingsButton(); toolsButton.parentElement.insertBefore(giteSettingsBtn, toolsButton.nextSibling); if (DEBUG_MODE) log("GITE Toolbar Settings button added/re-added."); if (settingsButtonObserver) { settingsButtonObserver.disconnect(); settingsButtonObserver = null; } settingsButtonObserverRetries = MAX_TOOLBAR_BUTTON_OBSERVER_TRIES; } else { if (!settingsButtonObserver && settingsButtonObserverRetries > 0) { if (DEBUG_MODE) warn(`Google Tools button (${TOOLS_BUTTON_SELECTOR}) not found/visible. Starting observer for GITE settings button.`); const observerTargetNode = document.querySelector(OBSERVER_TARGET_SELECTOR_ID) || document.body; if (!observerTargetNode) { error("Cannot start settingsButtonObserver: Observer target node not found."); return; } settingsButtonObserver = new MutationObserver((mutations, obs) => { const freshToolsBtn = document.querySelector(TOOLS_BUTTON_SELECTOR); if (freshToolsBtn && freshToolsBtn.parentElement && freshToolsBtn.offsetParent !== null) { if (DEBUG_MODE) log("Settings button observer: Google Tools button now available."); obs.disconnect(); settingsButtonObserver = null; addToolbarSettingsButtonIfNeeded(true); } else { settingsButtonObserverRetries--; if (DEBUG_MODE) log(`Settings button observer: Retries left: ${settingsButtonObserverRetries}`); if (settingsButtonObserverRetries <= 0) { obs.disconnect(); settingsButtonObserver = null; warn("Settings button observer timed out."); } } }); settingsButtonObserver.observe(observerTargetNode, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'hidden', 'aria-hidden'] }); } else if (settingsButtonObserverRetries <= 0 && settingsButtonObserver) { settingsButtonObserver.disconnect(); settingsButtonObserver = null; } } } else if (existingButton) existingButton.style.display = 'inline-flex'; } else { if (existingButton) { existingButton.remove(); if (DEBUG_MODE) log("GITE Toolbar Settings button removed as per settings."); } if (settingsButtonObserver) { settingsButtonObserver.disconnect(); settingsButtonObserver = null; } } } // Gets active color filter values from URL for comparison. function _getActiveColorFilterValues(currentUrl) { const imgcParam = currentUrl.searchParams.get('imgc'); const tbsParam = currentUrl.searchParams.get('tbs') || ""; let effectiveValue = ""; if (tbsParam.includes("ic:gray")) effectiveValue = "ic:gray"; else if (tbsParam.includes("ic:trans")) effectiveValue = "ic:trans"; else if (tbsParam.includes("ic:specific,isc:")) { const match = tbsParam.match(/ic:specific,isc:[^,]+/); if (match) effectiveValue = match[0]; } else if (tbsParam.includes("ic:color")) effectiveValue = "ic:color"; else if (imgcParam === "color" && !tbsParam.includes("ic:")) effectiveValue = "ic:color"; // Handle legacy 'Full color' without tbs=ic:color return { valueForComparison: effectiveValue, tbs: tbsParam, imgc: imgcParam }; } // Checks if any GITE-managed filters are active in the current URL. function areGiteFiltersActive() { const params = new URLSearchParams(window.location.search); if (params.get('imgsz') || params.get('imgar') || params.get('as_filetype') || params.has('cr')) return true; const qParam = params.get('q') || ""; if (qParam.includes("imagesize:") || qParam.includes("site:")) return true; const tbs = params.get('tbs'); if (tbs) { const giteTbsKeys = ['itp:', 'ic:', 'isc:', 'sur:', 'qdr:', 'cdr:', 'isz:', 'islt:', 'isilu:']; if (giteTbsKeys.some(key => tbs.includes(key))) return true; } if (params.get('imgc') === 'color' && !(tbs && tbs.includes("ic:"))) return true; // Legacy 'Full color' check return false; } // Updates visibility of GITE's custom "Clear" button. function updateGiteClearButtonVisibility() { let giteClearButton = document.getElementById(GITE_CLEAR_BUTTON_ID); const filtersContainer = document.querySelector(FILTER_BUTTONS_CONTAINER_SELECTOR); const containerParent = filtersContainer ? filtersContainer.parentElement : null; if (!giteClearButton && containerParent) { giteClearButton = document.createElement('a'); giteClearButton.id = GITE_CLEAR_BUTTON_ID; giteClearButton.className = FILTER_BUTTON_WRAPPER_CLASS.substring(1) + ' UbBYac'; giteClearButton.setAttribute(GITE_LANG_KEY_ATTR, 'btn_clear'); giteClearButton.textContent = getLocalizedString('btn_clear'); giteClearButton.href = "javascript:void(0);"; giteClearButton.addEventListener('click', (e) => { e.preventDefault(); if (DEBUG_MODE) log("GITE Clear button clicked."); const clearUrl = buildNewUrl('', '', null, true); window.location.href = clearUrl; }); let inserted = false; const advSearchAnchor = containerParent.querySelector('a[href*="advanced_image_search"]'); if (advSearchAnchor) { let currentElementToInsertAfter = advSearchAnchor; while(currentElementToInsertAfter && currentElementToInsertAfter.parentElement !== containerParent) { currentElementToInsertAfter = currentElementToInsertAfter.parentElement; if (!currentElementToInsertAfter || currentElementToInsertAfter === document.body) { currentElementToInsertAfter = null; break; }} if (currentElementToInsertAfter) { if (currentElementToInsertAfter.nextSibling) containerParent.insertBefore(giteClearButton, currentElementToInsertAfter.nextSibling); else containerParent.appendChild(giteClearButton); inserted = true; if (DEBUG_MODE) log("GITE Clear button inserted near Advanced Search link."); } } if (!inserted && filtersContainer && (!giteClearButton.parentNode || giteClearButton.parentNode !== filtersContainer)) { filtersContainer.appendChild(giteClearButton); inserted = true; if (DEBUG_MODE) log("GITE Clear button appended to filtersContainer (fallback)."); } if (!inserted && containerParent && (!giteClearButton.parentNode || giteClearButton.parentNode !== containerParent)) { containerParent.appendChild(giteClearButton); if (DEBUG_MODE) log("GITE Clear button appended to containerParent (last resort).");} } if (giteClearButton) { giteClearButton.style.display = areGiteFiltersActive() ? CLEAR_BUTTON_TARGET_DISPLAY_STYLE : 'none'; if (giteClearButton.classList.contains('UbBYac')) giteClearButton.style.marginLeft = "16px"; } else if (DEBUG_MODE && enhancedFiltersInitializedThisInstance) warn("GITE Clear button could not be created/found to update its visibility."); } // Checks if the current page is a Google Images search results page. function isImageUrl() { const href = window.location.href; return href.includes('tbm=isch') || href.includes('udm=2'); } // Clicks Google's "Tools" button to expand the filter bar if it's not already expanded. function clickToolsButtonToExpand(callback) { let attempts = BUTTON_CLICK_MAX_ATTEMPTS; if (giteToolsExpandTimer) { clearInterval(giteToolsExpandTimer); giteToolsExpandTimer = null; } if (DEBUG_MODE) log("Attempting to auto-expand Google 'Tools' bar..."); giteToolsExpandTimer = setInterval(() => { const toolsButton = document.querySelector(TOOLS_BUTTON_SELECTOR); const activeElement = document.activeElement; if (toolsButton) { if (toolsButton.getAttribute('aria-expanded') === 'true') { clearInterval(giteToolsExpandTimer); giteToolsExpandTimer = null; if (DEBUG_MODE) log("'Tools' bar already expanded."); if (activeElement && typeof activeElement.focus === 'function' && activeElement !== document.body) { try { activeElement.focus({ preventScroll:true }); } catch (e) { warn("Failed to restore focus after confirming tools expanded:", e); }} if (typeof callback === 'function') callback(true); return; } try { if (DEBUG_MODE) log(`Clicking 'Tools' button (Attempt: ${BUTTON_CLICK_MAX_ATTEMPTS - attempts + 1})`); toolsButton.click(); } catch(e) { error("Error clicking 'Tools' button:", e); clearInterval(giteToolsExpandTimer); giteToolsExpandTimer = null; if(typeof callback === 'function') callback(false); return; } setTimeout(() => { const freshToolsButton = document.querySelector(TOOLS_BUTTON_SELECTOR); if (freshToolsButton && freshToolsButton.getAttribute('aria-expanded') === 'true') { clearInterval(giteToolsExpandTimer); giteToolsExpandTimer = null; if (DEBUG_MODE) log("'Tools' bar expanded successfully after click."); if (activeElement && typeof activeElement.focus === 'function' && activeElement !== document.body) { try { activeElement.focus({ preventScroll:true }); } catch (e) { warn("Failed to restore focus post-tools-click:", e); }} if (typeof callback === 'function') callback(true); } else { if (DEBUG_MODE && freshToolsButton) log("'Tools' bar not expanded after click attempt."); else if (DEBUG_MODE && !freshToolsButton) log("'Tools' button disappeared after click attempt.");} }, POST_CLICK_DELAY_MS); } else if (DEBUG_MODE) log("'Tools' button not found for auto-expansion attempt."); attempts--; if (attempts <= 0) { clearInterval(giteToolsExpandTimer); giteToolsExpandTimer = null; warn("Failed to auto-expand 'Tools' bar after max attempts."); if (typeof callback === 'function') callback(false); } }, BUTTON_CLICK_RETRY_DELAY_MS); } // --- Main Filter Initialization and Update Logic --- // Dynamically adds a GITE filter category menu to the Google Images filter bar. function addFilterCategoryMenu(categoryKey, categoryTitleKey, paramName, idPrefix, anchorElementForInsertion) { const categoryTitle = getLocalizedString(categoryTitleKey); const optionsForCategory = getFilterOptionsFromSettings(categoryKey); const categoryConfig = giteSettings.filters[categoryKey]; const isCategoryEnabledInSettings = categoryConfig ? categoryConfig.enabled : true; if (!isCategoryEnabledInSettings) { if (DEBUG_MODE) log(`Skipping filter category '${categoryTitle}' (key: ${categoryKey}), as it's disabled in settings.`); return null; } const managedWithOptions = ['size', 'time', 'region', 'exactSize', 'site']; const isOnlyAnyOptionForManaged = managedWithOptions.includes(categoryKey) && optionsForCategory.length === 1 && (optionsForCategory[0].id === 'gite_site_any' || optionsForCategory[0].id === 'gite_exact_any'); if (optionsForCategory.length === 0 && managedWithOptions.includes(categoryKey)) { if (DEBUG_MODE) log(`Managed category '${categoryTitle}' (key: ${categoryKey}) has no displayable options. Menu will not be created unless it's site/exactSize with only an "Any" option.`); if (categoryKey !== 'site' && categoryKey !== 'exactSize') return null; } else if (optionsForCategory.length === 0 && !managedWithOptions.includes(categoryKey) ) { if(DEBUG_MODE) warn(`No options defined or enabled for non-managed category '${categoryTitle}' (key: ${categoryKey}). Skipping menu creation.`); return null; } else if (DEBUG_MODE && isOnlyAnyOptionForManaged) log(`Managed category '${categoryTitle}' (key: ${categoryKey}) only has the "Any" option available. Menu will be created.`); if (DEBUG_MODE) log(`Adding GITE filter category: ${categoryTitle} (key: ${categoryKey})`); const filtersContainer = document.querySelector(FILTER_BUTTONS_CONTAINER_SELECTOR); if (!filtersContainer) { error(`Cannot add filter '${categoryTitle}': Filter buttons container (${FILTER_BUTTONS_CONTAINER_SELECTOR}) not found.`); return null; } const templateSpan = document.querySelector(FILTER_SPAN_TEMPLATE_SELECTOR); if (!templateSpan) { error(`Cannot add filter '${categoryTitle}': Template span (${FILTER_SPAN_TEMPLATE_SELECTOR}) for cloning not found.`); return null; } const newFilterSpan = templateSpan.cloneNode(true); const gPopup = newFilterSpan.querySelector('g-popup'); const triggerButton = newFilterSpan.querySelector('div[jsname="oYxtQd"]'); const newTriggerButtonTextElement = triggerButton ? triggerButton.querySelector(FILTER_BUTTON_TEXT_CLASS) : null; const triggerButtonWrapperElement = triggerButton ? triggerButton.querySelector(FILTER_BUTTON_WRAPPER_CLASS) : null; const gMenu = newFilterSpan.querySelector('g-menu'); if (!gPopup || !triggerButton || !newTriggerButtonTextElement || !gMenu || !triggerButtonWrapperElement) { error(`Cloned template for '${categoryTitle}' is missing essential parts. Cannot create filter menu.`); if (newFilterSpan.remove) newFilterSpan.remove(); return null; } const newPopupId = `${idPrefix}-popup-${Date.now()}`; gPopup.id = newPopupId; gPopup.removeAttribute('__is_owner'); const gMenuPopupContainer = gPopup.querySelector('div[jsname="V68bde"]'); if (gMenuPopupContainer) gMenuPopupContainer.setAttribute('jsowner', newPopupId); newTriggerButtonTextElement.textContent = categoryTitle; newTriggerButtonTextElement.setAttribute(GITE_LANG_KEY_ATTR, categoryTitleKey); while (gMenu.firstChild) { gMenu.removeChild(gMenu.firstChild); } gMenu.style.removeProperty('display'); if (categoryKey === 'exactSize') { const inputArea = document.createElement('div'); inputArea.className = 'gite-custom-imagesize-input-area'; inputArea.style.cssText = 'padding: 8px 16px; border-bottom: 1px solid var(--gite-separator-color); margin-bottom: 8px;'; gMenu.appendChild(inputArea); const topRowControlsDiv = document.createElement('div'); topRowControlsDiv.className = 'gite-imagesize-input-controls'; inputArea.appendChild(topRowControlsDiv); const widthInput = document.createElement('input'); widthInput.type = 'number'; widthInput.id = 'gite-imagesize-width-menu'; widthInput.min = "1"; widthInput.setAttribute(GITE_LANG_KEY_ATTR, "exact_size_placeholder_width"); widthInput.setAttribute('data-gite-lang-target-attr', 'placeholder'); widthInput.placeholder = getLocalizedString('exact_size_placeholder_width'); topRowControlsDiv.appendChild(widthInput); const crossSpan = document.createElement('span'); crossSpan.textContent = '×'; topRowControlsDiv.appendChild(crossSpan); const heightInput = document.createElement('input'); heightInput.type = 'number'; heightInput.id = 'gite-imagesize-height-menu'; heightInput.min = "1"; heightInput.setAttribute(GITE_LANG_KEY_ATTR, "exact_size_placeholder_height"); heightInput.setAttribute('data-gite-lang-target-attr', 'placeholder'); heightInput.placeholder = getLocalizedString('exact_size_placeholder_height'); topRowControlsDiv.appendChild(heightInput); const applyButton = document.createElement('button'); applyButton.id = 'gite-imagesize-apply-menu'; applyButton.textContent = '✓'; applyButton.setAttribute(GITE_LANG_KEY_ATTR, "btn_apply"); applyButton.setAttribute('data-gite-lang-target-attr', 'title'); applyButton.title = getLocalizedString('btn_apply'); applyButton.style.cssText = "margin-left: 8px; padding: 6px 10px; background-color: var(--gite-datepicker-button-bg); color: var(--gite-datepicker-button-text); border: 1px solid var(--gite-datepicker-button-bg); border-radius: 4px; cursor: pointer;"; topRowControlsDiv.appendChild(applyButton); applyButton.addEventListener('click', () => { const widthVal = parseInt(widthInput.value, 10); const heightVal = parseInt(heightInput.value, 10); if (isNaN(widthVal) || isNaN(heightVal) || widthVal <= 0 || heightVal <= 0) { alert(getLocalizedString('alert_exact_size_invalid_input')); return; } const imagesizeValue = `${widthVal}x${heightVal}`; const exactSizeOptionForUrl = { categoryKey: 'exactSize', type: 'imagesize', value: imagesizeValue, originalTitleEn: getLocalizedString('filter_title_exact_size', 'en') }; const newUrl = buildNewUrl('q', imagesizeValue, exactSizeOptionForUrl); const parentPopupToClose = gMenu.closest('g-popup[jsname="V68bde"]'); if (parentPopupToClose) { const triggerToClose = parentPopupToClose.querySelector('div[jsname="oYxtQd"][role="button"]'); if (triggerToClose && triggerToClose.getAttribute('aria-expanded') === 'true') triggerToClose.click(); } window.location.href = newUrl; }); const handleEnterKey = (event) => { if (event.key === "Enter" || event.keyCode === 13) { event.preventDefault(); applyButton.click(); }}; widthInput.addEventListener('keypress', handleEnterKey); heightInput.addEventListener('keypress', handleEnterKey); const allExactSizeOptions = getFilterOptionsFromSettings('exactSize'); if (allExactSizeOptions.some(opt => opt.type !== 'imagesize_clear')) { const separatorAfterInput = { type: "separator", textKey: null, isEnabled: true }; gMenu.appendChild(createMenuItem(separatorAfterInput, ''));} allExactSizeOptions.forEach(opt => { gMenu.appendChild(createMenuItem(opt, 'q', false)); }); } else if (categoryKey === 'color') { let textOptionCount = 0; const colorPrefixOptions = optionsForCategory.filter(opt => opt.type !== 'palette' && opt.type !== 'separator'); const paletteOptionDefs = optionsForCategory.filter(opt => opt.type === 'palette'); const separatorOption = optionsForCategory.find(opt => opt.type === 'separator'); colorPrefixOptions.forEach(opt => { const menuItem = createMenuItem(opt, opt.paramNameOverride || 'tbs', false); gMenu.appendChild(menuItem); textOptionCount++; }); if (textOptionCount > 0 && paletteOptionDefs.length > 0 && separatorOption) gMenu.appendChild(createMenuItem(separatorOption, 'tbs', false)); if (paletteOptionDefs.length > 0) { const paletteContainer = document.createElement('div'); paletteContainer.className = 'gite-color-palette-container'; paletteOptionDefs.forEach(paletteOpt => { paletteContainer.appendChild(createMenuItem(paletteOpt, 'tbs', false)); }); gMenu.appendChild(paletteContainer); } } else if (categoryKey === 'region') { // Single column layout for Region const anyRegionTextKey = "option_text_region_any"; const anyRegionOptDef = { id: "gite_region_any_explicit", value: "", paramName: "cr", textKey: anyRegionTextKey, categoryKey: 'region', type: 'region_clear' }; const anyRegionLocalizedText = getLocalizedString(anyRegionTextKey); const anyRegionOptionForMenu = { ...anyRegionOptDef, text: anyRegionLocalizedText, originalTitleEn: getLocalizedString('filter_title_region', 'en') }; gMenu.appendChild(createMenuItem(anyRegionOptionForMenu, anyRegionOptDef.paramName, false)); const separatorDef = { type: "separator", textKey: null, isEnabled: true }; gMenu.appendChild(createMenuItem(separatorDef, '')); if (optionsForCategory.length === 0 && DEBUG_MODE) warn(`No specific region options to display for categoryKey: ${categoryKey} after 'Any Region'. Check settings.`); optionsForCategory.forEach(opt => { if (opt.id === anyRegionOptDef.id || opt.value === "") return; gMenu.appendChild(createMenuItem(opt, opt.paramName || 'cr', false)); }); } else { optionsForCategory.forEach(opt => { let actualParamNameForLink = opt.paramName || paramName; if (opt.tbsValue !== undefined && opt.tbsValue !== null) actualParamNameForLink = 'tbs'; else if (opt.paramNameOverride) actualParamNameForLink = opt.paramNameOverride; gMenu.appendChild(createMenuItem(opt, actualParamNameForLink, false)); }); } if (anchorElementForInsertion && anchorElementForInsertion.parentNode === filtersContainer) { if (anchorElementForInsertion.nextSibling) filtersContainer.insertBefore(newFilterSpan, anchorElementForInsertion.nextSibling); else filtersContainer.appendChild(newFilterSpan); } else filtersContainer.appendChild(newFilterSpan); return { menuElement: gMenu, paramName: paramName, categoryKey: categoryKey, originalTitleKey: categoryTitleKey, triggerButtonTextElement: newTriggerButtonTextElement, triggerButtonWrapper: triggerButtonWrapperElement, filterButtonSpan: newFilterSpan }; } // Hides a native Google filter button based on a characteristic checking function. function hideNativeElementByCharacteristic(characteristicCheckFn, categoryTitleKey, isInitialHideAttempt = false) { const categoryTitle = getLocalizedString(categoryTitleKey); const filtersContainer = document.querySelector(FILTER_BUTTONS_CONTAINER_SELECTOR); if (!filtersContainer) { if (DEBUG_MODE && isInitialHideAttempt) warn(`Filter container not found when trying to hide native '${categoryTitle}'.`); return null; } const nativeButtonSpans = Array.from(filtersContainer.querySelectorAll('span[jscontroller="nabPbb"]')); for (const span of nativeButtonSpans) { const gPopup = span.querySelector('g-popup'); if ((gPopup && gPopup.id && gPopup.id.startsWith('gite-')) || span.style.display === 'none') continue; if (characteristicCheckFn(span)) { const buttonText = span.querySelector(FILTER_BUTTON_TEXT_CLASS)?.textContent.trim() || categoryTitle; if (DEBUG_MODE) log(`Hiding native Google filter button ('${categoryTitle}'): ${buttonText}`); span.style.display = 'none'; return span; } } if (DEBUG_MODE && isInitialHideAttempt) warn(`Native filter button for '${categoryTitle}' not found to hide on initial attempt.`); return null; } function isNativeColorFilter(spanElement) { const gMenu = spanElement.querySelector('g-menu'); return gMenu && gMenu.querySelector('g-menu-item.YEwocc');} function isNativeTypeFilter(spanElement) { const gMenu = spanElement.querySelector('g-menu'); return gMenu && gMenu.querySelector('g-menu-item a[href*="itp:"]');} function isNativeTimeFilter(spanElement) { const gMenu = spanElement.querySelector('g-menu'); if (!gMenu) return false; const customRangeTextEn = getLocalizedString('option_text_time_custom_range', 'en'); return gMenu.querySelector('g-menu-item a[href*="qdr:"]') || (customRangeTextEn && Array.from(gMenu.querySelectorAll('g-menu-item .y0fQ9c span, g-menu-item div[role="menuitemradio"]')).some(s => (s.textContent || s.innerText || "").trim() === customRangeTextEn)); } function isNativeUsageRightsFilter(spanElement) { const gMenu = spanElement.querySelector('g-menu'); return gMenu && gMenu.querySelector('g-menu-item a[href*="sur:"]');} // --- Filter State Update Logic --- // Gets the active filter value from the URL for a specific category. function _getActiveFilterValueForComparison(currentUrl, categoryKey, paramNameForSimpleParams) { let valueForComparison = ""; let isEffectivelyActive = false; if (categoryKey === 'color') { const colorVals = _getActiveColorFilterValues(currentUrl); valueForComparison = colorVals.valueForComparison; } else if (categoryKey === 'exactSize') { const qParam = currentUrl.searchParams.get('q') || ""; valueForComparison = qParam.match(/imagesize:(\d+x\d+)/)?.[1] || ""; } else if (categoryKey === 'site') { const qParam = currentUrl.searchParams.get('q') || ""; valueForComparison = qParam.match(/site:([^\s]+)/)?.[1] || ""; } else if (categoryKey === 'time') { const tbs = currentUrl.searchParams.get('tbs') || ""; valueForComparison = tbs.match(/(cdr:1,cd_min:[^,]+,cd_max:[^,]+)/)?.[0] || tbs.match(/qdr:[^,]+/)?.[0] || ""; } else if (categoryKey === 'region') valueForComparison = currentUrl.searchParams.get(paramNameForSimpleParams) || ""; else if (paramNameForSimpleParams === 'tbs') { const tbs = currentUrl.searchParams.get('tbs') || ""; const firstDef = (GITE_OPTION_DEFINITIONS[categoryKey] || []).find(d => d.tbsValue && d.tbsValue.includes(':')); let tbsPrefix = ""; if (firstDef && firstDef.tbsValue) tbsPrefix = firstDef.tbsValue.split(':')[0] + ':'; else if (DEBUG_MODE && GITE_OPTION_DEFINITIONS[categoryKey]) warn(`_getActiveFilterValueForComparison: No tbsValue with colon found for category '${categoryKey}' to determine its tbsPrefix.`); if (tbsPrefix) valueForComparison = tbs.split(',').find(p => p.startsWith(tbsPrefix)) || ""; } else valueForComparison = currentUrl.searchParams.get(paramNameForSimpleParams) || ""; isEffectivelyActive = !!valueForComparison; return { valueForComparison, isEffectivelyActive }; } // Determines the display text for a filter button based on the active option. function _determineActiveOptionAndDisplayValue(optionsArray, categoryKey, valueForComparison, originalTitleLocalized) { let activeOptionFound = null; let activeFilterDisplayValue = originalTitleLocalized; const nonSeparatorOptions = optionsArray.filter(opt => opt.type !== "separator"); if (categoryKey === 'time' && typeof valueForComparison === 'string' && valueForComparison.startsWith("cdr:1")) activeOptionFound = nonSeparatorOptions.find(opt => opt.type === "custom_date_trigger"); else activeOptionFound = nonSeparatorOptions.find(opt => { let valueFromOptionDef = (opt.categoryKey === 'color' && opt.type === 'palette') ? opt.tbsValue : (opt.tbsValue !== undefined && opt.tbsValue !== null) ? opt.tbsValue : opt.value; return typeof valueFromOptionDef === 'string' && typeof valueForComparison === 'string' && valueFromOptionDef.toLowerCase() === valueForComparison.toLowerCase(); }); if (activeOptionFound) { if (categoryKey === 'site') activeFilterDisplayValue = (activeOptionFound.value !== "") ? (getLocalizedString('text_site_search_active_prefix') + activeOptionFound.text) : activeOptionFound.text; else if (activeOptionFound.type === "custom_date_trigger" && typeof valueForComparison === 'string' && valueForComparison.startsWith("cdr:1,cd_min:")) { const match = valueForComparison.match(/cd_min:([^,]+),cd_max:([^,]+)/); if (match) { const parseDate = (d) => { if (!d) return null; const p = d.split('/'); if (p.length === 3) return new Date(parseInt(p[2]), parseInt(p[0]) - 1, parseInt(p[1])); return null; }; const sD = parseDate(match[1]); const eD = parseDate(match[2]); if (sD && eD && !isNaN(sD.getTime()) && !isNaN(eD.getTime())) { const isZh = CURRENT_LANGUAGE.startsWith('zh'); const dateOpts = { year: 'numeric', month: isZh ? 'long' : 'short', day: 'numeric' }; const locale = isZh ? 'zh-TW' : (CURRENT_LANGUAGE === 'ja' ? 'ja-JP' : (GITE_I18N_STRINGS[CURRENT_LANGUAGE] ? CURRENT_LANGUAGE : 'en-US')); try { activeFilterDisplayValue = `${sD.toLocaleDateString(locale, dateOpts)} – ${eD.toLocaleDateString(locale, dateOpts)}`; } catch (e) { activeFilterDisplayValue = `${match[1]} – ${match[2]}`; warn("Error formatting date range for display:", e); } } else activeFilterDisplayValue = activeOptionFound.text; } else activeFilterDisplayValue = activeOptionFound.text; } else if (categoryKey === 'exactSize' && valueForComparison) activeFilterDisplayValue = (activeOptionFound && activeOptionFound.value === valueForComparison) ? activeOptionFound.text : valueForComparison; else if (categoryKey === 'color' && activeOptionFound && activeOptionFound.type === 'palette' && activeOptionFound.hex) activeFilterDisplayValue = activeOptionFound.text; else { const isActiveAndNotEmpty = (activeOptionFound.value !== undefined && activeOptionFound.value !== "") || (activeOptionFound.tbsValue !== undefined && activeOptionFound.tbsValue !== "") || (categoryKey === 'color' && activeOptionFound.tbsValue === "ic:color"); if (isActiveAndNotEmpty) activeFilterDisplayValue = activeOptionFound.text; else if (optionsArray.find(opt => opt.id === activeOptionFound.id && opt.value === "")) activeFilterDisplayValue = activeOptionFound.text; } } else if (valueForComparison) { if (categoryKey === 'size' && (valueForComparison.startsWith('isz:gt') || valueForComparison.startsWith('islt:')) ) { const m = valueForComparison.match(/(?:isz:gt|islt:)(.*)/i); if (m && m[1]) activeFilterDisplayValue = `${getLocalizedString('text_larger_than_prefix')}${m[1].toUpperCase()}`; } else if (categoryKey === 'exactSize') activeFilterDisplayValue = valueForComparison; else if (categoryKey === 'site') activeFilterDisplayValue = getLocalizedString('text_site_search_active_prefix') + valueForComparison; } return { activeOptionFound, activeFilterDisplayValue }; } // Updates the visual appearance of a filter button. function _updateFilterButtonAppearance(triggerButtonTextElement, triggerButtonWrapper, categoryKey, activeOptionFound, activeFilterDisplayValue, isFilterEffectivelyActive) { triggerButtonTextElement.innerHTML = ''; if (categoryKey === 'color' && activeOptionFound && activeOptionFound.type === 'palette' && activeOptionFound.hex && isFilterEffectivelyActive) { const indicator = document.createElement('span'); indicator.className = 'gite-active-color-indicator'; indicator.style.backgroundColor = activeOptionFound.hex; triggerButtonTextElement.appendChild(indicator); triggerButtonTextElement.appendChild(document.createTextNode(" " + activeFilterDisplayValue)); triggerButtonTextElement.title = activeFilterDisplayValue; } else { triggerButtonTextElement.textContent = activeFilterDisplayValue; triggerButtonTextElement.removeAttribute('title'); } if(triggerButtonWrapper) triggerButtonWrapper.classList.toggle("EISXeb", isFilterEffectivelyActive); } // Updates the 'aria-checked' state and selection highlight for items in a filter menu. function _updateMenuItemsSelectionState(menuElement, nonSeparatorOptions, valueForComparisonFromUrl) { const allMenuItems = menuElement.querySelectorAll(`${MENU_ITEM_SELECTOR}`); const googleSelectedItemClass = "nvELY"; allMenuItems.forEach(item => { const itemFilterValueAttr = item.getAttribute(FILTER_VALUE_ATTR); let thisItemShouldBeSelected = false; if (itemFilterValueAttr !== null && itemFilterValueAttr !== undefined) { const itemTextContentFromDOM = (item.querySelector('span')?.textContent || item.textContent || "").trim(); const isThisTheCustomDateTriggerItem = nonSeparatorOptions.find(opt => opt.text === itemTextContentFromDOM && opt.type === "custom_date_trigger"); if (isThisTheCustomDateTriggerItem && typeof valueForComparisonFromUrl === 'string' && valueForComparisonFromUrl.startsWith("cdr:1")) thisItemShouldBeSelected = true; else if (typeof itemFilterValueAttr === 'string' && typeof valueForComparisonFromUrl === 'string' && itemFilterValueAttr.toLowerCase() === valueForComparisonFromUrl.toLowerCase()) thisItemShouldBeSelected = true; } const swatchAnchor = item.querySelector('.gite-color-swatch'); const clickableInnerElement = item.querySelector('a[role="menuitemradio"], div[role="menuitemradio"] a, a.gite-color-swatch'); if (swatchAnchor) { swatchAnchor.classList.toggle('gite-selected-color', thisItemShouldBeSelected); swatchAnchor.style.borderColor = ''; if (thisItemShouldBeSelected) { const predefinedComplement = item.getAttribute('data-complement-color'); const hexColor = item.getAttribute('data-hex-color'); if (predefinedComplement) swatchAnchor.style.borderColor = predefinedComplement; else if (hexColor) swatchAnchor.style.borderColor = getSimpleContrastColor(hexColor); } } else if (!item.getAttribute('role') || item.getAttribute('role') !== 'separator') item.classList.toggle(googleSelectedItemClass, thisItemShouldBeSelected); if (clickableInnerElement && !item.classList.contains('gite-color-swatch-item') ) { const isCustomDateTriggerByText = nonSeparatorOptions.find(opt => opt.text === (item.querySelector('span')?.textContent || "").trim() && opt.type === "custom_date_trigger"); clickableInnerElement.setAttribute('aria-checked', (!isCustomDateTriggerByText && thisItemShouldBeSelected).toString()); } }); } // Updates the WxH input fields in the "Exact Size" filter menu based on current URL. function _updateExactSizeInputsInMenu(menuElement, valueForComparisonFromUrl, activeOptionFound) { const widthInput = menuElement.querySelector('#gite-imagesize-width-menu'); // Ensure using menu-specific ID const heightInput = menuElement.querySelector('#gite-imagesize-height-menu'); // Ensure using menu-specific ID if (widthInput && heightInput) { if (valueForComparisonFromUrl && valueForComparisonFromUrl.includes('x') && (!activeOptionFound || activeOptionFound.value !== valueForComparisonFromUrl)) { const [w, h] = valueForComparisonFromUrl.split('x'); widthInput.value = w; heightInput.value = h; } else if (!activeOptionFound || activeOptionFound.type === 'imagesize_clear' || valueForComparisonFromUrl === "") { widthInput.value = ''; heightInput.value = ''; } else if (activeOptionFound && activeOptionFound.type === 'imagesize' && activeOptionFound.value.includes('x')) { widthInput.value = ''; heightInput.value = '';} } } // Updates the state of a single GITE filter button and its menu based on the current URL. function updateFilterStates(filterInfo) { const { menuElement, paramName, categoryKey, originalTitleKey, triggerButtonTextElement, triggerButtonWrapper } = filterInfo; if (!menuElement || !triggerButtonTextElement) { warn(`updateFilterStates: Missing menuElement or triggerButtonTextElement for category '${categoryKey}'.`); return; } const originalTitleLocalized = getLocalizedString(originalTitleKey); const optionsArrayForCategory = getFilterOptionsFromSettings(categoryKey); const currentUrl = new URL(window.location.href); let effectiveParamName = paramName; if (categoryKey === 'region' && GITE_OPTION_DEFINITIONS.region[0]?.paramName) { effectiveParamName = GITE_OPTION_DEFINITIONS.region[0].paramName; } else if (categoryKey === 'site' || categoryKey === 'exactSize') { effectiveParamName = 'q'; } const activeFilterData = _getActiveFilterValueForComparison(currentUrl, categoryKey, effectiveParamName); // Defensive check for the returned object structure if (typeof activeFilterData !== 'object' || activeFilterData === null || !activeFilterData.hasOwnProperty('valueForComparison') || !activeFilterData.hasOwnProperty('isEffectivelyActive')) { error(`_getActiveFilterValueForComparison did not return expected object for category '${categoryKey}'. Got:`, activeFilterData); // Provide default values to prevent further errors, though the state might be incorrect activeFilterData.valueForComparison = ""; activeFilterData.isEffectivelyActive = false; } // Now we are sure valueForComparison and isEffectivelyActive exist on activeFilterData const { valueForComparison, isEffectivelyActive } = activeFilterData; const { activeOptionFound, activeFilterDisplayValue } = _determineActiveOptionAndDisplayValue(optionsArrayForCategory, categoryKey, valueForComparison, originalTitleLocalized); _updateFilterButtonAppearance(triggerButtonTextElement, triggerButtonWrapper, categoryKey, activeOptionFound, activeFilterDisplayValue, isEffectivelyActive); // isFilterEffectivelyActive is used here _updateMenuItemsSelectionState(menuElement, optionsArrayForCategory.filter(opt => opt.type !== "separator"), valueForComparison); if (categoryKey === 'exactSize') { _updateExactSizeInputsInMenu(menuElement, valueForComparison, activeOptionFound); } } let isInitializingFilters = false; // Lock for initializeEnhancedFilters // Initializes or re-initializes all GITE enhanced filters on the page. function initializeEnhancedFilters(isCalledAfterManualExpandOrReInit = false) { if (isInitializingFilters && !isCalledAfterManualExpandOrReInit) { if (DEBUG_MODE) log("Filter initialization already in progress. Skipping redundant call."); return; } isInitializingFilters = true; const templateSpanForCloningCheck = document.querySelector(FILTER_SPAN_TEMPLATE_SELECTOR); const nativeSizeMenuExists = !!document.querySelector(MENU_SELECTOR); const isFirstRunAttemptOnThisPageInstance = !enhancedFiltersInitializedThisInstance; // Check if we can skip re-initialization if not forced and already initialized if (enhancedFiltersInitializedThisInstance && !isCalledAfterManualExpandOrReInit && (templateSpanForCloningCheck || nativeSizeMenuExists) && customFilterMenus.length > 0) { if (DEBUG_MODE) log("GITE filters already initialized. Updating states only."); customFilterMenus.forEach(filterInfo => { if (filterInfo.filterButtonSpan && (filterInfo.filterButtonSpan.querySelector('g-popup[id^="gite-"]') || filterInfo.categoryKey === 'size')) { // Ensure text is updated based on current language filterInfo.triggerButtonTextElement.textContent = getLocalizedString(filterInfo.originalTitleKey); filterInfo.triggerButtonTextElement.setAttribute(GITE_LANG_KEY_ATTR, filterInfo.originalTitleKey); } updateFilterStates(filterInfo); }); // Re-hide native elements as they might reappear hideNativeElementByCharacteristic(isNativeColorFilter, 'filter_title_color', false); hideNativeElementByCharacteristic(isNativeTypeFilter, 'filter_title_type', false); hideNativeElementByCharacteristic(isNativeTimeFilter, 'filter_title_time', false); hideNativeElementByCharacteristic(isNativeUsageRightsFilter, 'filter_title_usage_rights', false); const nativeClearBtn = document.querySelector(NATIVE_CLEAR_BUTTON_SELECTOR); if (nativeClearBtn) nativeClearBtn.style.display = 'none'; updateGiteClearButtonVisibility(); isInitializingFilters = false; // Release lock return; } if (DEBUG_MODE) log(`Initializing GITE Filters (v${GITE_SCRIPT_VERSION}). Lang: ${CURRENT_LANGUAGE}. FirstRun: ${isFirstRunAttemptOnThisPageInstance}. Re-init call: ${isCalledAfterManualExpandOrReInit}`); if (!templateSpanForCloningCheck && !nativeSizeMenuExists) { error("CRITICAL: Template span for cloning AND native Size menu not found. GITE cannot initialize filters."); enhancedFiltersInitializedThisInstance = false; isInitializingFilters = false; // Release lock return; } // Clear previous GITE filter UI elements if (customFilterMenus.length > 0) { if (DEBUG_MODE) log("Clearing previous GITE filter UI elements for re-initialization."); customFilterMenus.forEach(filterInfo => { if (filterInfo.filterButtonSpan && filterInfo.filterButtonSpan.parentNode) { // Only remove GITE-created popup spans, or clean GITE content from native size menu if (filterInfo.filterButtonSpan.querySelector('g-popup[id^="gite-"]')) { filterInfo.filterButtonSpan.remove(); } else if (filterInfo.categoryKey === 'size' && filterInfo.filterButtonSpan.querySelector(MENU_SELECTOR)) { // For the native size menu, just remove GITE's additions const menuEl = filterInfo.filterButtonSpan.querySelector(MENU_SELECTOR); if (menuEl) { const giteCols = menuEl.querySelector('div[style*="display: flex"]'); if(giteCols) giteCols.remove(); menuEl.querySelectorAll(`g-menu-item[${CUSTOM_SIZER_ATTR}]`).forEach(it => it.remove()); } // Restore original title if it was a GITE modified native button const trigTxtEl = filterInfo.triggerButtonTextElement; if(trigTxtEl) { trigTxtEl.textContent = getLocalizedString('filter_title_size'); // Assuming 'filter_title_size' is the original key trigTxtEl.setAttribute(GITE_LANG_KEY_ATTR, 'filter_title_size'); } } } }); } customFilterMenus = []; // Reset the array let lastAddedButtonSpan = null; // Handle Size filter (modifies native or hides if disabled) const sizeMenuElement = document.querySelector(MENU_SELECTOR); if (sizeMenuElement) { const sizeGPopup = sizeMenuElement.closest('g-popup[jsname="V68bde"]'); if (sizeGPopup) { lastAddedButtonSpan = sizeGPopup.closest('span[jscontroller="nabPbb"]'); // The span containing the size filter button if (giteSettings.filters.size.enabled) { const sizeTriggerButton = sizeGPopup.querySelector('div[jsname="oYxtQd"]'); // The clickable part if (sizeTriggerButton) { const sizeTriggerTextEl = sizeTriggerButton.querySelector(FILTER_BUTTON_TEXT_CLASS); const sizeTriggerWrapperEl = sizeTriggerButton.querySelector(FILTER_BUTTON_WRAPPER_CLASS); if (sizeTriggerTextEl && sizeTriggerWrapperEl) { sizeTriggerTextEl.textContent = getLocalizedString('filter_title_size'); // Set title in current language sizeTriggerTextEl.setAttribute(GITE_LANG_KEY_ATTR, 'filter_title_size'); addSizesToMenu(sizeMenuElement); // Add GITE's size options customFilterMenus.push({ menuElement: sizeMenuElement, paramName: 'imgsz', categoryKey: 'size', originalTitleKey: 'filter_title_size', triggerButtonTextElement: sizeTriggerTextEl, triggerButtonWrapper: sizeTriggerWrapperEl, filterButtonSpan: lastAddedButtonSpan // Store the span for this native-turned-GITE filter }); if (DEBUG_MODE) log("GITE 'Size' filter initialized by modifying native size menu."); } else if(DEBUG_MODE) warn("Native size trigger text/wrapper not found for GITE Size init."); } else if(DEBUG_MODE) warn("Native size trigger button not found for GITE Size init."); } else { // If GITE's size filter is disabled, hide the native Google Size button if (lastAddedButtonSpan) lastAddedButtonSpan.style.display = 'none'; if (DEBUG_MODE) log("GITE 'Size' filter disabled by settings; hiding native Google Size button."); } } else if(DEBUG_MODE) warn("Native size menu's parent g-popup (jsname='V68bde') not found."); } else if (DEBUG_MODE) { warn("Native 'Size' menu (MENU_SELECTOR) not found. GITE Size filter cannot be initialized by modifying native. Will try to create if enabled."); // If native size menu doesn't exist but GITE size filter is enabled, it might need to be added like other custom filters. // This scenario needs careful handling if GITE_OPTION_DEFINITIONS.size relies on native structure. // For now, we assume if MENU_SELECTOR is gone, we can't reliably add the GITE size filter in its special way. } // Add other GITE filter categories const filterCategoriesToAdd = [ { key: 'exactSize', titleKey: 'filter_title_exact_size', param: 'q', idPrefix: 'gite-exactsize' }, { key: 'aspectRatio', titleKey: 'filter_title_aspect_ratio', param: 'imgar', idPrefix: 'gite-ar' }, { key: 'color', titleKey: 'filter_title_color', param: 'tbs', idPrefix: 'gite-color' }, { key: 'type', titleKey: 'filter_title_type', param: 'tbs', idPrefix: 'gite-type' }, { key: 'time', titleKey: 'filter_title_time', param: 'tbs', idPrefix: 'gite-time' }, { key: 'usageRights', titleKey: 'filter_title_usage_rights', param: 'tbs', idPrefix: 'gite-rights' }, { key: 'fileType', titleKey: 'filter_title_file_type', param: 'as_filetype', idPrefix: 'gite-filetype' }, { key: 'region', titleKey: 'filter_title_region', param: 'cr', idPrefix: 'gite-region' }, { key: 'site', titleKey: 'filter_title_site_search', param: 'q', idPrefix: 'gite-site' } ]; const currentTemplateSpanForCloning = document.querySelector(FILTER_SPAN_TEMPLATE_SELECTOR) || sizeMenuElement?.closest('span[jscontroller="nabPbb"]'); // Fallback to size menu's span if general template not found if (!currentTemplateSpanForCloning) { error("Template span for cloning filters is GONE. Cannot add custom filters."); enhancedFiltersInitializedThisInstance = false; isInitializingFilters = false; // Release lock return; } filterCategoriesToAdd.forEach(catConfig => { // Hide corresponding native Google filters first if (catConfig.key === 'color') hideNativeElementByCharacteristic(isNativeColorFilter, catConfig.titleKey, isFirstRunAttemptOnThisPageInstance); else if (catConfig.key === 'type') hideNativeElementByCharacteristic(isNativeTypeFilter, catConfig.titleKey, isFirstRunAttemptOnThisPageInstance); else if (catConfig.key === 'time') hideNativeElementByCharacteristic(isNativeTimeFilter, catConfig.titleKey, isFirstRunAttemptOnThisPageInstance); else if (catConfig.key === 'usageRights') hideNativeElementByCharacteristic(isNativeUsageRightsFilter, catConfig.titleKey, isFirstRunAttemptOnThisPageInstance); const categorySettings = giteSettings.filters[catConfig.key]; let isEnabledForDisplay = categorySettings && categorySettings.hasOwnProperty('enabled') ? categorySettings.enabled : true; // Default to true if not specified if (isEnabledForDisplay) { const filterMenuInfo = addFilterCategoryMenu(catConfig.key, catConfig.titleKey, catConfig.param, catConfig.idPrefix, lastAddedButtonSpan); if (filterMenuInfo) { customFilterMenus.push(filterMenuInfo); lastAddedButtonSpan = filterMenuInfo.filterButtonSpan; // Update anchor for next insertion } } else if (DEBUG_MODE) { log(`GITE filter '${getLocalizedString(catConfig.titleKey)}' (key: ${catConfig.key}) disabled by settings. Not adding to UI.`); } }); updateGiteClearButtonVisibility(); // Ensure GITE clear button is visible if GITE filters are active customFilterMenus.forEach(filterInfo => updateFilterStates(filterInfo)); // Update all filter states based on URL // Hide Google's native "Clear" button const nativeClearBtn = document.querySelector(NATIVE_CLEAR_BUTTON_SELECTOR); if (nativeClearBtn) nativeClearBtn.style.display = 'none'; enhancedFiltersInitializedThisInstance = true; isInitializingFilters = false; // Release lock setTimeout(updateGiteClearButtonVisibility, FINAL_UI_UPDATE_DELAY_MS); // Final check for clear button visibility } // --- Script Initialization and Event Handlers --- let menuObserver = null; let menuObserverRetries = MAX_OBSERVER_TRIES; // Moved from global function initializeMainScript() { enhancedFiltersInitializedThisInstance = false; // Reset for this instance settingsButtonObserverRetries = MAX_TOOLBAR_BUTTON_OBSERVER_TRIES; // Reset retries menuObserverRetries = MAX_OBSERVER_TRIES; // Reset retries addToolbarSettingsButtonIfNeeded(true); // Add/remove toolbar button based on settings const autoExpandEnabled = giteSettings && giteSettings.general ? giteSettings.general.autoExpandTools : true; const onUiReadyForFilters = (isManuallyExpandedEvent = false) => { if (DEBUG_MODE) log(`UI ready for filters. ManualExpansionEvent: ${isManuallyExpandedEvent}. AutoExpandSetting: ${autoExpandEnabled}`); if (menuObserver) { // Stop the observer once UI is ready menuObserver.disconnect(); menuObserver = null; if (DEBUG_MODE) log("Main menu observer stopped."); } // If auto-expand is off and this was a manual expansion, slight delay might be good if (isManuallyExpandedEvent && autoExpandEnabled === false) { if (DEBUG_MODE) log("Manual expansion detected & auto-expand is OFF. Delaying filter init slightly."); setTimeout(() => { initializeEnhancedFilters(true); }, 150); } else { initializeEnhancedFilters(isManuallyExpandedEvent); } }; if (autoExpandEnabled) { if (DEBUG_MODE) log("Auto-expand is ON. Attempting to click 'Tools' button..."); clickToolsButtonToExpand((success) => { if (DEBUG_MODE) log(success ? "'Tools' bar auto-expanded (or was already)." : "'Tools' bar NOT auto-expanded."); // Regardless of success, setup observer to ensure filters are initialized when elements are truly ready // or if the user manually expands later (if auto-expand failed or was already expanded) setupMenuObserver(onUiReadyForFilters); }); } else { if (DEBUG_MODE) log("Auto-expand is OFF. Setting up menu observer for UI elements or manual expansion."); setupMenuObserver(onUiReadyForFilters); } } function setupMenuObserver(callbackOnUiReady) { if (menuObserver) { menuObserver.disconnect(); menuObserver = null; } // Ensure no lingering observer menuObserverRetries = MAX_OBSERVER_TRIES; // Reset retries for this attempt const templateSpan = document.querySelector(FILTER_SPAN_TEMPLATE_SELECTOR); const nativeSizeMenu = document.querySelector(MENU_SELECTOR); const toolsBtnForCheck = document.querySelector(TOOLS_BUTTON_SELECTOR); const filterBarIsLikelyVisible = (toolsBtnForCheck && toolsBtnForCheck.getAttribute('aria-expanded') === 'true') || !!document.querySelector(FILTER_BUTTONS_CONTAINER_SELECTOR); // If key elements are already present and filter bar seems visible, call back immediately if ((templateSpan || nativeSizeMenu) && filterBarIsLikelyVisible) { if (DEBUG_MODE) log("Key UI elements & filter bar found immediately. Calling UI ready callback."); callbackOnUiReady(false); // Not a manual expansion event in this case return; } if (DEBUG_MODE) log("Key UI elements or filter bar not immediately available. Starting menu MutationObserver."); const observerTargetNode = document.querySelector(OBSERVER_TARGET_SELECTOR_ID) || document.body; if (!observerTargetNode) { error("Cannot start menuObserver: Observer target node not found."); return; } menuObserver = new MutationObserver((mutationsList, obs) => { const currentTemplateSpan = document.querySelector(FILTER_SPAN_TEMPLATE_SELECTOR); const currentNativeSizeMenu = document.querySelector(MENU_SELECTOR); const currentToolsButton = document.querySelector(TOOLS_BUTTON_SELECTOR); const currentToolsExpanded = currentToolsButton ? currentToolsButton.getAttribute('aria-expanded') === 'true' : false; const currentFilterButtonsContainer = document.querySelector(FILTER_BUTTONS_CONTAINER_SELECTOR); const currentFilterBarVisible = currentToolsExpanded || !!currentFilterButtonsContainer; if (DEBUG_MODE && (menuObserverRetries < 5 || mutationsList.length > 0)) { // Log more frequently near timeout or on mutations log(`Menu Observer Check (Retries left: ${menuObserverRetries}): TemplateSpan: ${!!currentTemplateSpan}, NativeSizeMenu: ${!!currentNativeSizeMenu}, ToolsExpanded: ${currentToolsExpanded}, FilterBarVisible: ${currentFilterBarVisible}`); } if ((currentTemplateSpan || currentNativeSizeMenu) && currentFilterBarVisible) { if (DEBUG_MODE) log(`Menu observer: Key UI elements & filter bar now available.`); obs.disconnect(); menuObserver = null; callbackOnUiReady(true); // Assume it might be due to a manual expansion if auto-expand didn't catch it earlier } else { menuObserverRetries--; if (menuObserverRetries <= 0) { obs.disconnect(); menuObserver = null; warn("GITE menu observer timed out."); } else if (DEBUG_MODE && menuObserverRetries % 5 === 0) { log(`Menu observer: Conditions not yet met. Retries left: ${menuObserverRetries}`); } } }); menuObserver.observe(observerTargetNode, { childList: true, subtree: true, attributes: true, attributeFilter: ['aria-expanded', 'style', 'class'] }); } window.addEventListener('popstate', function(event) { if (isImageUrl()) { if (DEBUG_MODE) log("Popstate event on an image URL. Re-evaluating GITE UI."); closeDatePicker(); // Close date picker if open setTimeout(() => { detectAndApplyThemeClass(); // Re-apply theme in case it changed enhancedFiltersInitializedThisInstance = false; // Mark for re-initialization // Stop any active observers if(menuObserver) { menuObserver.disconnect(); menuObserver = null; } if(settingsButtonObserver) { settingsButtonObserver.disconnect(); settingsButtonObserver = null; } // Remove GITE-added UI elements before re-initializing const oldClearBtn = document.getElementById(GITE_CLEAR_BUTTON_ID); if(oldClearBtn) oldClearBtn.remove(); const oldSettingsBtn = document.getElementById(GITE_SETTINGS_BUTTON_ID); if(oldSettingsBtn) oldSettingsBtn.remove(); if (customFilterMenus.length > 0) { customFilterMenus.forEach(filterInfo => { if (filterInfo.filterButtonSpan && filterInfo.filterButtonSpan.parentNode) { // Only remove GITE-created popup spans if (filterInfo.filterButtonSpan.querySelector('g-popup[id^="gite-"]')) { filterInfo.filterButtonSpan.remove(); } // For size, specific cleaning might be needed if it was a modified native button } }); } customFilterMenus = []; // Reset custom menus array if (DEBUG_MODE) log("Popstate: Re-initializing GITE main script."); initializeMainScript(); // Re-run the main initialization logic }, POPSTATE_UPDATE_DELAY_MS); } else { if (DEBUG_MODE) log("Popstate event: Navigated away from image URL. Cleaning up GITE UI."); closeDatePicker(); enhancedFiltersInitializedThisInstance = false; if(menuObserver) { menuObserver.disconnect(); menuObserver = null; } if(settingsButtonObserver) { settingsButtonObserver.disconnect(); settingsButtonObserver = null; } customFilterMenus = []; const giteClearBtn = document.getElementById(GITE_CLEAR_BUTTON_ID); if(giteClearBtn) giteClearBtn.remove(); const giteSettingsBtn = document.getElementById(GITE_SETTINGS_BUTTON_ID); if(giteSettingsBtn) giteSettingsBtn.remove(); } }); function handleResetAllSettingsConfirmed() { if (confirm(getLocalizedString('alert_confirm_reset_all_settings'))) { giteSettings = JSON.parse(JSON.stringify(GITE_DEFAULT_SETTINGS)); if (typeof GM_setValue === 'function') { try { GM_setValue(GITE_SETTINGS_GM_KEY, JSON.stringify(giteSettings)); alert(getLocalizedString('alert_settings_reset_to_default') + "\n" + getLocalizedString('gm_please_reload')); window.location.reload(); } catch (e) { error("Error saving reset GITE settings:", e); alert(getLocalizedString('alert_generic_error_saving')); } } else { alert(getLocalizedString('alert_gm_setvalue_unavailable')); } } } function setupGMMenus() { if (typeof GM_registerMenuCommand !== 'function') return; // Titles are fetched using getLocalizedString, so they will reflect current language try { GM_registerMenuCommand(getLocalizedString('gm_menu_gite_settings'), openSettingsPanel, "g"); GM_registerMenuCommand(getLocalizedString('gm_menu_reset_all_gite_settings'), handleResetAllSettingsConfirmed, "r"); if (DEBUG_MODE) log("Greasemonkey menu commands registered/updated."); } catch (e) { warn("Error registering Greasemonkey menu commands:", e); } } (() => { // Script Entry Point IIFE let storedSettingsJson = null; let parsedSettings = null; if (typeof GM_getValue === 'function') { try { storedSettingsJson = GM_getValue(GITE_SETTINGS_GM_KEY); if (storedSettingsJson) parsedSettings = JSON.parse(storedSettingsJson); } catch (e) { error("Error parsing stored GITE settings JSON:", e); parsedSettings = null; // Ensure settings are reset if parsing fails } } if (parsedSettings && parsedSettings.general && parsedSettings.filters) { giteSettings = parsedSettings; // Ensure all default general settings exist for (const key in GITE_DEFAULT_SETTINGS.general) { if (!giteSettings.general.hasOwnProperty(key)) { giteSettings.general[key] = JSON.parse(JSON.stringify(GITE_DEFAULT_SETTINGS.general[key])); if (DEBUG_MODE) log(`Settings Load: Added missing general setting default: general.${key}`); } } // Ensure all default filter categories and their basic structure exist for (const key in GITE_DEFAULT_SETTINGS.filters) { if (!giteSettings.filters.hasOwnProperty(key)) { giteSettings.filters[key] = JSON.parse(JSON.stringify(GITE_DEFAULT_SETTINGS.filters[key])); if (DEBUG_MODE) log(`Settings Load: Added missing filter category default: filters.${key}`); } else { const defaultCat = GITE_DEFAULT_SETTINGS.filters[key]; const storedCat = giteSettings.filters[key]; // Ensure 'enabled' field exists for categories that have it in defaults if (defaultCat.hasOwnProperty('enabled') && !storedCat.hasOwnProperty('enabled')) { storedCat.enabled = defaultCat.enabled; if (DEBUG_MODE) log(`Settings Load: Added missing 'enabled' field to filters.${key}`); } // Ensure 'options' array exists for categories that have it (and ensure items have textKey if missing) if (defaultCat.options && (!storedCat.options || !Array.isArray(storedCat.options))) { storedCat.options = JSON.parse(JSON.stringify(defaultCat.options)); if (DEBUG_MODE) log(`Settings Load: Replaced/Added missing 'options' array to filters.${key}`); } else if (defaultCat.options && storedCat.options) { // Ensure individual options have textKey (for backward compatibility if settings were saved before textKey was added to definition) storedCat.options.forEach(opt => { const defOpt = defaultCat.options.find(d => d.id === opt.id); if (defOpt && defOpt.textKey && !opt.textKey) { opt.textKey = defOpt.textKey; if (DEBUG_MODE) log(`Settings Load: Added missing 'textKey' to option '${opt.id}' in filters.${key}`); } }); } // Special handling for exactSize (predefinedOptions and userDefinedOptions) if (key === 'exactSize') { if (defaultCat.predefinedOptions && (!storedCat.predefinedOptions || !Array.isArray(storedCat.predefinedOptions))) { storedCat.predefinedOptions = JSON.parse(JSON.stringify(defaultCat.predefinedOptions)); if (DEBUG_MODE) log(`Settings Load: Replaced/Added missing 'predefinedOptions' to filters.exactSize`); } else if (defaultCat.predefinedOptions && storedCat.predefinedOptions){ storedCat.predefinedOptions.forEach(opt => { const defOpt = defaultCat.predefinedOptions.find(d => d.id === opt.id); if (defOpt && defOpt.textKey && !opt.textKey) { opt.textKey = defOpt.textKey; } }); } if (defaultCat.userDefinedOptions && !storedCat.userDefinedOptions) { // userDefinedOptions is an array storedCat.userDefinedOptions = JSON.parse(JSON.stringify(defaultCat.userDefinedOptions)); // Should be empty by default if (DEBUG_MODE) log(`Settings Load: Added missing 'userDefinedOptions' to filters.exactSize`); } } // Special handling for site (userDefinedOptions) if (key === 'site' && defaultCat.userDefinedOptions && !storedCat.userDefinedOptions) { storedCat.userDefinedOptions = JSON.parse(JSON.stringify(defaultCat.userDefinedOptions)); if (DEBUG_MODE) log(`Settings Load: Added missing 'userDefinedOptions' to filters.site`); } } } if (DEBUG_MODE) log("Loaded GITE settings from storage and ensured completeness with defaults."); } else { if (DEBUG_MODE) { if (storedSettingsJson && !parsedSettings) log("Stored settings were present but failed to parse. Initializing with defaults."); else if (storedSettingsJson && parsedSettings && (!parsedSettings.general || !parsedSettings.filters)) log("Stored settings format was invalid. Initializing with defaults."); else log("No GITE settings found in storage. Initializing with defaults."); } giteSettings = JSON.parse(JSON.stringify(GITE_DEFAULT_SETTINGS)); if (typeof GM_setValue === 'function') { try { GM_setValue(GITE_SETTINGS_GM_KEY, JSON.stringify(giteSettings)); if (DEBUG_MODE) log("Initialized and saved GITE settings with defaults."); } catch (e) { error("Error saving initial default GITE settings:", e); } } } initializeCurrentLanguage(); // Initialize language based on settings/browser detectAndApplyThemeClass(); // Apply theme early injectCustomHoverStyles(); // Inject CSS setupGMMenus(); // Setup Greasemonkey menus with correct language if (isImageUrl()) { if (DEBUG_MODE) log(`GITE Initializing main logic (v${GITE_SCRIPT_VERSION}). Language: ${CURRENT_LANGUAGE}.`); initializeMainScript(); } else { if (DEBUG_MODE) log(`Not a Google Images page. GITE (v${GITE_SCRIPT_VERSION}) not initializing main filter logic. Language: ${CURRENT_LANGUAGE}`); } })(); })();