generation for palette now
grid.appendChild(createMenuItem(p, catKey));
});
menu.appendChild(grid);
}
} else if (catKey === 'exactSize' || catKey === 'site') {
const userOpts = catKey === 'exactSize' ? giteSettings.filters.exactSize.userDefinedOptions : giteSettings.filters.site.userDefinedOptions;
let uOpts = userOpts || [];
if(catKey === 'site') {
const anyOpt = options.find(o => o.id === 'site_any');
const otherOpts = options.filter(o => o.id !== 'site_any');
if (anyOpt && anyOpt.isEnabled !== false) menu.appendChild(createMenuItem(anyOpt, catKey));
if (uOpts.length > 0 && anyOpt) menu.appendChild(el('div', { className: 'gite-menu-separator' }));
uOpts.forEach(opt => { if(opt.isEnabled) menu.appendChild(createMenuItem(opt, catKey)); });
if(otherOpts.length > 0 && uOpts.length > 0) menu.appendChild(el('div', { className: 'gite-menu-separator' }));
appendOptions(otherOpts);
} else {
if(uOpts.length > 0) {
uOpts.forEach(opt => { if(opt.isEnabled) menu.appendChild(createMenuItem(opt, catKey)); });
menu.appendChild(el('div', { className: 'gite-menu-separator' }));
}
appendOptions(options);
}
} else if (catKey === 'region') {
const anyOpt = options.find(o => o.id === 'reg_any');
const otherOpts = options.filter(o => o.id !== 'reg_any');
if (anyOpt && anyOpt.isEnabled !== false) menu.appendChild(createMenuItem(anyOpt, catKey));
if (anyOpt && otherOpts.length > 0) menu.appendChild(el('div', { className: 'gite-menu-separator' }));
appendOptions(otherOpts);
} else {
appendOptions(options);
}
// --- Interaction Logic (Menu & Keyboard) ---
const toggleMenu = (shouldOpen) => {
if (typeof shouldOpen === 'undefined') shouldOpen = !menu.classList.contains('show');
document.querySelectorAll('.gite-menu-dropdown').forEach(m => { if (m !== menu) m.classList.remove('show'); });
document.querySelectorAll('.gite-tb-btn').forEach(b => { if (b !== btn) { b.classList.remove('open'); b.setAttribute('aria-expanded', 'false'); } });
if (shouldOpen) {
menu.classList.add('show');
btn.classList.add('open');
btn.setAttribute('aria-expanded', 'true');
currentlyOpenMenuId = menu.id;
// [Auto Focus]
// 1. Try to find the first input (Exact Size)
let focusTarget = menu.querySelector('input');
// 2. Or the first menu item / palette link
if (!focusTarget) focusTarget = menu.querySelector('a.gite-menu-item, a.gite-swatch');
if (focusTarget) requestAnimationFrame(() => focusTarget.focus());
} else {
menu.classList.remove('show');
btn.classList.remove('open');
btn.setAttribute('aria-expanded', 'false');
currentlyOpenMenuId = null;
}
};
btn.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
e.preventDefault(); toggleMenu(true);
} else if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) {
e.preventDefault(); handleToolbarNav(e, btn);
}
});
btn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); toggleMenu(); };
menu.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
e.preventDefault(); toggleMenu(false); btn.focus();
} else if (e.key === 'Tab') {
// Allow Tab to bubble naturally (Input -> Input -> Button -> Items)
// Just ensure we don't trap or break it.
// If focus leaves the menu entirely, we might want to close it?
// For now, let's keep it open to allow complex interactions (Input fields)
} else {
handleMenuNav(e, menu);
}
});
const wrapper = el('div', { className: 'gite-tb-group' });
wrapper.append(btn, menu);
container.appendChild(wrapper);
}
// --- Keyboard Navigation Helpers ---
function handleToolbarNav(e, currentBtn) {
const allBtns = Array.from(document.querySelectorAll(`#${GITE_TOOLBAR_CONTAINER_ID} .gite-tb-btn`));
if (!allBtns.length) return;
const currIdx = allBtns.indexOf(currentBtn);
let nextIdx = currIdx;
if (e.key === 'ArrowLeft') nextIdx = currIdx - 1;
else if (e.key === 'ArrowRight') nextIdx = currIdx + 1;
else if (e.key === 'Home') nextIdx = 0;
else if (e.key === 'End') nextIdx = allBtns.length - 1;
if (nextIdx >= 0 && nextIdx < allBtns.length) allBtns[nextIdx].focus();
}
function handleMenuNav(e, menu) {
// Respect Input fields: don't hijack Arrows if inside an input
if (e.target.tagName === 'INPUT' && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) return;
if (e.target.tagName === 'INPUT' && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
// Let default input behavior happen for number inputs (increment/decrement)
// But if user wants to navigate out? Maybe only hijack if no value?
// For standard consistency, let's allow Up/Down to navigate OUT of inputs
// ONLY if they are not modifying value? No, native behavior is important.
// Let's say: Arrow Up/Down navigates, unless it's a number input?
// User requested: "Arrow keys to navigate menu items".
// Compromise: ArrowUp/Down moves focus. Left/Right modifies cursor.
// But number inputs use Up/Down.
// Solution: If type='number', allow native.
if (e.target.type === 'number') return;
}
const items = Array.from(menu.querySelectorAll('a.gite-menu-item, a.gite-swatch, button.gite-menu-btn-icon, input'));
if (!items.length) return;
let currIdx = items.indexOf(document.activeElement);
// If focus is not in list (weird), default to 0
if (currIdx === -1) currIdx = 0;
let nextIdx = currIdx;
if (e.key === 'ArrowDown') {
e.preventDefault();
nextIdx = (currIdx + 1) % items.length;
} else if (e.key === 'ArrowUp') {
e.preventDefault();
nextIdx = (currIdx - 1 + items.length) % items.length;
} else if (e.key === 'Home') {
e.preventDefault(); nextIdx = 0;
} else if (e.key === 'End') {
e.preventDefault(); nextIdx = items.length - 1;
}
if (nextIdx !== currIdx) items[nextIdx].focus();
}
function initializeEnhancedFilters() {
if (document.getElementById(GITE_TOOLBAR_CONTAINER_ID)) return;
const anchor = document.querySelector(BEFORE_APPBAR_SELECTOR);
if (!anchor) return;
const toolbar = el('div', { id: GITE_TOOLBAR_CONTAINER_ID });
if (giteSettings.general.toolbarFontSize) {
let fs = giteSettings.general.toolbarFontSize; if (!fs.includes('px')) fs += 'px';
toolbar.style.setProperty('--gite-menu-font-size', fs);
}
if (giteSettings.general.toolbarLineHeight) {
toolbar.style.setProperty('--gite-menu-line-height', giteSettings.general.toolbarLineHeight);
}
const filtersGroup = el('div', { className: 'gite-filters-group' });
toolbar.appendChild(filtersGroup);
const categories = [
{ k: 'size', t: 'filter_title_size', p: 'imgsz', id: 'gite-size' },
{ k: 'exactSize', t: 'filter_title_exact_size', p: 'q', id: 'gite-es' },
{ k: 'aspectRatio', t: 'filter_title_aspect_ratio', p: 'imgar', id: 'gite-ar' },
{ k: 'color', t: 'filter_title_color', p: 'tbs', id: 'gite-col' },
{ k: 'type', t: 'filter_title_type', p: 'tbs', id: 'gite-type' },
{ k: 'time', t: 'filter_title_time', p: 'tbs', id: 'gite-time' },
{ k: 'usageRights', t: 'filter_title_usage_rights', p: 'tbs', id: 'gite-rights' },
{ k: 'fileType', t: 'filter_title_file_type', p: 'as_filetype', id: 'gite-file' },
{ k: 'region', t: 'filter_title_region', p: 'cr', id: 'gite-reg' },
{ k: 'site', t: 'filter_title_site_search', p: 'q', id: 'gite-site' }
];
categories.forEach(c => addFilterCategoryMenu(filtersGroup, c.k, c.t, c.p, c.id));
if (giteSettings.general.showAdvancedSearchButton) {
const currentQ = new URL(window.location.href).searchParams.get('q') || "";
const cleanQ = currentQ.replace(/(?:\s|^)imagesize:\d+x\d+/gi, '').replace(/(?:\s|^)site:[^\s]+(?:\s+OR\s+site:[^\s]+)*/gi, '').trim();
const advUrl = `https://www.google.com/advanced_image_search?as_q=${encodeURIComponent(cleanQ)}`;
const advBtn = el('a', {
className: 'gite-tb-btn gite-tb-adv-link gite-ripple style-icon',
href: advUrl, title: getLocalizedString('filter_title_advanced_search'),
tabIndex: 0
});
advBtn.append(el('div', { className: 'gite-tb-icon', innerHTML: GITE_ICONS.sliders }));
// Add keyboard nav for this standalone button
advBtn.addEventListener('keydown', (e) => {
if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) {
e.preventDefault(); handleToolbarNav(e, advBtn);
}
});
filtersGroup.appendChild(advBtn);
}
const url = new URL(window.location.href);
const hasFilter = url.searchParams.get('tbs') || url.searchParams.get('imgsz') || url.searchParams.get('imgar') || url.searchParams.get('as_filetype') || url.searchParams.get('cr') || url.searchParams.get('q')?.match(/imagesize:|site:/);
if (hasFilter) {
const btnStyle = giteSettings.general.toolbarButtonStyle || 'text';
const clearBtn = el('a', {
className: `gite-tb-btn gite-ripple clear-action style-${btnStyle}`,
href: "javascript:void(0);",
title: btnStyle==='icon' ? getLocalizedString('btn_clear') : '',
tabIndex: 0
});
clearBtn.append(
el('div', { className: 'gite-tb-icon', innerHTML: GITE_ICONS.clear }),
el('span', { className: 'gite-btn-text', textContent: getLocalizedString('btn_clear') })
);
clearBtn.onclick = (e) => {
if(e.button===0) { e.preventDefault(); window.location.href = buildNewUrl('', '', {}, true); }
};
clearBtn.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); clearBtn.click(); }
else if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) {
e.preventDefault(); handleToolbarNav(e, clearBtn);
}
});
filtersGroup.appendChild(clearBtn);
}
const utilGroup = el('div', { className: 'gite-utilities-group' });
toolbar.appendChild(utilGroup);
if (giteSettings.general.showResultStats) {
utilGroup.appendChild(el('span', { id: GITE_RESULT_STATS_DISPLAY_ID }));
updateResultStats();
}
if (giteSettings.general.showSettingsButtonOnToolbar) {
const setBtn = el('div', {
id: GITE_SETTINGS_BUTTON_ID,
innerHTML: GITE_ICONS.gear,
title: getLocalizedString('gm_menu_gite_settings'),
tabIndex: 0,
role: 'button'
});
setBtn.onclick = openSettingsPanel;
setBtn.addEventListener('keydown', (e) => {
if(e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openSettingsPanel(); }
});
utilGroup.appendChild(setBtn);
}
anchor.parentNode.insertBefore(toolbar, anchor);
}
// --- 10. UI Interaction Helpers ---
function openDatePicker() {
document.querySelectorAll('.gite-menu-dropdown').forEach(m => m.classList.remove('show'));
document.querySelectorAll('.gite-tb-btn').forEach(b => { b.classList.remove('open'); b.setAttribute('aria-expanded', 'false'); });
const overlay = el('div', { className: 'gite-datepicker-overlay' });
const picker = el('div', { className: 'gite-custom-date-picker' });
const row1 = el('div', { className: 'gite-date-row' }, [
el('label', { for: 'gite-date-start' }, getLocalizedString('datepicker_label_from')),
el('input', { type: 'date', id: 'gite-date-start' })
]);
const row2 = el('div', { className: 'gite-date-row' }, [
el('label', { for: 'gite-date-end' }, getLocalizedString('datepicker_label_to')),
el('input', { type: 'date', id: 'gite-date-end' })
]);
const actions = el('div', { className: 'gite-datepicker-actions' });
const applyBtn = el('button', { className: 'gite-btn gite-btn-primary', textContent: getLocalizedString('btn_apply') });
const close = () => { if(overlay.parentNode) overlay.parentNode.removeChild(overlay); };
overlay.onclick = (e) => { if(e.target === overlay) close(); };
const doApply = () => {
const s = picker.querySelector('#gite-date-start').value;
const e = picker.querySelector('#gite-date-end').value;
if(!s || !e) { alert(getLocalizedString('alert_datepicker_select_dates')); return; }
if(new Date(e) < new Date(s)) { alert(getLocalizedString('alert_datepicker_end_before_start')); return; }
const formatDate = d => { const p = d.split('-'); return `${p[1]}/${p[2]}/${p[0]}`; };
const tbsVal = `cdr:1,cd_min:${formatDate(s)},cd_max:${formatDate(e)}`;
window.location.href = buildNewUrl('tbs', tbsVal, { categoryKey: 'time', tbsValue: tbsVal });
close();
};
applyBtn.onclick = doApply;
picker.addEventListener('keydown', (e) => {
if (e.key === 'Enter') doApply();
if (e.key === 'Escape') close();
});
actions.append(applyBtn);
picker.append(row1, row2, actions);
overlay.appendChild(picker);
document.body.appendChild(overlay);
setTimeout(() => { const i = document.getElementById('gite-date-start'); if(i) i.focus(); }, 50);
}
// --- 11. Settings Panel Logic ---
const CAT_TITLE_MAP = {
'general': 'settings_tab_general',
'exactSize': 'filter_title_exact_size',
'aspectRatio': 'filter_title_aspect_ratio',
'usageRights': 'filter_title_usage_rights',
'fileType': 'filter_title_file_type',
'site': 'filter_title_site_search'
};
function getCatTitleKey(k) { return CAT_TITLE_MAP[k] || `filter_title_${k}`; }
function renderToggle(id, labelKey, checked) {
const div = el('div', { className: 'gite-control-row' });
div.innerHTML = `${getLocalizedString(labelKey)}
`;
return div;
}
function renderRangeSlider(id, labelKey, value, min, max, step, unit = '') {
const div = el('div', { className: 'gite-control-row', style: "flex-direction:column; align-items:stretch; border:none; padding: 10px 0;" });
const topRow = el('div', { style: "display:flex; justify-content:space-between; margin-bottom:5px;" });
topRow.innerHTML = `${getLocalizedString(labelKey)}${value}${unit}`;
const input = el('input', { type: 'range', id: id, className: 'gite-range-input', min, max, step, value });
input.addEventListener('input', (e) => {
document.getElementById(`${id}-display`).textContent = `${e.target.value}${unit}`;
const tb = document.getElementById(GITE_TOOLBAR_CONTAINER_ID);
if (tb) tb.style.setProperty(id === 'setting-fontsize' ? '--gite-menu-font-size' : '--gite-menu-line-height', id === 'setting-fontsize' ? `${e.target.value}px` : e.target.value);
});
div.append(topRow, input);
return div;
}
function renderCustomList(catKey, listId) {
const ul = el('ul', { className: 'gite-list', id: listId });
const items = currentGiteSettingsForPanel.filters[catKey].userDefinedOptions || [];
const createLi = (opt) => {
const li = el('li', { className: 'gite-list-item' });
const checkContainer = el('div', { className: 'gite-item-check' });
const toggle = el('label', { className: 'gite-toggle', style: 'transform:scale(0.7); width:32px;' });
const chk = el('input', { type: 'checkbox', checked: opt.isEnabled });
chk.onchange = (e) => { opt.isEnabled = e.target.checked; };
toggle.append(chk, el('span', { className: 'gite-slider' }));
checkContainer.appendChild(toggle);
const labelDiv = el('div', { className: 'gite-item-label' });
if (catKey === 'site' && giteSettings.filters.site.showFavicons) {
// Check Multi
let iconSrc = '';
if(opt.value.includes(' OR ')) {
labelDiv.appendChild(el('div', { className: 'gite-tb-icon', innerHTML: GITE_ICONS.globe, style: "width:16px;height:16px;margin-right:8px;vertical-align:middle;display:inline-flex;" }));
} else {
labelDiv.appendChild(el('img', { src: `https://www.google.com/s2/favicons?sz=32&domain_url=${opt.value}`, style: "width:16px;height:16px;margin-right:8px;vertical-align:middle;border-radius:2px;", onerror: function(){this.style.display='none'} }));
}
}
labelDiv.appendChild(document.createTextNode(opt.label || opt.id));
const displayValue = (catKey === 'site' && opt.value) ? opt.value.replace(/\s+OR\s+/g, ', ') : opt.value;
const valDiv = el('div', { className: 'gite-item-value', textContent: displayValue, style: "margin-left:auto; margin-right:10px; color:var(--gite-text-secondary); font-size:0.9em;" });
const actions = el('div', { className: 'gite-item-actions' });
const editBtn = el('button', { className: 'gite-icon-btn gite-ripple', title: getLocalizedString('btn_edit_label'), innerHTML: GITE_ICONS.edit });
editBtn.onclick = () => {
const newLabel = prompt(getLocalizedString('btn_edit_label'), opt.label);
if (newLabel !== null) { opt.label = newLabel; ul.replaceWith(renderCustomList(catKey, listId)); showToast('alert_label_updated'); }
};
const delBtn = el('button', { className: 'gite-icon-btn danger gite-ripple', title: getLocalizedString('btn_delete'), innerHTML: GITE_ICONS.delete });
delBtn.onclick = () => {
// Direct delete, no confirm
const idx = currentGiteSettingsForPanel.filters[catKey].userDefinedOptions.indexOf(opt);
if (idx > -1) { currentGiteSettingsForPanel.filters[catKey].userDefinedOptions.splice(idx, 1); li.remove(); }
};
actions.append(editBtn, delBtn);
li.append(checkContainer, labelDiv, valDiv, actions);
return li;
};
if (items.length === 0) ul.innerHTML = `${getLocalizedString('settings_no_saved_items_placeholder')}`;
else items.forEach(item => ul.appendChild(createLi(item)));
return ul;
}
function switchTab(tabId) {
if (!giteSettingsPanelElement) return;
giteSettingsPanelElement.querySelectorAll('.gite-nav-item').forEach(n => n.classList.toggle('active', n.dataset.tab === tabId));
const contentArea = document.getElementById('gite-settings-content-area');
const titleArea = document.getElementById('gite-settings-page-title');
contentArea.innerHTML = '';
if (tabId === 'general') {
titleArea.setAttribute(GITE_LANG_KEY_ATTR, 'settings_tab_general');
titleArea.textContent = getLocalizedString('settings_tab_general');
const langGroup = el('div', { className: 'gite-input-group' });
langGroup.innerHTML = ``;
const langSel = el('select', { className: 'gite-input' });
['auto', 'en', 'zh-TW', 'ja'].forEach(l => {
const opt = el('option', { value: l, textContent: getLocalizedString(`settings_lang_${l.replace('-', '_')}`) });
if(l === currentGiteSettingsForPanel.general.selectedLanguage) opt.selected = true;
langSel.appendChild(opt);
});
langSel.onchange = (e) => {
const val = e.target.value;
currentGiteSettingsForPanel.general.selectedLanguage = val;
if (val === 'auto') {
const browserLang = (navigator.language || navigator.userLanguage || 'en').toLowerCase();
if (browserLang.startsWith('zh-tw') || browserLang.startsWith('zh-hk') || browserLang.startsWith('zh-hant')) CURRENT_LANGUAGE = 'zh-TW';
else if (browserLang.startsWith('ja')) CURRENT_LANGUAGE = 'ja';
else CURRENT_LANGUAGE = 'en';
} else {
CURRENT_LANGUAGE = val;
}
updateAllLocalizableElements(giteSettingsPanelElement);
switchTab('general');
};
langGroup.appendChild(langSel);
const themeGroup = el('div', { className: 'gite-input-group' });
themeGroup.innerHTML = ``;
const themeSel = el('select', { className: 'gite-input' });
['auto', 'light', 'dark'].forEach(t => {
const opt = el('option', { value: t, textContent: getLocalizedString(`settings_theme_${t}`) });
if(t === currentGiteSettingsForPanel.general.themePreference) opt.selected = true;
themeSel.appendChild(opt);
});
themeSel.onchange = (e) => {
currentGiteSettingsForPanel.general.themePreference = e.target.value;
const isDark = e.target.value === 'dark' || (e.target.value === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.classList.toggle('gite-detected-dark-theme', isDark);
};
themeGroup.appendChild(themeSel);
const styleGroup = el('div', { className: 'gite-input-group' });
styleGroup.innerHTML = ``;
const styleSel = el('select', { className: 'gite-input' });
['text', 'icon', 'both'].forEach(s => {
const opt = el('option', { value: s, textContent: getLocalizedString(`settings_btn_style_${s}`) });
if(s === (currentGiteSettingsForPanel.general.toolbarButtonStyle || 'text')) opt.selected = true;
styleSel.appendChild(opt);
});
styleSel.onchange = (e) => currentGiteSettingsForPanel.general.toolbarButtonStyle = e.target.value;
styleGroup.appendChild(styleSel);
const fsSlider = renderRangeSlider('setting-fontsize', 'settings_label_toolbar_font_size', parseFloat(currentGiteSettingsForPanel.general.toolbarFontSize), 8, 24, 0.5);
fsSlider.querySelector('input').onchange = (e) => currentGiteSettingsForPanel.general.toolbarFontSize = e.target.value + 'px';
const lhSlider = renderRangeSlider('setting-lineheight', 'settings_label_toolbar_line_height', parseFloat(currentGiteSettingsForPanel.general.toolbarLineHeight), 0.1, 2.5, 0.1);
lhSlider.querySelector('input').onchange = (e) => currentGiteSettingsForPanel.general.toolbarLineHeight = e.target.value;
const t1 = renderToggle('set-btn-toggle', 'settings_label_showtoolbarbutton', currentGiteSettingsForPanel.general.showSettingsButtonOnToolbar);
t1.querySelector('input').onchange = (e) => currentGiteSettingsForPanel.general.showSettingsButtonOnToolbar = e.target.checked;
const t2 = renderToggle('res-stats-toggle', 'settings_label_showresultstats', currentGiteSettingsForPanel.general.showResultStats);
t2.querySelector('input').onchange = (e) => currentGiteSettingsForPanel.general.showResultStats = e.target.checked;
const t3 = renderToggle('adv-search-toggle', 'settings_label_showadvsearch', currentGiteSettingsForPanel.general.showAdvancedSearchButton);
t3.querySelector('input').onchange = (e) => currentGiteSettingsForPanel.general.showAdvancedSearchButton = e.target.checked;
const t4 = renderToggle('reg-flags-toggle', 'settings_label_showregionflags', currentGiteSettingsForPanel.general.showRegionFlags);
t4.querySelector('input').onchange = (e) => currentGiteSettingsForPanel.general.showRegionFlags = e.target.checked;
const footer = el('div', { className: 'gite-category-footer' });
const rstBtn = el('button', {
className: 'gite-btn-reset-cat gite-ripple',
innerHTML: `${GITE_ICONS.refresh} ${getLocalizedString('btn_reset_general_settings')}`
});
rstBtn.onclick = () => { if(confirm(getLocalizedString('btn_reset_general_settings') + '?')) { currentGiteSettingsForPanel.general = JSON.parse(JSON.stringify(GITE_DEFAULT_SETTINGS.general)); switchTab('general'); showToast('alert_settings_reset_to_default'); detectAndApplyThemeClass(); }};
footer.appendChild(rstBtn);
contentArea.append(langGroup, themeGroup, styleGroup, el('hr', {style:'border:0;border-top:1px dashed var(--gite-border);margin:12px 0'}), fsSlider, lhSlider, el('hr', {style:'border:0;border-top:1px dashed var(--gite-border);margin:12px 0'}), t1, t2, t3, t4, footer);
} else {
const catKey = tabId;
const catSettings = currentGiteSettingsForPanel.filters[catKey];
const titleKey = getCatTitleKey(catKey);
let localizedTitle = getLocalizedString(titleKey);
titleArea.setAttribute(GITE_LANG_KEY_ATTR, titleKey);
titleArea.textContent = localizedTitle;
const enToggle = el('div', { className: 'gite-control-row' });
enToggle.innerHTML = `${getLocalizedString('settings_enable_filter_category_prefix')}${localizedTitle}${getLocalizedString('settings_enable_filter_category_suffix')}
`;
if (catKey === 'exactSize') {
const inputToggle = renderToggle('es-input-show', 'settings_label_show_exact_size_inputs_in_menu', catSettings.showInputsInMenu);
inputToggle.querySelector('input').onchange = (e) => catSettings.showInputsInMenu = e.target.checked;
contentArea.appendChild(inputToggle);
}
if (catKey === 'site') {
const favToggle = renderToggle('site-fav-show', 'settings_label_showfavicons', catSettings.showFavicons);
favToggle.querySelector('input').onchange = (e) => catSettings.showFavicons = e.target.checked;
contentArea.appendChild(favToggle);
}
if (catKey === 'exactSize' || catKey === 'site') {
const sectionTitleKey = catKey === 'site' ? 'settings_section_your_saved_sites' : 'settings_section_your_saved_sizes';
const sectionTitle = el('h3', { textContent: getLocalizedString(sectionTitleKey) });
sectionTitle.setAttribute(GITE_LANG_KEY_ATTR, sectionTitleKey);
const addBox = el('div', { className: 'gite-add-box' });
if (catKey === 'exactSize') {
addBox.innerHTML = `
`;
} else {
addBox.innerHTML = `
`;
}
contentArea.append(sectionTitle, addBox);
setTimeout(() => {
const addBtn = addBox.querySelector('button');
if(addBtn) addBtn.onclick = () => {
const list = currentGiteSettingsForPanel.filters[catKey].userDefinedOptions;
if(catKey === 'exactSize') {
const w = document.getElementById('new-es-w').value; const h = document.getElementById('new-es-h').value; const l = document.getElementById('new-es-l').value;
if(!w || !h) { alert(getLocalizedString('alert_exact_size_invalid_input')); return; }
if(list.some(i => i.value === `${w}x${h}`)) { alert(getLocalizedString('alert_size_already_saved')); return; }
list.push({ id: `user_es_${Date.now()}`, width: w, height: h, value: `${w}x${h}`, label: l || `${w}x${h}`, isEnabled: true, type:'imagesize', isCustom: true });
} else {
let dInput = document.getElementById('new-site-d').value.trim();
const l = document.getElementById('new-site-l').value.trim();
if(!dInput) { alert(getLocalizedString('alert_site_domain_empty')); return; }
if(!l) { alert(getLocalizedString('alert_site_label_empty')); return; }
const tokens = dInput.replace(/,/g, ' ')
.split(/\s+/)
.filter(t => t && t.toLowerCase() !== 'or');
let d = tokens.join(' OR ');
if(list.some(i => i.value === d)) { alert(getLocalizedString('alert_site_already_saved')); return; }
list.push({ id: `user_site_${Date.now()}`, value: d, label: l, isEnabled: true, type:'site_filter', isCustom: true });
}
showToast(catKey === 'site' ? 'alert_site_added' : 'alert_exact_size_added');
document.getElementById(`list-${catKey}`)?.replaceWith(renderCustomList(catKey, `list-${catKey}`));
};
}, 0);
contentArea.appendChild(renderCustomList(catKey, `list-${catKey}`));
}
const options = catKey === 'exactSize' ? catSettings.predefinedOptions : catSettings.options;
if (options && options.length > 0) {
const gridTitle = el('h3', { textContent: getLocalizedString('settings_section_predefined_options'), style: 'margin-top:20px;' });
gridTitle.setAttribute(GITE_LANG_KEY_ATTR, 'settings_section_predefined_options');
const grid = el('div', { className: 'gite-option-grid' });
options.forEach(opt => {
if (opt.type === 'separator') return;
const label = opt.customText || getLocalizedString(opt.textKey, CURRENT_LANGUAGE, opt.id);
const div = el('label', { className: 'gite-option-check-item gite-ripple' });
const chk = el('input', { type: 'checkbox', checked: opt.isEnabled !== false });
chk.onchange = (e) => { opt.isEnabled = e.target.checked; };
div.appendChild(chk);
if (catKey === 'region' && currentGiteSettingsForPanel.general.showRegionFlags && opt.paramName === 'cr') div.appendChild(el('span', { className: 'gite-icon-preview', textContent: getFlagEmoji(opt.id.split('_').pop()) }));
if (catKey === 'color' && opt.type === 'palette' && opt.hex) div.appendChild(el('span', { className: 'gite-icon-preview', style: `width:12px;height:12px;background:${opt.hex};border-radius:50%;border:1px solid rgba(0,0,0,0.1);` }));
if(opt.textKey && !opt.customText) {
const span = el('span', { textContent: ' ' + label });
span.setAttribute(GITE_LANG_KEY_ATTR, opt.textKey);
div.appendChild(span);
} else {
div.appendChild(document.createTextNode(' ' + label));
}
grid.appendChild(div);
});
contentArea.append(gridTitle, grid);
}
const footer = el('div', { className: 'gite-category-footer' });
const rstBtn = el('button', { className: 'gite-btn-reset-cat gite-ripple' });
rstBtn.innerHTML = `${GITE_ICONS.refresh}${getLocalizedString('btn_reset_options_for_category_prefix')}${localizedTitle}${getLocalizedString('btn_reset_options_for_category_suffix')}`;
rstBtn.onclick = () => {
const confirmMsg = getLocalizedString('btn_reset_options_for_category_prefix') + localizedTitle + getLocalizedString('btn_reset_options_for_category_suffix') + '?';
if(confirm(confirmMsg)) {
const defs = GITE_DEFAULT_SETTINGS.filters[catKey];
if (catKey === 'exactSize') { catSettings.predefinedOptions = JSON.parse(JSON.stringify(defs.predefinedOptions)); catSettings.userDefinedOptions = JSON.parse(JSON.stringify(defs.userDefinedOptions)); }
else if (catKey === 'site') { catSettings.userDefinedOptions = JSON.parse(JSON.stringify(defs.userDefinedOptions)); }
else { catSettings.options = JSON.parse(JSON.stringify(defs.options)); }
switchTab(catKey); showToast('alert_settings_reset_to_default');
}
};
footer.appendChild(rstBtn);
contentArea.appendChild(footer);
}
}
function ensureSettingsPanelDOM() {
if (document.getElementById('gite-settings-panel')) return;
const panelOverlay = el('div', { id: 'gite-settings-panel', className: 'gite-modal-overlay' });
const navItems = ['general','size','exactSize','aspectRatio','color','type','time','usageRights','fileType','region','site'].map(k => {
let iconName = k;
if (['exactSize','site','fileType','usageRights','aspectRatio'].includes(k)) iconName = k;
if (k === 'general') iconName = 'gear';
const titleKey = getCatTitleKey(k);
return `
${GITE_ICONS[iconName]}
${getLocalizedString(titleKey)}
`;
}).join('');
panelOverlay.innerHTML = `
`;
document.body.appendChild(panelOverlay);
panelOverlay.querySelector('#gite-settings-close').onclick = closeSettingsPanel;
panelOverlay.querySelector('#gite-settings-cancel').onclick = closeSettingsPanel;
panelOverlay.querySelector('#gite-settings-save').onclick = saveSettingsFromPanel;
panelOverlay.querySelector('#gite-settings-reset').onclick = resetAllSettings;
panelOverlay.querySelectorAll('.gite-nav-item').forEach(item => item.onclick = () => switchTab(item.dataset.tab));
panelOverlay.onclick = (e) => { if (e.target === panelOverlay) closeSettingsPanel(); };
// Settings Panel A11y (Esc to close)
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && isSettingsPanelOpen) closeSettingsPanel();
});
}
function openSettingsPanel() {
if (!giteSettingsPanelElement) ensureSettingsPanelDOM();
giteSettingsPanelElement = document.getElementById('gite-settings-panel');
currentGiteSettingsForPanel = JSON.parse(JSON.stringify(giteSettings));
giteSettingsPanelElement.classList.add('open');
isSettingsPanelOpen = true;
switchTab('general');
}
function closeSettingsPanel() {
if (giteSettingsPanelElement) giteSettingsPanelElement.classList.remove('open');
isSettingsPanelOpen = false;
const tb = document.getElementById(GITE_TOOLBAR_CONTAINER_ID);
if(tb) {
const fs = giteSettings.general.toolbarFontSize; const lh = giteSettings.general.toolbarLineHeight;
tb.style.setProperty('--gite-menu-font-size', fs.includes('px')?fs:`${fs}px`);
tb.style.setProperty('--gite-menu-line-height', lh);
}
detectAndApplyThemeClass();
}
function saveSettingsFromPanel() {
giteSettings = JSON.parse(JSON.stringify(currentGiteSettingsForPanel));
GM_setValue(GITE_SETTINGS_GM_KEY, JSON.stringify(giteSettings));
initializeCurrentLanguage();
detectAndApplyThemeClass();
const oldTb = document.getElementById(GITE_TOOLBAR_CONTAINER_ID);
if(oldTb) oldTb.remove();
initializeEnhancedFilters();
showToast('alert_settings_saved');
closeSettingsPanel();
}
function resetAllSettings() {
if (confirm(getLocalizedString('alert_confirm_reset_all_settings'))) {
giteSettings = JSON.parse(JSON.stringify(GITE_DEFAULT_SETTINGS));
GM_setValue(GITE_SETTINGS_GM_KEY, JSON.stringify(giteSettings));
alert(getLocalizedString('alert_settings_reset_to_default') + '\n' + getLocalizedString('gm_please_reload'));
window.location.reload();
}
}
// --- 12. Initialization ---
function updateResultStats() {
if (!giteSettings.general.showResultStats) return;
const target = document.getElementById(GITE_RESULT_STATS_DISPLAY_ID);
const source = document.getElementById('result-stats');
if (target && source) { target.textContent = source.textContent; target.style.display = 'inline'; }
}
function observeResultStats() {
if (resultStatsObserver) return;
resultStatsObserver = new MutationObserver(updateResultStats);
const source = document.getElementById('result-stats');
if (source) resultStatsObserver.observe(source, { childList: true, subtree: true, characterData: true });
new MutationObserver(() => { if (document.getElementById('result-stats')) updateResultStats(); }).observe(document.body, { childList: true });
}
function loadSettings() {
// V1 Key defined in configuration
const storedNew = GM_getValue(GITE_SETTINGS_GM_KEY);
// 1. If new version settings exist, load them directly
if (storedNew) {
try {
const parsed = JSON.parse(storedNew);
giteSettings = JSON.parse(JSON.stringify(GITE_DEFAULT_SETTINGS));
// Merge General Settings
if (parsed.general) Object.assign(giteSettings.general, parsed.general);
// Merge Filters
for (const key in giteSettings.filters) {
if (parsed.filters && parsed.filters[key]) {
// Inherit enabled state
if (parsed.filters[key].hasOwnProperty('enabled')) {
giteSettings.filters[key].enabled = parsed.filters[key].enabled;
}
// Merge Options (Simple selection types: size, time, etc.)
if (parsed.filters[key].options) {
const storedOpts = parsed.filters[key].options;
giteSettings.filters[key].options.forEach(defOpt => {
const match = storedOpts.find(s => s.id === defOpt.id);
if (match && match.hasOwnProperty('isEnabled')) defOpt.isEnabled = match.isEnabled;
});
}
// Merge Exact Size (Predefined + User Defined)
if (key === 'exactSize') {
if (parsed.filters[key].predefinedOptions) {
const storedPre = parsed.filters[key].predefinedOptions;
giteSettings.filters[key].predefinedOptions.forEach(defOpt => {
const match = storedPre.find(s => s.id === defOpt.id);
if (match && match.hasOwnProperty('isEnabled')) defOpt.isEnabled = match.isEnabled;
});
}
if (parsed.filters[key].userDefinedOptions) {
giteSettings.filters[key].userDefinedOptions = parsed.filters[key].userDefinedOptions;
}
if (parsed.filters[key].hasOwnProperty('showInputsInMenu')) {
giteSettings.filters[key].showInputsInMenu = parsed.filters[key].showInputsInMenu;
}
}
// Merge Site Search (User Defined + Flags)
if (key === 'site') {
if (parsed.filters[key].userDefinedOptions) {
giteSettings.filters[key].userDefinedOptions = parsed.filters[key].userDefinedOptions;
}
if (parsed.filters[key].hasOwnProperty('showFavicons')) {
giteSettings.filters[key].showFavicons = parsed.filters[key].showFavicons;
}
}
}
}
} catch (e) {
console.error("[GITE] Error loading V1 settings:", e);
// Fallback to defaults on error
giteSettings = JSON.parse(JSON.stringify(GITE_DEFAULT_SETTINGS));
}
}
// 2. If no new settings, try to migrate from old version (v0.3.x)
else {
const storedOld = GM_getValue('GITE_USER_SETTINGS'); // Old Key
giteSettings = JSON.parse(JSON.stringify(GITE_DEFAULT_SETTINGS));
if (storedOld) {
try {
const parsedOld = JSON.parse(storedOld);
if (DEBUG_MODE) console.log('[GITE] Migrating settings from v0.3.x...');
// Migrate General Settings
if (parsedOld.general) {
if (parsedOld.general.selectedLanguage) giteSettings.general.selectedLanguage = parsedOld.general.selectedLanguage;
if (parsedOld.general.themePreference) giteSettings.general.themePreference = parsedOld.general.themePreference;
if (parsedOld.general.showResultStats !== undefined) giteSettings.general.showResultStats = parsedOld.general.showResultStats;
if (parsedOld.general.toolbarFontSize) giteSettings.general.toolbarFontSize = parsedOld.general.toolbarFontSize;
if (parsedOld.general.toolbarLineHeight) giteSettings.general.toolbarLineHeight = parsedOld.general.toolbarLineHeight;
if (parsedOld.general.showRegionFlags !== undefined) giteSettings.general.showRegionFlags = parsedOld.general.showRegionFlags;
}
// Migrate Filters
if (parsedOld.filters) {
// Migrate Exact Size Custom Items
if (parsedOld.filters.exactSize && parsedOld.filters.exactSize.userDefinedOptions) {
giteSettings.filters.exactSize.userDefinedOptions = parsedOld.filters.exactSize.userDefinedOptions;
}
// Migrate Site Search (Smart Domain Matching)
if (parsedOld.filters.site && parsedOld.filters.site.userDefinedOptions) {
const oldSites = parsedOld.filters.site.userDefinedOptions;
const newDefaultSites = giteSettings.filters.site.userDefinedOptions; // Already loaded with v1.0.0 defaults
oldSites.forEach(oldSite => {
// Match by domain value
const matchIndex = newDefaultSites.findIndex(ns => ns.value === oldSite.value);
if (matchIndex !== -1) {
// Domain exists in new defaults: Inherit isEnabled state
newDefaultSites[matchIndex].isEnabled = oldSite.isEnabled;
// Note: We generally prefer the new label unless it was likely user-edited,
// but checking for user edits on default items is complex.
// We prioritize inheriting the enable/disable state here.
} else {
// Domain not in new defaults -> It is a custom user site -> Add to list
// Prefix ID to avoid potential (unlikely) collision with future internal IDs
const customSite = { ...oldSite };
if (!customSite.id.startsWith('user_site_')) {
customSite.id = 'migrated_' + customSite.id;
}
newDefaultSites.push(customSite);
}
});
}
}
// Save immediately to the new V1 key format
GM_setValue(GITE_SETTINGS_GM_KEY, JSON.stringify(giteSettings));
if (DEBUG_MODE) console.log('[GITE] Migration complete and saved to V1 key.');
} catch (e) {
console.error("[GITE] Error during settings migration:", e);
giteSettings = JSON.parse(JSON.stringify(GITE_DEFAULT_SETTINGS));
}
}
}
}
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
function main() {
loadSettings();
initializeCurrentLanguage();
injectStyles();
detectAndApplyThemeClass();
if (typeof GM_registerMenuCommand === 'function') {
GM_registerMenuCommand(getLocalizedString('gm_menu_gite_settings'), openSettingsPanel);
GM_registerMenuCommand(getLocalizedString('gm_menu_reset_all_gite_settings'), resetAllSettings);
}
const runInit = () => {
initializeEnhancedFilters();
observeResultStats();
if (!menuObserver) {
const debouncedReInit = debounce(() => {
const anchor = document.querySelector(BEFORE_APPBAR_SELECTOR);
const tb = document.getElementById(GITE_TOOLBAR_CONTAINER_ID);
if (anchor && !tb) { initializeEnhancedFilters(); }
}, 200);
menuObserver = new MutationObserver(debouncedReInit);
menuObserver.observe(document.body, { childList: true, subtree: true });
}
};
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', runInit);
else runInit();
window.addEventListener('popstate', () => {
if(DEBUG_MODE) console.log('[GITE] Popstate detected');
const tb = document.getElementById(GITE_TOOLBAR_CONTAINER_ID);
if(tb) tb.remove();
initializeEnhancedFilters();
});
document.addEventListener('click', (e) => {
if (currentlyOpenMenuId) {
const menu = document.getElementById(currentlyOpenMenuId);
const btnId = currentlyOpenMenuId.replace('-menu', '-btn');
const btn = document.getElementById(btnId);
if ((menu && !menu.contains(e.target)) && (btn && !btn.contains(e.target))) {
document.querySelectorAll('.gite-menu-dropdown').forEach(m => m.classList.remove('show'));
document.querySelectorAll('.gite-tb-btn').forEach(b => { b.classList.remove('open'); b.setAttribute('aria-expanded', 'false'); });
currentlyOpenMenuId = null;
}
}
});
}
main();
})();