@remit/web-client 0.0.256 → 0.0.258
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/locales/de/mail.json +0 -1
- package/public/locales/en/common.json +1 -22
- package/public/locales/en/mail.json +1 -58
- package/public/locales/fr/mail.json +0 -1
- package/public/locales/nl/mail.json +0 -1
- package/src/components/mail/DailyBrief.tsx +27 -19
- package/src/components/mail/MailViewChrome.tsx +2 -2
- package/src/components/mail/MoveToTrigger.tsx +2 -8
- package/src/lib/brief-criteria.ts +14 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/web-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.258",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Remit web client, published as composable primitives — the app shell, auth shells, and runtime config. A distributor imports what it composes and bundles it.",
|
|
6
6
|
"exports": {
|
|
@@ -1,26 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"app": {
|
|
3
|
-
"title": "Remit",
|
|
4
|
-
"loading": "Loading...",
|
|
5
|
-
"error": "Something went wrong"
|
|
6
|
-
},
|
|
7
|
-
"actions": {
|
|
8
|
-
"save": "Save",
|
|
9
|
-
"cancel": "Cancel",
|
|
10
|
-
"delete": "Delete",
|
|
11
|
-
"close": "Close",
|
|
12
|
-
"confirm": "Confirm"
|
|
13
|
-
},
|
|
14
|
-
"pagination": {
|
|
15
|
-
"previous": "Previous",
|
|
16
|
-
"next": "Next",
|
|
17
|
-
"page": "Page {{current}} of {{total}}",
|
|
18
|
-
"showing": "Showing {{from}}-{{to}} of {{total}}"
|
|
19
|
-
},
|
|
20
2
|
"accessibility": {
|
|
21
|
-
"skipToContent": "Skip to main content"
|
|
22
|
-
"loading": "Loading content",
|
|
23
|
-
"menuOpen": "Open menu",
|
|
24
|
-
"menuClose": "Close menu"
|
|
3
|
+
"skipToContent": "Skip to main content"
|
|
25
4
|
}
|
|
26
5
|
}
|
|
@@ -5,65 +5,8 @@
|
|
|
5
5
|
"drafts": "Drafts",
|
|
6
6
|
"trash": "Trash",
|
|
7
7
|
"archive": "Archive",
|
|
8
|
-
"spam": "Spam",
|
|
9
8
|
"junk": "Spam",
|
|
10
9
|
"flagged": "Starred",
|
|
11
|
-
"all": "All Mail"
|
|
12
|
-
"noAccounts": "No accounts configured",
|
|
13
|
-
"noMailboxes": "No mailboxes"
|
|
14
|
-
},
|
|
15
|
-
"emptyState": {
|
|
16
|
-
"selectMailbox": "Select a mailbox to view messages",
|
|
17
|
-
"selectMessage": "Select a message to read",
|
|
18
|
-
"noMessages": "No messages in this mailbox",
|
|
19
|
-
"messageNotFound": "Message not found"
|
|
20
|
-
},
|
|
21
|
-
"inbox": {
|
|
22
|
-
"title": "Inbox",
|
|
23
|
-
"empty": "No messages in your inbox",
|
|
24
|
-
"emptyFiltered": "No {{filter}} messages",
|
|
25
|
-
"noSubject": "(No subject)"
|
|
26
|
-
},
|
|
27
|
-
"thread": {
|
|
28
|
-
"messageCount": "{{count}} message",
|
|
29
|
-
"messageCount_other": "{{count}} messages",
|
|
30
|
-
"unreadCount": "{{count}} unread",
|
|
31
|
-
"participants": "{{count}} participant",
|
|
32
|
-
"participants_other": "{{count}} participants"
|
|
33
|
-
},
|
|
34
|
-
"filters": {
|
|
35
|
-
"all": "All",
|
|
36
|
-
"unread": "Unread",
|
|
37
|
-
"starred": "Starred",
|
|
38
|
-
"attachments": "Has attachments"
|
|
39
|
-
},
|
|
40
|
-
"actions": {
|
|
41
|
-
"compose": "Compose",
|
|
42
|
-
"reply": "Reply",
|
|
43
|
-
"replyAll": "Reply all",
|
|
44
|
-
"forward": "Forward",
|
|
45
|
-
"archive": "Archive",
|
|
46
|
-
"markRead": "Mark as read",
|
|
47
|
-
"markUnread": "Mark as unread",
|
|
48
|
-
"star": "Star",
|
|
49
|
-
"unstar": "Remove star",
|
|
50
|
-
"moveToTrash": "Move to trash"
|
|
51
|
-
},
|
|
52
|
-
"compose": {
|
|
53
|
-
"title": "New message",
|
|
54
|
-
"description": "Compose a new email message",
|
|
55
|
-
"to": "To",
|
|
56
|
-
"cc": "Cc",
|
|
57
|
-
"bcc": "Bcc",
|
|
58
|
-
"subject": "Subject",
|
|
59
|
-
"body": "Message",
|
|
60
|
-
"send": "Send",
|
|
61
|
-
"discard": "Discard",
|
|
62
|
-
"attachFiles": "Attach files"
|
|
63
|
-
},
|
|
64
|
-
"time": {
|
|
65
|
-
"justNow": "Just now",
|
|
66
|
-
"today": "Today",
|
|
67
|
-
"yesterday": "Yesterday"
|
|
10
|
+
"all": "All Mail"
|
|
68
11
|
}
|
|
69
12
|
}
|
|
@@ -24,11 +24,14 @@
|
|
|
24
24
|
* chips and the account source group, in a panel the list header's caret opens
|
|
25
25
|
* over the rows. `FilterPanelProvider` shares that panel's open state between
|
|
26
26
|
* the caret and the sheet, the same shape `MailViewChrome` gives the mailbox and
|
|
27
|
-
* Starred views. The
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* Starred views. The list draws those controls but applies none of them: the
|
|
28
|
+
* category and every chip a parameter can express travel with the section
|
|
29
|
+
* requests, and the two that cannot ("From contacts", "Today") are applied here
|
|
30
|
+
* alongside the residual tokens (#314). The phone search takeover reads the same
|
|
31
|
+
* selection and the same rows, so a filter set on one surface holds on the
|
|
32
|
+
* other. Under a query the chips are terms of that query — see
|
|
33
|
+
* `briefChipFilters` — so what narrows the list is readable and editable in the
|
|
34
|
+
* search field.
|
|
32
35
|
*
|
|
33
36
|
* Multi-select is the mailbox list's, not a copy of it: the same
|
|
34
37
|
* `ThreadListInteraction` cursor and `useSelection` state, and the same
|
|
@@ -114,7 +117,11 @@ import {
|
|
|
114
117
|
matchesSearchTokens,
|
|
115
118
|
toThreadRowData,
|
|
116
119
|
} from "@/lib/brief";
|
|
117
|
-
import {
|
|
120
|
+
import {
|
|
121
|
+
briefClientOnlyFilters,
|
|
122
|
+
briefCountsMatchRows,
|
|
123
|
+
briefCriteria,
|
|
124
|
+
} from "@/lib/brief-criteria";
|
|
118
125
|
import { isServerError } from "@/lib/error-classifier";
|
|
119
126
|
import { junkDestination } from "@/lib/junk-destination";
|
|
120
127
|
import type { ListHeaderChrome } from "@/lib/list-header-chrome";
|
|
@@ -659,11 +666,16 @@ export function DailyBrief({
|
|
|
659
666
|
refetchSections();
|
|
660
667
|
}, [underQuery, refetchSearch, refetchSections]);
|
|
661
668
|
|
|
662
|
-
// What the request could not carry: muted senders, the account pill,
|
|
669
|
+
// What the request could not carry: muted senders, the account pill, the two
|
|
670
|
+
// chips no endpoint takes a parameter for ("From contacts", "Today"), and the
|
|
663
671
|
// residual tokens (`from:`, `subject:`, `before:`, `after:`, `in:`,
|
|
664
672
|
// `account:`) that `listAllThreads` has no parameter for. Everything else was
|
|
665
673
|
// answered over the whole scope by the request itself, and the order the rows
|
|
666
674
|
// arrived in is kept — narrowing never re-sorts.
|
|
675
|
+
const clientOnlyFilters = useMemo(
|
|
676
|
+
() => briefClientOnlyFilters(requestFilters),
|
|
677
|
+
[requestFilters],
|
|
678
|
+
);
|
|
667
679
|
const narrowRows = useCallback(
|
|
668
680
|
(rows: RemitImapThreadMessageResponse[]): ThreadRowData[] =>
|
|
669
681
|
// One row per conversation, because that is what the server counted:
|
|
@@ -676,9 +688,10 @@ export function DailyBrief({
|
|
|
676
688
|
(t) =>
|
|
677
689
|
(selectedAccountId === "all" ||
|
|
678
690
|
t.accountId === selectedAccountId) &&
|
|
691
|
+
matchesBriefFilters(t, clientOnlyFilters) &&
|
|
679
692
|
matchesSearchTokens(t, residualTokens),
|
|
680
693
|
),
|
|
681
|
-
[selectedAccountId, residualTokens],
|
|
694
|
+
[selectedAccountId, clientOnlyFilters, residualTokens],
|
|
682
695
|
);
|
|
683
696
|
|
|
684
697
|
const briefRows = useMemo<BriefCategoryResult[]>(
|
|
@@ -826,18 +839,13 @@ export function DailyBrief({
|
|
|
826
839
|
[refreshState, onRefreshBrief, refreshError, hasNewMail, refreshAccountIds],
|
|
827
840
|
);
|
|
828
841
|
|
|
829
|
-
// The phone search takeover renders the
|
|
830
|
-
//
|
|
842
|
+
// The phone search takeover renders the rows the list renders: the same
|
|
843
|
+
// requests answered the category and the chips, so a second pass here would
|
|
844
|
+
// narrow one surface by a criterion the other already applied to the whole
|
|
845
|
+
// scope.
|
|
831
846
|
const searchResults = useMemo<SearchResult[]>(
|
|
832
|
-
() =>
|
|
833
|
-
|
|
834
|
-
.filter(
|
|
835
|
-
(t) =>
|
|
836
|
-
(chipCategory === "all" || t.category === chipCategory) &&
|
|
837
|
-
matchesBriefFilters(t, chipFilters),
|
|
838
|
-
)
|
|
839
|
-
.map((row) => rowToSearchResult(row, resultFolderIndex)),
|
|
840
|
-
[filteredRows, chipCategory, chipFilters, resultFolderIndex],
|
|
847
|
+
() => filteredRows.map((row) => rowToSearchResult(row, resultFolderIndex)),
|
|
848
|
+
[filteredRows, resultFolderIndex],
|
|
841
849
|
);
|
|
842
850
|
|
|
843
851
|
// "Related" (semantic) spans every account here — the brief is the
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* restores the sheet exactly as it was.
|
|
15
15
|
*
|
|
16
16
|
* The daily brief composes the same three parts itself — provider, header,
|
|
17
|
-
* sheet — around the kit `BriefSections`, whose sheet
|
|
18
|
-
* sections
|
|
17
|
+
* sheet — around the kit `BriefSections`, whose sheet reports the selection and
|
|
18
|
+
* flattens the sections when the brief is scoped to one category.
|
|
19
19
|
*/
|
|
20
20
|
import {
|
|
21
21
|
FilterPanelProvider,
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
useRef,
|
|
18
18
|
useState,
|
|
19
19
|
} from "react";
|
|
20
|
-
import { useTranslation } from "react-i18next";
|
|
21
20
|
import { Drawer } from "vaul";
|
|
22
21
|
import { ErrorState } from "@/components/ui/ErrorState";
|
|
23
22
|
import { useFolderAppointments } from "@/hooks/useArchiveMailbox";
|
|
@@ -86,7 +85,6 @@ export const MoveToTrigger = ({
|
|
|
86
85
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
87
86
|
const triggerLabel = label ?? "Move to folder";
|
|
88
87
|
const popoverId = useId();
|
|
89
|
-
const { t } = useTranslation("mail", { useSuspense: false });
|
|
90
88
|
const translator = useFolderLabelTranslator();
|
|
91
89
|
|
|
92
90
|
const {
|
|
@@ -223,12 +221,8 @@ export const MoveToTrigger = ({
|
|
|
223
221
|
// drawer runs its own focus and must not raise a phone keyboard.
|
|
224
222
|
autoFocusFilter={isDesktop}
|
|
225
223
|
labels={{
|
|
226
|
-
filterPlaceholder:
|
|
227
|
-
|
|
228
|
-
}),
|
|
229
|
-
filterAriaLabel: t("move_picker_filter_label", {
|
|
230
|
-
defaultValue: "Filter folders",
|
|
231
|
-
}),
|
|
224
|
+
filterPlaceholder: "Filter folders…",
|
|
225
|
+
filterAriaLabel: "Filter folders",
|
|
232
226
|
}}
|
|
233
227
|
/>
|
|
234
228
|
);
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* the request is wider than the list and the section totals are not the list's
|
|
19
19
|
* size. `briefCountsMatchRows` is how a caller knows to show no number instead.
|
|
20
20
|
*/
|
|
21
|
+
import type { BriefFilterId } from "@remit/ui";
|
|
21
22
|
import type { InboxFilterParams } from "./inbox-filters.js";
|
|
22
23
|
import { inboxFilterParams } from "./inbox-filters.js";
|
|
23
24
|
import type { SearchToken } from "./search-tokens.js";
|
|
@@ -34,12 +35,23 @@ export const BRIEF_TOKEN_PARAMS: readonly ThreadSearchTokenParamName[] = [
|
|
|
34
35
|
"attachments",
|
|
35
36
|
];
|
|
36
37
|
|
|
37
|
-
/** Chips the request cannot express, so the
|
|
38
|
-
export const BRIEF_CLIENT_ONLY_FILTERS: readonly
|
|
38
|
+
/** Chips the request cannot express, so the caller applies them over the rows. */
|
|
39
|
+
export const BRIEF_CLIENT_ONLY_FILTERS: readonly BriefFilterId[] = [
|
|
39
40
|
"contacts",
|
|
40
41
|
"today",
|
|
41
42
|
];
|
|
42
43
|
|
|
44
|
+
/**
|
|
45
|
+
* The chips on screen that no request carries, as the set to apply over the rows
|
|
46
|
+
* that came back. Every other chip was answered by the server over the whole
|
|
47
|
+
* scope, and applying one of those again here would narrow one page by a
|
|
48
|
+
* criterion the request already applied to everything (#312).
|
|
49
|
+
*/
|
|
50
|
+
export const briefClientOnlyFilters = (
|
|
51
|
+
attributes: ReadonlySet<string>,
|
|
52
|
+
): ReadonlySet<BriefFilterId> =>
|
|
53
|
+
new Set(BRIEF_CLIENT_ONLY_FILTERS.filter((id) => attributes.has(id)));
|
|
54
|
+
|
|
43
55
|
/** Everything a section request narrows by except its own category. */
|
|
44
56
|
export type BriefSectionParams = Omit<InboxFilterParams, "category">;
|
|
45
57
|
|