* feat(cli): add reconfigure-user.php to read/write per-user config attributes
Closes#8869.
Adds `cli/reconfigure-user.php`, a first-class CLI for per-user configuration attributes — the user-level equivalent of the existing `reconfigure.php` (system config).
### Usage
```sh
# List all attributes (sensitive keys redacted by default)
./cli/reconfigure-user.php --user alice --list
./cli/reconfigure-user.php --user alice --list --show-secrets
# Read a single attribute (exit 2 if key not found)
./cli/reconfigure-user.php --user alice --key language
# Set an attribute (type inferred from existing value: bool, int, string)
./cli/reconfigure-user.php --user alice --key language --set --value fr
# Set from stdin (recommended for secrets — keeps value out of shell history / ps)
./cli/reconfigure-user.php --user alice --key some_token --set --value-stdin < token.txt
# Create a new key, e.g. for an extension (unknown keys rejected by default)
./cli/reconfigure-user.php --user alice --key my_ext_setting --set --value hello --force
# Delete an attribute (exit 2 if key not found)
./cli/reconfigure-user.php --user alice --key some_token --unset
```
### Changes
- `cli/reconfigure-user.php` — new command
- `lib/Minz/Configuration::toArray()` — exposes the full config array (used by `--list`)
- `cli/README.md` — documents the new command
- `tests/cli/UserConfigOptionsParserTest.php` — PHPUnit tests for the options parser, following the existing `CliOptionsParserTest` pattern (shared `cli-parser-test.php` helper)
### Test plan
- `make test-all` passes
- Tested manually against a local FreshRSS instance: `--list`, `--key` (get), `--set` (bool/int/string inference), `--value-stdin`, `--unset`, `--force`, error paths (unknown key without `--force`, wrong type)
* unserialize allowed_classes
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Replace absolute positioning inside `.titleAuthorSummaryDate` with CSS Grid. Title, date, and summary use `grid-area` instead of `position: absolute` + `margin-top: 2.25rem`.
Drops magic numbers that had to stay in sync:
- Three uses of `155px` (date width, title min, title padding) all describing the same column
- `2.25rem` margin-top on summary that had to clear the absolute title
- `90%` summary max-width to keep it off the date
Truncated-title hover-extend now uses `grid-column: 1 / -1` instead of `padding-right: 0.5rem`.
Depends on #8823: grid inside `display: table` mis-sizes in older Gecko, causing summary to misbehave. With the flex base from that PR, the grid sizes correctly in SeaMonkey 2.53.
## Test plan
- `make test-all` passes
- Verified in LibreWolf and SeaMonkey 2.53 with Nord and Origine themes
- Wide, narrow, and mobile viewports
- Toggle combinations: date on/off, summary on/off, hover-extend on truncated titles
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
Convert `.horizontal-list` from `display: table` to `display: flex`. The title item gets `flex: 1; min-width: 0; align-self: stretch` to match the implicit sizing it got from table-cell.
Class name kept so `main.js` `.closest('.horizontal-list')` calls and theme overrides still match. No `gap` or logical properties, so it works back to Firefox 28 / SeaMonkey 2.53.
Prep for #8824 which converts article rows to CSS Grid (#6776). Grid inside `display: table` mis-sizes in older Gecko, so the outer table has to go first.
## Test plan
- `make test-all` passes
- Verified in LibreWolf and SeaMonkey 2.53 with Nord and Origine themes
- Wide, narrow, and mobile viewports
- Bundled themes (Ansum, Origine-compact, Mapco, Swage) checked: no overrides depend on `display: table`
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
* Add a warning message to the API password section, add a log warning when a client uses GET, use POST in sample curl API usage
References #8834.
* Apply suggestion from @Inverle
Co-authored-by: Inverle <inverle@proton.me>
* Apply suggestion from @Inverle (Polish translation)
Co-authored-by: Inverle <inverle@proton.me>
* French translation
* Dutch translation
* German
* Apply suggestion from @Inverle api via post
Co-authored-by: Inverle <inverle@proton.me>
* Apply suggestion from @Inverle via POST (French)
Co-authored-by: Inverle <inverle@proton.me>
* Add user-agent
* update translation info in README
* avoid phpcs line too long
* also trigger on email as suggested by @Inverle
* i18n: fr typography
* make fix-all
---------
Co-authored-by: Inverle <inverle@proton.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
#8839
**Change(s)**
- Fix some typos
- Revise the translations that I hadn't checked before
- Add some new translations
**File(s) changed**
`app/i18n/zh-TW/{admin,conf,gen,sub,user}.php`
`README.md`
`README.fr.md`
Reader view sets .aside.visible { padding-top: 0 } (swage.css:1236), so when
the sidebar is open the absolute-positioned logo lands on the Subscription
management button. Extending the existing hide rule from #8739 to also
cover body.reader is the smallest fix; the logo isn't load-bearing in
reader view. Closes#8802.
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
This keeps the mobile article navigation fixed to the bottom of the viewport by clearing any inherited top offset in the small-screen rule. It updates both LTR and RTL base theme CSS so the previous/next bar remains anchored while browsing on mobile Safari.
Verification: targeted CSS assertion for both base theme files; `git diff --check`.
Closes#8829
Co-authored-by: mzl2233 <mzl2233@users.noreply.github.com>
Reverts #8801 (keeping its incidental blank-line cleanups) and #8815, restoring icon-only feed column on mobile (#8814).
I filed the original PR thinking the icon-only behavior was an inconsistency bug versus the configurable `topline_website` setting, not realizing it was a deliberate mobile choice. The `clamp()` cap helped, but the column still takes space the title could use, and on phones icon-only is the better tradeoff.
A distinct narrow-viewport setting for `topline_website` was raised as a future improvement.
* Revert "fix(themes): cap website column at narrow when feed name is shown (#8815)"
This reverts commit af6a500638.
* Revert "fix(themes): show feed name at narrow widths when configured (#8801)"
This reverts commit 9ff4aec368.
---------
Co-authored-by: FreshRSS Pi <freshrss-pi@localhost>
* docs: add missing app rows to French README
Sync mobile and desktop tables with English README:
add FeedFlow and FluentFlame Reader, previously missing from
the French translation.
* fix(docs): correct Fluent Reader API in French README
Align with English README: Fluent Reader supports the Google Reader
API (preferred), not Fever. The app upstream documents both, and the
English table has long listed GReader.
---------
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
* docs: normalize APIs & native apps table glyphs to monochrome
* docs: use en-dash instead of ✗ for "no" in APIs & native apps table
Per maintainer feedback on #8821: ✗ reads as an error/failure mark; an
en-dash is a neutral "not applicable / no" glyph more appropriate for
a feature-matrix table.
* docs: normalize APIs & native apps table glyphs in French README
Mirror the monochrome-glyph substitutions from README.md:
`✔️` -> `✓`, `➖` -> `–`, `❔` -> `?`, `💲` -> `$`.
---------
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
#8747 (re?)introduced an animation I find unbearably slow. If it was always there I suppose I just got used to it.
How to test the feature manually:
1. Open and close the sidebar on a sufficiently small width.
* feat(import): accept .txt URL lists alongside OPML/JSON/ZIP
Detects .txt by extension and wraps the URL list into a minimal OPML
document so the existing import pipeline handles dedup, categories and
feed limits unchanged. Blank lines, `#` comments and a UTF-8 BOM are
skipped; lines that don't parse as URLs are logged and dropped without
aborting the batch.
Works through both `cli/import-for-user.php` and the web import form.
* utf8BOM
* ENT_COMPAT
---------
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Add an opt-in CLI that exports each user's database to
`data/users/<user>/sqlite-backups/<YYYYMMDDTHHMMSSZ>.sqlite` (UTC) and
prunes older files to a configured count. Gated by two new settings,
`auto_sqlite_export.enabled` and `auto_sqlite_export.retention`.
Kept separate from `cli/db-backup.php` / `cli/db-restore.php`, which
stay the fixed-filename migration tool. First step of #8183.
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
Browsers don't inherit font-family on button, input, select, textarea by
default, so the rule on html, body in frss.css didn't reach the "Mark as
read" button or the search input. Add an inherit rule so all themes
extending base-theme pick up their configured font.
Reported and tested by @JerryCrazy.
FixesFreshRSS/FreshRSS#8803
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
#button-update-loading and the generic .loading class (used by
#first_load) painted loader.gif as a background. Both now use the
#load_more.loading::after border-rotation spinner from #8804. The
unused #slider-content .loader rule is also removed. Drops the
--frss-loading-image variable, all twelve loader.gif files, and
references in the theme contribution docs.
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
Replace four :has(~.date) selectors with a has-date modifier class
on .flux_header, mirroring the existing has-thumbnail and has-summary
classes. The condition (whether the date is rendered) is known
server-side and can be exposed on the parent <ul> directly. Same
elements matched in modern browsers. The rules now also apply in
browsers without :has() support, fixing the date column overlapping
the title in SeaMonkey (#6776).
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
The :has(input[type="checkbox"]) check is redundant. Every <label>
inside .dropdown-menu .item already has a checkbox child (constructed
in main.js), so the selector matches the same set as :not(.noHover)
alone. Removing it makes the rule work in browsers without :has()
support (#6776).
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
#8801 dropped the unconditional 40px cap on .item.website at narrow widths so users with topline_website set to name/full would actually see the feed name on mobile. With the default 'full' mode, the column then inherited the 200px wide-width default, squashing the article title (#8814).
Re-introduce a cap for the name-bearing modes: max-width 30vw with width auto, so the favicon and name shrink to fit the viewport without overrunning the title cell.
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
* fix(themes): replace #load_more loading-state GIF with a CSS spinner
In its `.loading` state, the "Load more" button kept its `.btn` frame and the spinner inside was hard to see or invisible across themes. Replaces the GIF with a base-theme CSS spinner inheriting `currentColor`, drops the button frame, respects `prefers-reduced-motion`, and renders crisply at any DPI. Per-theme `#load_more.loading` overrides in Nord, Flat, Mapco, Ansum and Swage become redundant and are removed; a now-redundant `font-size: 0` rule in `base.css` is also dropped.
* fix(themes): use border-rotation spinner for older-browser compat
Replaces conic-gradient + mask + aspect-ratio (Firefox 83+, Safari 15.4+) with border + rotate, supported in Gecko since Firefox 16 (2012) and Blink/WebKit since 2015. Visual difference: a ring with a rotating gap instead of a tapered arc. currentColor inheritance and prefers-reduced-motion handling are preserved.
* fix(themes): regenerate RTL spinner stylesheet
rtlcss flips border-right-color to border-left-color for RTL; the manual port of the spinner block missed it. Regenerated via npm run rtlcss.
Reworked the spinner with border + rotate instead of conic-gradient + mask + aspect-ratio.
<img width="35" height="36" alt="Screenshot 2026-05-11 at 11 05 43" src="https://github.com/user-attachments/assets/e065efc9-9d6c-4369-8390-f0e89db81952" />
Oldest browser versions still supported: Firefox 16 (2012), SeaMonkey 2.13 (2012), Chrome 43 (2015), Safari 9 (2015), Edge 12 (2015). Below those, `@keyframes` and `animation` would need `-webkit-` fallbacks.
Verified now working in SeaMonkey 2.53.23. Couldn't test feed clicks or page reload there because login itself hits a separate cookie issue.
---------
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
The multiline class on the title <a> existed only so the parent <li>
could detect it via :has(.multiline). The condition (thumbnail or
summary enabled) is already exposed on the parent <ul> by the
has-thumbnail and has-summary classes, so keying the rule on those
directly removes the indirection. Same elements matched in modern
browsers. The rule now also applies where :has() isn't supported,
relevant to #6776.
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
Rename the Nord theme display name from "Nord theme" to "Nord". The upstream Nord palette project at https://www.nordtheme.com refers to itself consistently as "Nord" throughout its branding; the "theme" suffix is redundant. The rename also matches the other entries in the theme picker (Ansum, Dark, Mapco, Pafat, Swage), which all use a single short name.
The theme directory, file paths, and CSS class names are unchanged. Only the user-facing display name in the theme picker (and the corresponding rows in the user docs) is affected.
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
Two narrow @media rules in base-theme/frss.css predate the configurable topline_website modes from #4969 and force icon-only behavior regardless of the user's Website setting; #8631 partially papered over this for grid-layout entries. Drop the unconditional span-hide and its grid-layout exception, and scope the 40px width cap to .websiteicon (with a comment explaining why it stays at narrow).
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
When topline_website is "icon", the <li> renders class="item website
icon". Bare .icon rules in Nord and Dark apply a filter that cascades
onto the favicon <img> inside, so article-list favicons render tinted
in icon-only mode. No theme CSS targets the unprefixed value; layout
modes are driven from the parent <ul> (.websiteicon, .websitename).
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>