diff --git a/client_v2/AGENTS.md b/client_v2/AGENTS.md index d1e2caa4eb1..49b4b57d7e6 100644 --- a/client_v2/AGENTS.md +++ b/client_v2/AGENTS.md @@ -324,6 +324,10 @@ must not depend on stores or components. Helpers are pure and dependency-free. './Foo.module.pcss'` — and reference classes as `s.button`, `s[variant]`. Compose conditional classes with `clsx`, imported as `cn`: `import cn from 'clsx'` (e.g. `cn(s.button, s.primary, { [s.active]: on() })`). +- **Hover styles**: Wrap `:hover` styling in `@media (hover: hover)` so touch + devices do not keep a stuck highlight after a tap. Gate `:hover` together + with `:focus` on row links, since a tap also focuses an `` in Chrome; + leave `:focus-visible` outlines ungated for keyboard users. - **Colors**: Never hardcode color hex values in `.pcss` or inline styles. Always reference the CSS custom properties defined in `src/common/styles/vars.css` and `src/common/styles/colors/*.css` @@ -346,6 +350,14 @@ must not depend on stores or components. Helpers are pure and dependency-free. a conditional class via `cn(...)`, not an inline `style`. Exception: a single computed pixel offset for a dragging/positioning edge case may be acceptable when no class-based solution exists — justify it in a comment. +- **Comments**: Write comments only where they earn their place, and keep + them useful and compact — one or two lines explaining a non-obvious + *why*: a workaround, a browser quirk, or a cross-file constraint a + reader would otherwise break. Do not narrate what a rule or function + does, restate selector/property names, or spell out measurements, + arithmetic, and design history; that belongs with the value it + constrains or in the PR description. Applies to `.pcss`, `.tsx`, and + tests alike. Delete a comment once the code it describes changes. - **Reactivity**: Use `createSignal` for local state, `createMemo` for derived values, `createEffect` for side effects, `onMount` for initial data fetches, and `onCleanup` for teardown. Do not read signals imperatively diff --git a/client_v2/src/__locales/ar.json b/client_v2/src/__locales/ar.json index 4f5e0a1503f..aa79b8deffd 100644 --- a/client_v2/src/__locales/ar.json +++ b/client_v2/src/__locales/ar.json @@ -460,11 +460,11 @@ "ip_address": "عنوان IP", "known_tracker": "متعقب معروف", "language": "اللغة", - "last_days": "| آخر 0 يوم | آخر يوم واحد | آخر يومين | آخر %count% أيام | آخر %count% يومًا | آخر %count% يوم", - "last_hours": "| آخر 0 ساعة | آخر ساعة واحدة | آخر ساعتين | آخر %count% ساعات | آخر %count% ساعة | آخر %count% ساعة", + "last_days": "| آخر يوم واحد | آخر يومين | آخر %count% أيام | آخر %count% يومًا | آخر %count% يوم", + "last_hours": "| آخر ساعة واحدة | آخر ساعتين | آخر %count% ساعات | آخر %count% ساعة | آخر %count% ساعة", "last_updated_label": "آخر تحديث", "light_theme": "المظهر فاتح", - "list_updated": "| لا توجد قوائم محدثة | تم تحديث قائمة واحدة | تم تحديث قائمتين | تم تحديث %count% قوائم | تم تحديث %count% قائمة | تم تحديث %count% قائمة", + "list_updated": "| تم تحديث قائمة واحدة | تم تحديث قائمتين | تم تحديث %count% قوائم | تم تحديث %count% قائمة | تم تحديث %count% قائمة", "login": "تسجيل الدخول", "logout": "تسجيل خروج", "make_static": "اجعلها ثابتة", @@ -513,8 +513,8 @@ "password_requirements_uppercase": "على الأقل 1 حرف كبير", "path_to_file_placeholder": "طريق الملف: عنوان محلي أو عنوان على الشبكة العنكبوتية", "pause_for_hour": "إيقاف مؤقت لمدة %count% ساعة", - "pause_for_minutes": "| إيقاف مؤقت لمدة 0 دقيقة | إيقاف مؤقت لمدة دقيقة واحدة | إيقاف مؤقت لمدة دقيقتين | إيقاف مؤقت لمدة %count% دقائق | إيقاف مؤقت لمدة %count% دقيقة | إيقاف مؤقت لمدة %count% دقيقة", - "pause_for_seconds": "| إيقاف مؤقت لمدة 0 ثانية | إيقاف مؤقت لمدة ثانية واحدة | إيقاف مؤقت لمدة ثانيتين | إيقاف مؤقت لمدة %count% ثوانٍ | إيقاف مؤقت لمدة %count% ثانية | إيقاف مؤقت لمدة %count% ثانية", + "pause_for_minutes": "| إيقاف مؤقت لمدة دقيقة واحدة | إيقاف مؤقت لمدة دقيقتين | إيقاف مؤقت لمدة %count% دقائق | إيقاف مؤقت لمدة %count% دقيقة | إيقاف مؤقت لمدة %count% دقيقة", + "pause_for_seconds": "| إيقاف مؤقت لمدة ثانية واحدة | إيقاف مؤقت لمدة ثانيتين | إيقاف مؤقت لمدة %count% ثوانٍ | إيقاف مؤقت لمدة %count% ثانية | إيقاف مؤقت لمدة %count% ثانية", "pause_until_tomorrow": "إيقاف مؤقت حتى الغد، %time%", "period_notify": "لتغيير المدة الزمنية المعروضة، قم بتحرير الاحتفاظ بالاحصائيات في إعدادات الوضع العام", "plain_dns": "عنوان DNS العادي", @@ -635,14 +635,14 @@ "settings_confirm_decrease_stats_rotation_interval": "تقليل فترة الاحتفاظ بالإحصائيات؟", "settings_confirm_decrease_stats_rotation_interval_desc": "سيؤدي هذا إلى حذف جميع الاحصائيات الأقدم من %value%", "settings_custom": "مخصص", - "settings_days": "| 0 يوم | يوم واحد | يومان | %count% أيام | %count% يومًا | %count% يوم", + "settings_days": "| يوم واحد | يومان | %count% أيام | %count% يومًا | %count% يوم", "settings_domain_names": "أسماء النطاقات، واحد في كل سطر", "settings_filter_requests": "تصفية الطلبات", "settings_filter_requests_desc": "يمكنك استخدام Blocklists و Allowlists و قواعد المستخدم لإعداد قواعد التصفية", "settings_filtering_and_security": "التصفية والأمان", "settings_general_short": "الوضع العام", "settings_global": "عالمي", - "settings_hours": "| 0 ساعة | ساعة واحدة | ساعتان | %count% ساعات | %count% ساعة | %count% ساعة", + "settings_hours": "| ساعة واحدة | ساعتان | %count% ساعات | %count% ساعة | %count% ساعة", "settings_log_rotation_hours": "تدوير السجل، بالساعات", "settings_notify_changes_saved": "تم حفظ التغييرات", "settings_notify_query_log_cleared": "تم مسح سجل الاستعلام", diff --git a/client_v2/src/__locales/en.json b/client_v2/src/__locales/en.json index bbaacddcb2f..de98913edd5 100644 --- a/client_v2/src/__locales/en.json +++ b/client_v2/src/__locales/en.json @@ -3,7 +3,6 @@ "add_allowlist": "Add allowlist", "add_blocklist": "Add blocklist", "add_persistent_client": "Add as persistent client", - "add_to_allowlist": "Add to allowlist", "ads_blocked": "Blocked ads", "ads_blocked_card": "Blocked ads", "ads_blocked_tooltip": "The number of DNS queries blocked by adblock filters and hosts blocklist", @@ -12,7 +11,6 @@ "all_lists_up_to_date_toast": "All lists are already up-to-date", "all_rewrites_disabled": "All custom DNS responses will be disabled", "all_rewrites_enabled": "All custom DNS responses will be enabled", - "allow_service": "Allow service", "allowed": "Allowed", "allowlist_add": "Add allowlist", "allowlist_edit": "Edit allowlist", @@ -137,7 +135,7 @@ "dashboard": "Dashboard", "dashboard_statistics_disabled": "Statistics retention is disabled in General settings", "delete_btn": "Delete", - "delete_confirm": "Delete lease?", + "delete_confirm": "Remove lease?", "delete_confirm_desc": "This will free %ip%", "delete_table_action": "Delete", "delete_table_action_confirm": "Yes, delete", @@ -271,6 +269,7 @@ "dns_override_min_ttl_placeholder": "Time-to-live (TTL)", "dns_override_min_ttl_title": "Override minimum TTL", "dns_privacy": "DNS Privacy", + "dns_private_reverse_no_default_resolvers": "AdGuard Home could not determine suitable private reverse DNS resolvers for this system. Specify them to enable this setting.", "dns_private_reverse_resolve_clients_desc": "Resolves client IP addresses to their hostnames using PTR queries. Uses private DNS servers for local clients and upstream servers for clients with public IP addresses", "dns_private_reverse_resolve_clients_title": "Reverse lookup of client IP addresses", "dns_private_reverse_resolvers": "Private reverse DNS resolvers", @@ -280,9 +279,8 @@ "dns_private_reverse_servers_desc_2": "If this field is empty, your operating system's default DNS resolvers are used, except for AdGuard Home IP addresses. By default, AdGuard Home uses the following reverse DNS resolvers: %value_1% and %value_2%", "dns_private_reverse_servers_label": "Private reverse DNS servers, one per line", "dns_private_reverse_servers_placeholder": "IP addresses, sdns://, quic://, https://, h3://, or tls://", + "dns_private_reverse_servers_required": "Specify at least one private reverse DNS server or turn off private reverse DNS resolvers", "dns_private_reverse_servers_resolvers": "Default DNS resolvers: %value_1% and %value_2%", - "dns_private_reverse_servers_title": "Private reverse DNS servers", - "dns_protocols_title": "DNS protocols", "dns_queries": "DNS queries", "dns_queries_tooltip": "The number of DNS queries processed for the last 24 hours", "dns_query": "DNS queries", @@ -299,7 +297,6 @@ "dns_rate_limit_title": "Rate limit", "dns_rate_limit_value": "%value% req/s", "dns_rewrite_exists": "This DNS rewrite already exists", - "dns_rewrite_removed": "DNS rewrite removed", "dns_rewrite_same": "You can’t rewrite to the same domain or wildcard", "dns_rewrites": "DNS rewrites", "dns_rewrites_desc": "Lists your custom DNS responses for specific domain names", @@ -372,8 +369,6 @@ "encryption_server_tooltip": "The domain name of your server. It must match a DNS name in the certificate. If set, AdGuard Home will detect ClientIDs, respond to DDR queries, and perform additional connection validations", "encryption_subject": "Subject: %value%", "encryption_title": "Encryption", - "encryption_unable_read_cert": "Unable to read certificate file", - "encryption_unable_read_key": "Unable to read private key file", "error": "Error", "filter_added_successfully": "Added blocklist: %value%", "filter_added_successfully_allowlist": "Added allowlist: %value%", @@ -404,7 +399,6 @@ "form_error_answer_format": "Invalid result format", "form_error_client_id_format": "ClientID must contain only numbers, lowercase letters, and hyphens", "form_error_domain_format": "Invalid domain format", - "form_error_equal": "Must not be equal", "form_error_format": "Invalid format", "form_error_format_line": "Invalid format on line %line%", "form_error_format_lines": "Invalid format on lines %lines%", @@ -447,15 +441,14 @@ "inactivity_schedule_edit": "Edit schedule", "inactivity_schedule_no_schedules": "No schedule", "inactivity_schedule_replace_desc": "This schedule will replace any existing schedules for the same day of the week. Each day can have only one inactivity period", - "inactivity_schedule_time_from": "Time from", - "inactivity_schedule_time_to": "Time to", + "inactivity_schedule_time_from": "Time from (HH:mm)", + "inactivity_schedule_time_to": "Time to (HH:mm)", "inactivity_schedule_timezone": "Timezone", "install_auth_confirm": "Confirm password", "install_auth_password": "Password", "install_auth_password_enter": "Enter password", "install_auth_username": "Username", "install_auth_username_enter": "Enter username", - "install_saved": "Saved successfully", "install_settings_all_interfaces": "All interfaces", "install_settings_port": "Port", "install_static_configure": "AdGuard Home has detected that the dynamic IP address %ip% is used. Do you want it to be set as your static address?", @@ -519,7 +512,6 @@ "password_requirements_match": "Passwords match", "password_requirements_special": "English letters, numbers, and special characters", "password_requirements_uppercase": "At least 1 uppercase letter", - "path_to_file_placeholder": "Path to file: local or Internet address", "pause_for_hour": "Pause for %count% hour", "pause_for_minutes": "| Pause for %count% minute | Pause for %count% minutes", "pause_for_seconds": "| Pause for %count% second | Pause for %count% seconds", @@ -582,7 +574,7 @@ "remove": "Remove", "remove_tag": "Remove %value%", "remove_tls_certificate": "Remove TLS certificate?", - "remove_tls_certificate_desc": "Clients using encrypted DNS won’t be able to connect until a new TLS certificate is added", + "remove_tls_certificate_desc": "Removing the certificate will disable HTTPS and reset TLS settings. If you are connected over HTTPS, your session will end and you will be redirected to http://%host%:%port%", "report_an_issue": "Report a problem", "request_table_header": "Request", "requests_table_header": "Requests", @@ -621,6 +613,7 @@ "safe_search_used_tooltip": "The number of DNS queries to search engines for which Safe Search was enforced", "saturday": "Saturday", "save": "Save", + "save_anyway": "Save anyway", "save_btn": "Save", "search_placeholder": "Search", "select_language": "Select language", @@ -658,7 +651,7 @@ "settings_notify_query_log_cleared": "Query log cleared", "settings_notify_statistics_cleared": "Statistics cleared", "settings_parental_control": "Parental control", - "settings_parental_control_desc": "Block websites with content inappropriate for children", + "settings_parental_control_desc": "Block websites with inappropriate content, such as pornography, violence, gore, and other disturbing material, using AdGuard’s continuously updated family filter database", "settings_rotation_placeholder": "Use numbers", "settings_rule_syntax": "Rule syntax", "settings_safe_search": "Safe search", @@ -793,7 +786,7 @@ "tls_setup_config_title": "Enable encrypted DNS", "tls_setup_enable_anyway": "Enable anyway", "tls_setup_error_cert_incomplete": "Certificate is incomplete. Paste the full text, including the BEGIN and END lines", - "tls_setup_error_duplicate_port": "Port %port% is used by multiple DNS protocols. It must be unique", + "tls_setup_error_duplicate_port": "Port %port% is used by more than one protocol. Choose a different port for each encrypted DNS protocol", "tls_setup_error_ed25519_key": "This key type is not supported by browsers. Use an RSA or ECDSA key instead", "tls_setup_error_enable_failed": "Unable to enable encrypted DNS. Check the settings and try again", "tls_setup_error_key_incomplete": "Private key is incomplete. Paste the full text, including the BEGIN and END lines", diff --git a/client_v2/src/__locales/hr.json b/client_v2/src/__locales/hr.json index 2f61c282675..6cba256b700 100644 --- a/client_v2/src/__locales/hr.json +++ b/client_v2/src/__locales/hr.json @@ -460,11 +460,11 @@ "ip_address": "IP adresa", "known_tracker": "Poznati pratitelj", "language": "Jezik", - "last_days": "| Zadnji %count% dan | Zadnjih %count% dana", - "last_hours": "| Zadnjih %count% sat | Zadnjih %count% sati", + "last_days": "| Zadnji %count% dan | Zadnja %count% dana | Zadnjih %count% dana", + "last_hours": "| Zadnjih %count% sat | Zadnja %count% sata | Zadnjih %count% sati", "last_updated_label": "Zadnja izmjena", "light_theme": "Svijetla tema", - "list_updated": "| %count% popis ažuriran | %count% popisi ažurirani", + "list_updated": "| %count% popis ažuriran | %count% popisa ažurirana | %count% popisi ažurirani", "login": "PRIJAVA", "logout": "Odjava", "make_static": "Učini statičnim", @@ -513,8 +513,8 @@ "password_requirements_uppercase": "Najmanje jedno veliko slovo", "path_to_file_placeholder": "Put do datoteke: lokalna ili internetska adresa", "pause_for_hour": "Pauza na %count% sat", - "pause_for_minutes": "| Pauza od %count% minute | Pauza od %count% minuta", - "pause_for_seconds": "| Pauza %count% sekunde | Pauza na %count% sekunde", + "pause_for_minutes": "| Pauza od %count% minute | Pauza od %count% minute | Pauza od %count% minuta", + "pause_for_seconds": "| Pauza %count% sekunde | Pauza na %count% sekunde | Pauza na %count% sekunde", "pause_until_tomorrow": "Pauza do sutra, %time%", "period_notify": "Za promjenu prikazanih razdoblja uredite Zadržavanje statistike u Općim postavkama", "plain_dns": "Obični DNS", @@ -635,14 +635,14 @@ "settings_confirm_decrease_stats_rotation_interval": "Smanjiti interval zadržavanja statistika?", "settings_confirm_decrease_stats_rotation_interval_desc": "To će izbrisati sve statistike starije od %value%", "settings_custom": "Prilagođeno", - "settings_days": "| %count% dan | %count% dana", + "settings_days": "| %count% dan | %count% dana | %count% dana", "settings_domain_names": "Domene, po jedna po liniji", "settings_filter_requests": "Zahtjevi za filtriranje", "settings_filter_requests_desc": "Možete koristiti Blocklists, Allowlists i Korisnička pravila za postavljanje pravila filtriranja", "settings_filtering_and_security": "Filtriranje i sigurnost", "settings_general_short": "Općenito", "settings_global": "Globalno", - "settings_hours": "| %count% sat | %count% sati", + "settings_hours": "| %count% sat | %count% sata | %count% sati", "settings_log_rotation_hours": "Rotacija trupaca, u satima", "settings_notify_changes_saved": "Promjene su spremljene", "settings_notify_query_log_cleared": "Dnevnik upita očišćen", diff --git a/client_v2/src/__tests__/common/controls/Textarea.test.tsx b/client_v2/src/__tests__/common/controls/Textarea.test.tsx index 1aaa17ad70f..461001beb79 100644 --- a/client_v2/src/__tests__/common/controls/Textarea.test.tsx +++ b/client_v2/src/__tests__/common/controls/Textarea.test.tsx @@ -115,3 +115,35 @@ describe('Textarea — comment highlight', () => { expect(overlay?.parentElement).toBe(textarea?.parentElement); }); }); + +describe('Textarea — onCard', () => { + it('applies the onCard class to the field when set', () => { + const { container } = render(() =>