@jini-ai/admin 0.3.10 → 0.4.1
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 +64 -0
- package/README.md +71 -37
- package/dist/agent-plugins/adapters/http.d.ts +7 -0
- package/dist/agent-plugins/adapters/http.js +15 -0
- package/dist/agent-plugins/adapters/memory.d.ts +8 -0
- package/dist/agent-plugins/adapters/memory.js +25 -0
- package/dist/agent-plugins/agent-plugins.module.d.ts +51 -0
- package/dist/agent-plugins/agent-plugins.module.js +6 -0
- package/dist/agent-plugins/conformance/agent-plugins-api.conformance.d.ts +12 -0
- package/dist/agent-plugins/conformance/agent-plugins-api.conformance.js +47 -0
- package/dist/agent-plugins/controllers/agent-plugin-files.controller.d.ts +20 -0
- package/dist/agent-plugins/controllers/agent-plugin-files.controller.js +43 -0
- package/dist/agent-plugins/controllers/agent-plugins.controller.d.ts +25 -0
- package/dist/agent-plugins/controllers/agent-plugins.controller.js +91 -0
- package/dist/agent-plugins/index.d.ts +9 -0
- package/dist/agent-plugins/index.js +9 -0
- package/dist/agent-plugins/messages.en.d.ts +23 -0
- package/dist/agent-plugins/messages.en.js +14 -0
- package/dist/agent-plugins/models.d.ts +57 -0
- package/dist/agent-plugins/models.js +2 -0
- package/dist/agent-plugins/package-file-tree.d.ts +111 -0
- package/dist/agent-plugins/package-file-tree.js +188 -0
- package/dist/agent-plugins/ports.d.ts +18 -0
- package/dist/agent-plugins/ports.js +3 -0
- package/dist/agent-plugins/react/components/AgentPluginFileContent.d.ts +5 -0
- package/dist/agent-plugins/react/components/AgentPluginFileContent.js +9 -0
- package/dist/agent-plugins/react/components/AgentPluginFileTree.d.ts +9 -0
- package/dist/agent-plugins/react/components/AgentPluginFileTree.js +7 -0
- package/dist/agent-plugins/react/components/AgentPluginInspector.d.ts +3 -0
- package/dist/agent-plugins/react/components/AgentPluginInspector.js +11 -0
- package/dist/agent-plugins/react/components/AgentPluginListPanel.d.ts +6 -0
- package/dist/agent-plugins/react/components/AgentPluginListPanel.js +10 -0
- package/dist/agent-plugins/react/components/AgentPluginRow.d.ts +5 -0
- package/dist/agent-plugins/react/components/AgentPluginRow.js +8 -0
- package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.d.ts +90 -0
- package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.js +74 -0
- package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.d.ts +67 -0
- package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.js +50 -0
- package/dist/agent-plugins/react/hooks/AgentPluginRow.hooks.d.ts +55 -0
- package/dist/agent-plugins/react/hooks/AgentPluginRow.hooks.js +6 -0
- package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.d.ts +64 -0
- package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.js +39 -0
- package/dist/agent-plugins/react/hooks/AgentPluginsPorts.hooks.d.ts +7 -0
- package/dist/agent-plugins/react/hooks/AgentPluginsPorts.hooks.js +10 -0
- package/dist/agent-plugins/react/hooks/MarketplaceTab.hooks.d.ts +6 -0
- package/dist/agent-plugins/react/hooks/MarketplaceTab.hooks.js +3 -0
- package/dist/agent-plugins/react/index.d.ts +245 -0
- package/dist/agent-plugins/react/index.js +13 -0
- package/dist/agent-plugins/react/pages/AgentPluginsPage.d.ts +4 -0
- package/dist/agent-plugins/react/pages/AgentPluginsPage.js +12 -0
- package/dist/agent-plugins/react/tabs/DownloadedTab.d.ts +4 -0
- package/dist/agent-plugins/react/tabs/DownloadedTab.js +5 -0
- package/dist/agent-plugins/react/tabs/InstalledTab.d.ts +4 -0
- package/dist/agent-plugins/react/tabs/InstalledTab.js +5 -0
- package/dist/agent-plugins/react/tabs/MarketplaceTab.d.ts +4 -0
- package/dist/agent-plugins/react/tabs/MarketplaceTab.js +10 -0
- package/dist/agent-plugins/rules.d.ts +33 -0
- package/dist/agent-plugins/rules.js +51 -0
- package/dist/browser/index.d.ts +1 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/navigation.d.ts +19 -4
- package/dist/browser/navigation.js +8 -8
- package/dist/browser/shell-navigation.d.ts +12 -0
- package/dist/browser/shell-navigation.js +16 -0
- package/dist/contracts/credential-management.d.ts +8 -0
- package/dist/contracts/credential-management.js +3 -0
- package/dist/contracts/media-picker.d.ts +17 -0
- package/dist/contracts/media-picker.js +5 -0
- package/dist/contracts/playground-render-target.d.ts +18 -0
- package/dist/contracts/playground-render-target.js +3 -0
- package/dist/core/entities/rules.d.ts +138 -0
- package/dist/core/entities/rules.js +344 -0
- package/dist/core/gated/types.d.ts +8 -4
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +1 -0
- package/dist/core/manifest/rules.d.ts +24 -6
- package/dist/core/manifest/rules.js +12 -11
- package/dist/core/module/controller-store.d.ts +22 -0
- package/dist/core/module/controller-store.js +32 -0
- package/dist/core/module/create-admin.d.ts +41 -0
- package/dist/core/module/create-admin.js +187 -0
- package/dist/core/module/define-admin-module.d.ts +4 -0
- package/dist/core/module/define-admin-module.js +5 -0
- package/dist/core/module/index.d.ts +7 -0
- package/dist/core/module/index.js +4 -0
- package/dist/core/module/token.d.ts +19 -0
- package/dist/core/module/token.js +4 -0
- package/dist/core/module/types.d.ts +79 -0
- package/dist/core/module/types.js +2 -0
- package/dist/core/permissions/rules.d.ts +4 -1
- package/dist/core/permissions/rules.js +1 -1
- package/dist/core/ports/analytics.d.ts +1 -1
- package/dist/core/ports/auth.d.ts +2 -2
- package/dist/core/ports/comments.d.ts +9 -6
- package/dist/core/ports/database.d.ts +13 -10
- package/dist/core/ports/entities.d.ts +302 -0
- package/dist/core/ports/entities.js +47 -0
- package/dist/core/ports/forms.d.ts +19 -7
- package/dist/core/ports/identity.d.ts +42 -17
- package/dist/core/ports/index.d.ts +2 -0
- package/dist/core/ports/integrations.d.ts +9 -4
- package/dist/core/ports/media.d.ts +16 -7
- package/dist/core/ports/members.d.ts +7 -3
- package/dist/core/ports/menus.d.ts +46 -0
- package/dist/core/ports/menus.js +43 -0
- package/dist/core/ports/plugins.d.ts +3 -2
- package/dist/core/ports/recovery.d.ts +10 -6
- package/dist/core/ports/redirects.d.ts +17 -7
- package/dist/core/ports/seo.d.ts +12 -6
- package/dist/core/ports/shell.d.ts +41 -0
- package/dist/core/ports/shell.js +2 -0
- package/dist/core/ports/workspace.d.ts +3 -3
- package/dist/core/routing/rules.d.ts +17 -4
- package/dist/core/routing/rules.js +4 -4
- package/dist/core/transport/errors.d.ts +13 -4
- package/dist/core/transport/errors.js +4 -4
- package/dist/core/transport/http.d.ts +26 -16
- package/dist/core/transport/http.js +59 -21
- package/dist/core/transport/types.d.ts +26 -14
- package/dist/core/transport/types.js +9 -7
- package/dist/media/adapters/http.d.ts +27 -0
- package/dist/media/adapters/http.js +44 -0
- package/dist/media/adapters/memory.d.ts +11 -0
- package/dist/media/adapters/memory.js +139 -0
- package/dist/media/conformance/media-api.conformance.d.ts +7 -0
- package/dist/media/conformance/media-api.conformance.js +69 -0
- package/dist/media/controllers/edit-media.controller.d.ts +24 -0
- package/dist/media/controllers/edit-media.controller.js +69 -0
- package/dist/media/controllers/library.controller.d.ts +47 -0
- package/dist/media/controllers/library.controller.js +173 -0
- package/dist/media/controllers/providers.controller.d.ts +30 -0
- package/dist/media/controllers/providers.controller.js +76 -0
- package/dist/media/html-attributes.d.ts +82 -0
- package/dist/media/html-attributes.js +204 -0
- package/dist/media/index.d.ts +12 -0
- package/dist/media/index.js +9 -0
- package/dist/media/media.module.d.ts +83 -0
- package/dist/media/media.module.js +43 -0
- package/dist/media/messages.en.d.ts +24 -0
- package/dist/media/messages.en.js +24 -0
- package/dist/media/models.d.ts +72 -0
- package/dist/media/models.js +2 -0
- package/dist/media/ports.d.ts +75 -0
- package/dist/media/ports.js +7 -0
- package/dist/media/react/components/EditMediaPanel.d.ts +3 -0
- package/dist/media/react/components/EditMediaPanel.js +10 -0
- package/dist/media/react/components/MediaCard.d.ts +5 -0
- package/dist/media/react/components/MediaCard.js +17 -0
- package/dist/media/react/components/MediaDialog.d.ts +3 -0
- package/dist/media/react/components/MediaDialog.js +8 -0
- package/dist/media/react/components/MediaGrid.d.ts +4 -0
- package/dist/media/react/components/MediaGrid.js +8 -0
- package/dist/media/react/components/MediaIcons.d.ts +14 -0
- package/dist/media/react/components/MediaIcons.js +39 -0
- package/dist/media/react/components/MediaLightbox.d.ts +5 -0
- package/dist/media/react/components/MediaLightbox.js +12 -0
- package/dist/media/react/components/MediaPicker.d.ts +3 -0
- package/dist/media/react/components/MediaPicker.js +10 -0
- package/dist/media/react/components/MediaPurgeDialog.d.ts +4 -0
- package/dist/media/react/components/MediaPurgeDialog.js +9 -0
- package/dist/media/react/components/UploadButton.d.ts +3 -0
- package/dist/media/react/components/UploadButton.js +8 -0
- package/dist/media/react/hooks/EditMediaPanel.hooks.d.ts +84 -0
- package/dist/media/react/hooks/EditMediaPanel.hooks.js +105 -0
- package/dist/media/react/hooks/LibraryTab.hooks.d.ts +103 -0
- package/dist/media/react/hooks/LibraryTab.hooks.js +164 -0
- package/dist/media/react/hooks/MediaCard.hooks.d.ts +83 -0
- package/dist/media/react/hooks/MediaCard.hooks.js +66 -0
- package/dist/media/react/hooks/MediaDialog.hooks.d.ts +10 -0
- package/dist/media/react/hooks/MediaDialog.hooks.js +20 -0
- package/dist/media/react/hooks/MediaGrid.hooks.d.ts +29 -0
- package/dist/media/react/hooks/MediaGrid.hooks.js +6 -0
- package/dist/media/react/hooks/MediaLightbox.hooks.d.ts +27 -0
- package/dist/media/react/hooks/MediaLightbox.hooks.js +64 -0
- package/dist/media/react/hooks/MediaPage.hooks.d.ts +68 -0
- package/dist/media/react/hooks/MediaPage.hooks.js +68 -0
- package/dist/media/react/hooks/MediaPicker.hooks.d.ts +26 -0
- package/dist/media/react/hooks/MediaPicker.hooks.js +25 -0
- package/dist/media/react/hooks/MediaPorts.hooks.d.ts +9 -0
- package/dist/media/react/hooks/MediaPorts.hooks.js +10 -0
- package/dist/media/react/hooks/MediaPurgeDialog.hooks.d.ts +22 -0
- package/dist/media/react/hooks/MediaPurgeDialog.hooks.js +6 -0
- package/dist/media/react/hooks/ProvidersTab.hooks.d.ts +55 -0
- package/dist/media/react/hooks/ProvidersTab.hooks.js +124 -0
- package/dist/media/react/hooks/UploadButton.hooks.d.ts +48 -0
- package/dist/media/react/hooks/UploadButton.hooks.js +118 -0
- package/dist/media/react/index.d.ts +373 -0
- package/dist/media/react/index.js +73 -0
- package/dist/media/react/pages/MediaPage.d.ts +4 -0
- package/dist/media/react/pages/MediaPage.js +9 -0
- package/dist/media/react/tabs/LibraryTab.d.ts +4 -0
- package/dist/media/react/tabs/LibraryTab.js +14 -0
- package/dist/media/react/tabs/ProvidersTab.d.ts +4 -0
- package/dist/media/react/tabs/ProvidersTab.js +10 -0
- package/dist/media/rules.d.ts +86 -0
- package/dist/media/rules.js +134 -0
- package/dist/media/styles.d.ts +4 -0
- package/dist/media/styles.js +5 -0
- package/dist/playground/adapters/http.d.ts +8 -0
- package/dist/playground/adapters/http.js +4 -0
- package/dist/playground/adapters/memory.d.ts +10 -0
- package/dist/playground/adapters/memory.js +30 -0
- package/dist/playground/conformance/playground-api.conformance.d.ts +6 -0
- package/dist/playground/conformance/playground-api.conformance.js +37 -0
- package/dist/playground/controllers/playground.controller.d.ts +16 -0
- package/dist/playground/controllers/playground.controller.js +21 -0
- package/dist/playground/index.d.ts +7 -0
- package/dist/playground/index.js +5 -0
- package/dist/playground/messages.en.d.ts +9 -0
- package/dist/playground/messages.en.js +2 -0
- package/dist/playground/models.d.ts +4 -0
- package/dist/playground/models.js +2 -0
- package/dist/playground/playground.module.d.ts +31 -0
- package/dist/playground/playground.module.js +5 -0
- package/dist/playground/ports.d.ts +5 -0
- package/dist/playground/ports.js +2 -0
- package/dist/playground/react/components/Canvas.d.ts +3 -0
- package/dist/playground/react/components/Canvas.js +9 -0
- package/dist/playground/react/hooks/Canvas.hooks.d.ts +7 -0
- package/dist/playground/react/hooks/Canvas.hooks.js +18 -0
- package/dist/playground/react/hooks/PlaygroundPage.hooks.d.ts +8 -0
- package/dist/playground/react/hooks/PlaygroundPage.hooks.js +2 -0
- package/dist/playground/react/hooks/PlaygroundPorts.hooks.d.ts +4 -0
- package/dist/playground/react/hooks/PlaygroundPorts.hooks.js +6 -0
- package/dist/playground/react/index.d.ts +136 -0
- package/dist/playground/react/index.js +10 -0
- package/dist/playground/react/pages/PlaygroundPage.d.ts +4 -0
- package/dist/playground/react/pages/PlaygroundPage.js +11 -0
- package/dist/playground/react/tabs/CanvasTab.d.ts +4 -0
- package/dist/playground/react/tabs/CanvasTab.js +5 -0
- package/dist/playground/rules.d.ts +4 -0
- package/dist/playground/rules.js +2 -0
- package/dist/react/OverlayHost.d.ts +3 -0
- package/dist/react/OverlayHost.hooks.d.ts +9 -0
- package/dist/react/OverlayHost.hooks.js +9 -0
- package/dist/react/OverlayHost.js +7 -0
- package/dist/react/bind-react.d.ts +46 -0
- package/dist/react/bind-react.js +28 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.hooks.d.ts +7 -1
- package/dist/react/components/ConfirmButton/ConfirmButton.hooks.js +2 -2
- package/dist/react/components/ConfirmButton/ConfirmButton.js +1 -1
- package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.d.ts +17 -3
- package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.js +3 -3
- package/dist/react/components/ConfirmDialog/ConfirmDialog.js +5 -5
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.d.ts +6 -2
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js +7 -8
- package/dist/react/components/RowMenu/RowMenu.d.ts +5 -1
- package/dist/react/components/RowMenu/RowMenu.hooks.d.ts +8 -2
- package/dist/react/components/RowMenu/RowMenu.hooks.js +4 -4
- package/dist/react/components/RowMenu/RowMenu.js +6 -6
- package/dist/react/components/Sidebar.d.ts +1 -1
- package/dist/react/components/Sidebar.js +103 -7
- package/dist/react/entities/EntityCell.d.ts +10 -0
- package/dist/react/entities/EntityCell.js +18 -0
- package/dist/react/entities/EntityDetail.d.ts +3 -0
- package/dist/react/entities/EntityDetail.js +21 -0
- package/dist/react/entities/EntityEdit.d.ts +13 -0
- package/dist/react/entities/EntityEdit.js +45 -0
- package/dist/react/entities/EntityField.d.ts +20 -0
- package/dist/react/entities/EntityField.js +31 -0
- package/dist/react/entities/EntityIndex.d.ts +3 -0
- package/dist/react/entities/EntityIndex.js +10 -0
- package/dist/react/entities/EntityList.d.ts +7 -0
- package/dist/react/entities/EntityList.js +52 -0
- package/dist/react/entities/contribution.d.ts +26 -0
- package/dist/react/entities/contribution.js +78 -0
- package/dist/react/entities/index.d.ts +10 -0
- package/dist/react/entities/index.js +7 -0
- package/dist/react/entities/rules.d.ts +179 -0
- package/dist/react/entities/rules.js +224 -0
- package/dist/react/entities/types.d.ts +45 -0
- package/dist/react/entities/types.js +5 -0
- package/dist/react/entities/use-entity-edit.d.ts +35 -0
- package/dist/react/entities/use-entity-edit.js +98 -0
- package/dist/react/entities/use-entity-read.d.ts +22 -0
- package/dist/react/entities/use-entity-read.js +27 -0
- package/dist/react/hooks/use-nav-sections.d.ts +12 -3
- package/dist/react/hooks/use-nav-sections.js +14 -14
- package/dist/react/hooks/use-sidebar-rail.d.ts +7 -1
- package/dist/react/hooks/use-sidebar-rail.js +11 -11
- package/dist/react/index.d.ts +3 -1
- package/dist/react/index.js +5 -2
- package/dist/react/overlays.d.ts +38 -0
- package/dist/react/overlays.js +46 -0
- package/dist/react/shell/AdminShell.d.ts +9 -0
- package/dist/react/shell/AdminShell.hooks.d.ts +15 -0
- package/dist/react/shell/AdminShell.hooks.js +27 -0
- package/dist/react/shell/AdminShell.js +46 -0
- package/dist/react/shell/index.d.ts +7 -0
- package/dist/react/shell/index.js +5 -0
- package/dist/react/shell/model.d.ts +25 -0
- package/dist/react/shell/model.js +29 -0
- package/dist/react/shell/theme-ports.d.ts +28 -0
- package/dist/react/shell/theme-ports.js +2 -0
- package/dist/react/shell/types.d.ts +99 -0
- package/dist/react/shell/types.js +2 -0
- package/dist/react/shell/use-admin-shell-session.d.ts +13 -0
- package/dist/react/shell/use-admin-shell-session.js +81 -0
- package/dist/react/shell/use-admin-theme.d.ts +16 -0
- package/dist/react/shell/use-admin-theme.js +57 -0
- package/dist/react/types.d.ts +6 -4
- package/dist/react/types.js +2 -2
- package/dist/react/use-controller.d.ts +16 -0
- package/dist/react/use-controller.js +18 -0
- package/dist/security/adapters/http.d.ts +16 -0
- package/dist/security/adapters/http.js +94 -0
- package/dist/security/adapters/memory.d.ts +15 -0
- package/dist/security/adapters/memory.js +60 -0
- package/dist/security/conformance/security-api.conformance.d.ts +10 -0
- package/dist/security/conformance/security-api.conformance.js +48 -0
- package/dist/security/controllers/access-tokens.controller.d.ts +44 -0
- package/dist/security/controllers/access-tokens.controller.js +112 -0
- package/dist/security/controllers/other-credentials.controller.d.ts +30 -0
- package/dist/security/controllers/other-credentials.controller.js +74 -0
- package/dist/security/controllers/root-key.controller.d.ts +35 -0
- package/dist/security/controllers/root-key.controller.js +90 -0
- package/dist/security/index.d.ts +9 -0
- package/dist/security/index.js +9 -0
- package/dist/security/messages.en.d.ts +12 -0
- package/dist/security/messages.en.js +2 -0
- package/dist/security/models.d.ts +112 -0
- package/dist/security/models.js +2 -0
- package/dist/security/ports.d.ts +58 -0
- package/dist/security/ports.js +5 -0
- package/dist/security/react/components/CredentialEditor.d.ts +3 -0
- package/dist/security/react/components/CredentialEditor.js +8 -0
- package/dist/security/react/hooks/AccessTokensTab.hooks.d.ts +94 -0
- package/dist/security/react/hooks/AccessTokensTab.hooks.js +36 -0
- package/dist/security/react/hooks/CredentialEditor.hooks.d.ts +49 -0
- package/dist/security/react/hooks/CredentialEditor.hooks.js +14 -0
- package/dist/security/react/hooks/RootKeyTab.hooks.d.ts +29 -0
- package/dist/security/react/hooks/RootKeyTab.hooks.js +19 -0
- package/dist/security/react/hooks/SecurityPage.hooks.d.ts +17 -0
- package/dist/security/react/hooks/SecurityPage.hooks.js +12 -0
- package/dist/security/react/hooks/SecurityPorts.hooks.d.ts +9 -0
- package/dist/security/react/hooks/SecurityPorts.hooks.js +6 -0
- package/dist/security/react/index.d.ts +215 -0
- package/dist/security/react/index.js +10 -0
- package/dist/security/react/pages/SecurityPage.d.ts +4 -0
- package/dist/security/react/pages/SecurityPage.js +10 -0
- package/dist/security/react/tabs/AccessTokensTab.d.ts +4 -0
- package/dist/security/react/tabs/AccessTokensTab.js +10 -0
- package/dist/security/react/tabs/RootKeyTab.d.ts +4 -0
- package/dist/security/react/tabs/RootKeyTab.js +9 -0
- package/dist/security/rules.d.ts +57 -0
- package/dist/security/rules.js +63 -0
- package/dist/security/security.module.d.ts +45 -0
- package/dist/security/security.module.js +5 -0
- package/dist/source-control/adapters/http.d.ts +6 -0
- package/dist/source-control/adapters/http.js +13 -0
- package/dist/source-control/adapters/memory.d.ts +9 -0
- package/dist/source-control/adapters/memory.js +35 -0
- package/dist/source-control/conformance/source-control-api.conformance.d.ts +10 -0
- package/dist/source-control/conformance/source-control-api.conformance.js +48 -0
- package/dist/source-control/controllers/source-control.controller.d.ts +26 -0
- package/dist/source-control/controllers/source-control.controller.js +70 -0
- package/dist/source-control/index.d.ts +8 -0
- package/dist/source-control/index.js +6 -0
- package/dist/source-control/messages.en.d.ts +11 -0
- package/dist/source-control/messages.en.js +2 -0
- package/dist/source-control/models.d.ts +70 -0
- package/dist/source-control/models.js +2 -0
- package/dist/source-control/ports.d.ts +23 -0
- package/dist/source-control/ports.js +3 -0
- package/dist/source-control/react/components/SourceControlCredentialFields.d.ts +5 -0
- package/dist/source-control/react/components/SourceControlCredentialFields.js +8 -0
- package/dist/source-control/react/components/SourceControlProviderRow.d.ts +5 -0
- package/dist/source-control/react/components/SourceControlProviderRow.js +7 -0
- package/dist/source-control/react/hooks/ProvidersTab.hooks.d.ts +45 -0
- package/dist/source-control/react/hooks/ProvidersTab.hooks.js +20 -0
- package/dist/source-control/react/hooks/SourceControlCredentialFields.hooks.d.ts +43 -0
- package/dist/source-control/react/hooks/SourceControlCredentialFields.hooks.js +9 -0
- package/dist/source-control/react/hooks/SourceControlPage.hooks.d.ts +17 -0
- package/dist/source-control/react/hooks/SourceControlPage.hooks.js +5 -0
- package/dist/source-control/react/hooks/SourceControlPorts.hooks.d.ts +9 -0
- package/dist/source-control/react/hooks/SourceControlPorts.hooks.js +6 -0
- package/dist/source-control/react/index.d.ts +156 -0
- package/dist/source-control/react/index.js +10 -0
- package/dist/source-control/react/pages/SourceControlPage.d.ts +4 -0
- package/dist/source-control/react/pages/SourceControlPage.js +11 -0
- package/dist/source-control/react/tabs/ProvidersTab.d.ts +4 -0
- package/dist/source-control/react/tabs/ProvidersTab.js +10 -0
- package/dist/source-control/rules.d.ts +32 -0
- package/dist/source-control/rules.js +35 -0
- package/dist/source-control/source-control.module.d.ts +36 -0
- package/dist/source-control/source-control.module.js +6 -0
- package/package.json +207 -4
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { countMediaTabs } from '../rules.js';
|
|
2
|
+
import type { MediaApiPort } from '../ports.js';
|
|
3
|
+
import type { MediaAsset, MediaQuery, UploadInput } from '../models.js';
|
|
4
|
+
export interface LibraryState {
|
|
5
|
+
readonly items: readonly MediaAsset[] | null;
|
|
6
|
+
readonly query: MediaQuery;
|
|
7
|
+
readonly loading: boolean;
|
|
8
|
+
readonly busy: boolean;
|
|
9
|
+
readonly pendingIds: readonly string[];
|
|
10
|
+
readonly error: string | null;
|
|
11
|
+
readonly pendingPurge: MediaAsset | null;
|
|
12
|
+
readonly hasUntyped: boolean;
|
|
13
|
+
readonly counts: ReturnType<typeof countMediaTabs> | null;
|
|
14
|
+
}
|
|
15
|
+
export declare function createLibraryController({ api }: {
|
|
16
|
+
api: MediaApiPort;
|
|
17
|
+
}, { query }?: {
|
|
18
|
+
query?: MediaQuery;
|
|
19
|
+
}): {
|
|
20
|
+
getSnapshot: () => Readonly<LibraryState>;
|
|
21
|
+
subscribe: ({ listener }: {
|
|
22
|
+
listener: () => void;
|
|
23
|
+
}) => () => void;
|
|
24
|
+
load: (_required?: Record<string, never>, _optional?: Record<string, never>) => Promise<void>;
|
|
25
|
+
setQuery({ query }: {
|
|
26
|
+
query: MediaQuery;
|
|
27
|
+
}, _optional?: Record<string, never>): Promise<void>;
|
|
28
|
+
upload({ input, alt }: {
|
|
29
|
+
input: UploadInput;
|
|
30
|
+
alt?: string;
|
|
31
|
+
}, _optional?: Record<string, never>): Promise<boolean>;
|
|
32
|
+
trash({ id }: {
|
|
33
|
+
id: string;
|
|
34
|
+
}, _optional?: Record<string, never>): Promise<boolean>;
|
|
35
|
+
restore({ id }: {
|
|
36
|
+
id: string;
|
|
37
|
+
}, _optional?: Record<string, never>): Promise<boolean>;
|
|
38
|
+
requestPurge({ item }: {
|
|
39
|
+
item: MediaAsset;
|
|
40
|
+
}, _optional?: Record<string, never>): void;
|
|
41
|
+
cancelPurge(_required?: Record<string, never>, _optional?: Record<string, never>): void;
|
|
42
|
+
confirmPurge({ confirmed }: {
|
|
43
|
+
confirmed: boolean;
|
|
44
|
+
}, _optional?: Record<string, never>): Promise<boolean>;
|
|
45
|
+
dispose(_required?: Record<string, never>, _optional?: Record<string, never>): void;
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=library.controller.d.ts.map
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createControllerStore } from '../../core/module/controller-store.js';
|
|
2
|
+
import { queryMedia, countMediaTabs, canRestoreMedia, isRetryableMediaError } from '../rules.js';
|
|
3
|
+
export function createLibraryController({ api }, { query = {} } = {}) {
|
|
4
|
+
const store = createControllerStore({
|
|
5
|
+
initial: {
|
|
6
|
+
items: null,
|
|
7
|
+
query,
|
|
8
|
+
loading: false,
|
|
9
|
+
busy: false,
|
|
10
|
+
pendingIds: [],
|
|
11
|
+
error: null,
|
|
12
|
+
pendingPurge: null,
|
|
13
|
+
hasUntyped: false,
|
|
14
|
+
counts: null,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
let library = null;
|
|
18
|
+
let readFailed = false, retryAttempt = 0;
|
|
19
|
+
let retryTimer = null;
|
|
20
|
+
function cancelRetry() {
|
|
21
|
+
if (retryTimer !== null)
|
|
22
|
+
clearTimeout(retryTimer);
|
|
23
|
+
retryTimer = null;
|
|
24
|
+
}
|
|
25
|
+
let readAbort = null, generation = 0;
|
|
26
|
+
async function load(_required = {}, _optional = {}) {
|
|
27
|
+
if (store.signal.aborted)
|
|
28
|
+
return;
|
|
29
|
+
cancelRetry();
|
|
30
|
+
readAbort?.abort();
|
|
31
|
+
const abort = new AbortController();
|
|
32
|
+
readAbort = abort;
|
|
33
|
+
const ticket = ++generation;
|
|
34
|
+
store.set({ patch: { loading: true, error: null } });
|
|
35
|
+
try {
|
|
36
|
+
const all = await api.list({}, { signal: abort.signal });
|
|
37
|
+
const items = queryMedia({ media: all, query: store.getSnapshot().query });
|
|
38
|
+
if (ticket === generation) {
|
|
39
|
+
readFailed = false;
|
|
40
|
+
retryAttempt = 0;
|
|
41
|
+
library = all;
|
|
42
|
+
store.set({
|
|
43
|
+
patch: {
|
|
44
|
+
items: Object.freeze(items.map((item) => Object.freeze({ ...item }))),
|
|
45
|
+
counts: countMediaTabs({ media: all }),
|
|
46
|
+
hasUntyped: all.some((item) => item.contentType === null),
|
|
47
|
+
error: null,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// A later background failure must not blank an already-rendered screen.
|
|
54
|
+
if (!abort.signal.aborted && ticket === generation) {
|
|
55
|
+
readFailed = true;
|
|
56
|
+
store.set({
|
|
57
|
+
patch: { loading: false, error: error instanceof Error ? error.message : 'Failed to load media' },
|
|
58
|
+
});
|
|
59
|
+
// Retry reads only, never destructive writes. Keep trying at a capped delay so
|
|
60
|
+
// recovery needs no manual repair, while an offline service isn't hammered.
|
|
61
|
+
if (isRetryableMediaError({ error })) {
|
|
62
|
+
const delay = Math.min(1000 * 2 ** Math.min(retryAttempt++, 5), 30000);
|
|
63
|
+
retryTimer = setTimeout(() => { retryTimer = null; void load(); }, delay);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
if (ticket === generation)
|
|
69
|
+
store.set({ patch: { loading: false } });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let writes = Promise.resolve();
|
|
73
|
+
let exclusiveWrite = false;
|
|
74
|
+
function write(run, id) {
|
|
75
|
+
if (store.signal.aborted || exclusiveWrite || (id === undefined ? store.getSnapshot().busy : store.getSnapshot().pendingIds.includes(id)))
|
|
76
|
+
return Promise.resolve(false);
|
|
77
|
+
if (id === undefined)
|
|
78
|
+
exclusiveWrite = true;
|
|
79
|
+
// Serialize accepted writes rather than dropping another card's click. Reserve
|
|
80
|
+
// each id immediately so a double click on the same asset cannot enqueue twice.
|
|
81
|
+
store.set({ patch: { busy: true, pendingIds: id === undefined ? store.getSnapshot().pendingIds : [...store.getSnapshot().pendingIds, id] } });
|
|
82
|
+
const operation = writes.then(async () => {
|
|
83
|
+
if (store.signal.aborted)
|
|
84
|
+
return false;
|
|
85
|
+
// Starting a write clears other writes' failures, so a stale failure from one must
|
|
86
|
+
// not survive past the start of an unrelated one.
|
|
87
|
+
generation++;
|
|
88
|
+
readAbort?.abort();
|
|
89
|
+
cancelRetry();
|
|
90
|
+
store.set({ patch: { loading: false, error: null } });
|
|
91
|
+
try {
|
|
92
|
+
await run();
|
|
93
|
+
if (store.signal.aborted)
|
|
94
|
+
return false;
|
|
95
|
+
await load();
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (!store.signal.aborted)
|
|
100
|
+
store.set({ patch: { loading: false, error: error instanceof Error ? error.message : 'Media write failed' } });
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
if (id === undefined)
|
|
105
|
+
exclusiveWrite = false;
|
|
106
|
+
const pendingIds = store.getSnapshot().pendingIds.filter(value => value !== id);
|
|
107
|
+
store.set({ patch: { busy: pendingIds.length > 0, pendingIds } });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
writes = operation.catch(() => undefined);
|
|
111
|
+
return operation;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
getSnapshot: store.getSnapshot,
|
|
115
|
+
subscribe: store.subscribe,
|
|
116
|
+
load,
|
|
117
|
+
async setQuery({ query }, _optional = {}) {
|
|
118
|
+
// Already-fetched full library owns display order/filtering. Tab/search/order changes
|
|
119
|
+
// never spend another request or flash a loading zero badge.
|
|
120
|
+
store.set({ patch: { query, ...(library ? { items: queryMedia({ media: library, query }) } : {}) } });
|
|
121
|
+
if (!library || readFailed)
|
|
122
|
+
await load();
|
|
123
|
+
},
|
|
124
|
+
async upload({ input, alt = '' }, _optional = {}) {
|
|
125
|
+
return write(() => api.upload(input, { alt, signal: store.signal }));
|
|
126
|
+
},
|
|
127
|
+
// Trashing (the reversible first rung of the ladder) has no confirm step — only
|
|
128
|
+
// the irreversible purge step gates on the dialog, matching the original asymmetry.
|
|
129
|
+
async trash({ id }, _optional = {}) {
|
|
130
|
+
return write(() => api.trash({ id }, { signal: store.signal }), id);
|
|
131
|
+
},
|
|
132
|
+
async restore({ id }, _optional = {}) {
|
|
133
|
+
if (!canRestoreMedia({ api })) {
|
|
134
|
+
store.set({ patch: { error: 'Restoring media is unavailable' } });
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return write(() => api.restore({ id }, { signal: store.signal }), id);
|
|
138
|
+
},
|
|
139
|
+
requestPurge({ item }, _optional = {}) {
|
|
140
|
+
if (!store.getSnapshot().busy && item.status === 'trashed')
|
|
141
|
+
store.set({ patch: { pendingPurge: item } });
|
|
142
|
+
},
|
|
143
|
+
cancelPurge(_required = {}, _optional = {}) {
|
|
144
|
+
if (!store.getSnapshot().busy)
|
|
145
|
+
store.set({ patch: { pendingPurge: null } });
|
|
146
|
+
},
|
|
147
|
+
async confirmPurge({ confirmed }, _optional = {}) {
|
|
148
|
+
const item = store.getSnapshot().pendingPurge;
|
|
149
|
+
if (!confirmed || !item)
|
|
150
|
+
return false;
|
|
151
|
+
const saved = await write(async () => {
|
|
152
|
+
const result = await api.delete({ id: item.id }, { signal: store.signal });
|
|
153
|
+
if (!result.purged)
|
|
154
|
+
throw new Error('The file was not permanently deleted');
|
|
155
|
+
// A failed follow-up read must not leave a permanently deleted card focusable.
|
|
156
|
+
if (library) {
|
|
157
|
+
library = library.filter(row => row.id !== item.id);
|
|
158
|
+
store.set({ patch: { items: queryMedia({ media: library, query: store.getSnapshot().query }), counts: countMediaTabs({ media: library }) } });
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
if (saved)
|
|
162
|
+
store.set({ patch: { pendingPurge: null } });
|
|
163
|
+
return saved;
|
|
164
|
+
},
|
|
165
|
+
dispose(_required = {}, _optional = {}) {
|
|
166
|
+
generation++;
|
|
167
|
+
cancelRetry();
|
|
168
|
+
readAbort?.abort();
|
|
169
|
+
store.dispose();
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=library.controller.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MediaProvidersPort } from '../ports.js';
|
|
2
|
+
import type { MediaProvider } from '../models.js';
|
|
3
|
+
export declare function createProvidersController({ api }: {
|
|
4
|
+
api: MediaProvidersPort;
|
|
5
|
+
}, _optional?: Record<string, never>): {
|
|
6
|
+
getSnapshot: () => Readonly<{
|
|
7
|
+
items: readonly MediaProvider[];
|
|
8
|
+
loading: boolean;
|
|
9
|
+
saving: boolean;
|
|
10
|
+
error: string | null;
|
|
11
|
+
}>;
|
|
12
|
+
subscribe: ({ listener }: {
|
|
13
|
+
listener: () => void;
|
|
14
|
+
}) => () => void;
|
|
15
|
+
load: (_required?: Record<string, never>, _optional?: Record<string, never>) => Promise<void>;
|
|
16
|
+
save({ id, credential }: {
|
|
17
|
+
id: string;
|
|
18
|
+
credential: string;
|
|
19
|
+
}, _optional?: Record<string, never>): Promise<boolean>;
|
|
20
|
+
saveSettings({ id, baseUrl, model }: {
|
|
21
|
+
id: string;
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
model: string;
|
|
24
|
+
}, _optional?: Record<string, never>): Promise<boolean>;
|
|
25
|
+
remove({ id }: {
|
|
26
|
+
id: string;
|
|
27
|
+
}, _optional?: Record<string, never>): Promise<boolean>;
|
|
28
|
+
dispose(_required?: Record<string, never>, _optional?: Record<string, never>): void;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=providers.controller.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createControllerStore } from '../../core/module/controller-store.js';
|
|
2
|
+
export function createProvidersController({ api }, _optional = {}) {
|
|
3
|
+
const store = createControllerStore({
|
|
4
|
+
initial: {
|
|
5
|
+
items: [],
|
|
6
|
+
loading: false,
|
|
7
|
+
saving: false,
|
|
8
|
+
error: null,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
let generation = 0;
|
|
12
|
+
async function load(_required = {}, _optional = {}) {
|
|
13
|
+
if (store.signal.aborted)
|
|
14
|
+
return;
|
|
15
|
+
const ticket = ++generation;
|
|
16
|
+
store.set({ patch: { loading: true } });
|
|
17
|
+
try {
|
|
18
|
+
const items = await api.list({}, { signal: store.signal });
|
|
19
|
+
// null leaves local edits untouched; [] is an authoritative empty answer.
|
|
20
|
+
if (ticket === generation)
|
|
21
|
+
store.set({
|
|
22
|
+
patch: items === null
|
|
23
|
+
? { error: 'Provider service unavailable' }
|
|
24
|
+
: { items: Object.freeze([...items]), error: null },
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
if (ticket === generation)
|
|
29
|
+
store.set({ patch: { error: 'Provider service unavailable' } });
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
if (ticket === generation)
|
|
33
|
+
store.set({ patch: { loading: false } });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function write(run) {
|
|
37
|
+
if (store.signal.aborted || store.getSnapshot().saving)
|
|
38
|
+
return false;
|
|
39
|
+
store.set({ patch: { saving: true, error: null } });
|
|
40
|
+
try {
|
|
41
|
+
await run();
|
|
42
|
+
if (store.signal.aborted)
|
|
43
|
+
return false;
|
|
44
|
+
await load();
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
store.set({ patch: { error: 'Failed to save provider credentials' } });
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
store.set({ patch: { saving: false } });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
getSnapshot: store.getSnapshot,
|
|
57
|
+
subscribe: store.subscribe,
|
|
58
|
+
load,
|
|
59
|
+
save({ id, credential }, _optional = {}) {
|
|
60
|
+
return write(() => api.saveCredential({ id, credential }, { signal: store.signal }));
|
|
61
|
+
},
|
|
62
|
+
saveSettings({ id, baseUrl, model }, _optional = {}) {
|
|
63
|
+
if (!api.saveSettings)
|
|
64
|
+
return Promise.resolve(false);
|
|
65
|
+
return write(() => api.saveSettings({ id, baseUrl, model }, { signal: store.signal }));
|
|
66
|
+
},
|
|
67
|
+
remove({ id }, _optional = {}) {
|
|
68
|
+
return write(() => api.removeCredential({ id }, { signal: store.signal }));
|
|
69
|
+
},
|
|
70
|
+
dispose(_required = {}, _optional = {}) {
|
|
71
|
+
generation++;
|
|
72
|
+
store.dispose();
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=providers.controller.js.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** Browser-only live metadata hint, separate from server validation across the browser/Node
|
|
2
|
+
* boundary. The server must enforce the same policy on writes and rendering independently.
|
|
3
|
+
* Hints never gate Save: an invalid attribute once silently blocked saving unrelated title/alt/
|
|
4
|
+
* caption/credit/class changes. Rejections must instead reach the authoritative atomic write.
|
|
5
|
+
* One flat allowlist includes global and image/video extras: per-element name filtering is
|
|
6
|
+
* unnecessary because an unused loading attribute on a wrapper is harmless. Extend the list,
|
|
7
|
+
* not the tokenizer, when adding an allowed name. data-/aria- families remain open-ended. */
|
|
8
|
+
export declare const MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES: readonly ["class", "id", "style", "title", "role", "tabindex", "lang", "dir", "hidden", "translate", "draggable", "loading", "decoding", "playsinline", "muted", "loop", "autoplay", "poster"];
|
|
9
|
+
/** Whether `name` is on the allowlist — a well-formed attribute name
|
|
10
|
+
* ({@link MEDIA_HTML_ATTRIBUTE_NAME_PATTERN}) that is either an exact match against
|
|
11
|
+
* {@link MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES} or carries a `data-`/`aria-` prefix (both open-ended
|
|
12
|
+
* families with no fixed suffix list). Case-insensitive: HTML attribute names are themselves
|
|
13
|
+
* case-insensitive, and an operator typing `DATA-FOO` should not slip past a lowercase-only check.
|
|
14
|
+
*
|
|
15
|
+
* @complexity O(n) in the name's length (one anchored regex test), then O(1) — one prefix check,
|
|
16
|
+
* one fixed-length array lookup.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isAllowedMediaHtmlAttributeName({ name }: {
|
|
19
|
+
name: string;
|
|
20
|
+
}, _optional?: Record<string, never>): boolean;
|
|
21
|
+
/** Why one parsed attribute token was rejected — see {@link parseMediaHtmlAttributes}'s own doc for
|
|
22
|
+
* why `event-handler`/`unsafe-url` are checked, and reported, ahead of plain allowlist membership.
|
|
23
|
+
* `unsafe-url` replaces the old `javascript-url` name (2026-09-23: it now also covers
|
|
24
|
+
* `vbscript:`/`data:` schemes, not just `javascript:`) — the i18n message key for it is unchanged.
|
|
25
|
+
* `unsafe-style` is new: a `style` value carrying a known CSS injection/escape vector. */
|
|
26
|
+
export type MediaHtmlAttributeRejectionReason = "disallowed-name" | "event-handler" | "unsafe-url" | "unsafe-style" | "malformed";
|
|
27
|
+
export interface MediaHtmlAttributeError {
|
|
28
|
+
reason: MediaHtmlAttributeRejectionReason;
|
|
29
|
+
/** The exact attribute name (or, for `malformed`, the unparsable fragment) the caller's error
|
|
30
|
+
* message must name — never a generic "invalid input" (owner requirement: a visible, specific
|
|
31
|
+
* error naming the rejected attribute). */
|
|
32
|
+
attribute: string;
|
|
33
|
+
}
|
|
34
|
+
export interface ParsedMediaHtmlAttributes {
|
|
35
|
+
/** Lowercased attribute name -> value, for every token that WAS accepted. Never all-or-nothing
|
|
36
|
+
* except on `malformed` (see {@link parseMediaHtmlAttributes}'s own header): a rejected token
|
|
37
|
+
* (2026-09-23) is simply absent here while every other accepted token in the same string still
|
|
38
|
+
* lands. A boolean attribute (`muted`, written with no `="..."`) maps to `""`. */
|
|
39
|
+
attributes: Record<string, string>;
|
|
40
|
+
/** Every rejection found, in the order encountered — zero or more (2026-09-23). Unlike
|
|
41
|
+
* `attributes`, this is never reset to empty except that a `malformed` entry is always the last
|
|
42
|
+
* one recorded (parsing stops there). */
|
|
43
|
+
errors: MediaHtmlAttributeError[];
|
|
44
|
+
/** The FIRST rejection in {@link errors}, or `null` when every token was accepted — kept as its
|
|
45
|
+
* own field for the existing single-reason hint consumers (`MediaEditDialog.hooks.tsx`,
|
|
46
|
+
* `use-edit-media-panel.hooks.ts`) that only ever show one reason at a time. */
|
|
47
|
+
error: MediaHtmlAttributeError | null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Parses the `HTML attributes` field's free text (`name="value" name2="value2"`, or a bare
|
|
51
|
+
* boolean `name`) into a validated attribute map, per-token (ported 2026-09-23 from the shared
|
|
52
|
+
* server parser, `html-attributes.ts`'s `parseEmbedHtmlAttributes`): an accepted token lands in
|
|
53
|
+
* `attributes`, a rejected one lands in `errors` and is simply omitted — this string never fails
|
|
54
|
+
* closed as a whole EXCEPT when the tokenizer itself loses sync (a stray quote or other unparseable
|
|
55
|
+
* fragment), in which case `attributes` is `{}` and the sole entry in `errors` has
|
|
56
|
+
* `reason: "malformed"` (token boundaries downstream of a sync loss cannot be trusted — value text
|
|
57
|
+
* could re-parse as a name — so that one case still discards everything).
|
|
58
|
+
*
|
|
59
|
+
* This is a SECURITY boundary, not a syntax convenience: media metadata is authored in this admin
|
|
60
|
+
* but rendered on the public site, so a free-text HTML-attribute passthrough is a stored-XSS vector
|
|
61
|
+
* (`onerror`, `onclick`, `href="javascript:"`, any `on*` handler, an unsafe `style` value) the
|
|
62
|
+
* moment it reaches a public page. The allowlist in {@link isAllowedMediaHtmlAttributeName} is
|
|
63
|
+
* therefore the ONLY path to acceptance — nothing here tries to sanitize or escape an otherwise-
|
|
64
|
+
* disallowed name or value into something safe; it is rejected outright, and the caller must show
|
|
65
|
+
* the reason (not silently drop it) so an operator can tell a typo from a hard "no". NOTE: this
|
|
66
|
+
* validator runs in the admin only — see this repo's media-admin-ui report for where the write and
|
|
67
|
+
* render paths that would actually persist and emit this attribute still need the SAME allowlist
|
|
68
|
+
* enforced server-side (a client-only check is not a control, since the API accepts whatever a
|
|
69
|
+
* caller sends).
|
|
70
|
+
*
|
|
71
|
+
* @complexity Time O(n) in `text`'s length (one regex pass over it), space O(k) for k accepted
|
|
72
|
+
* attributes plus O(e) for e rejected tokens.
|
|
73
|
+
*/
|
|
74
|
+
export declare function parseMediaHtmlAttributes({ text }: {
|
|
75
|
+
text: string;
|
|
76
|
+
}, _optional?: Record<string, never>): ParsedMediaHtmlAttributes;
|
|
77
|
+
/** The hint names the rejected token so operators can distinguish a typo from a hard no.
|
|
78
|
+
* This is client feedback only; the server must independently enforce write/render policy. */
|
|
79
|
+
export declare function describeMediaHtmlAttributeError({ error }: {
|
|
80
|
+
error: MediaHtmlAttributeError;
|
|
81
|
+
}, _optional?: Record<string, never>): string;
|
|
82
|
+
//# sourceMappingURL=html-attributes.d.ts.map
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/** Browser-only live metadata hint, separate from server validation across the browser/Node
|
|
2
|
+
* boundary. The server must enforce the same policy on writes and rendering independently.
|
|
3
|
+
* Hints never gate Save: an invalid attribute once silently blocked saving unrelated title/alt/
|
|
4
|
+
* caption/credit/class changes. Rejections must instead reach the authoritative atomic write.
|
|
5
|
+
* One flat allowlist includes global and image/video extras: per-element name filtering is
|
|
6
|
+
* unnecessary because an unused loading attribute on a wrapper is harmless. Extend the list,
|
|
7
|
+
* not the tokenizer, when adding an allowed name. data-/aria- families remain open-ended. */
|
|
8
|
+
export const MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES = [
|
|
9
|
+
// Global attributes (2026-09-23).
|
|
10
|
+
"class",
|
|
11
|
+
"id",
|
|
12
|
+
"style",
|
|
13
|
+
"title",
|
|
14
|
+
"role",
|
|
15
|
+
"tabindex",
|
|
16
|
+
"lang",
|
|
17
|
+
"dir",
|
|
18
|
+
"hidden",
|
|
19
|
+
"translate",
|
|
20
|
+
"draggable",
|
|
21
|
+
// Pre-existing <img>/<video> extras, kept everywhere (one list, not one per element).
|
|
22
|
+
"loading",
|
|
23
|
+
"decoding",
|
|
24
|
+
"playsinline",
|
|
25
|
+
"muted",
|
|
26
|
+
"loop",
|
|
27
|
+
"autoplay",
|
|
28
|
+
"poster",
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* The shape EVERY accepted attribute name must have, checked before allowlist membership
|
|
32
|
+
* (2026-09-07 stored-XSS fix; keep identical in `@jini-ai/cms/media`'s `html-attributes.ts` — see
|
|
33
|
+
* that file's copy of this comment for the full incident).
|
|
34
|
+
*
|
|
35
|
+
* The `data-`/`aria-` families below are open-ended by design — no fixed suffix list — so the
|
|
36
|
+
* prefix check alone accepted whatever characters {@link HTML_ATTRIBUTE_TOKEN}'s name class
|
|
37
|
+
* (`[^\s="']+`, which excludes only whitespace, `=` and quotes) let through. `<`, `>` and `/` are
|
|
38
|
+
* all legal in that class, and the server renderer templating a name into ` name="value"` escapes
|
|
39
|
+
* only the VALUE — so a stored `data-x><svg/onload=alert(1)` closed the `<img>` and opened a live
|
|
40
|
+
* `<svg onload>` on the public page. Constraining the NAME to the characters a real HTML attribute
|
|
41
|
+
* name can contain is what makes "the value is escaped" sufficient.
|
|
42
|
+
*/
|
|
43
|
+
const MEDIA_HTML_ATTRIBUTE_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
44
|
+
/** Whether `name` is on the allowlist — a well-formed attribute name
|
|
45
|
+
* ({@link MEDIA_HTML_ATTRIBUTE_NAME_PATTERN}) that is either an exact match against
|
|
46
|
+
* {@link MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES} or carries a `data-`/`aria-` prefix (both open-ended
|
|
47
|
+
* families with no fixed suffix list). Case-insensitive: HTML attribute names are themselves
|
|
48
|
+
* case-insensitive, and an operator typing `DATA-FOO` should not slip past a lowercase-only check.
|
|
49
|
+
*
|
|
50
|
+
* @complexity O(n) in the name's length (one anchored regex test), then O(1) — one prefix check,
|
|
51
|
+
* one fixed-length array lookup.
|
|
52
|
+
*/
|
|
53
|
+
export function isAllowedMediaHtmlAttributeName({ name }, _optional = {}) {
|
|
54
|
+
const lower = name.toLowerCase();
|
|
55
|
+
if (!MEDIA_HTML_ATTRIBUTE_NAME_PATTERN.test(lower))
|
|
56
|
+
return false;
|
|
57
|
+
if (lower.startsWith("data-") || lower.startsWith("aria-"))
|
|
58
|
+
return true;
|
|
59
|
+
return MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES.includes(lower);
|
|
60
|
+
}
|
|
61
|
+
/** Matches one `name`, or one `name="value"`/`name='value'`/`name=value` pair — the same loose
|
|
62
|
+
* shape real HTML attribute syntax allows, since that is the syntax an operator typing this field
|
|
63
|
+
* would naturally reach for. */
|
|
64
|
+
const HTML_ATTRIBUTE_TOKEN = /([^\s="']+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s"']+)))?/g;
|
|
65
|
+
/** The highest ASCII code point {@link isUnsafeMediaUrlValue} strips: every C0 control character
|
|
66
|
+
* plus the ordinary space, i.e. code points `0`-`32` inclusive. Named rather than written as an
|
|
67
|
+
* inline regex control-character range so the linter never has to reason about a literal control
|
|
68
|
+
* character inside a pattern. Ported from the shared server parser, `html-attributes.ts`. */
|
|
69
|
+
const ASCII_WHITESPACE_OR_CONTROL_MAX_CODE_POINT = 0x20;
|
|
70
|
+
/**
|
|
71
|
+
* Whether `value`'s scheme, once every ASCII whitespace/control character (code points `0`-`32`) is
|
|
72
|
+
* stripped and the result lowercased, starts with `javascript:`, `vbscript:` or `data:` — the three
|
|
73
|
+
* schemes a browser will still execute or navigate to even when the URL carries interior whitespace
|
|
74
|
+
* a naive end-trim (the old `.trim()`-only check) would miss, e.g. `java\tscript:x`: browsers strip
|
|
75
|
+
* tabs and newlines from inside a URL before parsing its scheme, so this check must too. Ported
|
|
76
|
+
* 2026-09-23 from the shared server parser's `isUnsafeUrlValue` (`html-attributes.ts`).
|
|
77
|
+
*
|
|
78
|
+
* @complexity O(n) in the value's length (one filter pass, one prefix test).
|
|
79
|
+
*/
|
|
80
|
+
function isUnsafeMediaUrlValue(value) {
|
|
81
|
+
let stripped = "";
|
|
82
|
+
for (const char of value) {
|
|
83
|
+
if ((char.codePointAt(0) ?? 0) > ASCII_WHITESPACE_OR_CONTROL_MAX_CODE_POINT)
|
|
84
|
+
stripped += char;
|
|
85
|
+
}
|
|
86
|
+
stripped = stripped.toLowerCase();
|
|
87
|
+
return stripped.startsWith("javascript:") || stripped.startsWith("vbscript:") || stripped.startsWith("data:");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Whether a `style` value carries a known CSS injection or CSS-escape-bypass vector:
|
|
91
|
+
* `expression(...)` (legacy IE script-in-CSS), a `javascript:`/`vbscript:` value inside a CSS
|
|
92
|
+
* `url(...)`, `-moz-binding`/`behavior:` (XBL/HTC script bindings), `@import` (pulls in a second,
|
|
93
|
+
* unvetted stylesheet), or ANY backslash — a CSS escape sequence (`\65` is `e`) can respell any of
|
|
94
|
+
* the above past a plain substring check, so a backslash anywhere in the value is rejected outright.
|
|
95
|
+
* Plain `url(...)` values (background images) are otherwise allowed. Ported 2026-09-23 from the
|
|
96
|
+
* shared server parser's `isUnsafeStyleValue` (`html-attributes.ts`).
|
|
97
|
+
*
|
|
98
|
+
* @complexity O(n) in the value's length (one regex test).
|
|
99
|
+
*/
|
|
100
|
+
const UNSAFE_MEDIA_STYLE_PATTERN = /expression\(|javascript:|vbscript:|-moz-binding|behavior:|@import|\\/i;
|
|
101
|
+
function isUnsafeMediaStyleValue(value) {
|
|
102
|
+
return UNSAFE_MEDIA_STYLE_PATTERN.test(value);
|
|
103
|
+
}
|
|
104
|
+
/** Classifies one already-tokenized `name`/`value` pair. Split out of {@link parseMediaHtmlAttributes}
|
|
105
|
+
* so each rejection reason is its own directly testable branch, and so the loop below reads as
|
|
106
|
+
* "tokenize, then classify" rather than one function doing both.
|
|
107
|
+
*
|
|
108
|
+
* Order matters (ported 2026-09-23 from the shared server parser's `classifyEmbedHtmlAttributeToken`):
|
|
109
|
+
* `on*` is checked FIRST (so an `onerror` always reports as `event-handler`, the more actionable
|
|
110
|
+
* reason, never the generic `disallowed-name`), then the URL-scheme check runs against EVERY value
|
|
111
|
+
* regardless of name (matching the pre-existing `javascript:` check this is ported from), then
|
|
112
|
+
* allowlist membership, then — only for an already-allowed `style` — the CSS-specific unsafe-value
|
|
113
|
+
* check.
|
|
114
|
+
*
|
|
115
|
+
* @complexity O(1) — four fixed checks against one already-extracted token; each check's own O(n) in
|
|
116
|
+
* the token's length is linear, not nested.
|
|
117
|
+
*/
|
|
118
|
+
function classifyMediaHtmlAttributeToken(name, value) {
|
|
119
|
+
if (name.startsWith("on"))
|
|
120
|
+
return { reason: "event-handler", attribute: name };
|
|
121
|
+
if (isUnsafeMediaUrlValue(value))
|
|
122
|
+
return { reason: "unsafe-url", attribute: name };
|
|
123
|
+
if (!isAllowedMediaHtmlAttributeName({ name }))
|
|
124
|
+
return { reason: "disallowed-name", attribute: name };
|
|
125
|
+
if (name === "style" && isUnsafeMediaStyleValue(value))
|
|
126
|
+
return { reason: "unsafe-style", attribute: name };
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Parses the `HTML attributes` field's free text (`name="value" name2="value2"`, or a bare
|
|
131
|
+
* boolean `name`) into a validated attribute map, per-token (ported 2026-09-23 from the shared
|
|
132
|
+
* server parser, `html-attributes.ts`'s `parseEmbedHtmlAttributes`): an accepted token lands in
|
|
133
|
+
* `attributes`, a rejected one lands in `errors` and is simply omitted — this string never fails
|
|
134
|
+
* closed as a whole EXCEPT when the tokenizer itself loses sync (a stray quote or other unparseable
|
|
135
|
+
* fragment), in which case `attributes` is `{}` and the sole entry in `errors` has
|
|
136
|
+
* `reason: "malformed"` (token boundaries downstream of a sync loss cannot be trusted — value text
|
|
137
|
+
* could re-parse as a name — so that one case still discards everything).
|
|
138
|
+
*
|
|
139
|
+
* This is a SECURITY boundary, not a syntax convenience: media metadata is authored in this admin
|
|
140
|
+
* but rendered on the public site, so a free-text HTML-attribute passthrough is a stored-XSS vector
|
|
141
|
+
* (`onerror`, `onclick`, `href="javascript:"`, any `on*` handler, an unsafe `style` value) the
|
|
142
|
+
* moment it reaches a public page. The allowlist in {@link isAllowedMediaHtmlAttributeName} is
|
|
143
|
+
* therefore the ONLY path to acceptance — nothing here tries to sanitize or escape an otherwise-
|
|
144
|
+
* disallowed name or value into something safe; it is rejected outright, and the caller must show
|
|
145
|
+
* the reason (not silently drop it) so an operator can tell a typo from a hard "no". NOTE: this
|
|
146
|
+
* validator runs in the admin only — see this repo's media-admin-ui report for where the write and
|
|
147
|
+
* render paths that would actually persist and emit this attribute still need the SAME allowlist
|
|
148
|
+
* enforced server-side (a client-only check is not a control, since the API accepts whatever a
|
|
149
|
+
* caller sends).
|
|
150
|
+
*
|
|
151
|
+
* @complexity Time O(n) in `text`'s length (one regex pass over it), space O(k) for k accepted
|
|
152
|
+
* attributes plus O(e) for e rejected tokens.
|
|
153
|
+
*/
|
|
154
|
+
export function parseMediaHtmlAttributes({ text }, _optional = {}) {
|
|
155
|
+
const trimmed = text.trim();
|
|
156
|
+
if (trimmed === "")
|
|
157
|
+
return { attributes: {}, errors: [], error: null };
|
|
158
|
+
const attributes = {};
|
|
159
|
+
const errors = [];
|
|
160
|
+
HTML_ATTRIBUTE_TOKEN.lastIndex = 0;
|
|
161
|
+
let consumed = 0;
|
|
162
|
+
// A `for` loop (rather than `while ((match = …exec(trimmed)) !== null)`) keeps every assignment
|
|
163
|
+
// to `match` in statement position, never inside the loop's own test expression.
|
|
164
|
+
for (let match = HTML_ATTRIBUTE_TOKEN.exec(trimmed); match !== null; match = HTML_ATTRIBUTE_TOKEN.exec(trimmed)) {
|
|
165
|
+
// Non-whitespace text between the previous match and this one is a fragment the token pattern
|
|
166
|
+
// could not parse as a name (e.g. a stray quote) — the tokenizer has lost sync, so everything
|
|
167
|
+
// parsed so far is discarded rather than trusted.
|
|
168
|
+
const skipped = trimmed.slice(consumed, match.index);
|
|
169
|
+
if (skipped.trim() !== "") {
|
|
170
|
+
const malformed = { reason: "malformed", attribute: skipped.trim() };
|
|
171
|
+
errors.push(malformed);
|
|
172
|
+
return { attributes: {}, errors, error: malformed };
|
|
173
|
+
}
|
|
174
|
+
consumed = match.index + match[0].length;
|
|
175
|
+
const name = match[1].toLowerCase();
|
|
176
|
+
const value = match[2] ?? match[3] ?? match[4] ?? "";
|
|
177
|
+
const rejection = classifyMediaHtmlAttributeToken(name, value);
|
|
178
|
+
if (rejection) {
|
|
179
|
+
errors.push(rejection);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
attributes[name] = value;
|
|
183
|
+
}
|
|
184
|
+
const trailing = trimmed.slice(consumed);
|
|
185
|
+
if (trailing.trim() !== "") {
|
|
186
|
+
const malformed = { reason: "malformed", attribute: trailing.trim() };
|
|
187
|
+
errors.push(malformed);
|
|
188
|
+
return { attributes: {}, errors, error: malformed };
|
|
189
|
+
}
|
|
190
|
+
return { attributes, errors, error: errors[0] ?? null };
|
|
191
|
+
}
|
|
192
|
+
/** The hint names the rejected token so operators can distinguish a typo from a hard no.
|
|
193
|
+
* This is client feedback only; the server must independently enforce write/render policy. */
|
|
194
|
+
export function describeMediaHtmlAttributeError({ error }, _optional = {}) {
|
|
195
|
+
const name = error.attribute;
|
|
196
|
+
switch (error.reason) {
|
|
197
|
+
case 'event-handler': return `Event handler attributes like '${name}' are not allowed.`;
|
|
198
|
+
case 'unsafe-url': return `'${name}' cannot use a javascript: value.`;
|
|
199
|
+
case 'unsafe-style': return `'${name}' cannot use an unsafe style value.`;
|
|
200
|
+
case 'malformed': return `Could not parse HTML attributes near '${name}'.`;
|
|
201
|
+
default: return `'${name}' is not an allowed HTML attribute.`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=html-attributes.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { mediaModule } from './media.module.js';
|
|
2
|
+
export { mediaApiToken, mediaProvidersToken } from './ports.js';
|
|
3
|
+
export type { MediaApiPort, MediaProvidersPort, MediaEventsPort } from './ports.js';
|
|
4
|
+
export type { MediaAsset, MediaQuery, UploadInput, MediaMetadataPatch, MediaRequestOptions, MediaProvider, MediaProviderOption, MediaProviderConfiguration, MediaProviderConfigurationMap, MediaTabId, MediaContentTabId, MediaOrderBy, } from './models.js';
|
|
5
|
+
export { mediaMessagesEn } from './messages.en.js';
|
|
6
|
+
export { filterMediaByTab, resolveActiveTab, queryMedia, diffMediaMetadata, acceptsMedia, safeMediaUrl, canReplaceMedia, canRestoreMedia, isRetryableMediaError, parseOptionalPixelSize, formatByteSize, formatUploadDate, countMediaTabs, mediaEmbedSnippet, mediaRowHandles, } from './rules.js';
|
|
7
|
+
export { createLibraryController } from './controllers/library.controller.js';
|
|
8
|
+
export type { LibraryState } from './controllers/library.controller.js';
|
|
9
|
+
export { createEditMediaController } from './controllers/edit-media.controller.js';
|
|
10
|
+
export { createProvidersController } from './controllers/providers.controller.js';
|
|
11
|
+
export { parseMediaHtmlAttributes, describeMediaHtmlAttributeError, isAllowedMediaHtmlAttributeName, MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES } from './html-attributes.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { mediaModule } from './media.module.js';
|
|
2
|
+
export { mediaApiToken, mediaProvidersToken } from './ports.js';
|
|
3
|
+
export { mediaMessagesEn } from './messages.en.js';
|
|
4
|
+
export { filterMediaByTab, resolveActiveTab, queryMedia, diffMediaMetadata, acceptsMedia, safeMediaUrl, canReplaceMedia, canRestoreMedia, isRetryableMediaError, parseOptionalPixelSize, formatByteSize, formatUploadDate, countMediaTabs, mediaEmbedSnippet, mediaRowHandles, } from './rules.js';
|
|
5
|
+
export { createLibraryController } from './controllers/library.controller.js';
|
|
6
|
+
export { createEditMediaController } from './controllers/edit-media.controller.js';
|
|
7
|
+
export { createProvidersController } from './controllers/providers.controller.js';
|
|
8
|
+
export { parseMediaHtmlAttributes, describeMediaHtmlAttributeError, isAllowedMediaHtmlAttributeName, MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES } from './html-attributes.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|