diff --git a/eslint.config.js b/eslint.config.js index 8490d2f6..9e396fd3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,7 +5,6 @@ import jsxA11y from 'eslint-plugin-jsx-a11y'; import prettier from 'eslint-config-prettier'; export default [ - // Ignore patterns { ignores: [ '**/node_modules/**', @@ -18,7 +17,6 @@ export default [ ], }, - // Base config for all JS/JSX files { files: ['**/*.{js,jsx,mjs}'], languageOptions: { @@ -26,7 +24,6 @@ export default [ sourceType: 'module', parserOptions: { ecmaFeatures: { jsx: true } }, globals: { - // Browser globals window: 'readonly', document: 'readonly', navigator: 'readonly', @@ -48,7 +45,6 @@ export default [ AbortController: 'readonly', btoa: 'readonly', atob: 'readonly', - // Node globals for scripts process: 'readonly', __dirname: 'readonly', __filename: 'readonly', @@ -64,21 +60,17 @@ export default [ ...react.configs['jsx-runtime'].rules, ...reactHooks.configs.recommended.rules, - // React specific rules - 'react/prop-types': 'off', // Using PropTypes is optional - 'react/react-in-jsx-scope': 'off', // Not needed with React 17+ - 'react/jsx-uses-react': 'off', // Not needed with React 17+ + 'react/prop-types': 'off', + 'react/react-in-jsx-scope': 'off', + 'react/jsx-uses-react': 'off', - // General rules 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], 'no-console': ['warn', { allow: ['warn', 'error'] }], - // Modern JS 'prefer-const': 'warn', 'no-var': 'error', }, }, - // Prettier config (must be last to override other formatting rules) prettier, ]; diff --git a/scripts/findUnusedTranslations.cjs b/scripts/findUnusedTranslations.cjs index ec9ac5ab..28ef59b8 100644 --- a/scripts/findUnusedTranslations.cjs +++ b/scripts/findUnusedTranslations.cjs @@ -65,27 +65,19 @@ function isKeyUsed(key, files, fileContentsCache) { const keySegments = key.split('.'); const patterns = []; - // Pattern 1: Full key match - // Example: 'modals.main.settings.sections.greeting.event_name' const escapedFullKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); patterns.push(new RegExp(`['"\`]${escapedFullKey}['"\`]`, 'g')); - // Pattern 2: Last 2 segments (catches most template literal cases) - // Example: 'greeting.event_name' for dynamic construction like `${PREFIX}.event_name` if (keySegments.length >= 2) { const lastTwo = keySegments.slice(-2).map(s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('\\.'); patterns.push(new RegExp(`['"\`]${lastTwo}['"\`]`, 'g')); } - // Pattern 3: Last 3 segments (more specific than 2, less than full) - // Example: 'sections.greeting.event_name' if (keySegments.length >= 3) { const lastThree = keySegments.slice(-3).map(s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('\\.'); patterns.push(new RegExp(`['"\`]${lastThree}['"\`]`, 'g')); } - // Pattern 4: Final segment after a dot (for deeply nested template literals) - // Example: .event_name' in template literal like `${SECTION}.${SUBSECTION}.event_name` if (keySegments.length >= 3) { const finalSegment = keySegments[keySegments.length - 1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); patterns.push(new RegExp(`\\.${finalSegment}['"\`]`, 'g')); diff --git a/scripts/updateTranslationPercentages.cjs b/scripts/updateTranslationPercentages.cjs index e2eef21c..9820936f 100644 --- a/scripts/updateTranslationPercentages.cjs +++ b/scripts/updateTranslationPercentages.cjs @@ -2,7 +2,6 @@ const fs = require('fs'); const path = require('path'); const https = require('https'); -// Language code mappings between Weblate and Mue const CODE_MAPPINGS = { de: 'de_DE', id: 'id_ID', diff --git a/scripts/updatetranslations.cjs b/scripts/updatetranslations.cjs index 74354b9d..cb518305 100644 --- a/scripts/updatetranslations.cjs +++ b/scripts/updatetranslations.cjs @@ -18,12 +18,10 @@ const localesDir = path.join(__dirname, '../src/i18n/locales'); const achievementsDir = path.join(localesDir, 'achievements'); const manifestLocalesDir = path.join(__dirname, '../manifest/_locales'); -// Check if the locales directory exists, if not, create it if (!fs.existsSync(localesDir)) { fs.mkdirSync(localesDir, { recursive: true }); } -// Check if the achievements directory exists, if not, create it if (!fs.existsSync(achievementsDir)) { fs.mkdirSync(achievementsDir, { recursive: true }); } @@ -74,7 +72,6 @@ fs.readdirSync(achievementsDir).forEach((file) => { const locales = fs.readdirSync(localesDir); locales.forEach((locale) => { if (!fs.existsSync(path.join(achievementsDir, locale))) { - // ignore directories if (fs.lstatSync(path.join(localesDir, locale)).isDirectory()) { return; } @@ -85,29 +82,23 @@ fs.readdirSync(achievementsDir).forEach((file) => { }); }); -// Sync manifest locales with src/i18n/locales console.log('Syncing manifest/_locales with src/i18n/locales...'); const enManifestPath = path.join(manifestLocalesDir, 'en', 'messages.json'); -// Check if the English manifest exists if (!fs.existsSync(enManifestPath)) { console.error(`English manifest file does not exist at '${enManifestPath}'`); } else { const enManifest = fs.readFileSync(enManifestPath, 'utf8'); - // Get all locales from src/i18n/locales const localeFiles = fs.readdirSync(localesDir).filter((file) => { - // Only include JSON files, not directories return !fs.lstatSync(path.join(localesDir, file)).isDirectory() && file.endsWith('.json'); }); localeFiles.forEach((localeFile) => { - // Extract locale code (e.g., "en_GB.json" -> "en_GB") const localeCode = localeFile.replace('.json', ''); const manifestLocalePath = path.join(manifestLocalesDir, localeCode); const manifestMessagesPath = path.join(manifestLocalePath, 'messages.json'); - // Check if this locale exists in manifest/_locales if (!fs.existsSync(manifestLocalePath)) { console.log(`Creating missing locale: ${localeCode}`); fs.mkdirSync(manifestLocalePath, { recursive: true }); diff --git a/src/components/Elements/AddModal/AddModal.jsx b/src/components/Elements/AddModal/AddModal.jsx index 4626ccc6..01f70f62 100644 --- a/src/components/Elements/AddModal/AddModal.jsx +++ b/src/components/Elements/AddModal/AddModal.jsx @@ -20,17 +20,14 @@ function AddModal({ urlError, iconError, addLink, closeModal, edit, editData, ed const [suggestedName, setSuggestedName] = useState(''); const [resetKey, setResetKey] = useState(Date.now()); - // Reset form when modal opens for adding new link useEffect(() => { if (!edit) { - // Clear localStorage for the form fields localStorage.removeItem('quicklink_modal_name'); localStorage.removeItem('quicklink_modal_url'); localStorage.removeItem('quicklink_modal_iconType'); localStorage.removeItem('quicklink_modal_icon_url'); localStorage.removeItem('quicklink_modal_emoji'); - // Reset all state setName(''); setUrl(''); setIcon(''); @@ -40,12 +37,10 @@ function AddModal({ urlError, iconError, addLink, closeModal, edit, editData, ed setUploadError(''); setSuggestedName(''); - // Change the key to force remount setResetKey(Date.now()); } }, [edit]); - // Extract domain name from URL as suggestion useEffect(() => { if (name || !url) { setSuggestedName(''); @@ -60,10 +55,8 @@ function AddModal({ urlError, iconError, addLink, closeModal, edit, editData, ed const domain = new URL(urlToTest).hostname; if (domain) { - // Extract first part of domain (e.g., "google" from "google.com", "bbc" from "bbc.co.uk") const parts = domain.split('.'); let name = parts[0]; - // Handle cases like "co.uk" where we want "bbc" not "co" if (parts.length > 2 && parts[parts.length - 2] === 'co') { name = parts[parts.length - 3]; } @@ -89,7 +82,6 @@ function AddModal({ urlError, iconError, addLink, closeModal, edit, editData, ed }; const handleSubmit = () => { - // Use suggested name if no name was entered const finalName = name || suggestedName || ''; if (edit) { @@ -151,7 +143,6 @@ function AddModal({ urlError, iconError, addLink, closeModal, edit, editData, ed name="quicklink_modal_url" noSetting={true} onChange={(value) => { - // Auto-add https:// if no protocol specified let finalValue = value; if (value && !value.startsWith('http://') && !value.startsWith('https://')) { finalValue = 'https://' + value; diff --git a/src/components/Elements/MainModal/Main.jsx b/src/components/Elements/MainModal/Main.jsx index 5ce64dfc..f0c3a0a5 100644 --- a/src/components/Elements/MainModal/Main.jsx +++ b/src/components/Elements/MainModal/Main.jsx @@ -11,7 +11,6 @@ const Settings = lazy(() => import('../../../features/misc/views/Settings')); const Library = lazy(() => import('../../../features/misc/views/Library')); const Discover = lazy(() => import('../../../features/misc/views/Discover')); -// Map tab types to their corresponding components const TAB_COMPONENTS = { [TAB_TYPES.SETTINGS]: Settings, [TAB_TYPES.LIBRARY]: Library, @@ -19,7 +18,6 @@ const TAB_COMPONENTS = { }; function MainModal({ modalClose, deepLinkData }) { - // Initialize with deep link tab if provided, otherwise default to settings const initialTab = deepLinkData?.tab || TAB_TYPES.SETTINGS; const [currentTab, setCurrentTab] = useState(initialTab); const [currentSection, setCurrentSection] = useState(''); @@ -30,27 +28,22 @@ function MainModal({ modalClose, deepLinkData }) { const [navigationTrigger, setNavigationTrigger] = useState(null); const [iframeBreadcrumbs, setIframeBreadcrumbs] = useState([]); - // Clear product view when changing tabs useEffect(() => { setProductView(null); }, [currentTab]); - // Handle deep link updates (when modal opens via EventBus with new deep link) useEffect(() => { if (deepLinkData) { - // Update tab if different if (deepLinkData.tab && deepLinkData.tab !== currentTab) { setCurrentTab(deepLinkData.tab); } - // Handle settings section navigation with subsection if (deepLinkData.tab === TAB_TYPES.SETTINGS && deepLinkData.section) { setNavigationTrigger({ type: 'settings-section', data: deepLinkData.section, timestamp: Date.now(), }); - // Set sub-section if present if (deepLinkData.subSection) { setCurrentSubSection(deepLinkData.subSection); } @@ -58,10 +51,8 @@ function MainModal({ modalClose, deepLinkData }) { } }, [deepLinkData]); - // Clear hash when modal closes useEffect(() => { return () => { - // When modal unmounts, clear the hash if (window.location.hash) { window.history.replaceState(null, null, window.location.pathname); } @@ -69,38 +60,30 @@ function MainModal({ modalClose, deepLinkData }) { }, []); useEffect(() => { - // Listen for browser back/forward navigation via popstate const handlePopState = () => { const linkData = window.location.hash ? parseDeepLink(window.location.hash) : null; if (linkData) { - // Update tab if different if (linkData.tab && linkData.tab !== currentTab) { setCurrentTab(linkData.tab); } - // Handle settings section navigation if (linkData.tab === TAB_TYPES.SETTINGS && linkData.section) { setNavigationTrigger({ type: 'settings-section', data: linkData.section, timestamp: Date.now(), }); - // Set sub-section if present in hash setCurrentSubSection(linkData.subSection || null); return; } - // Handle product and collection navigation if (linkData.itemId && linkData.collection && linkData.fromCollection) { - // Product viewed from within a collection - // First set collection state, then navigate to product setNavigationTrigger({ type: 'collection', data: linkData.collection, timestamp: Date.now(), }); - // Small delay to ensure collection state is set before navigating to product setTimeout(() => { setNavigationTrigger({ type: 'product', @@ -112,7 +95,6 @@ function MainModal({ modalClose, deepLinkData }) { }); }, 100); } else if (linkData.itemId) { - // Product navigation (standalone) setNavigationTrigger({ type: 'product', data: { @@ -122,14 +104,12 @@ function MainModal({ modalClose, deepLinkData }) { timestamp: Date.now(), }); } else if (linkData.collection) { - // Collection page navigation setNavigationTrigger({ type: 'collection', data: linkData.collection, timestamp: Date.now(), }); } else { - // Back to main view (clear collection state) setProductView(null); setNavigationTrigger({ type: 'main', @@ -146,7 +126,6 @@ function MainModal({ modalClose, deepLinkData }) { const handleChangeTab = (newTab) => { setCurrentTab(newTab); - // Update URL hash when tab changes if (newTab === TAB_TYPES.DISCOVER) { updateHash(`#${newTab}/all`); } else if (newTab === TAB_TYPES.LIBRARY) { @@ -159,11 +138,8 @@ function MainModal({ modalClose, deepLinkData }) { const handleSectionChange = (section, sectionName) => { setCurrentSection(section); setCurrentSectionName(sectionName); - // Clear sub-section when changing sections setCurrentSubSection(null); - // Update URL hash when section changes if (currentTab === TAB_TYPES.DISCOVER) { - // For Discover tab, update with the section type const sectionMap = { [variables.getMessage('modals.main.marketplace.all')]: 'all', [variables.getMessage('modals.main.marketplace.photo_packs')]: 'photo_packs', @@ -176,19 +152,16 @@ function MainModal({ modalClose, deepLinkData }) { updateHash(`#${currentTab}/${sectionKey}`); } } else if (currentTab === TAB_TYPES.SETTINGS && sectionName) { - // For Settings tab, update with the section name updateHash(`#${currentTab}/${sectionName}`, false); } }; const handleSubSectionChange = (subSection, sectionName) => { setCurrentSubSection(subSection); - // Update URL hash when sub-section changes if (currentTab === TAB_TYPES.SETTINGS && sectionName) { if (subSection) { updateHash(`#${currentTab}/${sectionName}/${subSection}`); } else { - // Going back to section, remove sub-section from hash updateHash(`#${currentTab}/${sectionName}`); } } @@ -196,8 +169,6 @@ function MainModal({ modalClose, deepLinkData }) { const handleProductView = (product) => { setProductView(product); - // URL hash is already updated by child components (Browse.jsx) - // Browser history automatically tracks hash changes }; const handleResetDiscoverToAll = () => { @@ -206,7 +177,6 @@ function MainModal({ modalClose, deepLinkData }) { }; const handleBack = () => { - // Clear iframe breadcrumbs when navigating back setIframeBreadcrumbs([]); window.history.back(); }; @@ -215,8 +185,6 @@ function MainModal({ modalClose, deepLinkData }) { window.history.forward(); }; - // Browser manages history state, so we always show buttons enabled - // Browser will handle whether there's actually history to go back/forward const canGoBack = true; const canGoForward = true; diff --git a/src/components/Elements/MainModal/backend/Tab.jsx b/src/components/Elements/MainModal/backend/Tab.jsx index 838ee962..3534c79b 100644 --- a/src/components/Elements/MainModal/backend/Tab.jsx +++ b/src/components/Elements/MainModal/backend/Tab.jsx @@ -7,18 +7,14 @@ function Tab({ label, currentTab, onClick, navbarTab, isCollapsed }) { const t = useT(); const isExperimental = localStorage.getItem('experimental') !== 'false'; - // Get the icon component for this label (label is already translated) const IconComponent = getIconComponent(label, { getMessage: t }); - // Determine if this label should have a divider after it const hasDivider = DIVIDER_LABELS.some((key) => t(key) === label); - // Build className const baseClass = navbarTab ? 'navbar-item' : 'tab-list-item'; const activeClass = navbarTab ? 'navbar-item-active' : 'tab-list-active'; const className = `${baseClass}${currentTab === label ? ` ${activeClass}` : ''}`; - // Hide experimental tab if experimental mode is disabled const isExperimentalTab = label === t('modals.main.settings.sections.experimental.title'); if (isExperimentalTab && !isExperimental) { return