nexus-shared 4.0.0 → 4.1.0
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/CHANGELOG.md +795 -0
- package/README.md +220 -14
- package/dist/Client.Index.d.ts +13 -0
- package/dist/Client.Index.js +13 -0
- package/dist/Components/Chats/ChatComposer.d.ts +7 -1
- package/dist/Components/Chats/ChatComposer.js +9 -6
- package/dist/Components/Chats/ChatParts.d.ts +15 -2
- package/dist/Components/Chats/ChatParts.js +15 -4
- package/dist/Components/Chats/ChatPopup.d.ts +46 -0
- package/dist/Components/Chats/ChatPopup.js +34 -0
- package/dist/Components/Chats/ChatThread.d.ts +9 -1
- package/dist/Components/Chats/ChatThread.js +54 -6
- package/dist/Components/Dashboards/DashboardBoard.d.ts +62 -0
- package/dist/Components/Dashboards/DashboardBoard.js +594 -0
- package/dist/Components/Dashboards/DashboardWidgets.d.ts +155 -0
- package/dist/Components/Dashboards/DashboardWidgets.js +159 -0
- package/dist/Components/Dashboards/WidgetFrame.d.ts +51 -0
- package/dist/Components/Dashboards/WidgetFrame.js +45 -0
- package/dist/Components/Documents/Button.d.ts +6 -1
- package/dist/Components/Documents/Button.js +2 -2
- package/dist/Components/Documents/TabButtons.d.ts +6 -0
- package/dist/Components/Documents/TabButtons.js +5 -4
- package/dist/Components/Domains/DomainCard.d.ts +98 -0
- package/dist/Components/Domains/DomainCard.js +182 -0
- package/dist/Components/Forms/ApiForm.js +5 -1
- package/dist/Components/Forms/Crud.js +72 -24
- package/dist/Components/Forms/CrudActions.d.ts +5 -4
- package/dist/Components/Forms/CrudActions.js +42 -12
- package/dist/Components/Forms/CrudHistory.d.ts +6 -1
- package/dist/Components/Forms/CrudHistory.js +14 -6
- package/dist/Components/Forms/CrudParts.d.ts +11 -4
- package/dist/Components/Forms/CrudParts.js +76 -10
- package/dist/Components/Forms/CrudRowForms.js +65 -5
- package/dist/Components/Forms/CrudTree.js +7 -7
- package/dist/Components/Forms/CrudWorkspace.d.ts +15 -5
- package/dist/Components/Forms/CrudWorkspace.js +19 -11
- package/dist/Components/Forms/Form.d.ts +3 -2
- package/dist/Components/Forms/Form.js +33 -26
- package/dist/Components/Forms/FormQuestion.d.ts +6 -0
- package/dist/Components/Forms/FormQuestion.js +28 -0
- package/dist/Components/Forms/SubmitForm.js +2 -15
- package/dist/Components/Inputs/ComputedInput.d.ts +51 -0
- package/dist/Components/Inputs/ComputedInput.js +179 -0
- package/dist/Components/Inputs/Dropdown.js +4 -2
- package/dist/Components/Inputs/GroupForm.js +3 -2
- package/dist/Components/Inputs/InputRenderer.d.ts +5 -1
- package/dist/Components/Inputs/MarkdownEditor.d.ts +6 -0
- package/dist/Components/Inputs/MarkdownEditor.js +32 -6
- package/dist/Components/Inputs/PictureInput.js +8 -3
- package/dist/Components/Inputs/RowsInput.d.ts +14 -1
- package/dist/Components/Inputs/RowsInput.js +78 -5
- package/dist/Components/Inputs/TabularForm.js +16 -3
- package/dist/Components/Inputs/TreeView.d.ts +24 -1
- package/dist/Components/Inputs/TreeView.js +84 -7
- package/dist/Components/Layouts/Popup.d.ts +22 -2
- package/dist/Components/Layouts/Popup.js +108 -6
- package/dist/Components/Messages/MailClient.d.ts +50 -0
- package/dist/Components/Messages/MailClient.js +410 -0
- package/dist/Components/Messages/MailColumns.d.ts +21 -0
- package/dist/Components/Messages/MailColumns.js +92 -0
- package/dist/Components/Messages/MailCompose.d.ts +46 -0
- package/dist/Components/Messages/MailCompose.js +154 -0
- package/dist/Components/Messages/MailReader.d.ts +43 -0
- package/dist/Components/Messages/MailReader.js +207 -0
- package/dist/Components/Messages/MailThread.d.ts +71 -0
- package/dist/Components/Messages/MailThread.js +185 -0
- package/dist/Components/Messages/MessageBell.d.ts +41 -0
- package/dist/Components/Messages/MessageBell.js +76 -0
- package/dist/Components/Viewers/DataTable.js +7 -5
- package/dist/Components/Viewers/DataTableColumns.d.ts +2 -2
- package/dist/Components/Viewers/DataTableColumns.js +2 -1
- package/dist/Components/Viewers/DataTableParts.d.ts +3 -1
- package/dist/Components/Viewers/DataTableParts.js +2 -2
- package/dist/Components/Viewers/OwnerFiles.d.ts +57 -5
- package/dist/Components/Viewers/OwnerFiles.js +142 -31
- package/dist/Helpers/ApiModules.js +4 -0
- package/dist/Helpers/AuditEntries.d.ts +50 -0
- package/dist/Helpers/AuditEntries.js +96 -0
- package/dist/Helpers/ChatBackend.d.ts +8 -1
- package/dist/Helpers/ChatBackend.js +18 -0
- package/dist/Helpers/ChatHelpers.d.ts +2 -0
- package/dist/Helpers/ChatHelpers.js +4 -1
- package/dist/Helpers/CrudColumns.d.ts +44 -6
- package/dist/Helpers/CrudColumns.js +63 -13
- package/dist/Helpers/CrudHelpers.d.ts +36 -1
- package/dist/Helpers/CrudHelpers.js +61 -0
- package/dist/Helpers/CrudPickerPlan.d.ts +58 -0
- package/dist/Helpers/CrudPickerPlan.js +46 -0
- package/dist/Helpers/CrudRowForms.d.ts +18 -1
- package/dist/Helpers/CrudRowForms.js +35 -2
- package/dist/Helpers/DashboardHelpers.d.ts +115 -0
- package/dist/Helpers/DashboardHelpers.js +305 -0
- package/dist/Helpers/DomainCards.d.ts +140 -0
- package/dist/Helpers/DomainCards.js +370 -0
- package/dist/Helpers/FileAdapter.d.ts +82 -2
- package/dist/Helpers/FileAdapter.js +72 -1
- package/dist/Helpers/FormStore.d.ts +11 -1
- package/dist/Helpers/FormStore.js +66 -19
- package/dist/Helpers/InputConditions.d.ts +6 -0
- package/dist/Helpers/InputConditions.js +24 -0
- package/dist/Helpers/InputMemory.js +40 -30
- package/dist/Helpers/InputNarrowing.d.ts +38 -6
- package/dist/Helpers/InputNarrowing.js +102 -21
- package/dist/Helpers/MailClientHelpers.d.ts +96 -0
- package/dist/Helpers/MailClientHelpers.js +211 -0
- package/dist/Helpers/MessageBackend.d.ts +152 -0
- package/dist/Helpers/MessageBackend.js +329 -0
- package/dist/Helpers/MessageHelpers.d.ts +160 -0
- package/dist/Helpers/MessageHelpers.js +573 -0
- package/dist/Helpers/MessageUnread.d.ts +95 -0
- package/dist/Helpers/MessageUnread.js +245 -0
- package/dist/Helpers/NexusDriveAdapter.d.ts +17 -1
- package/dist/Helpers/NexusDriveAdapter.js +32 -3
- package/dist/Helpers/NumberHelpers.d.ts +61 -0
- package/dist/Helpers/NumberHelpers.js +124 -8
- package/dist/Helpers/PagedPicker.d.ts +42 -0
- package/dist/Helpers/PagedPicker.js +85 -0
- package/dist/Helpers/RowsStore.d.ts +24 -0
- package/dist/Helpers/RowsStore.js +79 -10
- package/dist/Helpers/TableHelpers.d.ts +15 -2
- package/dist/Helpers/TableHelpers.js +28 -1
- package/dist/Helpers/TableStore.d.ts +7 -3
- package/dist/Helpers/TableStore.js +9 -4
- package/dist/Helpers/ToolbarFit.d.ts +23 -0
- package/dist/Helpers/ToolbarFit.js +30 -0
- package/dist/Helpers/TreeStore.d.ts +75 -6
- package/dist/Helpers/TreeStore.js +182 -18
- package/dist/Interfaces/ApiInterfaces.d.ts +10 -1
- package/dist/Interfaces/ApiInterfaces.js +10 -1
- package/dist/Interfaces/ChatInterfaces.d.ts +22 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +78 -5
- package/dist/Interfaces/DashboardInterfaces.d.ts +123 -0
- package/dist/Interfaces/DashboardInterfaces.js +1 -0
- package/dist/Interfaces/DomainInterfaces.d.ts +145 -0
- package/dist/Interfaces/DomainInterfaces.js +1 -0
- package/dist/Interfaces/FileInterfaces.d.ts +21 -0
- package/dist/Interfaces/FileInterfaces.js +2 -0
- package/dist/Interfaces/FormInterfaces.d.ts +78 -11
- package/dist/Interfaces/InputInterfaces.d.ts +12 -5
- package/dist/Interfaces/MailInterfaces.d.ts +192 -0
- package/dist/Interfaces/MailInterfaces.js +1 -0
- package/dist/Interfaces/TableInterfaces.d.ts +28 -2
- package/dist/Services/JoinSources.d.ts +47 -6
- package/dist/Services/JoinSources.js +68 -33
- package/dist/Services/MessageApi.d.ts +18 -0
- package/dist/Services/MessageApi.js +64 -0
- package/dist/Shared.Index.d.ts +12 -0
- package/dist/Shared.Index.js +12 -0
- package/package.json +7 -1
- package/src/Styles/Nexus.Button.css +29 -0
- package/src/Styles/Nexus.Chat.css +114 -22
- package/src/Styles/Nexus.Crud.css +119 -28
- package/src/Styles/Nexus.Dashboard.css +763 -0
- package/src/Styles/Nexus.Domain.css +384 -0
- package/src/Styles/Nexus.Editor.css +20 -0
- package/src/Styles/Nexus.Files.css +27 -0
- package/src/Styles/Nexus.Index.css +3 -0
- package/src/Styles/Nexus.Input.css +31 -0
- package/src/Styles/Nexus.Mail.css +1062 -0
- package/src/Styles/Nexus.Popup.css +58 -0
- package/src/Styles/Nexus.Table.css +1458 -1449
- package/src/Styles/Nexus.Theme.Picker.css +1 -0
- package/src/Styles/Nexus.Tree.css +10 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { ChatDomainValue, ChatId, ChatMessageKind, ChatSource } from "./ChatInterfaces.ts";
|
|
2
|
+
import type { DropdownPage, DropdownPageRequest } from "./InputInterfaces.ts";
|
|
3
|
+
import type { TablePage } from "./TableInterfaces.ts";
|
|
4
|
+
/** How a person is on a thread: who sent it, and who it was sent to, copied to, or copied to unseen. */
|
|
5
|
+
export type MailRole = "from" | "to" | "cc" | "bcc";
|
|
6
|
+
/** The lists of a mailbox: every thread not archived, the ones with unread lines, the ones the reader started, the archived. */
|
|
7
|
+
export type MailList = "inbox" | "unread" | "sent" | "archived";
|
|
8
|
+
/** What a thread opened with: a mail a person wrote, or a notification a module wrote about an event. */
|
|
9
|
+
export type MailHeadKind = Extract<ChatMessageKind, "mail" | "notification">;
|
|
10
|
+
/**
|
|
11
|
+
* What a conversation in a mailbox is, which decides how it opens and which of the mailbox's types it is counted under:
|
|
12
|
+
* `mail` a mail thread about no record, `chat` a one-to-one or group chat (it opens in a chat popup), `domain` a mail
|
|
13
|
+
* thread about a record (its `domainType` names the kind: a leave request, a requisition). The Nexus wire's `kind`:
|
|
14
|
+
* Mail 1, Chat 2, Domain 3.
|
|
15
|
+
*/
|
|
16
|
+
export type MailThreadKind = "mail" | "chat" | "domain";
|
|
17
|
+
/** Someone a mail can be sent to, or was. */
|
|
18
|
+
export interface MailPerson {
|
|
19
|
+
id: ChatId;
|
|
20
|
+
name: string;
|
|
21
|
+
/** False for someone who has left: their name stays on what they wrote, and they cannot be addressed again. */
|
|
22
|
+
isActive?: boolean;
|
|
23
|
+
/** A photo URL. Without one, the initials of the name. */
|
|
24
|
+
avatar?: string | null;
|
|
25
|
+
}
|
|
26
|
+
/** The record a thread is about: its kind (the app's own domain value), its id as the reader may open it, and its words. */
|
|
27
|
+
export interface MailDomain {
|
|
28
|
+
domainType: ChatDomainValue;
|
|
29
|
+
/** The record's id, sealed for this reader. `null` when the app knows no way to open records of this kind. */
|
|
30
|
+
id: ChatId | null;
|
|
31
|
+
/** The record's words as the module wrote them: "LR2082-0007 · Puja Shrestha". */
|
|
32
|
+
label: string | null;
|
|
33
|
+
}
|
|
34
|
+
/** One thread in a list of them: what an inbox row shows. A record, so a CRUD can list it. */
|
|
35
|
+
export interface MailInboxRow extends Record<string, unknown> {
|
|
36
|
+
id: ChatId;
|
|
37
|
+
subject: string;
|
|
38
|
+
fromId: ChatId;
|
|
39
|
+
fromName: string;
|
|
40
|
+
/** How the reader is on it. */
|
|
41
|
+
myRole: MailRole;
|
|
42
|
+
/** What it opened with. */
|
|
43
|
+
kind: MailHeadKind;
|
|
44
|
+
/** The kind of record it is about, when it is about one. */
|
|
45
|
+
domainType?: ChatDomainValue | null;
|
|
46
|
+
/** That record's words, shown beside the subject. */
|
|
47
|
+
domainLabel?: string | null;
|
|
48
|
+
/** When its newest line was written, as a UTC moment. */
|
|
49
|
+
lastMessageTime: string;
|
|
50
|
+
/** Who wrote it. */
|
|
51
|
+
lastAuthorName?: string | null;
|
|
52
|
+
/** Its first characters, Markdown as written (`markdownToPlainText` for a line of text). */
|
|
53
|
+
lastPreview: string;
|
|
54
|
+
messageCount: number;
|
|
55
|
+
/** Lines the reader has not read. */
|
|
56
|
+
unreadCount: number;
|
|
57
|
+
/** The reader put it aside. */
|
|
58
|
+
archived: boolean;
|
|
59
|
+
/** What the conversation is: mail, chat, or mail about a record. Absent from an older backend: read from `domainType`. */
|
|
60
|
+
threadKind?: MailThreadKind;
|
|
61
|
+
/** The reader starred it. Absent when the backend keeps no stars. */
|
|
62
|
+
starred?: boolean;
|
|
63
|
+
/** A photo of the sender: the row's avatar. Without one, their initials. */
|
|
64
|
+
fromAvatar?: string | null;
|
|
65
|
+
}
|
|
66
|
+
/** A thread opened: its subject and its people, as the reader may see them, and how they may act on it. */
|
|
67
|
+
export interface MailThreadHead {
|
|
68
|
+
id: ChatId;
|
|
69
|
+
subject: string;
|
|
70
|
+
from: MailPerson;
|
|
71
|
+
to: readonly MailPerson[];
|
|
72
|
+
cc: readonly MailPerson[];
|
|
73
|
+
/**
|
|
74
|
+
* The people copied unseen, as the reader may see them: all of them for the sender, the reader's own row for a person
|
|
75
|
+
* copied unseen, nobody for anyone else. The backend decides; the thread shows what it is given.
|
|
76
|
+
*/
|
|
77
|
+
bcc: readonly MailPerson[];
|
|
78
|
+
myRole: MailRole;
|
|
79
|
+
archived: boolean;
|
|
80
|
+
/** The reader may reply. */
|
|
81
|
+
canSend: boolean;
|
|
82
|
+
/** When the thread was started, as a UTC moment. */
|
|
83
|
+
createdTime: string;
|
|
84
|
+
messageCount: number;
|
|
85
|
+
unreadCount: number;
|
|
86
|
+
/** The record it is about. */
|
|
87
|
+
domain?: MailDomain | null;
|
|
88
|
+
/** What the conversation is: mail, chat, or mail about a record. */
|
|
89
|
+
threadKind?: MailThreadKind;
|
|
90
|
+
}
|
|
91
|
+
/** What a list of threads asks for. */
|
|
92
|
+
export interface MailInboxRequest {
|
|
93
|
+
list: MailList;
|
|
94
|
+
/** Matched in the subject and the record's words. */
|
|
95
|
+
search?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Only the threads about one record: its kind and its id (as its own module gave it to the reader). The kind alone
|
|
98
|
+
* (no `parentId`) is a mailbox's type: every thread about a record of that kind.
|
|
99
|
+
*/
|
|
100
|
+
domainType?: ChatDomainValue;
|
|
101
|
+
parentId?: ChatId;
|
|
102
|
+
/** Only one kind of conversation: a mailbox's Mail, Chat, or a record kind's type (`domain` with its `domainType`). */
|
|
103
|
+
threadKind?: MailThreadKind;
|
|
104
|
+
/** Threads before this page. */
|
|
105
|
+
skip: number;
|
|
106
|
+
/** Threads on this page. */
|
|
107
|
+
take: number;
|
|
108
|
+
signal?: AbortSignal;
|
|
109
|
+
}
|
|
110
|
+
/** What the reader has not read: how many lines, and the newest threads holding them. */
|
|
111
|
+
export interface MailUnread {
|
|
112
|
+
count: number;
|
|
113
|
+
/** The newest threads with unread lines, newest first. */
|
|
114
|
+
newest: readonly MailInboxRow[];
|
|
115
|
+
}
|
|
116
|
+
/** One list of a mailbox with how much of it is unread: its folder's count. */
|
|
117
|
+
export interface MailFolderCount {
|
|
118
|
+
list: MailList;
|
|
119
|
+
/** Conversations in it with unread lines. */
|
|
120
|
+
unread: number;
|
|
121
|
+
}
|
|
122
|
+
/** One type of conversation in a mailbox (Mail, Chat, Leave, Request ...), counted across the reader's inbox. */
|
|
123
|
+
export interface MailKindCount {
|
|
124
|
+
threadKind: MailThreadKind;
|
|
125
|
+
/** The record kind of a `domain` type; `null` for Mail and Chat. */
|
|
126
|
+
domainType: ChatDomainValue | null;
|
|
127
|
+
/** Conversations of this type with unread lines. */
|
|
128
|
+
unread: number;
|
|
129
|
+
/** Conversations of this type in the inbox. A type with none is not listed. */
|
|
130
|
+
total: number;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The counts beside a mailbox's folders and types, read in one call. The Nexus wire (`Mail/Folders`):
|
|
134
|
+
* `{ folders: [{ list, unread }], kinds: [{ kind, domainType, unread, total }] }`, lists 1-4 and kinds 1-3.
|
|
135
|
+
*/
|
|
136
|
+
export interface MailFolders {
|
|
137
|
+
folders: readonly MailFolderCount[];
|
|
138
|
+
kinds: readonly MailKindCount[];
|
|
139
|
+
}
|
|
140
|
+
/** Who a new message is sent to by default: the people the reader set, or the backend's rule (their supervisor). */
|
|
141
|
+
export interface MailDefaults {
|
|
142
|
+
to: readonly MailPerson[];
|
|
143
|
+
cc: readonly MailPerson[];
|
|
144
|
+
bcc: readonly MailPerson[];
|
|
145
|
+
}
|
|
146
|
+
/** A new message: the people by id, at least one in To; its subject and body; and this browser's id for it, so a retry writes it once. */
|
|
147
|
+
export interface MailDraft {
|
|
148
|
+
to: readonly ChatId[];
|
|
149
|
+
cc?: readonly ChatId[];
|
|
150
|
+
bcc?: readonly ChatId[];
|
|
151
|
+
subject: string;
|
|
152
|
+
/** Markdown. */
|
|
153
|
+
body: string;
|
|
154
|
+
clientId: string;
|
|
155
|
+
}
|
|
156
|
+
/** A new message written: its thread, and its first line. */
|
|
157
|
+
export interface MailSent {
|
|
158
|
+
id: ChatId;
|
|
159
|
+
messageId: ChatId;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Where a mailbox's data comes from. A thread's lines come through `threadSource`, an ordinary `ChatSource`, so the
|
|
163
|
+
* chat's store and thread draw, page, send and mark read a mail thread unchanged.
|
|
164
|
+
*/
|
|
165
|
+
export interface MessageSource {
|
|
166
|
+
/** One page of a list, newest line first. */
|
|
167
|
+
inbox(request: MailInboxRequest): Promise<TablePage<MailInboxRow>>;
|
|
168
|
+
/** A thread's head: its subject, its people, the reader's role. */
|
|
169
|
+
thread(id: ChatId, signal?: AbortSignal): Promise<MailThreadHead>;
|
|
170
|
+
/** A thread's lines: history, replies and the read mark, as a chat. */
|
|
171
|
+
threadSource(id: ChatId): ChatSource;
|
|
172
|
+
/** Writes a new message, and answers its thread. */
|
|
173
|
+
compose(draft: MailDraft, signal?: AbortSignal): Promise<MailSent>;
|
|
174
|
+
/** Puts threads aside for the reader alone, and answers how many changed. */
|
|
175
|
+
archive(ids: readonly ChatId[], signal?: AbortSignal): Promise<number>;
|
|
176
|
+
/** Brings them back to the inbox, and answers how many changed. */
|
|
177
|
+
unarchive(ids: readonly ChatId[], signal?: AbortSignal): Promise<number>;
|
|
178
|
+
/** What the reader has not read. */
|
|
179
|
+
unread(signal?: AbortSignal): Promise<MailUnread>;
|
|
180
|
+
/** The people a message can be sent to, searched by name, a page at a time (the reader left out). */
|
|
181
|
+
people(query: DropdownPageRequest): Promise<DropdownPage<MailPerson>>;
|
|
182
|
+
/** Who a new message goes to by default. */
|
|
183
|
+
defaults(signal?: AbortSignal): Promise<MailDefaults>;
|
|
184
|
+
/** The counts beside the folders and the types. Without it a mail client shows no counts and no Types. */
|
|
185
|
+
folders?(signal?: AbortSignal): Promise<MailFolders>;
|
|
186
|
+
/** Marks whole conversations read for the reader, and answers how many changed. Without it, no Mark read. */
|
|
187
|
+
markRead?(ids: readonly ChatId[], signal?: AbortSignal): Promise<number>;
|
|
188
|
+
/** Marks conversations unread again (their newest line), and answers how many changed. Without it, no Mark unread. */
|
|
189
|
+
markUnread?(ids: readonly ChatId[], signal?: AbortSignal): Promise<number>;
|
|
190
|
+
/** Stars conversations for the reader, or takes the star away. Without it, a mail client draws no stars. */
|
|
191
|
+
star?(ids: readonly ChatId[], starred: boolean, signal?: AbortSignal): Promise<number>;
|
|
192
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -79,6 +79,16 @@ export interface TableColumn<TRow extends TableRow = TableRow> {
|
|
|
79
79
|
wrap?: boolean;
|
|
80
80
|
/** Its text is Markdown: the table shows the text, and a record's details show it formatted. */
|
|
81
81
|
markdown?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* The value is a list of rows (a subscriber's contacts, a guest's devices), and these are its columns: a record's
|
|
84
|
+
* details (`CrudDetails`) draw it as a small read-only table across the whole line - a header row, each row's number
|
|
85
|
+
* first, an option's tone as its tag, a yes or no in words - and say "None" for an empty list. A data table shows how
|
|
86
|
+
* many rows the list holds.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* { key: "contacts", label: "Contacts", group: "Contact details", table: [{ key: "number", label: "Number" }, { key: "isPrimary", label: "Primary", format: "boolean" }] }
|
|
90
|
+
*/
|
|
91
|
+
table?: readonly TableColumn[];
|
|
82
92
|
/** The user can drag the heading to another place, or move it from its menu. Default true. */
|
|
83
93
|
movable?: boolean;
|
|
84
94
|
/** The user can drag the edge of the heading to change the width. Default true; `false` for a column that must keep its width. */
|
|
@@ -87,6 +97,12 @@ export interface TableColumn<TRow extends TableRow = TableRow> {
|
|
|
87
97
|
pinnable?: boolean;
|
|
88
98
|
/** Pinned before the user pins anything: `left` keeps it at the start, `right` at the end. */
|
|
89
99
|
pinned?: TablePinSide;
|
|
100
|
+
/**
|
|
101
|
+
* Drawn first of the page's columns (after the row number) until the user moves it: a picture, a logo. When the
|
|
102
|
+
* page pins other columns on the left, a lead column is pinned there too, at their head, so it still comes first.
|
|
103
|
+
* The user hides, moves, pins and unpins it like any column; Reset columns brings it back. `pictureColumn` sets it.
|
|
104
|
+
*/
|
|
105
|
+
lead?: boolean;
|
|
90
106
|
/**
|
|
91
107
|
* The value is another record's, looked up by the key this row holds: a country's sub-region named from the
|
|
92
108
|
* sub-regions' `FindListing`. The table collects every key its rows hold, asks once for the ones it has not got, keeps
|
|
@@ -145,6 +161,11 @@ export interface TableJoin {
|
|
|
145
161
|
}
|
|
146
162
|
/** Which side a pinned column stays on while the others scroll. */
|
|
147
163
|
export type TablePinSide = "left" | "right";
|
|
164
|
+
/**
|
|
165
|
+
* The sides the user chose, by column key: a side, or `null` for a column they unpinned that its params pin. A key
|
|
166
|
+
* that is not here keeps the column's own `pinned`.
|
|
167
|
+
*/
|
|
168
|
+
export type TablePins = Readonly<Record<string, TablePinSide | null>>;
|
|
148
169
|
export type TableSortDirection = "asc" | "desc";
|
|
149
170
|
export interface TableSort {
|
|
150
171
|
/** A column's `key` (its `sortKey` goes to a server). */
|
|
@@ -220,8 +241,8 @@ export interface TableState<TRow extends TableRow = TableRow> {
|
|
|
220
241
|
order: readonly string[];
|
|
221
242
|
/** Widths the user set, in pixels, by column key (and by `#index`, `#actions` for the table's own columns). */
|
|
222
243
|
widths: Readonly<Record<string, number>>;
|
|
223
|
-
/** The side
|
|
224
|
-
pinned:
|
|
244
|
+
/** The side the user pinned each column to, by column key; `null` where they unpinned a column its params pin. */
|
|
245
|
+
pinned: TablePins;
|
|
225
246
|
}
|
|
226
247
|
/** A column as the table draws it: where it sits, whether it is pinned, and how far from that edge. */
|
|
227
248
|
export interface TableViewColumn<TRow extends TableRow = TableRow> {
|
|
@@ -480,6 +501,11 @@ export interface DataTableProps<TRow extends TableRow = TableRow> {
|
|
|
480
501
|
* number; a section's row with none shows the section's icon.
|
|
481
502
|
*/
|
|
482
503
|
rowMarks?: (row: TRow) => readonly TableRowMark[];
|
|
504
|
+
/**
|
|
505
|
+
* Rows that read strong while this says so: a thread with unread lines (`mailRowUnread`), a task nobody has opened.
|
|
506
|
+
* Every cell's text is heavier and in the text colour; the row keeps its height, and a column with a width its width.
|
|
507
|
+
*/
|
|
508
|
+
rowStrong?: (row: TRow) => boolean;
|
|
483
509
|
/**
|
|
484
510
|
* One section of pinned rows, before any `sections`: the same as a section with the key `pins` and the pin icon.
|
|
485
511
|
* Leave it out for no such section.
|
|
@@ -23,6 +23,14 @@ export interface PickerController {
|
|
|
23
23
|
activeListing: ApiEndpoint;
|
|
24
24
|
findListing: ApiEndpoint;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* A controller whose picker reads another listing (`CrudPickerOptions.list`): it may have no `activeListing`, and a
|
|
28
|
+
* `findListing` only when it has one - a pay run's CRUD endpoints, every run of the company in its NormalListing.
|
|
29
|
+
*/
|
|
30
|
+
export interface PickerListController {
|
|
31
|
+
activeListing?: ApiEndpoint;
|
|
32
|
+
findListing?: ApiEndpoint;
|
|
33
|
+
}
|
|
26
34
|
/** What `crudPicker` needs to know of the records: the field a person reads, and the one under it. */
|
|
27
35
|
export interface CrudPickerOptions {
|
|
28
36
|
/** The field shown as the option: `"regionName"`. */
|
|
@@ -46,9 +54,32 @@ export interface CrudPickerOptions {
|
|
|
46
54
|
/**
|
|
47
55
|
* The query parameter the controller's `activeListing` narrows by on the server, for a picker another input narrows
|
|
48
56
|
* (`narrowedBy`): `"subscriptionGroupId"` asks `...?subscriptionGroupId={the group}` and keeps each answer apart. Without
|
|
49
|
-
* it, the whole list is read once and narrowed in the browser by the records' own field (`narrowedBy.key`).
|
|
57
|
+
* it, the whole list is read once and narrowed in the browser by the records' own field (`narrowedBy.key`). Narrowed by
|
|
58
|
+
* several values at once (`narrowedBy` as a list), it names the narrowings' keys the server takes as query parameters
|
|
59
|
+
* (a list of them, or one); a value the listing's path takes (`{itemId}` in `list`) always goes there, and the rest
|
|
60
|
+
* narrow in the browser.
|
|
61
|
+
*/
|
|
62
|
+
narrowQuery?: string | readonly string[];
|
|
63
|
+
/**
|
|
64
|
+
* The listing the options are read from, in place of the controller's `activeListing`: a controller with none, or a
|
|
65
|
+
* picker that offers more than the usable records - every pay run of the company (`PAY_RUNS.lists.normal`), drafts too.
|
|
66
|
+
* Read once and kept for the page as the active list is, and marked old when a CRUD on that controller changes a
|
|
67
|
+
* record (`expireLookups` reads the CRUD's `lists`).
|
|
50
68
|
*/
|
|
51
|
-
|
|
69
|
+
list?: ApiEndpoint;
|
|
70
|
+
/**
|
|
71
|
+
* Which records are usable: offered, and shown untagged when a form holds one. Default the backend's rule
|
|
72
|
+
* (`CrudBackend.isPickable`: for Nexus a record under 30, so one pending in 30-39 - an invited person - is not). A list
|
|
73
|
+
* whose records use a band of their own says so: `usable: run => run.recordStatus < NEXUS_TRASH_STATUS` offers a pay
|
|
74
|
+
* run's draft (31) beside the locked runs.
|
|
75
|
+
*/
|
|
76
|
+
usable?: (record: TableJoinedRecord) => boolean;
|
|
77
|
+
/**
|
|
78
|
+
* The option shown for a record, when its words are not one field: `run => ({ ...run, label: run.monthName + " · " +
|
|
79
|
+
* run.runCode, state: STATES[run.payRunState] })` with `labelKey: "label"` and `descriptionKey: "state"`. Keep the
|
|
80
|
+
* record's fields (spread it): the value, the search and the page read the option. Made once per record read.
|
|
81
|
+
*/
|
|
82
|
+
option?: (record: TableJoinedRecord) => TableJoinedRecord;
|
|
52
83
|
backend?: CrudBackend;
|
|
53
84
|
}
|
|
54
85
|
/** The dropdown params `crudPicker` gives: spread them into a dropdown's input. */
|
|
@@ -68,13 +99,23 @@ export interface CrudPickerParams {
|
|
|
68
99
|
* found through `findListing`, sharing the calls and answers of the tables' joins, and shows tagged, never offered again.
|
|
69
100
|
* A value that is not the records' key (`valueKey`) is found through the picker's own `find` instead.
|
|
70
101
|
*
|
|
102
|
+
* A picker can read another listing (`list`), say which records are usable (`usable`) and make each option's words
|
|
103
|
+
* (`option`): every pay run of the company, drafts too, named by month and code with its state under it.
|
|
104
|
+
*
|
|
71
105
|
* @example { type: EInputTypes.Dropdown, name: "regionId", label: "Region", required: true, ...crudPicker(REGIONS, { labelKey: "regionName" }) }
|
|
72
106
|
* @example crudPicker(COMPANY_USERS, { labelKey: "fullname", valueKey: "userId", find: COMPANY_USERS_BY_ACCOUNT })
|
|
107
|
+
* @example crudPicker(PAY_RUNS, { list: PAY_RUNS.lists.normal, usable: run => run.recordStatus < NEXUS_TRASH_STATUS, labelKey: "label", descriptionKey: "state", option: runOption })
|
|
73
108
|
*/
|
|
74
|
-
export declare function crudPicker(controller: PickerController,
|
|
109
|
+
export declare function crudPicker(controller: PickerController, options: CrudPickerOptions): CrudPickerParams;
|
|
110
|
+
export declare function crudPicker(controller: PickerListController, options: CrudPickerOptions & {
|
|
111
|
+
list: ApiEndpoint;
|
|
112
|
+
}): CrudPickerParams;
|
|
75
113
|
/**
|
|
76
114
|
* Marks what the page looked up from a controller as old, after a write there may have changed it: the records its
|
|
77
|
-
* `findListing` named and its pickers' own finds answered, and the
|
|
78
|
-
*
|
|
115
|
+
* `findListing` named and its pickers' own finds answered, and the lists its pickers offer - its `activeListing`, and
|
|
116
|
+
* each of a CRUD's `lists` a picker reads instead (`CrudPickerOptions.list`). Drawn as they are until the next ask brings
|
|
117
|
+
* the new ones.
|
|
79
118
|
*/
|
|
80
|
-
export declare function expireLookups(controller:
|
|
119
|
+
export declare function expireLookups(controller: PickerListController & {
|
|
120
|
+
lists?: Partial<Record<string, ApiEndpoint>>;
|
|
121
|
+
}): void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isApiEndpoint } from "../Helpers/ApiRoutes.js";
|
|
2
2
|
import { getCrudBackend } from "../Helpers/CrudBackend.js";
|
|
3
|
+
import { crudPickerNarrowAsk, crudPickerPlan, fitsNarrow, pathValueNames } from "../Helpers/CrudPickerPlan.js";
|
|
3
4
|
import { filterItems } from "../Helpers/DropdownHelpers.js";
|
|
4
5
|
import { JOIN_LIFETIME, joinSourceName, tableJoins } from "../Helpers/TableJoins.js";
|
|
5
6
|
import { api } from "./BrowserApi.js";
|
|
@@ -58,51 +59,80 @@ function peekSource(name, key, aged) {
|
|
|
58
59
|
export function peekFound(findListing, key, aged = false) {
|
|
59
60
|
return peekSource(joinSourceName(findListing), key, aged);
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
* A value that is not the records' key (`valueKey`) is found through the picker's own `find` instead.
|
|
67
|
-
*
|
|
68
|
-
* @example { type: EInputTypes.Dropdown, name: "regionId", label: "Region", required: true, ...crudPicker(REGIONS, { labelKey: "regionName" }) }
|
|
69
|
-
* @example crudPicker(COMPANY_USERS, { labelKey: "fullname", valueKey: "userId", find: COMPANY_USERS_BY_ACCOUNT })
|
|
70
|
-
*/
|
|
71
|
-
export function crudPicker(controller, { labelKey, descriptionKey, valueKey = "id", find, narrowQuery, backend = getCrudBackend() }) {
|
|
72
|
-
const listName = joinSourceName(controller.activeListing);
|
|
62
|
+
export function crudPicker(controller, options) {
|
|
63
|
+
const { labelKey, descriptionKey, find, narrowQuery, option, backend = getCrudBackend() } = options;
|
|
64
|
+
const plan = crudPickerPlan(controller, options, backend);
|
|
65
|
+
const valueKey = options.valueKey ?? "id";
|
|
66
|
+
const listName = joinSourceName(plan.list);
|
|
73
67
|
// Where a value the kept list does not offer is looked up: `findListing` while the values are the records' keys, the
|
|
74
|
-
// picker's own find for another field, and nowhere without
|
|
75
|
-
const lookup =
|
|
76
|
-
if (find && lookup) {
|
|
68
|
+
// picker's own find for another field, and nowhere without either.
|
|
69
|
+
const lookup = plan.lookup ? joinSource(plan.lookup.source, backend, plan.lookup.key) : null;
|
|
70
|
+
if (find && lookup && controller.findListing) {
|
|
77
71
|
// Marked old with the controller's own lookups, after a save there (`expireLookups`).
|
|
78
72
|
const beside = joinSourceName(controller.findListing);
|
|
79
73
|
const kept = finds.get(beside) ?? new Set();
|
|
80
74
|
kept.add(lookup.name);
|
|
81
75
|
finds.set(beside, kept);
|
|
82
76
|
}
|
|
77
|
+
// Each record's option, made once per record read (a new read makes new records, and so new options).
|
|
78
|
+
const made = new WeakMap();
|
|
79
|
+
const shown = (record) => {
|
|
80
|
+
if (!option)
|
|
81
|
+
return record;
|
|
82
|
+
let kept = made.get(record);
|
|
83
|
+
if (!kept)
|
|
84
|
+
made.set(record, (kept = option(record)));
|
|
85
|
+
return kept;
|
|
86
|
+
};
|
|
87
|
+
const shownAll = (records) => (option ? records.map(shown) : records);
|
|
83
88
|
// The words a person reads, not the key: a Nexus key is an encrypted token, and a search would find letters in it.
|
|
84
89
|
const searchKeys = descriptionKey ? [labelKey, descriptionKey] : [labelKey];
|
|
85
|
-
const loadList = (signal, query) => api.send(
|
|
90
|
+
const loadList = (signal, query, params) => api.send(plan.list, { signal, toast: false, ...(query && { query }), ...(params && { params }) }).then(answer => {
|
|
86
91
|
if (!answer.ok)
|
|
87
92
|
throw new Error(answer.message ?? "The list could not be loaded.");
|
|
88
93
|
return backend.readList(answer.result);
|
|
89
94
|
});
|
|
95
|
+
// A listing whose path takes values (`StockBalance/Units/{storeId}/{itemId}`) is only ever read narrowed by them: its
|
|
96
|
+
// lists are kept one per values, and a saved value is named from them.
|
|
97
|
+
const pathNarrowed = pathValueNames(plan.list).length > 0;
|
|
98
|
+
const keepNarrowedName = (name) => {
|
|
99
|
+
const kept = narrowedLists.get(listName) ?? new Set();
|
|
100
|
+
kept.add(name);
|
|
101
|
+
narrowedLists.set(listName, kept);
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* The options narrowed by several values at once (`narrowedBy` as a list, a line's Tags of its Item in the form's Store),
|
|
105
|
+
* or by values the listing's path takes: each in the path, the query (`narrowQuery`) or the browser, as
|
|
106
|
+
* `crudPickerNarrowAsk` says.
|
|
107
|
+
*/
|
|
108
|
+
const narrowedByAll = async (narrows) => {
|
|
109
|
+
const ask = crudPickerNarrowAsk(plan.list, listName, narrows, narrowQuery);
|
|
110
|
+
if (!ask)
|
|
111
|
+
return [];
|
|
112
|
+
const server = Object.keys(ask.params).length + Object.keys(ask.query).length > 0;
|
|
113
|
+
if (server)
|
|
114
|
+
keepNarrowedName(ask.name);
|
|
115
|
+
const rows = server ? await tableJoins.list(ask.name, signal => loadList(signal, Object.keys(ask.query).length ? ask.query : undefined, Object.keys(ask.params).length ? ask.params : undefined)) : await tableJoins.list(listName, loadList);
|
|
116
|
+
return ask.filters.length ? rows.filter(row => ask.filters.every(narrow => fitsNarrow(row, narrow))) : rows;
|
|
117
|
+
};
|
|
118
|
+
/** Every record the page holds of a path-narrowed listing: each list kept, fresh unless `aged`. */
|
|
119
|
+
const heldNarrowed = (lifetime = JOIN_LIFETIME) => (pathNarrowed ? [...(narrowedLists.get(listName) ?? [])].flatMap(name => tableJoins.peekList(name, lifetime) ?? []) : []);
|
|
90
120
|
/** The options narrowed by another input's value (`narrowedBy`): asked of the server by `narrowQuery`, else the kept list's. */
|
|
91
121
|
const narrowedList = async ({ key, value, orUnset }) => {
|
|
92
122
|
// The server narrows by a value; which records it adds for `orUnset` is its endpoint's rule.
|
|
93
|
-
if (narrowQuery && value !== null) {
|
|
123
|
+
if (typeof narrowQuery === "string" && value !== null) {
|
|
94
124
|
const name = `${listName}?${narrowQuery}=${String(value)}`;
|
|
95
|
-
|
|
96
|
-
kept.add(name);
|
|
97
|
-
narrowedLists.set(listName, kept);
|
|
125
|
+
keepNarrowedName(name);
|
|
98
126
|
return tableJoins.list(name, signal => loadList(signal, { [narrowQuery]: String(value) }));
|
|
99
127
|
}
|
|
100
128
|
const all = await tableJoins.list(listName, loadList);
|
|
101
129
|
return all.filter(row => (value !== null && String(row[key]) === String(value)) || (orUnset && unset(row[key])));
|
|
102
130
|
};
|
|
103
131
|
return {
|
|
104
|
-
async loadOptions({ search, skip, pageSize, narrow }) {
|
|
105
|
-
|
|
132
|
+
async loadOptions({ search, skip, pageSize, narrow, narrows }) {
|
|
133
|
+
// Several values at once, or values the path takes: each where it goes. One value alone: as before.
|
|
134
|
+
const several = narrows && narrows.length > 1 ? narrows : null;
|
|
135
|
+
const all = shownAll(several || (narrow && pathNarrowed) ? await narrowedByAll(several ?? [narrow]) : narrow ? await narrowedList(narrow) : pathNarrowed ? [] : await tableJoins.list(listName, loadList));
|
|
106
136
|
const matches = search ? filterItems(all, search, searchKeys) : all;
|
|
107
137
|
return { items: matches.slice(skip, skip + pageSize), total: matches.length, hasMore: skip + pageSize < matches.length };
|
|
108
138
|
},
|
|
@@ -110,29 +140,31 @@ export function crudPicker(controller, { labelKey, descriptionKey, valueKey = "i
|
|
|
110
140
|
// does not offer - a parent since trashed - is looked up.
|
|
111
141
|
async resolveOptions(values) {
|
|
112
142
|
const keys = values.map(String);
|
|
113
|
-
const kept = tableJoins.peekList(listName) ?? [];
|
|
114
|
-
const found = kept.filter(row => keys.includes(String(row[valueKey])));
|
|
143
|
+
const kept = [...(tableJoins.peekList(listName) ?? []), ...heldNarrowed()];
|
|
144
|
+
const found = kept.filter((row, index) => keys.includes(String(row[valueKey])) && kept.findIndex(other => String(other[valueKey]) === String(row[valueKey])) === index);
|
|
115
145
|
const missing = keys.filter(key => !found.some(row => String(row[valueKey]) === key));
|
|
116
146
|
if (missing.length === 0 || !lookup)
|
|
117
|
-
return found;
|
|
118
|
-
return [...found, ...(await tableJoins.records(lookup, missing))];
|
|
147
|
+
return shownAll(found);
|
|
148
|
+
return shownAll([...found, ...(await tableJoins.records(lookup, missing))]);
|
|
119
149
|
},
|
|
120
150
|
// The same at once, from what the page holds: a box named on its first draw, a saved record named with no call. Only
|
|
121
151
|
// what is fresh, unless an old record is asked for: a save names a value with one rather than with nothing.
|
|
122
152
|
peekOption(key, aged = false) {
|
|
123
|
-
const kept = tableJoins.peekList(listName, aged ? Infinity : JOIN_LIFETIME)
|
|
124
|
-
|
|
153
|
+
const kept = (tableJoins.peekList(listName, aged ? Infinity : JOIN_LIFETIME) ?? heldNarrowed(aged ? Infinity : JOIN_LIFETIME)).find(row => String(row[valueKey]) === key);
|
|
154
|
+
const record = kept ?? (lookup ? peekSource(lookup.name, key, aged) : undefined);
|
|
155
|
+
return record ? shown(record) : undefined;
|
|
125
156
|
},
|
|
126
157
|
valueKey,
|
|
127
158
|
labelKey,
|
|
128
159
|
...(descriptionKey && { descriptionKey }),
|
|
129
|
-
isOptionInactive: item => !
|
|
160
|
+
isOptionInactive: item => !plan.usable(item),
|
|
130
161
|
};
|
|
131
162
|
}
|
|
132
163
|
/**
|
|
133
164
|
* Marks what the page looked up from a controller as old, after a write there may have changed it: the records its
|
|
134
|
-
* `findListing` named and its pickers' own finds answered, and the
|
|
135
|
-
*
|
|
165
|
+
* `findListing` named and its pickers' own finds answered, and the lists its pickers offer - its `activeListing`, and
|
|
166
|
+
* each of a CRUD's `lists` a picker reads instead (`CrudPickerOptions.list`). Drawn as they are until the next ask brings
|
|
167
|
+
* the new ones.
|
|
136
168
|
*/
|
|
137
169
|
export function expireLookups(controller) {
|
|
138
170
|
if (controller.findListing) {
|
|
@@ -141,8 +173,11 @@ export function expireLookups(controller) {
|
|
|
141
173
|
for (const other of finds.get(name) ?? [])
|
|
142
174
|
tableJoins.expire(other);
|
|
143
175
|
}
|
|
144
|
-
|
|
145
|
-
|
|
176
|
+
const lists = [controller.activeListing, ...Object.values(controller.lists ?? {})];
|
|
177
|
+
for (const list of lists) {
|
|
178
|
+
if (!list)
|
|
179
|
+
continue;
|
|
180
|
+
const name = joinSourceName(list);
|
|
146
181
|
tableJoins.expireList(name);
|
|
147
182
|
for (const narrowed of narrowedLists.get(name) ?? [])
|
|
148
183
|
tableJoins.expireList(narrowed);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type MessageBackend } from "../Helpers/MessageBackend.ts";
|
|
2
|
+
import type { ApiCallOptions } from "../Interfaces/ApiInterfaces.ts";
|
|
3
|
+
import type { MessageSource } from "../Interfaces/MailInterfaces.ts";
|
|
4
|
+
/** Options of a mailbox's source. */
|
|
5
|
+
export interface ApiMessageSourceOptions {
|
|
6
|
+
/** The backend to use, when it is not the app's registered one. */
|
|
7
|
+
backend?: MessageBackend;
|
|
8
|
+
/** Extras on every call, such as a header. */
|
|
9
|
+
call?: Omit<ApiCallOptions, "body" | "signal" | "params">;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A mailbox through the app's API. One is enough for a page: a thread's lines come from `threadSource(id)`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const mail = useMemo(() => apiMessageSource(), []);
|
|
16
|
+
* <MailThread source={mail} threadId={row.id} userId={me.id} />
|
|
17
|
+
*/
|
|
18
|
+
export declare function apiMessageSource(options?: ApiMessageSourceOptions): MessageSource;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { unwrapApiResult } from "../Helpers/ApiResponses.js";
|
|
3
|
+
import { getMessageBackend } from "../Helpers/MessageBackend.js";
|
|
4
|
+
import { api } from "./BrowserApi.js";
|
|
5
|
+
/**
|
|
6
|
+
* A mailbox through the app's API. One is enough for a page: a thread's lines come from `threadSource(id)`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const mail = useMemo(() => apiMessageSource(), []);
|
|
10
|
+
* <MailThread source={mail} threadId={row.id} userId={me.id} />
|
|
11
|
+
*/
|
|
12
|
+
export function apiMessageSource(options) {
|
|
13
|
+
const backend = options?.backend ?? getMessageBackend();
|
|
14
|
+
// Each action's method is its endpoint's: these two only say whether a body goes with it.
|
|
15
|
+
const call = (signal) => ({ ...options?.call, ...(signal && { signal }), toast: false });
|
|
16
|
+
const withBody = async (action, body, signal) => unwrapApiResult(await api.send(backend.endpoint(action), { ...call(signal), body }));
|
|
17
|
+
const withoutBody = async (action, signal, params) => unwrapApiResult(await api.send(backend.endpoint(action), { ...call(signal), ...(params && { params }) }));
|
|
18
|
+
const changed = async (action, ids, signal) => backend.readChanged(await withBody(action, backend.markRequest(ids), signal));
|
|
19
|
+
const readFolders = backend.readFolders?.bind(backend);
|
|
20
|
+
return {
|
|
21
|
+
// The optional doors, only where the backend has them: a mail client hides what a source cannot do.
|
|
22
|
+
...(readFolders && backend.supports("folders") && { folders: async (signal) => readFolders(await withoutBody("folders", signal)) }),
|
|
23
|
+
...(backend.supports("markRead") && { markRead: (ids, signal) => changed("markRead", ids, signal) }),
|
|
24
|
+
...(backend.supports("markUnread") && { markUnread: (ids, signal) => changed("markUnread", ids, signal) }),
|
|
25
|
+
...(backend.supports("star") && { star: (ids, starred, signal) => changed(starred ? "star" : "unstar", ids, signal) }),
|
|
26
|
+
async inbox(request) {
|
|
27
|
+
return backend.readInbox(await withBody("inbox", backend.inboxRequest(request), request.signal));
|
|
28
|
+
},
|
|
29
|
+
async thread(id, signal) {
|
|
30
|
+
return backend.readThread(await withoutBody("thread", signal, backend.threadParams(id)));
|
|
31
|
+
},
|
|
32
|
+
threadSource(id) {
|
|
33
|
+
return {
|
|
34
|
+
async history(request) {
|
|
35
|
+
return backend.readHistory(await withBody("history", backend.historyRequest(id, request), request.signal));
|
|
36
|
+
},
|
|
37
|
+
async send(draft, signal) {
|
|
38
|
+
return backend.readReply(await withBody("reply", backend.replyRequest(id, draft), signal), draft);
|
|
39
|
+
},
|
|
40
|
+
async markRead(upTo, signal) {
|
|
41
|
+
await withBody("read", backend.readMarkRequest(id, upTo), signal);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
async compose(draft, signal) {
|
|
46
|
+
return backend.readComposed(await withBody("compose", backend.composeRequest(draft), signal));
|
|
47
|
+
},
|
|
48
|
+
async archive(ids, signal) {
|
|
49
|
+
return backend.readChanged(await withBody("archive", backend.markRequest(ids), signal));
|
|
50
|
+
},
|
|
51
|
+
async unarchive(ids, signal) {
|
|
52
|
+
return backend.readChanged(await withBody("unarchive", backend.markRequest(ids), signal));
|
|
53
|
+
},
|
|
54
|
+
async unread(signal) {
|
|
55
|
+
return backend.readUnread(await withoutBody("unread", signal));
|
|
56
|
+
},
|
|
57
|
+
async people(query) {
|
|
58
|
+
return backend.readPeople(await withBody("people", backend.peopleRequest(query), query.signal));
|
|
59
|
+
},
|
|
60
|
+
async defaults(signal) {
|
|
61
|
+
return backend.readDefaults(await withoutBody("defaults", signal));
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
package/dist/Shared.Index.d.ts
CHANGED
|
@@ -17,12 +17,15 @@ export * from "./Helpers/ChatStore.ts";
|
|
|
17
17
|
export * from "./Helpers/ChatSummaries.ts";
|
|
18
18
|
export * from "./Helpers/ClassHelpers.ts";
|
|
19
19
|
export * from "./Helpers/CodeHelpers.ts";
|
|
20
|
+
export { setAuditActorNames, type AuditActorNamesResolver } from "./Helpers/AuditEntries.ts";
|
|
20
21
|
export * from "./Helpers/CrudBackend.ts";
|
|
21
22
|
export * from "./Helpers/CrudColumns.ts";
|
|
22
23
|
export * from "./Helpers/CrudDetailsTabs.ts";
|
|
23
24
|
export * from "./Helpers/CrudHelpers.ts";
|
|
25
|
+
export * from "./Helpers/DashboardHelpers.ts";
|
|
24
26
|
export * from "./Helpers/DateHelpers.ts";
|
|
25
27
|
export * from "./Helpers/DateTimeHelpers.ts";
|
|
28
|
+
export * from "./Helpers/DomainCards.ts";
|
|
26
29
|
export * from "./Helpers/DropdownHelpers.ts";
|
|
27
30
|
export * from "./Helpers/ExcelBackend.ts";
|
|
28
31
|
export * from "./Helpers/ExcelHelpers.ts";
|
|
@@ -40,9 +43,14 @@ export * from "./Helpers/CrudScopes.ts";
|
|
|
40
43
|
export * from "./Helpers/CrudRowForms.ts";
|
|
41
44
|
export * from "./Helpers/MarkdownEditing.ts";
|
|
42
45
|
export * from "./Helpers/MarkdownHelpers.ts";
|
|
46
|
+
export * from "./Helpers/MailClientHelpers.ts";
|
|
47
|
+
export * from "./Helpers/MessageBackend.ts";
|
|
43
48
|
export * from "./Helpers/MessageBuilder.ts";
|
|
49
|
+
export * from "./Helpers/MessageHelpers.ts";
|
|
50
|
+
export * from "./Helpers/MessageUnread.ts";
|
|
44
51
|
export * from "./Helpers/NepaliCalendar.ts";
|
|
45
52
|
export * from "./Helpers/NumberHelpers.ts";
|
|
53
|
+
export * from "./Helpers/PagedPicker.ts";
|
|
46
54
|
export * from "./Helpers/Permissions.ts";
|
|
47
55
|
export * from "./Helpers/QrCode.ts";
|
|
48
56
|
export * from "./Helpers/RecordCards.ts";
|
|
@@ -55,16 +63,20 @@ export * from "./Helpers/TableJoins.ts";
|
|
|
55
63
|
export * from "./Helpers/TableStore.ts";
|
|
56
64
|
export * from "./Helpers/ThemeHelpers.ts";
|
|
57
65
|
export * from "./Helpers/TimeHelpers.ts";
|
|
66
|
+
export * from "./Helpers/ToolbarFit.ts";
|
|
58
67
|
export * from "./Helpers/TreeStore.ts";
|
|
59
68
|
export * from "./Helpers/ValidationHelpers.ts";
|
|
60
69
|
export * from "./Interfaces/ApiInterfaces.ts";
|
|
61
70
|
export * from "./Interfaces/ChatInterfaces.ts";
|
|
62
71
|
export * from "./Interfaces/CrudInterfaces.ts";
|
|
72
|
+
export * from "./Interfaces/DashboardInterfaces.ts";
|
|
63
73
|
export * from "./Interfaces/DateInterfaces.ts";
|
|
74
|
+
export * from "./Interfaces/DomainInterfaces.ts";
|
|
64
75
|
export * from "./Interfaces/ExcelInterfaces.ts";
|
|
65
76
|
export * from "./Interfaces/FileInterfaces.ts";
|
|
66
77
|
export * from "./Interfaces/FormInterfaces.ts";
|
|
67
78
|
export * from "./Interfaces/InputInterfaces.ts";
|
|
79
|
+
export * from "./Interfaces/MailInterfaces.ts";
|
|
68
80
|
export * from "./Interfaces/MessageInterfaces.ts";
|
|
69
81
|
export * from "./Interfaces/PreferenceInterfaces.ts";
|
|
70
82
|
export * from "./Interfaces/RecordInterfaces.ts";
|