// ==UserScript==
// @name Google Search Custom Sidebar
// @name:zh-TW Google 搜尋自訂側邊欄
// @name:ja Google検索カスタムサイドバー
// @namespace https://greasyfork.org/en/users/1467948-stonedkhajiit
// @version 0.0.1
// @description Customizable Google Search sidebar: quick filters (lang, time, filetype, country, date), site search, Verbatim & Personalization tools.
// @description:zh-TW Google 搜尋自訂側邊欄:快速篩選(語言、時間、檔案類型、國家、日期)、站內搜尋、一字不差與個人化工具。
// @description:ja Google検索カスタムサイドバー:高速フィルター(言語,期間,ファイル形式,国,日付)、サイト検索、完全一致検索とパーソナライズツール。
// @match https://www.google.com/search*
// @include /^https:\/\/(?:ipv4|ipv6|www)\.google\.(?:[a-z\.]+)\/search\?(?:.+&)?q=[^&]+(?:&.+)?$/
// @exclude /^https:\/\/(?:ipv4|ipv6|www)\.google\.(?:[a-z\.]+)\/search\?(?:.+&)?tbm=isch(?:&.+)?$/
// @exclude /^https:\/\/(?:ipv4|ipv6|www)\.google\.(?:[a-z\.]+)\/search\?(?:.+&)?udm=2(?:&.+)?$/
// @exclude /^https:\/\/(?:ipv4|ipv6|www)\.google\.(?:[a-z\.]+)\/search\?(?:.+&)?tbm=shop(?:&.+)?$/
// @exclude /^https:\/\/(?:ipv4|ipv6|www)\.google\.(?:[a-z\.]+)\/search\?(?:.+&)?udm=28(?:&.+)?$/
// @exclude /^https:\/\/(?:ipv4|ipv6|www)\.google\.(?:[a-z\.]+)\/search\?(?:.+&)?tbm=bks(?:&.+)?$/
// @exclude /^https:\/\/(?:ipv4|ipv6|www)\.google\.(?:[a-z\.]+)\/search\?(?:.+&)?tbm=flm(?:&.+)?$/
// @exclude /^https:\/\/(?:ipv4|ipv6|www)\.google\.(?:[a-z\.]+)\/search\?(?:.+&)?tbm=fin(?:&.+)?$/
// @exclude /^https:\/\/(?:ipv4|ipv6|www)\.google\.(?:[a-z\.]+)\/search\?(?:.+&)?tbm=lcl(?:&.+)?$/
// @icon https://www.google.com/favicon.ico
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_deleteValue
// @run-at document-idle
// @author StonedKhajiit
// @license MIT
// @require https://update.greasyfork.org/scripts/535624/1586990/Google%20Search%20Custom%20Sidebar%20-%20i18n.js
// @require https://update.greasyfork.org/scripts/535625/1586991/Google%20Search%20Custom%20Sidebar%20-%20Styles.js
// ==/UserScript==
(function() {
'use strict';
// --- Constants and Configuration ---
const SCRIPT_INTERNAL_NAME = 'GoogleSearchCustomSidebar';
const SCRIPT_VERSION = '0.0.1';
const LOG_PREFIX = `[${SCRIPT_INTERNAL_NAME} v${SCRIPT_VERSION}]`;
const DEFAULT_SECTION_ORDER = [ 'sidebar-section-language', 'sidebar-section-time', 'sidebar-section-filetype', 'sidebar-section-country', 'sidebar-section-date-range', 'sidebar-section-site-search', 'sidebar-section-tools' ];
const defaultSettings = {
sidebarPosition: { left: 0, top: 80 },
sectionStates: {},
theme: 'system',
hoverMode: false,
idleOpacity: 0.8,
sidebarWidth: 135,
fontSize: 12.5,
headerIconSize: 16,
verticalSpacingMultiplier: 0.5,
interfaceLanguage: 'auto',
visibleSections: {
'sidebar-section-language': true, 'sidebar-section-time': true, 'sidebar-section-filetype': true,
'sidebar-section-country': true, 'sidebar-section-date-range': true, 'sidebar-section-site-search': true,
'sidebar-section-tools': true
},
sectionDisplayMode: 'remember',
accordionMode: false,
resetButtonLocation: 'topBlock',
verbatimButtonLocation: 'header',
advancedSearchLinkLocation: 'header',
personalizationButtonLocation: 'tools',
countryDisplayMode: 'iconAndText',
customLanguages: [], customTimeRanges: [], customFiletypes: [], customCountries: [],
favoriteSites: [
{ text: 'Wikipedia (EN)', url: 'en.wikipedia.org' }, { text: 'Stack Overflow', url: 'stackoverflow.com' },
{ text: 'GitHub', url: 'github.com' }, { text: 'Greasy Fork', url: 'greasyfork.org' },
{ text: 'Bluesky', url: 'bsky.app' }, { text: 'X.com', url: 'x.com' },
{ text: 'Reddit', url: 'reddit.com' }, { text: 'IMDb', url: 'imdb.com' },
{ text: 'Steam', url: 'store.steampowered.com' }, { text: 'Last.fm', url: 'last.fm' },
{ text: 'Metacritic', url: 'metacritic.com' }, { text: 'TMDb', url: 'themoviedb.org' },
{ text: 'Hacker News', url: 'news.ycombinator.com' }
],
sidebarCollapsed: false,
draggableHandleEnabled: true,
enabledPredefinedOptions: {
language: ['lang_en'], country: ['countryUS'],
time: ['d', 'w', 'm', 'y', 'h'],
filetype: ['pdf', 'docx', 'doc', 'xlsx', 'xls', 'pptx', 'ppt']
},
sidebarSectionOrder: [...DEFAULT_SECTION_ORDER]
};
let sidebar = null, systemThemeMediaQuery = null;
const MIN_SIDEBAR_TOP_POSITION = 5;
let debouncedSaveSettings;
let globalMessageTimeout = null;
const IDS = { /* ... (same as before) ... */
SIDEBAR: 'customizable-search-sidebar', SETTINGS_OVERLAY: 'settings-overlay', SETTINGS_WINDOW: 'settings-window',
COLLAPSE_BUTTON: 'sidebar-collapse-button', SETTINGS_BUTTON: 'open-settings-button',
TOOL_RESET_BUTTON: 'tool-reset-button', TOOL_VERBATIM: 'tool-verbatim', TOOL_PERSONALIZE: 'tool-personalize-search',
FIXED_TOP_BUTTONS: 'sidebar-fixed-top-buttons',
SETTINGS_MESSAGE_BAR: 'gscs-settings-message-bar',
SETTING_WIDTH: 'setting-sidebar-width', SETTING_FONT_SIZE: 'setting-font-size', SETTING_HEADER_ICON_SIZE: 'setting-header-icon-size',
SETTING_VERTICAL_SPACING: 'setting-vertical-spacing', SETTING_INTERFACE_LANGUAGE: 'setting-interface-language',
SETTING_SECTION_MODE: 'setting-section-display-mode', SETTING_ACCORDION: 'setting-accordion-mode',
SETTING_DRAGGABLE: 'setting-draggable-handle', SETTING_RESET_LOCATION: 'setting-reset-button-location',
SETTING_VERBATIM_LOCATION: 'setting-verbatim-button-location', SETTING_ADV_SEARCH_LOCATION: 'setting-adv-search-link-location',
SETTING_PERSONALIZE_LOCATION: 'setting-personalize-button-location',
SETTING_COUNTRY_DISPLAY_MODE: 'setting-country-display-mode', SETTING_THEME: 'setting-theme',
SETTING_HOVER: 'setting-hover-mode', SETTING_OPACITY: 'setting-idle-opacity',
TAB_PANE_GENERAL: 'tab-pane-general', TAB_PANE_APPEARANCE: 'tab-pane-appearance', TAB_PANE_FEATURES: 'tab-pane-features', TAB_PANE_CUSTOM: 'tab-pane-custom',
SITES_LIST: 'custom-sites-list', LANG_LIST: 'custom-languages-list', TIME_LIST: 'custom-time-ranges-list',
FT_LIST: 'custom-filetypes-list', COUNTRIES_LIST: 'custom-countries-list',
NEW_SITE_NAME: 'new-site-name', NEW_SITE_URL: 'new-site-url', ADD_SITE_BTN: 'add-site-button',
NEW_LANG_TEXT: 'new-lang-text', NEW_LANG_VALUE: 'new-lang-value', ADD_LANG_BTN: 'add-lang-button',
NEW_TIME_TEXT: 'new-timerange-text', NEW_TIME_VALUE: 'new-timerange-value', ADD_TIME_BTN: 'add-timerange-button',
NEW_FT_TEXT: 'new-ft-text', NEW_FT_VALUE: 'new-ft-value', ADD_FT_BTN: 'add-ft-button',
NEW_COUNTRY_TEXT: 'new-country-text', NEW_COUNTRY_VALUE: 'new-country-value', ADD_COUNTRY_BTN: 'add-country-button',
DATE_MIN: 'date-min', DATE_MAX: 'date-max', DATE_RANGE_ERROR_MSG: 'date-range-error-msg',
SIDEBAR_SECTION_ORDER_LIST: 'sidebar-section-order-list',
NOTIFICATION_CONTAINER: 'gscs-notification-container'
};
const CSS = { /* ... (same as before) ... */
SIDEBAR_COLLAPSED: 'sidebar-collapsed', SIDEBAR_HEADER: 'sidebar-header', SIDEBAR_CONTENT_WRAPPER: 'sidebar-content-wrapper',
DRAG_HANDLE: 'sidebar-drag-handle', SETTINGS_BUTTON: 'sidebar-settings-button', HEADER_BUTTON: 'sidebar-header-button',
SIDEBAR_SECTION: 'sidebar-section', FIXED_TOP_BUTTON_ITEM: 'fixed-top-button-item', SECTION_TITLE: 'section-title',
SECTION_CONTENT: 'section-content', COLLAPSED: 'collapsed', FILTER_OPTION: 'filter-option', SELECTED: 'selected',
DATE_INPUT_LABEL: 'date-input-label', DATE_INPUT: 'date-input', TOOL_BUTTON: 'tool-button', ACTIVE: 'active',
CUSTOM_LIST: 'custom-list', ITEM_CONTROLS: 'item-controls', EDIT_CUSTOM_ITEM: 'edit-custom-item',
DELETE_CUSTOM_ITEM: 'delete-custom-item', CUSTOM_LIST_INPUT_GROUP: 'custom-list-input-group',
ADD_CUSTOM_BUTTON: 'add-custom-button', SETTINGS_HEADER: 'settings-header', SETTINGS_CLOSE_BTN: 'settings-close-button',
SETTINGS_TABS: 'settings-tabs', TAB_BUTTON: 'tab-button', SETTINGS_TAB_CONTENT: 'settings-tab-content',
TAB_PANE: 'tab-pane', SETTING_ITEM: 'setting-item', INLINE_LABEL: 'inline', SETTINGS_FOOTER: 'settings-footer',
SAVE_BUTTON: 'save-button', CANCEL_BUTTON: 'cancel-button', RESET_BUTTON: 'reset-button',
LIGHT_THEME: 'light-theme', DARK_THEME: 'dark-theme',
SIMPLE_ITEM: 'simple', RANGE_VALUE: 'range-value', RANGE_HINT: 'setting-range-hint', SECTION_ORDER_LIST: 'section-order-list',
INPUT_ERROR_MESSAGE: 'input-error-message', ERROR_VISIBLE: 'error-visible', INPUT_HAS_ERROR: 'input-has-error',
DATE_RANGE_ERROR_MSG: 'date-range-error-message',
MESSAGE_BAR: 'gscs-message-bar', MSG_INFO: 'gscs-msg-info', MSG_SUCCESS: 'gscs-msg-success',
MSG_WARNING: 'gscs-msg-warning', MSG_ERROR: 'gscs-msg-error', MANAGE_CUSTOM_BUTTON: 'manage-custom-button',
NOTIFICATION: 'gscs-notification',
NTF_INFO: 'gscs-ntf-info', NTF_SUCCESS: 'gscs-ntf-success',
NTF_WARNING: 'gscs-ntf-warning', NTF_ERROR: 'gscs-ntf-error'
};
const DATA_ATTR = { /* ... (same as before) ... */
FILTER_TYPE: 'filterType', FILTER_VALUE: 'filterValue', SITE_URL: 'siteUrl', SECTION_ID: 'sectionId',
LIST_ID: 'listId', INDEX: 'index', LISTENER_ATTACHED: 'listenerAttached', TAB: 'tab', MANAGE_TYPE: 'managetype'
};
const STORAGE_KEY = 'googleSearchCustomSidebarSettings_v1';
const SVG_ICONS = { /* ... (same as before, including single personalization icon) ... */
chevronLeft: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>`,
chevronRight: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>`,
settings: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06-.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>`,
reset: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>`,
verbatim: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><g transform="translate(-4.3875 -3.2375) scale(1.15)"><path d="M6 17.5c0 1.5 1.5 2.5 3 2.5h1.5c1.5 0 3-1 3-2.5V9c0-1.5-1.5-2.5-3-2.5H9C7.5 6.5 6 7.5 6 9v8.5z"/><path d="M15 17.5c0 1.5 1.5 2.5 3 2.5h1.5c1.5 0 3-1 3-2.5V9c0-1.5-1.5-2.5-3-2.5H18c-1.5 0-3 1-3 2.5v8.5z"/></g></svg>`,
magnifyingGlass: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>`,
close: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`,
edit: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>`,
delete: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>`,
add: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>`,
update: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>`,
personalization: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>`,
};
const PREDEFINED_OPTIONS = { /* ... (same as before) ... */
language: [ { textKey: 'predefined_lang_en', value: 'lang_en' }, { textKey: 'predefined_lang_ja', value: 'lang_ja' }, { textKey: 'predefined_lang_ko', value: 'lang_ko' }, { textKey: 'predefined_lang_fr', value: 'lang_fr' }, { textKey: 'predefined_lang_de', value: 'lang_de' }, { textKey: 'predefined_lang_es', value: 'lang_es' }, { textKey: 'predefined_lang_it', value: 'lang_it' }, { textKey: 'predefined_lang_pt', value: 'lang_pt' }, { textKey: 'predefined_lang_ru', value: 'lang_ru' }, { textKey: 'predefined_lang_ar', value: 'lang_ar' }, { textKey: 'predefined_lang_hi', value: 'lang_hi' }, { textKey: 'predefined_lang_nl', value: 'lang_nl' }, { textKey: 'predefined_lang_tr', value: 'lang_tr' }, { textKey: 'predefined_lang_vi', value: 'lang_vi' }, { textKey: 'predefined_lang_th', value: 'lang_th' }, { textKey: 'predefined_lang_id', value: 'lang_id' }, { textKey: 'predefined_lang_zh_tw', value: 'lang_zh-TW' }, { textKey: 'predefined_lang_zh_cn', value: 'lang_zh-CN' }, { textKey: 'predefined_lang_zh_all', value: 'lang_zh-TW|lang_zh-CN' }, ],
country: [ { textKey: 'predefined_country_us', value: 'countryUS' }, { textKey: 'predefined_country_gb', value: 'countryGB' }, { textKey: 'predefined_country_ca', value: 'countryCA' }, { textKey: 'predefined_country_au', value: 'countryAU' }, { textKey: 'predefined_country_de', value: 'countryDE' }, { textKey: 'predefined_country_fr', value: 'countryFR' }, { textKey: 'predefined_country_jp', value: 'countryJP' }, { textKey: 'predefined_country_kr', value: 'countryKR' }, { textKey: 'predefined_country_cn', value: 'countryCN' }, { textKey: 'predefined_country_in', value: 'countryIN' }, { textKey: 'predefined_country_br', value: 'countryBR' }, { textKey: 'predefined_country_mx', value: 'countryMX' }, { textKey: 'predefined_country_es', value: 'countryES' }, { textKey: 'predefined_country_it', value: 'countryIT' }, { textKey: 'predefined_country_ru', value: 'countryRU' }, { textKey: 'predefined_country_nl', value: 'countryNL' }, { textKey: 'predefined_country_sg', value: 'countrySG' }, { textKey: 'predefined_country_hk', value: 'countryHK' }, { textKey: 'predefined_country_tw', value: 'countryTW' }, { textKey: 'predefined_country_my', value: 'countryMY' }, { textKey: 'predefined_country_vn', value: 'countryVN' }, { textKey: 'predefined_country_ph', value: 'countryPH' }, { textKey: 'predefined_country_th', value: 'countryTH' }, { textKey: 'predefined_country_za', value: 'countryZA' }, { textKey: 'predefined_country_tr', value: 'countryTR' }, ],
time: [ { textKey: 'predefined_time_h', value: 'h' }, { textKey: 'predefined_time_h2', value: 'h2' }, { textKey: 'predefined_time_h6', value: 'h6' }, { textKey: 'predefined_time_h12', value: 'h12' }, { textKey: 'predefined_time_d', value: 'd' }, { textKey: 'predefined_time_d2', value: 'd2' }, { textKey: 'predefined_time_d3', value: 'd3' }, { textKey: 'predefined_time_w', value: 'w' }, { textKey: 'predefined_time_m', value: 'm' }, { textKey: 'predefined_time_y', value: 'y' }, ],
filetype: [ { textKey: 'predefined_filetype_pdf', value: 'pdf' }, { textKey: 'predefined_filetype_docx', value: 'docx' }, { textKey: 'predefined_filetype_doc', value: 'doc' }, { textKey: 'predefined_filetype_xlsx', value: 'xlsx' }, { textKey: 'predefined_filetype_xls', value: 'xls' }, { textKey: 'predefined_filetype_pptx', value: 'pptx' }, { textKey: 'predefined_filetype_ppt', value: 'ppt' }, { textKey: 'predefined_filetype_txt', value: 'txt' }, { textKey: 'predefined_filetype_rtf', value: 'rtf' }, { textKey: 'predefined_filetype_html', value: 'html' }, { textKey: 'predefined_filetype_htm', value: 'htm' }, { textKey: 'predefined_filetype_xml', value: 'xml' }, { textKey: 'predefined_filetype_jpg', value: 'jpg' }, { textKey: 'predefined_filetype_png', value: 'png' }, { textKey: 'predefined_filetype_gif', value: 'gif' }, { textKey: 'predefined_filetype_svg', value: 'svg' }, { textKey: 'predefined_filetype_bmp', value: 'bmp' }, { textKey: 'predefined_filetype_js', value: 'js' }, { textKey: 'predefined_filetype_css', value: 'css' }, { textKey: 'predefined_filetype_py', value: 'py' }, { textKey: 'predefined_filetype_java', value: 'java' }, { textKey: 'predefined_filetype_cpp', value: 'cpp' }, { textKey: 'predefined_filetype_cs', value: 'cs' }, { textKey: 'predefined_filetype_kml', value: 'kml'}, { textKey: 'predefined_filetype_kmz', value: 'kmz'}, ]
};
const ALL_SECTION_DEFINITIONS = [ /* ... (same as before) ... */ { id: 'sidebar-section-language', type: 'filter', titleKey: 'section_language', scriptDefined: [{textKey:'filter_any_language',v:''}], param: 'lr', predefinedOptionsKey: 'language', customItemsKey: 'customLanguages' }, { id: 'sidebar-section-time', type: 'filter', titleKey: 'section_time', scriptDefined: [{textKey:'filter_any_time',v:''}], param: 'qdr', predefinedOptionsKey: 'time', customItemsKey: 'customTimeRanges' }, { id: 'sidebar-section-filetype', type: 'filter', titleKey: 'section_filetype', scriptDefined: [{ textKey: 'filter_any_format', v: '' }], param: 'filetype', predefinedOptionsKey: 'filetype', customItemsKey: 'customFiletypes' }, { id: 'sidebar-section-country', type: 'filter', titleKey: 'section_country', scriptDefined: [{textKey:'filter_any_country',v:''}], param: 'cr', predefinedOptionsKey: 'country', customItemsKey: 'customCountries' }, { id: 'sidebar-section-date-range', type: 'date', titleKey: 'section_date_range' }, { id: 'sidebar-section-site-search', type: 'site', titleKey: 'section_site_search' }, { id: 'sidebar-section-tools', type: 'tools', titleKey: 'section_tools' } ];
const LocalizationService = (function() { /* ... (LocalizationService with updated 'en' strings, as provided in the previous response) ... */
const builtInTranslations = {
'en': {
scriptName: 'Google Search Custom Sidebar', settingsTitle: 'Google Search Custom Sidebar Settings', manageOptionsTitle: 'Manage Options', manageSitesTitle: 'Manage Favorite Sites', manageLanguagesTitle: 'Manage Custom Languages', manageCountriesTitle: 'Manage Countries/Regions', manageTimeRangesTitle: 'Manage Time Ranges', manageFileTypesTitle: 'Manage File Types', section_language: 'Language', section_time: 'Time', section_filetype: 'File Type', section_country: 'Country/Region', section_date_range: 'Date Range', section_site_search: 'Site Search', section_tools: 'Tools', filter_any_language: 'Any Language', filter_any_time: 'Any Time', filter_any_format: 'Any Format', filter_any_country: 'Any Country/Region', filter_clear_site_search: 'Clear Site Search', filter_clear_tooltip_suffix: '(Clear)', predefined_lang_zh_tw: 'Traditional Chinese', predefined_lang_zh_cn: 'Simplified Chinese', predefined_lang_zh_all: 'All Chinese', predefined_lang_en: 'English', predefined_lang_ja: 'Japanese', predefined_lang_ko: 'Korean', predefined_lang_fr: 'French', predefined_lang_de: 'German', predefined_lang_es: 'Spanish', predefined_lang_it: 'Italian', predefined_lang_pt: 'Portuguese', predefined_lang_ru: 'Russian', predefined_lang_ar: 'Arabic', predefined_lang_hi: 'Hindi', predefined_lang_nl: 'Dutch', predefined_lang_tr: 'Turkish', predefined_lang_vi: 'Vietnamese', predefined_lang_th: 'Thai', predefined_lang_id: 'Indonesian', predefined_country_tw: '🇹🇼 Taiwan', predefined_country_jp: '🇯🇵 Japan', predefined_country_kr: '🇰🇷 South Korea', predefined_country_cn: '🇨🇳 China', predefined_country_hk: '🇭🇰 Hong Kong', predefined_country_sg: '🇸🇬 Singapore', predefined_country_my: '🇲🇾 Malaysia', predefined_country_vn: '🇻🇳 Vietnam', predefined_country_ph: '🇵🇭 Philippines', predefined_country_th: '🇹🇭 Thailand', predefined_country_us: '🇺🇸 United States', predefined_country_ca: '🇨🇦 Canada', predefined_country_br: '🇧🇷 Brazil', predefined_country_mx: '🇲🇽 Mexico', predefined_country_gb: '🇬🇧 United Kingdom', predefined_country_de: '🇩🇪 Germany', predefined_country_fr: '🇫🇷 France', predefined_country_it: '🇮🇹 Italy', predefined_country_es: '🇪🇸 Spain', predefined_country_ru: '🇷🇺 Russia', predefined_country_nl: '🇳🇱 Netherlands', predefined_country_au: '🇦🇺 Australia', predefined_country_in: '🇮🇳 India', predefined_country_za: '🇿🇦 South Africa', predefined_country_tr: '🇹🇷 Turkey', predefined_time_h: 'Past hour', predefined_time_h2: 'Past 2 hours', predefined_time_h6: 'Past 6 hours', predefined_time_h12: 'Past 12 hours', predefined_time_d: 'Past 24 hours', predefined_time_d2: 'Past 2 days', predefined_time_d3: 'Past 3 days', predefined_time_w: 'Past week', predefined_time_m: 'Past month', predefined_time_y: 'Past year', predefined_filetype_pdf: 'PDF', predefined_filetype_docx: 'Word (docx)', predefined_filetype_doc: 'Word (doc)', predefined_filetype_xlsx: 'Excel (xlsx)', predefined_filetype_xls: 'Excel (xls)', predefined_filetype_pptx: 'PowerPoint (pptx)', predefined_filetype_ppt: 'PowerPoint (ppt)', predefined_filetype_txt: 'Plain Text', predefined_filetype_rtf: 'Rich Text Format', predefined_filetype_html: 'Web Page (html)', predefined_filetype_htm: 'Web Page (htm)', predefined_filetype_xml: 'XML', predefined_filetype_jpg: 'JPEG Image', predefined_filetype_png: 'PNG Image', predefined_filetype_gif: 'GIF Image', predefined_filetype_svg: 'SVG Image', predefined_filetype_bmp: 'BMP Image', predefined_filetype_js: 'JavaScript', predefined_filetype_css: 'CSS', predefined_filetype_py: 'Python', predefined_filetype_java: 'Java', predefined_filetype_cpp: 'C++', predefined_filetype_cs: 'C#', predefined_filetype_kml: 'Google Earth (kml)', predefined_filetype_kmz: 'Google Earth (kmz)',
tool_reset_filters: 'Reset Filters', tool_verbatim_search: 'Verbatim Search', tool_advanced_search: 'Advanced Search', tool_apply_date: 'Apply Dates',
tool_personalization_toggle: 'Personalization',
link_advanced_search_title: 'Open Google Advanced Search page', tooltip_site_search: 'Search within {siteUrl}', tooltip_clear_site_search: 'Remove site: restriction', tooltip_toggle_personalization_on: 'Click to turn Personalization ON (Results tailored to you)', tooltip_toggle_personalization_off: 'Click to turn Personalization OFF (More generic results)', settings_tab_general: 'General', settings_tab_appearance: 'Appearance', settings_tab_features: 'Features', settings_tab_custom: 'Custom', settings_close_button_title: 'Close', settings_interface_language: 'Interface Language:', settings_language_auto: 'Auto (Browser Default)', settings_section_mode: 'Section Collapse Mode:', settings_section_mode_remember: 'Remember State', settings_section_mode_expand: 'Expand All', settings_section_mode_collapse: 'Collapse All', settings_accordion_mode: 'Accordion Mode (only when "Remember State" is active)', settings_enable_drag: 'Enable Dragging', settings_reset_button_location: 'Reset Button Location:', settings_verbatim_button_location: 'Verbatim Button Location:', settings_adv_search_location: '"Advanced Search" Link Location:', settings_personalize_button_location: 'Personalization Button Location:', settings_location_tools: 'Tools Section', settings_location_top: 'Top Block', settings_location_header: 'Sidebar Header', settings_location_hide: 'Hide', settings_sidebar_width: 'Sidebar Width (px)', settings_width_range_hint: '(Range: 90-270, Step: 5)', settings_font_size: 'Base Font Size (px)', settings_font_size_range_hint: '(Range: 8-24, Step: 0.5)', settings_header_icon_size: 'Header Icon Size (px)', settings_header_icon_size_range_hint: '(Range: 8-32, Step: 0.5)', settings_vertical_spacing: 'Vertical Spacing', settings_vertical_spacing_range_hint: '(Multiplier Range: 0.05-1.5, Step: 0.05)', settings_theme: 'Theme:', settings_theme_system: 'Follow System', settings_theme_light: 'Light', settings_theme_dark: 'Dark', settings_theme_minimal_light: 'Minimal (Light)', settings_theme_minimal_dark: 'Minimal (Dark)', settings_hover_mode: 'Hover Mode', settings_idle_opacity: 'Idle Opacity:', settings_opacity_range_hint: '(Range: 0.1-1.0, Step: 0.05)', settings_country_display: 'Country/Region Display:', settings_country_display_icontext: 'Icon & Text', settings_country_display_text: 'Text Only', settings_country_display_icon: 'Icon Only', settings_visible_sections: 'Visible Sections:', settings_section_order: 'Adjust Sidebar Section Order:', settings_section_order_hint: '(Only affects checked sections)', settings_no_orderable_sections: 'No visible sections to order.', settings_move_up_title: 'Move Up', settings_move_down_title: 'Move Down', settings_custom_intro: 'Manage custom filter options for each section:', settings_manage_sites_button: 'Manage Favorite Sites...', settings_manage_languages_button: 'Manage Custom Languages...', settings_manage_countries_button: 'Manage Countries/Regions...', settings_manage_time_ranges_button: 'Manage Time Ranges...', settings_manage_file_types_button: 'Manage File Types...', settings_save_button: 'Save Settings', settings_cancel_button: 'Cancel', settings_reset_all_button: 'Reset All',
modal_label_enable_predefined: 'Enable Predefined {type}:', modal_label_my_custom: 'My Custom {type}:', modal_placeholder_name: 'Name', modal_placeholder_domain: 'Domain', modal_placeholder_text: 'Text', modal_placeholder_value: 'Value', modal_hint_domain: 'Format: valid top-level domain, e.g., `wikipedia.org`', modal_hint_language: 'Format: starts with `lang_`, e.g., `lang_ja`, `lang_zh-TW`. Use `|` for multiple.', modal_hint_country: 'Format: `country` + 2-letter uppercase code, e.g., `countryDE`', modal_hint_time: 'Format: `h`, `d`, `w`, `m`, `y`, optionally followed by numbers, e.g., `h1`, `d7`, `w`', modal_hint_filetype: 'Format: file extension, e.g., `pdf`, `docx`', modal_tooltip_domain: 'Please enter a valid domain', modal_tooltip_language: 'Format: lang_xx or lang_xx-XX, separate multiple with |', modal_tooltip_country: 'Format: countryXX (XX = uppercase country code)', modal_tooltip_time: 'Format: h, d, w, m, y, optionally followed by numbers', modal_tooltip_filetype: 'File extension (without the dot)', modal_button_add_title: 'Add', modal_button_update_title: 'Update Item', modal_button_cancel_edit_title: 'Cancel Edit', modal_button_edit_title: 'Edit', modal_button_delete_title: 'Delete', modal_button_complete: 'Done', value_empty: '(empty)', date_range_from: 'From:', date_range_to: 'To:', sidebar_collapse_title: 'Collapse', sidebar_expand_title: 'Expand', sidebar_drag_title: 'Drag', sidebar_settings_title: 'Settings',
alert_invalid_start_date: 'Invalid start date', alert_invalid_end_date: 'Invalid end date', alert_end_before_start: 'End date cannot be earlier than start date', alert_start_in_future: 'Start date cannot be in the future', alert_end_in_future: 'End date cannot be in the future', alert_select_date: 'Please select a date', alert_error_applying_date: 'Error applying date range', alert_error_applying_filter: 'Error applying filter {type}={value}', alert_error_applying_site_search: 'Error applying site search for {site}', alert_error_clearing_site_search: 'Error clearing site search', alert_error_resetting_filters: 'Error resetting filters', alert_error_toggling_verbatim: 'Error toggling Verbatim search', alert_error_toggling_personalization: 'Error toggling Personalization search', alert_enter_display_name: 'Please enter the display name for {type}.', alert_enter_value: 'Please enter the corresponding value for {type}.', alert_invalid_value_format: 'The value format for {type} is incorrect. {hint}', alert_duplicate_name: 'Custom item display name "{name}" already exists. Please use a different name.', alert_update_failed_invalid_index: 'Update failed: Invalid item index.', alert_edit_failed_missing_fields: 'Cannot edit: Input or button fields not found.', confirm_delete_item: 'Are you sure you want to delete the item "{name}"?', confirm_reset_settings: 'Are you sure you want to reset all settings to their default values?', alert_settings_reset_success: 'Settings have been reset to default. You can continue editing or click "Save Settings" to confirm.', confirm_reset_all_menu: 'Are you sure you want to reset all settings to their default values?\nThis cannot be undone and requires a page refresh to take effect.', alert_reset_all_menu_success: 'All settings have been reset to defaults.\nPlease refresh the page to apply the changes.', alert_reset_all_menu_fail: 'Failed to reset settings via menu command! Please check the console.', alert_init_fail: '{scriptName} initialization failed. Some features may not work. Please check the console for technical details.\nTechnical Error: {error}', menu_open_settings: '⚙️ Open Settings', menu_reset_all_settings: '🚨 Reset All Settings',
}
};
let effectiveTranslations = JSON.parse(JSON.stringify(builtInTranslations));
let _currentLocale = 'en';
function _mergeExternalTranslations() { if (typeof window.GSCS_Namespace !== 'undefined' && typeof window.GSCS_Namespace.i18nPack === 'object' && typeof window.GSCS_Namespace.i18nPack.translations === 'object') { const externalTranslations = window.GSCS_Namespace.i18nPack.translations; for (const langCode in externalTranslations) { if (Object.prototype.hasOwnProperty.call(externalTranslations, langCode)) { if (!effectiveTranslations[langCode]) { effectiveTranslations[langCode] = {}; } Object.assign(effectiveTranslations[langCode], externalTranslations[langCode]); } } } else { console.warn(`${LOG_PREFIX} [i18n] External i18n pack (window.GSCS_Namespace.i18nPack) not found or invalid. Using built-in translations only.`); } }
function _detectBrowserLocale() { let locale = 'en'; try { if (navigator.languages && navigator.languages.length) { locale = navigator.languages[0]; } else if (navigator.language) { locale = navigator.language; } } catch (e) { console.warn(`${LOG_PREFIX} [i18n] Error accessing navigator.language(s):`, e); } if (effectiveTranslations[locale]) return locale; if (locale.includes('-')) { const parts = locale.split('-'); if (parts.length > 0 && effectiveTranslations[parts[0]]) return parts[0]; if (parts.length > 2 && effectiveTranslations[`${parts[0]}-${parts[1]}`]) return `${parts[0]}-${parts[1]}`; } return 'en'; }
function _updateActiveLocale(settingsToUse) { let newLocale = 'en'; const langSettingSource = (settingsToUse && Object.keys(settingsToUse).length > 0 && typeof settingsToUse.interfaceLanguage === 'string') ? settingsToUse : defaultSettings; const userSelectedLang = langSettingSource.interfaceLanguage; if (userSelectedLang && userSelectedLang !== 'auto') { if (effectiveTranslations[userSelectedLang]) { newLocale = userSelectedLang; } else if (userSelectedLang.includes('-')) { const genericLang = userSelectedLang.split('-')[0]; if (effectiveTranslations[genericLang]) { newLocale = genericLang; } else { newLocale = _detectBrowserLocale(); } } else { newLocale = _detectBrowserLocale(); } } else { newLocale = _detectBrowserLocale(); } if (_currentLocale !== newLocale) { _currentLocale = newLocale; } if (userSelectedLang && userSelectedLang !== 'auto' && _currentLocale !== userSelectedLang && !userSelectedLang.includes(_currentLocale)) { console.warn(`${LOG_PREFIX} [i18n] User selected language "${userSelectedLang}" was not fully available or matched. Using best match: "${_currentLocale}".`); } }
_mergeExternalTranslations();
function getString(key, replacements = {}) { let str = `[ERR: ${key} @ ${_currentLocale}]`; let found = false; if (effectiveTranslations[_currentLocale] && typeof effectiveTranslations[_currentLocale][key] !== 'undefined') { str = effectiveTranslations[_currentLocale][key]; found = true; } else if (_currentLocale.includes('-')) { const genericLang = _currentLocale.split('-')[0]; if (effectiveTranslations[genericLang] && typeof effectiveTranslations[genericLang][key] !== 'undefined') { str = effectiveTranslations[genericLang][key]; found = true; } } if (!found && _currentLocale !== 'en') { if (effectiveTranslations['en'] && typeof effectiveTranslations['en'][key] !== 'undefined') { str = effectiveTranslations['en'][key]; found = true; } } if (!found) { if (!(effectiveTranslations['en'] && typeof effectiveTranslations['en'][key] !== 'undefined')) { console.error(`${LOG_PREFIX} [i18n] CRITICAL: Missing translation for key: "${key}" in BOTH locale: "${_currentLocale}" AND default locale "en".`); } else { str = effectiveTranslations['en'][key]; found = true; } if(!found) str = `[ERR_NF: ${key}]`; } if (typeof str === 'string') { for (const placeholder in replacements) { if (Object.prototype.hasOwnProperty.call(replacements, placeholder)) { str = str.replace(new RegExp(`\\{${placeholder}\\}`, 'g'), replacements[placeholder]); } } } else { console.error(`${LOG_PREFIX} [i18n] CRITICAL: Translation for key "${key}" is not a string:`, str); return `[INVALID_TYPE_FOR_KEY: ${key}]`; } return str; }
return { getString: getString, getCurrentLocale: function() { return _currentLocale; }, getTranslationsForLocale: function(locale = 'en') { return effectiveTranslations[locale] || effectiveTranslations['en']; }, initializeBaseLocale: function() { _updateActiveLocale(defaultSettings); }, updateActiveLocale: function(activeSettings) { _updateActiveLocale(activeSettings); }, getAvailableLocales: function() { const locales = new Set(['auto', 'en']); Object.keys(effectiveTranslations).forEach(lang => { if (Object.keys(effectiveTranslations[lang]).length > 0) { locales.add(lang); } }); return Array.from(locales).sort((a, b) => { if (a === 'auto') return -1; if (b === 'auto') return 1; if (a === 'en' && b !== 'auto') return -1; if (b === 'en' && a !== 'auto') return 1; let nameA = a, nameB = b; try { nameA = new Intl.DisplayNames([a],{type:'language'}).of(a); } catch(e){} try { nameB = new Intl.DisplayNames([b],{type:'language'}).of(b); } catch(e){} return nameA.localeCompare(nameB); }); } };
})();
const _ = LocalizationService.getString;
const Utils = { /* ... (same as before) ... */ debounce: function(func, wait) { let timeout; return function executedFunction(...args) { const context = this; const later = () => { timeout = null; func.apply(context, args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, mergeDeep: function(target, source) { if (!source) return target; target = target || {}; for (const key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { const targetValue = target[key]; const sourceValue = source[key]; if (sourceValue && typeof sourceValue === 'object' && !Array.isArray(sourceValue)) { target[key] = Utils.mergeDeep(targetValue, sourceValue); } else if (typeof sourceValue !== 'undefined') { target[key] = sourceValue; } } } return target; }, clamp: function(num, min, max) { return Math.min(Math.max(num, min), max); }, parseIconAndText: function(fullText) { const match = fullText.match(/^(\P{L}\P{N}\s*)+/u); let icon = ''; let text = fullText; if (match && match[0].trim() !== '') { icon = match[0].trim(); text = fullText.substring(icon.length).trim(); } return { icon, text }; }, getCurrentURL: function() { try { return new URL(window.location.href); } catch (e) { console.error(`${LOG_PREFIX} Error creating URL object:`, e); return null; } } };
const NotificationManager = (function() { /* ... (same as before) ... */ let container = null; function init() { if (document.getElementById(IDS.NOTIFICATION_CONTAINER)) { container = document.getElementById(IDS.NOTIFICATION_CONTAINER); return; } container = document.createElement('div'); container.id = IDS.NOTIFICATION_CONTAINER; if (document.body) { document.body.appendChild(container); } else { console.error(LOG_PREFIX + " NotificationManager.init(): document.body is not available!"); container = null; } } function show(messageKey, messageArgs = {}, type = 'info', duration = 3000) { if (!container) { const alertMsg = (typeof _ === 'function' && _(messageKey, messageArgs) && !(_(messageKey, messageArgs).startsWith('[ERR:'))) ? _(messageKey, messageArgs) : `${messageKey} (args: ${JSON.stringify(messageArgs)})`; alert(alertMsg); return null; } const notificationElement = document.createElement('div'); notificationElement.classList.add(CSS.NOTIFICATION); const typeClass = CSS[`NTF_${type.toUpperCase()}`] || CSS.NTF_INFO; notificationElement.classList.add(typeClass); notificationElement.textContent = _(messageKey, messageArgs); if (duration <= 0) { const closeButton = document.createElement('span'); closeButton.innerHTML = '×'; closeButton.style.cursor = 'pointer'; closeButton.style.marginLeft = '10px'; closeButton.style.float = 'right'; closeButton.onclick = () => notificationElement.remove(); notificationElement.appendChild(closeButton); } container.appendChild(notificationElement); if (duration > 0) { setTimeout(() => { notificationElement.style.opacity = '0'; setTimeout(() => notificationElement.remove(), 500); }, duration); } return notificationElement; } return { init: init, show: show }; })();
function getListMapping(listId) { /* ... (same as before) ... */ const listMappings = { [IDS.SITES_LIST]: { arrayKey: 'favoriteSites', valueKey: 'url', populateFn: populateFavoriteSitesList, textInput: `#${IDS.NEW_SITE_NAME}`, valueInput: `#${IDS.NEW_SITE_URL}`, addButton: `#${IDS.ADD_SITE_BTN}`, nameKey: 'section_site_search' }, [IDS.LANG_LIST]: { arrayKey: 'customLanguages', valueKey: 'value', populateFn: populateCustomLanguagesList, textInput: `#${IDS.NEW_LANG_TEXT}`, valueInput: `#${IDS.NEW_LANG_VALUE}`, addButton: `#${IDS.ADD_LANG_BTN}`, nameKey: 'section_language' }, [IDS.TIME_LIST]: { arrayKey: 'customTimeRanges', valueKey: 'value', populateFn: populateCustomTimeRangesList,textInput: `#${IDS.NEW_TIME_TEXT}`, valueInput: `#${IDS.NEW_TIME_VALUE}`, addButton: `#${IDS.ADD_TIME_BTN}`, nameKey: 'section_time' }, [IDS.FT_LIST]: { arrayKey: 'customFiletypes', valueKey: 'value', populateFn: populateCustomFiletypesList, textInput: `#${IDS.NEW_FT_TEXT}`, valueInput: `#${IDS.NEW_FT_VALUE}`, addButton: `#${IDS.ADD_FT_BTN}`, nameKey: 'section_filetype' }, [IDS.COUNTRIES_LIST]: { arrayKey: 'customCountries', valueKey: 'value', populateFn: populateCustomCountriesList, textInput: `#${IDS.NEW_COUNTRY_TEXT}`, valueInput: `#${IDS.NEW_COUNTRY_VALUE}`, addButton: `#${IDS.ADD_COUNTRY_BTN}`, nameKey: 'section_country' } }; return listMappings[listId] || null; }
function validateCustomInput(inputElement) { /* ... (same as before) ... */ if (!inputElement) return false; const value = inputElement.value.trim(); const id = inputElement.id; let isValid = false; let isEmpty = value === ''; if (id === IDS.NEW_SITE_NAME || id === IDS.NEW_LANG_TEXT || id === IDS.NEW_TIME_TEXT || id === IDS.NEW_FT_TEXT || id === IDS.NEW_COUNTRY_TEXT) { isValid = !isEmpty; } else if (id === IDS.NEW_SITE_URL) { isValid = isEmpty || /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(value); } else if (id === IDS.NEW_LANG_VALUE) { isValid = isEmpty || /^lang_[a-zA-Z]{2,3}(?:-[a-zA-Z0-9]{2,4})?(?:\|lang_[a-zA-Z]{2,3}(?:-[a-zA-Z0-9]{2,4})?)*$/.test(value); } else if (id === IDS.NEW_TIME_VALUE) { isValid = isEmpty || /^[hdwmy]\d*$/.test(value); } else if (id === IDS.NEW_FT_VALUE) { isValid = isEmpty || /^[a-zA-Z0-9]+$/.test(value); } else if (id === IDS.NEW_COUNTRY_VALUE) { isValid = isEmpty || /^country[A-Z]{2}$/.test(value); } inputElement.classList.remove('input-valid', 'input-invalid', CSS.INPUT_HAS_ERROR); _clearInputError(inputElement); if (!isEmpty) { inputElement.classList.add(isValid ? 'input-valid' : 'input-invalid'); if (!isValid) inputElement.classList.add(CSS.INPUT_HAS_ERROR); } return isValid || isEmpty; }
function _getInputErrorElement(inputElement) { /* ... (same as before) ... */ if (!inputElement || !inputElement.id) return null; let errorEl = inputElement.nextElementSibling; if (errorEl && errorEl.classList.contains(CSS.INPUT_ERROR_MESSAGE) && errorEl.id === `${inputElement.id}-error-msg`) { return errorEl; } const parentDiv = inputElement.parentElement; if (parentDiv) { return parentDiv.querySelector(`#${inputElement.id}-error-msg`); } return null; }
function _showInputError(inputElement, messageKey, messageArgs = {}) { /* ... (same as before) ... */ if (!inputElement) return; const errorElement = _getInputErrorElement(inputElement); if (errorElement) { errorElement.textContent = _(messageKey, messageArgs); errorElement.classList.add(CSS.ERROR_VISIBLE); } inputElement.classList.add(CSS.INPUT_HAS_ERROR); inputElement.classList.remove('input-valid'); }
function _clearInputError(inputElement) { /* ... (same as before) ... */ if (!inputElement) return; const errorElement = _getInputErrorElement(inputElement); if (errorElement) { errorElement.textContent = ''; errorElement.classList.remove(CSS.ERROR_VISIBLE); } inputElement.classList.remove(CSS.INPUT_HAS_ERROR, 'input-invalid'); }
function _clearAllInputErrorsInGroup(inputGroupElement) { /* ... (same as before) ... */ if (!inputGroupElement) return; inputGroupElement.querySelectorAll(`input[type="text"]`).forEach(input => { _clearInputError(input); input.classList.remove('input-valid', 'input-invalid'); }); }
function _showGlobalMessage(messageKey, messageArgs = {}, type = 'info', duration = 3000, targetElementId = IDS.SETTINGS_MESSAGE_BAR) { /* ... (same as before) ... */ const messageBar = document.getElementById(targetElementId); if (!messageBar) { if (targetElementId !== IDS.SETTINGS_MESSAGE_BAR && NotificationManager && typeof NotificationManager.show === 'function') { NotificationManager.show(messageKey, messageArgs, type, duration > 0 ? duration : 5000); } else { const alertMsg = (typeof _ === 'function' && _(messageKey, messageArgs) && !(_(messageKey, messageArgs).startsWith('[ERR:'))) ? _(messageKey, messageArgs) : `${messageKey} (args: ${JSON.stringify(messageArgs)})`; alert(alertMsg); } return; } if (globalMessageTimeout && targetElementId === IDS.SETTINGS_MESSAGE_BAR) { clearTimeout(globalMessageTimeout); globalMessageTimeout = null; } messageBar.textContent = _(messageKey, messageArgs); messageBar.className = `${CSS.MESSAGE_BAR}`; messageBar.classList.add(CSS[`MSG_${type.toUpperCase()}`] || CSS.MSG_INFO); messageBar.style.display = 'block'; if (duration > 0 && targetElementId === IDS.SETTINGS_MESSAGE_BAR) { globalMessageTimeout = setTimeout(() => { messageBar.style.display = 'none'; messageBar.textContent = ''; messageBar.className = CSS.MESSAGE_BAR; }, duration); } }
function _validateAndPrepareCustomItemData(textInput, valueInput, itemTypeName, listId) { /* ... (same as before) ... */ if (!textInput || !valueInput) { _showGlobalMessage('alert_edit_failed_missing_fields', {}, 'error', 0); return { isValid: false }; } _clearInputError(textInput); _clearInputError(valueInput); const text = textInput.value.trim(); const value = valueInput.value.trim(); let hint = ''; if (text === '') { _showInputError(textInput, 'alert_enter_display_name', { type: itemTypeName }); textInput.focus(); return { isValid: false, errorField: textInput }; } else { textInput.classList.remove(CSS.INPUT_HAS_ERROR); validateCustomInput(textInput); } if (value === '') { _showInputError(valueInput, 'alert_enter_value', { type: itemTypeName }); valueInput.focus(); return { isValid: false, errorField: valueInput }; } else { const isValueFormatValid = validateCustomInput(valueInput); if (!isValueFormatValid && !valueInput.classList.contains('input-invalid')) { if (listId === IDS.COUNTRIES_LIST) hint = _('modal_tooltip_country'); else if (listId === IDS.LANG_LIST) hint = _('modal_tooltip_language'); else if (listId === IDS.TIME_LIST) hint = _('modal_tooltip_time'); else if (listId === IDS.FT_LIST) hint = _('modal_tooltip_filetype'); else if (listId === IDS.SITES_LIST) hint = _('modal_tooltip_domain'); _showInputError(valueInput, 'alert_invalid_value_format', { type: itemTypeName, hint: hint }); valueInput.focus(); return { isValid: false, errorField: valueInput }; } else if (!isValueFormatValid) { valueInput.focus(); return { isValid: false, errorField: valueInput }; } else { valueInput.classList.remove(CSS.INPUT_HAS_ERROR); } } textInput.classList.remove(CSS.INPUT_HAS_ERROR); valueInput.classList.remove(CSS.INPUT_HAS_ERROR); return { isValid: true, text: text, value: value }; }
function _isDuplicateCustomItem(text, itemsToCheck, listId, editingIndex, editingItemInfoRef) { /* ... (same as before) ... */ const lowerText = text.toLowerCase(); return itemsToCheck.some((item, idx) => { if (editingItemInfoRef && editingItemInfoRef.listId === listId && editingIndex === idx) { return false; } return item.text.toLowerCase() === lowerText; }); }
function createCustomListItem(index, item, valueKey, listId) { /* ... (same as before) ... */ const listItem = document.createElement('li'); listItem.dataset[DATA_ATTR.INDEX] = index; listItem.dataset[DATA_ATTR.LIST_ID] = listId; const textSpan = document.createElement('span'); let displayValue = item[valueKey] ?? _('value_empty'); let displayKey = valueKey; if (listId === IDS.LANG_LIST) displayKey = 'lr'; else if (listId === IDS.FT_LIST) displayKey = 'filetype'; else if (listId === IDS.SITES_LIST) displayKey = 'site'; else if (listId === IDS.TIME_LIST) displayKey = 'qdr'; else if (listId === IDS.COUNTRIES_LIST) displayKey = 'cr'; textSpan.textContent = `${item.text} (${displayKey}=${displayValue})`; textSpan.title = textSpan.textContent; const controlsSpan = document.createElement('span'); controlsSpan.classList.add(CSS.ITEM_CONTROLS); controlsSpan.innerHTML = `<button class="${CSS.EDIT_CUSTOM_ITEM}" title="${_('modal_button_edit_title')}">${SVG_ICONS.edit}</button> <button class="${CSS.DELETE_CUSTOM_ITEM}" title="${_('modal_button_delete_title')}">${SVG_ICONS.delete}</button>`; listItem.appendChild(textSpan); listItem.appendChild(controlsSpan); return listItem; }
function populateCustomList(listElementId, items, valueKey, contextElement = document) { /* ... (same as before) ... */ const listElement = contextElement.querySelector(`#${listElementId}`); if (!listElement) { console.warn(`${LOG_PREFIX} List element not found: #${listElementId}`); return; } listElement.innerHTML = ''; const fragment = document.createDocumentFragment(); if (!Array.isArray(items)) items = []; items.forEach((item, index) => { if (item && typeof item.text === 'string' && typeof item[valueKey] === 'string') { fragment.appendChild(createCustomListItem(index, item, valueKey, listElementId)); } }); listElement.appendChild(fragment); }
function populateFavoriteSitesList(sites, context = document) { /* ... (same as before) ... */ populateCustomList(IDS.SITES_LIST, sites, 'url', context); }
function populateCustomLanguagesList(langs, context = document) { /* ... (same as before) ... */ populateCustomList(IDS.LANG_LIST, langs, 'value', context); }
function populateCustomTimeRangesList(ranges, context = document) { /* ... (same as before) ... */ populateCustomList(IDS.TIME_LIST, ranges, 'value', context); }
function populateCustomFiletypesList(types, context = document) { /* ... (same as before) ... */ populateCustomList(IDS.FT_LIST, types, 'value', context); }
function populateCustomCountriesList(countries, context = document) { /* ... (same as before) ... */ populateCustomList(IDS.COUNTRIES_LIST, countries, 'value', context); }
function applyThemeToElement(element, themeSetting) { /* ... (same as before) ... */ if (!element) return; element.classList.remove( CSS.LIGHT_THEME, CSS.DARK_THEME, 'minimal-theme', 'minimal-light', 'minimal-dark' ); let effectiveTheme = themeSetting; const isSettingsOrModal = element.id === IDS.SETTINGS_WINDOW || element.id === IDS.SETTINGS_OVERLAY || element.classList.contains('settings-modal-content') || element.classList.contains('settings-modal-overlay'); if (isSettingsOrModal) { if (themeSetting === 'minimal-light') effectiveTheme = 'light'; else if (themeSetting === 'minimal-dark') effectiveTheme = 'dark'; } switch (effectiveTheme) { case 'dark': element.classList.add(CSS.DARK_THEME); break; case 'minimal-light': element.classList.add('minimal-theme', 'minimal-light'); break; case 'minimal-dark': element.classList.add('minimal-theme', 'minimal-dark'); break; case 'system': const systemIsDark = systemThemeMediaQuery && systemThemeMediaQuery.matches; element.classList.add(systemIsDark ? CSS.DARK_THEME : CSS.LIGHT_THEME); break; case 'light': default: element.classList.add(CSS.LIGHT_THEME); break; } }
const ModalManager = (function() { /* ... (same as before, ModalManager remains unchanged internally) ... */ let _currentModal = null; let _currentModalContent = null; let _editingItemInfo = null; function _createPredefinedListHTML(currentOptionType, typeNameKey, predefinedOptionsSource, enabledPredefinedValues) { const label = _(typeNameKey); const optionsHTML = (predefinedOptionsSource[currentOptionType] || []).map(option => { const checkboxId = `predefined-${currentOptionType}-${option.value.replace(/[^a-zA-Z0-9-_]/g, '')}`; const translatedOptionText = _(option.textKey); const isChecked = enabledPredefinedValues.has(option.value); return `<li><input type="checkbox" id="${checkboxId}" value="${option.value}" data-option-type="${currentOptionType}" ${isChecked ? 'checked' : ''}><label for="${checkboxId}">${translatedOptionText}</label></li>`; }).join(''); return `<label style="font-weight: bold;">${_('modal_label_enable_predefined', { type: label })}</label><ul class="predefined-options-list" data-option-type="${currentOptionType}">${optionsHTML}</ul>`; } function _createCustomListHTML(currentListId, textPlaceholderKey, valuePlaceholderKey, hintKey, formatTooltipKey) { const mapping = getListMapping(currentListId); if (!mapping) return ''; const typeName = _(mapping.nameKey); const textInputId = mapping.textInput.substring(1); const valueInputId = mapping.valueInput.substring(1); const inputGroupHTML = `<div class="${CSS.CUSTOM_LIST_INPUT_GROUP}"><div><input type="text" id="${textInputId}" placeholder="${_(textPlaceholderKey)}"><span id="${textInputId}-error-msg" class="${CSS.INPUT_ERROR_MESSAGE}"></span></div><div><input type="text" id="${valueInputId}" placeholder="${_(valuePlaceholderKey)}" title="${_(formatTooltipKey)}"><span id="${valueInputId}-error-msg" class="${CSS.INPUT_ERROR_MESSAGE}"></span></div><button id="${mapping.addButton.substring(1)}" class="${CSS.ADD_CUSTOM_BUTTON} custom-list-action-button" data-list-id="${currentListId}" title="${_('modal_button_add_title')}">${SVG_ICONS.add}</button><button class="cancel-edit-button" style="display: none;" title="${_('modal_button_cancel_edit_title')}">${SVG_ICONS.close}</button></div>`; const hintHTML = `<span class="setting-value-hint">${_(hintKey)}</span>`; return `<label style="font-weight: bold; margin-top: 1em; display: block;">${_('modal_label_my_custom', { type: typeName })}</label><ul id="${currentListId}" class="${CSS.CUSTOM_LIST}"></ul>${inputGroupHTML}${hintHTML}`; } function _resetEditStateInternal(contextElement = _currentModalContent) { if (_editingItemInfo) { const mapping = getListMapping(_editingItemInfo.listId); if (_editingItemInfo.addButton) { _editingItemInfo.addButton.innerHTML = SVG_ICONS.add; _editingItemInfo.addButton.title = _('modal_button_add_title'); _editingItemInfo.addButton.classList.remove('update-mode'); } if (_editingItemInfo.cancelButton) { _editingItemInfo.cancelButton.style.display = 'none'; } if (mapping && contextElement) { const textInput = contextElement.querySelector(mapping.textInput); const valueInput = contextElement.querySelector(mapping.valueInput); const inputGroup = textInput?.closest(`.${CSS.CUSTOM_LIST_INPUT_GROUP}`); if(inputGroup) _clearAllInputErrorsInGroup(inputGroup); if (textInput) { textInput.value = ''; textInput.classList.remove('input-valid', 'input-invalid', CSS.INPUT_HAS_ERROR); _clearInputError(textInput);} if (valueInput) { valueInput.value = ''; valueInput.classList.remove('input-valid', 'input-invalid', CSS.INPUT_HAS_ERROR); _clearInputError(valueInput);} } } _editingItemInfo = null; } function _prepareEditItemActionInternal(item, index, listId, mapping, contextElement) { const textInput = contextElement.querySelector(mapping.textInput); const valueInput = contextElement.querySelector(mapping.valueInput); const addButton = contextElement.querySelector(mapping.addButton); const cancelButton = addButton?.parentElement?.querySelector('.cancel-edit-button'); if (textInput && valueInput && addButton && cancelButton) { if (_editingItemInfo && (_editingItemInfo.listId !== listId || _editingItemInfo.index !== index)) { _resetEditStateInternal(contextElement); } textInput.value = item.text; valueInput.value = item[mapping.valueKey]; _editingItemInfo = { listId, index, addButton, cancelButton, arrayKey: mapping.arrayKey }; addButton.innerHTML = SVG_ICONS.update; addButton.title = _('modal_button_update_title'); addButton.classList.add('update-mode'); cancelButton.style.display = 'inline-block'; validateCustomInput(valueInput); validateCustomInput(textInput); textInput.focus(); } else { const errorSourceInput = textInput || valueInput || addButton?.closest(`.${CSS.CUSTOM_LIST_INPUT_GROUP}`)?.querySelector('input[type="text"]'); if (errorSourceInput) _showInputError(errorSourceInput, 'alert_edit_failed_missing_fields'); else _showGlobalMessage('alert_edit_failed_missing_fields', {}, 'error', 0, _currentModalContent?.querySelector(`#${IDS.SETTINGS_MESSAGE_BAR}`) ? IDS.SETTINGS_MESSAGE_BAR : null); } } function _handleCustomListActionsInternal(event, contextElement, tempCustomItemsRef) { const button = event.target.closest(`button.${CSS.EDIT_CUSTOM_ITEM}, button.${CSS.DELETE_CUSTOM_ITEM}`); if (!button) return; const listItem = button.closest(`li[data-${DATA_ATTR.INDEX}][data-list-id]`); if (!listItem) return; const index = parseInt(listItem.dataset[DATA_ATTR.INDEX], 10); const listId = listItem.getAttribute('data-list-id'); if (isNaN(index) || !listId) return; const mapping = getListMapping(listId); if (!mapping) return; const targetArray = tempCustomItemsRef; if (!Array.isArray(targetArray) || index < 0 || index >= targetArray.length) return; const item = targetArray[index]; if (button.classList.contains(CSS.DELETE_CUSTOM_ITEM)) { if (confirm(_('confirm_delete_item', { name: item.text }))) { if (_editingItemInfo && _editingItemInfo.listId === listId && _editingItemInfo.index === index) { _resetEditStateInternal(contextElement); } targetArray.splice(index, 1); mapping.populateFn(targetArray, contextElement); } } else if (button.classList.contains(CSS.EDIT_CUSTOM_ITEM)) { _prepareEditItemActionInternal(item, index, listId, mapping, contextElement); } } function _handleCustomItemSubmitInternal(listId, contextElement, tempCustomItemsRef) { const mapping = getListMapping(listId); if (!mapping) return; const { arrayKey, valueKey, populateFn, textInput: textInputSelector, valueInput: valueInputSelector, nameKey } = mapping; const itemTypeName = _(nameKey); const textInput = contextElement.querySelector(textInputSelector); const valueInput = contextElement.querySelector(valueInputSelector); const inputGroup = textInput?.closest(`.${CSS.CUSTOM_LIST_INPUT_GROUP}`); if (inputGroup) _clearAllInputErrorsInGroup(inputGroup); const validationResult = _validateAndPrepareCustomItemData(textInput, valueInput, itemTypeName, listId); if (!validationResult.isValid) { if (validationResult.errorField) validationResult.errorField.focus(); return; } const { text, value } = validationResult; const itemsToCheck = tempCustomItemsRef; const editingIdx = (_editingItemInfo && _editingItemInfo.listId === listId) ? _editingItemInfo.index : -1; if (_isDuplicateCustomItem(text, itemsToCheck, listId, editingIdx, _editingItemInfo)) { if (textInput) _showInputError(textInput, 'alert_duplicate_name', { name: text }); textInput?.focus(); return; } const itemData = { text: text, value: value }; if (_editingItemInfo && _editingItemInfo.listId === listId) { const indexToUpdate = _editingItemInfo.index; if (indexToUpdate >= 0 && indexToUpdate < itemsToCheck.length) { itemsToCheck[indexToUpdate] = { text: itemData.text, [valueKey]: itemData.value }; populateFn(itemsToCheck, contextElement); _resetEditStateInternal(contextElement); } else { if(textInput) _showInputError(textInput, 'alert_update_failed_invalid_index'); _resetEditStateInternal(contextElement); } } else { itemsToCheck.push({ text: itemData.text, [valueKey]: itemData.value }); populateFn(itemsToCheck, contextElement); if (textInput) { textInput.value = ''; _clearInputError(textInput); textInput.focus(); } if (valueInput) { valueInput.value = ''; _clearInputError(valueInput); } } } function _bindModalContentEventsInternal(modalContent, tempCustomItemsArrayRef) { if (!modalContent || modalContent.dataset.modalEventsBound === 'true') return; modalContent.dataset.modalEventsBound = 'true'; modalContent.addEventListener('click', (event) => { const target = event.target; let listIdForAction = ''; const addButton = target.closest(`.${CSS.ADD_CUSTOM_BUTTON}.custom-list-action-button`); const itemControlButton = target.closest(`.${CSS.EDIT_CUSTOM_ITEM}, .${CSS.DELETE_CUSTOM_ITEM}`); const cancelEditButton = target.closest('.cancel-edit-button'); if (addButton) listIdForAction = addButton.dataset.listId; else if (itemControlButton) listIdForAction = itemControlButton.closest('li')?.dataset.listId; else if (cancelEditButton) { const inputGroup = cancelEditButton.closest(`.${CSS.CUSTOM_LIST_INPUT_GROUP}`); listIdForAction = inputGroup?.querySelector(`.${CSS.ADD_CUSTOM_BUTTON}`)?.dataset.listId; if (inputGroup) _clearAllInputErrorsInGroup(inputGroup); } if (itemControlButton && listIdForAction) { _handleCustomListActionsInternal(event, modalContent, tempCustomItemsArrayRef); return; } if (addButton && listIdForAction) { _handleCustomItemSubmitInternal(listIdForAction, modalContent, tempCustomItemsArrayRef); return; } if (cancelEditButton) { _resetEditStateInternal(modalContent); return; } }); modalContent.addEventListener('input', (event) => { const target = event.target; if (target.matches(`#${IDS.NEW_SITE_NAME}, #${IDS.NEW_SITE_URL}, #${IDS.NEW_LANG_TEXT}, #${IDS.NEW_LANG_VALUE}, #${IDS.NEW_TIME_TEXT}, #${IDS.NEW_TIME_VALUE}, #${IDS.NEW_FT_TEXT}, #${IDS.NEW_FT_VALUE}, #${IDS.NEW_COUNTRY_TEXT}, #${IDS.NEW_COUNTRY_VALUE}`)) { _clearInputError(target); validateCustomInput(target); } }); } return { show: function(titleKey, contentHTML, onCompleteCallback, currentTheme) { this.hide(); _currentModal = document.createElement('div'); _currentModal.className = 'settings-modal-overlay'; applyThemeToElement(_currentModal, currentTheme); _currentModalContent = document.createElement('div'); _currentModalContent.className = 'settings-modal-content'; applyThemeToElement(_currentModalContent, currentTheme); const header = document.createElement('div'); header.className = 'settings-modal-header'; header.innerHTML = `<h4>${_(titleKey)}</h4><button class="settings-modal-close-btn" title="${_('settings_close_button_title')}">${SVG_ICONS.close}</button>`; const body = document.createElement('div'); body.className = 'settings-modal-body'; body.innerHTML = contentHTML; const footer = document.createElement('div'); footer.className = 'settings-modal-footer'; footer.innerHTML = `<button class="modal-complete-btn">${_('modal_button_complete')}</button>`; _currentModalContent.appendChild(header); _currentModalContent.appendChild(body); _currentModalContent.appendChild(footer); _currentModal.appendChild(_currentModalContent); let closeModalHandlerInstance = null; let completeModalHandlerInstance = null; const self = this; closeModalHandlerInstance = (event) => { if (event.target === _currentModal || event.target.closest('.settings-modal-close-btn')) { self.hide(true); _currentModal?.removeEventListener('click', closeModalHandlerInstance, true); header.querySelector('.settings-modal-close-btn')?.removeEventListener('click', closeModalHandlerInstance); footer.querySelector('.modal-complete-btn')?.removeEventListener('click', completeModalHandlerInstance); } }; completeModalHandlerInstance = () => { if (onCompleteCallback && typeof onCompleteCallback === 'function') { onCompleteCallback(_currentModalContent); } _currentModal?.removeEventListener('click', closeModalHandlerInstance, true); header.querySelector('.settings-modal-close-btn')?.removeEventListener('click', closeModalHandlerInstance); footer.querySelector('.modal-complete-btn')?.removeEventListener('click', completeModalHandlerInstance); self.hide(false); }; _currentModal.addEventListener('click', closeModalHandlerInstance, true); header.querySelector('.settings-modal-close-btn').addEventListener('click', closeModalHandlerInstance); footer.querySelector('.modal-complete-btn').addEventListener('click', completeModalHandlerInstance); _currentModalContent.addEventListener('click', (event) => event.stopPropagation()); document.body.appendChild(_currentModal); return _currentModalContent; }, hide: function(isCancel = false) { if (_currentModal) { const inputGroup = _currentModalContent?.querySelector(`.${CSS.CUSTOM_LIST_INPUT_GROUP}`); if (inputGroup) _clearAllInputErrorsInGroup(inputGroup); _resetEditStateInternal(); _currentModal.remove(); } _currentModal = null; _currentModalContent = null; }, openManageCustomOptions: function(manageType, currentSettingsRef, PREDEFINED_OPTIONS_REF, onModalCompleteCallback) { const modalConfigs = { 'site': () => ({ modalTitleKey: 'manageSitesTitle', customListId: IDS.SITES_LIST, customItemsArrayKey: 'favoriteSites', textPKey: 'modal_placeholder_name', valPKey: 'modal_placeholder_domain', hintKey: 'modal_hint_domain', fmtKey: 'modal_tooltip_domain', }), 'language': () => ({ modalTitleKey: 'manageLanguagesTitle', customListId: IDS.LANG_LIST, customItemsArrayKey: 'customLanguages', textPKey: 'modal_placeholder_text', valPKey: 'modal_placeholder_value', hintKey: 'modal_hint_language', fmtKey: 'modal_tooltip_language', predefinedOptionTypeKey: 'language', predefinedTypeNameKey: 'section_language', }), 'country': () => ({ modalTitleKey: 'manageCountriesTitle', customListId: IDS.COUNTRIES_LIST, customItemsArrayKey: 'customCountries', textPKey: 'modal_placeholder_text', valPKey: 'modal_placeholder_value', hintKey: 'modal_hint_country', fmtKey: 'modal_tooltip_country', predefinedOptionTypeKey: 'country', predefinedTypeNameKey: 'section_country', }), 'time': () => ({ modalTitleKey: 'manageTimeRangesTitle', customListId: IDS.TIME_LIST, customItemsArrayKey: 'customTimeRanges', textPKey: 'modal_placeholder_text', valPKey: 'modal_placeholder_value', hintKey: 'modal_hint_time', fmtKey: 'modal_tooltip_time', predefinedOptionTypeKey: 'time', predefinedTypeNameKey: 'section_time', }), 'filetype': () => ({ modalTitleKey: 'manageFileTypesTitle', customListId: IDS.FT_LIST, customItemsArrayKey: 'customFiletypes', textPKey: 'modal_placeholder_text', valPKey: 'modal_placeholder_value', hintKey: 'modal_hint_filetype', fmtKey: 'modal_tooltip_filetype', predefinedOptionTypeKey: 'filetype', predefinedTypeNameKey: 'section_filetype', }), }; const getConfigFn = modalConfigs[manageType]; if (!getConfigFn) return; const config = getConfigFn(); const tempCustomItems = JSON.parse(JSON.stringify(currentSettingsRef[config.customItemsArrayKey] || [])); let contentHTML = ''; if (config.predefinedOptionTypeKey && config.predefinedTypeNameKey) { const enabledValues = new Set(currentSettingsRef.enabledPredefinedOptions[config.predefinedOptionTypeKey] || []); contentHTML += _createPredefinedListHTML(config.predefinedOptionTypeKey, config.predefinedTypeNameKey, PREDEFINED_OPTIONS_REF, enabledValues) + '<hr>'; } contentHTML += _createCustomListHTML(config.customListId, config.textPKey, config.valPKey, config.hintKey, config.fmtKey); const modalContentElement = this.show(config.modalTitleKey, contentHTML, (modalContent) => { let newEnabledPredefined = null; if (config.predefinedOptionTypeKey) { newEnabledPredefined = []; modalContent.querySelectorAll(`.predefined-options-list input[data-option-type="${config.predefinedOptionTypeKey}"]:checked`).forEach(cb => newEnabledPredefined.push(cb.value)); } onModalCompleteCallback(tempCustomItems, newEnabledPredefined, config.customItemsArrayKey, config.predefinedOptionTypeKey); }, currentSettingsRef.theme); if (modalContentElement) { if (config.customListId && getListMapping(config.customListId)) { const mapping = getListMapping(config.customListId); mapping.populateFn(tempCustomItems, modalContentElement); } _bindModalContentEventsInternal(modalContentElement, tempCustomItems); } }, resetEditStateGlobally: function() { _resetEditStateInternal(_currentModalContent || document); }, isModalOpen: function() { return !!_currentModal; } }; })();
const SettingsManager = (function() { /* ... (same content as previous complete code block, including all personalization settings logic) ... */ let _settingsWindow = null; let _settingsOverlay = null; let _currentSettings = {}; let _settingsBackup = {}; let _defaultSettingsRef = null; let _isInitialized = false; let _applySettingsToSidebar_cb = ()=>{}; let _buildSidebarUI_cb = ()=>{}; let _applySectionCollapseStates_cb = ()=>{}; let _initMenuCommands_cb = ()=>{}; let _renderSectionOrderList_ext_cb = ()=>{}; function _createPaneGeneralHTML_internal() { let langOpts = LocalizationService.getAvailableLocales().map(lc => { let dn; if (lc==='auto') dn=_( 'settings_language_auto'); else { try {dn=new Intl.DisplayNames([lc],{type:'language'}).of(lc);dn=dn.charAt(0).toUpperCase()+dn.slice(1);}catch(e){dn=lc;}dn=`${dn} (${lc})`;} return `<option value="${lc}">${dn}</option>`;}).join(''); return `<div class="${CSS.SETTING_ITEM}"><label for="${IDS.SETTING_INTERFACE_LANGUAGE}">${_('settings_interface_language')}</label><select id="${IDS.SETTING_INTERFACE_LANGUAGE}">${langOpts}</select></div>` + `<div class="${CSS.SETTING_ITEM}"><label for="${IDS.SETTING_SECTION_MODE}">${_('settings_section_mode')}</label><select id="${IDS.SETTING_SECTION_MODE}"><option value="remember">${_('settings_section_mode_remember')}</option><option value="expandAll">${_('settings_section_mode_expand')}</option><option value="collapseAll">${_('settings_section_mode_collapse')}</option></select><div style="margin-top:0.6em;"><input type="checkbox" id="${IDS.SETTING_ACCORDION}"><label for="${IDS.SETTING_ACCORDION}" class="${CSS.INLINE_LABEL}">${_('settings_accordion_mode')}</label></div></div>` + `<div class="${CSS.SETTING_ITEM}"><input type="checkbox" id="${IDS.SETTING_DRAGGABLE}"><label for="${IDS.SETTING_DRAGGABLE}" class="${CSS.INLINE_LABEL}">${_('settings_enable_drag')}</label></div>` + `<div class="${CSS.SETTING_ITEM}"><label for="${IDS.SETTING_RESET_LOCATION}">${_('settings_reset_button_location')}</label><select id="${IDS.SETTING_RESET_LOCATION}"><option value="tools">${_('settings_location_tools')}</option><option value="topBlock">${_('settings_location_top')}</option><option value="header">${_('settings_location_header')}</option><option value="none">${_('settings_location_hide')}</option></select></div>` + `<div class="${CSS.SETTING_ITEM}"><label for="${IDS.SETTING_VERBATIM_LOCATION}">${_('settings_verbatim_button_location')}</label><select id="${IDS.SETTING_VERBATIM_LOCATION}"><option value="tools">${_('settings_location_tools')}</option><option value="topBlock">${_('settings_location_top')}</option><option value="header">${_('settings_location_header')}</option><option value="none">${_('settings_location_hide')}</option></select></div>` + `<div class="${CSS.SETTING_ITEM}"><label for="${IDS.SETTING_ADV_SEARCH_LOCATION}">${_('settings_adv_search_location')}</label><select id="${IDS.SETTING_ADV_SEARCH_LOCATION}"><option value="tools">${_('settings_location_tools')}</option><option value="topBlock">${_('settings_location_top')}</option><option value="header">${_('settings_location_header')}</option><option value="none">${_('settings_location_hide')}</option></select></div>`+ `<div class="${CSS.SETTING_ITEM}"><label for="${IDS.SETTING_PERSONALIZE_LOCATION}">${_('settings_personalize_button_location')}</label><select id="${IDS.SETTING_PERSONALIZE_LOCATION}"><option value="tools">${_('settings_location_tools')}</option><option value="topBlock">${_('settings_location_top')}</option><option value="header">${_('settings_location_header')}</option><option value="none">${_('settings_location_hide')}</option></select></div>`;} function _createPaneAppearanceHTML_internal() { return `<div class="${CSS.SETTING_ITEM}"><label for="${IDS.SETTING_WIDTH}">${_('settings_sidebar_width')}</label><span class="${CSS.RANGE_HINT}">${_('settings_width_range_hint')}</span><input type="range" id="${IDS.SETTING_WIDTH}" min="90" max="270" step="5"><span class="${CSS.RANGE_VALUE}"></span></div>` + `<div class="${CSS.SETTING_ITEM}"><label for="${IDS.SETTING_FONT_SIZE}">${_('settings_font_size')}</label><span class="${CSS.RANGE_HINT}">${_('settings_font_size_range_hint')}</span><input type="range" id="${IDS.SETTING_FONT_SIZE}" min="8" max="24" step="0.5"><span class="${CSS.RANGE_VALUE}"></span></div>` + `<div class="${CSS.SETTING_ITEM}"><label for="${IDS.SETTING_HEADER_ICON_SIZE}">${_('settings_header_icon_size')}</label><span class="${CSS.RANGE_HINT}">${_('settings_header_icon_size_range_hint')}</span><input type="range" id="${IDS.SETTING_HEADER_ICON_SIZE}" min="8" max="32" step="0.5"><span class="${CSS.RANGE_VALUE}"></span></div>` + `<div class="${CSS.SETTING_ITEM}"><label for="${IDS.SETTING_VERTICAL_SPACING}">${_('settings_vertical_spacing')}</label><span class="${CSS.RANGE_HINT}">${_('settings_vertical_spacing_range_hint')}</span><input type="range" id="${IDS.SETTING_VERTICAL_SPACING}" min="0.05" max="1.5" step="0.05"><span class="${CSS.RANGE_VALUE}"></span></div>` + `<div class="${CSS.SETTING_ITEM}"><label for="${IDS.SETTING_THEME}">${_('settings_theme')}</label><select id="${IDS.SETTING_THEME}"><option value="system">${_('settings_theme_system')}</option><option value="light">${_('settings_theme_light')}</option><option value="dark">${_('settings_theme_dark')}</option><option value="minimal-light">${_('settings_theme_minimal_light')}</option><option value="minimal-dark">${_('settings_theme_minimal_dark')}</option></select></div><div class="${CSS.SETTING_ITEM}"><input type="checkbox" id="${IDS.SETTING_HOVER}"><label for="${IDS.SETTING_HOVER}" class="${CSS.INLINE_LABEL}">${_('settings_hover_mode')}</label><div style="margin-top:0.8em;padding-left:1.5em;"><label for="${IDS.SETTING_OPACITY}" style="display:block;margin-bottom:0.4em;font-weight:normal;">${_('settings_idle_opacity')}</label><span class="${CSS.RANGE_HINT}" style="width:auto;display:inline-block;margin-right:1em;">${_('settings_opacity_range_hint')}</span><input type="range" id="${IDS.SETTING_OPACITY}" min="0.1" max="1.0" step="0.05" style="width:calc(100% - 18em);vertical-align:middle;display:inline-block;"><span class="${CSS.RANGE_VALUE}" style="display:inline-block;min-width:3em;text-align:right;vertical-align:middle;"></span></div></div><div class="${CSS.SETTING_ITEM}"><label for="${IDS.SETTING_COUNTRY_DISPLAY_MODE}">${_('settings_country_display')}</label><select id="${IDS.SETTING_COUNTRY_DISPLAY_MODE}"><option value="iconAndText">${_('settings_country_display_icontext')}</option><option value="textOnly">${_('settings_country_display_text')}</option><option value="iconOnly">${_('settings_country_display_icon')}</option></select></div>`;} function _createPaneFeaturesHTML_internal() { const visItemsHTML = ALL_SECTION_DEFINITIONS.map(def=>{const dn=_(def.titleKey)||def.id;return `<div class="${CSS.SETTING_ITEM} ${CSS.SIMPLE_ITEM}"><input type="checkbox" id="setting-visible-${def.id}" data-${DATA_ATTR.SECTION_ID}="${def.id}"><label for="setting-visible-${def.id}" class="${CSS.INLINE_LABEL}">${dn}</label></div>`;}).join(''); return `<p>${_('settings_visible_sections')}</p>${visItemsHTML}<hr style="margin:1.2em 0;"><p style="font-weight:bold;margin-bottom:0.5em;">${_('settings_section_order')}</p><p style="font-size:0.9em;margin-top:-0.3em;margin-bottom:0.7em;">${_('settings_section_order_hint')}</p><ul id="${IDS.SIDEBAR_SECTION_ORDER_LIST}" class="${CSS.SECTION_ORDER_LIST}"></ul>`;} function _createPaneCustomHTML_internal() { return `<div class="${CSS.SETTING_ITEM}"><p>${_('settings_custom_intro')}</p><button class="${CSS.MANAGE_CUSTOM_BUTTON}" data-${DATA_ATTR.MANAGE_TYPE}="site">${_('settings_manage_sites_button')}</button></div><div class="${CSS.SETTING_ITEM}"><button class="${CSS.MANAGE_CUSTOM_BUTTON}" data-${DATA_ATTR.MANAGE_TYPE}="language">${_('settings_manage_languages_button')}</button></div><div class="${CSS.SETTING_ITEM}"><button class="${CSS.MANAGE_CUSTOM_BUTTON}" data-${DATA_ATTR.MANAGE_TYPE}="country">${_('settings_manage_countries_button')}</button></div><div class="${CSS.SETTING_ITEM}"><button class="${CSS.MANAGE_CUSTOM_BUTTON}" data-${DATA_ATTR.MANAGE_TYPE}="time">${_('settings_manage_time_ranges_button')}</button></div><div class="${CSS.SETTING_ITEM}"><button class="${CSS.MANAGE_CUSTOM_BUTTON}" data-${DATA_ATTR.MANAGE_TYPE}="filetype">${_('settings_manage_file_types_button')}</button></div>`;}
function _populateSliderSetting_internal(win,id,value,formatFn=(val)=>val){const i=win.querySelector(`#${id}`);if(i){i.value=value;let vs=i.parentNode.querySelector(`.${CSS.RANGE_VALUE}`);if(vs&&vs.classList.contains(CSS.RANGE_VALUE)){vs.textContent=formatFn(value);}}}
function _populateGeneralSettings_internal(win,s){ const lS=win.querySelector(`#${IDS.SETTING_INTERFACE_LANGUAGE}`);if(lS)lS.value=s.interfaceLanguage;const sMS=win.querySelector(`#${IDS.SETTING_SECTION_MODE}`),acC=win.querySelector(`#${IDS.SETTING_ACCORDION}`);if(sMS&&acC){sMS.value=s.sectionDisplayMode;const iRM=s.sectionDisplayMode==='remember';acC.disabled=!iRM;acC.checked=iRM?s.accordionMode:false;} const dC=win.querySelector(`#${IDS.SETTING_DRAGGABLE}`);if(dC)dC.checked=s.draggableHandleEnabled;const rLS=win.querySelector(`#${IDS.SETTING_RESET_LOCATION}`);if(rLS)rLS.value=s.resetButtonLocation;const vLS=win.querySelector(`#${IDS.SETTING_VERBATIM_LOCATION}`);if(vLS)vLS.value=s.verbatimButtonLocation;const aSLS=win.querySelector(`#${IDS.SETTING_ADV_SEARCH_LOCATION}`);if(aSLS)aSLS.value=s.advancedSearchLinkLocation; const pznLS = win.querySelector(`#${IDS.SETTING_PERSONALIZE_LOCATION}`); if (pznLS) pznLS.value = s.personalizationButtonLocation;}
function _populateAppearanceSettings_internal(win,s){_populateSliderSetting_internal(win,IDS.SETTING_WIDTH,s.sidebarWidth);_populateSliderSetting_internal(win,IDS.SETTING_FONT_SIZE,s.fontSize,v=>parseFloat(v).toFixed(1));_populateSliderSetting_internal(win,IDS.SETTING_HEADER_ICON_SIZE,s.headerIconSize,v=>parseFloat(v).toFixed(1));_populateSliderSetting_internal(win,IDS.SETTING_VERTICAL_SPACING,s.verticalSpacingMultiplier,v=>`x ${parseFloat(v).toFixed(2)}`);_populateSliderSetting_internal(win,IDS.SETTING_OPACITY,s.idleOpacity,v=>parseFloat(v).toFixed(2));const tS=win.querySelector(`#${IDS.SETTING_THEME}`);if(tS)tS.value=s.theme;const cDS=win.querySelector(`#${IDS.SETTING_COUNTRY_DISPLAY_MODE}`);if(cDS)cDS.value=s.countryDisplayMode;const hC=win.querySelector(`#${IDS.SETTING_HOVER}`),oI=win.querySelector(`#${IDS.SETTING_OPACITY}`);if(hC&&oI){hC.checked=s.hoverMode;const iHE=s.hoverMode;oI.disabled=!iHE;const oC=oI.closest('div');if(oC){oC.style.opacity=iHE?'1':'0.6';oC.style.pointerEvents=iHE?'auto':'none';}}}
function _populateFeatureSettings_internal(win,s,renderFn){win.querySelectorAll(`#${IDS.TAB_PANE_FEATURES} input[type="checkbox"][data-${DATA_ATTR.SECTION_ID}]`)?.forEach(cb=>{const sId=cb.getAttribute(`data-${DATA_ATTR.SECTION_ID}`);if(sId&&s.visibleSections.hasOwnProperty(sId)){cb.checked=s.visibleSections[sId];}else if(sId&&_defaultSettingsRef.visibleSections.hasOwnProperty(sId)){cb.checked=_defaultSettingsRef.visibleSections[sId]??false;}});renderFn(s);}
function _initializeActiveSettingsTab_internal(){if(!_settingsWindow)return;const tC=_settingsWindow.querySelector(`.${CSS.SETTINGS_TABS}`),cC=_settingsWindow.querySelector(`.${CSS.SETTINGS_TAB_CONTENT}`);if(!tC||!cC)return;const aTB=tC.querySelector(`.${CSS.TAB_BUTTON}.${CSS.ACTIVE}`);const tT=(aTB&&aTB.dataset[DATA_ATTR.TAB])?aTB.dataset[DATA_ATTR.TAB]:'general';tC.querySelectorAll(`.${CSS.TAB_BUTTON}`).forEach(b=>b.classList.toggle(CSS.ACTIVE,b.dataset[DATA_ATTR.TAB]===tT));cC.querySelectorAll(`.${CSS.TAB_PANE}`).forEach(p=>p.classList.toggle(CSS.ACTIVE,p.dataset[DATA_ATTR.TAB]===tT));}
function _loadFromStorage(){try{const s=GM_getValue(STORAGE_KEY,'{}');return JSON.parse(s||'{}');}catch(e){console.error(`${LOG_PREFIX} Error loading/parsing settings:`,e);return{};}}
function _validateAndMergeSettings(saved){let nS=JSON.parse(JSON.stringify(_defaultSettingsRef));nS=Utils.mergeDeep(nS,saved);_validateAndMergeCoreSettings_internal(nS,saved,_defaultSettingsRef);_validateAndMergeAppearanceSettings_internal(nS,saved,_defaultSettingsRef);_validateAndMergeFeatureSettings_internal(nS,saved,_defaultSettingsRef);_validateAndMergeCustomLists_internal(nS,saved,_defaultSettingsRef);_validateAndMergePredefinedOptions_internal(nS,saved,_defaultSettingsRef);_finalizeSectionOrder_internal(nS,saved,_defaultSettingsRef);return nS;}
function _validateAndMergeCoreSettings_internal(t,s,d){if(typeof t.sidebarPosition!=='object'||t.sidebarPosition===null||Array.isArray(t.sidebarPosition)){t.sidebarPosition=JSON.parse(JSON.stringify(d.sidebarPosition));}t.sidebarPosition.left=parseInt(t.sidebarPosition.left,10)||d.sidebarPosition.left;t.sidebarPosition.top=parseInt(t.sidebarPosition.top,10)||d.sidebarPosition.top;if(typeof t.sectionStates!=='object'||t.sectionStates===null||Array.isArray(t.sectionStates)){t.sectionStates={};}t.sidebarCollapsed=!!t.sidebarCollapsed;t.draggableHandleEnabled=typeof t.draggableHandleEnabled==='boolean'?t.draggableHandleEnabled:d.draggableHandleEnabled;t.interfaceLanguage=typeof s.interfaceLanguage==='string'?s.interfaceLanguage:d.interfaceLanguage;}
function _validateAndMergeAppearanceSettings_internal(t,s,d){t.sidebarWidth=Utils.clamp(parseInt(t.sidebarWidth,10)||d.sidebarWidth,90,270);t.fontSize=Utils.clamp(parseFloat(t.fontSize)||d.fontSize,8,24);t.headerIconSize=Utils.clamp(parseFloat(t.headerIconSize)||d.headerIconSize,8,32);t.verticalSpacingMultiplier=Utils.clamp(parseFloat(t.verticalSpacingMultiplier)||d.verticalSpacingMultiplier,0.05,1.5);t.idleOpacity=Utils.clamp(parseFloat(t.idleOpacity)||d.idleOpacity,0.1,1.0);t.hoverMode=!!t.hoverMode;const vT=['system','light','dark','minimal-light','minimal-dark'];if(t.theme==='minimal')t.theme='minimal-light';else if(!vT.includes(t.theme))t.theme=d.theme;}
function _validateAndMergeFeatureSettings_internal(t,s,d){if(typeof t.visibleSections!=='object'||t.visibleSections===null||Array.isArray(t.visibleSections)){t.visibleSections=JSON.parse(JSON.stringify(d.visibleSections));}const vSI=new Set(ALL_SECTION_DEFINITIONS.map(def=>def.id));Object.keys(d.visibleSections).forEach(id=>{if(!vSI.has(id)){console.warn(`${LOG_PREFIX} Invalid section ID in defaultSettings.visibleSections: ${id}`);}else if(typeof t.visibleSections[id]!=='boolean'){t.visibleSections[id]=d.visibleSections[id]??true;}});const vSM=['remember','expandAll','collapseAll'];if(!vSM.includes(t.sectionDisplayMode))t.sectionDisplayMode=d.sectionDisplayMode;t.accordionMode=!!t.accordionMode;const validButtonLocations=['header','topBlock','tools','none'];if(!validButtonLocations.includes(t.resetButtonLocation))t.resetButtonLocation=d.resetButtonLocation;if(!validButtonLocations.includes(t.verbatimButtonLocation))t.verbatimButtonLocation=d.verbatimButtonLocation;if(!validButtonLocations.includes(t.advancedSearchLinkLocation))t.advancedSearchLinkLocation=d.advancedSearchLinkLocation; if (!validButtonLocations.includes(t.personalizationButtonLocation)) { t.personalizationButtonLocation = d.personalizationButtonLocation; } const vCDM=['iconAndText','textOnly','iconOnly'];if(!vCDM.includes(t.countryDisplayMode))t.countryDisplayMode=d.countryDisplayMode;}
function _validateAndMergeCustomLists_internal(t,s,d){const lK=['favoriteSites','customLanguages','customTimeRanges','customFiletypes','customCountries'];lK.forEach(k=>{t[k]=Array.isArray(t[k])?t[k].filter(i=>i&&typeof i.text==='string'&&typeof i[k==='favoriteSites'?'url':'value']==='string'&&i.text.trim()!==''&&i[k==='favoriteSites'?'url':'value'].trim()!==''):JSON.parse(JSON.stringify(d[k]));});}
function _validateAndMergePredefinedOptions_internal(t,s,d){t.enabledPredefinedOptions=JSON.parse(JSON.stringify(d.enabledPredefinedOptions));const sEO=s.enabledPredefinedOptions;if(sEO&&typeof sEO==='object'){for(const type in d.enabledPredefinedOptions){if(PREDEFINED_OPTIONS[type]&&Array.isArray(sEO[type])){const vV=new Set(PREDEFINED_OPTIONS[type].map(opt=>opt.value));t.enabledPredefinedOptions[type]=sEO[type].filter(val=>typeof val==='string'&&vV.has(val));}}}for(const type in d.enabledPredefinedOptions){if(!Array.isArray(t.enabledPredefinedOptions[type])){t.enabledPredefinedOptions[type]=[...(d.enabledPredefinedOptions[type]||[])];}}}
function _finalizeSectionOrder_internal(t,s,d){const fO=[];const cVOS=new Set();const vSI=new Set(ALL_SECTION_DEFINITIONS.map(def=>def.id));const oS=(Array.isArray(s.sidebarSectionOrder)&&s.sidebarSectionOrder.length>0)?s.sidebarSectionOrder:d.sidebarSectionOrder;oS.forEach(id=>{if(typeof id==='string'&&vSI.has(id)&&t.visibleSections[id]===true&&!cVOS.has(id)){fO.push(id);cVOS.add(id);}});d.sidebarSectionOrder.forEach(id=>{if(typeof id==='string'&&vSI.has(id)&&t.visibleSections[id]===true&&!cVOS.has(id)){fO.push(id);}});t.sidebarSectionOrder=fO;}
const _sEH_internal = { [IDS.SETTING_WIDTH]:(t,vS)=>_hSLI(t,'sidebarWidth',vS,90,270,5), [IDS.SETTING_FONT_SIZE]:(t,vS)=>_hSLI(t,'fontSize',vS,8,24,0.5,v=>parseFloat(v).toFixed(1)), [IDS.SETTING_HEADER_ICON_SIZE]:(t,vS)=>_hSLI(t,'headerIconSize',vS,8,32,0.5,v=>parseFloat(v).toFixed(1)), [IDS.SETTING_VERTICAL_SPACING]:(t,vS)=>_hSLI(t,'verticalSpacingMultiplier',vS,0.05,1.5,0.05,v=>`x ${parseFloat(v).toFixed(2)}`), [IDS.SETTING_OPACITY]:(t,vS)=>_hSLI(t,'idleOpacity',vS,0.1,1.0,0.05,v=>parseFloat(v).toFixed(2)), [IDS.SETTING_INTERFACE_LANGUAGE]:(t)=>{const nL=t.value;if(_currentSettings.interfaceLanguage!==nL){_currentSettings.interfaceLanguage=nL;LocalizationService.updateActiveLocale(_currentSettings);_initMenuCommands_cb();publicApi.populateWindow();_buildSidebarUI_cb();}}, [IDS.SETTING_THEME]:(t)=>{_currentSettings.theme=t.value;_applySettingsToSidebar_cb(_currentSettings);}, [IDS.SETTING_HOVER]:(t)=>{_currentSettings.hoverMode=t.checked;const oI=_settingsWindow.querySelector(`#${IDS.SETTING_OPACITY}`);if(oI){const iHE=_currentSettings.hoverMode;oI.disabled=!iHE;const oC=oI.closest('div');if(oC){oC.style.opacity=iHE?'1':'0.6';oC.style.pointerEvents=iHE?'auto':'none';}}_applySettingsToSidebar_cb(_currentSettings);}, [IDS.SETTING_DRAGGABLE]:(t)=>{_currentSettings.draggableHandleEnabled=t.checked;_applySettingsToSidebar_cb(_currentSettings);DragManager.setDraggable(t.checked, sidebar, sidebar?.querySelector(`.${CSS.DRAG_HANDLE}`), _currentSettings, debouncedSaveSettings);}, [IDS.SETTING_ACCORDION]:(t)=>{const sMS=_settingsWindow.querySelector(`#${IDS.SETTING_SECTION_MODE}`);if(sMS?.value==='remember')_currentSettings.accordionMode=t.checked;else{t.checked=false;_currentSettings.accordionMode=false;}_applySettingsToSidebar_cb(_currentSettings);_applySectionCollapseStates_cb();}, [IDS.SETTING_SECTION_MODE]:(t)=>{_currentSettings.sectionDisplayMode=t.value;const aC=_settingsWindow.querySelector(`#${IDS.SETTING_ACCORDION}`);if(aC){const iRM=t.value==='remember';aC.disabled=!iRM;if(!iRM){aC.checked=false;_currentSettings.accordionMode=false;}else{aC.checked=_settingsBackup?.accordionMode??_currentSettings.accordionMode??_defaultSettingsRef.accordionMode;_currentSettings.accordionMode=aC.checked;}}_applySettingsToSidebar_cb(_currentSettings);_applySectionCollapseStates_cb();}, [IDS.SETTING_RESET_LOCATION]:(t)=>{_currentSettings.resetButtonLocation=t.value;_buildSidebarUI_cb();}, [IDS.SETTING_VERBATIM_LOCATION]:(t)=>{_currentSettings.verbatimButtonLocation=t.value;_buildSidebarUI_cb();}, [IDS.SETTING_ADV_SEARCH_LOCATION]:(t)=>{_currentSettings.advancedSearchLinkLocation=t.value;_buildSidebarUI_cb();}, [IDS.SETTING_PERSONALIZE_LOCATION]: (target) => { _currentSettings.personalizationButtonLocation = target.value; _buildSidebarUI_cb(); }, [IDS.SETTING_COUNTRY_DISPLAY_MODE]:(t)=>{_currentSettings.countryDisplayMode=t.value;_buildSidebarUI_cb();}, };
function _hSLI(t,sK,vS,min,max,step,fFn=v=>v){const v=Utils.clamp((step===1||step===5)?parseInt(t.value,10):parseFloat(t.value),min,max);if(isNaN(v))_currentSettings[sK]=_defaultSettingsRef[sK];else _currentSettings[sK]=v;if(vS)vS.textContent=fFn(_currentSettings[sK]);_applySettingsToSidebar_cb(_currentSettings);}
function _lUH_internal(e){const t=e.target;if(!t)return;const sI=t.id;const vS=(t.type==='range')?t.parentNode.querySelector(`.${CSS.RANGE_VALUE}`):null;if(_sEH_internal[sI]){if(t.type==='range')_sEH_internal[sI](t,vS);else _sEH_internal[sI](t);}}
const publicApi = { initialize: function(dS, applyCb, buildCb, collapseCb, menuCb, renderOrderCb) { if(_isInitialized) return; _defaultSettingsRef=dS; _applySettingsToSidebar_cb=applyCb; _buildSidebarUI_cb=buildCb; _applySectionCollapseStates_cb=collapseCb; _initMenuCommands_cb=menuCb; _renderSectionOrderList_ext_cb=renderOrderCb; this.load(); this.buildSkeleton(); _isInitialized=true; }, load: function(){const s=_loadFromStorage();_currentSettings=_validateAndMergeSettings(s);LocalizationService.updateActiveLocale(_currentSettings);}, save: function(lC='SaveBtn'){try{GM_setValue(STORAGE_KEY,JSON.stringify(_currentSettings));console.log(`${LOG_PREFIX} Settings saved by SM${lC?` (${lC})`:''}.`);_settingsBackup=JSON.parse(JSON.stringify(_currentSettings));}catch(e){console.error(`${LOG_PREFIX} SM save error:`,e);NotificationManager.show('alert_generic_error', { context: 'saving settings' }, 'error');}}, reset: function(){if(confirm(_('confirm_reset_settings'))){_currentSettings=JSON.parse(JSON.stringify(_defaultSettingsRef));if(!_currentSettings.sidebarSectionOrder||_currentSettings.sidebarSectionOrder.length===0){_currentSettings.sidebarSectionOrder=[..._defaultSettingsRef.sidebarSectionOrder];}LocalizationService.updateActiveLocale(_currentSettings);this.populateWindow();_applySettingsToSidebar_cb(_currentSettings);_buildSidebarUI_cb();_initMenuCommands_cb();_showGlobalMessage('alert_settings_reset_success',{},'success',4000);}}, resetAllFromMenu: function(){if(confirm(_('confirm_reset_all_menu'))){try{GM_setValue(STORAGE_KEY,JSON.stringify(_defaultSettingsRef));alert(_('alert_reset_all_menu_success'));}catch(e){_showGlobalMessage('alert_reset_all_menu_fail',{},'error',0);}}}, getCurrentSettings: function(){return _currentSettings;}, buildSkeleton: function(){if(_settingsWindow)return;_settingsOverlay=document.createElement('div');_settingsOverlay.id=IDS.SETTINGS_OVERLAY;_settingsWindow=document.createElement('div');_settingsWindow.id=IDS.SETTINGS_WINDOW;const h=document.createElement('div');h.classList.add(CSS.SETTINGS_HEADER);h.innerHTML=`<h3>${_('settingsTitle')}</h3><button class="${CSS.SETTINGS_CLOSE_BTN}" title="${_('settings_close_button_title')}">${SVG_ICONS.close}</button>`;const mB=document.createElement('div');mB.id=IDS.SETTINGS_MESSAGE_BAR;mB.classList.add(CSS.MESSAGE_BAR);mB.style.display='none';const ts=document.createElement('div');ts.classList.add(CSS.SETTINGS_TABS);ts.innerHTML=`<button class="${CSS.TAB_BUTTON} ${CSS.ACTIVE}" data-${DATA_ATTR.TAB}="general">${_('settings_tab_general')}</button> <button class="${CSS.TAB_BUTTON}" data-${DATA_ATTR.TAB}="appearance">${_('settings_tab_appearance')}</button> <button class="${CSS.TAB_BUTTON}" data-${DATA_ATTR.TAB}="features">${_('settings_tab_features')}</button> <button class="${CSS.TAB_BUTTON}" data-${DATA_ATTR.TAB}="custom">${_('settings_tab_custom')}</button>`;const c=document.createElement('div');c.classList.add(CSS.SETTINGS_TAB_CONTENT);c.innerHTML=`<div class="${CSS.TAB_PANE} ${CSS.ACTIVE}" data-${DATA_ATTR.TAB}="general" id="${IDS.TAB_PANE_GENERAL}"></div><div class="${CSS.TAB_PANE}" data-${DATA_ATTR.TAB}="appearance" id="${IDS.TAB_PANE_APPEARANCE}"></div><div class="${CSS.TAB_PANE}" data-${DATA_ATTR.TAB}="features" id="${IDS.TAB_PANE_FEATURES}"></div><div class="${CSS.TAB_PANE}" data-${DATA_ATTR.TAB}="custom" id="${IDS.TAB_PANE_CUSTOM}"></div>`;const f=document.createElement('div');f.classList.add(CSS.SETTINGS_FOOTER);f.innerHTML=`<button class="${CSS.RESET_BUTTON}">${_('settings_reset_all_button')}</button><button class="${CSS.CANCEL_BUTTON}">${_('settings_cancel_button')}</button><button class="${CSS.SAVE_BUTTON}">${_('settings_save_button')}</button>`;_settingsWindow.appendChild(h);_settingsWindow.appendChild(mB);_settingsWindow.appendChild(ts);_settingsWindow.appendChild(c);_settingsWindow.appendChild(f);_settingsOverlay.appendChild(_settingsWindow);document.body.appendChild(_settingsOverlay);this.bindEvents();}, populateWindow: function(){if(!_settingsWindow)return;try{_settingsWindow.querySelector(`.${CSS.SETTINGS_HEADER} h3`).textContent=_( 'settingsTitle');_settingsWindow.querySelector(`.${CSS.SETTINGS_CLOSE_BTN}`).title=_( 'settings_close_button_title');_settingsWindow.querySelector(`button[data-${DATA_ATTR.TAB}="general"]`).textContent=_( 'settings_tab_general');_settingsWindow.querySelector(`button[data-${DATA_ATTR.TAB}="appearance"]`).textContent=_( 'settings_tab_appearance');_settingsWindow.querySelector(`button[data-${DATA_ATTR.TAB}="features"]`).textContent=_( 'settings_tab_features');_settingsWindow.querySelector(`button[data-${DATA_ATTR.TAB}="custom"]`).textContent=_( 'settings_tab_custom');_settingsWindow.querySelector(`.${CSS.RESET_BUTTON}`).textContent=_( 'settings_reset_all_button');_settingsWindow.querySelector(`.${CSS.CANCEL_BUTTON}`).textContent=_( 'settings_cancel_button');_settingsWindow.querySelector(`.${CSS.SAVE_BUTTON}`).textContent=_( 'settings_save_button');const pG=_settingsWindow.querySelector(`#${IDS.TAB_PANE_GENERAL}`);if(pG)pG.innerHTML=_createPaneGeneralHTML_internal();const pA=_settingsWindow.querySelector(`#${IDS.TAB_PANE_APPEARANCE}`);if(pA)pA.innerHTML=_createPaneAppearanceHTML_internal();const pF=_settingsWindow.querySelector(`#${IDS.TAB_PANE_FEATURES}`);if(pF)pF.innerHTML=_createPaneFeaturesHTML_internal();const pC=_settingsWindow.querySelector(`#${IDS.TAB_PANE_CUSTOM}`);if(pC)pC.innerHTML=_createPaneCustomHTML_internal();_populateGeneralSettings_internal(_settingsWindow,_currentSettings);_populateAppearanceSettings_internal(_settingsWindow,_currentSettings);_populateFeatureSettings_internal(_settingsWindow,_currentSettings, _renderSectionOrderList_ext_cb);ModalManager.resetEditStateGlobally();_initializeActiveSettingsTab_internal();this.bindLiveUpdateEvents();this.bindFeaturesTabEvents(); }catch(e){_showGlobalMessage('alert_init_fail',{scriptName:SCRIPT_INTERNAL_NAME,error:"Settings UI pop err"},'error',0);}}, show: function(){if(!_settingsOverlay||!_settingsWindow)return;_settingsBackup=JSON.parse(JSON.stringify(_currentSettings));LocalizationService.updateActiveLocale(_currentSettings);this.populateWindow();applyThemeToElement(_settingsWindow,_currentSettings.theme);applyThemeToElement(_settingsOverlay,_currentSettings.theme);_settingsOverlay.style.display='flex';}, hide: function(isC=false){if(!_settingsOverlay)return;ModalManager.resetEditStateGlobally();if(ModalManager.isModalOpen())ModalManager.hide(true);_settingsOverlay.style.display='none';const mB=document.getElementById(IDS.SETTINGS_MESSAGE_BAR);if(mB)mB.style.display='none';if(isC&&_settingsBackup&&Object.keys(_settingsBackup).length>0){_currentSettings=JSON.parse(JSON.stringify(_settingsBackup));LocalizationService.updateActiveLocale(_currentSettings);this.populateWindow();_applySettingsToSidebar_cb(_currentSettings);_buildSidebarUI_cb();_initMenuCommands_cb();}else if(isC){console.warn(`${LOG_PREFIX} SM: Cancelled, no backup.`);}}, bindEvents: function(){if(!_settingsWindow||_settingsWindow.dataset.eventsBound==='true')return;_settingsWindow.querySelector(`.${CSS.SETTINGS_CLOSE_BTN}`)?.addEventListener('click',()=>this.hide(true));_settingsWindow.querySelector(`.${CSS.CANCEL_BUTTON}`)?.addEventListener('click',()=>this.hide(true));_settingsWindow.querySelector(`.${CSS.SAVE_BUTTON}`)?.addEventListener('click',()=>{this.save();LocalizationService.updateActiveLocale(_currentSettings);_initMenuCommands_cb();_buildSidebarUI_cb();this.hide(false);});_settingsWindow.querySelector(`.${CSS.RESET_BUTTON}`)?.addEventListener('click',()=>this.reset());const tC=_settingsWindow.querySelector(`.${CSS.SETTINGS_TABS}`);if(tC){tC.addEventListener('click',e=>{const tg=e.target.closest(`.${CSS.TAB_BUTTON}`);if(tg&&!tg.classList.contains(CSS.ACTIVE)){ModalManager.resetEditStateGlobally();const tT=tg.dataset[DATA_ATTR.TAB];if(!tT)return;tC.querySelectorAll(`.${CSS.TAB_BUTTON}`).forEach(b=>b.classList.remove(CSS.ACTIVE));tg.classList.add(CSS.ACTIVE);_settingsWindow.querySelector(`.${CSS.SETTINGS_TAB_CONTENT}`)?.querySelectorAll(`.${CSS.TAB_PANE}`)?.forEach(p=>p.classList.remove(CSS.ACTIVE));_settingsWindow.querySelector(`.${CSS.SETTINGS_TAB_CONTENT} .${CSS.TAB_PANE}[data-${DATA_ATTR.TAB}="${tT}"]`)?.classList.add(CSS.ACTIVE);}});}_settingsWindow.dataset.eventsBound='true';const cTP=_settingsWindow.querySelector(`#${IDS.TAB_PANE_CUSTOM}`);if(cTP){cTP.addEventListener('click',(e)=>{const b=e.target.closest(`button.${CSS.MANAGE_CUSTOM_BUTTON}`);if(b){const mT=b.dataset[DATA_ATTR.MANAGE_TYPE];if(mT){ModalManager.openManageCustomOptions(mT,_currentSettings,PREDEFINED_OPTIONS,(tCI,nEP,cIK,pOTK)=>{if(cIK){_currentSettings[cIK]=tCI;}if(pOTK&&nEP){_currentSettings.enabledPredefinedOptions[pOTK]=nEP;}_buildSidebarUI_cb();this.populateWindow();});}}});}}, bindLiveUpdateEvents: function(){if(!_settingsWindow)return;_settingsWindow.querySelectorAll('input[type="range"]').forEach(el=>{el.removeEventListener('input',_lUH_internal);el.addEventListener('input',_lUH_internal);});_settingsWindow.querySelectorAll('select, input[type="checkbox"]:not([data-section-id])').forEach(el=>{if(_sEH_internal[el.id]){el.removeEventListener('change',_lUH_internal);el.addEventListener('change',_lUH_internal);}});}, bindFeaturesTabEvents: function() {const fP=_settingsWindow.querySelector(`#${IDS.TAB_PANE_FEATURES}`);if(fP){fP.querySelectorAll(`input[type="checkbox"][data-${DATA_ATTR.SECTION_ID}]`).forEach(cb=>{cb.removeEventListener('change',this._handleVisibleSectionChange);cb.addEventListener('change',this._handleVisibleSectionChange.bind(this));});const oLE=fP.querySelector(`#${IDS.SIDEBAR_SECTION_ORDER_LIST}`);if(oLE){oLE.removeEventListener('click',this._handleSectionOrderChange);oLE.addEventListener('click',this._handleSectionOrderChange.bind(this));}}}, _handleVisibleSectionChange: function(e){const t=e.target;const sI=t.getAttribute(`data-${DATA_ATTR.SECTION_ID}`);if(sI&&_currentSettings.visibleSections.hasOwnProperty(sI)){_currentSettings.visibleSections[sI]=t.checked;_finalizeSectionOrder_internal(_currentSettings,_currentSettings,_defaultSettingsRef);_renderSectionOrderList_ext_cb(_currentSettings);_buildSidebarUI_cb();}}, _handleSectionOrderChange: function(e){const tB=e.target.closest('button.move-section-up, button.move-section-down');if(!tB)return;const lI=tB.closest('li[data-section-id]');if(!lI)return;const sITM=lI.dataset.sectionId;let vOS=_currentSettings.sidebarSectionOrder.filter(id=>_currentSettings.visibleSections[id]);const cIV=vOS.indexOf(sITM);if(cIV===-1)return;let nIV=-1;if(tB.classList.contains('move-section-up')&&cIV>0){nIV=cIV-1;}else if(tB.classList.contains('move-section-down')&&cIV<vOS.length-1){nIV=cIV+1;}if(nIV!==-1){[vOS[cIV],vOS[nIV]]=[vOS[nIV],vOS[cIV]];let nFO=[...vOS];_defaultSettingsRef.sidebarSectionOrder.forEach(id=>{if(!_currentSettings.visibleSections[id]&&!nFO.includes(id)){}});_currentSettings.sidebarSectionOrder=[...nFO];_defaultSettingsRef.sidebarSectionOrder.forEach(id=>{if(!_currentSettings.visibleSections[id]&&!_currentSettings.sidebarSectionOrder.includes(id)){_currentSettings.sidebarSectionOrder.push(id);}});_renderSectionOrderList_ext_cb(_currentSettings);}} }; return publicApi; })();
const DragManager = (function() { /* ... (same as before) ... */ let _isDragging = false; let _dragStartX, _dragStartY, _sidebarStartX, _sidebarStartY; let _sidebarElement, _handleElement; let _settingsManagerRef, _saveCallbackRef; function _getEventCoordinates(e) { return (e.touches && e.touches.length > 0) ? { x: e.touches[0].clientX, y: e.touches[0].clientY } : { x: e.clientX, y: e.clientY }; } function _startDrag(e) { const currentSettings = _settingsManagerRef.getCurrentSettings(); if (!currentSettings.draggableHandleEnabled || currentSettings.sidebarCollapsed || (e.type === 'mousedown' && e.button !== 0)) { return; } e.preventDefault(); _isDragging = true; const coords = _getEventCoordinates(e); _dragStartX = coords.x; _dragStartY = coords.y; _sidebarStartX = _sidebarElement.offsetLeft; _sidebarStartY = _sidebarElement.offsetTop; _sidebarElement.style.cursor = 'grabbing'; _sidebarElement.style.userSelect = 'none'; document.body.style.cursor = 'grabbing'; } function _drag(e) { if (!_isDragging) return; e.preventDefault(); const coords = _getEventCoordinates(e); const dx = coords.x - _dragStartX; const dy = coords.y - _dragStartY; let newLeft = _sidebarStartX + dx; let newTop = _sidebarStartY + dy; const maxLeft = window.innerWidth - (_sidebarElement?.offsetWidth ?? 0); const maxTop = window.innerHeight - (_sidebarElement?.offsetHeight ?? 0); newLeft = Utils.clamp(newLeft, 0, maxLeft); newTop = Utils.clamp(newTop, MIN_SIDEBAR_TOP_POSITION, maxTop); if (_sidebarElement) { _sidebarElement.style.left = `${newLeft}px`; _sidebarElement.style.top = `${newTop}px`; } } function _stopDrag() { if (_isDragging) { _isDragging = false; if (_sidebarElement) { _sidebarElement.style.cursor = 'default'; _sidebarElement.style.userSelect = ''; } document.body.style.cursor = ''; const currentSettings = _settingsManagerRef.getCurrentSettings(); if (!currentSettings.sidebarPosition) currentSettings.sidebarPosition = {}; currentSettings.sidebarPosition.left = _sidebarElement.offsetLeft; currentSettings.sidebarPosition.top = _sidebarElement.offsetTop; if (typeof _saveCallbackRef === 'function') { _saveCallbackRef('Drag Stop'); } } } return { init: function(sidebarEl, handleEl, settingsMgr, saveCb) { _sidebarElement = sidebarEl; _handleElement = handleEl; _settingsManagerRef = settingsMgr; _saveCallbackRef = saveCb; if (_handleElement) { _handleElement.addEventListener('mousedown', _startDrag); _handleElement.addEventListener('touchstart', _startDrag, { passive: false }); } document.addEventListener('mousemove', _drag); document.addEventListener('touchmove', _drag, { passive: false }); document.addEventListener('mouseup', _stopDrag); document.addEventListener('touchend', _stopDrag); document.addEventListener('touchcancel', _stopDrag); }, setDraggable: function(isEnabled, sidebarEl, handleEl) { _sidebarElement = sidebarEl; _handleElement = handleEl; if (_handleElement) { _handleElement.style.display = isEnabled ? 'block' : 'none'; } } }; })();
const URLActionManager = (function() { /* ... (same as before, with personalization functions and debug logs commented out) ... */
function _getURLObject() { try { return new URL(window.location.href); } catch (e) { console.error(`${LOG_PREFIX} Error creating URL object: `, e); return null; }}
function _navigateTo(url) { const urlString = url.toString(); /* console.log(`${LOG_PREFIX} Navigating to: ${urlString}`); */ window.location.href = urlString; }
function _setSearchParam(urlObj, paramName, value) { urlObj.searchParams.set(paramName, value); }
function _deleteSearchParam(urlObj, paramName) { urlObj.searchParams.delete(paramName); }
function _getTbsParts(urlObj) { const tbs = urlObj.searchParams.get('tbs'); return tbs ? tbs.split(',').filter(p => p.trim() !== '') : []; }
function _setTbsParam(urlObj, tbsPartsArray) { const newTbsValue = tbsPartsArray.join(','); if (newTbsValue) { _setSearchParam(urlObj, 'tbs', newTbsValue); } else { _deleteSearchParam(urlObj, 'tbs'); }}
return {
triggerResetFilters: function() { try { const u = _getURLObject(); if (!u) return; const q = u.searchParams.get('q') || ''; const nP = new URLSearchParams(); const cQ = q.replace(/\s*site:[\w.-]+\s*/g, ' ').trim(); if (cQ) { nP.set('q', cQ); } u.search = nP.toString(); _navigateTo(u); } catch (e) { NotificationManager.show('alert_error_resetting_filters', {}, 'error', 5000); }},
triggerToggleVerbatim: function() { try { const u = _getURLObject(); if (!u) return; let tP = _getTbsParts(u); const vP = 'li:1'; const iCA = tP.includes(vP); tP = tP.filter(p => p !== vP); if (!iCA) { tP.push(vP); } _setTbsParam(u, tP); _navigateTo(u); } catch (e) { NotificationManager.show('alert_error_toggling_verbatim', {}, 'error', 5000); }},
isPersonalizationActive: function() { try { const currentUrl = _getURLObject(); if (!currentUrl) { return true; } return currentUrl.searchParams.get('pws') !== '0'; } catch (e) { console.warn(`${LOG_PREFIX} [URLActionManager.isPersonalizationActive] Error checking personalization status:`, e); return true; } },
triggerTogglePersonalization: function() { /* console.log(`${LOG_PREFIX} [URLActionManager.triggerTogglePersonalization] Clicked!`); */ try { const u = _getURLObject(); if (!u) { return; } const personalizationCurrentlyActive = URLActionManager.isPersonalizationActive(); if (personalizationCurrentlyActive) { _setSearchParam(u, 'pws', '0'); } else { _deleteSearchParam(u, 'pws'); } _navigateTo(u); } catch (e) { NotificationManager.show('alert_error_toggling_personalization', {}, 'error', 5000); console.error(`${LOG_PREFIX} [URLActionManager.triggerTogglePersonalization] Error:`, e); } },
applyFilter: function(type, value) { try { const u = _getURLObject(); if (!u) return; let tP = _getTbsParts(u); let pTP; const isTimeFilter = ['qdr'].includes(type); const isStandaloneParam = ['lr', 'cr'].includes(type); const isFileType = type === 'filetype'; if (isTimeFilter) { pTP = tP.filter(p => !p.startsWith(`${type}:`) && !p.startsWith('cdr:') && !p.startsWith('cd_min:') && !p.startsWith('cd_max:')); if (value !== '') pTP.push(`${type}:${value}`); } else if (isFileType) { _deleteSearchParam(u, 'as_filetype'); _deleteSearchParam(u, 'filetype'); pTP = tP.filter(p => !p.startsWith('ft:') && !p.startsWith('aft:')); if (value !== '') _setSearchParam(u, 'as_filetype', value); } else if (isStandaloneParam) { _deleteSearchParam(u, type); if (value !== '') _setSearchParam(u, type, value); pTP = tP; } else { pTP = tP; } _setTbsParam(u, pTP); _navigateTo(u); } catch (e) { NotificationManager.show('alert_error_applying_filter', { type: type, value: value }, 'error', 5000); }},
applySiteSearch: function(siteUrl) { if (!siteUrl) return; try { const u = _getURLObject(); if (!u) return; const q = u.searchParams.get('q') || ''; const qNS = q.replace(/\s*site:[\w.-]+\s*/g, ' ').trim(); const nQ = `${qNS} site:${siteUrl}`.trim(); _setSearchParam(u, 'q', nQ); _deleteSearchParam(u, 'tbs'); _deleteSearchParam(u, 'lr'); _deleteSearchParam(u, 'cr'); _deleteSearchParam(u, 'as_filetype'); _deleteSearchParam(u, 'filetype'); _navigateTo(u); } catch (e) { NotificationManager.show('alert_error_applying_site_search', { site: siteUrl }, 'error', 5000); }},
clearSiteSearch: function() { try { const u = _getURLObject(); if (!u) return; const q = u.searchParams.get('q') || ''; const nQ = q.replace(/\s*site:[\w.-]+\s*/g, ' ').trim(); if (nQ) { _setSearchParam(u, 'q', nQ); } else { _deleteSearchParam(u, 'q'); } _navigateTo(u); } catch (e) { NotificationManager.show('alert_error_clearing_site_search', {}, 'error', 5000); }},
isVerbatimActive: function() { try { const currentUrl = _getURLObject(); if (!currentUrl) return false; return /li:1/.test(currentUrl.searchParams.get('tbs') || ''); } catch (e) { console.warn(`${LOG_PREFIX} Error checking verbatim status:`, e); return false; }},
applyDateRange: function(dateMinStr, dateMaxStr) { try { const url = _getURLObject(); if (!url) return; let dateTbsPart = 'cdr:1'; if (dateMinStr) { const [y, m, d] = dateMinStr.split('-'); dateTbsPart += `,cd_min:${m}/${d}/${y}`; } if (dateMaxStr) { const [y, m, d] = dateMaxStr.split('-'); dateTbsPart += `,cd_max:${m}/${d}/${y}`; } let tbsParts = _getTbsParts(url); let preservedTbsParts = tbsParts.filter(p => !p.startsWith('qdr:') && !p.startsWith('cdr:') && !p.startsWith('cd_min:') && !p.startsWith('cd_max:')); let newTbsParts = [...preservedTbsParts, dateTbsPart]; _setTbsParam(url, newTbsParts); _navigateTo(url); } catch (e) { NotificationManager.show('alert_error_applying_date', {}, 'error', 5000); }}
};
})();
function addGlobalStyles() { /* ... (same as before) ... */ if (typeof window.GSCS_Namespace !== 'undefined' && typeof window.GSCS_Namespace.stylesText === 'string') { const cleanedCSS = window.GSCS_Namespace.stylesText.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1').replace(/\n\s*\n/g, '\n'); GM_addStyle(cleanedCSS); } else { console.error(`${LOG_PREFIX} CRITICAL: CSS styles provider not found.`); if (typeof IDS !== 'undefined' && IDS.SIDEBAR) { GM_addStyle(`#${IDS.SIDEBAR} { border: 3px dashed red !important; padding: 15px !important; background: white !important; color: red !important; } #${IDS.SIDEBAR}::before { content: "Error: CSS Missing!"; }`);} } }
function setupSystemThemeListener() { /* ... (same as before) ... */ if (systemThemeMediaQuery && systemThemeMediaQuery._sidebarThemeListener) { try { systemThemeMediaQuery.removeEventListener('change', systemThemeMediaQuery._sidebarThemeListener); } catch (e) {} systemThemeMediaQuery._sidebarThemeListener = null; } if (window.matchMedia) { systemThemeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const listener = (event) => { const cs = SettingsManager.getCurrentSettings(); if (sidebar && cs.theme === 'system') { applyThemeToElement(sidebar, 'system'); } }; systemThemeMediaQuery.addEventListener('change', listener); systemThemeMediaQuery._sidebarThemeListener = listener; } }
function buildSidebarSkeleton() { /* ... (same as before) ... */ sidebar = document.createElement('div'); sidebar.id = IDS.SIDEBAR; const header = document.createElement('div'); header.classList.add(CSS.SIDEBAR_HEADER); const collapseBtn = document.createElement('button'); collapseBtn.id = IDS.COLLAPSE_BUTTON; collapseBtn.innerHTML = SVG_ICONS.chevronLeft; collapseBtn.title = _('sidebar_collapse_title'); const dragHandle = document.createElement('div'); dragHandle.classList.add(CSS.DRAG_HANDLE); dragHandle.title = _('sidebar_drag_title'); const settingsBtn = document.createElement('button'); settingsBtn.id = IDS.SETTINGS_BUTTON; settingsBtn.classList.add(CSS.SETTINGS_BUTTON); settingsBtn.innerHTML = SVG_ICONS.settings; settingsBtn.title = _('sidebar_settings_title'); header.appendChild(collapseBtn); header.appendChild(dragHandle); header.appendChild(settingsBtn); sidebar.appendChild(header); document.body.appendChild(sidebar); }
function applySettings(settingsToApply) { /* ... (same as before) ... */ if (!sidebar) return; const currentSettings = settingsToApply || SettingsManager.getCurrentSettings(); let targetTop = currentSettings.sidebarPosition.top; targetTop = Math.max(MIN_SIDEBAR_TOP_POSITION, targetTop); sidebar.style.left = `${currentSettings.sidebarPosition.left}px`; sidebar.style.top = `${targetTop}px`; sidebar.style.setProperty('--sidebar-font-base-size', `${currentSettings.fontSize}px`); sidebar.style.setProperty('--sidebar-header-icon-base-size', `${currentSettings.headerIconSize}px`); sidebar.style.setProperty('--sidebar-spacing-multiplier', currentSettings.verticalSpacingMultiplier); if (!currentSettings.sidebarCollapsed) { sidebar.style.width = `${currentSettings.sidebarWidth}px`; } else { sidebar.style.width = '40px';} applyThemeToElement(sidebar, currentSettings.theme); if (sidebar._hoverListeners) { sidebar.removeEventListener('mouseenter', sidebar._hoverListeners.enter); sidebar.removeEventListener('mouseleave', sidebar._hoverListeners.leave); sidebar._hoverListeners = null; sidebar.style.opacity = '1';} if (currentSettings.hoverMode && !currentSettings.sidebarCollapsed) { const idleOpacityValue = currentSettings.idleOpacity; sidebar.style.opacity = idleOpacityValue.toString(); const enterL = () => { if (!currentSettings.sidebarCollapsed) sidebar.style.opacity = '1'; }; const leaveL = () => { if (!currentSettings.sidebarCollapsed) sidebar.style.opacity = idleOpacityValue.toString(); }; sidebar.addEventListener('mouseenter', enterL); sidebar.addEventListener('mouseleave', leaveL); sidebar._hoverListeners = { enter: enterL, leave: leaveL }; } else { sidebar.style.opacity = '1'; } applySidebarCollapseVisuals(currentSettings.sidebarCollapsed); }
// --- UI Building Helper Functions ---
function _parseTimeValueToMinutes(timeValue) {
if (!timeValue || typeof timeValue !== 'string') return Infinity;
const match = timeValue.match(/^([hdwmy])(\d*)$/i);
if (!match) return Infinity;
const unit = match[1].toLowerCase();
const number = parseInt(match[2] || '1', 10);
if (isNaN(number)) return Infinity;
switch (unit) {
case 'h': return number * 60;
case 'd': return number * 24 * 60;
case 'w': return number * 7 * 24 * 60;
case 'm': return number * 30 * 24 * 60; // Approximation
case 'y': return number * 365 * 24 * 60; // Approximation
default: return Infinity;
}
}
function _prepareFilterOptions(id, scriptDefinedOptions, enabledPredefinedValues = [], customOptions = [], predefinedOptions = []) {
const CHINESE_LANG_SORT_ORDER = { 'lang_zh-TW|lang_zh-CN': 0, 'lang_zh-TW': 1, 'lang_zh-CN': 2 };
const combinedOptions = [];
const addedValues = new Set();
const isLanguageSection = (id === 'sidebar-section-language');
const isTimeSection = (id === 'sidebar-section-time');
const validCustomOptions = Array.isArray(customOptions) ? customOptions.filter(cOpt => cOpt && typeof cOpt.text === 'string' && typeof cOpt.value === 'string') : [];
validCustomOptions.forEach(opt => {
combinedOptions.push({ text: opt.text, value: opt.value, originalText: opt.text, isCustom: true });
addedValues.add(opt.value);
});
scriptDefinedOptions.forEach(opt => {
if (opt && typeof opt.textKey === 'string' && typeof opt.v === 'string' && !addedValues.has(opt.v)) {
const translatedText = _(opt.textKey);
combinedOptions.push({ text: translatedText, value: opt.v, originalText: translatedText });
addedValues.add(opt.v);
}
});
const enabledSet = new Set(enabledPredefinedValues);
if (Array.isArray(predefinedOptions)) {
predefinedOptions.forEach(opt => {
if (opt && typeof opt.textKey === 'string' && typeof opt.value === 'string' && enabledSet.has(opt.value) && !addedValues.has(opt.value)) {
const translatedText = _(opt.textKey);
combinedOptions.push({ text: translatedText, value: opt.value, originalText: translatedText });
}
});
}
combinedOptions.sort((a, b) => {
if (a.value === '' && b.value !== '') return -1;
if (a.value !== '' && b.value === '') return 1;
if (a.value === '' && b.value === '') return 0;
if (isTimeSection) {
const timeA = _parseTimeValueToMinutes(a.value);
const timeB = _parseTimeValueToMinutes(b.value);
if (timeA !== timeB) {
return timeA - timeB;
}
return (a.originalText || a.text).localeCompare(b.originalText || b.text, LocalizationService.getCurrentLocale() === 'en' ? undefined : LocalizationService.getCurrentLocale(), { numeric: true, sensitivity: 'base' });
} else if (isLanguageSection) {
const aS = CHINESE_LANG_SORT_ORDER.hasOwnProperty(a.value);
const bS = CHINESE_LANG_SORT_ORDER.hasOwnProperty(b.value);
if (aS && bS) return CHINESE_LANG_SORT_ORDER[a.value] - CHINESE_LANG_SORT_ORDER[b.value];
else if (aS) return -1;
else if (bS) return 1;
}
const sTA = a.text;
const sTB = b.text;
const sL = LocalizationService.getCurrentLocale() === 'en' ? undefined : LocalizationService.getCurrentLocale();
return sTA.localeCompare(sTB, sL, { numeric: true, sensitivity: 'base' });
});
return combinedOptions;
}
function _createFilterOptionElement(optionData, filterParam, isCountrySection, countryDisplayMode) {
const optionElement = document.createElement('div');
optionElement.classList.add(CSS.FILTER_OPTION);
const displayText = optionData.text;
if (isCountrySection) {
const { icon, text: countryTextOnly } = Utils.parseIconAndText(displayText);
switch (countryDisplayMode) {
case 'textOnly': optionElement.textContent = countryTextOnly || displayText; break;
case 'iconOnly': if (icon) { optionElement.innerHTML = `<span class="country-icon-container">${icon}</span>`; } else { optionElement.textContent = countryTextOnly || displayText; } break;
case 'iconAndText': default: if (icon) { const textPart = countryTextOnly || displayText.substring(icon.length).trim(); optionElement.innerHTML = `<span class="country-icon-container">${icon}</span>${textPart}`; } else { optionElement.textContent = displayText; } break;
}
} else {
optionElement.textContent = displayText;
}
optionElement.title = `${displayText} (${filterParam}=${optionData.value || _('filter_clear_tooltip_suffix')})`;
optionElement.dataset[DATA_ATTR.FILTER_TYPE] = filterParam;
optionElement.dataset[DATA_ATTR.FILTER_VALUE] = optionData.value;
return optionElement;
}
function buildSidebarUI() {
if (!sidebar) { console.error("Sidebar element not ready for buildSidebarUI"); return; }
const currentSettings = SettingsManager.getCurrentSettings();
const header = sidebar.querySelector(`.${CSS.SIDEBAR_HEADER}`);
if (!header) { console.error("Sidebar header not found in buildSidebarUI"); return; }
sidebar.querySelectorAll(`#${IDS.FIXED_TOP_BUTTONS}, .${CSS.SIDEBAR_CONTENT_WRAPPER}`).forEach(el => el.remove());
header.querySelectorAll(`.${CSS.HEADER_BUTTON}:not(#${IDS.SETTINGS_BUTTON}):not(#${IDS.COLLAPSE_BUTTON}), a.${CSS.HEADER_BUTTON}`).forEach(el => el.remove());
const rBL = currentSettings.resetButtonLocation;
const vBL = currentSettings.verbatimButtonLocation;
const aSL = currentSettings.advancedSearchLinkLocation;
const pznBL = currentSettings.personalizationButtonLocation;
const sBR = header.querySelector(`#${IDS.SETTINGS_BUTTON}`);
_buildSidebarHeaderControls(header, sBR, rBL, vBL, aSL, pznBL, _createAdvancedSearchElementHTML, _createPersonalizationButtonHTML, currentSettings);
const fTC = _buildSidebarFixedTopControls(rBL, vBL, aSL, pznBL, _createAdvancedSearchElementHTML, _createPersonalizationButtonHTML, currentSettings);
if (fTC) header.after(fTC);
const cW = document.createElement('div'); cW.classList.add(CSS.SIDEBAR_CONTENT_WRAPPER);
const sDM = new Map(ALL_SECTION_DEFINITIONS.map(def => [def.id, def]));
const sF = _buildSidebarSections(sDM, rBL, vBL, aSL, pznBL, _createAdvancedSearchElementHTML, _createPersonalizationButtonHTML, currentSettings, PREDEFINED_OPTIONS);
cW.appendChild(sF); sidebar.appendChild(cW);
_initializeSidebarEventListenersAndStates();
}
function _buildSidebarSections(sectionDefinitionMap, rBL, vBL, aSL, pznBL, createAdvancedSearchElementFn, createPersonalizationButtonFn, currentSettings, PREDEFINED_OPTIONS_REF) {
const contentFragment = document.createDocumentFragment();
currentSettings.sidebarSectionOrder.forEach(sectionId => {
if (!currentSettings.visibleSections[sectionId]) return;
const sectionData = sectionDefinitionMap.get(sectionId);
if (!sectionData) { console.warn(`${LOG_PREFIX} No definition for section ID: ${sectionId}`); return; }
let sectionElement = null;
const sectionTitleKey = sectionData.titleKey; const sectionIdForDisplay = sectionData.id;
switch (sectionData.type) {
case 'filter': sectionElement = createFilterSection(sectionIdForDisplay, sectionTitleKey, sectionData.scriptDefined, sectionData.param, currentSettings.enabledPredefinedOptions[sectionData.predefinedOptionsKey] || [], currentSettings[sectionData.customItemsKey] || [], PREDEFINED_OPTIONS_REF[sectionData.predefinedOptionsKey] || [], currentSettings.countryDisplayMode); break;
case 'date': sectionElement = _createDateSectionElement(sectionIdForDisplay, sectionTitleKey); break;
case 'site': sectionElement = _createSiteSearchSectionElement(sectionIdForDisplay, sectionTitleKey, currentSettings.favoriteSites); break;
case 'tools':
sectionElement = _createToolsSectionElement( sectionIdForDisplay, sectionTitleKey, rBL, vBL, aSL, pznBL, createAdvancedSearchElementFn, createPersonalizationButtonFn );
break;
default: console.warn(`${LOG_PREFIX} Unknown section type: ${sectionData.type} for ID: ${sectionIdForDisplay}`); break;
}
if (sectionElement) contentFragment.appendChild(sectionElement);
});
return contentFragment;
}
function createFilterSection(id, titleKey, scriptDefinedOptions, filterParam, enabledPredefinedValues = [], customOptions = [], predefinedOptions = [], countryDisplayMode) {
if (!sidebar) return null;
const { section, sectionContent, sectionTitle } = _createSectionShell(id, titleKey);
sectionTitle.textContent = _(titleKey);
const fragment = document.createDocumentFragment();
const isCountrySection = (id === 'sidebar-section-country');
const combinedOptions = _prepareFilterOptions(id, scriptDefinedOptions, enabledPredefinedValues, customOptions, predefinedOptions);
combinedOptions.forEach(option => {
fragment.appendChild(_createFilterOptionElement(option, filterParam, isCountrySection, countryDisplayMode));
});
sectionContent.innerHTML = ''; sectionContent.appendChild(fragment);
const oldListener = sectionContent._filterClickListener; if (oldListener) sectionContent.removeEventListener('click', oldListener);
const newListener = function(event) {
const target = event.target.closest(`.${CSS.FILTER_OPTION}`);
if (target && target.classList.contains(CSS.FILTER_OPTION)) {
event.preventDefault();
const clickedFilterType = target.dataset[DATA_ATTR.FILTER_TYPE];
const clickedFilterValue = target.dataset[DATA_ATTR.FILTER_VALUE];
if (typeof clickedFilterType !== 'undefined' && typeof clickedFilterValue !== 'undefined') {
this.querySelectorAll(`.${CSS.FILTER_OPTION}`).forEach(opt => opt.classList.remove(CSS.SELECTED));
target.classList.add(CSS.SELECTED);
if (clickedFilterValue === '') { const anyOpt = this.querySelector(`.${CSS.FILTER_OPTION}[data-${DATA_ATTR.FILTER_VALUE}=""]`); if (anyOpt) anyOpt.classList.add(CSS.SELECTED); }
URLActionManager.applyFilter(clickedFilterType, clickedFilterValue);
}
}
};
sectionContent.addEventListener('click', newListener); sectionContent._filterClickListener = newListener;
return section;
}
function _createSiteSearchSectionElement(sectionId, titleKey, favoriteSites) { const { section, sectionContent, sectionTitle } = _createSectionShell(sectionId, titleKey); sectionTitle.textContent = _(titleKey); const list = document.createElement('ul'); list.classList.add(CSS.CUSTOM_LIST); sectionContent.appendChild(list); populateSiteSearchList(list, favoriteSites); return section; }
function populateSiteSearchList(listElement, favoriteSitesArray) { if (!listElement) { console.error("Site search list element missing in populateSiteSearchList"); return; } listElement.innerHTML = ''; const sites = Array.isArray(favoriteSitesArray) ? favoriteSitesArray : []; const fragment = document.createDocumentFragment(); sites.forEach((site, index) => { if (site?.text && site?.url) { const li = document.createElement('li'); const opt = document.createElement('div'); opt.classList.add(CSS.FILTER_OPTION); opt.dataset[DATA_ATTR.SITE_URL] = site.url; opt.title = _('tooltip_site_search', { siteUrl: site.url }); opt.textContent = site.text; li.appendChild(opt); fragment.appendChild(li); }}); const clearLi = document.createElement('li'); const clearOpt = document.createElement('div'); clearOpt.classList.add(CSS.FILTER_OPTION); clearOpt.id = 'clear-site-search-option'; clearOpt.title = _('tooltip_clear_site_search'); clearOpt.textContent = _('filter_clear_site_search'); clearLi.appendChild(clearOpt); fragment.appendChild(clearLi); listElement.appendChild(fragment); if (!listElement.dataset[DATA_ATTR.LISTENER_ATTACHED]) { listElement.dataset[DATA_ATTR.LISTENER_ATTACHED] = 'true'; listElement.addEventListener('click', (event) => { const target = event.target.closest(`.${CSS.FILTER_OPTION}`); if (target) { listElement.querySelectorAll(`.${CSS.FILTER_OPTION}.${CSS.SELECTED}`).forEach(opt => opt.classList.remove(CSS.SELECTED)); if (target.id === 'clear-site-search-option') { URLActionManager.clearSiteSearch(); } else { const siteUrl = target.dataset[DATA_ATTR.SITE_URL]; if (siteUrl) { URLActionManager.applySiteSearch(siteUrl); target.classList.add(CSS.SELECTED); } } } }); } }
function renderSectionOrderList(settingsRef) { const settingsWindowEl = document.getElementById(IDS.SETTINGS_WINDOW); const orderListElement = settingsWindowEl?.querySelector(`#${IDS.SIDEBAR_SECTION_ORDER_LIST}`); if (!orderListElement) { return; } orderListElement.innerHTML = ''; const currentSettings = settingsRef || SettingsManager.getCurrentSettings(); const visibleOrderedSections = currentSettings.sidebarSectionOrder.filter(id => currentSettings.visibleSections[id]); if (visibleOrderedSections.length === 0) { orderListElement.innerHTML = `<li><span style="font-style:italic;color:var(--settings-tab-color);">${_('settings_no_orderable_sections')}</span></li>`; return; } const fragment = document.createDocumentFragment(); visibleOrderedSections.forEach((sectionId, index) => { const definition = ALL_SECTION_DEFINITIONS.find(def => def.id === sectionId); const displayName = definition ? _(definition.titleKey) : sectionId; const listItem = document.createElement('li'); listItem.dataset.sectionId = sectionId; const nameSpan = document.createElement('span'); nameSpan.textContent = displayName; listItem.appendChild(nameSpan); const buttonsDiv = document.createElement('div'); buttonsDiv.className = 'order-buttons'; const upButton = document.createElement('button'); upButton.innerHTML = '↑'; upButton.title = _('settings_move_up_title'); upButton.classList.add('move-section-up'); upButton.disabled = (index === 0); buttonsDiv.appendChild(upButton); const downButton = document.createElement('button'); downButton.innerHTML = '↓'; downButton.title = _('settings_move_down_title'); downButton.classList.add('move-section-down'); downButton.disabled = (index === visibleOrderedSections.length - 1); buttonsDiv.appendChild(downButton); listItem.appendChild(buttonsDiv); fragment.appendChild(listItem); }); orderListElement.appendChild(fragment); }
function _initMenuCommands() { if (typeof GM_registerMenuCommand === 'function') { const openSettingsText = _('menu_open_settings'); const resetAllText = _('menu_reset_all_settings'); if (typeof GM_unregisterMenuCommand === 'function') { try { GM_unregisterMenuCommand(openSettingsText); GM_unregisterMenuCommand(resetAllText); } catch (e) {} } GM_registerMenuCommand(openSettingsText, SettingsManager.show.bind(SettingsManager)); GM_registerMenuCommand(resetAllText, SettingsManager.resetAllFromMenu.bind(SettingsManager)); } }
function _createSectionShell(id, titleKey) { const section = document.createElement('div'); section.id = id; section.classList.add(CSS.SIDEBAR_SECTION); const sectionTitle = document.createElement('div'); sectionTitle.classList.add(CSS.SECTION_TITLE); sectionTitle.textContent = _(titleKey); section.appendChild(sectionTitle); const sectionContent = document.createElement('div'); sectionContent.classList.add(CSS.SECTION_CONTENT); section.appendChild(sectionContent); return { section, sectionContent, sectionTitle }; }
function _createDateSectionElement(sectionId, titleKey) {
const { section, sectionContent, sectionTitle } = _createSectionShell(sectionId, titleKey);
sectionTitle.textContent = _(titleKey);
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, '0');
const dd = String(today.getDate()).padStart(2, '0');
const todayString = `${yyyy}-${mm}-${dd}`;
sectionContent.innerHTML =
`<label class="${CSS.DATE_INPUT_LABEL}" for="${IDS.DATE_MIN}">${_('date_range_from')}</label>` +
`<input type="date" class="${CSS.DATE_INPUT}" id="${IDS.DATE_MIN}" max="${todayString}">` +
`<label class="${CSS.DATE_INPUT_LABEL}" for="${IDS.DATE_MAX}">${_('date_range_to')}</label>` +
`<input type="date" class="${CSS.DATE_INPUT}" id="${IDS.DATE_MAX}" max="${todayString}">` +
`<span id="${IDS.DATE_RANGE_ERROR_MSG}" class="${CSS.DATE_RANGE_ERROR_MSG} ${CSS.INPUT_ERROR_MESSAGE}"></span>` +
`<button class="${CSS.TOOL_BUTTON} apply-date-range">${_('tool_apply_date')}</button>`;
return section;
}
function _createStandardButton({ id = null, className, svgIcon, textContent = null, title, clickHandler, isActive = false }) {
const button = document.createElement('button');
if (id) button.id = id;
button.classList.add(className);
if (isActive) button.classList.add(CSS.ACTIVE);
button.title = title;
let content = svgIcon || '';
if (textContent) { content = svgIcon ? `${svgIcon} ${textContent}` : textContent; }
button.innerHTML = content.trim();
if (clickHandler) {
if (!button.dataset[DATA_ATTR.LISTENER_ATTACHED]) {
const wrappedClickHandler = function(event) {
clickHandler(event);
};
button.addEventListener('click', wrappedClickHandler);
button.dataset[DATA_ATTR.LISTENER_ATTACHED] = 'true';
}
}
return button;
}
function _createPersonalizationButtonHTML(forLocation = 'tools') {
const personalizationActive = URLActionManager.isPersonalizationActive();
const isIconOnlyLocation = (forLocation === 'header');
const svgIcon = SVG_ICONS.personalization || '';
const displayText = !isIconOnlyLocation ? _('tool_personalization_toggle') : '';
const titleKey = personalizationActive ? 'tooltip_toggle_personalization_off' : 'tooltip_toggle_personalization_on';
return _createStandardButton({
id: IDS.TOOL_PERSONALIZE,
className: (forLocation === 'header') ? CSS.HEADER_BUTTON : CSS.TOOL_BUTTON,
svgIcon: svgIcon,
textContent: displayText,
title: _(titleKey),
clickHandler: () => URLActionManager.triggerTogglePersonalization(),
isActive: personalizationActive
});
}
function _createAdvancedSearchElementHTML(isButtonLike = false) {
const el = document.createElement('a');
let iconHTML = SVG_ICONS.magnifyingGlass || '';
if (isButtonLike) {
el.classList.add(CSS.TOOL_BUTTON);
el.innerHTML = `${iconHTML} ${_('tool_advanced_search')}`;
} else {
el.classList.add(CSS.HEADER_BUTTON);
el.innerHTML = iconHTML;
}
const baseUrl = "https://www.google.com/advanced_search";
let finalUrl = baseUrl;
try {
const currentFullUrl = Utils.getCurrentURL();
if (currentFullUrl) {
const currentQuery = currentFullUrl.searchParams.get('q');
if (currentQuery) {
const queryWithoutSite = currentQuery.replace(/\s*site:[\w.-]+\s*/gi, ' ').trim();
if (queryWithoutSite) {
finalUrl = `${baseUrl}?as_q=${encodeURIComponent(queryWithoutSite)}`;
}
}
}
} catch (e) {
console.warn(`${LOG_PREFIX} Error constructing advanced search URL with query:`, e);
}
el.href = finalUrl;
el.target = "_blank";
el.rel = "noopener noreferrer";
el.title = _('link_advanced_search_title');
return el;
}
function _buildSidebarHeaderControls(headerEl, settingsBtnRef, rBL, vBL, aSL, pznBL, advSearchFn, personalizeBtnFn, settings) {
const verbatimActive = URLActionManager.isVerbatimActive();
const buttonsInOrder = [];
if (aSL === 'header' && advSearchFn && settings.advancedSearchLinkLocation !== 'none') {
buttonsInOrder.push(advSearchFn(false));
}
if (vBL === 'header' && settings.verbatimButtonLocation !== 'none') {
buttonsInOrder.push(_createStandardButton({ id: IDS.TOOL_VERBATIM, className: CSS.HEADER_BUTTON, svgIcon: SVG_ICONS.verbatim, title: _('tool_verbatim_search'), clickHandler: URLActionManager.triggerToggleVerbatim, isActive: verbatimActive }));
}
if (pznBL === 'header' && personalizeBtnFn && settings.personalizationButtonLocation !== 'none') {
buttonsInOrder.push(personalizeBtnFn('header'));
}
if (rBL === 'header' && settings.resetButtonLocation !== 'none') {
buttonsInOrder.push(_createStandardButton({ id: IDS.TOOL_RESET_BUTTON, className: CSS.HEADER_BUTTON, svgIcon: SVG_ICONS.reset, title: _('tool_reset_filters'), clickHandler: URLActionManager.triggerResetFilters }));
}
buttonsInOrder.forEach(btn => {
if (settingsBtnRef) {
headerEl.insertBefore(btn, settingsBtnRef);
} else {
headerEl.appendChild(btn);
}
});
}
function _buildSidebarFixedTopControls(rBL, vBL, aSL, pznBL, advSearchFn, personalizeBtnFn, settings) {
const fTBC = document.createElement('div');
fTBC.id = IDS.FIXED_TOP_BUTTONS;
const fTF = document.createDocumentFragment();
const verbatimActive = URLActionManager.isVerbatimActive();
if (rBL === 'topBlock' && settings.resetButtonLocation !== 'none') {
const btn = _createStandardButton({ id: IDS.TOOL_RESET_BUTTON, className: CSS.TOOL_BUTTON, svgIcon: SVG_ICONS.reset, textContent: _('tool_reset_filters'), title: _('tool_reset_filters'), clickHandler: URLActionManager.triggerResetFilters });
const bD = document.createElement('div'); bD.classList.add(CSS.FIXED_TOP_BUTTON_ITEM); bD.appendChild(btn);
fTF.appendChild(bD);
}
if (pznBL === 'topBlock' && personalizeBtnFn && settings.personalizationButtonLocation !== 'none') {
const btnPzn = personalizeBtnFn('topBlock');
const bDPzn = document.createElement('div'); bDPzn.classList.add(CSS.FIXED_TOP_BUTTON_ITEM); bDPzn.appendChild(btnPzn);
fTF.appendChild(bDPzn);
}
if (vBL === 'topBlock' && settings.verbatimButtonLocation !== 'none') {
const btnVerbatim = _createStandardButton({ id: IDS.TOOL_VERBATIM, className: CSS.TOOL_BUTTON, svgIcon: SVG_ICONS.verbatim, textContent: _('tool_verbatim_search'), title: _('tool_verbatim_search'), clickHandler: URLActionManager.triggerToggleVerbatim, isActive: verbatimActive });
const bDVerbatim = document.createElement('div'); bDVerbatim.classList.add(CSS.FIXED_TOP_BUTTON_ITEM); bDVerbatim.appendChild(btnVerbatim);
fTF.appendChild(bDVerbatim);
}
if (aSL === 'topBlock' && advSearchFn && settings.advancedSearchLinkLocation !== 'none') {
const linkEl = advSearchFn(true);
const bDAdv = document.createElement('div'); bDAdv.classList.add(CSS.FIXED_TOP_BUTTON_ITEM); bDAdv.appendChild(linkEl);
fTF.appendChild(bDAdv);
}
if (fTF.childElementCount > 0) { fTBC.appendChild(fTF); return fTBC; }
return null;
}
function _createToolsSectionElement(sectionId, titleKey, rBL, vBL, aSL, pznBL, advSearchFn, personalizeBtnFn) {
const { section, sectionContent, sectionTitle } = _createSectionShell(sectionId, titleKey);
sectionTitle.textContent = _(titleKey);
const frag = document.createDocumentFragment();
const verbatimActive = URLActionManager.isVerbatimActive();
const currentSettings = SettingsManager.getCurrentSettings();
if (rBL === 'tools' && currentSettings.resetButtonLocation !== 'none') {
const btn = _createStandardButton({ id: IDS.TOOL_RESET_BUTTON, className: CSS.TOOL_BUTTON, svgIcon: SVG_ICONS.reset, textContent: _('tool_reset_filters'), title: _('tool_reset_filters'), clickHandler: URLActionManager.triggerResetFilters });
frag.appendChild(btn);
}
if (pznBL === 'tools' && personalizeBtnFn && currentSettings.personalizationButtonLocation !== 'none') {
const btnPzn = personalizeBtnFn('tools');
frag.appendChild(btnPzn);
}
if (vBL === 'tools' && currentSettings.verbatimButtonLocation !== 'none') {
const btnVerbatim = _createStandardButton({ id: IDS.TOOL_VERBATIM, className: CSS.TOOL_BUTTON, svgIcon: SVG_ICONS.verbatim, textContent: _('tool_verbatim_search'), title: _('tool_verbatim_search'), clickHandler: URLActionManager.triggerToggleVerbatim, isActive: verbatimActive });
frag.appendChild(btnVerbatim);
}
if (aSL === 'tools' && advSearchFn && currentSettings.advancedSearchLinkLocation !== 'none') {
frag.appendChild(advSearchFn(true));
}
if (frag.childElementCount > 0) {
sectionContent.appendChild(frag);
return section;
}
return null;
}
function _validateDateInputs(minInput, maxInput, errorMsgElement) {
_clearElementMessage(errorMsgElement, CSS.ERROR_VISIBLE);
minInput.classList.remove(CSS.INPUT_HAS_ERROR);
maxInput.classList.remove(CSS.INPUT_HAS_ERROR);
let isValid = true;
const today = new Date();
today.setHours(0, 0, 0, 0);
const startDateStr = minInput.value;
const endDateStr = maxInput.value;
let startDate = null;
let endDate = null;
if (startDateStr) {
startDate = new Date(startDateStr);
startDate.setHours(0,0,0,0);
if (startDate > today) {
_showElementMessage(errorMsgElement, 'alert_start_in_future', {}, CSS.ERROR_VISIBLE);
minInput.classList.add(CSS.INPUT_HAS_ERROR);
isValid = false;
}
}
if (endDateStr) {
endDate = new Date(endDateStr);
endDate.setHours(0,0,0,0);
// Note: max attribute on input type=date already prevents selecting future dates from calendar.
// This JS validation catches manual input or programmatic changes.
// If we strictly rely on 'max' attribute, this specific check 'endDate > today' might be redundant
// for calendar selection, but good for manual input.
if (endDate > today && !maxInput.getAttribute('max')) { // Only show error if max not set by attribute which should prevent this
if (isValid) _showElementMessage(errorMsgElement, 'alert_end_in_future', {}, CSS.ERROR_VISIBLE);
else errorMsgElement.textContent += " " + _('alert_end_in_future');
maxInput.classList.add(CSS.INPUT_HAS_ERROR);
isValid = false;
}
}
if (startDate && endDate && startDate > endDate) {
if (isValid) _showElementMessage(errorMsgElement, 'alert_end_before_start', {}, CSS.ERROR_VISIBLE);
else errorMsgElement.textContent += " " + _('alert_end_before_start');
minInput.classList.add(CSS.INPUT_HAS_ERROR);
maxInput.classList.add(CSS.INPUT_HAS_ERROR);
isValid = false;
}
return isValid;
}
function addDateRangeListener() {
const dateRangeSection = sidebar?.querySelector('#sidebar-section-date-range');
if (!dateRangeSection) return;
const applyButton = dateRangeSection.querySelector('.apply-date-range');
const errorMsgElement = dateRangeSection.querySelector(`#${IDS.DATE_RANGE_ERROR_MSG}`);
const dateMinInput = dateRangeSection.querySelector(`#${IDS.DATE_MIN}`);
const dateMaxInput = dateRangeSection.querySelector(`#${IDS.DATE_MAX}`);
if (!applyButton || !errorMsgElement || !dateMinInput || !dateMaxInput) {
console.warn(`${LOG_PREFIX} Date range elements not found for listener setup.`);
return;
}
const handleDateValidation = () => {
const isValid = _validateDateInputs(dateMinInput, dateMaxInput, errorMsgElement);
applyButton.disabled = !isValid;
};
if (!dateMinInput.dataset[DATA_ATTR.LISTENER_ATTACHED]) {
dateMinInput.addEventListener('input', handleDateValidation);
dateMinInput.addEventListener('change', handleDateValidation); // Also on change for calendar
dateMinInput.dataset[DATA_ATTR.LISTENER_ATTACHED] = 'true';
}
if (!dateMaxInput.dataset[DATA_ATTR.LISTENER_ATTACHED]) {
dateMaxInput.addEventListener('input', handleDateValidation);
dateMaxInput.addEventListener('change', handleDateValidation); // Also on change for calendar
dateMaxInput.dataset[DATA_ATTR.LISTENER_ATTACHED] = 'true';
}
if (!applyButton.dataset[DATA_ATTR.LISTENER_ATTACHED]) {
applyButton.dataset[DATA_ATTR.LISTENER_ATTACHED] = 'true';
applyButton.addEventListener('click', () => {
if (!_validateDateInputs(dateMinInput, dateMaxInput, errorMsgElement)) {
// NotificationManager.show('alert_select_date', {}, 'warning', 3000); // Error already shown by _validateDateInputs
return;
}
URLActionManager.applyDateRange(dateMinInput.value, dateMaxInput.value);
});
}
handleDateValidation(); // Initial check
}
function _initializeSidebarEventListenersAndStates() { addDateRangeListener(); addToolButtonListeners(); initializeSelectedFilters(); applySectionCollapseStates(); }
function _clearElementMessage(element, visibleClass = CSS.ERROR_VISIBLE) { if(!element)return; element.textContent=''; element.classList.remove(visibleClass);}
function _showElementMessage(element, messageKey, messageArgs = {}, visibleClass = CSS.ERROR_VISIBLE) { if(!element)return; element.textContent=_(messageKey,messageArgs); element.classList.add(visibleClass);}
function addToolButtonListeners() { const queryAreas = [ sidebar?.querySelector(`.${CSS.SIDEBAR_HEADER}`), sidebar?.querySelector(`#${IDS.FIXED_TOP_BUTTONS}`), sidebar?.querySelector(`#sidebar-section-tools .${CSS.SECTION_CONTENT}`) ].filter(Boolean); queryAreas.forEach(area => { area.querySelectorAll(`#${IDS.TOOL_VERBATIM}:not([data-${DATA_ATTR.LISTENER_ATTACHED}])`).forEach(b => { b.addEventListener('click', URLActionManager.triggerToggleVerbatim); b.dataset[DATA_ATTR.LISTENER_ATTACHED] = 'true'; }); area.querySelectorAll(`#${IDS.TOOL_RESET_BUTTON}:not([data-${DATA_ATTR.LISTENER_ATTACHED}])`).forEach(b => { b.addEventListener('click', URLActionManager.triggerResetFilters); b.dataset[DATA_ATTR.LISTENER_ATTACHED] = 'true'; }); }); }
function applySidebarCollapseVisuals(isCollapsed) { if(!sidebar)return;const cB=sidebar.querySelector(`#${IDS.COLLAPSE_BUTTON}`);if(isCollapsed){sidebar.classList.add(CSS.SIDEBAR_COLLAPSED);if(cB){cB.innerHTML=SVG_ICONS.chevronRight;cB.title=_('sidebar_expand_title');}}else{sidebar.classList.remove(CSS.SIDEBAR_COLLAPSED);if(cB){cB.innerHTML=SVG_ICONS.chevronLeft;cB.title=_('sidebar_collapse_title');}}}
function applySectionCollapseStates() { if(!sidebar)return; const currentSettings = SettingsManager.getCurrentSettings(); const sections = sidebar.querySelectorAll(`.${CSS.SIDEBAR_CONTENT_WRAPPER} .${CSS.SIDEBAR_SECTION}`); sections.forEach(section => { const content = section.querySelector(`.${CSS.SECTION_CONTENT}`); const title = section.querySelector(`.${CSS.SECTION_TITLE}`); const sectionId = section.id; if (content && title && sectionId) { let shouldBeCollapsed = false; if (currentSettings.sectionDisplayMode === 'collapseAll') { shouldBeCollapsed = true; } else if (currentSettings.sectionDisplayMode === 'expandAll') { shouldBeCollapsed = false; } else { shouldBeCollapsed = currentSettings.sectionStates?.[sectionId] === true; } content.classList.toggle(CSS.COLLAPSED, shouldBeCollapsed); title.classList.toggle(CSS.COLLAPSED, shouldBeCollapsed); if (currentSettings.sectionDisplayMode === 'remember') { if (!currentSettings.sectionStates) currentSettings.sectionStates = {}; currentSettings.sectionStates[sectionId] = shouldBeCollapsed; } } }); }
function initializeSelectedFilters() {
if (!sidebar) return;
try {
const u = URLActionManager._getURLObject ? URLActionManager._getURLObject() : Utils.getCurrentURL();
if (!u) return;
const p = u.searchParams;
const cT = p.get('tbs') || '';
const cQ = p.get('q') || '';
_initializeStandaloneFilterState(p, 'language', 'lr');
_initializeStandaloneFilterState(p, 'country', 'cr');
_initializeStandaloneFilterState(p, 'filetype', 'as_filetype');
_initializeTimeFilterState(cT);
_initializeVerbatimState();
_initializePersonalizationState();
_initializeDateRangeInputs(cT);
_initializeSiteSearchState(cQ);
} catch (e) {
console.error(`${LOG_PREFIX} Error initializing filter highlights:`, e);
}
}
function _initializeStandaloneFilterState(p,fT,pTC){ const sI=`sidebar-section-${fT}`;const s=sidebar?.querySelector(`#${sI}`);if(!s){return;}const uV=p.get(pTC);const o=s.querySelectorAll(`.${CSS.FILTER_OPTION}`);o.forEach(opt=>{const oV=opt.dataset[DATA_ATTR.FILTER_VALUE];opt.classList.toggle(CSS.SELECTED,(uV!==null&&uV===oV)||(uV===null&&oV===''));});}
function _initializeTimeFilterState(cT){ const tS=sidebar?.querySelector('#sidebar-section-time');if(!tS)return;const qM=cT.match(/qdr:([^,]+)/);const aQV=qM?qM[1]:null;const hDR=/cdr:1/.test(cT);const tO=tS.querySelectorAll(`.${CSS.FILTER_OPTION}`);tO.forEach(o=>{const oV=o.dataset[DATA_ATTR.FILTER_VALUE];let sS=false;if(hDR){sS=(oV==='');}else if(aQV){sS=(oV===aQV);}else{sS=(oV==='');}o.classList.toggle(CSS.SELECTED,sS);});}
function _initializeVerbatimState(){ const iVA = URLActionManager.isVerbatimActive(); sidebar?.querySelectorAll(`#${IDS.TOOL_VERBATIM}`).forEach(b=>b.classList.toggle(CSS.ACTIVE, iVA));}
function _initializePersonalizationState() {
const isActive = URLActionManager.isPersonalizationActive();
sidebar?.querySelectorAll(`#${IDS.TOOL_PERSONALIZE}`).forEach(button => {
button.classList.toggle(CSS.ACTIVE, isActive);
const titleKey = isActive ? 'tooltip_toggle_personalization_off' : 'tooltip_toggle_personalization_on';
button.title = _(titleKey);
// Text and icon are primarily set by _createPersonalizationButtonHTML.
// This ensures they are correct if the button was somehow not fully reconstructed.
const svgIcon = SVG_ICONS.personalization || '';
const isIconOnly = button.classList.contains(CSS.HEADER_BUTTON) && !button.classList.contains(CSS.TOOL_BUTTON);
const currentText = !isIconOnly ? _('tool_personalization_toggle') : '';
let newHTML = '';
if (svgIcon) newHTML += svgIcon;
if (currentText) newHTML += (svgIcon && currentText ? ' ' : '') + currentText;
button.innerHTML = newHTML.trim();
});
}
function _initializeDateRangeInputs(cT){
const dS = sidebar?.querySelector('#sidebar-section-date-range');
if (!dS) return;
const dMI = dS.querySelector(`#${IDS.DATE_MIN}`);
const dMXI = dS.querySelector(`#${IDS.DATE_MAX}`);
const eME = dS.querySelector(`#${IDS.DATE_RANGE_ERROR_MSG}`);
const applyButton = dS.querySelector('.apply-date-range');
if (eME) _clearElementMessage(eME, CSS.ERROR_VISIBLE);
if (/cdr:1/.test(cT)) {
const mIM = cT.match(/cd_min:(\d{1,2})\/(\d{1,2})\/(\d{4})/);
const mAM = cT.match(/cd_max:(\d{1,2})\/(\d{1,2})\/(\d{4})/);
if (dMI) dMI.value = mIM ? `${mIM[3]}-${mIM[1].padStart(2, '0')}-${mIM[2].padStart(2, '0')}` : '';
if (dMXI) dMXI.value = mAM ? `${mAM[3]}-${mAM[1].padStart(2, '0')}-${mAM[2].padStart(2, '0')}` : '';
} else {
if (dMI) dMI.value = '';
if (dMXI) dMXI.value = '';
}
// After setting values from URL, trigger validation
if (dMI && dMXI && eME && applyButton) {
const isValid = _validateDateInputs(dMI, dMXI, eME);
applyButton.disabled = !isValid;
}
}
function _initializeSiteSearchState(cQ){ const sS=sidebar?.querySelector('#sidebar-section-site-search');if(!sS)return;const sM=cQ.match(/site:([\w.-]+)/);const aSU=sM?sM[1]:null;const sO=sS.querySelectorAll(`.${CSS.FILTER_OPTION}[data-${DATA_ATTR.SITE_URL}]`);sO.forEach(o=>{o.classList.toggle(CSS.SELECTED,aSU&&o.dataset[DATA_ATTR.SITE_URL]===aSU);});const cO=sS.querySelector('#clear-site-search-option');if(cO){cO.classList.toggle(CSS.SELECTED,!aSU);}}
function bindSidebarEvents() { /* ... (same as before) ... */ if (!sidebar) return; const collapseButton = sidebar.querySelector(`#${IDS.COLLAPSE_BUTTON}`); const settingsButton = sidebar.querySelector(`#${IDS.SETTINGS_BUTTON}`); if (collapseButton) collapseButton.title = _('sidebar_collapse_title'); if (settingsButton) settingsButton.title = _('sidebar_settings_title'); sidebar.addEventListener('click', (e) => { const settingsBtnTarget = e.target.closest(`#${IDS.SETTINGS_BUTTON}`); if (settingsBtnTarget) { SettingsManager.show(); return; } const collapseBtnTarget = e.target.closest(`#${IDS.COLLAPSE_BUTTON}`); if (collapseBtnTarget) { toggleSidebarCollapse(); return; } const sectionTitleTarget = e.target.closest(`.${CSS.SIDEBAR_CONTENT_WRAPPER} .${CSS.SECTION_TITLE}`); if (sectionTitleTarget && !sidebar.classList.contains(CSS.SIDEBAR_COLLAPSED)) { handleSectionCollapse(e); return; } }); }
function toggleSidebarCollapse() { /* ... (same as before) ... */ const cs = SettingsManager.getCurrentSettings(); cs.sidebarCollapsed = !cs.sidebarCollapsed; applySettings(cs); SettingsManager.save('Sidebar Collapse');}
function handleSectionCollapse(event) { /* ... (same as before) ... */ const title = event.target.closest(`.${CSS.SECTION_TITLE}`); if (!title || sidebar?.classList.contains(CSS.SIDEBAR_COLLAPSED) || title.closest(`#${IDS.FIXED_TOP_BUTTONS}`)) return; const section = title.closest(`.${CSS.SIDEBAR_SECTION}`); if (!section) return; const content = section.querySelector(`.${CSS.SECTION_CONTENT}`); const sectionId = section.id; if (!content || !sectionId) return; const currentSettings = SettingsManager.getCurrentSettings(); const isCurrentlyCollapsed = content.classList.contains(CSS.COLLAPSED); const shouldBeCollapsedAfterClick = !isCurrentlyCollapsed; let overallStateChanged = false; if (currentSettings.accordionMode && !shouldBeCollapsedAfterClick) { const sectionsContainer = section.parentElement; if (_applyAccordionEffectToSections(sectionId, sectionsContainer, currentSettings)) overallStateChanged = true; } if (_toggleSectionVisualState(section, title, content, sectionId, shouldBeCollapsedAfterClick, currentSettings)) overallStateChanged = true; if (overallStateChanged && currentSettings.sectionDisplayMode === 'remember') { debouncedSaveSettings('Section Collapse/Accordion'); } }
function _applyAccordionEffectToSections(clickedSectionId, allSectionsContainer, currentSettings) { /* ... (same as before) ... */ let stateChangedForAccordion = false; allSectionsContainer?.querySelectorAll(`.${CSS.SIDEBAR_SECTION}`)?.forEach(otherSection => { if (otherSection.id !== clickedSectionId) { const otherContent = otherSection.querySelector(`.${CSS.SECTION_CONTENT}`); const otherTitle = otherSection.querySelector(`.${CSS.SECTION_TITLE}`); if (otherContent && !otherContent.classList.contains(CSS.COLLAPSED)) { otherContent.classList.add(CSS.COLLAPSED); otherTitle?.classList.add(CSS.COLLAPSED); if (currentSettings.sectionDisplayMode === 'remember') { if (!currentSettings.sectionStates) currentSettings.sectionStates = {}; if (currentSettings.sectionStates[otherSection.id] !== true) { currentSettings.sectionStates[otherSection.id] = true; stateChangedForAccordion = true; } } } } }); return stateChangedForAccordion; }
function _toggleSectionVisualState(sectionEl, titleEl, contentEl, sectionId, newCollapsedState, currentSettings) { /* ... (same as before) ... */ let sectionStateActuallyChanged = false; const isCurrentlyCollapsed = contentEl.classList.contains(CSS.COLLAPSED); if (isCurrentlyCollapsed !== newCollapsedState) { contentEl.classList.toggle(CSS.COLLAPSED, newCollapsedState); titleEl.classList.toggle(CSS.COLLAPSED, newCollapsedState); sectionStateActuallyChanged = true; } if (currentSettings.sectionDisplayMode === 'remember') { if (!currentSettings.sectionStates) currentSettings.sectionStates = {}; if (currentSettings.sectionStates[sectionId] !== newCollapsedState) { currentSettings.sectionStates[sectionId] = newCollapsedState; if (!sectionStateActuallyChanged) sectionStateActuallyChanged = true; } } return sectionStateActuallyChanged; }
// --- Main Initialization ---
function initializeScript() {
console.log(LOG_PREFIX + " Initializing script...");
debouncedSaveSettings = Utils.debounce(() => SettingsManager.save('Debounced Save'), 800);
try {
addGlobalStyles();
NotificationManager.init();
LocalizationService.initializeBaseLocale();
SettingsManager.initialize( defaultSettings, applySettings, buildSidebarUI, applySectionCollapseStates, _initMenuCommands, renderSectionOrderList );
setupSystemThemeListener();
buildSidebarSkeleton();
DragManager.init( sidebar, sidebar.querySelector(`.${CSS.DRAG_HANDLE}`), SettingsManager, debouncedSaveSettings );
const initialSettings = SettingsManager.getCurrentSettings();
DragManager.setDraggable(initialSettings.draggableHandleEnabled, sidebar, sidebar.querySelector(`.${CSS.DRAG_HANDLE}`));
applySettings(initialSettings);
buildSidebarUI(); // This also calls _initializeSidebarEventListenersAndStates
bindSidebarEvents();
_initMenuCommands();
console.log(`${LOG_PREFIX} Script initialization complete. Final effective locale: ${LocalizationService.getCurrentLocale()}`);
} catch (error) {
console.error(`${LOG_PREFIX} [initializeScript] CRITICAL ERROR DURING INITIALIZATION:`, error, error.stack);
const scriptNameForAlert = (typeof _ === 'function' && _('scriptName') && !(_('scriptName').startsWith('[ERR:'))) ? _('scriptName') : SCRIPT_INTERNAL_NAME;
if (typeof NotificationManager !== 'undefined' && NotificationManager.show) {
NotificationManager.show('alert_init_fail', { scriptName: scriptNameForAlert, error: error.message }, 'error', 0);
} else {
_showGlobalMessage('alert_init_fail', { scriptName: scriptNameForAlert, error: error.message }, 'error', 0);
}
if(sidebar && sidebar.remove) sidebar.remove();
const settingsOverlayEl = document.getElementById(IDS.SETTINGS_OVERLAY);
if(settingsOverlayEl) settingsOverlayEl.remove();
ModalManager.hide();
}
}
// --- Script Entry Point ---
if (document.getElementById(IDS.SIDEBAR)) { console.warn(`${LOG_PREFIX} Sidebar with ID "${IDS.SIDEBAR}" already exists. Skipping initialization.`); return; }
if (typeof window.GSCS_Namespace === 'undefined' || typeof window.GSCS_Namespace.stylesText !== 'string') { console.warn(`${LOG_PREFIX} Styles provider not yet available. Delaying initialization.`); let attempts = 0; const maxAttempts = 20; const interval = 100; const checkStylesInterval = setInterval(() => { attempts++; if (typeof window.GSCS_Namespace !== 'undefined' && typeof window.GSCS_Namespace.stylesText === 'string') { clearInterval(checkStylesInterval); if (document.readyState === 'complete' || document.readyState === 'interactive' || document.readyState === 'loaded') { initializeScript(); } else { window.addEventListener('DOMContentLoaded', initializeScript, { once: true }); } } else if (attempts >= maxAttempts) { clearInterval(checkStylesInterval); console.error(`${LOG_PREFIX} Styles provider failed to load. Initializing without external styles.`); if (document.readyState === 'complete' || document.readyState === 'interactive' || document.readyState === 'loaded') { initializeScript(); } else { window.addEventListener('DOMContentLoaded', initializeScript, { once: true }); } } }, interval);
} else { if (document.readyState === 'complete' || document.readyState === 'interactive' || document.readyState === 'loaded') { initializeScript(); } else { window.addEventListener('DOMContentLoaded', initializeScript, { once: true }); } }
})();