1513 Commits
Author SHA1 Message Date
Pythame bc962e9aad fix: escape labels during JSON import and render tag dropdown as text (#9504)
- Normalize label names parsed from Google Reader JSON categories during import, using the same escaping helper as the other imported string fields.
- Decode stored label names before returning them to the dynamic tag dropdown.
- Build dynamic tag dropdown entries with DOM text nodes instead of HTML string concatenation.
2026-10-25 11:48:58 +01:00
Inverle 0addf32b36 Translate non-English code comments and identifiers (#9383)
Note: The changes in this PR are entirely AI-authored but manually reviewed and tested

## Summary

Translate the remaining French prose and French-derived identifiers in the main FreshRSS/Minz codebase to English.

Why: helps to understand the code

## Scope

- Minz comments, PHPDoc, and paginator identifiers
- Application comments and related identifiers
- Installer database identifiers
- Theme metadata descriptions and the identified CSS comments, previous discussion: https://github.com/FreshRSS/FreshRSS/pull/8241#discussion_r2553181653
- Related call sites and view helpers

External libraries and intentional i18n/docs content are excluded.
2026-10-20 09:52:31 +02:00
Danny 8769b4e29b Do not log the Fever API key on authentication failure (#9468)
## Problem

``p/api/fever.php`` logs the full Fever API key on every authentication failure:

```php
Minz_Log::warning('Fever API: wrong credentials! ' . $feverKey .
    ' ; Remote IP address=' . Minz_Request::connectionRemoteAddress(), API_LOG);
```

The Fever key is the user's equivalent API password and it does not expire, so it lands in ``data/users/<user>/log_api.txt`` (written at the default production log level) where anyone with log read access can harvest it. Failed-auth attempts are exactly the case where the submitted credential should never be persisted — this looks like a leftover from before the earlier log-scrubbing pass (#5001).

## Fix

Keep the remote IP address (useful for brute-force monitoring) but drop the key from the message. One-line change; no behavior change otherwise.
2026-10-19 20:21:35 +02:00
Inverle 74556f806f Disallow user page navigation while refreshing feeds (#9446)
For example to prevent an accidental tab closing during feeds refresh.
(beforeunload confirmation is used to achieve this, similar to leave validation)
2026-10-15 22:29:41 +02:00
HawkForge 6d30fb4af0 Fix Mark as read button left edge in Origine on mobile (#9441)
At max-width 840px the text button is hidden and only the dropdown toggle
is shown. Origine restores its left border and radius with a rule that
used `#nav_menu_read_all .btn` until #9205 turned the ID into a class.
`.nav_menu_read_all .btn` is now weaker than the generic
`.stick .btn + .dropdown > .btn` and `.group .dropdown:last-child > .btn`
rules, so the toggle lost its left border and rounded left corners.
Use the same selector as the other themes.

Closes #9428
2026-10-15 16:34:02 +02:00
abp002andAlejandro Borrego 48547b4a6c Reduce memory consumption of export (#9350)
Contributes to #6139 (export part; the import will follow in another PR)

Exporting favourites was collecting the whole file in memory. With many articles, it fails with the default PHP memory limit.

## Changes
- New `Minz_View::helperToFile()`, similar to `helperToString()`, writing the output of a helper into a file by chunks of 64 KiB instead of keeping it in memory.
- `FreshRSS_Export_Service`: the `generate*()` methods now return the path of a temporary file instead of the content, and `zip()` builds the archive from those files (`ZipArchive::addFile()` instead of `addFromString()`). Temporary files are created in `TMP_PATH` (already required to be writable) and deleted at the end of the script.
- The export is sent with `readfile()`: Web UI, `cli/export-zip-for-user.php`, `cli/export-opml-for-user.php` and the OPML export of the Google Reader API.
- Note: this changes what `FreshRSS_Export_Service` returns (a path instead of the content). All callers in the repository are updated, and I could not find any public extension using this service.

## Measurements
Peak PHP memory (`memory_get_peak_usage(true)`), SQLite database with synthetic starred articles:

| Scenario | Before | After |
|---|---|---|
| Web, export your favourites, 10,000 articles (69 MB JSON) | 136 MB (fails with the default `memory_limit` of 128 MB) | 6 MB |
| Web, ZIP (OPML + favourites + labelled articles + 4 feeds), 10,000 articles | 152 MB | 6 MB |
| CLI `export-zip-for-user.php`, 10,000 articles | 189 MB (fails with 128 MB) | 8 MB |
| Web, export your favourites, 30,000 articles (208 MB JSON) | 404 MB | 10 MB |

Co-authored-by: Alejandro Borrego <210245044+abp002@users.noreply.github.com>
2026-10-05 21:47:19 +02:00
Alexandre Alapetite da9dc448c6 Fix API custom favicon (#9409) 2026-10-05 07:59:57 +02:00
Abdelrhman Mohamed 820687e78c Keep the reader view article date on one line (#9388)
Follow-up to #9369, as discussed in https://github.com/FreshRSS/FreshRSS/pull/9369#issuecomment-5954861516

Changes proposed in this pull request:

- In the reader view, the date in the line under the article title had a fixed `width: 155px`. A long date such as "28 September 2026 at 18:30" needs about 190px, so at desktop width it broke into two lines. This was already the case before #9369.
- `.content .subtitle.horizontal-list .item.date` now uses the 155px as `min-width` and has `white-space: nowrap`, so the box grows to fit the date and the date stays on one line.
- Short dates are unchanged: they keep the 155px box and the same position.
- If the feed name, authors and date do not fit on one row, the date moves to the next line as a whole (the `flex-wrap` from #9369), instead of breaking in the middle.
- In the `max-width: 840px` block the date is reset to `min-width: 0; white-space: normal`, so nothing can overflow on very narrow screens. The phone layout from #9369 is unchanged.
- `frss.rtl.css` was regenerated with `rtlcss`.
2026-10-04 18:21:22 +02:00
Abdelrhman Mohamed e7cbb59c66 Wrap the reader view article subtitle on narrow screens (#9369)
In the reader view, the feed name, authors and date share one
horizontal-list row whose items are `flex: 0 0 auto` and whose date is
a fixed 155px wide. The row could neither wrap nor shrink, so a long
feed name or author list pushed the date past the article box, and on
a phone the whole page scrolled sideways.

Let the row wrap and let its items shrink, so a long feed name is
ellipsized and the authors wrap inside the box. On narrow screens the
date then sits on its own line, so drop its fixed width there.
Rows that already fit are laid out as before.

Closes #9360
2026-10-02 08:40:33 +02:00
Nikolai Shcheglov 1f6d7853b3 Fix error when opening a label menu in sidebar (#9377)
Since #9170, opening the menu of a label in the sidebar throws
`TypeError: Cannot set properties of null (setting 'href')`, as the
menu of labels has no website link. The rest of the click handler is
then skipped, so for instance the menu does not flip upwards when there
is no room below it.

Assisted-by: Claude <noreply@anthropic.com>
2026-10-01 08:24:29 +02:00
InverleandAlexandre Alapetite 0285dc8970 Check SVG optimization in the tests CI workflow with SVGO (#9361)
* Check SVG optimization in the tests CI workflow with SVGO

Previously it was only recommended by the documentation that SVG images should be optimized using [SVGOMG](https://github.com/jakearchibald/svgomg), the web GUI version of https://github.com/svg/svgo (CLI)


Now all SVG images are optimized, and optimization is mandatory enforced by the tests CI workflow.
\+ SVGs are sanitized of potentially malicious JS code using the https://svgo.dev/docs/plugins/removeScripts/ plugin

* Output prettified SVGs instead and remove unchanged files from stdout

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Use `removeScripts` SVGO plugin to reduce risk of malicious SVGs

* Move the CLI options to the JS config instead

* Disable `removeComments` plugin included in the default preset

* editorconfig, and easier config

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-09-29 16:18:01 +02:00
Alexandre Alapetite a8e5e6300b Better enforce feed limits (#9357)
E.g. when added from API
Fix https://github.com/FreshRSS/FreshRSS/issues/8822#issuecomment-5781019651
2026-09-28 10:41:29 +02:00
Alexandre Alapetiteandkta1kri 64f7f24c6e Improve WebSub security (#9330)
* Improve WebSub security
New keys are now more random

* Refuse to downgrade from HTTPS to HTTP

Co-authored-by: kta1kri <77656895+kta1kri@users.noreply.github.com>
2026-09-20 17:25:43 +02:00
Alexandre Alapetite 8067fd994b Use PHP SensitiveParameter (#9322)
https://www.php.net/class.sensitiveparameter
Active from PHP 8.2+ but parses without effect on PHP 8.1
2026-09-19 19:52:07 +02:00
Alexandre Alapetite 74092a7fda Fix regression sharing links (#9303)
Fix https://github.com/FreshRSS/FreshRSS/issues/9302
Some links must be decoded before use.
Regression from https://github.com/FreshRSS/FreshRSS/pull/9146
2026-09-14 20:50:09 +02:00
András Marczinkó 64ce16db86 Added the missing semicolons to the grid-area manage-labels and the manage-share (#9298)
It added 4 missing semicolons to the base-theme frss.css files.
2026-09-12 22:20:59 +02:00
InverleandAlexandre Alapetite 2cabd29d1b Fix infinite redirect loop due to SCRIPT_NAME in PATH_INFO (#9282)
* Fix infinite redirect loop due to `SCRIPT_NAME` in `PATH_INFO`

* Move to Minz_Request::pathInfo()

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-09-10 14:20:35 +02:00
AdamKorczandAlexandre Alapetite dc5cf8221b Limit the length and parentheses nesting depth of a search query (#9277)
* Limit the length and parentheses nesting depth of a search query.

* Use exception

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-09-09 16:12:41 +02:00
Ali GündoğarandAlexandre Alapetite 970b135190 Ignore security-relevant OPML attributes for dynamic OPML sources (#9276)
* Ignore security-relevant OPML attributes for dynamic OPML sources

OPML content can come from an untrusted source (a dynamic OPML category re-fetches its remote content on every refresh cycle). Such content must not be able to configure feed cURL parameters (CURLOPT_COOKIE, CURLOPT_PROXY, CURLOPT_POSTFIELDS, etc.) nor create further dynamic OPML categories. importOpml() therefore defaults to untrusted, and the local file import controller explicitly opts in.

* Review

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-09-09 16:11:38 +02:00
Inverle b182fb94e8 Fix desync between favicon and title unread counters (#9262)
Closes #8237
Regression from https://github.com/FreshRSS/FreshRSS/commit/80e7e15e3f619408db1752a3899ff708bc7bcc93, https://github.com/FreshRSS/FreshRSS/commit/d289c5340f3b21f1561e57390e89b0b5c7881d17

The function for updating the favicon count would only get called if there were articles newly marked as unread, so if there were only articles newly marked as read, the separate counters would display different values.

Behavior is now the same as for updating the title - always updating the favicon counter, without requiring any conditions to be true.
2026-09-06 21:35:15 +02:00
András Marczinkó 1a33a2999f Fix typo in alert error border color variable (#9266) 2026-09-06 21:33:40 +02:00
SamCyber001 12b5be6afc Gate greader/json share formats behind Share by OPML (#9248)
* Gate greader/json share formats behind Share by OPML

Follow-up to #9246, per @Alkarex's suggestion on GHSA-rpmm-h4hx-5p6p.

The greader and json share formats expose origin.feedUrl (the feed's subscription
URL). Sharing feed URLs is what the "Share by OPML" option explicitly means, whereas
"Share by RSS" is meant to share article content. This moves greader and json in
p/api/query.php from the shareRss gate to the shareOpml gate, so feed URLs are only
served when the owner opted into OPML sharing.

Note: moved json alongside greader too, since both emit feedUrl (same toGReader path).

* sharedUrlGreader: gate on shareOpml

* Move greader share link under Share by OPML group
2026-08-31 14:43:55 +02:00
Inverle 0a44b7d017 Follow-up #9169 remove form IDs everywhere (#9205)
* Follow-up #9169 remove form IDs everywhere

* Add some `<form name>` alongside `class`
2026-08-29 23:21:39 +02:00
Alexandre Alapetite f902047204 Offer to add a prefix when resharing user labels (#9236)
* Offer to add a prefix when resharing user labels
Improve https://github.com/FreshRSS/FreshRSS/pull/8112

* make fix-all
2026-08-28 08:51:57 +02:00
InverleandAlexandre Alapetite a84c0a8bc2 Fix all broken links in the entire repository (#9207)
* Fix all broken links in the entire repository

* Update docs/CHANGELOG-old2.md

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update config.default.php

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update README.md

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update docs/i18n/freshrss.fr.po

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update docs/i18n/templates/freshrss.pot

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update README.md

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update docs/fr/users/01_Installation.md

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update docs/fr/users/01_Installation.md

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update docs/en/internationalization.md

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Manual fixes, preferences
* Prefer canonical when known, prefer shorter, prefer roots, prefer URLs with content negotation (e.g. language preference)
* Keep 302, 307 unchanged
* Use only `.example`, `example.net` or similarly reserved domains for URL examples

* Fix language negotiation for developer.mozilla.org

* Restore a Stackoverflow 302

* Fix some example.com

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-08-19 16:19:21 +02:00
Inverle 48ece4200f Improve aside keyboard navigation (#9202)
* Allow jumping from category to feed with keyboard shortcuts

* It's now possible to jump from categories to feeds (press shift+j when focused on a category)
   * Similarly, it's possible to jump from a feed to the current category by using alt+k
* If the very first feed is focused and the user presses shift+k, there is no jump to the last unread feed in that category anymore, instead nothing happens.
   * Same if the very last is focused and the user presses shift+j (no jump to the first unread feed in that category)
* Alt+home/end (first/last category) now jumps regardless if the first/last category is read or unread
   * In addition, shift+home/end does not jump between unread feeds anymore, now it jumps between the first visible feed in the category and the last visible feed in the category 
* Alt+h (jump to next unread category) now fall backs to the same behavior as alt+j (jump to next category) if there are no more unread categories after

* Remove dead `jump_to_unread` code in `prev_feed`

* Jump to first/last categories even if they're not unread

* Ensure jump is only to visible items

* Focus current category if jumping from feed

* `delayedClick(null)` will now throw an error
2026-08-17 20:41:26 +02:00
24039afb10 Remove stale dynamic favicon links (#9127)
* Remove stale dynamic favicon links

- remove every previous dynamic favicon link before appending the updated unread-count icon
- prevent stale duplicate favicon links from being preferred by browsers

Fixes #8237.

* Replace dynamic favicon in place

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Inverle <inverle@proton.me>
2026-08-15 00:08:07 +02:00
Inverle 1146d05bdd Allow modifiers for open website shortcut (#9186)
Minimal changes borrowed from https://github.com/FreshRSS/FreshRSS/pull/8990

Allows usage of <kbd>Ctrl/Shift</kbd> keys with the open website shortcut, so that you can open an article either in a new background tab or a new window.

Doesn't work in Chromium browsers, the website opens normally in a new tab there instead. (Firefox only)
2026-08-09 20:58:40 +02:00
Alexandre Alapetiteandsenti-man acef1de21f Security: Use hash_equals() for GReader API token comparison (#9183)
GReaderAPI::authorizationToUser() compared the long-lived GReader
bearer token with the non-constant-time === operator instead of
hash_equals(), unlike every other token/secret comparison in the
codebase (Auth::checkCsrfProtection(), FeverAPI, the OPML/RSS feed
token check, and the short-lived GReader token check a few lines
below in the same file), all of which already use hash_equals().

This brings the long-lived token check in line with the existing
hash_equals(sha1(...), $token) pattern already used for the
short-lived token a few lines below.

Co-authored-by: senti-man <sentiman49@gmail.com>
2026-08-08 20:52:55 +02:00
Alexandre Alapetite cb482dd836 Bump globals and @eslint/js (#9181)
Follow-up of https://github.com/FreshRSS/FreshRSS/pull/9178
2026-08-07 10:27:15 +02:00
Alexandre Alapetite 324c1e3978 Bump neostandard and blocked eslint (#9178)
https://github.com/neostandard/neostandard/issues/350
https://github.com/neostandard/neostandard/releases/tag/v0.14.0-next

Replaces https://github.com/FreshRSS/FreshRSS/pull/8751
2026-08-07 09:41:22 +02:00
Inverle 6bd14a6121 Refactor to use CSRF field directly instead of borrowing it from a form (#9169)
HTML injections outside of a form with a `_csrf` field set, could instead refer to the CSRF token form with a payload such as:
```html
<button form="post-csrf" formaction="./i/?c=auth&a=logout" formmethod="post">Continue reading</button>
```
While the `post-csrf` form is useful for reducing the amount of code needed (no need to repeat the `_csrf` field in forms), I don't think it's worth the added security risk.
2026-08-05 22:19:43 +02:00
John BraytonandAlexandre Alapetite 551427ce38 Issue #9166: Detect JSON Feed URLs when subscribing via the API (#9167)
* feat(api): Add detection of JSON feed when subscribing via the Google Reader API.

* Added self to CREDITS.md

* Rather than making a network request, use heuristics based on the URL string to determine if the feed URL likely points to a JSON feed.

* Minor syntax preference and comment reduction

* Minor syntax

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-08-05 21:39:35 +02:00
Inverle 0cf8815de1 Fix HTML injection via decoded website attribute in feed menu (#9170)
To reproduce:
1. Add a feed e.g. `https://github.com/FreshRSS/FreshRSS/releases.atom`
2. Change its website URL to `https://github.com/FreshRSS/FreshRSS/releases.atom?"><marquee>test</marquee><?x`
3. Open the feed's menu from the sidebar after saving the settings

Follow-up of https://github.com/FreshRSS/FreshRSS/pull/9146 (same issue but in a different menu)
2026-08-03 00:02:04 +02:00
Alexandre Alapetite 1dced38fc1 Fix PHPStan 2.2.7 (#9156) 2026-08-01 23:14:35 +02:00
Gerard Alvear Porras 2a42d79cda Fix sharing menu conflicts with extensions (#9074)
Detect only the share menu that belongs directly to the share dropdown.
An extension can add an unrelated nested `.dropdown-menu`. The previous descendant selector then skipped share-menu creation, leaving share URL placeholders unreplaced.
Fixes #7820.
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
2026-07-30 12:22:51 +02:00
Alexandre Alapetite 1884db7bec New CSS class alert-info (#9145)
To be used by https://github.com/FreshRSS/FreshRSS/pull/9144

For instance bluish info box in the Origine theme in light mode:
<img width="813" height="196" alt="image" src="https://github.com/user-attachments/assets/1649cf9d-0287-4b18-9cc7-80f457ec4ae0" />
2026-07-29 16:34:52 +02:00
Alexandre Alapetite 1b7178a18b Security: apply encodeURIComponent to share template (#9146)
For instance if an article title is `<title>XSS &quot;&gt;&lt;img src=x&gt; END</title>`

Reported by @meifukun
2026-07-28 11:47:07 +02:00
1460e75730 Fix auto-share shortcut on first use (#9072)
* Fix auto-share shortcut on first use


- Re-read the share actions after creating the lazy-loaded share menu.
- Restore the documented one-key share behavior when exactly one action is configured.

## Why

The shortcut checked the action count before it built the menu. On first use it therefore found no action and required a second press.

Fixes #7299.

* Slight variable scope adjustment

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-24 19:17:14 +02:00
Frans de Jonge 6e7bd22c1b Fix weird hover issues on overflowing .item-element.title (#9140)
Just remove the whole shebang leftover from the pre-grid CSS.

Fix #8824.
2026-07-23 13:23:43 +02:00
InverleandAlexandre Alapetite f2e97b6399 Redirect away from wrong URL paths to fix displaying of relative URLs (#9075)
* Redirect away from wrong URL paths to fix displaying of relative URLs

* Also strip path info for API pages

* Error for API

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-22 19:07:01 +02:00
46f5e13019 Refresh only feeds in the current view (#9060)
* Refresh only feeds in the current view

Closes #8025. Passes the current feed or category selection to the refresh endpoint and limits the batch accordingly. 

* Fix case of Dynamic OPML, and keep feed order

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-21 17:46:55 +02:00
Gerard Alvear PorrasandGerard Alvear 9e92ace6f2 Fix extension settings cancel action (#9053)
Closes #3662. Closes the extension settings slider when its reset/cancel control is used while preserving unsaved-change confirmation.

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
2026-07-21 17:45:36 +02:00
1b8eb17858 Add per-feed option to show or hide enclosures (#9015)
* Add per-feed option to show or hide enclosures

Some feeds attach lots of enclosures (images, audio, video, downloads)
that clutter the article view for readers who only want the text. Add
a per-feed "Show enclosures" setting (Feed configuration > Display),
defaulting to "by default" (i.e. shown, matching current behaviour),
following the same ternary-attribute pattern used by existing
per-feed overrides such as show_unread_count and read_upon_gone.

Also apply the preference to the GReader-compat API content call and
the Fever API entry serialization, so third-party clients like Reeder
and Fever apps respect it too, not just the normal web UI. Renamed the
translation key to display_enclosures to match the underlying feed
attribute name.

Fixes #4999

* i18n: fr

* Fix Fever API

* Remove accessor

* Forgotten fix

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-20 18:45:21 +02:00
c5fc194624 Detect JSON feeds from .json URLs (#9058)
* Detect JSON feeds from .json URLs

Closes #7574. Detects .json feed URLs while adding a subscription and selects the JSON Feed source type automatically. 

* Auto open details

* Broaden JSON feed URL detection

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-20 18:01:41 +02:00
CronandAlexandre Alapetite 94ff6e4366 Stop the unread-count poller after the session ends (#9065)
* fix: stop the unread poller after the session ends

refreshUnreads() polls nbUnreadsPerFeed every 2 minutes and never checks
the response status, so once the session ends it keeps polling a dead
session indefinitely. Each request is a 401 (auth proxy / OIDC image) or
403 (stock); behind fail2ban the repeated failures ban the client IP.

Handle it like the other badAjax(status == 403) call sites: on a 401/403,
reload to re-authenticate instead of retrying.

* Enlarge HTTP 4xx status range

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-18 22:26:32 +02:00
1944b97bbf Use :user-invalid instead of :invalid for form field styling (#9025)
* Use :user-invalid instead of :invalid for form field styling

Every theme styled required inputs and selects with :invalid, which
matches as soon as an empty required field renders, before the user
has even touched it. This made fields such as the username on the
"Add user" admin form show a red border on first load, with nothing
wrong yet.

Switch to :user-invalid, which only matches after the user has
interacted with the field (e.g. on blur or submit), so a field only
turns red once it is actually flagged as invalid by the user's own
input. Browser support for :user-invalid is now good enough across
evergreen browsers (~89%, per caniuse) for this to be safe, which
was the blocker that stopped a previous attempt at this fix in #3724.

Fixes #3647

* CSS: support legacy Gecko invalid styles

* Drop legacy -moz-ui-invalid
Not important enough

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-18 20:19:56 +02:00
28bc73afed Dispatch a JS event when an entry finishes being marked read/unread (#9031)
* Dispatch a JS event when an entry finishes being marked read/unread

Extensions that render their own read/unread indicator outside of the
main article list (e.g. a custom reading pane) had no way to know when
a mark read/unread AJAX request completed, so their own UI could get
stuck (e.g. a spinner never reset).

Dispatch a `freshrss:entryStateChange` event on `document` with the
entry id and new state once the toggle has been applied, following the
existing `freshrss:*` JavaScript event convention used elsewhere in
main.js.

Fixes #8862

* Minor preferences

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-12 23:04:12 +02:00
TowyTowyandClaude Fable 5 8aa8655cda fix(js): use Event() constructor instead of deprecated initEvent() (#9035)
`document.createEvent('Event')` followed by `Event.initEvent()` is
deprecated (https://developer.mozilla.org/en-US/docs/Web/API/Event/initEvent).
Replace the two remaining usages in `main.js` with the standard
`new Event(name, { bubbles, cancelable })` constructor, matching the
pattern already used in `extra.js`. The `bubbles` and `cancelable`
flags are kept identical (both `true`) so dispatch behaviour is
unchanged.

Fixes #5152

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 22:51:15 +02:00
Jam BalayaandClaude Opus 4.8 3d04c8ef49 Prefer OS-bundled Japanese fonts when UI language is Japanese (#9002)
Add a :lang(ja)-scoped font-family in the shared frss.css so kanji/kana
render with Japanese glyphs on Japanese UIs, without affecting Chinese
(zh-CN/zh-TW) users. Revives the intent of PR #5671 with a
cross-platform font list. frss.rtl.css is regenerated via rtlcss.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 21:01:53 +02:00