@multiplatform.one/backoffice 6.3.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/LICENSE +201 -0
- package/README.md +310 -0
- package/dist/cjs/chrome/BackofficeAwesomebar.cjs +95 -0
- package/dist/cjs/chrome/BackofficeAwesomebar.native.js +106 -0
- package/dist/cjs/chrome/BackofficeAwesomebar.native.js.map +1 -0
- package/dist/cjs/chrome/CommandPalette.cjs +453 -0
- package/dist/cjs/chrome/CommandPalette.native.js +505 -0
- package/dist/cjs/chrome/CommandPalette.native.js.map +1 -0
- package/dist/cjs/chrome/HelpMenu.cjs +131 -0
- package/dist/cjs/chrome/HelpMenu.native.js +145 -0
- package/dist/cjs/chrome/HelpMenu.native.js.map +1 -0
- package/dist/cjs/chrome/NotificationsButton.cjs +112 -0
- package/dist/cjs/chrome/NotificationsButton.native.js +115 -0
- package/dist/cjs/chrome/NotificationsButton.native.js.map +1 -0
- package/dist/cjs/chrome/UserMenu.cjs +158 -0
- package/dist/cjs/chrome/UserMenu.native.js +186 -0
- package/dist/cjs/chrome/UserMenu.native.js.map +1 -0
- package/dist/cjs/chrome/awesomebar-data.cjs +173 -0
- package/dist/cjs/chrome/awesomebar-data.native.js +233 -0
- package/dist/cjs/chrome/awesomebar-data.native.js.map +1 -0
- package/dist/cjs/chrome/command-palette-model.cjs +208 -0
- package/dist/cjs/chrome/command-palette-model.native.js +301 -0
- package/dist/cjs/chrome/command-palette-model.native.js.map +1 -0
- package/dist/cjs/chrome/help-about-dialog.cjs +136 -0
- package/dist/cjs/chrome/help-about-dialog.native.js +146 -0
- package/dist/cjs/chrome/help-about-dialog.native.js.map +1 -0
- package/dist/cjs/chrome/help-shortcuts-dialog.cjs +155 -0
- package/dist/cjs/chrome/help-shortcuts-dialog.native.js +168 -0
- package/dist/cjs/chrome/help-shortcuts-dialog.native.js.map +1 -0
- package/dist/cjs/chrome/notifications-data.cjs +190 -0
- package/dist/cjs/chrome/notifications-data.native.js +271 -0
- package/dist/cjs/chrome/notifications-data.native.js.map +1 -0
- package/dist/cjs/chrome/notifications-panel.cjs +171 -0
- package/dist/cjs/chrome/notifications-panel.native.js +189 -0
- package/dist/cjs/chrome/notifications-panel.native.js.map +1 -0
- package/dist/cjs/chrome/notifications-utils.cjs +43 -0
- package/dist/cjs/chrome/notifications-utils.native.js +46 -0
- package/dist/cjs/chrome/notifications-utils.native.js.map +1 -0
- package/dist/cjs/chrome/shortcuts.cjs +58 -0
- package/dist/cjs/chrome/shortcuts.native.js +72 -0
- package/dist/cjs/chrome/shortcuts.native.js.map +1 -0
- package/dist/cjs/chrome/user-session.cjs +100 -0
- package/dist/cjs/chrome/user-session.native.js +120 -0
- package/dist/cjs/chrome/user-session.native.js.map +1 -0
- package/dist/cjs/config/BackofficeProvider.cjs +164 -0
- package/dist/cjs/config/BackofficeProvider.native.js +217 -0
- package/dist/cjs/config/BackofficeProvider.native.js.map +1 -0
- package/dist/cjs/config/capabilities.cjs +123 -0
- package/dist/cjs/config/capabilities.native.js +155 -0
- package/dist/cjs/config/capabilities.native.js.map +1 -0
- package/dist/cjs/config/events.cjs +18 -0
- package/dist/cjs/config/events.native.js +21 -0
- package/dist/cjs/config/events.native.js.map +1 -0
- package/dist/cjs/config/slots.cjs +18 -0
- package/dist/cjs/config/slots.native.js +21 -0
- package/dist/cjs/config/slots.native.js.map +1 -0
- package/dist/cjs/i18n/en.cjs +101 -0
- package/dist/cjs/i18n/en.native.js +104 -0
- package/dist/cjs/i18n/en.native.js.map +1 -0
- package/dist/cjs/i18n/index.cjs +77 -0
- package/dist/cjs/i18n/index.native.js +102 -0
- package/dist/cjs/i18n/index.native.js.map +1 -0
- package/dist/cjs/index.cjs +109 -0
- package/dist/cjs/index.native.js +112 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/navigation/navigator.cjs +144 -0
- package/dist/cjs/navigation/navigator.native.js +307 -0
- package/dist/cjs/navigation/navigator.native.js.map +1 -0
- package/dist/cjs/navigation/paths.cjs +103 -0
- package/dist/cjs/navigation/paths.native.js +143 -0
- package/dist/cjs/navigation/paths.native.js.map +1 -0
- package/dist/cjs/oneAdapter/index.cjs +53 -0
- package/dist/cjs/oneAdapter/index.native.js +64 -0
- package/dist/cjs/oneAdapter/index.native.js.map +1 -0
- package/dist/cjs/shared/useFrappeApp.cjs +56 -0
- package/dist/cjs/shared/useFrappeApp.native.js +60 -0
- package/dist/cjs/shared/useFrappeApp.native.js.map +1 -0
- package/dist/cjs/shell/BackofficeShell.cjs +194 -0
- package/dist/cjs/shell/BackofficeShell.native.js +223 -0
- package/dist/cjs/shell/BackofficeShell.native.js.map +1 -0
- package/dist/cjs/shell/WorkspaceSidebar.cjs +115 -0
- package/dist/cjs/shell/WorkspaceSidebar.native.js +143 -0
- package/dist/cjs/shell/WorkspaceSidebar.native.js.map +1 -0
- package/dist/cjs/shell/useWorkspaces.cjs +131 -0
- package/dist/cjs/shell/useWorkspaces.native.js +173 -0
- package/dist/cjs/shell/useWorkspaces.native.js.map +1 -0
- package/dist/cjs/shell/workspaceIcons.cjs +64 -0
- package/dist/cjs/shell/workspaceIcons.native.js +67 -0
- package/dist/cjs/shell/workspaceIcons.native.js.map +1 -0
- package/dist/esm/chrome/BackofficeAwesomebar.mjs +70 -0
- package/dist/esm/chrome/BackofficeAwesomebar.mjs.map +1 -0
- package/dist/esm/chrome/BackofficeAwesomebar.native.js +78 -0
- package/dist/esm/chrome/BackofficeAwesomebar.native.js.map +1 -0
- package/dist/esm/chrome/CommandPalette.mjs +428 -0
- package/dist/esm/chrome/CommandPalette.mjs.map +1 -0
- package/dist/esm/chrome/CommandPalette.native.js +477 -0
- package/dist/esm/chrome/CommandPalette.native.js.map +1 -0
- package/dist/esm/chrome/HelpMenu.mjs +106 -0
- package/dist/esm/chrome/HelpMenu.mjs.map +1 -0
- package/dist/esm/chrome/HelpMenu.native.js +117 -0
- package/dist/esm/chrome/HelpMenu.native.js.map +1 -0
- package/dist/esm/chrome/NotificationsButton.mjs +87 -0
- package/dist/esm/chrome/NotificationsButton.mjs.map +1 -0
- package/dist/esm/chrome/NotificationsButton.native.js +87 -0
- package/dist/esm/chrome/NotificationsButton.native.js.map +1 -0
- package/dist/esm/chrome/UserMenu.mjs +133 -0
- package/dist/esm/chrome/UserMenu.mjs.map +1 -0
- package/dist/esm/chrome/UserMenu.native.js +147 -0
- package/dist/esm/chrome/UserMenu.native.js.map +1 -0
- package/dist/esm/chrome/awesomebar-data.mjs +144 -0
- package/dist/esm/chrome/awesomebar-data.mjs.map +1 -0
- package/dist/esm/chrome/awesomebar-data.native.js +190 -0
- package/dist/esm/chrome/awesomebar-data.native.js.map +1 -0
- package/dist/esm/chrome/command-palette-model.mjs +183 -0
- package/dist/esm/chrome/command-palette-model.mjs.map +1 -0
- package/dist/esm/chrome/command-palette-model.native.js +273 -0
- package/dist/esm/chrome/command-palette-model.native.js.map +1 -0
- package/dist/esm/chrome/help-about-dialog.mjs +111 -0
- package/dist/esm/chrome/help-about-dialog.mjs.map +1 -0
- package/dist/esm/chrome/help-about-dialog.native.js +118 -0
- package/dist/esm/chrome/help-about-dialog.native.js.map +1 -0
- package/dist/esm/chrome/help-shortcuts-dialog.mjs +130 -0
- package/dist/esm/chrome/help-shortcuts-dialog.mjs.map +1 -0
- package/dist/esm/chrome/help-shortcuts-dialog.native.js +140 -0
- package/dist/esm/chrome/help-shortcuts-dialog.native.js.map +1 -0
- package/dist/esm/chrome/notifications-data.mjs +165 -0
- package/dist/esm/chrome/notifications-data.mjs.map +1 -0
- package/dist/esm/chrome/notifications-data.native.js +232 -0
- package/dist/esm/chrome/notifications-data.native.js.map +1 -0
- package/dist/esm/chrome/notifications-panel.mjs +146 -0
- package/dist/esm/chrome/notifications-panel.mjs.map +1 -0
- package/dist/esm/chrome/notifications-panel.native.js +161 -0
- package/dist/esm/chrome/notifications-panel.native.js.map +1 -0
- package/dist/esm/chrome/notifications-utils.mjs +17 -0
- package/dist/esm/chrome/notifications-utils.mjs.map +1 -0
- package/dist/esm/chrome/notifications-utils.native.js +17 -0
- package/dist/esm/chrome/notifications-utils.native.js.map +1 -0
- package/dist/esm/chrome/shortcuts.mjs +33 -0
- package/dist/esm/chrome/shortcuts.mjs.map +1 -0
- package/dist/esm/chrome/shortcuts.native.js +44 -0
- package/dist/esm/chrome/shortcuts.native.js.map +1 -0
- package/dist/esm/chrome/user-session.mjs +73 -0
- package/dist/esm/chrome/user-session.mjs.map +1 -0
- package/dist/esm/chrome/user-session.native.js +79 -0
- package/dist/esm/chrome/user-session.native.js.map +1 -0
- package/dist/esm/config/BackofficeProvider.mjs +132 -0
- package/dist/esm/config/BackofficeProvider.mjs.map +1 -0
- package/dist/esm/config/BackofficeProvider.native.js +171 -0
- package/dist/esm/config/BackofficeProvider.native.js.map +1 -0
- package/dist/esm/config/capabilities.mjs +97 -0
- package/dist/esm/config/capabilities.mjs.map +1 -0
- package/dist/esm/config/capabilities.native.js +126 -0
- package/dist/esm/config/capabilities.native.js.map +1 -0
- package/dist/esm/config/events.mjs +2 -0
- package/dist/esm/config/events.mjs.map +1 -0
- package/dist/esm/config/events.native.js +2 -0
- package/dist/esm/config/events.native.js.map +1 -0
- package/dist/esm/config/slots.mjs +2 -0
- package/dist/esm/config/slots.mjs.map +1 -0
- package/dist/esm/config/slots.native.js +2 -0
- package/dist/esm/config/slots.native.js.map +1 -0
- package/dist/esm/i18n/en.mjs +76 -0
- package/dist/esm/i18n/en.mjs.map +1 -0
- package/dist/esm/i18n/en.native.js +76 -0
- package/dist/esm/i18n/en.native.js.map +1 -0
- package/dist/esm/i18n/index.mjs +40 -0
- package/dist/esm/i18n/index.mjs.map +1 -0
- package/dist/esm/i18n/index.native.js +62 -0
- package/dist/esm/i18n/index.native.js.map +1 -0
- package/dist/esm/index.mjs +26 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +26 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/navigation/navigator.mjs +115 -0
- package/dist/esm/navigation/navigator.mjs.map +1 -0
- package/dist/esm/navigation/navigator.native.js +275 -0
- package/dist/esm/navigation/navigator.native.js.map +1 -0
- package/dist/esm/navigation/paths.mjs +66 -0
- package/dist/esm/navigation/paths.mjs.map +1 -0
- package/dist/esm/navigation/paths.native.js +103 -0
- package/dist/esm/navigation/paths.native.js.map +1 -0
- package/dist/esm/oneAdapter/index.mjs +28 -0
- package/dist/esm/oneAdapter/index.mjs.map +1 -0
- package/dist/esm/oneAdapter/index.native.js +36 -0
- package/dist/esm/oneAdapter/index.native.js.map +1 -0
- package/dist/esm/shared/useFrappeApp.mjs +31 -0
- package/dist/esm/shared/useFrappeApp.mjs.map +1 -0
- package/dist/esm/shared/useFrappeApp.native.js +32 -0
- package/dist/esm/shared/useFrappeApp.native.js.map +1 -0
- package/dist/esm/shell/BackofficeShell.mjs +169 -0
- package/dist/esm/shell/BackofficeShell.mjs.map +1 -0
- package/dist/esm/shell/BackofficeShell.native.js +195 -0
- package/dist/esm/shell/BackofficeShell.native.js.map +1 -0
- package/dist/esm/shell/WorkspaceSidebar.mjs +89 -0
- package/dist/esm/shell/WorkspaceSidebar.mjs.map +1 -0
- package/dist/esm/shell/WorkspaceSidebar.native.js +114 -0
- package/dist/esm/shell/WorkspaceSidebar.native.js.map +1 -0
- package/dist/esm/shell/useWorkspaces.mjs +104 -0
- package/dist/esm/shell/useWorkspaces.mjs.map +1 -0
- package/dist/esm/shell/useWorkspaces.native.js +143 -0
- package/dist/esm/shell/useWorkspaces.native.js.map +1 -0
- package/dist/esm/shell/workspaceIcons.mjs +39 -0
- package/dist/esm/shell/workspaceIcons.mjs.map +1 -0
- package/dist/esm/shell/workspaceIcons.native.js +39 -0
- package/dist/esm/shell/workspaceIcons.native.js.map +1 -0
- package/dist/jsx/chrome/BackofficeAwesomebar.mjs +70 -0
- package/dist/jsx/chrome/BackofficeAwesomebar.mjs.map +1 -0
- package/dist/jsx/chrome/BackofficeAwesomebar.native.js +106 -0
- package/dist/jsx/chrome/BackofficeAwesomebar.native.js.map +1 -0
- package/dist/jsx/chrome/CommandPalette.mjs +428 -0
- package/dist/jsx/chrome/CommandPalette.mjs.map +1 -0
- package/dist/jsx/chrome/CommandPalette.native.js +505 -0
- package/dist/jsx/chrome/CommandPalette.native.js.map +1 -0
- package/dist/jsx/chrome/HelpMenu.mjs +106 -0
- package/dist/jsx/chrome/HelpMenu.mjs.map +1 -0
- package/dist/jsx/chrome/HelpMenu.native.js +145 -0
- package/dist/jsx/chrome/HelpMenu.native.js.map +1 -0
- package/dist/jsx/chrome/NotificationsButton.mjs +87 -0
- package/dist/jsx/chrome/NotificationsButton.mjs.map +1 -0
- package/dist/jsx/chrome/NotificationsButton.native.js +115 -0
- package/dist/jsx/chrome/NotificationsButton.native.js.map +1 -0
- package/dist/jsx/chrome/UserMenu.mjs +133 -0
- package/dist/jsx/chrome/UserMenu.mjs.map +1 -0
- package/dist/jsx/chrome/UserMenu.native.js +186 -0
- package/dist/jsx/chrome/UserMenu.native.js.map +1 -0
- package/dist/jsx/chrome/awesomebar-data.mjs +144 -0
- package/dist/jsx/chrome/awesomebar-data.mjs.map +1 -0
- package/dist/jsx/chrome/awesomebar-data.native.js +233 -0
- package/dist/jsx/chrome/awesomebar-data.native.js.map +1 -0
- package/dist/jsx/chrome/command-palette-model.mjs +183 -0
- package/dist/jsx/chrome/command-palette-model.mjs.map +1 -0
- package/dist/jsx/chrome/command-palette-model.native.js +301 -0
- package/dist/jsx/chrome/command-palette-model.native.js.map +1 -0
- package/dist/jsx/chrome/help-about-dialog.mjs +111 -0
- package/dist/jsx/chrome/help-about-dialog.mjs.map +1 -0
- package/dist/jsx/chrome/help-about-dialog.native.js +146 -0
- package/dist/jsx/chrome/help-about-dialog.native.js.map +1 -0
- package/dist/jsx/chrome/help-shortcuts-dialog.mjs +130 -0
- package/dist/jsx/chrome/help-shortcuts-dialog.mjs.map +1 -0
- package/dist/jsx/chrome/help-shortcuts-dialog.native.js +168 -0
- package/dist/jsx/chrome/help-shortcuts-dialog.native.js.map +1 -0
- package/dist/jsx/chrome/notifications-data.mjs +165 -0
- package/dist/jsx/chrome/notifications-data.mjs.map +1 -0
- package/dist/jsx/chrome/notifications-data.native.js +271 -0
- package/dist/jsx/chrome/notifications-data.native.js.map +1 -0
- package/dist/jsx/chrome/notifications-panel.mjs +146 -0
- package/dist/jsx/chrome/notifications-panel.mjs.map +1 -0
- package/dist/jsx/chrome/notifications-panel.native.js +189 -0
- package/dist/jsx/chrome/notifications-panel.native.js.map +1 -0
- package/dist/jsx/chrome/notifications-utils.mjs +17 -0
- package/dist/jsx/chrome/notifications-utils.mjs.map +1 -0
- package/dist/jsx/chrome/notifications-utils.native.js +46 -0
- package/dist/jsx/chrome/notifications-utils.native.js.map +1 -0
- package/dist/jsx/chrome/shortcuts.mjs +33 -0
- package/dist/jsx/chrome/shortcuts.mjs.map +1 -0
- package/dist/jsx/chrome/shortcuts.native.js +72 -0
- package/dist/jsx/chrome/shortcuts.native.js.map +1 -0
- package/dist/jsx/chrome/user-session.mjs +73 -0
- package/dist/jsx/chrome/user-session.mjs.map +1 -0
- package/dist/jsx/chrome/user-session.native.js +120 -0
- package/dist/jsx/chrome/user-session.native.js.map +1 -0
- package/dist/jsx/config/BackofficeProvider.mjs +132 -0
- package/dist/jsx/config/BackofficeProvider.mjs.map +1 -0
- package/dist/jsx/config/BackofficeProvider.native.js +217 -0
- package/dist/jsx/config/BackofficeProvider.native.js.map +1 -0
- package/dist/jsx/config/capabilities.mjs +97 -0
- package/dist/jsx/config/capabilities.mjs.map +1 -0
- package/dist/jsx/config/capabilities.native.js +155 -0
- package/dist/jsx/config/capabilities.native.js.map +1 -0
- package/dist/jsx/config/events.mjs +2 -0
- package/dist/jsx/config/events.mjs.map +1 -0
- package/dist/jsx/config/events.native.js +21 -0
- package/dist/jsx/config/events.native.js.map +1 -0
- package/dist/jsx/config/slots.mjs +2 -0
- package/dist/jsx/config/slots.mjs.map +1 -0
- package/dist/jsx/config/slots.native.js +21 -0
- package/dist/jsx/config/slots.native.js.map +1 -0
- package/dist/jsx/i18n/en.mjs +76 -0
- package/dist/jsx/i18n/en.mjs.map +1 -0
- package/dist/jsx/i18n/en.native.js +104 -0
- package/dist/jsx/i18n/en.native.js.map +1 -0
- package/dist/jsx/i18n/index.mjs +40 -0
- package/dist/jsx/i18n/index.mjs.map +1 -0
- package/dist/jsx/i18n/index.native.js +102 -0
- package/dist/jsx/i18n/index.native.js.map +1 -0
- package/dist/jsx/index.js +26 -0
- package/dist/jsx/index.js.map +1 -0
- package/dist/jsx/index.mjs +26 -0
- package/dist/jsx/index.mjs.map +1 -0
- package/dist/jsx/index.native.js +112 -0
- package/dist/jsx/index.native.js.map +1 -0
- package/dist/jsx/navigation/navigator.mjs +115 -0
- package/dist/jsx/navigation/navigator.mjs.map +1 -0
- package/dist/jsx/navigation/navigator.native.js +307 -0
- package/dist/jsx/navigation/navigator.native.js.map +1 -0
- package/dist/jsx/navigation/paths.mjs +66 -0
- package/dist/jsx/navigation/paths.mjs.map +1 -0
- package/dist/jsx/navigation/paths.native.js +143 -0
- package/dist/jsx/navigation/paths.native.js.map +1 -0
- package/dist/jsx/oneAdapter/index.mjs +28 -0
- package/dist/jsx/oneAdapter/index.mjs.map +1 -0
- package/dist/jsx/oneAdapter/index.native.js +64 -0
- package/dist/jsx/oneAdapter/index.native.js.map +1 -0
- package/dist/jsx/shared/useFrappeApp.mjs +31 -0
- package/dist/jsx/shared/useFrappeApp.mjs.map +1 -0
- package/dist/jsx/shared/useFrappeApp.native.js +60 -0
- package/dist/jsx/shared/useFrappeApp.native.js.map +1 -0
- package/dist/jsx/shell/BackofficeShell.mjs +169 -0
- package/dist/jsx/shell/BackofficeShell.mjs.map +1 -0
- package/dist/jsx/shell/BackofficeShell.native.js +223 -0
- package/dist/jsx/shell/BackofficeShell.native.js.map +1 -0
- package/dist/jsx/shell/WorkspaceSidebar.mjs +89 -0
- package/dist/jsx/shell/WorkspaceSidebar.mjs.map +1 -0
- package/dist/jsx/shell/WorkspaceSidebar.native.js +143 -0
- package/dist/jsx/shell/WorkspaceSidebar.native.js.map +1 -0
- package/dist/jsx/shell/useWorkspaces.mjs +104 -0
- package/dist/jsx/shell/useWorkspaces.mjs.map +1 -0
- package/dist/jsx/shell/useWorkspaces.native.js +173 -0
- package/dist/jsx/shell/useWorkspaces.native.js.map +1 -0
- package/dist/jsx/shell/workspaceIcons.mjs +39 -0
- package/dist/jsx/shell/workspaceIcons.mjs.map +1 -0
- package/dist/jsx/shell/workspaceIcons.native.js +67 -0
- package/dist/jsx/shell/workspaceIcons.native.js.map +1 -0
- package/package.json +123 -0
- package/src/chrome/BackofficeAwesomebar.tsx +89 -0
- package/src/chrome/CommandPalette.spec.tsx +252 -0
- package/src/chrome/CommandPalette.tsx +520 -0
- package/src/chrome/HelpMenu.tsx +119 -0
- package/src/chrome/NotificationsButton.tsx +87 -0
- package/src/chrome/UserMenu.tsx +173 -0
- package/src/chrome/awesomebar-data.tsx +252 -0
- package/src/chrome/command-palette-model.ts +291 -0
- package/src/chrome/help-about-dialog.tsx +130 -0
- package/src/chrome/help-shortcuts-dialog.tsx +144 -0
- package/src/chrome/notifications-data.tsx +287 -0
- package/src/chrome/notifications-panel.tsx +175 -0
- package/src/chrome/notifications-utils.tsx +44 -0
- package/src/chrome/shortcuts.ts +53 -0
- package/src/chrome/user-session.tsx +116 -0
- package/src/config/BackofficeProvider.spec.tsx +126 -0
- package/src/config/BackofficeProvider.tsx +315 -0
- package/src/config/capabilities.ts +172 -0
- package/src/config/events.ts +19 -0
- package/src/config/slots.ts +28 -0
- package/src/i18n/en.ts +83 -0
- package/src/i18n/index.ts +69 -0
- package/src/index.ts +150 -0
- package/src/navigation/navigator.spec.ts +106 -0
- package/src/navigation/navigator.ts +209 -0
- package/src/navigation/paths.ts +146 -0
- package/src/oneAdapter/index.tsx +61 -0
- package/src/shared/useFrappeApp.ts +39 -0
- package/src/shell/BackofficeShell.tsx +222 -0
- package/src/shell/WorkspaceSidebar.tsx +119 -0
- package/src/shell/useWorkspaces.ts +202 -0
- package/src/shell/workspaceIcons.ts +76 -0
- package/types/chrome/BackofficeAwesomebar.d.ts +12 -0
- package/types/chrome/BackofficeAwesomebar.d.ts.map +1 -0
- package/types/chrome/CommandPalette.d.ts +6 -0
- package/types/chrome/CommandPalette.d.ts.map +1 -0
- package/types/chrome/HelpMenu.d.ts +2 -0
- package/types/chrome/HelpMenu.d.ts.map +1 -0
- package/types/chrome/NotificationsButton.d.ts +2 -0
- package/types/chrome/NotificationsButton.d.ts.map +1 -0
- package/types/chrome/UserMenu.d.ts +6 -0
- package/types/chrome/UserMenu.d.ts.map +1 -0
- package/types/chrome/awesomebar-data.d.ts +52 -0
- package/types/chrome/awesomebar-data.d.ts.map +1 -0
- package/types/chrome/command-palette-model.d.ts +47 -0
- package/types/chrome/command-palette-model.d.ts.map +1 -0
- package/types/chrome/help-about-dialog.d.ts +12 -0
- package/types/chrome/help-about-dialog.d.ts.map +1 -0
- package/types/chrome/help-shortcuts-dialog.d.ts +6 -0
- package/types/chrome/help-shortcuts-dialog.d.ts.map +1 -0
- package/types/chrome/notifications-data.d.ts +34 -0
- package/types/chrome/notifications-data.d.ts.map +1 -0
- package/types/chrome/notifications-panel.d.ts +11 -0
- package/types/chrome/notifications-panel.d.ts.map +1 -0
- package/types/chrome/notifications-utils.d.ts +21 -0
- package/types/chrome/notifications-utils.d.ts.map +1 -0
- package/types/chrome/shortcuts.d.ts +6 -0
- package/types/chrome/shortcuts.d.ts.map +1 -0
- package/types/chrome/user-session.d.ts +19 -0
- package/types/chrome/user-session.d.ts.map +1 -0
- package/types/config/BackofficeProvider.d.ts +128 -0
- package/types/config/BackofficeProvider.d.ts.map +1 -0
- package/types/config/capabilities.d.ts +109 -0
- package/types/config/capabilities.d.ts.map +1 -0
- package/types/config/events.d.ts +39 -0
- package/types/config/events.d.ts.map +1 -0
- package/types/config/slots.d.ts +32 -0
- package/types/config/slots.d.ts.map +1 -0
- package/types/i18n/en.d.ts +83 -0
- package/types/i18n/en.d.ts.map +1 -0
- package/types/i18n/index.d.ts +27 -0
- package/types/i18n/index.d.ts.map +1 -0
- package/types/index.d.ts +55 -0
- package/types/index.d.ts.map +1 -0
- package/types/navigation/navigator.d.ts +98 -0
- package/types/navigation/navigator.d.ts.map +1 -0
- package/types/navigation/paths.d.ts +60 -0
- package/types/navigation/paths.d.ts.map +1 -0
- package/types/oneAdapter/index.d.ts +8 -0
- package/types/oneAdapter/index.d.ts.map +1 -0
- package/types/shared/useFrappeApp.d.ts +10 -0
- package/types/shared/useFrappeApp.d.ts.map +1 -0
- package/types/shell/BackofficeShell.d.ts +10 -0
- package/types/shell/BackofficeShell.d.ts.map +1 -0
- package/types/shell/WorkspaceSidebar.d.ts +9 -0
- package/types/shell/WorkspaceSidebar.d.ts.map +1 -0
- package/types/shell/useWorkspaces.d.ts +43 -0
- package/types/shell/useWorkspaces.d.ts.map +1 -0
- package/types/shell/workspaceIcons.d.ts +9 -0
- package/types/shell/workspaceIcons.d.ts.map +1 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Live Notification Log feed for the navbar bell (desk parity).
|
|
4
|
+
*
|
|
5
|
+
* Data flows through the live client (`useFrappeCollection` + `useLiveQuery`)
|
|
6
|
+
* so realtime inserts pop into the panel without a refetch. Read state is a
|
|
7
|
+
* different story: the desk RPCs flip `read` with `update_modified=False`
|
|
8
|
+
* (no realtime doc_update event), so this hook layers local optimistic
|
|
9
|
+
* overrides on top of the collection and lets a later sync reconcile.
|
|
10
|
+
*
|
|
11
|
+
* RPC shapes (probed against the reference bench):
|
|
12
|
+
* - POST /api/method/frappe.desk.doctype.notification_log.notification_log.mark_as_read
|
|
13
|
+
* body {docname} → {} (sets read=1)
|
|
14
|
+
* - POST /api/method/frappe.desk.doctype.notification_log.notification_log.mark_all_as_read
|
|
15
|
+
* body {} → {} (marks every unread row for the session user)
|
|
16
|
+
* - Fallback per doc: POST frappe.client.set_value
|
|
17
|
+
* {doctype: "Notification Log", name, fieldname: "read", value: 1}
|
|
18
|
+
*/
|
|
19
|
+
import {
|
|
20
|
+
Order,
|
|
21
|
+
useFrappeCollection,
|
|
22
|
+
useLiveQuery,
|
|
23
|
+
type FixtureProvider,
|
|
24
|
+
type FrappeApp,
|
|
25
|
+
} from "@multiplatform.one/frappe";
|
|
26
|
+
import { useFrappeConfig } from "@multiplatform.one/frappe-ui";
|
|
27
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
28
|
+
import { useFrappeApp } from "../shared/useFrappeApp";
|
|
29
|
+
import { parseFrappeTimestamp, stripHtmlToText } from "./notifications-utils";
|
|
30
|
+
|
|
31
|
+
export interface NotificationLogDoc {
|
|
32
|
+
name: string;
|
|
33
|
+
doctype: string;
|
|
34
|
+
subject?: string;
|
|
35
|
+
type?: string;
|
|
36
|
+
document_type?: string;
|
|
37
|
+
document_name?: string;
|
|
38
|
+
read?: 0 | 1;
|
|
39
|
+
from_user?: string;
|
|
40
|
+
creation?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A Notification Log row prepared for display. */
|
|
44
|
+
export interface NotificationItem extends NotificationLogDoc {
|
|
45
|
+
/** Server `read` merged with local optimistic mark-as-read overrides. */
|
|
46
|
+
unread: boolean;
|
|
47
|
+
/** Subject with HTML stripped ("Jane mentioned you in a comment: …"). */
|
|
48
|
+
subjectText: string;
|
|
49
|
+
/** `creation` as epoch ms (undefined when unparseable). */
|
|
50
|
+
timestampMs?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const NOTIFICATION_FIELDS = [
|
|
54
|
+
"name",
|
|
55
|
+
"subject",
|
|
56
|
+
"type",
|
|
57
|
+
"document_type",
|
|
58
|
+
"document_name",
|
|
59
|
+
"read",
|
|
60
|
+
"from_user",
|
|
61
|
+
"creation",
|
|
62
|
+
] as const;
|
|
63
|
+
|
|
64
|
+
/** Recent-notifications window (desk loads pages of 20; badge caps at 99+). */
|
|
65
|
+
const NOTIFICATION_WINDOW_LIMIT = 100;
|
|
66
|
+
|
|
67
|
+
const MARK_AS_READ_METHOD = "frappe.desk.doctype.notification_log.notification_log.mark_as_read";
|
|
68
|
+
const MARK_ALL_AS_READ_METHOD =
|
|
69
|
+
"frappe.desk.doctype.notification_log.notification_log.mark_all_as_read";
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Session user (module cache — one fetch per FrappeApp per session)
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
const sessionUserCache = new WeakMap<FrappeApp, Promise<string | undefined>>();
|
|
76
|
+
|
|
77
|
+
interface SessionUserState {
|
|
78
|
+
user?: string;
|
|
79
|
+
/** True once the probe settled (Guest / unreachable resolve to no user). */
|
|
80
|
+
resolved: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function useNotificationsSessionUser(fixtureMode: boolean): SessionUserState {
|
|
84
|
+
const app = useFrappeApp();
|
|
85
|
+
const [state, setState] = useState<SessionUserState>({ resolved: false });
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
// Fixture mode has no session endpoint; report resolved-with-no-user.
|
|
89
|
+
// No app (no provider config) settles the same way so the panel never
|
|
90
|
+
// skeletons forever.
|
|
91
|
+
if (fixtureMode || !app) {
|
|
92
|
+
setState({ resolved: true });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
let cancelled = false;
|
|
96
|
+
let promise = sessionUserCache.get(app);
|
|
97
|
+
if (!promise) {
|
|
98
|
+
promise = app
|
|
99
|
+
.call()
|
|
100
|
+
.get<{ message?: string }>("frappe.auth.get_logged_user")
|
|
101
|
+
.then((res) => {
|
|
102
|
+
const user = res.payload?.message;
|
|
103
|
+
return user && user !== "Guest" ? user : undefined;
|
|
104
|
+
})
|
|
105
|
+
.catch(() => {
|
|
106
|
+
// Evict failed probes so a later mount can retry.
|
|
107
|
+
sessionUserCache.delete(app);
|
|
108
|
+
return undefined;
|
|
109
|
+
});
|
|
110
|
+
sessionUserCache.set(app, promise);
|
|
111
|
+
}
|
|
112
|
+
promise.then((user) => {
|
|
113
|
+
if (!cancelled) setState({ user, resolved: true });
|
|
114
|
+
});
|
|
115
|
+
return () => {
|
|
116
|
+
cancelled = true;
|
|
117
|
+
};
|
|
118
|
+
}, [app, fixtureMode]);
|
|
119
|
+
|
|
120
|
+
return state;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Mark-as-read RPCs
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
async function setReadViaSetValue(app: FrappeApp, name: string): Promise<void> {
|
|
128
|
+
await app.call().post("frappe.client.set_value", {
|
|
129
|
+
doctype: "Notification Log",
|
|
130
|
+
name,
|
|
131
|
+
fieldname: "read",
|
|
132
|
+
value: 1,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function markAsReadOnServer(app: FrappeApp, name: string): Promise<void> {
|
|
137
|
+
try {
|
|
138
|
+
await app.call().post(MARK_AS_READ_METHOD, { docname: name });
|
|
139
|
+
} catch {
|
|
140
|
+
// Desk RPC unavailable (older bench / stripped app) — plain doc write.
|
|
141
|
+
await setReadViaSetValue(app, name);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function markAllAsReadOnServer(
|
|
146
|
+
app: FrappeApp,
|
|
147
|
+
unreadNames: readonly string[],
|
|
148
|
+
): Promise<void> {
|
|
149
|
+
try {
|
|
150
|
+
await app.call().post(MARK_ALL_AS_READ_METHOD, {});
|
|
151
|
+
} catch {
|
|
152
|
+
await Promise.all(unreadNames.map((name) => setReadViaSetValue(app, name)));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Hook
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
export interface UseNotificationsResult {
|
|
161
|
+
/** Prepared rows, newest first. */
|
|
162
|
+
items: NotificationItem[];
|
|
163
|
+
/** Unread rows within the window (badge value). */
|
|
164
|
+
unreadCount: number;
|
|
165
|
+
/** True until the session probe + first collection snapshot settle. */
|
|
166
|
+
isLoading: boolean;
|
|
167
|
+
/** Mark one notification read (optimistic; reverts if the RPC fails). */
|
|
168
|
+
markAsRead: (name: string) => Promise<void>;
|
|
169
|
+
/** Mark every unread notification read (optimistic; reverts on failure). */
|
|
170
|
+
markAllAsRead: () => Promise<void>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function useNotifications(): UseNotificationsResult {
|
|
174
|
+
const config = useFrappeConfig();
|
|
175
|
+
const app = useFrappeApp();
|
|
176
|
+
const fixtureMode = Boolean(config?.fixtures);
|
|
177
|
+
const { user: sessionUser, resolved: sessionResolved } = useNotificationsSessionUser(fixtureMode);
|
|
178
|
+
|
|
179
|
+
// Stable connection config (fresh identities would churn the collection).
|
|
180
|
+
const connectionConfig = useMemo(
|
|
181
|
+
() =>
|
|
182
|
+
config
|
|
183
|
+
? {
|
|
184
|
+
baseURL: config.baseURL,
|
|
185
|
+
socketPort: config.socketPort,
|
|
186
|
+
auth: config.auth,
|
|
187
|
+
syncOptions: config.syncOptions,
|
|
188
|
+
socketOptions: config.socketOptions,
|
|
189
|
+
fixtures: config.fixtures as FixtureProvider | undefined,
|
|
190
|
+
}
|
|
191
|
+
: undefined,
|
|
192
|
+
[config],
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Live mode waits for the session user (the for_user filter needs it);
|
|
196
|
+
// fixture mode reads the fixture set unfiltered.
|
|
197
|
+
const ready = Boolean(connectionConfig) && (fixtureMode || Boolean(sessionUser));
|
|
198
|
+
const collection = useFrappeCollection<NotificationLogDoc>(
|
|
199
|
+
ready ? connectionConfig : undefined,
|
|
200
|
+
"Notification Log",
|
|
201
|
+
{
|
|
202
|
+
fields: NOTIFICATION_FIELDS,
|
|
203
|
+
filters: !fixtureMode && sessionUser ? [["for_user", "=", sessionUser]] : undefined,
|
|
204
|
+
orderBy: { field: "creation", order: Order.Desc },
|
|
205
|
+
limit: NOTIFICATION_WINDOW_LIMIT,
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const { data: rawData, isLoading: queryLoading } = useLiveQuery(
|
|
210
|
+
(q) => (collection ? q.from({ _notification: collection }) : null),
|
|
211
|
+
[collection],
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// Local read overrides — see module note on why realtime can't carry these.
|
|
215
|
+
const [readOverrides, setReadOverrides] = useState<ReadonlySet<string>>(new Set());
|
|
216
|
+
|
|
217
|
+
const items = useMemo<NotificationItem[]>(() => {
|
|
218
|
+
const docs = (rawData as NotificationLogDoc[] | undefined) ?? [];
|
|
219
|
+
return docs
|
|
220
|
+
.filter((doc) => doc.name)
|
|
221
|
+
.map((doc) => ({
|
|
222
|
+
...doc,
|
|
223
|
+
unread: !doc.read && !readOverrides.has(doc.name),
|
|
224
|
+
subjectText: stripHtmlToText(doc.subject),
|
|
225
|
+
timestampMs: parseFrappeTimestamp(doc.creation),
|
|
226
|
+
}))
|
|
227
|
+
.sort((a, b) => (b.timestampMs ?? 0) - (a.timestampMs ?? 0) || a.name.localeCompare(b.name));
|
|
228
|
+
}, [rawData, readOverrides]);
|
|
229
|
+
|
|
230
|
+
const unreadCount = useMemo(() => items.filter((item) => item.unread).length, [items]);
|
|
231
|
+
|
|
232
|
+
const isLoading =
|
|
233
|
+
(Boolean(connectionConfig) && !fixtureMode && !sessionResolved) ||
|
|
234
|
+
// Session resolved without a user (Guest / unreachable bench): settle on
|
|
235
|
+
// an empty feed instead of skeleting forever.
|
|
236
|
+
(ready && (collection === undefined || Boolean(queryLoading)));
|
|
237
|
+
|
|
238
|
+
const addOverrides = useCallback((names: readonly string[]) => {
|
|
239
|
+
if (!names.length) return;
|
|
240
|
+
setReadOverrides((prev) => {
|
|
241
|
+
const next = new Set(prev);
|
|
242
|
+
for (const name of names) next.add(name);
|
|
243
|
+
return next;
|
|
244
|
+
});
|
|
245
|
+
}, []);
|
|
246
|
+
|
|
247
|
+
const removeOverrides = useCallback((names: readonly string[]) => {
|
|
248
|
+
if (!names.length) return;
|
|
249
|
+
setReadOverrides((prev) => {
|
|
250
|
+
const next = new Set(prev);
|
|
251
|
+
for (const name of names) next.delete(name);
|
|
252
|
+
return next;
|
|
253
|
+
});
|
|
254
|
+
}, []);
|
|
255
|
+
|
|
256
|
+
const markAsRead = useCallback(
|
|
257
|
+
async (name: string) => {
|
|
258
|
+
addOverrides([name]);
|
|
259
|
+
if (fixtureMode || !app) return;
|
|
260
|
+
try {
|
|
261
|
+
await markAsReadOnServer(app, name);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
removeOverrides([name]);
|
|
264
|
+
throw err;
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
[app, fixtureMode, addOverrides, removeOverrides],
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const markAllAsRead = useCallback(async () => {
|
|
271
|
+
const unreadNames = items.filter((item) => item.unread).map((item) => item.name);
|
|
272
|
+
if (!unreadNames.length) return;
|
|
273
|
+
addOverrides(unreadNames);
|
|
274
|
+
if (fixtureMode || !app) return;
|
|
275
|
+
try {
|
|
276
|
+
await markAllAsReadOnServer(app, unreadNames);
|
|
277
|
+
} catch (err) {
|
|
278
|
+
removeOverrides(unreadNames);
|
|
279
|
+
throw err;
|
|
280
|
+
}
|
|
281
|
+
}, [app, fixtureMode, items, addOverrides, removeOverrides]);
|
|
282
|
+
|
|
283
|
+
return useMemo(
|
|
284
|
+
() => ({ items, unreadCount, isLoading, markAsRead, markAllAsRead }),
|
|
285
|
+
[items, unreadCount, isLoading, markAsRead, markAllAsRead],
|
|
286
|
+
);
|
|
287
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notifications panel body (desk bell-dropdown parity): header with
|
|
3
|
+
* "Notifications" + mark-all-as-read + settings placeholder, then the
|
|
4
|
+
* newest-first Notification Log list. Presentational — data, routing, and
|
|
5
|
+
* the popover/sheet shell live in NotificationsButton.
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
Avatar,
|
|
9
|
+
Button,
|
|
10
|
+
EmptyState,
|
|
11
|
+
Separator,
|
|
12
|
+
SizableText,
|
|
13
|
+
Skeleton,
|
|
14
|
+
SkeletonCircle,
|
|
15
|
+
Text,
|
|
16
|
+
View,
|
|
17
|
+
XStack,
|
|
18
|
+
YStack,
|
|
19
|
+
} from "@multiplatform.one/components";
|
|
20
|
+
import { formatRelativeTimestamp } from "@multiplatform.one/theme";
|
|
21
|
+
import { GearSixIcon } from "@phosphor-icons/react";
|
|
22
|
+
import { useTranslation } from "react-i18next";
|
|
23
|
+
import { BACKOFFICE_I18N_NAMESPACE } from "../i18n";
|
|
24
|
+
import type { NotificationItem } from "./notifications-data";
|
|
25
|
+
|
|
26
|
+
export interface NotificationsPanelProps {
|
|
27
|
+
items: readonly NotificationItem[];
|
|
28
|
+
isLoading: boolean;
|
|
29
|
+
/** Panel width — a fixed measure when floating, "100%" in sheet mode. */
|
|
30
|
+
width?: number | string;
|
|
31
|
+
onItemPress: (item: NotificationItem) => void;
|
|
32
|
+
onMarkAllRead: () => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function NotificationRow({ item, onPress }: { item: NotificationItem; onPress: () => void }) {
|
|
36
|
+
const { t } = useTranslation(BACKOFFICE_I18N_NAMESPACE);
|
|
37
|
+
const subject = item.subjectText || t("notifications.untitled", "Notification");
|
|
38
|
+
const relative = formatRelativeTimestamp(item.timestampMs ?? item.creation);
|
|
39
|
+
return (
|
|
40
|
+
<XStack
|
|
41
|
+
alignItems="flex-start"
|
|
42
|
+
gap="$3"
|
|
43
|
+
paddingHorizontal="$3"
|
|
44
|
+
paddingVertical="$2.5"
|
|
45
|
+
backgroundColor={item.unread ? "$color2" : "transparent"}
|
|
46
|
+
hoverStyle={{ backgroundColor: "$color3" }}
|
|
47
|
+
pressStyle={{ backgroundColor: "$color4" }}
|
|
48
|
+
cursor="pointer"
|
|
49
|
+
onPress={onPress}
|
|
50
|
+
role="button"
|
|
51
|
+
aria-label={subject}
|
|
52
|
+
data-testid="backoffice-notification-row"
|
|
53
|
+
>
|
|
54
|
+
<Avatar name={item.from_user || "?"} size="$2.5" />
|
|
55
|
+
<YStack flex={1} minWidth={0} gap="$1">
|
|
56
|
+
<Text
|
|
57
|
+
fontSize="$3"
|
|
58
|
+
fontWeight={item.unread ? "600" : "400"}
|
|
59
|
+
color={item.unread ? "$color12" : "$color11"}
|
|
60
|
+
numberOfLines={2}
|
|
61
|
+
>
|
|
62
|
+
{subject}
|
|
63
|
+
</Text>
|
|
64
|
+
{relative ? (
|
|
65
|
+
<Text fontSize="$2" color="$color9">
|
|
66
|
+
{relative}
|
|
67
|
+
</Text>
|
|
68
|
+
) : null}
|
|
69
|
+
</YStack>
|
|
70
|
+
{item.unread && (
|
|
71
|
+
<View
|
|
72
|
+
width={8}
|
|
73
|
+
height={8}
|
|
74
|
+
borderRadius={1000}
|
|
75
|
+
backgroundColor="$accentBackground"
|
|
76
|
+
marginTop="$1.5"
|
|
77
|
+
flexShrink={0}
|
|
78
|
+
aria-hidden
|
|
79
|
+
/>
|
|
80
|
+
)}
|
|
81
|
+
</XStack>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function NotificationSkeletonRows() {
|
|
86
|
+
return (
|
|
87
|
+
<YStack data-testid="backoffice-notifications-loading">
|
|
88
|
+
{Array.from({ length: 3 }, (_, index) => (
|
|
89
|
+
<XStack
|
|
90
|
+
key={index}
|
|
91
|
+
alignItems="center"
|
|
92
|
+
gap="$3"
|
|
93
|
+
paddingHorizontal="$3"
|
|
94
|
+
paddingVertical="$2.5"
|
|
95
|
+
>
|
|
96
|
+
<SkeletonCircle size={28} />
|
|
97
|
+
<YStack flex={1} gap="$1.5">
|
|
98
|
+
<Skeleton height={12} width="85%" />
|
|
99
|
+
<Skeleton height={10} width={64} />
|
|
100
|
+
</YStack>
|
|
101
|
+
</XStack>
|
|
102
|
+
))}
|
|
103
|
+
</YStack>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function NotificationsPanel({
|
|
108
|
+
items,
|
|
109
|
+
isLoading,
|
|
110
|
+
width,
|
|
111
|
+
onItemPress,
|
|
112
|
+
onMarkAllRead,
|
|
113
|
+
}: NotificationsPanelProps) {
|
|
114
|
+
const { t } = useTranslation(BACKOFFICE_I18N_NAMESPACE);
|
|
115
|
+
const hasUnread = items.some((item) => item.unread);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<YStack
|
|
119
|
+
width={width}
|
|
120
|
+
maxWidth="100%"
|
|
121
|
+
paddingVertical="$2"
|
|
122
|
+
data-testid="backoffice-notifications-panel"
|
|
123
|
+
>
|
|
124
|
+
<XStack alignItems="center" gap="$2" paddingHorizontal="$3" paddingBottom="$2">
|
|
125
|
+
<SizableText size="$4" fontWeight="700" flex={1} numberOfLines={1}>
|
|
126
|
+
{t("notifications.title", "Notifications")}
|
|
127
|
+
</SizableText>
|
|
128
|
+
<Button
|
|
129
|
+
chromeless
|
|
130
|
+
size="$2"
|
|
131
|
+
disabled={!hasUnread}
|
|
132
|
+
opacity={hasUnread ? 1 : 0.5}
|
|
133
|
+
onPress={onMarkAllRead}
|
|
134
|
+
data-testid="backoffice-notifications-mark-all"
|
|
135
|
+
>
|
|
136
|
+
{t("notifications.markAllRead", "Mark all as read")}
|
|
137
|
+
</Button>
|
|
138
|
+
{/* Desk links to /app/notification-settings; the backoffice has no
|
|
139
|
+
settings surface yet — disabled placeholder documents the gap. */}
|
|
140
|
+
<Button
|
|
141
|
+
chromeless
|
|
142
|
+
circular
|
|
143
|
+
size="$2"
|
|
144
|
+
icon={GearSixIcon}
|
|
145
|
+
disabled
|
|
146
|
+
opacity={0.5}
|
|
147
|
+
aria-label={t(
|
|
148
|
+
"notifications.settingsUnavailable",
|
|
149
|
+
"Notification settings (not available yet)",
|
|
150
|
+
)}
|
|
151
|
+
/>
|
|
152
|
+
</XStack>
|
|
153
|
+
<Separator />
|
|
154
|
+
{isLoading ? (
|
|
155
|
+
<NotificationSkeletonRows />
|
|
156
|
+
) : items.length === 0 ? (
|
|
157
|
+
<EmptyState
|
|
158
|
+
compact
|
|
159
|
+
paddingVertical="$6"
|
|
160
|
+
title={t("notifications.emptyTitle", "No new notifications")}
|
|
161
|
+
description={t(
|
|
162
|
+
"notifications.emptyDescription",
|
|
163
|
+
"Mentions, assignments, and shares will show up here.",
|
|
164
|
+
)}
|
|
165
|
+
/>
|
|
166
|
+
) : (
|
|
167
|
+
<YStack>
|
|
168
|
+
{items.map((item) => (
|
|
169
|
+
<NotificationRow key={item.name} item={item} onPress={() => onItemPress(item)} />
|
|
170
|
+
))}
|
|
171
|
+
</YStack>
|
|
172
|
+
)}
|
|
173
|
+
</YStack>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny pure helpers for the notifications panel. Notification Log subjects
|
|
3
|
+
* arrive as HTML fragments ('<b>Jane</b> mentioned you in <a>…</a>') and
|
|
4
|
+
* timestamps in frappe's "YYYY-MM-DD HH:mm:ss.ffffff" register.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Strip HTML tags from a Notification Log subject and return readable text.
|
|
9
|
+
* Closing block tags / <br> become spaces so "<div>a</div><div>b</div>"
|
|
10
|
+
* reads "a b"; basic entities are decoded (& last so "&lt;" stays
|
|
11
|
+
* literal "<"); whitespace collapses to single spaces.
|
|
12
|
+
*/
|
|
13
|
+
export function stripHtmlToText(html: string | null | undefined): string {
|
|
14
|
+
if (!html) return "";
|
|
15
|
+
const withBreaks = html.replace(/<\s*(?:br|\/p|\/div|\/li|\/tr|\/h[1-6])\s*\/?\s*>/gi, " ");
|
|
16
|
+
const withoutTags = withBreaks.replace(/<[^>]*>/g, "");
|
|
17
|
+
const decoded = withoutTags
|
|
18
|
+
.replace(/ /gi, " ")
|
|
19
|
+
.replace(/</gi, "<")
|
|
20
|
+
.replace(/>/gi, ">")
|
|
21
|
+
.replace(/"/gi, '"')
|
|
22
|
+
.replace(/'|'/gi, "'")
|
|
23
|
+
.replace(/&/gi, "&");
|
|
24
|
+
return decoded.replace(/\s+/g, " ").trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse a frappe timestamp ("2026-08-12 09:34:09.025850") to epoch ms.
|
|
29
|
+
* The space separator and microsecond precision trip Safari/JSC, so
|
|
30
|
+
* normalize to ISO-ish ("T" separator, milliseconds) before Date parsing.
|
|
31
|
+
* Frappe emits server-local wall time with no zone; parsing as local time
|
|
32
|
+
* matches how desk renders it. Unparseable input → undefined.
|
|
33
|
+
*/
|
|
34
|
+
export function parseFrappeTimestamp(value: string | null | undefined): number | undefined {
|
|
35
|
+
if (!value) return undefined;
|
|
36
|
+
const isoish = value
|
|
37
|
+
.trim()
|
|
38
|
+
.replace(" ", "T")
|
|
39
|
+
.replace(/(\.\d{3})\d+$/, "$1");
|
|
40
|
+
const parsed = new Date(isoish).getTime();
|
|
41
|
+
if (!Number.isNaN(parsed)) return parsed;
|
|
42
|
+
const fallback = new Date(value).getTime();
|
|
43
|
+
return Number.isNaN(fallback) ? undefined : fallback;
|
|
44
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global backoffice keyboard shortcuts (web only).
|
|
3
|
+
*
|
|
4
|
+
* INTEGRATION: the shell (user-owned) should call this once near the root of
|
|
5
|
+
* the desk chrome and wire `onFocusSearch` to the awesomebar, e.g.:
|
|
6
|
+
*
|
|
7
|
+
* useBackofficeShortcuts({ onFocusSearch: () => focusAwesomebar() });
|
|
8
|
+
*
|
|
9
|
+
* Bindings (keep chrome/help-shortcuts-dialog.tsx in sync):
|
|
10
|
+
* - Mod+K / Mod+G → onFocusSearch (opens the command palette)
|
|
11
|
+
*
|
|
12
|
+
* SSR / native / GJS safe: no-ops when there is no DOM document. Events from
|
|
13
|
+
* editable targets (inputs, textareas, selects, contenteditable) are ignored
|
|
14
|
+
* so typing is never hijacked. Cleans up its listener on unmount.
|
|
15
|
+
*/
|
|
16
|
+
import { useEffect, useRef } from "react";
|
|
17
|
+
|
|
18
|
+
export interface BackofficeShortcutHandlers {
|
|
19
|
+
/** Focus the awesomebar / global search (Mod+K, Mod+G). */
|
|
20
|
+
onFocusSearch?: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isEditableTarget(target: EventTarget | null): boolean {
|
|
24
|
+
if (typeof HTMLElement === "undefined" || !(target instanceof HTMLElement)) return false;
|
|
25
|
+
if (target.isContentEditable) return true;
|
|
26
|
+
const tag = target.tagName;
|
|
27
|
+
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function useBackofficeShortcuts(handlers: BackofficeShortcutHandlers): void {
|
|
31
|
+
// Ref keeps the listener stable while consumers pass inline handler objects.
|
|
32
|
+
const handlersRef = useRef(handlers);
|
|
33
|
+
handlersRef.current = handlers;
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (typeof document === "undefined" || typeof document.addEventListener !== "function") {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
40
|
+
if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey) return;
|
|
41
|
+
if (event.repeat || isEditableTarget(event.target)) return;
|
|
42
|
+
const key = event.key.toLowerCase();
|
|
43
|
+
if (key === "k" || key === "g") {
|
|
44
|
+
const { onFocusSearch } = handlersRef.current;
|
|
45
|
+
if (!onFocusSearch) return;
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
onFocusSearch();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
document.addEventListener("keydown", onKeyDown);
|
|
51
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
52
|
+
}, []);
|
|
53
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Session-user info for the navbar user menu, module-cached so the avatar
|
|
4
|
+
* button and the menu header fetch once per session (per FrappeApp).
|
|
5
|
+
*
|
|
6
|
+
* Flow (one cached promise): frappe.auth.get_logged_user →
|
|
7
|
+
* frappe.client.get_value(User, <name>, ["full_name","user_image","email"]).
|
|
8
|
+
*/
|
|
9
|
+
import type { FrappeApp } from "@multiplatform.one/frappe";
|
|
10
|
+
import { useFrappeConfig } from "@multiplatform.one/frappe-ui";
|
|
11
|
+
import { useEffect, useMemo, useState } from "react";
|
|
12
|
+
import { useFrappeApp } from "../shared/useFrappeApp";
|
|
13
|
+
|
|
14
|
+
export interface SessionUserInfo {
|
|
15
|
+
/** Session user docname (e.g. "Administrator" or an email login). */
|
|
16
|
+
user: string;
|
|
17
|
+
fullName: string;
|
|
18
|
+
email?: string;
|
|
19
|
+
/** Absolute avatar URL (User.user_image resolved against the bench). */
|
|
20
|
+
imageUrl?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface UserDocBits {
|
|
24
|
+
full_name?: string;
|
|
25
|
+
user_image?: string | null;
|
|
26
|
+
email?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Keyed by FrappeApp instance: a different bench/auth config gets its own
|
|
30
|
+
// entry (useFrappeApp memoizes apps per config). Failed fetches self-evict
|
|
31
|
+
// so the next mount retries.
|
|
32
|
+
let sessionCache = new WeakMap<FrappeApp, Promise<SessionUserInfo>>();
|
|
33
|
+
|
|
34
|
+
/** Logout hygiene: drops every cached session-user fetch in this module. */
|
|
35
|
+
export function clearSessionUserCache(): void {
|
|
36
|
+
sessionCache = new WeakMap();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Resolve a User.user_image value (usually "/files/…") to an absolute URL. */
|
|
40
|
+
export function resolveUserImageUrl(
|
|
41
|
+
userImage: string | null | undefined,
|
|
42
|
+
baseURL: string,
|
|
43
|
+
): string | undefined {
|
|
44
|
+
if (!userImage) return undefined;
|
|
45
|
+
// Already absolute (http(s), data:, blob:, …) — leave untouched.
|
|
46
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(userImage)) return userImage;
|
|
47
|
+
return `${baseURL.replace(/\/+$/, "")}${userImage.startsWith("/") ? "" : "/"}${userImage}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function fetchSessionUserInfo(app: FrappeApp, baseURL: string): Promise<SessionUserInfo> {
|
|
51
|
+
const user = await app.auth().getLoggedInUser();
|
|
52
|
+
if (!user) throw new Error("No session user");
|
|
53
|
+
// Param format probed against the bench: filters takes the plain docname,
|
|
54
|
+
// fieldname a JSON array (the http client JSON-encodes non-string params).
|
|
55
|
+
const res = await app.call().get<{ message?: UserDocBits }>("frappe.client.get_value", {
|
|
56
|
+
doctype: "User",
|
|
57
|
+
filters: user,
|
|
58
|
+
fieldname: ["full_name", "user_image", "email"],
|
|
59
|
+
});
|
|
60
|
+
const bits = res.payload?.message ?? {};
|
|
61
|
+
return {
|
|
62
|
+
user,
|
|
63
|
+
fullName: bits.full_name || user,
|
|
64
|
+
email: bits.email || (user.includes("@") ? user : undefined),
|
|
65
|
+
imageUrl: resolveUserImageUrl(bits.user_image, baseURL),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Module-cached session-user info from the ambient Frappe connection. */
|
|
70
|
+
export function useSessionUserInfo(): {
|
|
71
|
+
info?: SessionUserInfo;
|
|
72
|
+
loading: boolean;
|
|
73
|
+
error?: Error;
|
|
74
|
+
} {
|
|
75
|
+
const app = useFrappeApp();
|
|
76
|
+
const config = useFrappeConfig();
|
|
77
|
+
const baseURL = config?.baseURL ?? "";
|
|
78
|
+
const [info, setInfo] = useState<SessionUserInfo | undefined>(undefined);
|
|
79
|
+
const [loading, setLoading] = useState(true);
|
|
80
|
+
const [error, setError] = useState<Error | undefined>(undefined);
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!app) {
|
|
84
|
+
setInfo(undefined);
|
|
85
|
+
setLoading(false);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
let cancelled = false;
|
|
89
|
+
let promise = sessionCache.get(app);
|
|
90
|
+
if (!promise) {
|
|
91
|
+
promise = fetchSessionUserInfo(app, baseURL);
|
|
92
|
+
sessionCache.set(app, promise);
|
|
93
|
+
promise.catch(() => {
|
|
94
|
+
if (sessionCache.get(app) === promise) sessionCache.delete(app);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
setLoading(true);
|
|
98
|
+
setError(undefined);
|
|
99
|
+
promise
|
|
100
|
+
.then((value) => {
|
|
101
|
+
if (cancelled) return;
|
|
102
|
+
setInfo(value);
|
|
103
|
+
setLoading(false);
|
|
104
|
+
})
|
|
105
|
+
.catch((err: Error) => {
|
|
106
|
+
if (cancelled) return;
|
|
107
|
+
setError(err);
|
|
108
|
+
setLoading(false);
|
|
109
|
+
});
|
|
110
|
+
return () => {
|
|
111
|
+
cancelled = true;
|
|
112
|
+
};
|
|
113
|
+
}, [app, baseURL]);
|
|
114
|
+
|
|
115
|
+
return useMemo(() => ({ info, loading, error }), [info, loading, error]);
|
|
116
|
+
}
|