nexus-shared 3.2.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 +2359 -0
- package/README.md +1911 -1123
- package/dist/Client.Index.d.ts +27 -0
- package/dist/Client.Index.js +27 -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/DocumentFrame.d.ts +50 -0
- package/dist/Components/Documents/DocumentFrame.js +83 -0
- package/dist/Components/Documents/Menu.d.ts +4 -0
- package/dist/Components/Documents/Menu.js +40 -24
- package/dist/Components/Documents/PrintDocument.d.ts +95 -0
- package/dist/Components/Documents/PrintDocument.js +62 -0
- package/dist/Components/Documents/TabButtons.d.ts +6 -0
- package/dist/Components/Documents/TabButtons.js +5 -4
- package/dist/Components/Documents/Tabs.d.ts +59 -0
- package/dist/Components/Documents/Tabs.js +70 -0
- package/dist/Components/Domains/DomainCard.d.ts +98 -0
- package/dist/Components/Domains/DomainCard.js +182 -0
- package/dist/Components/Forms/ApiForm.js +24 -3
- package/dist/Components/Forms/Crud.d.ts +2 -2
- package/dist/Components/Forms/Crud.js +628 -247
- package/dist/Components/Forms/CrudActions.d.ts +137 -0
- package/dist/Components/Forms/CrudActions.js +214 -0
- package/dist/Components/Forms/CrudHistory.d.ts +6 -1
- package/dist/Components/Forms/CrudHistory.js +14 -6
- package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
- package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
- package/dist/Components/Forms/CrudParts.d.ts +37 -2
- package/dist/Components/Forms/CrudParts.js +123 -7
- package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
- package/dist/Components/Forms/CrudRowForms.js +174 -0
- package/dist/Components/Forms/CrudScopes.d.ts +28 -0
- package/dist/Components/Forms/CrudScopes.js +76 -0
- package/dist/Components/Forms/CrudTree.d.ts +90 -0
- package/dist/Components/Forms/CrudTree.js +363 -0
- package/dist/Components/Forms/CrudWorkspace.d.ts +73 -0
- package/dist/Components/Forms/CrudWorkspace.js +177 -0
- package/dist/Components/Forms/ExcelImport.js +3 -3
- package/dist/Components/Forms/Form.d.ts +6 -5
- package/dist/Components/Forms/Form.js +134 -21
- package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
- package/dist/Components/Forms/FormLeaveGuard.js +65 -0
- package/dist/Components/Forms/FormQuestion.d.ts +6 -0
- package/dist/Components/Forms/FormQuestion.js +28 -0
- package/dist/Components/Forms/RecordFlows.d.ts +50 -0
- package/dist/Components/Forms/RecordFlows.js +282 -0
- package/dist/Components/Forms/SubmitForm.d.ts +37 -2
- package/dist/Components/Forms/SubmitForm.js +182 -10
- package/dist/Components/Inputs/CheckBox.js +1 -1
- package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
- package/dist/Components/Inputs/ChoiceField.js +28 -2
- package/dist/Components/Inputs/ComputedInput.d.ts +51 -0
- package/dist/Components/Inputs/ComputedInput.js +179 -0
- package/dist/Components/Inputs/Dropdown.d.ts +13 -0
- package/dist/Components/Inputs/Dropdown.js +8 -6
- package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
- package/dist/Components/Inputs/DropdownParts.js +16 -4
- package/dist/Components/Inputs/Field.d.ts +7 -1
- package/dist/Components/Inputs/Field.js +2 -2
- package/dist/Components/Inputs/GroupForm.d.ts +6 -6
- package/dist/Components/Inputs/GroupForm.js +12 -12
- package/dist/Components/Inputs/InputRenderer.d.ts +13 -6
- package/dist/Components/Inputs/InputRenderer.js +37 -32
- package/dist/Components/Inputs/MarkdownEditor.d.ts +6 -0
- package/dist/Components/Inputs/MarkdownEditor.js +32 -6
- package/dist/Components/Inputs/PictureInput.d.ts +41 -0
- package/dist/Components/Inputs/PictureInput.js +75 -0
- package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
- package/dist/Components/Inputs/RadioGroup.js +8 -3
- package/dist/Components/Inputs/RowsInput.d.ts +15 -2
- package/dist/Components/Inputs/RowsInput.js +96 -8
- package/dist/Components/Inputs/TabularForm.d.ts +5 -5
- package/dist/Components/Inputs/TabularForm.js +45 -32
- package/dist/Components/Inputs/TextArea.d.ts +6 -0
- package/dist/Components/Inputs/TextArea.js +11 -2
- package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
- package/dist/Components/Inputs/TextBoxTypes.js +17 -0
- package/dist/Components/Inputs/TreeView.d.ts +76 -2
- package/dist/Components/Inputs/TreeView.js +227 -16
- package/dist/Components/Layouts/MessageBox.d.ts +15 -2
- package/dist/Components/Layouts/MessageBox.js +56 -7
- package/dist/Components/Layouts/Popup.d.ts +34 -7
- package/dist/Components/Layouts/Popup.js +143 -13
- package/dist/Components/Layouts/PopupDock.d.ts +3 -1
- package/dist/Components/Layouts/PopupDock.js +2 -1
- package/dist/Components/Layouts/PopupHost.js +5 -4
- 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 +153 -49
- package/dist/Components/Viewers/DataTableColumns.d.ts +2 -2
- package/dist/Components/Viewers/DataTableColumns.js +2 -1
- package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
- package/dist/Components/Viewers/DataTableFilters.js +17 -2
- package/dist/Components/Viewers/DataTableParts.d.ts +23 -5
- package/dist/Components/Viewers/DataTableParts.js +26 -14
- package/dist/Components/Viewers/OwnerFiles.d.ts +227 -0
- package/dist/Components/Viewers/OwnerFiles.js +700 -0
- package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
- package/dist/Components/Viewers/QrCodeImage.js +15 -0
- package/dist/Components/Viewers/RecordCards.d.ts +16 -0
- package/dist/Components/Viewers/RecordCards.js +509 -0
- 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/BusyHooks.d.ts +6 -0
- package/dist/Helpers/BusyHooks.js +39 -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/CrudBackend.d.ts +20 -2
- package/dist/Helpers/CrudBackend.js +46 -2
- package/dist/Helpers/CrudColumns.d.ts +91 -6
- package/dist/Helpers/CrudColumns.js +148 -7
- package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
- package/dist/Helpers/CrudDetailsTabs.js +126 -0
- package/dist/Helpers/CrudHelpers.d.ts +116 -2
- package/dist/Helpers/CrudHelpers.js +256 -0
- package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
- package/dist/Helpers/CrudNarrowPick.js +157 -0
- package/dist/Helpers/CrudPickerPlan.d.ts +58 -0
- package/dist/Helpers/CrudPickerPlan.js +46 -0
- package/dist/Helpers/CrudRowForms.d.ts +32 -0
- package/dist/Helpers/CrudRowForms.js +76 -0
- package/dist/Helpers/CrudScopes.d.ts +32 -0
- package/dist/Helpers/CrudScopes.js +79 -0
- 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/DropdownHelpers.d.ts +3 -1
- package/dist/Helpers/DropdownHelpers.js +6 -2
- package/dist/Helpers/ExcelHelpers.d.ts +1 -1
- package/dist/Helpers/ExcelHelpers.js +12 -11
- package/dist/Helpers/FileAdapter.d.ts +158 -0
- package/dist/Helpers/FileAdapter.js +173 -0
- package/dist/Helpers/FilePrimaries.d.ts +48 -0
- package/dist/Helpers/FilePrimaries.js +138 -0
- package/dist/Helpers/FormConfirm.d.ts +32 -0
- package/dist/Helpers/FormConfirm.js +40 -0
- package/dist/Helpers/FormStore.d.ts +100 -5
- package/dist/Helpers/FormStore.js +368 -25
- package/dist/Helpers/InputConditions.d.ts +56 -0
- package/dist/Helpers/InputConditions.js +192 -0
- package/dist/Helpers/InputMemory.d.ts +106 -0
- package/dist/Helpers/InputMemory.js +395 -0
- package/dist/Helpers/InputNarrowing.d.ts +50 -0
- package/dist/Helpers/InputNarrowing.js +148 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
- package/dist/Helpers/InputParamsHelpers.js +157 -74
- 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 +71 -0
- package/dist/Helpers/NexusDriveAdapter.js +236 -0
- package/dist/Helpers/NumberHelpers.d.ts +156 -0
- package/dist/Helpers/NumberHelpers.js +242 -0
- package/dist/Helpers/PagedPicker.d.ts +42 -0
- package/dist/Helpers/PagedPicker.js +85 -0
- package/dist/Helpers/Permissions.js +2 -1
- package/dist/Helpers/QrCode.d.ts +26 -0
- package/dist/Helpers/QrCode.js +463 -0
- package/dist/Helpers/RecordCards.d.ts +205 -0
- package/dist/Helpers/RecordCards.js +378 -0
- package/dist/Helpers/RequestCache.d.ts +14 -2
- package/dist/Helpers/RequestCache.js +22 -3
- package/dist/Helpers/RowsStore.d.ts +61 -2
- package/dist/Helpers/RowsStore.js +203 -16
- package/dist/Helpers/ServerPreview.d.ts +69 -0
- package/dist/Helpers/ServerPreview.js +99 -0
- package/dist/Helpers/TableColumns.js +4 -3
- package/dist/Helpers/TableExport.d.ts +7 -1
- package/dist/Helpers/TableExport.js +9 -3
- package/dist/Helpers/TableFilters.d.ts +1 -1
- package/dist/Helpers/TableFilters.js +24 -23
- package/dist/Helpers/TableHelpers.d.ts +38 -4
- package/dist/Helpers/TableHelpers.js +78 -10
- package/dist/Helpers/TableJoins.d.ts +13 -0
- package/dist/Helpers/TableJoins.js +34 -0
- package/dist/Helpers/TableStore.d.ts +28 -4
- package/dist/Helpers/TableStore.js +73 -11
- package/dist/Helpers/ToolbarFit.d.ts +23 -0
- package/dist/Helpers/ToolbarFit.js +30 -0
- package/dist/Helpers/TreeStore.d.ts +156 -6
- package/dist/Helpers/TreeStore.js +547 -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 +539 -6
- 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 +183 -0
- package/dist/Interfaces/FileInterfaces.js +6 -0
- package/dist/Interfaces/FormInterfaces.d.ts +460 -32
- package/dist/Interfaces/FormInterfaces.js +79 -1
- package/dist/Interfaces/InputInterfaces.d.ts +22 -3
- package/dist/Interfaces/MailInterfaces.d.ts +192 -0
- package/dist/Interfaces/MailInterfaces.js +1 -0
- package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
- package/dist/Interfaces/RecordInterfaces.js +1 -0
- package/dist/Interfaces/TableInterfaces.d.ts +102 -3
- package/dist/Services/ExcelApi.js +5 -0
- package/dist/Services/FileApi.d.ts +13 -0
- package/dist/Services/FileApi.js +29 -0
- package/dist/Services/ImageEditorLoader.d.ts +14 -0
- package/dist/Services/ImageEditorLoader.js +51 -0
- package/dist/Services/JoinSources.d.ts +79 -6
- package/dist/Services/JoinSources.js +132 -21
- package/dist/Services/MessageApi.d.ts +18 -0
- package/dist/Services/MessageApi.js +64 -0
- package/dist/Services/ModuleImport.d.ts +7 -0
- package/dist/Services/ModuleImport.js +22 -0
- package/dist/Shared.Index.d.ts +28 -0
- package/dist/Shared.Index.js +28 -0
- package/package.json +13 -2
- package/src/Styles/Nexus.Avatar.css +6 -5
- package/src/Styles/Nexus.Button.css +39 -4
- package/src/Styles/Nexus.Chat.css +114 -22
- package/src/Styles/Nexus.Choice.css +16 -0
- package/src/Styles/Nexus.Crud.css +486 -21
- package/src/Styles/Nexus.Dashboard.css +763 -0
- package/src/Styles/Nexus.Domain.css +384 -0
- package/src/Styles/Nexus.Dropdown.css +8 -0
- package/src/Styles/Nexus.Editor.css +20 -0
- package/src/Styles/Nexus.Files.css +437 -0
- package/src/Styles/Nexus.Form.css +5 -2
- package/src/Styles/Nexus.Index.css +7 -0
- package/src/Styles/Nexus.Input.css +73 -0
- package/src/Styles/Nexus.Mail.css +1062 -0
- package/src/Styles/Nexus.Menu.css +10 -1
- package/src/Styles/Nexus.Popup.css +135 -1
- package/src/Styles/Nexus.Print.css +288 -0
- package/src/Styles/Nexus.QrCode.css +12 -0
- package/src/Styles/Nexus.Record.css +430 -0
- package/src/Styles/Nexus.Tab.Buttons.css +87 -22
- package/src/Styles/Nexus.Table.css +1458 -1387
- package/src/Styles/Nexus.Theme.Picker.css +1 -0
- package/src/Styles/Nexus.Themes.css +12 -6
- package/src/Styles/Nexus.Tokens.css +8 -3
- package/src/Styles/Nexus.Tree.css +41 -1
package/dist/Client.Index.d.ts
CHANGED
|
@@ -3,15 +3,27 @@ export * from "./Components/Chats/ChatButton.tsx";
|
|
|
3
3
|
export * from "./Components/Chats/ChatComposer.tsx";
|
|
4
4
|
export * from "./Components/Chats/ChatConversations.tsx";
|
|
5
5
|
export * from "./Components/Chats/ChatPanel.tsx";
|
|
6
|
+
export * from "./Components/Chats/ChatPopup.tsx";
|
|
6
7
|
export * from "./Components/Chats/ChatParts.tsx";
|
|
7
8
|
export * from "./Components/Chats/ChatThread.tsx";
|
|
9
|
+
export * from "./Components/Dashboards/DashboardBoard.tsx";
|
|
10
|
+
export * from "./Components/Dashboards/DashboardWidgets.tsx";
|
|
11
|
+
export * from "./Components/Dashboards/WidgetFrame.tsx";
|
|
12
|
+
export * from "./Components/Documents/DocumentFrame.tsx";
|
|
13
|
+
export * from "./Components/Domains/DomainCard.tsx";
|
|
8
14
|
export * from "./Components/Documents/Menu.tsx";
|
|
9
15
|
export * from "./Components/Documents/SplitButton.tsx";
|
|
10
16
|
export * from "./Components/Documents/TabButtons.tsx";
|
|
17
|
+
export * from "./Components/Documents/Tabs.tsx";
|
|
11
18
|
export * from "./Components/Forms/ApiForm.tsx";
|
|
12
19
|
export * from "./Components/Forms/Crud.tsx";
|
|
20
|
+
export { openCrudForm, type CrudFormRequest } from "./Components/Forms/CrudActions.tsx";
|
|
13
21
|
export * from "./Components/Forms/CrudHistory.tsx";
|
|
22
|
+
export * from "./Components/Forms/CrudNarrowPicker.tsx";
|
|
14
23
|
export * from "./Components/Forms/CrudParts.tsx";
|
|
24
|
+
export * from "./Components/Forms/CrudRowForms.tsx";
|
|
25
|
+
export * from "./Components/Forms/CrudScopes.tsx";
|
|
26
|
+
export * from "./Components/Forms/CrudTree.tsx";
|
|
15
27
|
export * from "./Components/Forms/ExcelImport.tsx";
|
|
16
28
|
export * from "./Components/Forms/Form.tsx";
|
|
17
29
|
export * from "./Components/Forms/SubmitForm.tsx";
|
|
@@ -27,6 +39,7 @@ export * from "./Components/Inputs/GroupForm.tsx";
|
|
|
27
39
|
export * from "./Components/Inputs/InputRenderer.tsx";
|
|
28
40
|
export * from "./Components/Inputs/MarkdownEditor.tsx";
|
|
29
41
|
export * from "./Components/Inputs/NumberBox.tsx";
|
|
42
|
+
export * from "./Components/Inputs/PictureInput.tsx";
|
|
30
43
|
export * from "./Components/Inputs/RadioGroup.tsx";
|
|
31
44
|
export * from "./Components/Inputs/ReadOnlyNotice.tsx";
|
|
32
45
|
export * from "./Components/Inputs/RichTextEditor.tsx";
|
|
@@ -45,17 +58,31 @@ export * from "./Components/Layouts/PopupRules.ts";
|
|
|
45
58
|
export * from "./Components/Layouts/ThemePicker.tsx";
|
|
46
59
|
export * from "./Components/Layouts/ThemeSwitcher.tsx";
|
|
47
60
|
export * from "./Components/Layouts/Toaster.tsx";
|
|
61
|
+
export * from "./Components/Messages/MailClient.tsx";
|
|
62
|
+
export * from "./Components/Messages/MailColumns.tsx";
|
|
63
|
+
export * from "./Components/Messages/MailCompose.tsx";
|
|
64
|
+
export * from "./Components/Messages/MailReader.tsx";
|
|
65
|
+
export * from "./Components/Messages/MailThread.tsx";
|
|
66
|
+
export * from "./Components/Messages/MessageBell.tsx";
|
|
48
67
|
export * from "./Components/Viewers/Avatar.tsx";
|
|
49
68
|
export * from "./Components/Viewers/DataTable.tsx";
|
|
50
69
|
export * from "./Components/Viewers/DataTableParts.tsx";
|
|
70
|
+
export * from "./Components/Viewers/OwnerFiles.tsx";
|
|
71
|
+
export * from "./Components/Viewers/RecordCards.tsx";
|
|
51
72
|
export * from "./Helpers/ChatHooks.ts";
|
|
52
73
|
export * from "./Helpers/DatePreferences.ts";
|
|
53
74
|
export * from "./Helpers/DragHelpers.ts";
|
|
54
75
|
export * from "./Helpers/PopoverHelpers.ts";
|
|
55
76
|
export * from "./Helpers/PopoverHooks.ts";
|
|
77
|
+
export * from "./Helpers/ServerPreview.ts";
|
|
78
|
+
export { downloadText } from "./Helpers/TableExport.ts";
|
|
56
79
|
export * from "./Helpers/UserPreferences.ts";
|
|
57
80
|
export * from "./Services/BrowserApi.ts";
|
|
58
81
|
export * from "./Services/ChatApi.ts";
|
|
59
82
|
export * from "./Services/ExcelApi.ts";
|
|
83
|
+
export * from "./Services/FileApi.ts";
|
|
84
|
+
export * from "./Services/ImageEditorLoader.ts";
|
|
85
|
+
export * from "./Services/ModuleImport.ts";
|
|
60
86
|
export * from "./Services/JoinSources.ts";
|
|
87
|
+
export * from "./Services/MessageApi.ts";
|
|
61
88
|
export * from "./Services/ThemeService.ts";
|
package/dist/Client.Index.js
CHANGED
|
@@ -6,15 +6,27 @@ export * from "./Components/Chats/ChatButton.js";
|
|
|
6
6
|
export * from "./Components/Chats/ChatComposer.js";
|
|
7
7
|
export * from "./Components/Chats/ChatConversations.js";
|
|
8
8
|
export * from "./Components/Chats/ChatPanel.js";
|
|
9
|
+
export * from "./Components/Chats/ChatPopup.js";
|
|
9
10
|
export * from "./Components/Chats/ChatParts.js";
|
|
10
11
|
export * from "./Components/Chats/ChatThread.js";
|
|
12
|
+
export * from "./Components/Dashboards/DashboardBoard.js";
|
|
13
|
+
export * from "./Components/Dashboards/DashboardWidgets.js";
|
|
14
|
+
export * from "./Components/Dashboards/WidgetFrame.js";
|
|
15
|
+
export * from "./Components/Documents/DocumentFrame.js";
|
|
16
|
+
export * from "./Components/Domains/DomainCard.js";
|
|
11
17
|
export * from "./Components/Documents/Menu.js";
|
|
12
18
|
export * from "./Components/Documents/SplitButton.js";
|
|
13
19
|
export * from "./Components/Documents/TabButtons.js";
|
|
20
|
+
export * from "./Components/Documents/Tabs.js";
|
|
14
21
|
export * from "./Components/Forms/ApiForm.js";
|
|
15
22
|
export * from "./Components/Forms/Crud.js";
|
|
23
|
+
export { openCrudForm } from "./Components/Forms/CrudActions.js";
|
|
16
24
|
export * from "./Components/Forms/CrudHistory.js";
|
|
25
|
+
export * from "./Components/Forms/CrudNarrowPicker.js";
|
|
17
26
|
export * from "./Components/Forms/CrudParts.js";
|
|
27
|
+
export * from "./Components/Forms/CrudRowForms.js";
|
|
28
|
+
export * from "./Components/Forms/CrudScopes.js";
|
|
29
|
+
export * from "./Components/Forms/CrudTree.js";
|
|
18
30
|
export * from "./Components/Forms/ExcelImport.js";
|
|
19
31
|
export * from "./Components/Forms/Form.js";
|
|
20
32
|
export * from "./Components/Forms/SubmitForm.js";
|
|
@@ -30,6 +42,7 @@ export * from "./Components/Inputs/GroupForm.js";
|
|
|
30
42
|
export * from "./Components/Inputs/InputRenderer.js";
|
|
31
43
|
export * from "./Components/Inputs/MarkdownEditor.js";
|
|
32
44
|
export * from "./Components/Inputs/NumberBox.js";
|
|
45
|
+
export * from "./Components/Inputs/PictureInput.js";
|
|
33
46
|
export * from "./Components/Inputs/RadioGroup.js";
|
|
34
47
|
export * from "./Components/Inputs/ReadOnlyNotice.js";
|
|
35
48
|
export * from "./Components/Inputs/RichTextEditor.js";
|
|
@@ -48,19 +61,33 @@ export * from "./Components/Layouts/PopupRules.js";
|
|
|
48
61
|
export * from "./Components/Layouts/ThemePicker.js";
|
|
49
62
|
export * from "./Components/Layouts/ThemeSwitcher.js";
|
|
50
63
|
export * from "./Components/Layouts/Toaster.js";
|
|
64
|
+
export * from "./Components/Messages/MailClient.js";
|
|
65
|
+
export * from "./Components/Messages/MailColumns.js";
|
|
66
|
+
export * from "./Components/Messages/MailCompose.js";
|
|
67
|
+
export * from "./Components/Messages/MailReader.js";
|
|
68
|
+
export * from "./Components/Messages/MailThread.js";
|
|
69
|
+
export * from "./Components/Messages/MessageBell.js";
|
|
51
70
|
export * from "./Components/Viewers/Avatar.js";
|
|
52
71
|
export * from "./Components/Viewers/DataTable.js";
|
|
53
72
|
export * from "./Components/Viewers/DataTableParts.js";
|
|
73
|
+
export * from "./Components/Viewers/OwnerFiles.js";
|
|
74
|
+
export * from "./Components/Viewers/RecordCards.js";
|
|
54
75
|
// Helpers
|
|
55
76
|
export * from "./Helpers/ChatHooks.js";
|
|
56
77
|
export * from "./Helpers/DatePreferences.js";
|
|
57
78
|
export * from "./Helpers/DragHelpers.js";
|
|
58
79
|
export * from "./Helpers/PopoverHelpers.js";
|
|
59
80
|
export * from "./Helpers/PopoverHooks.js";
|
|
81
|
+
export * from "./Helpers/ServerPreview.js";
|
|
82
|
+
export { downloadText } from "./Helpers/TableExport.js";
|
|
60
83
|
export * from "./Helpers/UserPreferences.js";
|
|
61
84
|
// Services
|
|
62
85
|
export * from "./Services/BrowserApi.js";
|
|
63
86
|
export * from "./Services/ChatApi.js";
|
|
64
87
|
export * from "./Services/ExcelApi.js";
|
|
88
|
+
export * from "./Services/FileApi.js";
|
|
89
|
+
export * from "./Services/ImageEditorLoader.js";
|
|
90
|
+
export * from "./Services/ModuleImport.js";
|
|
65
91
|
export * from "./Services/JoinSources.js";
|
|
92
|
+
export * from "./Services/MessageApi.js";
|
|
66
93
|
export * from "./Services/ThemeService.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
1
2
|
import type { ChatSnapshot, ChatStore } from "../../Helpers/ChatStore.ts";
|
|
2
3
|
import type { ChatMessage, ChatParticipant } from "../../Interfaces/ChatInterfaces.ts";
|
|
3
4
|
import { type MarkdownToolbarItem } from "../Inputs/MarkdownEditor.tsx";
|
|
@@ -32,10 +33,15 @@ export interface ChatComposerProps {
|
|
|
32
33
|
height?: number;
|
|
33
34
|
/** Why the user may not write here. */
|
|
34
35
|
readOnlyMessage?: string;
|
|
36
|
+
/**
|
|
37
|
+
* What the writer should know before sending, in a line over the writing area that the writing area is described by:
|
|
38
|
+
* "You were sent a blind copy. Replying shows you to everyone."
|
|
39
|
+
*/
|
|
40
|
+
notice?: ReactNode;
|
|
35
41
|
/** Most characters in one message. Default 4,000. */
|
|
36
42
|
maxLength?: number;
|
|
37
43
|
autoFocus?: boolean;
|
|
38
44
|
className?: string;
|
|
39
45
|
}
|
|
40
46
|
/** Writes and sends a message, in Markdown. */
|
|
41
|
-
export declare function ChatComposer({ store, state, draftKey, placeholder, files, replyTo, onCancelReply, sendKey, mentions, mentionHref, toolbar, height, readOnlyMessage, maxLength, autoFocus, className, }: ChatComposerProps): import("react").JSX.Element;
|
|
47
|
+
export declare function ChatComposer({ store, state, draftKey, placeholder, files, replyTo, onCancelReply, sendKey, mentions, mentionHref, toolbar, height, readOnlyMessage, notice, maxLength, autoFocus, className, }: ChatComposerProps): import("react").JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { iconPaperclip, iconSend, iconX, InlineIcon } from "nexus-icons";
|
|
4
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { iconInfoCircle, iconPaperclip, iconSend, iconX, InlineIcon } from "nexus-icons";
|
|
4
|
+
import { useCallback, useEffect, useId, useRef, useState } from "react";
|
|
5
5
|
import { applyChatMention, chatFileSize, findChatMention, matchChatMentions, readChatMentions } from "../../Helpers/ChatHelpers.js";
|
|
6
6
|
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
7
7
|
import { continueMarkdownList } from "../../Helpers/MarkdownEditing.js";
|
|
@@ -54,8 +54,9 @@ function insideCodeFence(value, caret) {
|
|
|
54
54
|
return (before.match(/^```/gm) ?? []).length % 2 === 1;
|
|
55
55
|
}
|
|
56
56
|
/** Writes and sends a message, in Markdown. */
|
|
57
|
-
export function ChatComposer({ store, state, draftKey, placeholder = "Write a message…", files = false, replyTo, onCancelReply, sendKey = "enter", mentions, mentionHref, toolbar = CHAT_TOOLBAR, height = 84, readOnlyMessage = "You can read this conversation, but not write in it.", maxLength = 4000, autoFocus = false, className, }) {
|
|
57
|
+
export function ChatComposer({ store, state, draftKey, placeholder = "Write a message…", files = false, replyTo, onCancelReply, sendKey = "enter", mentions, mentionHref, toolbar = CHAT_TOOLBAR, height = 84, readOnlyMessage = "You can read this conversation, but not write in it.", notice, maxLength = 4000, autoFocus = false, className, }) {
|
|
58
58
|
const boxRef = useRef(null);
|
|
59
|
+
const noticeId = useId();
|
|
59
60
|
const fileRef = useRef(null);
|
|
60
61
|
// The text lives here so Send, the draft, and clearing after a send all read the same thing. A message is short, so
|
|
61
62
|
// a render per keystroke costs nothing — unlike the long documents the editor is otherwise built for.
|
|
@@ -169,9 +170,11 @@ export function ChatComposer({ store, state, draftKey, placeholder = "Write a me
|
|
|
169
170
|
return (_jsx("div", { className: cx("nx-chat-composer nx-chat-composer--readonly", className), children: _jsx("p", { children: readOnlyMessage }) }));
|
|
170
171
|
}
|
|
171
172
|
const empty = text.trim() === "" && picked.length === 0;
|
|
172
|
-
return (_jsxs("div", { className: cx("nx-chat-composer", className), children: [replyTo && (_jsxs("div", { className: "nx-chat-composer__reply", children: [_jsxs("span", { className: "nx-chat-composer__reply-text", children: [_jsx("span", { className: "nx-chat-composer__reply-author", children: replyTo.authorName || "Unknown" }), _jsx("span", { children: markdownToPlainText(replyTo.body) })] }), _jsx("button", { type: "button", onClick: onCancelReply, "aria-label": "Stop replying", children: _jsx(InlineIcon, { icon: iconX }) })] })), picked.length > 0 && (_jsx("ul", { className: "nx-chat-composer__files", children: picked.map((file, index) => (_jsxs("li", { children: [_jsx("span", { className: "nx-chat-composer__file-name", children: file.name }), _jsx("span", { className: "nx-chat-composer__file-size", children: chatFileSize(file.size) }), _jsx("button", { type: "button", onClick: () => setPicked(held => held.filter((_, at) => at !== index)), "aria-label": `Remove ${file.name}`, children: _jsx(InlineIcon, { icon: iconX }) })] }, `${file.name}-${index}`))) })), picking && (_jsx("ul", { className: "nx-chat-mentions", role: "listbox", "aria-label": "People", children: offered.map((person, index) => (_jsx("li", { children: _jsxs("button", { type: "button", className: cx("nx-chat-mentions__item", index === at && "is-at"), role: "option", "aria-selected": index === at,
|
|
173
|
+
return (_jsxs("div", { className: cx("nx-chat-composer", className), children: [notice && (_jsxs("p", { className: "nx-chat-composer__notice", id: noticeId, children: [_jsx(InlineIcon, { icon: iconInfoCircle }), _jsx("span", { children: notice })] })), replyTo && (_jsxs("div", { className: "nx-chat-composer__reply", children: [_jsxs("span", { className: "nx-chat-composer__reply-text", children: [_jsx("span", { className: "nx-chat-composer__reply-author", children: replyTo.authorName || "Unknown" }), _jsx("span", { children: markdownToPlainText(replyTo.body) })] }), _jsx("button", { type: "button", onClick: onCancelReply, "aria-label": "Stop replying", children: _jsx(InlineIcon, { icon: iconX }) })] })), picked.length > 0 && (_jsx("ul", { className: "nx-chat-composer__files", children: picked.map((file, index) => (_jsxs("li", { children: [_jsx("span", { className: "nx-chat-composer__file-name", children: file.name }), _jsx("span", { className: "nx-chat-composer__file-size", children: chatFileSize(file.size) }), _jsx("button", { type: "button", onClick: () => setPicked(held => held.filter((_, at) => at !== index)), "aria-label": `Remove ${file.name}`, children: _jsx(InlineIcon, { icon: iconX }) })] }, `${file.name}-${index}`))) })), picking && (_jsx("ul", { className: "nx-chat-mentions", role: "listbox", "aria-label": "People", children: offered.map((person, index) => (_jsx("li", { children: _jsxs("button", { type: "button", className: cx("nx-chat-mentions__item", index === at && "is-at"), role: "option", "aria-selected": index === at,
|
|
173
174
|
// The editor must not lose the caret before the name goes in.
|
|
174
|
-
onMouseDown: event => event.preventDefault(), onMouseEnter: () => setAt(index), onClick: () => putMention(person), children: [_jsx(ChatAvatar, { name: person.name, src: person.avatar, size: "sm" }), _jsx("span", { className: "nx-chat-mentions__name", children: person.name }), person.role && _jsx("span", { className: "nx-chat-mentions__role", children: person.role })] }) }, String(person.id)))) })), _jsx(MarkdownEditor, { ref: boxRef, className: "nx-chat-composer__editor", "aria-label": "Message", placeholder: placeholder, value: text, onValueChange: change, onKeyDown: keyDown,
|
|
175
|
+
onMouseDown: event => event.preventDefault(), onMouseEnter: () => setAt(index), onClick: () => putMention(person), children: [_jsx(ChatAvatar, { name: person.name, src: person.avatar, size: "sm" }), _jsx("span", { className: "nx-chat-mentions__name", children: person.name }), person.role && _jsx("span", { className: "nx-chat-mentions__role", children: person.role })] }) }, String(person.id)))) })), _jsx(MarkdownEditor, { ref: boxRef, className: "nx-chat-composer__editor", "aria-label": "Message", "aria-describedby": notice ? noticeId : undefined, placeholder: placeholder, value: text, onValueChange: change, onKeyDown: keyDown,
|
|
175
176
|
// Moving the caret away from an `@…` closes the list, as clicking elsewhere in the text does.
|
|
176
|
-
onSelect: () => setMention(mentions ? findChatMention(boxRef.current?.value ?? "", boxRef.current?.selectionStart ?? 0) : null), onBlur: () => setMention(null), modes: ["write", "preview"], toolbar: toolbar,
|
|
177
|
+
onSelect: () => setMention(mentions ? findChatMention(boxRef.current?.value ?? "", boxRef.current?.selectionStart ?? 0) : null), onBlur: () => setMention(null), modes: ["write", "preview"], toolbar: toolbar,
|
|
178
|
+
// One row on a phone too: the buttons that do not fit go into a menu, so the thread above keeps its room.
|
|
179
|
+
toolbarOverflow: "menu", height: height, minHeight: 72, maxHeight: 360, heightKey: "nx-chat", showCount: false, maxLength: maxLength }), _jsxs("div", { className: "nx-chat-composer__row", children: [_jsx("span", { className: "nx-chat-composer__hint", children: sendKey === "enter" ? "Enter sends · Shift+Enter for a line" : "Ctrl+Enter sends" }), files && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "nx-chat-composer__attach", onClick: () => fileRef.current?.click(), "aria-label": "Attach a file", title: "Attach a file", children: _jsx(InlineIcon, { icon: iconPaperclip }) }), _jsx("input", { ref: fileRef, type: "file", multiple: true, hidden: true, onChange: addFiles })] })), _jsxs("button", { type: "button", className: "nx-chat-composer__send", onClick: send, disabled: empty, "aria-label": "Send", title: sendKey === "enter" ? "Send (Enter)" : "Send (Ctrl+Enter)", children: [_jsx(InlineIcon, { icon: iconSend }), _jsx("span", { children: "Send" })] })] })] }));
|
|
177
180
|
}
|
|
@@ -40,6 +40,15 @@ export declare function ChatSystemNote({ message, time }: {
|
|
|
40
40
|
message: ChatMessage;
|
|
41
41
|
time: ChatTimeOptions;
|
|
42
42
|
}): import("react").JSX.Element;
|
|
43
|
+
/**
|
|
44
|
+
* A notification (`kind: "notification"`): what an app wrote about an event, under the name of the person who acted -
|
|
45
|
+
* "Approved at Supervisor. Now with Accounts." It is a quiet line across the thread, not a bubble on a side: the bell,
|
|
46
|
+
* who acted and when, and the words under them, so a thread of replies reads around its events.
|
|
47
|
+
*/
|
|
48
|
+
export declare function ChatEventNote({ message, time }: {
|
|
49
|
+
message: ChatMessage;
|
|
50
|
+
time: ChatTimeOptions;
|
|
51
|
+
}): import("react").JSX.Element;
|
|
43
52
|
export interface ChatMessageProps {
|
|
44
53
|
message: ChatMessage;
|
|
45
54
|
/** This user wrote it: it sits on the right. */
|
|
@@ -76,10 +85,14 @@ export declare function ChatEmpty({ message, hint }: {
|
|
|
76
85
|
message?: string;
|
|
77
86
|
hint?: ReactNode;
|
|
78
87
|
}): import("react").JSX.Element;
|
|
79
|
-
/**
|
|
80
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Something that did not load, with a way to try again. `flat`: its tint without an edge, for a failure that already
|
|
90
|
+
* sits in a framed panel (a dashboard widget).
|
|
91
|
+
*/
|
|
92
|
+
export declare function ChatFailure({ message, onRetry, flat }: {
|
|
81
93
|
message: string;
|
|
82
94
|
onRetry?: () => void;
|
|
95
|
+
flat?: boolean;
|
|
83
96
|
}): import("react").JSX.Element;
|
|
84
97
|
/** Messages on their way in. */
|
|
85
98
|
export declare function ChatLoading({ label }: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { iconAlertCircle, iconCheck, iconChecks, iconCornerUpLeft, iconFile, iconLoader2, iconRefresh, iconTrash, iconX, InlineIcon } from "nexus-icons";
|
|
3
|
+
import { iconAlertCircle, iconBell, iconCheck, iconChecks, iconCornerUpLeft, iconFile, iconLoader2, iconRefresh, iconTrash, iconX, InlineIcon } from "nexus-icons";
|
|
4
4
|
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
5
5
|
import { chatDeliveryState, chatFileSize, chatTimeLabel, sameChatId, CHAT_DELIVERY_LABELS } from "../../Helpers/ChatHelpers.js";
|
|
6
6
|
import { markdownToPlainText } from "../../Helpers/MarkdownHelpers.js";
|
|
@@ -57,6 +57,14 @@ export function ChatQuote({ message, onOpen }) {
|
|
|
57
57
|
export function ChatSystemNote({ message, time }) {
|
|
58
58
|
return (_jsxs("div", { className: "nx-chat-note", children: [_jsx("span", { children: markdownToPlainText(message.body) }), _jsx("time", { dateTime: message.createdAt, children: chatTimeLabel(message.createdAt, time) })] }));
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* A notification (`kind: "notification"`): what an app wrote about an event, under the name of the person who acted -
|
|
62
|
+
* "Approved at Supervisor. Now with Accounts." It is a quiet line across the thread, not a bubble on a side: the bell,
|
|
63
|
+
* who acted and when, and the words under them, so a thread of replies reads around its events.
|
|
64
|
+
*/
|
|
65
|
+
export function ChatEventNote({ message, time }) {
|
|
66
|
+
return (_jsxs("div", { className: "nx-chat-event", children: [_jsx(InlineIcon, { icon: iconBell, className: "nx-chat-event__icon" }), _jsxs("div", { className: "nx-chat-event__text", children: [_jsxs("span", { className: "nx-chat-event__meta", children: [_jsx("span", { className: "nx-chat-event__author", children: message.authorName || "Unknown" }), _jsx("time", { dateTime: message.createdAt, children: chatTimeLabel(message.createdAt, time) })] }), message.body && _jsx(MarkdownView, { className: "nx-chat-event__body", value: message.body })] })] }));
|
|
67
|
+
}
|
|
60
68
|
/**
|
|
61
69
|
* One message. Its own messages sit on the right in the primary tint, everyone else's on the left on a surface. The
|
|
62
70
|
* actions are one row of icon buttons that appear on hover or keyboard focus — and never move the message, so a long
|
|
@@ -86,9 +94,12 @@ export function ChatParticipantList({ participants, userId, empty = "Nobody else
|
|
|
86
94
|
export function ChatEmpty({ message = "No messages yet.", hint }) {
|
|
87
95
|
return (_jsxs("div", { className: "nx-chat-empty", children: [_jsx("p", { children: message }), hint && _jsx("p", { className: "nx-chat-empty__hint", children: hint })] }));
|
|
88
96
|
}
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Something that did not load, with a way to try again. `flat`: its tint without an edge, for a failure that already
|
|
99
|
+
* sits in a framed panel (a dashboard widget).
|
|
100
|
+
*/
|
|
101
|
+
export function ChatFailure({ message, onRetry, flat = false }) {
|
|
102
|
+
return (_jsxs("div", { className: flat ? "nx-chat-failure nx-chat-failure--flat" : "nx-chat-failure", role: "alert", children: [_jsx(InlineIcon, { icon: iconAlertCircle }), _jsx("span", { children: message }), onRetry && (_jsxs("button", { type: "button", onClick: onRetry, children: [_jsx(InlineIcon, { icon: iconRefresh }), " Try again"] }))] }));
|
|
92
103
|
}
|
|
93
104
|
/** Messages on their way in. */
|
|
94
105
|
export function ChatLoading({ label = "Loading…" }) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { ChatId, ChatMessage, ChatSource, ChatSummary } from "../../Interfaces/ChatInterfaces.ts";
|
|
3
|
+
import { type PopupSize, type PopupWindowState } from "../Layouts/Popup.tsx";
|
|
4
|
+
export interface ChatPopupProps {
|
|
5
|
+
open: boolean;
|
|
6
|
+
/** Asked to close: its close button, Esc inside it, or its bar on the dock. */
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
/** Where the lines come from: a thread's discussion (`mailDiscussionSource(mail.threadSource(id))`), a chat's source. */
|
|
9
|
+
source: ChatSource;
|
|
10
|
+
/** The conversation: keys its lines, so two popups on one conversation never share a store by accident. */
|
|
11
|
+
conversationId: ChatId;
|
|
12
|
+
/** Who is reading: their lines sit on the right. */
|
|
13
|
+
userId: ChatId;
|
|
14
|
+
/** The title bar: the chat's name, "Discussion: Cable stock". */
|
|
15
|
+
title: ReactNode;
|
|
16
|
+
/** Muted after the title, and under it on the dock's bar: the thread it belongs to, the people. */
|
|
17
|
+
description?: ReactNode;
|
|
18
|
+
/** Its icon. Default the chat's. */
|
|
19
|
+
icon?: ReactNode;
|
|
20
|
+
/** What an empty conversation says. Default "No messages yet." */
|
|
21
|
+
emptyMessage?: string;
|
|
22
|
+
/** The reply box's placeholder. Default "Write a message…". */
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
/** Where the unsent text is kept. Default `chat-popup:<conversation>:<reader>`. */
|
|
25
|
+
draftKey?: string;
|
|
26
|
+
/** Width: `xs` 360, `sm` 440 (default), `md` 560. */
|
|
27
|
+
size?: Extract<PopupSize, "xs" | "sm" | "md">;
|
|
28
|
+
/** Height, a CSS length. Default `30rem`, never taller than the window. */
|
|
29
|
+
height?: string;
|
|
30
|
+
/** The minimise button. Default true. */
|
|
31
|
+
minimizable?: boolean;
|
|
32
|
+
/** The window state, when the page controls it. */
|
|
33
|
+
windowState?: PopupWindowState;
|
|
34
|
+
onWindowStateChange?: (state: PopupWindowState) => void;
|
|
35
|
+
/** Every time the counts change, a read included: a list's row follows it. */
|
|
36
|
+
onSummary?: (summary: ChatSummary) => void;
|
|
37
|
+
/** After a line of the reader's own is saved. */
|
|
38
|
+
onSent?: (message: ChatMessage) => void;
|
|
39
|
+
/** Lines per page. Default 30. */
|
|
40
|
+
pageSize?: number;
|
|
41
|
+
/** Milliseconds between checks for new lines. 0 turns it off. Default 15,000. */
|
|
42
|
+
pollInterval?: number;
|
|
43
|
+
className?: string;
|
|
44
|
+
}
|
|
45
|
+
/** A conversation in a docked, minimisable window. */
|
|
46
|
+
export declare function ChatPopup({ open, onClose, source, conversationId, userId, title, description, icon, emptyMessage, placeholder, draftKey, size, height, minimizable, windowState, onWindowStateChange, onSummary, onSent, pageSize, pollInterval, className, }: ChatPopupProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { iconMessageCircle, InlineIcon } from "nexus-icons";
|
|
4
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { useChat } from "../../Helpers/ChatHooks.js";
|
|
6
|
+
import { Popup } from "../Layouts/Popup.js";
|
|
7
|
+
import { ChatComposer } from "./ChatComposer.js";
|
|
8
|
+
import { ChatThread } from "./ChatThread.js";
|
|
9
|
+
/** A conversation in a docked, minimisable window. */
|
|
10
|
+
export function ChatPopup({ open, onClose, source, conversationId, userId, title, description, icon, emptyMessage, placeholder = "Write a message…", draftKey, size = "sm", height = "30rem", minimizable = true, windowState, onWindowStateChange, onSummary, onSent, pageSize, pollInterval, className, }) {
|
|
11
|
+
const [replyTo, setReplyTo] = useState(null);
|
|
12
|
+
// Its own place for the store: the thread drawn as mail behind it reads every line, the popup its discussion.
|
|
13
|
+
const scope = useMemo(() => ({ domain: "chat-popup", parentId: conversationId, userId }), [conversationId, userId]);
|
|
14
|
+
const { store, state } = useChat({ scope, conversationId, source, pageSize, pollInterval, enabled: open });
|
|
15
|
+
const latest = useRef({ onSummary, onSent });
|
|
16
|
+
latest.current = { onSummary, onSent };
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (state.loaded)
|
|
19
|
+
latest.current.onSummary?.(state.summary);
|
|
20
|
+
}, [state.loaded, state.summary]);
|
|
21
|
+
// A line of the reader's own, saved: the page may count it (a Discuss button's number).
|
|
22
|
+
const known = useRef(new Set());
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
for (const message of state.messages) {
|
|
25
|
+
const key = String(message.clientId ?? message.id);
|
|
26
|
+
if (message.status !== "sent" || known.current.has(key))
|
|
27
|
+
continue;
|
|
28
|
+
known.current.add(key);
|
|
29
|
+
if (message.clientId && String(message.authorId) === String(userId))
|
|
30
|
+
latest.current.onSent?.(message);
|
|
31
|
+
}
|
|
32
|
+
}, [state.messages, userId]);
|
|
33
|
+
return (_jsx(Popup, { open: open, onClose: onClose, title: title, description: description, icon: icon ?? _jsx(InlineIcon, { icon: iconMessageCircle }), size: size, height: height, docked: true, minimizable: minimizable, movable: false, reopen: "restore", flush: true, windowState: windowState, onWindowStateChange: onWindowStateChange, className: className, children: _jsxs("div", { className: "nx-chat-panel nx-chat-popup", children: [_jsx("div", { className: "nx-chat-panel__body", children: _jsx(ChatThread, { store: store, state: state, userId: userId, onReply: setReplyTo, emptyMessage: emptyMessage }) }), _jsx(ChatComposer, { store: store, state: state, draftKey: draftKey ?? `chat-popup:${String(conversationId)}:${String(userId)}`, placeholder: placeholder, replyTo: replyTo, onCancelReply: () => setReplyTo(null), autoFocus: true })] }) }));
|
|
34
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
1
2
|
import { type ChatTimeOptions } from "../../Helpers/ChatHelpers.ts";
|
|
2
3
|
import type { ChatSnapshot, ChatStore } from "../../Helpers/ChatStore.ts";
|
|
3
4
|
import type { ChatId, ChatMessage } from "../../Interfaces/ChatInterfaces.ts";
|
|
@@ -16,7 +17,14 @@ export interface ChatThreadProps {
|
|
|
16
17
|
marksRead?: boolean;
|
|
17
18
|
/** Shown when there is not a single message. */
|
|
18
19
|
emptyMessage?: string;
|
|
20
|
+
/**
|
|
21
|
+
* What the conversation opens with, above its first message and scrolling with it - a mail thread's subject, people
|
|
22
|
+
* and record - so a tall head is read in the thread's one scroll, never in a box of its own. With one, the thread opens
|
|
23
|
+
* at its top, or at the first unread line when that would be out of view, and older pages load from the "Earlier
|
|
24
|
+
* messages" button only, so the lead stays where the reader scrolled it.
|
|
25
|
+
*/
|
|
26
|
+
lead?: ReactNode;
|
|
19
27
|
className?: string;
|
|
20
28
|
}
|
|
21
29
|
/** The messages of a conversation, in days and runs, with the lines that break them up. */
|
|
22
|
-
export declare function ChatThread({ store, state, userId, time, replies, onReply, marksRead, emptyMessage, className }: ChatThreadProps): import("react").JSX.Element;
|
|
30
|
+
export declare function ChatThread({ store, state, userId, time, replies, onReply, marksRead, emptyMessage, lead, className }: ChatThreadProps): import("react").JSX.Element;
|
|
@@ -7,7 +7,7 @@ import { cx } from "../../Helpers/ClassHelpers.js";
|
|
|
7
7
|
import { chatBadgeText, chatMessageKey, groupChatMessages, sameChatId } from "../../Helpers/ChatHelpers.js";
|
|
8
8
|
import { useBrowserTimeZone, useDatePreferences } from "../../Helpers/DatePreferences.js";
|
|
9
9
|
import { Button } from "../Documents/Button.js";
|
|
10
|
-
import { ChatAvatar, ChatDayDivider, ChatEmpty, ChatFailure, ChatLoading, ChatMessageItem, ChatSystemNote, ChatUnreadLine } from "./ChatParts.js";
|
|
10
|
+
import { ChatAvatar, ChatDayDivider, ChatEmpty, ChatEventNote, ChatFailure, ChatLoading, ChatMessageItem, ChatSystemNote, ChatUnreadLine } from "./ChatParts.js";
|
|
11
11
|
/*
|
|
12
12
|
* The thread: the conversation itself, scrolling. It opens at the newest message, keeps itself at the bottom while the
|
|
13
13
|
* reader is already there, and when they are not, it leaves the scroll alone and offers a button down to the newest
|
|
@@ -21,7 +21,7 @@ const EDGE = 32;
|
|
|
21
21
|
/** How near the top loading the page above starts. */
|
|
22
22
|
const TOP_EDGE = 120;
|
|
23
23
|
/** The messages of a conversation, in days and runs, with the lines that break them up. */
|
|
24
|
-
export function ChatThread({ store, state, userId, time, replies = true, onReply, marksRead = true, emptyMessage, className }) {
|
|
24
|
+
export function ChatThread({ store, state, userId, time, replies = true, onReply, marksRead = true, emptyMessage, lead, className }) {
|
|
25
25
|
const preferences = useDatePreferences();
|
|
26
26
|
const zone = useBrowserTimeZone();
|
|
27
27
|
const settings = useMemo(() => ({ ...preferences, ...(zone && { timeZone: zone }), ...time }), [preferences, zone, time]);
|
|
@@ -32,6 +32,8 @@ export function ChatThread({ store, state, userId, time, replies = true, onReply
|
|
|
32
32
|
const anchor = useRef(null);
|
|
33
33
|
const lastKey = useRef(null);
|
|
34
34
|
const count = useRef(0);
|
|
35
|
+
// With a lead, the first messages place the thread once: at its top, or at the first unread line.
|
|
36
|
+
const placed = useRef(false);
|
|
35
37
|
const days = useMemo(() => groupChatMessages(state.messages, { userId, unreadFrom: state.unreadFrom, ...settings }), [state.messages, state.unreadFrom, userId, settings]);
|
|
36
38
|
const byId = useMemo(() => new Map(state.messages.map(message => [String(message.id), message])), [state.messages]);
|
|
37
39
|
const scrollToEnd = useCallback((smooth) => {
|
|
@@ -41,11 +43,44 @@ export function ChatThread({ store, state, userId, time, replies = true, onReply
|
|
|
41
43
|
// Smooth only when the visitor and the system both allow motion; otherwise it jumps.
|
|
42
44
|
list.scrollTo({ top: list.scrollHeight, behavior: smooth && smoothScrollAllowed() ? "smooth" : "auto" });
|
|
43
45
|
}, []);
|
|
46
|
+
/** Whether the newest message is on screen: the reader is at the bottom. */
|
|
47
|
+
const measureBottom = useCallback(() => {
|
|
48
|
+
const list = listRef.current;
|
|
49
|
+
if (!list)
|
|
50
|
+
return;
|
|
51
|
+
const bottom = list.scrollHeight - list.scrollTop - list.clientHeight <= EDGE;
|
|
52
|
+
setAtBottom(bottom);
|
|
53
|
+
if (bottom)
|
|
54
|
+
setMissed(0);
|
|
55
|
+
}, []);
|
|
56
|
+
/**
|
|
57
|
+
* A thread with a lead opens at its top - the lead and the first messages - unless its first unread line would be out of
|
|
58
|
+
* view there; then that line opens at the top. The reader is at the bottom only if the newest message shows.
|
|
59
|
+
*/
|
|
60
|
+
const placeFirst = useCallback(() => {
|
|
61
|
+
const list = listRef.current;
|
|
62
|
+
if (!list)
|
|
63
|
+
return;
|
|
64
|
+
const unread = list.querySelector(".nx-chat-unread-line");
|
|
65
|
+
const top = list.getBoundingClientRect().top;
|
|
66
|
+
const within = (element) => element.getBoundingClientRect().bottom - top + list.scrollTop;
|
|
67
|
+
const run = unread?.nextElementSibling instanceof HTMLElement ? unread.nextElementSibling : null;
|
|
68
|
+
list.scrollTop = unread && run && within(run) > list.clientHeight ? Math.max(0, within(unread) - unread.offsetHeight - 8) : 0;
|
|
69
|
+
measureBottom();
|
|
70
|
+
}, [measureBottom]);
|
|
44
71
|
// New messages: stay at the bottom when already there, otherwise hold the place and count what came in.
|
|
45
72
|
useLayoutEffect(() => {
|
|
46
73
|
const list = listRef.current;
|
|
47
74
|
if (!list)
|
|
48
75
|
return;
|
|
76
|
+
if (lead && !placed.current && state.messages.length > 0 && !state.loading) {
|
|
77
|
+
placed.current = true;
|
|
78
|
+
count.current = state.messages.length;
|
|
79
|
+
const newest = state.messages[state.messages.length - 1];
|
|
80
|
+
lastKey.current = newest ? chatMessageKey(newest) : null;
|
|
81
|
+
placeFirst();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
49
84
|
if (anchor.current) {
|
|
50
85
|
// A page of older messages: the scroll keeps the message it was on, rather than jumping to the new top.
|
|
51
86
|
list.scrollTop = anchor.current.top + (list.scrollHeight - anchor.current.height);
|
|
@@ -67,7 +102,18 @@ export function ChatThread({ store, state, userId, time, replies = true, onReply
|
|
|
67
102
|
setMissed(value => value + 1);
|
|
68
103
|
// `missed` is read, not depended on: a second message must not re-run the first one's scroll.
|
|
69
104
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
70
|
-
}, [state.messages, atBottom, userId, scrollToEnd]);
|
|
105
|
+
}, [state.messages, state.loading, atBottom, userId, scrollToEnd, lead, placeFirst]);
|
|
106
|
+
// A lead that grows (a record's card landing, its details unfolded) moves the newest message down: whether the reader is
|
|
107
|
+
// at the bottom is worked out again, so a line that arrives next does not pull the lead out of view.
|
|
108
|
+
const leadRef = useRef(null);
|
|
109
|
+
useLayoutEffect(() => {
|
|
110
|
+
const element = leadRef.current;
|
|
111
|
+
if (!element)
|
|
112
|
+
return;
|
|
113
|
+
const observer = new ResizeObserver(() => measureBottom());
|
|
114
|
+
observer.observe(element);
|
|
115
|
+
return () => observer.disconnect();
|
|
116
|
+
}, [lead !== undefined, measureBottom]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
71
117
|
// The conversation counts as read once its newest message has been on screen.
|
|
72
118
|
useEffect(() => {
|
|
73
119
|
if (!marksRead || !atBottom || state.loading || state.summary.unreadCount === 0)
|
|
@@ -82,13 +128,15 @@ export function ChatThread({ store, state, userId, time, replies = true, onReply
|
|
|
82
128
|
setAtBottom(bottom);
|
|
83
129
|
if (bottom)
|
|
84
130
|
setMissed(0);
|
|
85
|
-
|
|
131
|
+
// Under a lead the top is the lead's: earlier pages come from their button, so the lead is not pushed away.
|
|
132
|
+
if (!lead && list.scrollTop <= TOP_EDGE && state.hasMore && !state.loadingOlder) {
|
|
86
133
|
anchor.current = { height: list.scrollHeight, top: list.scrollTop };
|
|
87
134
|
void store.loadOlder();
|
|
88
135
|
}
|
|
89
|
-
}, [state.hasMore, state.loadingOlder, store]);
|
|
136
|
+
}, [state.hasMore, state.loadingOlder, store, lead]);
|
|
90
137
|
const quiet = !state.loading && state.messages.length === 0;
|
|
91
|
-
|
|
138
|
+
const lines = (_jsxs(_Fragment, { children: [state.loadingOlder && _jsx(ChatLoading, { label: "Loading earlier messages\u2026" }), !state.loadingOlder && state.hasMore && (_jsx("div", { className: "nx-chat-thread__older", children: _jsx(Button, { size: "sm", variant: "ghost", onClick: () => void store.loadOlder(), children: "Earlier messages" }) })), state.loading && state.messages.length === 0 && _jsx(ChatLoading, { label: "Loading the conversation\u2026" }), state.error && _jsx(ChatFailure, { message: state.error, onRetry: () => void store.refresh() }), quiet && !state.error && _jsx(ChatEmpty, { message: emptyMessage ?? "No messages yet.", hint: _jsxs(_Fragment, { children: [_jsx(InlineIcon, { icon: iconMessageCircle }), " Write the first one below."] }) }), days.map(day => (_jsxs("section", { className: "nx-chat-thread__day", children: [_jsx(ChatDayDivider, { label: day.label }), day.runs.map((run, runIndex) => (_jsxs(Fragment, { children: [run.startsUnread && _jsx(ChatUnreadLine, { count: state.summary.unreadCount }), _jsx("div", { className: cx("nx-chat-run", run.mine && "nx-chat-run--mine", (run.system || run.notification) && "nx-chat-run--system"), children: run.system ? (run.messages.map(message => _jsx(ChatSystemNote, { message: message, time: settings }, chatMessageKey(message)))) : run.notification ? (run.messages.map(message => _jsx(ChatEventNote, { message: message, time: settings }, chatMessageKey(message)))) : (_jsxs(_Fragment, { children: [!run.mine && _jsx(ChatAvatarColumn, { name: run.authorName, src: run.authorAvatar }), _jsx("div", { className: "nx-chat-run__messages", children: run.messages.map((message, index) => (_jsx(ChatMessageItem, { message: message, mine: run.mine, first: index === 0, last: index === run.messages.length - 1, time: settings, participants: state.participants, userId: userId, quoted: message.replyToId ? byId.get(String(message.replyToId)) : undefined, onReply: replies && onReply ? onReply : undefined, onRemove: run.mine && state.features.remove ? message => void store.remove(message.id) : undefined, onRetry: store.retry, onDiscard: store.discard }, chatMessageKey(message)))) })] })) })] }, `${day.date}-${runIndex}`)))] }, day.date)))] }));
|
|
139
|
+
return (_jsxs("div", { className: cx("nx-chat-thread", className), children: [lead ? (_jsxs("div", { className: "nx-chat-thread__list", ref: listRef, onScroll: onScroll, tabIndex: 0, role: "region", "aria-label": "Thread", children: [_jsx("div", { ref: leadRef, className: "nx-chat-thread__lead", children: lead }), _jsx("div", { className: "nx-chat-thread__log", role: "log", "aria-live": "polite", "aria-relevant": "additions", children: lines })] })) : (_jsx("div", { className: "nx-chat-thread__list", ref: listRef, onScroll: onScroll, role: "log", "aria-live": "polite", "aria-relevant": "additions", tabIndex: 0, children: lines })), !atBottom && (_jsxs("button", { type: "button", className: "nx-chat-thread__jump", onClick: () => scrollToEnd(true), "aria-label": missed > 0 ? `${missed} new, go to the newest` : "Go to the newest message", children: [_jsx(InlineIcon, { icon: iconArrowDown }), missed > 0 && _jsx("span", { className: "nx-chat-thread__jump-count", children: chatBadgeText(missed) })] }))] }));
|
|
92
140
|
}
|
|
93
141
|
/** The avatar beside a run of somebody else's messages, kept out of the bubbles so they line up under each other. */
|
|
94
142
|
function ChatAvatarColumn({ name, src }) {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { DashboardLayout, DashboardLayoutItem, DashboardSaveResult, DashboardVersion, WidgetDefinition, WidgetModuleLoader } from "../../Interfaces/DashboardInterfaces.ts";
|
|
3
|
+
export interface DashboardBoardProps {
|
|
4
|
+
/** The place this board is: `home`, or a module's dashboard (`message`, `staffing`). */
|
|
5
|
+
place: string;
|
|
6
|
+
/** The module whose `own` widgets may go here. Default: `place` (the Home has no module of that name, so none). */
|
|
7
|
+
placeModule?: string | null;
|
|
8
|
+
/** Every widget the app declares; the board draws those allowed here that the person may see. */
|
|
9
|
+
widgets: readonly WidgetDefinition<any>[];
|
|
10
|
+
/** Whether the person may see a widget now: the company holds its module, the person its key. Default: every widget. */
|
|
11
|
+
canSee?: (widget: WidgetDefinition<any>) => boolean;
|
|
12
|
+
/** The person's saved layout for this place, or `null` when they saved none (the default is drawn). */
|
|
13
|
+
layout?: DashboardLayout | null;
|
|
14
|
+
/** The saved layout's version, given back with a save so a stale one is refused. */
|
|
15
|
+
version?: DashboardVersion;
|
|
16
|
+
/** The layout drawn when the person has none, and what Reset goes back to: the company's for this place, else the code's. */
|
|
17
|
+
defaultLayout: DashboardLayout;
|
|
18
|
+
/**
|
|
19
|
+
* Saves the person's layout (the whole of it: widgets they cannot see now keep their places), with the version it was
|
|
20
|
+
* edited from. Answer `stale` with the saved layout when the version is old (the server's 409). Without it the board's
|
|
21
|
+
* edits last as long as the page.
|
|
22
|
+
*/
|
|
23
|
+
onSave?: (layout: DashboardLayoutItem[], version: DashboardVersion) => Promise<DashboardSaveResult>;
|
|
24
|
+
/** Forgets the person's layout: Reset then Save. Without it, Reset saves the default as theirs through `onSave`. */
|
|
25
|
+
onReset?: (version: DashboardVersion) => Promise<DashboardSaveResult>;
|
|
26
|
+
/** Shows Customise. Default true. */
|
|
27
|
+
canCustomise?: boolean;
|
|
28
|
+
/** Offers "Save as the company's default" beside Save, with `onSaveCompanyDefault`. */
|
|
29
|
+
canSaveCompanyDefault?: boolean;
|
|
30
|
+
onSaveCompanyDefault?: (layout: DashboardLayoutItem[]) => Promise<DashboardSaveResult | void>;
|
|
31
|
+
/** The one call per module per draw, for widgets with `batch`. */
|
|
32
|
+
loadModule?: WidgetModuleLoader;
|
|
33
|
+
/** Data read with the page (on the server), by widget code: drawn at once, kept as if asked now. */
|
|
34
|
+
initialData?: Readonly<Record<string, unknown>>;
|
|
35
|
+
/** A module's name in the Add list. Default: the module's code with a capital. */
|
|
36
|
+
moduleLabel?: (module: string) => ReactNode;
|
|
37
|
+
/** Moves a link inside the board through the app's router (`router.push`). Default: the browser follows it. */
|
|
38
|
+
onNavigate?: (href: string) => void;
|
|
39
|
+
/** The board's heading. */
|
|
40
|
+
title?: ReactNode;
|
|
41
|
+
/** More controls in the board's bar, before Refresh and Customise. */
|
|
42
|
+
actions?: ReactNode;
|
|
43
|
+
/** How near the screen a widget starts to load, as a margin. Default `200px`. */
|
|
44
|
+
loadMargin?: string;
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A dashboard of widgets on a 12-column grid, for one place (the Home, a module's dashboard). Each widget takes a size
|
|
49
|
+
* preset (S 3×1, M 6×2, L 6×4, W 12×2, T 12×4), the grid fills every gap (`grid-auto-flow: dense`), and a layout is
|
|
50
|
+
* an ordered list of `{ code, size }`; on a phone every widget is the full width, in order. Widgets load only when near
|
|
51
|
+
* the screen, each module asked once per draw (`loadModule`), kept for the page's life or their `refresh` seconds.
|
|
52
|
+
*
|
|
53
|
+
* Customise turns on edit mode: move by dragging a widget's handle or with the arrow keys on a focused widget, Enter or
|
|
54
|
+
* Space for its size menu (only its sizes), Delete to remove it; Add widget opens a drawer of the widgets this place
|
|
55
|
+
* may hold, by module; Save, Cancel, Reset to default. A save another window beat (`stale`) shows the layout saved
|
|
56
|
+
* there and says so.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* <DashboardBoard place="home" widgets={WIDGETS} canSee={canSee} layout={saved?.items} version={saved?.version}
|
|
60
|
+
* defaultLayout={HOME_DEFAULT} loadModule={loadWidgets} onSave={saveLayout} onReset={resetLayout} />
|
|
61
|
+
*/
|
|
62
|
+
export declare function DashboardBoard({ place, placeModule, widgets, canSee, layout, version, defaultLayout, onSave, onReset, canCustomise, canSaveCompanyDefault, onSaveCompanyDefault, loadModule, initialData, moduleLabel, onNavigate, title, actions, loadMargin, className, }: DashboardBoardProps): import("react").JSX.Element;
|