1 parent
de4d70970c
commit
648556f762
13 files changed
+147
-59
No files matched your search
+14
-7
@@ -1,5 +1,18 @@
|
|||||||
# RustBox changelog
|
# RustBox changelog
|
||||||
|
|
||||||
|
### 0.2.25
|
||||||
|
|
||||||
|
- Filters: **Apply now** runs a rule on mail that is already in a folder (Sieve itself only sees new mail). *Check matches* in the rule editor previews how many messages in the chosen folder match; each rule in the list has its own *Apply now*. Matching uses IMAP `UID SEARCH` on the server (`POST /api/filters/match`) with an exact lower bound and a superset upper bound; messages in between are decided from the local cache, and anything that still cannot be decided exactly is reported and left untouched. Flags and labels are applied first, then copies, then a single move. Redirect, discard and *set flags* are skipped for existing messages and listed in the confirmation.
|
||||||
|
- Filters: **exceptions** (*Except if …*, written as `allof (…, not anyof (…))` and read back by the editor), new conditions **To address**, **Cc address**, **Size larger/smaller than** (`500K`, `2M`, `1G`), **Has attachment** (Sieve `mime`) and **High priority**, and new actions **Add label** (RustBox label, via its IMAP keyword) and **Move to Trash**.
|
||||||
|
- Filters: **templates** at the top of the rule editor (sender to folder, subject words, VIP flag, newsletters, alias, large attachments, forward a copy), an offer to **create a missing target folder** when a rule moves mail to one, and a message context-menu entry **Always move messages from this sender…** that adds the rule and offers to apply it to the current folder. The quick filter dialog can also apply the new rule to existing messages.
|
||||||
|
- Filter summaries (*When / Then*, AND / OR / NOT, *except if*) are translated.
|
||||||
|
- Confirmation dialogs translate their button labels.
|
||||||
|
- Fixed editing a predefined style mixing two styles. *Allow editing predefined style* no longer turns the style into a RustBox custom copy on the first change: it copies the style's layout and typography into your settings and lets you adjust them while the style keeps its palette, shapes, tokens and gradients. Only changing a colour creates a RustBox custom copy, after a confirmation; the copy takes all current colours, layout and typography from the screen (never values saved earlier for another style) and fixes the theme to the current light/dark mode, because a palette copy belongs to one mode. Switching to another style locks editing again; changing the style depth re-seeds the layout from the same style.
|
||||||
|
- Fixed *Soft* contrast removing all borders when no palette overrode them (the line colour referred to itself, which CSS treats as invalid).
|
||||||
|
- The message context menu is shorter: forwarding, labels, snooze, filters, sender actions and technical tools (headers, source, Message-ID, .eml, print) are grouped into submenus. The menu and its submenus never exceed the window height (they scroll instead), so *Delete* stays reachable on small screens.
|
||||||
|
- No database migration (still V054; V055 is next).
|
||||||
|
- Bumped the application/source release to 0.2.25.
|
||||||
|
|
||||||
### 0.2.24
|
### 0.2.24
|
||||||
|
|
||||||
- Fixed **Style gradients** doing nothing at the default *Standard* depth: gradients were only part of the immersive tokens. They now live in a dedicated gradient layer (`59-skin-gradients.css`) that applies at every style depth.
|
- Fixed **Style gradients** doing nothing at the default *Standard* depth: gradients were only part of the immersive tokens. They now live in a dedicated gradient layer (`59-skin-gradients.css`) that applies at every style depth.
|
||||||
@@ -16,13 +29,7 @@
|
|||||||
- Calendar: added an optional **second reminder** per event (for example *1 day before*; choices from *at start* to *1 week before*) and **default reminders for new events** in Settings → Notifications. All-day events are reminded at the start of the working day (default 09:00), so "1 day before" fires the previous morning. Both reminders are exported to ICS. Migration `V054`.
|
- Calendar: added an optional **second reminder** per event (for example *1 day before*; choices from *at start* to *1 week before*) and **default reminders for new events** in Settings → Notifications. All-day events are reminded at the start of the working day (default 09:00), so "1 day before" fires the previous morning. Both reminders are exported to ICS. Migration `V054`.
|
||||||
- Fixed clearing an event reminder: choosing *None* in the editor now removes it instead of keeping the old value.
|
- Fixed clearing an event reminder: choosing *None* in the editor now removes it instead of keeping the old value.
|
||||||
- The interface-style list is grouped into categories (RustBox & classic, Roundcube, mail apps, apps, editor themes, atmospheric, retro) and uses short, consistent names in English and Polish (e.g. *Gmail*, *Windows XP*, *Czysty*, *Papier*).
|
- The interface-style list is grouped into categories (RustBox & classic, Roundcube, mail apps, apps, editor themes, atmospheric, retro) and uses short, consistent names in English and Polish (e.g. *Gmail*, *Windows XP*, *Czysty*, *Papier*).
|
||||||
- Filters: **Apply now** runs a rule on mail that is already in a folder (Sieve itself only sees new mail). *Check matches* in the rule editor previews how many messages in the chosen folder match; each rule in the list has its own *Apply now*. Matching uses IMAP `UID SEARCH` on the server (`POST /api/filters/match`) with an exact lower bound and a superset upper bound; messages in between are decided from the local cache, and anything that still cannot be decided exactly is reported and left untouched. Flags and labels are applied first, then copies, then a single move. Redirect, discard and *set flags* are skipped for existing messages and listed in the confirmation.
|
- `skin_depth` and `skin_gradients` remain in migration `V053`.
|
||||||
- Filters: **exceptions** (*Except if …*, written as `allof (…, not anyof (…))` and read back by the editor), new conditions **To address**, **Cc address**, **Size larger/smaller than** (`500K`, `2M`, `1G`), **Has attachment** (Sieve `mime`) and **High priority**, and new actions **Add label** (RustBox label, via its IMAP keyword) and **Move to Trash**.
|
|
||||||
- Filters: **templates** at the top of the rule editor (sender to folder, subject words, VIP flag, newsletters, alias, large attachments, forward a copy), an offer to **create a missing target folder** when a rule moves mail to one, and a message context-menu entry **Always move messages from this sender…** that adds the rule and offers to apply it to the current folder. The quick filter dialog can also apply the new rule to existing messages.
|
|
||||||
- Filter summaries (*When / Then*, AND / OR / NOT, *except if*) are translated.
|
|
||||||
- Confirmation dialogs translate their button labels.
|
|
||||||
- The message context menu is shorter: forwarding, labels, snooze, filters, sender actions and technical tools (headers, source, Message-ID, .eml, print) are grouped into submenus. The menu and its submenus never exceed the window height (they scroll instead), so *Delete* stays reachable on small screens.
|
|
||||||
- `skin_depth` and `skin_gradients` remain in migration `V053`; the filter work needs no migration.
|
|
||||||
- Bumped the application/source release to 0.2.24.
|
- Bumped the application/source release to 0.2.24.
|
||||||
|
|
||||||
### 0.2.23
|
### 0.2.23
|
||||||
|
|||||||
Generated
+1
-1
@@ -3506,7 +3506,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustbox"
|
name = "rustbox"
|
||||||
version = "0.2.24"
|
version = "0.2.25"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"async-imap",
|
"async-imap",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustbox"
|
name = "rustbox"
|
||||||
version = "0.2.24"
|
version = "0.2.25"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.99.1"
|
rust-version = "1.99.1"
|
||||||
default-run = "rustbox"
|
default-run = "rustbox"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# RustBox attribution / notice
|
# RustBox attribution / notice
|
||||||
|
|
||||||
RustBox 0.2.24 is derived from the Oxi webmail project:
|
RustBox 0.2.25 is derived from the Oxi webmail project:
|
||||||
|
|
||||||
- Upstream: `c0h1b4/oxi`
|
- Upstream: `c0h1b4/oxi`
|
||||||
- Base snapshot: `7f1507c486b78cf33e54beb701dd460d3bd2bd5d`
|
- Base snapshot: `7f1507c486b78cf33e54beb701dd460d3bd2bd5d`
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# RustBox 0.2.24
|
# RustBox 0.2.25
|
||||||
|
|
||||||
RustBox is a webmail derived from `c0h1b4/oxi`. The consumed Oxi revision is recorded in `UPSTREAM_BASE`.
|
RustBox is a webmail derived from `c0h1b4/oxi`. The consumed Oxi revision is recorded in `UPSTREAM_BASE`.
|
||||||
|
|
||||||
|
|||||||
+10
-5
@@ -1,4 +1,11 @@
|
|||||||
RustBox 0.2.24 release manifest
|
RustBox 0.2.25 release manifest
|
||||||
|
|
||||||
|
0.2.25 release changes:
|
||||||
|
- filters: "Apply now" runs a visual Sieve rule on messages already in a folder; `POST /api/filters/match` finds matches with IMAP `UID SEARCH` (exact lower bound, superset upper bound, the gap decided from the local cache; undecidable messages are reported and skipped), and the client applies flags/labels, copies and a final move; per-rule "Apply now" in the rule list and an "apply to existing messages" option in the quick filter dialog
|
||||||
|
- filters: exceptions ("except if"), new conditions (To only, Cc only, size larger/smaller, has attachment, high priority), new actions (add RustBox label, move to Trash), rule templates, creating a missing target folder, "Always move messages from this sender…" in the message context menu, and translated rule summaries
|
||||||
|
- the message context menu groups forwarding, labels, snooze, filters, sender actions and technical tools into submenus and never exceeds the window height (scrolls instead), so Delete stays reachable
|
||||||
|
- fixes editing a predefined style mixing two styles: "Allow editing predefined style" seeds the layout/typography from the style and keeps the style; only a colour change creates a confirmed RustBox custom copy taken from the screen, with the theme pinned to the current light/dark mode; switching style locks editing again
|
||||||
|
- fixes "Soft" contrast removing all borders (self-referencing line colour)
|
||||||
|
|
||||||
0.2.24 release changes:
|
0.2.24 release changes:
|
||||||
- fixes the "Style gradients" switch having no visible effect at the default "standard" depth: gradients moved out of the immersive-only tokens into a dedicated gradient layer (`59-skin-gradients.css`) that applies at every style depth
|
- fixes the "Style gradients" switch having no visible effect at the default "standard" depth: gradients moved out of the immersive-only tokens into a dedicated gradient layer (`59-skin-gradients.css`) that applies at every style depth
|
||||||
@@ -9,8 +16,6 @@ RustBox 0.2.24 release manifest
|
|||||||
- groups the interface-style selector into categories (RustBox & classic, Roundcube, mail apps, apps, editor themes, atmospheric, retro) with short, consistent names in English and Polish
|
- groups the interface-style selector into categories (RustBox & classic, Roundcube, mail apps, apps, editor themes, atmospheric, retro) with short, consistent names in English and Polish
|
||||||
- adds a live low-contrast warning for custom colours (WCAG thresholds), skeleton rows while a folder loads and a short cross-fade when the theme or style changes
|
- adds a live low-contrast warning for custom colours (WCAG thresholds), skeleton rows while a folder loads and a short cross-fade when the theme or style changes
|
||||||
- fixes the RustBox default selection colour being dark in the light theme, subjects being squeezed by snippets in message rows, and the flagged-message marker (now a side bar instead of a bottom line); reader-toolbar labels collapse below 820px instead of overflowing
|
- fixes the RustBox default selection colour being dark in the light theme, subjects being squeezed by snippets in message rows, and the flagged-message marker (now a side bar instead of a bottom line); reader-toolbar labels collapse below 820px instead of overflowing
|
||||||
- filters: "Apply now" runs a visual Sieve rule on messages already in a folder; `POST /api/filters/match` finds matches with IMAP `UID SEARCH` (exact lower bound, superset upper bound, the gap decided from the local cache; undecidable messages are reported and skipped), and the client applies flags/labels, copies and a final move; per-rule "Apply now" in the rule list and an "apply to existing messages" option in the quick filter dialog
|
|
||||||
- filters: exceptions ("except if"), new conditions (To only, Cc only, size larger/smaller, has attachment, high priority), new actions (add RustBox label, move to Trash), rule templates, creating a missing target folder, "Always move messages from this sender…" in the message context menu, and translated rule summaries
|
|
||||||
|
|
||||||
0.2.23 release changes:
|
0.2.23 release changes:
|
||||||
- adds a "Style depth" appearance setting (`skin_depth`, migration V053): `colors` applies only a predefined style's palette, `standard` (default, previous behavior) adds its fixed layout profile, `immersive` additionally applies its structural design tokens — typography, control/button/avatar shapes, floating or flush panes, app-bar and search treatment, selection markers, row cards and backdrop
|
- adds a "Style depth" appearance setting (`skin_depth`, migration V053): `colors` applies only a predefined style's palette, `standard` (default, previous behavior) adds its fixed layout profile, `immersive` additionally applies its structural design tokens — typography, control/button/avatar shapes, floating or flush panes, app-bar and search treatment, selection markers, row cards and backdrop
|
||||||
@@ -106,14 +111,14 @@ Docker examples:
|
|||||||
|
|
||||||
Operations/docs:
|
Operations/docs:
|
||||||
- standalone `scripts/migrate_roundcube.py` imports Roundcube contacts/identities using RustBox config.toml and supports SQLite/MySQL/PostgreSQL targets
|
- standalone `scripts/migrate_roundcube.py` imports Roundcube contacts/identities using RustBox config.toml and supports SQLite/MySQL/PostgreSQL targets
|
||||||
- README.md documents 0.2.24 configuration
|
- README.md documents 0.2.25 configuration
|
||||||
- docker/README.md documents Compose variants
|
- docker/README.md documents Compose variants
|
||||||
- docs/STORAGE_SEARCH_OPERATIONS.md documents failure modes, shared-storage requirements and migration behavior
|
- docs/STORAGE_SEARCH_OPERATIONS.md documents failure modes, shared-storage requirements and migration behavior
|
||||||
- TODO.md leaves live SILO/MinIO/AWS/Ceph and real multi-node acceptance validation explicitly pending
|
- TODO.md leaves live SILO/MinIO/AWS/Ceph and real multi-node acceptance validation explicitly pending
|
||||||
|
|
||||||
Build/release metadata:
|
Build/release metadata:
|
||||||
- `scripts/package-release.sh` runs the full Rust release gate before creating ZIP/SHA256 artifacts
|
- `scripts/package-release.sh` runs the full Rust release gate before creating ZIP/SHA256 artifacts
|
||||||
- Cargo.toml version = 0.2.24
|
- Cargo.toml version = 0.2.25
|
||||||
- rust-toolchain.toml pins Rust 1.99.1
|
- rust-toolchain.toml pins Rust 1.99.1
|
||||||
- Cargo.lock is retained for reproducible application builds; the release gate generates it only when missing
|
- Cargo.lock is retained for reproducible application builds; the release gate generates it only when missing
|
||||||
- CI and scripts/build-release.sh use the retained Cargo.lock (generating it only if missing), then run locked tests/Clippy/release build
|
- CI and scripts/build-release.sh use the retained Cargo.lock (generating it only if missing), then run locked tests/Clippy/release build
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
RustBox 0.2.24 - final source validation
|
RustBox 0.2.25 - final source validation
|
||||||
|
|
||||||
|
0.2.25 scope:
|
||||||
|
- filters: apply now on existing messages (`POST /api/filters/match`, IMAP UID SEARCH bounds + cache), exceptions, new conditions and actions, templates, missing-folder creation, "always move from this sender"
|
||||||
|
- message context menu with submenus that fit the window
|
||||||
|
- fixes: editing a predefined style no longer mixes two styles; "Soft" contrast keeps borders
|
||||||
|
|
||||||
0.2.24 scope:
|
0.2.24 scope:
|
||||||
- "Style gradients" now applies at every style depth through `59-skin-gradients.css`, to every predefined style except RustBox
|
- "Style gradients" now applies at every style depth through `59-skin-gradients.css`, to every predefined style except RustBox
|
||||||
@@ -59,7 +64,7 @@ RustBox 0.2.24 - final source validation
|
|||||||
- Composing shows only compact attachment/message size-limit values on one line
|
- Composing shows only compact attachment/message size-limit values on one line
|
||||||
- 0.2.9 realtime notification gating, browser shortcut preservation and 300-message defaults remain intact
|
- 0.2.9 realtime notification gating, browser shortcut preservation and 300-message defaults remain intact
|
||||||
- V047-V052 are consolidated into compatibility baseline V052; V053 adds `skin_depth` and `skin_gradients`; V054 adds the theme schedule and account colour; V055 is next
|
- V047-V052 are consolidated into compatibility baseline V052; V053 adds `skin_depth` and `skin_gradients`; V054 adds the theme schedule and account colour; V055 is next
|
||||||
- version metadata identifies 0.2.24
|
- version metadata identifies 0.2.25
|
||||||
|
|
||||||
Completed in this environment:
|
Completed in this environment:
|
||||||
- offline release validator: scripts/validate-release-static.py (455/455 checks passed)
|
- offline release validator: scripts/validate-release-static.py (455/455 checks passed)
|
||||||
@@ -67,9 +72,9 @@ Completed in this environment:
|
|||||||
- all JSON files parse successfully
|
- all JSON files parse successfully
|
||||||
- Python helper scripts compile successfully
|
- Python helper scripts compile successfully
|
||||||
- shell scripts pass `bash -n`
|
- shell scripts pass `bash -n`
|
||||||
- release metadata consistently identifies 0.2.24
|
- release metadata consistently identifies 0.2.25
|
||||||
- V052 baseline consolidation plus the V053 skin-depth migration are validated offline
|
- V052 baseline consolidation plus the V053 skin-depth migration are validated offline
|
||||||
- the new 0.2.24 styles rendered at standard and immersive depth with gradients on and off; earlier: all 37 interface styles rendered in light and dark at the immersive depth against a mocked API (Playwright), with no page errors
|
- 0.2.25 filter UI (apply now, run now, always move from sender), context submenus and preset editing exercised in Playwright against a mocked API; the 0.2.24 styles rendered at standard and immersive depth with gradients on and off; earlier: all 37 interface styles rendered in light and dark at the immersive depth against a mocked API (Playwright), with no page errors
|
||||||
|
|
||||||
|
|
||||||
Not executable in this packaging environment:
|
Not executable in this packaging environment:
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
# RustBox 0.2.24 - implementation status
|
# RustBox 0.2.25 - implementation status
|
||||||
|
|
||||||
- Draft saves are realtime-silent: Drafts folder/list updates continue, but saving a draft does not surface as a new-mail notification.
|
- Draft saves are realtime-silent: Drafts folder/list updates continue, but saving a draft does not surface as a new-mail notification.
|
||||||
This file tracks the `0.2.24` source snapshot. Storage/search scope and outstanding acceptance work are tracked in `TODO.md`. A feature is only listed as implemented when working code exists in this source tree; partial items are called out explicitly.
|
This file tracks the `0.2.25` source snapshot. Storage/search scope and outstanding acceptance work are tracked in `TODO.md`. A feature is only listed as implemented when working code exists in this source tree; partial items are called out explicitly.
|
||||||
|
|
||||||
|
## 0.2.25 filters
|
||||||
|
|
||||||
|
- **Apply now** (`POST /api/filters/match`, `src/routes/filters.rs`): the client sends the rule as a JSON tree (`{op: allof|anyof, args}`, `{not}`, `{test: {kind, match, value, header}}`, max depth 12 / 64 tests). The server translates it into two IMAP `UID SEARCH` criteria — an upper bound (superset) and a lower bound (certainly matching) — and runs them via the new `ImapClient::search_uids`. Header/subject `:contains` tests and sizes are exact; address tests (IMAP `FROM` also matches display names), `:is` / `:matches`, non-ASCII values and has-attachment are narrowed from the `messages` cache with three-valued evaluation; messages that still cannot be decided are counted as `uncertain` and not touched. At most 20 000 UIDs are returned.
|
||||||
|
- The client (`91-sieve-apply.js`) applies, per message: flags (`\Seen`, IMAP keywords), labels (bulk tag endpoint), copies, then one move/Trash — via the existing message endpoints, 4 requests at a time with progress. Redirect, discard and set-flags are not applied to existing messages and are listed as skipped in the confirmation.
|
||||||
|
- Entry points: *Check matches / Apply now* in the rule editor (re-check required after the rule changes), *Apply now* per rule in the rule list (rules using syntax the editor cannot rewrite are refused), the quick filter dialog option, and *Always move messages from this sender…* in the message context menu.
|
||||||
|
- Editor additions: exceptions (`allof (<conditions>, not anyof (<exceptions>))`, parsed back), conditions `to-only`, `cc-only`, `size-over` / `size-under`, `has-attachment` (requires `mime`), `priority-high` (`X-Priority` 1/2 or `Importance: high`), actions `label` (`addflag "RustBox-<tag id>"`) and `trash`, templates, missing-folder creation on save. No schema change.
|
||||||
|
- Not done: drag-and-drop reordering of rules (the ↑/↓ buttons remain).
|
||||||
|
|
||||||
|
## 0.2.25 interface fixes
|
||||||
|
|
||||||
|
- Message context menu: grouped into submenus (Forward, Labels, Snooze, Filters, Sender, More); menus are capped at the window height and scroll; scrolling inside a menu does not close it; submenus open on hover, click or →, close with ← / Esc.
|
||||||
|
- Predefined-style editing: enabling it copies the style's layout profile into the user's display preferences and the layout then follows those values while the style's palette, tokens and gradients stay active. A colour change asks to create a RustBox custom copy built from the computed palette on screen plus the current layout, and pins `theme` to the resolved light/dark mode. Changing `interface_skin` clears `preset_scale_unlocked`; changing `skin_depth` while editing re-seeds the layout.
|
||||||
|
- `data-contrast="soft"` no longer defines `--line` from itself (a CSS custom-property cycle that left it empty).
|
||||||
|
|
||||||
## 0.2.24 style gradients and retro styles
|
## 0.2.24 style gradients and retro styles
|
||||||
|
|
||||||
@@ -16,14 +30,6 @@ This file tracks the `0.2.24` source snapshot. Storage/search scope and outstand
|
|||||||
- Calendar: optional second reminder per event (`reminder_extra_minutes`, V054; choices from "at start" to "1 week before"), default reminders for new events in Settings → Notifications (`calendar_settings.default_reminder_minutes` / `default_extra_reminder_minutes`), both exported as VALARMs. All-day events are reminded relative to the working-day start (default 09:00). `UpdateEvent` now distinguishes an absent reminder from `null`, so clearing a reminder works.
|
- Calendar: optional second reminder per event (`reminder_extra_minutes`, V054; choices from "at start" to "1 week before"), default reminders for new events in Settings → Notifications (`calendar_settings.default_reminder_minutes` / `default_extra_reminder_minutes`), both exported as VALARMs. All-day events are reminded relative to the working-day start (default 09:00). `UpdateEvent` now distinguishes an absent reminder from `null`, so clearing a reminder works.
|
||||||
- The interface-style selector is grouped with `<optgroup>`s (labels translated through the scoped `_text` map) and uses short names; `INTERFACE_SKINS` follows the same order.
|
- The interface-style selector is grouped with `<optgroup>`s (labels translated through the scoped `_text` map) and uses short names; `INTERFACE_SKINS` follows the same order.
|
||||||
|
|
||||||
## 0.2.24 filters
|
|
||||||
|
|
||||||
- **Apply now** (`POST /api/filters/match`, `src/routes/filters.rs`): the client sends the rule as a JSON tree (`{op: allof|anyof, args}`, `{not}`, `{test: {kind, match, value, header}}`, max depth 12 / 64 tests). The server translates it into two IMAP `UID SEARCH` criteria — an upper bound (superset) and a lower bound (certainly matching) — and runs them via the new `ImapClient::search_uids`. Header/subject `:contains` tests and sizes are exact; address tests (IMAP `FROM` also matches display names), `:is` / `:matches`, non-ASCII values and has-attachment are narrowed from the `messages` cache with three-valued evaluation; messages that still cannot be decided are counted as `uncertain` and not touched. At most 20 000 UIDs are returned.
|
|
||||||
- The client (`91-sieve-apply.js`) applies, per message: flags (`\Seen`, IMAP keywords), labels (bulk tag endpoint), copies, then one move/Trash — via the existing message endpoints, 4 requests at a time with progress. Redirect, discard and set-flags are not applied to existing messages and are listed as skipped in the confirmation.
|
|
||||||
- Entry points: *Check matches / Apply now* in the rule editor (re-check required after the rule changes), *Apply now* per rule in the rule list (rules using syntax the editor cannot rewrite are refused), the quick filter dialog option, and *Always move messages from this sender…* in the message context menu.
|
|
||||||
- Editor additions: exceptions (`allof (<conditions>, not anyof (<exceptions>))`, parsed back), conditions `to-only`, `cc-only`, `size-over` / `size-under`, `has-attachment` (requires `mime`), `priority-high` (`X-Priority` 1/2 or `Importance: high`), actions `label` (`addflag "RustBox-<tag id>"`) and `trash`, templates, missing-folder creation on save. No schema change.
|
|
||||||
- Not done: drag-and-drop reordering of rules (the ↑/↓ buttons remain).
|
|
||||||
|
|
||||||
## 0.2.23 interface styles
|
## 0.2.23 interface styles
|
||||||
|
|
||||||
- Adds Appearance → **Style depth** (`display_preferences.skin_depth`, migration V053): `colors` applies only a predefined style's palette and leaves layout controls editable; `standard` (default) keeps the previous palette + fixed layout behavior; `immersive` additionally applies the style's structural tokens.
|
- Adds Appearance → **Style depth** (`display_preferences.skin_depth`, migration V053): `colors` applies only a predefined style's palette and leaves layout controls editable; `standard` (default) keeps the previous palette + fixed layout behavior; `immersive` additionally applies the style's structural tokens.
|
||||||
|
|||||||
@@ -127,9 +127,11 @@ html[data-font-family="mono"] {
|
|||||||
--font-sans: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
|
--font-sans: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Not derived from var(--line): a custom property referring to itself is a
|
||||||
|
cycle, which made --line empty (no borders) wherever no palette overrode it. */
|
||||||
html[data-contrast="soft"] {
|
html[data-contrast="soft"] {
|
||||||
--line: color-mix(in srgb, var(--line) 70%, transparent);
|
--line: color-mix(in srgb, var(--text) 11%, var(--panel));
|
||||||
--line-strong: color-mix(in srgb, var(--line-strong) 78%, transparent);
|
--line-strong: color-mix(in srgb, var(--text) 20%, var(--panel));
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-contrast="high"] {
|
html[data-contrast="high"] {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
"unavailable": "Unavailable",
|
"unavailable": "Unavailable",
|
||||||
"show_full_addresses": "Show full addresses",
|
"show_full_addresses": "Show full addresses",
|
||||||
"collapse_addresses": "Collapse addresses",
|
"collapse_addresses": "Collapse addresses",
|
||||||
"fixed_interface_style_note": "predefined interface style. Enable preset editing below to create a customizable copy on the first change.",
|
"fixed_interface_style_note": "predefined interface style. Allow editing below to adjust its layout and typography.",
|
||||||
"custom_interface_style_note": "RustBox is fully customizable: colors, spacing, panes, typography and advanced interface controls are available below.",
|
"custom_interface_style_note": "RustBox is fully customizable: colors, spacing, panes, typography and advanced interface controls are available below.",
|
||||||
"reply": "Reply",
|
"reply": "Reply",
|
||||||
"reply_all": "Reply all",
|
"reply_all": "Reply all",
|
||||||
@@ -74,5 +74,6 @@
|
|||||||
"theme_dark_from": "Dark from",
|
"theme_dark_from": "Dark from",
|
||||||
"account_color": "Account color",
|
"account_color": "Account color",
|
||||||
"account_color_note": "A stripe on the app bar and a dot next to the address show which account is active.",
|
"account_color_note": "A stripe on the app bar and a dot next to the address show which account is active.",
|
||||||
"contrast_warning": "Low contrast"
|
"contrast_warning": "Low contrast",
|
||||||
|
"edited_interface_style_note": "predefined interface style with your own layout. Changing a colour creates a RustBox custom copy."
|
||||||
}
|
}
|
||||||
+12
-4
@@ -618,7 +618,6 @@
|
|||||||
"Uploading files…": "Wysyłanie plików…",
|
"Uploading files…": "Wysyłanie plików…",
|
||||||
"Upload complete": "Wysyłanie zakończone",
|
"Upload complete": "Wysyłanie zakończone",
|
||||||
"Allow editing predefined style": "Zezwól na edycję gotowego stylu",
|
"Allow editing predefined style": "Zezwól na edycję gotowego stylu",
|
||||||
"When enabled, the first appearance change creates and saves a RustBox custom copy of the selected preset.": "Po włączeniu pierwsza zmiana wyglądu utworzy i zapisze edytowalną kopię wybranego stylu jako RustBox custom.",
|
|
||||||
"Maximum attachment size": "Maksymalny rozmiar załącznika",
|
"Maximum attachment size": "Maksymalny rozmiar załącznika",
|
||||||
"Server-wide limit set with": "Globalny limit serwera ustawiany przez",
|
"Server-wide limit set with": "Globalny limit serwera ustawiany przez",
|
||||||
"in rustbox.toml.": "w rustbox.toml.",
|
"in rustbox.toml.": "w rustbox.toml.",
|
||||||
@@ -781,9 +780,17 @@
|
|||||||
"More": "Więcej",
|
"More": "Więcej",
|
||||||
"Snooze…": "Odłóż…",
|
"Snooze…": "Odłóż…",
|
||||||
"View snoozed messages…": "Pokaż odłożone wiadomości…",
|
"View snoozed messages…": "Pokaż odłożone wiadomości…",
|
||||||
"Open full screen": "Otwórz na pełnym ekranie"
|
"Open full screen": "Otwórz na pełnym ekranie",
|
||||||
|
"Layout and typography can be adjusted while the style keeps its colours, shapes and gradients. Changing a colour creates a RustBox custom copy.": "Układ i typografię można zmieniać, a styl zachowuje swoje kolory, kształty i gradienty. Zmiana koloru tworzy własną kopię RustBox.",
|
||||||
|
"Colours of a predefined style cannot be changed in place.": "Kolorów gotowego stylu nie da się zmieniać bezpośrednio.",
|
||||||
|
"RustBox will switch to its custom style with a copy of the current colours, layout and typography of": "RustBox przełączy się na styl własny z kopią obecnych kolorów, układu i typografii stylu",
|
||||||
|
"The theme will be fixed to the current mode": "Motyw zostanie ustawiony na obecny tryb",
|
||||||
|
"because the copied colours belong to it.": "bo skopiowane kolory należą do niego.",
|
||||||
|
"Shapes, fonts, app bar and gradients specific to the style are not part of the copy.": "Kształty, czcionki, pasek aplikacji i gradienty charakterystyczne dla stylu nie wchodzą do kopii.",
|
||||||
|
"Create a custom copy?": "Utworzyć własną kopię?",
|
||||||
|
"Create custom copy": "Utwórz własną kopię"
|
||||||
},
|
},
|
||||||
"fixed_interface_style_note": "gotowy styl interfejsu. Włącz edycję presetu poniżej, aby przy pierwszej zmianie utworzyć jego edytowalną kopię.",
|
"fixed_interface_style_note": "gotowy styl interfejsu. Włącz edycję poniżej, aby dopasować jego układ i typografię.",
|
||||||
"custom_interface_style_note": "RustBox można w pełni dostosować: kolory, odstępy, panele, typografię i zaawansowane ustawienia interfejsu.",
|
"custom_interface_style_note": "RustBox można w pełni dostosować: kolory, odstępy, panele, typografię i zaawansowane ustawienia interfejsu.",
|
||||||
"reply": "Odpowiedz",
|
"reply": "Odpowiedz",
|
||||||
"reply_all": "Odpowiedz wszystkim",
|
"reply_all": "Odpowiedz wszystkim",
|
||||||
@@ -810,5 +817,6 @@
|
|||||||
"theme_dark_from": "Ciemny od",
|
"theme_dark_from": "Ciemny od",
|
||||||
"account_color": "Kolor konta",
|
"account_color": "Kolor konta",
|
||||||
"account_color_note": "Pasek u góry aplikacji i kropka przy adresie pokazują, które konto jest aktywne.",
|
"account_color_note": "Pasek u góry aplikacji i kropka przy adresie pokazują, które konto jest aktywne.",
|
||||||
"contrast_warning": "Niski kontrast"
|
"contrast_warning": "Niski kontrast",
|
||||||
|
"edited_interface_style_note": "gotowy styl interfejsu z Twoim układem. Zmiana koloru tworzy własną kopię RustBox."
|
||||||
}
|
}
|
||||||
+2
-2
@@ -718,8 +718,8 @@
|
|||||||
style.</small></label>
|
style.</small></label>
|
||||||
<label id="pref-preset-scale-unlocked-row" class="checkbox-row hidden"><input
|
<label id="pref-preset-scale-unlocked-row" class="checkbox-row hidden"><input
|
||||||
id="pref-preset-scale-unlocked" type="checkbox"><span>Allow editing predefined style</span><small
|
id="pref-preset-scale-unlocked" type="checkbox"><span>Allow editing predefined style</span><small
|
||||||
class="muted">When enabled, the first appearance change creates and saves a RustBox custom copy of the
|
class="muted">Layout and typography can be adjusted while the style keeps its colours, shapes and
|
||||||
selected preset.</small></label>
|
gradients. Changing a colour creates a RustBox custom copy.</small></label>
|
||||||
<label><span data-i18n="color_preset">Color preset</span><select id="pref-color-preset">
|
<label><span data-i18n="color_preset">Color preset</span><select id="pref-color-preset">
|
||||||
<option value="sage">Sage</option>
|
<option value="sage">Sage</option>
|
||||||
<option value="blue">Blue</option>
|
<option value="blue">Blue</option>
|
||||||
|
|||||||
@@ -310,7 +310,10 @@ function applyPreferences() {
|
|||||||
const fixedStyle = layoutInterfaceStyle(p);
|
const fixedStyle = layoutInterfaceStyle(p);
|
||||||
const skinDepth = resolvedSkinDepth(p);
|
const skinDepth = resolvedSkinDepth(p);
|
||||||
const previousLook = `${root.dataset.theme}|${root.dataset.interfaceSkin}|${root.dataset.skinDepth}|${root.dataset.skinGradients}`;
|
const previousLook = `${root.dataset.theme}|${root.dataset.interfaceSkin}|${root.dataset.skinDepth}|${root.dataset.skinGradients}`;
|
||||||
const visual = fixedStyle
|
// A predefined style fixes the layout unless editing is allowed; then the
|
||||||
|
// user's own values (seeded from the style when editing was enabled) win,
|
||||||
|
// while the style's palette, tokens and gradients stay in place.
|
||||||
|
const visual = fixedStyle && !p.presetScaleUnlocked
|
||||||
? { ...p, ...fixedStyle.preferences }
|
? { ...p, ...fixedStyle.preferences }
|
||||||
: p;
|
: p;
|
||||||
root.dataset.theme = resolveTheme(p.theme);
|
root.dataset.theme = resolveTheme(p.theme);
|
||||||
@@ -450,7 +453,9 @@ function applyPreferences() {
|
|||||||
if (skinNote) {
|
if (skinNote) {
|
||||||
const styleLabel = state.prefs.language === "pl" ? (state.translations._text?.[paletteStyle?.label] || paletteStyle?.label) : paletteStyle?.label;
|
const styleLabel = state.prefs.language === "pl" ? (state.translations._text?.[paletteStyle?.label] || paletteStyle?.label) : paletteStyle?.label;
|
||||||
skinNote.textContent = paletteStyle
|
skinNote.textContent = paletteStyle
|
||||||
? `${styleLabel} — ${tr("fixed_interface_style_note", "predefined interface style. Enable preset editing below to create a customizable copy on the first change.")}`
|
? `${styleLabel} — ${p.presetScaleUnlocked
|
||||||
|
? tr("edited_interface_style_note", "predefined interface style with your own layout. Changing a colour creates a RustBox custom copy.")
|
||||||
|
: tr("fixed_interface_style_note", "predefined interface style. Allow editing below to adjust its layout and typography.")}`
|
||||||
: tr("custom_interface_style_note", "RustBox is fully customizable: colors, spacing, panes, typography and advanced interface controls are available below.");
|
: tr("custom_interface_style_note", "RustBox is fully customizable: colors, spacing, panes, typography and advanced interface controls are available below.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -684,7 +689,7 @@ function presetCssHex(styles, name, fallback) {
|
|||||||
|
|
||||||
function customAppearancePayload(prefs) {
|
function customAppearancePayload(prefs) {
|
||||||
const keys = [
|
const keys = [
|
||||||
"interfaceSkin", "skinDepth", "skinGradients", "presetScaleUnlocked", "colorPreset", "accentColor", "backgroundColor", "panelColor", "textColor",
|
"theme", "interfaceSkin", "skinDepth", "skinGradients", "presetScaleUnlocked", "colorPreset", "accentColor", "backgroundColor", "panelColor", "textColor",
|
||||||
"density", "preview", "scale", "fontFamily", "fontSize", "rowHeight", "folderPaneWidth", "messagePaneWidth",
|
"density", "preview", "scale", "fontFamily", "fontSize", "rowHeight", "folderPaneWidth", "messagePaneWidth",
|
||||||
"borderRadius", "contrast", "iconSize", "toolbarDensity", "sidebarItemHeight", "readerMaxWidth", "composerWidth",
|
"borderRadius", "contrast", "iconSize", "toolbarDensity", "sidebarItemHeight", "readerMaxWidth", "composerWidth",
|
||||||
"selectionColor", "unreadFontWeight", "unreadBadgeStyle", "scrollbarDensity", "compactHeaders", "showBorders", "monochrome",
|
"selectionColor", "unreadFontWeight", "unreadBadgeStyle", "scrollbarDensity", "compactHeaders", "showBorders", "monochrome",
|
||||||
@@ -717,20 +722,54 @@ function schedulePromotedAppearanceSave() {
|
|||||||
}, 180);
|
}, 180);
|
||||||
}
|
}
|
||||||
|
|
||||||
function promotePresetToCustom(key, value) {
|
const presetLayoutPreferenceKeys = [...presetEditablePreferenceKeys].filter((key) => !presetColorPreferenceKeys.has(key));
|
||||||
const paletteStyle = activeInterfaceStyle(state.prefs);
|
|
||||||
if (!paletteStyle || !presetEditablePreferenceKeys.has(key)) return false;
|
|
||||||
// At the "colors" depth the layout already follows the user's own settings,
|
|
||||||
// so only a palette change needs a custom copy of the style.
|
|
||||||
const layoutStyle = layoutInterfaceStyle(state.prefs);
|
|
||||||
if (!layoutStyle && !presetColorPreferenceKeys.has(key)) return false;
|
|
||||||
if (!state.prefs.presetScaleUnlocked) return false;
|
|
||||||
const fixedStyle = paletteStyle;
|
|
||||||
|
|
||||||
|
// "Allow editing predefined style": seed the user's own layout values from the
|
||||||
|
// style, so editing starts from exactly what is on screen and never mixes in
|
||||||
|
// values saved earlier for another style.
|
||||||
|
async function setPresetEditing(enabled) {
|
||||||
|
const layoutStyle = layoutInterfaceStyle(state.prefs);
|
||||||
|
const payload = { preset_scale_unlocked: enabled };
|
||||||
|
if (enabled && layoutStyle) {
|
||||||
|
for (const key of presetLayoutPreferenceKeys) {
|
||||||
|
if (!(key in layoutStyle.preferences)) continue;
|
||||||
|
state.prefs[key] = layoutStyle.preferences[key];
|
||||||
|
if (preferenceMap[key]) payload[preferenceMap[key]] = layoutStyle.preferences[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.prefs.presetScaleUnlocked = enabled;
|
||||||
|
applyPreferences();
|
||||||
|
try {
|
||||||
|
await request("/settings/display", { method: "PUT", body: JSON.stringify(payload) });
|
||||||
|
} catch (error) {
|
||||||
|
if (!suppressPaneWidthValidationError(error)) toast(error.message, "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colours of a predefined style come from its light and dark palettes and
|
||||||
|
// cannot be overridden one by one without mixing two palettes. Changing one
|
||||||
|
// therefore turns the style into a RustBox custom copy of the palette that is
|
||||||
|
// on screen now, pinned to the current light/dark mode so it stays coherent.
|
||||||
|
async function promotePresetToCustom(key, value) {
|
||||||
|
const paletteStyle = activeInterfaceStyle(state.prefs);
|
||||||
|
if (!paletteStyle || !presetColorPreferenceKeys.has(key) || !state.prefs.presetScaleUnlocked) return false;
|
||||||
|
const currentTheme = resolveTheme(state.prefs.theme);
|
||||||
|
const pinTheme = state.prefs.theme !== currentTheme;
|
||||||
|
const label = trText(paletteStyle.label);
|
||||||
|
const message = `${trText("Colours of a predefined style cannot be changed in place.")}\n\n${trText("RustBox will switch to its custom style with a copy of the current colours, layout and typography of")} “${label}”.${pinTheme ? `\n${trText("The theme will be fixed to the current mode")} (${trText(currentTheme === "light" ? "Light" : "Dark")}), ${trText("because the copied colours belong to it.")}` : ""}\n\n${trText("Shapes, fonts, app bar and gradients specific to the style are not part of the copy.")}`;
|
||||||
|
const confirmed = await appConfirm(message, { title: trText("Create a custom copy?"), confirmLabel: trText("Create custom copy") });
|
||||||
|
if (!confirmed) {
|
||||||
|
applyPreferences();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const layoutStyle = layoutInterfaceStyle(state.prefs);
|
||||||
const styles = getComputedStyle(root);
|
const styles = getComputedStyle(root);
|
||||||
state.prefs = {
|
state.prefs = {
|
||||||
...state.prefs,
|
...state.prefs,
|
||||||
...(layoutStyle ? layoutStyle.preferences : {}),
|
// With editing allowed the user's layout already holds the style's values
|
||||||
|
// (seeded by setPresetEditing); without a fixed layout it is the user's own.
|
||||||
|
...(layoutStyle && !state.prefs.presetScaleUnlocked ? layoutStyle.preferences : {}),
|
||||||
|
theme: currentTheme,
|
||||||
interfaceSkin: "rustbox",
|
interfaceSkin: "rustbox",
|
||||||
presetScaleUnlocked: false,
|
presetScaleUnlocked: false,
|
||||||
colorPreset: "custom",
|
colorPreset: "custom",
|
||||||
@@ -738,12 +777,13 @@ function promotePresetToCustom(key, value) {
|
|||||||
backgroundColor: presetCssHex(styles, "--bg", state.prefs.backgroundColor),
|
backgroundColor: presetCssHex(styles, "--bg", state.prefs.backgroundColor),
|
||||||
panelColor: presetCssHex(styles, "--panel", state.prefs.panelColor),
|
panelColor: presetCssHex(styles, "--panel", state.prefs.panelColor),
|
||||||
textColor: presetCssHex(styles, "--text", state.prefs.textColor),
|
textColor: presetCssHex(styles, "--text", state.prefs.textColor),
|
||||||
selectionColor: presetCssHex(styles, "--selected", fixedStyle.preferences.selectionColor || state.prefs.selectionColor),
|
selectionColor: presetCssHex(styles, "--selected", state.prefs.selectionColor),
|
||||||
folderBackground: presetCssHex(styles, "--folder-bg", state.prefs.folderBackground || "#101310"),
|
folderBackground: presetCssHex(styles, "--folder-bg", ""),
|
||||||
messageBackground: presetCssHex(styles, "--message-bg", state.prefs.messageBackground || "#181c19"),
|
messageBackground: presetCssHex(styles, "--message-bg", ""),
|
||||||
readerBackground: presetCssHex(styles, "--reader-bg", state.prefs.readerBackground || "#181c19"),
|
readerBackground: presetCssHex(styles, "--reader-bg", ""),
|
||||||
[key]: value,
|
|
||||||
};
|
};
|
||||||
|
state.prefs[key] = value;
|
||||||
|
if (["accentColor", "backgroundColor", "panelColor", "textColor"].includes(key)) state.prefs.colorPreset = "custom";
|
||||||
presetPromotionActive = true;
|
presetPromotionActive = true;
|
||||||
applyPreferences();
|
applyPreferences();
|
||||||
schedulePromotedAppearanceSave();
|
schedulePromotedAppearanceSave();
|
||||||
@@ -751,7 +791,21 @@ function promotePresetToCustom(key, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function savePreference(key, value) {
|
function savePreference(key, value) {
|
||||||
if (promotePresetToCustom(key, value)) return;
|
if (key === "presetScaleUnlocked") { setPresetEditing(Boolean(value)); return; }
|
||||||
|
// Edited layout values belong to the style they were seeded from: a new
|
||||||
|
// style starts locked again, and a new depth re-seeds from the same style.
|
||||||
|
if (key === "interfaceSkin" && state.prefs.presetScaleUnlocked && value !== state.prefs.interfaceSkin) {
|
||||||
|
state.prefs.presetScaleUnlocked = false;
|
||||||
|
savePreference("presetScaleUnlocked", false);
|
||||||
|
}
|
||||||
|
if (key === "skinDepth" && state.prefs.presetScaleUnlocked && value !== state.prefs.skinDepth) {
|
||||||
|
state.prefs.skinDepth = value;
|
||||||
|
setPresetEditing(true);
|
||||||
|
}
|
||||||
|
if (activeInterfaceStyle(state.prefs) && state.prefs.presetScaleUnlocked && presetColorPreferenceKeys.has(key)) {
|
||||||
|
promotePresetToCustom(key, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
state.prefs[key] = value;
|
state.prefs[key] = value;
|
||||||
if (presetPromotionActive && presetEditablePreferenceKeys.has(key)) {
|
if (presetPromotionActive && presetEditablePreferenceKeys.has(key)) {
|
||||||
if (["accentColor", "backgroundColor", "panelColor", "textColor"].includes(key)) state.prefs.colorPreset = "custom";
|
if (["accentColor", "backgroundColor", "panelColor", "textColor"].includes(key)) state.prefs.colorPreset = "custom";
|
||||||
|
|||||||
Reference in new issue
Block a user