@notis_ai/cli 0.2.17 → 0.2.19
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/README.md +542 -146
- package/bin/check-runtime.js +15 -0
- package/bin/notis.js +2 -0
- package/config/notis_app_design_rules.json +320 -0
- package/config/portal-route-roots.json +45 -0
- package/dist/agent-hooks/notis-agent-hook.mjs +42323 -18258
- package/dist/base-skills/notis-cli/.notis-base-skill.json +1 -0
- package/dist/base-skills/notis-cli/SKILL.md +51 -280
- package/dist/base-skills/notis-cli/references/app-delivery.md +17 -0
- package/dist/base-skills/notis-cli/references/intelligence.md +94 -0
- package/dist/base-skills/notis-cli/references/native-databases.md +21 -0
- package/dist/base-skills/notis-cli/references/tool-examples.md +56 -0
- package/dist/base-skills/notis-cli/references/troubleshooting.md +67 -0
- package/dist/base-skills/notis-query/.notis-base-skill.json +1 -0
- package/dist/base-skills/notis-query/SKILL.md +35 -699
- package/dist/base-skills/notis-query/references/database-discovery.md +59 -0
- package/dist/base-skills/notis-query/references/documents.md +74 -0
- package/dist/base-skills/notis-query/references/query.md +76 -0
- package/dist/base-skills/notis-spaces/.notis-base-skill.json +1 -0
- package/dist/base-skills/notis-spaces/SKILL.md +218 -0
- package/dist/base-skills/notis-spaces/references/architecture.md +116 -0
- package/dist/base-skills/notis-spaces/references/context.md +81 -0
- package/dist/base-skills/notis-spaces/references/design.md +141 -0
- package/dist/base-skills/notis-spaces/references/fixtures.md +29 -0
- package/dist/base-skills/notis-spaces/references/memory.md +58 -0
- package/dist/base-skills/notis-spaces/references/reading.md +73 -0
- package/dist/base-skills/notis-spaces/references/release.md +120 -0
- package/dist/base-skills/notis-spaces/references/reports.md +77 -0
- package/dist/base-skills/notis-spaces/references/sdk.md +273 -0
- package/dist/base-skills/notis-spaces/references/store.md +118 -0
- package/dist/base-skills/notis-spaces/references/troubleshooting.md +28 -0
- package/dist/base-skills/notis-spaces/references/views.md +229 -0
- package/{template/packages → dist}/sdk/package.json +9 -10
- package/dist/sdk/src/agentContext.ts +36 -0
- package/dist/sdk/src/bundleModule.ts +175 -0
- package/dist/sdk/src/components/Dialog.tsx +103 -0
- package/dist/sdk/src/components/DocumentEditor.tsx +113 -0
- package/dist/sdk/src/components/DocumentPage.tsx +184 -0
- package/{template/packages → dist}/sdk/src/components/Markdown.tsx +9 -3
- package/{template/packages → dist}/sdk/src/components/MarkdownEditor.tsx +26 -5
- package/{template/packages → dist}/sdk/src/components/MultiSelectActionBar.tsx +95 -12
- package/{template/packages → dist}/sdk/src/components/MultiSelectCheckbox.tsx +4 -2
- package/dist/sdk/src/components/NotisCommentBoundary.tsx +172 -0
- package/{template/packages → dist}/sdk/src/components/NotisSelectionBoundary.tsx +9 -5
- package/dist/sdk/src/components/RecordComponents.tsx +111 -0
- package/dist/sdk/src/components/Skeleton.tsx +38 -0
- package/dist/sdk/src/config.ts +31 -0
- package/{template/packages → dist}/sdk/src/documents.ts +21 -6
- package/dist/sdk/src/hooks/useAction.ts +28 -0
- package/dist/sdk/src/hooks/useAgentContext.ts +23 -0
- package/{template/packages → dist}/sdk/src/hooks/useBackend.ts +3 -2
- package/dist/sdk/src/hooks/useCloudComputer.ts +94 -0
- package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
- package/{template/packages → dist}/sdk/src/hooks/useCollectionInteractions.ts +157 -147
- package/dist/sdk/src/hooks/useDatabaseSchema.ts +50 -0
- package/{template/packages → dist}/sdk/src/hooks/useDatabaseSubscription.ts +2 -2
- package/dist/sdk/src/hooks/useDocument.ts +51 -0
- package/{template/packages → dist}/sdk/src/hooks/useDocuments.ts +25 -59
- package/{template/packages → dist}/sdk/src/hooks/useHandover.ts +7 -7
- package/dist/sdk/src/hooks/useLongPressSelection.ts +79 -0
- package/dist/sdk/src/hooks/useMarqueeSelection.ts +194 -0
- package/{template/packages → dist}/sdk/src/hooks/useMultiSelect.ts +2 -8
- package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
- package/dist/sdk/src/hooks/useNativeDocuments.ts +143 -0
- package/{template/packages → dist}/sdk/src/hooks/useNotis.ts +6 -1
- package/dist/sdk/src/hooks/useNotisNavigation.ts +70 -0
- package/dist/sdk/src/hooks/useQuery.ts +81 -0
- package/dist/sdk/src/hooks/useToolQuery.ts +12 -0
- package/dist/sdk/src/hooks/useTopBarSearch.ts +102 -0
- package/dist/sdk/src/hooks/useTopBarTitleLeaf.ts +75 -0
- package/{template/packages → dist}/sdk/src/hooks/useUpsertDocument.ts +1 -1
- package/dist/sdk/src/hooks/useViewParams.ts +195 -0
- package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
- package/{template/packages → dist}/sdk/src/index.ts +64 -1
- package/{template/packages → dist}/sdk/src/interactions/actions.ts +14 -1
- package/{template/packages → dist}/sdk/src/interactions/shortcuts.tsx +129 -23
- package/dist/sdk/src/interactions/visibility.ts +13 -0
- package/{template/packages → dist}/sdk/src/interactions.ts +5 -1
- package/dist/sdk/src/nativeData.ts +39 -0
- package/{template/packages → dist}/sdk/src/provider.tsx +9 -4
- package/dist/sdk/src/queryCache.ts +292 -0
- package/dist/sdk/src/queryErrors.ts +51 -0
- package/dist/sdk/src/runtime.ts +675 -0
- package/dist/sdk/src/runtimeModules.ts +33 -0
- package/dist/sdk/src/space.ts +442 -0
- package/dist/sdk/src/standardViewParams.ts +36 -0
- package/{template/packages → dist}/sdk/src/styles.css +81 -16
- package/dist/sdk/src/tailwind.ts +66 -0
- package/dist/sdk/src/ui/charts/RenderChartData.tsx +25 -0
- package/dist/sdk/src/ui/engine/core.ts +392 -0
- package/dist/sdk/src/ui/engine/dom.ts +183 -0
- package/dist/sdk/src/ui/engine/hooks.ts +195 -0
- package/dist/sdk/src/ui/engine/instance.ts +63 -0
- package/dist/sdk/src/ui/engine/keycapContext.ts +2 -0
- package/dist/sdk/src/ui/engine/registry.ts +262 -0
- package/dist/sdk/src/ui/engine/types.ts +62 -0
- package/dist/sdk/src/ui/icons.tsx +91 -0
- package/dist/sdk/src/ui/util.ts +83 -0
- package/dist/sdk/src/viewPeriod.ts +26 -0
- package/dist/sdk/src/vite.ts +92 -0
- package/dist/sdk/src/words/actions.ts +295 -0
- package/dist/sdk/src/words/clashes.ts +158 -0
- package/dist/sdk/src/words/format.ts +136 -0
- package/dist/sdk/src/words/index.ts +13 -0
- package/dist/sdk/src/words/keymap.ts +72 -0
- package/dist/sdk/src/words/keys.ts +297 -0
- package/dist/sdk/src/words/shell.ts +46 -0
- package/dist/sdk/src/words/types.ts +65 -0
- package/dist/skill-sync/index.js +832 -151
- package/dist/skill-sync/index.js.map +4 -4
- package/dist/skill-sync-worker.mjs +3941 -0
- package/dist/space-harness/host.js +1460 -0
- package/dist/space-sdk/dist/chunk-ZSN57ATH.js +5 -0
- package/dist/space-sdk/dist/index.js +15 -0
- package/dist/space-sdk/dist/ui.js +18 -0
- package/dist/space-sdk/package.json +33 -0
- package/dist/space-sdk/src/agentContext.d.ts +36 -0
- package/dist/space-sdk/src/components/Dialog.d.ts +20 -0
- package/dist/space-sdk/src/components/DocumentEditor.d.ts +46 -0
- package/dist/space-sdk/src/components/DocumentPage.d.ts +53 -0
- package/dist/space-sdk/src/components/Markdown.d.ts +18 -0
- package/dist/space-sdk/src/components/MarkdownEditor.d.ts +11 -0
- package/dist/space-sdk/src/components/MultiSelectActionBar.d.ts +31 -0
- package/dist/space-sdk/src/components/MultiSelectCheckbox.d.ts +14 -0
- package/dist/space-sdk/src/components/MultiSelectDragOverlay.d.ts +8 -0
- package/dist/space-sdk/src/components/NotisCommentBoundary.d.ts +36 -0
- package/dist/space-sdk/src/components/NotisSelectionBoundary.d.ts +16 -0
- package/dist/space-sdk/src/components/RecordComponents.d.ts +64 -0
- package/dist/space-sdk/src/components/ShortcutHints.d.ts +12 -0
- package/dist/space-sdk/src/components/Skeleton.d.ts +23 -0
- package/dist/space-sdk/src/config.ts +31 -0
- package/dist/space-sdk/src/documents.d.ts +59 -0
- package/dist/space-sdk/src/hooks/useAction.d.ts +8 -0
- package/dist/space-sdk/src/hooks/useActiveResource.d.ts +3 -0
- package/dist/space-sdk/src/hooks/useAgentContext.d.ts +7 -0
- package/dist/space-sdk/src/hooks/useBackend.d.ts +23 -0
- package/dist/space-sdk/src/hooks/useCloudComputer.d.ts +45 -0
- package/dist/space-sdk/src/hooks/useCloudComputerShell.d.ts +49 -0
- package/dist/space-sdk/src/hooks/useCollectionInteractions.d.ts +118 -0
- package/dist/space-sdk/src/hooks/useDatabaseSchema.d.ts +14 -0
- package/dist/space-sdk/src/hooks/useDatabaseSubscription.d.ts +26 -0
- package/dist/space-sdk/src/hooks/useDocument.d.ts +22 -0
- package/dist/space-sdk/src/hooks/useDocuments.d.ts +31 -0
- package/dist/space-sdk/src/hooks/useHandover.d.ts +43 -0
- package/dist/space-sdk/src/hooks/useLongPressSelection.d.ts +9 -0
- package/dist/space-sdk/src/hooks/useMarqueeSelection.d.ts +57 -0
- package/dist/space-sdk/src/hooks/useMultiSelect.d.ts +51 -0
- package/dist/space-sdk/src/hooks/useNativeDocumentBody.d.ts +24 -0
- package/dist/space-sdk/src/hooks/useNativeDocuments.d.ts +55 -0
- package/dist/space-sdk/src/hooks/useNotis.d.ts +25 -0
- package/dist/space-sdk/src/hooks/useNotisNavigation.d.ts +33 -0
- package/dist/space-sdk/src/hooks/useQuery.d.ts +22 -0
- package/dist/space-sdk/src/hooks/useTool.d.ts +20 -0
- package/dist/space-sdk/src/hooks/useToolQuery.d.ts +3 -0
- package/dist/space-sdk/src/hooks/useTools.d.ts +15 -0
- package/dist/space-sdk/src/hooks/useTopBarSearch.d.ts +48 -0
- package/dist/space-sdk/src/hooks/useTopBarTitleLeaf.d.ts +24 -0
- package/dist/space-sdk/src/hooks/useUpsertDocument.d.ts +27 -0
- package/dist/space-sdk/src/hooks/useViewParams.d.ts +76 -0
- package/dist/space-sdk/src/hooks/useViewerReads.d.ts +38 -0
- package/dist/space-sdk/src/index.d.ts +98 -0
- package/dist/space-sdk/src/interactions/actions.d.ts +33 -0
- package/dist/space-sdk/src/interactions/shortcuts.d.ts +42 -0
- package/dist/space-sdk/src/interactions/visibility.d.ts +2 -0
- package/dist/space-sdk/src/nativeData.d.ts +66 -0
- package/dist/space-sdk/src/provider.d.ts +17 -0
- package/dist/space-sdk/src/queryCache.d.ts +45 -0
- package/dist/space-sdk/src/queryErrors.ts +51 -0
- package/dist/space-sdk/src/runtime.d.ts +615 -0
- package/dist/space-sdk/src/runtimeModules.ts +33 -0
- package/dist/space-sdk/src/space.ts +442 -0
- package/dist/space-sdk/src/standardViewParams.d.ts +15 -0
- package/dist/space-sdk/src/tailwind-candidates.d.ts +3418 -0
- package/dist/space-sdk/src/tailwind.ts +66 -0
- package/dist/space-sdk/src/ui/actions/ActionToast.d.ts +15 -0
- package/dist/space-sdk/src/ui/actions/actionWords.d.ts +16 -0
- package/dist/space-sdk/src/ui/actions/index.d.ts +7 -0
- package/dist/space-sdk/src/ui/actions/useActions.d.ts +69 -0
- package/dist/space-sdk/src/ui/actions/useToast.d.ts +41 -0
- package/dist/space-sdk/src/ui/blocks/Tabs.d.ts +139 -0
- package/dist/space-sdk/src/ui/blocks/blocks.d.ts +217 -0
- package/dist/space-sdk/src/ui/blocks/index.d.ts +4 -0
- package/dist/space-sdk/src/ui/board/BoardView.d.ts +36 -0
- package/dist/space-sdk/src/ui/board/boardModel.d.ts +46 -0
- package/dist/space-sdk/src/ui/board/boardWords.d.ts +9 -0
- package/dist/space-sdk/src/ui/board/index.d.ts +5 -0
- package/dist/space-sdk/src/ui/calendar/CalendarView.d.ts +49 -0
- package/dist/space-sdk/src/ui/calendar/DayPicker.d.ts +23 -0
- package/dist/space-sdk/src/ui/calendar/calendarMath.d.ts +81 -0
- package/dist/space-sdk/src/ui/charts/Chart.d.ts +203 -0
- package/dist/space-sdk/src/ui/charts/ChartTable.d.ts +37 -0
- package/dist/space-sdk/src/ui/charts/RenderChartData.d.ts +16 -0
- package/dist/space-sdk/src/ui/charts/StatRow.d.ts +59 -0
- package/dist/space-sdk/src/ui/charts/chartData.d.ts +84 -0
- package/dist/space-sdk/src/ui/comments/CommentPane.d.ts +58 -0
- package/dist/space-sdk/src/ui/comments/commentModel.d.ts +157 -0
- package/dist/space-sdk/src/ui/comments/commentWords.d.ts +60 -0
- package/dist/space-sdk/src/ui/comments/index.d.ts +4 -0
- package/dist/space-sdk/src/ui/comments/useComments.d.ts +146 -0
- package/dist/space-sdk/src/ui/engine/HelpSheet.d.ts +12 -0
- package/dist/space-sdk/src/ui/engine/KeyboardEngine.d.ts +27 -0
- package/dist/space-sdk/src/ui/engine/core.d.ts +73 -0
- package/dist/space-sdk/src/ui/engine/hooks.d.ts +64 -0
- package/dist/space-sdk/src/ui/engine/index.d.ts +14 -0
- package/dist/space-sdk/src/ui/engine/instance.d.ts +21 -0
- package/dist/space-sdk/src/ui/engine/keycapContext.d.ts +2 -0
- package/dist/space-sdk/src/ui/engine/registry.d.ts +77 -0
- package/dist/space-sdk/src/ui/engine/step.d.ts +11 -0
- package/dist/space-sdk/src/ui/engine/types.d.ts +54 -0
- package/dist/space-sdk/src/ui/fields/fieldWords.d.ts +56 -0
- package/dist/space-sdk/src/ui/fields/fields.d.ts +162 -0
- package/dist/space-sdk/src/ui/fields/index.d.ts +5 -0
- package/dist/space-sdk/src/ui/fields/useFieldDraft.d.ts +22 -0
- package/dist/space-sdk/src/ui/gallery/AttachmentGallery.d.ts +53 -0
- package/dist/space-sdk/src/ui/gallery/ImageViewer.d.ts +27 -0
- package/dist/space-sdk/src/ui/gallery/galleryWords.d.ts +95 -0
- package/dist/space-sdk/src/ui/gallery/index.d.ts +5 -0
- package/dist/space-sdk/src/ui/icons.d.ts +13 -0
- package/dist/space-sdk/src/ui/index.d.ts +54 -0
- package/dist/space-sdk/src/ui/keycap/Keycap.d.ts +22 -0
- package/dist/space-sdk/src/ui/keycap/index.d.ts +2 -0
- package/dist/space-sdk/src/ui/layout/Panels.d.ts +50 -0
- package/dist/space-sdk/src/ui/layout/chatPanelEdge.d.ts +77 -0
- package/dist/space-sdk/src/ui/layout/index.d.ts +5 -0
- package/dist/space-sdk/src/ui/layout/panelMath.d.ts +91 -0
- package/dist/space-sdk/src/ui/list/ListReader.d.ts +380 -0
- package/dist/space-sdk/src/ui/list/Row.d.ts +145 -0
- package/dist/space-sdk/src/ui/list/SelectionBar.d.ts +65 -0
- package/dist/space-sdk/src/ui/list/index.d.ts +14 -0
- package/dist/space-sdk/src/ui/list/rowState.d.ts +22 -0
- package/dist/space-sdk/src/ui/list/useListKeys.d.ts +89 -0
- package/dist/space-sdk/src/ui/list/useReorder.d.ts +86 -0
- package/dist/space-sdk/src/ui/list/useSelection.d.ts +90 -0
- package/dist/space-sdk/src/ui/open/OpenAsMenu.d.ts +15 -0
- package/dist/space-sdk/src/ui/open/index.d.ts +6 -0
- package/dist/space-sdk/src/ui/open/openModeStore.d.ts +39 -0
- package/dist/space-sdk/src/ui/open/useOpenMode.d.ts +17 -0
- package/dist/space-sdk/src/ui/overlay/Menu.d.ts +88 -0
- package/dist/space-sdk/src/ui/overlay/Popover.d.ts +28 -0
- package/dist/space-sdk/src/ui/overlay/Tooltip.d.ts +19 -0
- package/dist/space-sdk/src/ui/overlay/index.d.ts +9 -0
- package/dist/space-sdk/src/ui/overlay/layerRoot.d.ts +24 -0
- package/dist/space-sdk/src/ui/overlay/position.d.ts +31 -0
- package/dist/space-sdk/src/ui/period/PeriodPicker.d.ts +30 -0
- package/dist/space-sdk/src/ui/period/dateFormat.d.ts +50 -0
- package/dist/space-sdk/src/ui/period/dateWords.d.ts +88 -0
- package/dist/space-sdk/src/ui/period/periods.d.ts +70 -0
- package/dist/space-sdk/src/ui/period/usePeriod.d.ts +23 -0
- package/dist/space-sdk/src/ui/present/index.d.ts +3 -0
- package/dist/space-sdk/src/ui/present/presentWords.d.ts +27 -0
- package/dist/space-sdk/src/ui/present/screens.d.ts +36 -0
- package/dist/space-sdk/src/ui/present/useAudienceScreen.d.ts +38 -0
- package/dist/space-sdk/src/ui/styles.d.ts +21 -0
- package/dist/space-sdk/src/ui/timeline/TimelineView.d.ts +44 -0
- package/dist/space-sdk/src/ui/timeline/timelineMath.d.ts +123 -0
- package/dist/space-sdk/src/viewPeriod.d.ts +8 -0
- package/dist/space-sdk/src/vite.ts +92 -0
- package/dist/space-sdk/src/words/actions.ts +295 -0
- package/dist/space-sdk/src/words/clashes.ts +158 -0
- package/dist/space-sdk/src/words/format.ts +136 -0
- package/dist/space-sdk/src/words/index.ts +13 -0
- package/dist/space-sdk/src/words/keymap.ts +72 -0
- package/dist/space-sdk/src/words/keys.ts +297 -0
- package/dist/space-sdk/src/words/shell.ts +46 -0
- package/dist/space-sdk/src/words/types.ts +65 -0
- package/dist/view-renderer/dist/host.css +1 -0
- package/dist/view-renderer/dist/host.js +2374 -0
- package/dist/view-renderer/dist/theme.css +73 -0
- package/dist/view-renderer/src/dom.js +212 -0
- package/dist/view-renderer/src/errors.js +8 -0
- package/dist/view-renderer/src/host.tsx +250 -0
- package/dist/view-renderer/src/index.js +4 -0
- package/dist/view-renderer/src/png.js +213 -0
- package/dist/view-renderer/src/pool.js +110 -0
- package/dist/view-renderer/src/remote-broker.js +59 -0
- package/dist/view-renderer/src/renderer.js +271 -0
- package/package.json +21 -11
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +63 -12
- package/skills/notis-onboarding/BRIEF.md +14 -9
- package/skills/notis-query/cli.md +1 -1
- package/skills/notis-spaces/cli.md +540 -0
- package/src/cli.js +8 -8
- package/src/command-specs/agents.js +121 -8
- package/src/command-specs/auth.js +3 -3
- package/src/command-specs/diagnostics.js +97 -20
- package/src/command-specs/documents.js +158 -0
- package/src/command-specs/files.js +29 -0
- package/src/command-specs/handover.js +331 -79
- package/src/command-specs/helpers.js +50 -40
- package/src/command-specs/index.js +14 -5
- package/src/command-specs/meta.js +38 -9
- package/src/command-specs/onboarding.js +1 -1
- package/src/command-specs/profile.js +42 -1
- package/src/command-specs/skills.js +314 -9
- package/src/command-specs/smoke.js +16 -4
- package/src/command-specs/spaces.js +783 -0
- package/src/command-specs/tools.js +118 -21
- package/src/command-specs/version-history.js +20 -0
- package/src/command-specs/views.js +69 -0
- package/src/runtime/agent-browser.js +178 -267
- package/src/runtime/agent-setup.js +172 -13
- package/src/runtime/app-boundary-validator.js +338 -2
- package/src/runtime/app-data-literals.js +82 -0
- package/src/runtime/app-platform.js +750 -1568
- package/src/runtime/auth-recovery.js +17 -0
- package/src/runtime/base-skills.d.ts +11 -0
- package/src/runtime/base-skills.js +100 -3
- package/src/runtime/computer-identity.d.ts +30 -0
- package/src/runtime/computer-identity.js +151 -0
- package/src/runtime/config-lock.js +135 -0
- package/src/runtime/confined-space-build.js +74 -0
- package/src/runtime/confined-space-verify.js +49 -0
- package/src/runtime/delegated-context.js +5 -21
- package/src/runtime/native-record-files.js +47 -0
- package/src/runtime/oauth.js +342 -31
- package/src/runtime/profiles.js +19 -112
- package/src/runtime/retired-sdk-files.js +24 -0
- package/src/runtime/retry.js +150 -0
- package/src/runtime/skill-sync/cloud-client.ts +33 -8
- package/src/runtime/skill-sync/index.ts +443 -106
- package/src/runtime/skill-sync/local-scanner.ts +248 -32
- package/src/runtime/skill-sync/native-identity.ts +102 -0
- package/src/runtime/skill-sync/native-plan.ts +70 -0
- package/src/runtime/skill-sync/symlink-manager.ts +66 -16
- package/src/runtime/skill-sync/sync-plan.ts +34 -2
- package/src/runtime/skill-sync/types.ts +54 -0
- package/src/runtime/skill-sync/write-cloud-skill.ts +4 -4
- package/src/runtime/skill-sync-service.js +109 -0
- package/src/runtime/source-root.js +32 -0
- package/src/runtime/space-collection-scaffold.js +107 -0
- package/src/runtime/space-database-structures.js +229 -0
- package/src/runtime/space-fixture-broker.js +68 -0
- package/src/runtime/space-lock.js +337 -0
- package/src/runtime/space-platform.js +133 -0
- package/src/runtime/space-source.js +290 -0
- package/src/runtime/space-test-server.js +354 -0
- package/src/runtime/space-verification.js +94 -0
- package/src/runtime/space-verify-browser-worker.js +51 -0
- package/src/runtime/space-view-manifest.js +368 -0
- package/src/runtime/sync-skills.d.ts +5 -1
- package/src/runtime/sync-skills.js +11 -2
- package/src/runtime/tool-confirmation.js +138 -0
- package/src/runtime/transport.js +118 -24
- package/src/runtime/upsell.js +219 -0
- package/src/runtime/view-render.js +49 -0
- package/src/skill-sync-worker-entry.js +2 -0
- package/src/skill-sync-worker.js +114 -0
- package/src/space-harness-host.ts +122 -0
- package/template/.harness/index.html.tmpl +9 -352
- package/template/app/globals.css +29 -3
- package/template/app/layout.tsx +2 -6
- package/template/components/ui/badge.tsx +7 -4
- package/template/components/ui/button.tsx +1 -1
- package/template/components/ui/native-select.tsx +24 -0
- package/template/notis.config.ts +4 -36
- package/template/package-lock.json +1800 -1867
- package/template/package.json +16 -14
- package/template/postcss.config.mjs +1 -1
- package/template/tailwind.config.ts +1 -6
- package/template/tsconfig.json +1 -0
- package/template/vite.config.ts +2 -2
- package/dist/base-skills/notis-apps/SKILL.md +0 -613
- package/skills/notis-apps/cli.md +0 -285
- package/src/command-specs/apps.js +0 -3000
- package/src/runtime/app-changelog.js +0 -79
- package/src/runtime/app-dev-consumers.js +0 -154
- package/src/runtime/app-dev-host-lock.js +0 -80
- package/src/runtime/app-dev-process-identity.js +0 -111
- package/src/runtime/app-dev-roots.js +0 -284
- package/src/runtime/app-dev-server.js +0 -1140
- package/src/runtime/app-dev-sessions.js +0 -185
- package/src/runtime/app-registry-scaffolds.js +0 -367
- package/src/runtime/assets/store-screenshot-dark.png +0 -0
- package/src/runtime/cli-mode.generated.js +0 -5
- package/src/runtime/cli-mode.js +0 -34
- package/src/runtime/store-screenshot.js +0 -143
- package/template/CHANGELOG.md +0 -5
- package/template/app/page.tsx +0 -60
- package/template/components/ui/card.tsx +0 -56
- package/template/packages/sdk/src/components/DocumentEditor.tsx +0 -103
- package/template/packages/sdk/src/config.ts +0 -259
- package/template/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/template/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/template/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/template/packages/sdk/src/hooks/useNotisNavigation.ts +0 -52
- package/template/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/template/packages/sdk/src/runtime.ts +0 -439
- package/template/packages/sdk/src/vite.ts +0 -56
- package/template/packages/sdk/tsconfig.json +0 -15
- /package/{template/packages → dist}/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/components/ShortcutHints.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useActiveResource.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useTool.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useTools.ts +0 -0
- /package/{template/packages → dist}/sdk/src/ui.ts +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import type { AnchorHTMLAttributes, ReactElement } from 'react';
|
|
3
|
+
import type { AnchorHTMLAttributes, ReactElement, TableHTMLAttributes } from 'react';
|
|
4
4
|
import ReactMarkdown, { type Components } from 'react-markdown';
|
|
5
5
|
import remarkGfm from 'remark-gfm';
|
|
6
6
|
|
|
@@ -32,14 +32,20 @@ function MarkdownLink({
|
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/** A table in a scroll box of its own, so a wide table scrolls sideways on a phone instead of the page (C17). */
|
|
36
|
+
function MarkdownTable({ node: _node, ...rest }: TableHTMLAttributes<HTMLTableElement> & { node?: unknown }) {
|
|
37
|
+
return <div className="notis-markdown-table"><table {...rest} /></div>;
|
|
38
|
+
}
|
|
39
|
+
|
|
35
40
|
const defaultComponents: Components = {
|
|
36
41
|
a: MarkdownLink,
|
|
42
|
+
table: MarkdownTable,
|
|
37
43
|
};
|
|
38
44
|
|
|
39
45
|
/**
|
|
40
46
|
* Renders a markdown value with Notis typography. Markdown is a value type:
|
|
41
|
-
* use this anywhere markdown appears
|
|
42
|
-
* generated text. Styling comes from `.notis-markdown` rules in the SDK
|
|
47
|
+
* use this anywhere markdown appears (document content, database fields,
|
|
48
|
+
* generated text). Styling comes from `.notis-markdown` rules in the SDK
|
|
43
49
|
* stylesheet and follows the portal theme variables.
|
|
44
50
|
*/
|
|
45
51
|
export function Markdown({ value, size = 'base', className, components }: MarkdownProps): ReactElement {
|
|
@@ -1,20 +1,39 @@
|
|
|
1
|
-
import type { ChangeEvent, KeyboardEvent, ReactElement } from 'react';
|
|
1
|
+
import type { ChangeEvent, CSSProperties, KeyboardEvent, ReactElement } from 'react';
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { useNotisRuntime } from '../provider';
|
|
4
4
|
import type { NotisMarkdownEditorProps } from '../runtime';
|
|
5
|
+
import { pick, type Words } from '../words/types';
|
|
6
|
+
|
|
7
|
+
/** The words of the fallback Markdown editor, in English and French (C9). */
|
|
8
|
+
export const MARKDOWN_EDITOR_WORDS = Object.freeze({
|
|
9
|
+
placeholder: { en: 'Write in Markdown', fr: 'Écris en Markdown' },
|
|
10
|
+
}) satisfies Readonly<Record<string, Words>>;
|
|
11
|
+
|
|
12
|
+
const editableStyle: CSSProperties = {
|
|
13
|
+
minHeight: 360, width: '100%', resize: 'vertical', boxSizing: 'border-box', padding: '12px 16px', borderRadius: 8,
|
|
14
|
+
border: '1px solid hsl(var(--border))', background: 'hsl(var(--background))', color: 'hsl(var(--foreground))',
|
|
15
|
+
font: 'inherit', fontSize: 14, lineHeight: '22px',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Read-only reads as text, not a field (C9): no border, no resize, no caret, a muted tint.
|
|
19
|
+
const readOnlyStyle: CSSProperties = {
|
|
20
|
+
...editableStyle, resize: 'none', border: '1px solid transparent', background: 'hsl(var(--muted) / .4)',
|
|
21
|
+
cursor: 'default', outline: 'none',
|
|
22
|
+
};
|
|
5
23
|
|
|
6
24
|
function MarkdownEditorFallback({
|
|
7
25
|
value,
|
|
8
26
|
revision,
|
|
9
27
|
readOnly,
|
|
10
28
|
autosaveMs = 1500,
|
|
11
|
-
placeholder
|
|
29
|
+
placeholder,
|
|
12
30
|
className,
|
|
13
31
|
onChange,
|
|
14
32
|
onSave,
|
|
15
33
|
onDirtyChange,
|
|
16
34
|
onSavingChange,
|
|
17
35
|
}: NotisMarkdownEditorProps) {
|
|
36
|
+
const locale = useNotisRuntime()?.context?.locale === 'fr' ? 'fr' : 'en';
|
|
18
37
|
const [draft, setDraft] = useState(value);
|
|
19
38
|
const [saveAttempt, setSaveAttempt] = useState(0);
|
|
20
39
|
const draftRef = useRef(value);
|
|
@@ -102,16 +121,18 @@ function MarkdownEditorFallback({
|
|
|
102
121
|
<textarea
|
|
103
122
|
value={draft}
|
|
104
123
|
readOnly={readOnly}
|
|
105
|
-
|
|
124
|
+
aria-readonly={readOnly || undefined}
|
|
125
|
+
data-readonly={readOnly ? '' : undefined}
|
|
126
|
+
placeholder={readOnly ? undefined : placeholder ?? pick(MARKDOWN_EDITOR_WORDS.placeholder, locale)}
|
|
106
127
|
className={className}
|
|
107
128
|
onChange={handleChange}
|
|
108
129
|
onKeyDown={handleKeyDown}
|
|
109
|
-
style={
|
|
130
|
+
style={readOnly ? readOnlyStyle : editableStyle}
|
|
110
131
|
/>
|
|
111
132
|
);
|
|
112
133
|
}
|
|
113
134
|
|
|
114
|
-
/** Use Notis' editor while leaving persistence entirely under app control. */
|
|
135
|
+
/** Use Notis' editor while leaving persistence entirely under app control; outside Notis a plain EN/FR textarea stands in. */
|
|
115
136
|
export function MarkdownEditor(props: NotisMarkdownEditorProps): ReactElement {
|
|
116
137
|
const runtime = useNotisRuntime();
|
|
117
138
|
const HostEditor = runtime?.ui?.MarkdownEditor;
|
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import React, {
|
|
4
|
+
useEffect,
|
|
4
5
|
useMemo,
|
|
6
|
+
useRef,
|
|
5
7
|
useState,
|
|
6
8
|
type CSSProperties,
|
|
7
9
|
type ReactElement,
|
|
8
10
|
} from 'react';
|
|
9
11
|
import type { ResolvedCollectionAction } from '../interactions/actions';
|
|
10
|
-
import { shortcutDisplay, useShortcuts, type ShortcutDefinition } from '../interactions/shortcuts';
|
|
12
|
+
import { activateShortcutCollection, shortcutDisplay, useShortcuts, type ShortcutDefinition } from '../interactions/shortcuts';
|
|
13
|
+
import { isInteractionElementVisible } from '../interactions/visibility';
|
|
11
14
|
import type { ShortcutScope } from '../interactions/shortcuts';
|
|
12
15
|
|
|
13
16
|
export type MultiSelectAction = ResolvedCollectionAction;
|
|
14
17
|
|
|
18
|
+
/** The Portal owns global launcher positioning; app code only reports its own bar. */
|
|
19
|
+
export const BULK_ACTION_LAYOUT_EVENT = 'notis:bulk-actions-layout';
|
|
20
|
+
export interface BulkActionLayoutDetail {
|
|
21
|
+
bar: HTMLElement;
|
|
22
|
+
active: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
export interface MultiSelectActionBarProps {
|
|
16
26
|
selectedCount: number;
|
|
17
27
|
actions: MultiSelectAction[];
|
|
@@ -25,11 +35,14 @@ export interface MultiSelectActionBarProps {
|
|
|
25
35
|
shortcutsEnabled?: boolean;
|
|
26
36
|
/** Override the default collection shortcut scope. */
|
|
27
37
|
shortcutScope?: ShortcutScope;
|
|
38
|
+
collectionOwnerId?: string;
|
|
39
|
+
isAvailable?: () => boolean;
|
|
40
|
+
onClearSelection?: () => void;
|
|
28
41
|
}
|
|
29
42
|
|
|
30
43
|
const containerBaseStyle: CSSProperties = {
|
|
31
44
|
position: 'fixed',
|
|
32
|
-
bottom: '1rem',
|
|
45
|
+
bottom: 'calc(var(--notis-viewport-bottom, 0px) + max(1rem, var(--notis-safe-area-bottom, env(safe-area-inset-bottom, 0px))))',
|
|
33
46
|
left: '50%',
|
|
34
47
|
transform: 'translateX(-50%)',
|
|
35
48
|
zIndex: 60,
|
|
@@ -46,6 +59,8 @@ const containerBaseStyle: CSSProperties = {
|
|
|
46
59
|
pointerEvents: 'auto',
|
|
47
60
|
fontSize: '13px',
|
|
48
61
|
lineHeight: 1.2,
|
|
62
|
+
width: 'max-content',
|
|
63
|
+
maxWidth: 'calc(100% - 2rem)',
|
|
49
64
|
};
|
|
50
65
|
|
|
51
66
|
const countStyle: CSSProperties = {
|
|
@@ -103,7 +118,7 @@ const iconSlotStyle: CSSProperties = {
|
|
|
103
118
|
display: 'inline-flex',
|
|
104
119
|
alignItems: 'center',
|
|
105
120
|
justifyContent: 'center',
|
|
106
|
-
|
|
121
|
+
color: 'inherit',
|
|
107
122
|
};
|
|
108
123
|
|
|
109
124
|
export function MultiSelectActionBar({
|
|
@@ -114,15 +129,55 @@ export function MultiSelectActionBar({
|
|
|
114
129
|
style,
|
|
115
130
|
shortcutsEnabled = true,
|
|
116
131
|
shortcutScope = 'collection',
|
|
132
|
+
collectionOwnerId,
|
|
133
|
+
isAvailable,
|
|
134
|
+
onClearSelection,
|
|
117
135
|
}: MultiSelectActionBarProps): ReactElement | null {
|
|
136
|
+
const barRef = useRef<HTMLDivElement>(null);
|
|
137
|
+
const visible = selectedCount > 0;
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
const bar = barRef.current;
|
|
140
|
+
if (!visible || !bar) return;
|
|
141
|
+
// Notify the owning document, including from a shadow root or after unmount.
|
|
142
|
+
// Only the Portal host may measure this bar and change global layout styles.
|
|
143
|
+
const report = (active: boolean) => {
|
|
144
|
+
const owner = bar.ownerDocument;
|
|
145
|
+
const LayoutEvent = owner.defaultView?.CustomEvent;
|
|
146
|
+
if (LayoutEvent) {
|
|
147
|
+
owner.dispatchEvent(new LayoutEvent<BulkActionLayoutDetail>(BULK_ACTION_LAYOUT_EVENT, {
|
|
148
|
+
detail: { bar, active },
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const update = () => report(isAvailable?.() !== false && isInteractionElementVisible(bar));
|
|
153
|
+
update();
|
|
154
|
+
const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(update);
|
|
155
|
+
observer?.observe(bar);
|
|
156
|
+
const VisibilityObserver = bar.ownerDocument.defaultView?.MutationObserver;
|
|
157
|
+
const visibilityObserver = VisibilityObserver ? new VisibilityObserver(update) : null;
|
|
158
|
+
let element: Element | null = bar;
|
|
159
|
+
while (element) {
|
|
160
|
+
visibilityObserver?.observe(element, { attributes: true, attributeFilter: element === bar.ownerDocument.documentElement ? ['hidden', 'inert', 'aria-hidden', 'class'] : ['hidden', 'inert', 'aria-hidden', 'style', 'class'] });
|
|
161
|
+
const root: Node = element.getRootNode();
|
|
162
|
+
element = element.parentElement ?? ('host' in root ? (root as ShadowRoot).host : null);
|
|
163
|
+
}
|
|
164
|
+
return () => {
|
|
165
|
+
observer?.disconnect();
|
|
166
|
+
visibilityObserver?.disconnect();
|
|
167
|
+
report(false);
|
|
168
|
+
};
|
|
169
|
+
}, [isAvailable, visible]);
|
|
170
|
+
|
|
118
171
|
const actionShortcuts = useMemo<ShortcutDefinition[]>(() => {
|
|
119
172
|
return actions.flatMap((action): ShortcutDefinition[] => {
|
|
120
|
-
if (!action.shortcut
|
|
173
|
+
if (!action.shortcut) return [];
|
|
121
174
|
return [{
|
|
122
175
|
id: `collection.action.${action.id}`,
|
|
123
176
|
keys: action.shortcut,
|
|
124
177
|
label: action.label,
|
|
125
|
-
|
|
178
|
+
allowRepeat: Boolean(action.disabled || action.pending),
|
|
179
|
+
// Keep advertised keys owned while disabled/pending; never fall through to another action.
|
|
180
|
+
onTrigger: () => { if (!action.disabled && !action.pending) action.onRun(); },
|
|
126
181
|
}];
|
|
127
182
|
});
|
|
128
183
|
}, [actions]);
|
|
@@ -130,6 +185,8 @@ export function MultiSelectActionBar({
|
|
|
130
185
|
enabled: shortcutsEnabled && selectedCount > 0,
|
|
131
186
|
scope: shortcutScope,
|
|
132
187
|
priority: 25,
|
|
188
|
+
collectionOwnerId,
|
|
189
|
+
isAvailable: () => isAvailable?.() !== false && isInteractionElementVisible(barRef.current),
|
|
133
190
|
});
|
|
134
191
|
|
|
135
192
|
if (selectedCount === 0) return null;
|
|
@@ -147,16 +204,42 @@ export function MultiSelectActionBar({
|
|
|
147
204
|
|
|
148
205
|
return (
|
|
149
206
|
<div
|
|
207
|
+
ref={barRef}
|
|
208
|
+
data-notis-bulk-actions
|
|
209
|
+
onFocusCapture={() => { if (collectionOwnerId) activateShortcutCollection(collectionOwnerId); }}
|
|
210
|
+
onMouseDownCapture={() => { if (collectionOwnerId) activateShortcutCollection(collectionOwnerId); }}
|
|
211
|
+
onKeyDown={(event) => {
|
|
212
|
+
if (event.key !== 'Escape' || !shortcutsEnabled || !onClearSelection || isAvailable?.() === false) return;
|
|
213
|
+
event.preventDefault();
|
|
214
|
+
event.stopPropagation();
|
|
215
|
+
onClearSelection();
|
|
216
|
+
}}
|
|
150
217
|
role="toolbar"
|
|
151
218
|
aria-label={`Bulk actions for ${selectedCount} selected ${countWord}`}
|
|
152
219
|
className={className}
|
|
153
220
|
style={composedStyle}
|
|
154
221
|
>
|
|
155
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
222
|
+
<style>{`
|
|
223
|
+
[data-notis-bulk-action-icon][data-has-shortcut] { display: none !important; }
|
|
224
|
+
@media (max-width: 639px) {
|
|
225
|
+
[data-notis-bulk-actions] { flex-wrap: wrap; width: calc(100% - 2rem) !important; }
|
|
226
|
+
[data-notis-bulk-count] { flex-basis: 100%; padding: 0.375rem 0.5rem !important; font-size: 14px !important; }
|
|
227
|
+
[data-notis-bulk-divider] { display: none; }
|
|
228
|
+
[data-notis-bulk-action-list] { width: 100%; }
|
|
229
|
+
[data-notis-bulk-actions] button { min-height: 48px; font-size: 16px !important; }
|
|
230
|
+
}
|
|
231
|
+
@media (hover: none) and (pointer: coarse) {
|
|
232
|
+
[data-notis-bulk-actions] kbd { display: none !important; }
|
|
233
|
+
[data-notis-bulk-action-icon][data-has-shortcut] { display: inline-flex !important; }
|
|
234
|
+
}
|
|
235
|
+
`}</style>
|
|
236
|
+
<span data-notis-bulk-count style={countStyle}>{countLabel}</span>
|
|
237
|
+
{actions.length > 0 ? <span data-notis-bulk-divider aria-hidden style={dividerStyle} /> : null}
|
|
238
|
+
<div data-notis-bulk-action-list style={{ display: 'flex', minWidth: 0, overflowX: 'auto', overscrollBehaviorX: 'contain', gap: '0.25rem' }}>
|
|
239
|
+
{actions.map((action) => (
|
|
240
|
+
<ActionButton key={action.id} action={action} />
|
|
241
|
+
))}
|
|
242
|
+
</div>
|
|
160
243
|
</div>
|
|
161
244
|
);
|
|
162
245
|
}
|
|
@@ -192,8 +275,8 @@ function ActionButton({ action }: { action: MultiSelectAction }) {
|
|
|
192
275
|
onBlur={() => setHover(false)}
|
|
193
276
|
style={buttonStyle}
|
|
194
277
|
>
|
|
195
|
-
{
|
|
196
|
-
<span aria-hidden style={iconSlotStyle}>{action.icon}</span>
|
|
278
|
+
{action.icon ? (
|
|
279
|
+
<span data-notis-bulk-action-icon data-has-shortcut={action.shortcut ? '' : undefined} aria-hidden style={iconSlotStyle}>{action.icon}</span>
|
|
197
280
|
) : null}
|
|
198
281
|
{display ? <kbd aria-hidden style={keycapStyle}>{display}</kbd> : null}
|
|
199
282
|
<span>{action.pending ? `${action.label}…` : action.label}</span>
|
|
@@ -23,7 +23,9 @@ const baseStyle: CSSProperties = {
|
|
|
23
23
|
width: '16px',
|
|
24
24
|
height: '16px',
|
|
25
25
|
borderRadius: '4px',
|
|
26
|
-
|
|
26
|
+
borderWidth: '1px',
|
|
27
|
+
borderStyle: 'solid',
|
|
28
|
+
borderColor: 'hsl(var(--border))',
|
|
27
29
|
background: 'transparent',
|
|
28
30
|
color: 'hsl(var(--primary-foreground))',
|
|
29
31
|
cursor: 'pointer',
|
|
@@ -56,7 +58,7 @@ export function MultiSelectCheckbox({
|
|
|
56
58
|
};
|
|
57
59
|
|
|
58
60
|
// When alwaysVisible is false and the row isn't selected, defer the
|
|
59
|
-
// hover-reveal to the parent
|
|
61
|
+
// hover-reveal to the parent: consumers wrap the checkbox in a `.group`
|
|
60
62
|
// hover scope and pass `data-notis-hover-reveal` styles via className.
|
|
61
63
|
// Out of the box, we render the checkbox at full opacity if selected,
|
|
62
64
|
// otherwise inherit the parent's reveal state.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react';
|
|
3
|
+
import type { ContextResource } from '../runtime';
|
|
4
|
+
import { NotisSelectionBoundary } from './NotisSelectionBoundary';
|
|
5
|
+
import { useAgentContext } from '../hooks/useAgentContext';
|
|
6
|
+
|
|
7
|
+
export interface NotisCommentBoxProps {
|
|
8
|
+
quote?: string;
|
|
9
|
+
value: string;
|
|
10
|
+
onChange(value: string): void;
|
|
11
|
+
onSubmit(): void;
|
|
12
|
+
onCancel(): void;
|
|
13
|
+
pending?: boolean;
|
|
14
|
+
error?: string;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Compact, chrome-themed presentation. Apps retain ownership of comment state. */
|
|
19
|
+
export function NotisCommentBox({ quote, value, onChange, onSubmit, onCancel, pending, error, className = '' }: NotisCommentBoxProps) {
|
|
20
|
+
return <div data-notis-comment-ui role="dialog" aria-label="Comment on selection"
|
|
21
|
+
style={{ colorScheme: 'dark', background: 'hsl(var(--sidebar-background, 0 0% 7.5%))', color: 'hsl(var(--sidebar-foreground, 240 4.8% 95.9%))', borderColor: 'hsl(var(--sidebar-border, 0 0% 23.5%))' }}
|
|
22
|
+
className={`flex min-w-0 flex-col gap-2 rounded-xl border p-2.5 text-sm shadow-lg ${className}`}>
|
|
23
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
24
|
+
{quote && <blockquote title={quote} className="min-w-0 flex-1 truncate border-l-2 border-current/40 pl-2 opacity-70">{quote}</blockquote>}
|
|
25
|
+
<button type="button" aria-label="Cancel comment" onClick={onCancel} disabled={pending} className="ml-auto flex size-7 shrink-0 items-center justify-center rounded-lg opacity-70 hover:bg-white/10 hover:opacity-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current disabled:opacity-40 [@media(pointer:coarse)]:size-9">
|
|
26
|
+
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><path d="m6 6 12 12M18 6 6 18" /></svg>
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
<textarea autoFocus rows={2} name="selection-comment" aria-label="Your comment" placeholder="Add a comment…" value={value} onChange={event => onChange(event.target.value)} disabled={pending}
|
|
30
|
+
onKeyDown={event => { if (event.nativeEvent.isComposing || pending) return; if (event.key === 'Escape') { event.preventDefault(); onCancel(); } if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { event.preventDefault(); if (value.trim()) onSubmit(); } }}
|
|
31
|
+
className="w-full min-w-0 resize-none rounded bg-transparent px-0.5 py-1 text-base text-inherit outline-none placeholder:text-current placeholder:opacity-60 sm:text-sm" />
|
|
32
|
+
{error && <p role="alert" className="text-sm text-red-300">{error}</p>}
|
|
33
|
+
<div className="flex items-center justify-between gap-3">
|
|
34
|
+
<span className="text-xs opacity-60">⌘ / Ctrl ↵ to add</span>
|
|
35
|
+
<button type="button" disabled={pending || !value.trim()} onClick={onSubmit}
|
|
36
|
+
style={{ background: 'hsl(var(--sidebar-foreground, 240 4.8% 95.9%))', color: 'hsl(var(--sidebar-background, 0 0% 7.5%))' }}
|
|
37
|
+
className="min-h-8 shrink-0 rounded-lg px-2.5 text-sm font-medium hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current disabled:opacity-40 [@media(pointer:coarse)]:min-h-9">{pending ? 'Adding…' : 'Add to chat'}</button>
|
|
38
|
+
</div>
|
|
39
|
+
</div>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface NotisCommentBoundaryProps {
|
|
43
|
+
resource?: ContextResource | null;
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
className?: string;
|
|
46
|
+
commentClassName?: string;
|
|
47
|
+
/** Replace the optional standard editor while retaining selection/context behavior. */
|
|
48
|
+
renderComment?: (props: NotisCommentBoxProps) => ReactNode;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type SelectionRect = Pick<DOMRect, 'left' | 'right' | 'top' | 'bottom'>;
|
|
52
|
+
|
|
53
|
+
/** Place the tile after the final selected line, otherwise below (or above near the viewport bottom). */
|
|
54
|
+
export function getCommentActionPosition(rects: SelectionRect[], viewport: { width: number; height: number }, rightBoundary = viewport.width, trailing: SelectionRect[] = []) {
|
|
55
|
+
const last = rects[rects.length - 1];
|
|
56
|
+
const size = 32, gap = 8;
|
|
57
|
+
const clampX = (x: number) => Math.max(gap, Math.min(viewport.width - size - gap, x));
|
|
58
|
+
const clampY = (y: number) => Math.max(gap, Math.min(viewport.height - size - gap, y));
|
|
59
|
+
const beside = { left: last.right + gap, top: clampY((last.top + last.bottom - size) / 2) };
|
|
60
|
+
const overlapsText = trailing.some(rect => rect.left < beside.left + size && rect.right > beside.left && rect.top < beside.top + size && rect.bottom > beside.top);
|
|
61
|
+
if (beside.left + size <= Math.min(viewport.width, rightBoundary) - gap && !overlapsText) return beside;
|
|
62
|
+
return { left: clampX(last.right - size), top: clampY(last.bottom + gap + size <= viewport.height - gap ? last.bottom + gap : last.top - size - gap) };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Browser paragraph selection may include an adjacent block's empty starting boundary. */
|
|
66
|
+
export function getOwnedCommentRange(selected: Range, content: Element): Range | null {
|
|
67
|
+
const owned = content.ownerDocument.createRange();
|
|
68
|
+
owned.selectNodeContents(content);
|
|
69
|
+
const range = selected.cloneRange();
|
|
70
|
+
// Only clip whitespace outside the resource; actual neighboring content must
|
|
71
|
+
// never be silently attached to this resource's comment.
|
|
72
|
+
if (range.compareBoundaryPoints(Range.START_TO_START, owned) < 0) {
|
|
73
|
+
const before = range.cloneRange();
|
|
74
|
+
before.setEnd(owned.startContainer, owned.startOffset);
|
|
75
|
+
if (before.toString().trim()) return null;
|
|
76
|
+
range.setStart(owned.startContainer, owned.startOffset);
|
|
77
|
+
}
|
|
78
|
+
if (range.compareBoundaryPoints(Range.END_TO_END, owned) > 0) {
|
|
79
|
+
const after = range.cloneRange();
|
|
80
|
+
after.setStart(owned.endContainer, owned.endOffset);
|
|
81
|
+
if (after.toString().trim()) return null;
|
|
82
|
+
range.setEnd(owned.endContainer, owned.endOffset);
|
|
83
|
+
}
|
|
84
|
+
if (range.collapsed || !content.contains(range.startContainer) || !content.contains(range.endContainer)) return null;
|
|
85
|
+
return range;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Selection-to-chat convenience. No annotation database, polling or background attachment. */
|
|
89
|
+
export function NotisCommentBoundary({ resource, children, className, commentClassName, renderComment }: NotisCommentBoundaryProps) {
|
|
90
|
+
const context = useAgentContext();
|
|
91
|
+
const root = useRef<HTMLDivElement>(null);
|
|
92
|
+
const [selection, setSelection] = useState<{ id: string; text: string; resource?: ContextResource | null; top: number; left: number } | null>(null);
|
|
93
|
+
const [editing, setEditing] = useState(false);
|
|
94
|
+
const editingRef = useRef(false);
|
|
95
|
+
const [comment, setComment] = useState('');
|
|
96
|
+
const [pending, setPending] = useState(false);
|
|
97
|
+
const [error, setError] = useState('');
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
const node = root.current;
|
|
100
|
+
if (!node) return;
|
|
101
|
+
const document = node.ownerDocument;
|
|
102
|
+
const scope = node.getRootNode() as ShadowRoot & { getSelection?: () => Selection | null };
|
|
103
|
+
const clearInvalidSelection = () => {
|
|
104
|
+
// Opening the editor focuses its input and collapses the browser range.
|
|
105
|
+
// Keep the captured quote/editor; only the unopened action follows live selection.
|
|
106
|
+
if (editingRef.current) return;
|
|
107
|
+
const selected = scope.getSelection?.() || document.defaultView?.getSelection();
|
|
108
|
+
const content = node.firstElementChild;
|
|
109
|
+
if (!selected?.rangeCount || selected.isCollapsed || !selected.toString().trim()
|
|
110
|
+
|| !content || !getOwnedCommentRange(selected.getRangeAt(0), content)) setSelection(null);
|
|
111
|
+
};
|
|
112
|
+
document.addEventListener('selectionchange', clearInvalidSelection);
|
|
113
|
+
if (scope !== (document as unknown as ShadowRoot)) scope.addEventListener('selectionchange', clearInvalidSelection);
|
|
114
|
+
return () => {
|
|
115
|
+
document.removeEventListener('selectionchange', clearInvalidSelection);
|
|
116
|
+
if (scope !== (document as unknown as ShadowRoot)) scope.removeEventListener('selectionchange', clearInvalidSelection);
|
|
117
|
+
};
|
|
118
|
+
}, []);
|
|
119
|
+
const open = () => { editingRef.current = true; setEditing(true); };
|
|
120
|
+
const capture = (target: EventTarget | null) => {
|
|
121
|
+
// Clicking the action/editor must not unmount it before its click runs.
|
|
122
|
+
if (editing || (target instanceof Element && target.closest('[data-notis-comment-ui]'))) return;
|
|
123
|
+
const node = root.current;
|
|
124
|
+
const scope = node?.getRootNode() as ShadowRoot & { getSelection?: () => Selection | null };
|
|
125
|
+
const selected = scope?.getSelection?.() || window.getSelection();
|
|
126
|
+
if (!node || !selected?.rangeCount || selected.isCollapsed) { setSelection(null); return; }
|
|
127
|
+
const content = node.firstElementChild;
|
|
128
|
+
const range = content ? getOwnedCommentRange(selected.getRangeAt(0), content) : null;
|
|
129
|
+
if (!range) { setSelection(null); return; }
|
|
130
|
+
const start = range.startContainer.nodeType === Node.ELEMENT_NODE ? range.startContainer as Element : range.startContainer.parentElement;
|
|
131
|
+
const end = range.endContainer.nodeType === Node.ELEMENT_NODE ? range.endContainer as Element : range.endContainer.parentElement;
|
|
132
|
+
if (!node.contains(range.startContainer) || !node.contains(range.endContainer)
|
|
133
|
+
|| start?.closest('input,textarea,button,[contenteditable],[data-notis-comment-ui]')
|
|
134
|
+
|| end?.closest('input,textarea,button,[contenteditable],[data-notis-comment-ui]')) { setSelection(null); return; }
|
|
135
|
+
const rect = range.getBoundingClientRect();
|
|
136
|
+
const lines = Array.from(range.getClientRects?.() || []).filter(line => line.width > 0 && line.height > 0);
|
|
137
|
+
const boundary = node.firstElementChild?.getBoundingClientRect();
|
|
138
|
+
const trailing: DOMRect[] = [];
|
|
139
|
+
const endBlock = end?.closest('p,li,h1,h2,h3,h4,h5,h6,td,th,div');
|
|
140
|
+
if (endBlock && node.contains(endBlock)) {
|
|
141
|
+
const remainder = range.cloneRange();
|
|
142
|
+
remainder.selectNodeContents(endBlock);
|
|
143
|
+
remainder.setStart(range.endContainer, range.endOffset);
|
|
144
|
+
trailing.push(...Array.from(remainder.getClientRects?.() || []).filter(line => line.width > 0 && line.height > 0));
|
|
145
|
+
}
|
|
146
|
+
const position = getCommentActionPosition(lines.length ? lines : [rect], { width: window.innerWidth, height: window.innerHeight }, boundary?.width ? boundary.right : window.innerWidth, trailing);
|
|
147
|
+
setSelection({ id: crypto.randomUUID(), text: selected.toString().trim(), resource: resource ? JSON.parse(JSON.stringify(resource)) : null, ...position });
|
|
148
|
+
};
|
|
149
|
+
const close = () => { editingRef.current = false; setEditing(false); setSelection(null); setComment(''); setError(''); };
|
|
150
|
+
const submit = async () => {
|
|
151
|
+
if (!selection || !comment.trim() || pending) return;
|
|
152
|
+
setPending(true); setError('');
|
|
153
|
+
try {
|
|
154
|
+
const added = await context.add({ id: selection.id, kind: 'comment', title: selection.resource?.label || 'Comment', icon: 'phosphor:chat-text', text: selection.text, comment, resource: selection.resource });
|
|
155
|
+
if (!added) throw new Error('The comment could not be added to chat.');
|
|
156
|
+
close();
|
|
157
|
+
} catch (reason) { setError(reason instanceof Error ? reason.message : 'Could not add this comment.'); }
|
|
158
|
+
finally { setPending(false); }
|
|
159
|
+
};
|
|
160
|
+
const editorProps: NotisCommentBoxProps = { quote: selection?.text, value: comment, onChange: setComment, onSubmit: () => void submit(), onCancel: close, pending, error, className: commentClassName };
|
|
161
|
+
return <div ref={root} className="contents" onMouseUp={event => capture(event.target)} onDoubleClick={event => capture(event.target)} onKeyUp={event => { if (!editing && (event.shiftKey || event.key === 'Shift' || ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'a'))) capture(event.target); }}>
|
|
162
|
+
<NotisSelectionBoundary resource={resource} className={className} style={className ? undefined : { display: 'contents' }}>{children}</NotisSelectionBoundary>
|
|
163
|
+
{selection && <div data-notis-comment-ui className="fixed z-50 max-h-[calc(100dvh-1rem)]" style={{ overflow: editing ? 'auto' : 'visible', top: editing ? Math.min(selection.top, Math.max(8, window.innerHeight - 320)) : selection.top, left: `clamp(8px, ${selection.left}px, calc(100vw - ${editing ? 'min(20rem, calc(100vw - 1rem))' : '2rem'} - 8px))`, width: editing ? 'min(20rem, calc(100vw - 1rem))' : undefined } as CSSProperties}>
|
|
164
|
+
{editing ? (renderComment ? renderComment(editorProps) : <NotisCommentBox {...editorProps} />) : <button type="button" aria-label="Comment" title="Comment" onMouseDown={event => event.preventDefault()} onClick={open}
|
|
165
|
+
style={{ background: 'hsl(var(--sidebar-background, 0 0% 7.5%))', color: 'hsl(var(--sidebar-foreground, 240 4.8% 95.9%))' }}
|
|
166
|
+
className="relative flex size-8 items-center justify-center rounded-lg ring-1 ring-white/15 hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current">
|
|
167
|
+
<svg aria-hidden="true" width="18" height="18" viewBox="0 0 256 256" fill="currentColor"><path d="M216,48H40A16,16,0,0,0,24,64V224a15.85,15.85,0,0,0,9.24,14.5A16.13,16.13,0,0,0,40,240a15.89,15.89,0,0,0,10.25-3.78l.09-.07L83,208H216a16,16,0,0,0,16-16V64A16,16,0,0,0,216,48ZM40,224h0ZM216,192H80a8,8,0,0,0-5.23,1.95L40,224V64H216ZM88,112a8,8,0,0,1,8-8h64a8,8,0,0,1,0,16H96A8,8,0,0,1,88,112Zm0,32a8,8,0,0,1,8-8h64a8,8,0,1,1,0,16H96A8,8,0,0,1,88,144Z" /></svg>
|
|
168
|
+
<span aria-hidden="true" className="absolute left-1/2 top-1/2 hidden size-12 -translate-x-1/2 -translate-y-1/2 [@media(pointer:coarse)]:block" />
|
|
169
|
+
</button>}
|
|
170
|
+
</div>}
|
|
171
|
+
</div>;
|
|
172
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CSSProperties, ClipboardEvent, ReactNode } from 'react';
|
|
2
|
-
import { useCallback } from 'react';
|
|
2
|
+
import React, { useCallback } from 'react';
|
|
3
3
|
import { useNotisRuntime } from '../provider';
|
|
4
4
|
import type { ContextResource, ContextSelection } from '../runtime';
|
|
5
5
|
|
|
@@ -31,10 +31,13 @@ export function NotisSelectionBoundary({
|
|
|
31
31
|
}: NotisSelectionBoundaryProps) {
|
|
32
32
|
const runtime = useNotisRuntime();
|
|
33
33
|
const onCopy = useCallback((event: ClipboardEvent<HTMLDivElement>) => {
|
|
34
|
-
const
|
|
35
|
-
|
|
34
|
+
const target = event.target as Element | null;
|
|
35
|
+
if (target?.closest?.('input,textarea,[contenteditable="true"]')) return;
|
|
36
|
+
const scope = event.currentTarget.getRootNode() as ShadowRoot & { getSelection?: () => Selection | null };
|
|
37
|
+
const selection = scope?.getSelection?.() || window.getSelection();
|
|
38
|
+
const text = selection?.toString() ?? '';
|
|
36
39
|
if (
|
|
37
|
-
!text
|
|
40
|
+
!text.trim()
|
|
38
41
|
|| !selection
|
|
39
42
|
|| !event.currentTarget.contains(selection.anchorNode)
|
|
40
43
|
|| !event.currentTarget.contains(selection.focusNode)
|
|
@@ -42,7 +45,8 @@ export function NotisSelectionBoundary({
|
|
|
42
45
|
|
|
43
46
|
const payload: ContextSelection = {
|
|
44
47
|
id: selectionId(),
|
|
45
|
-
text
|
|
48
|
+
text,
|
|
49
|
+
...(runtime?.contextSource ? { source: runtime.contextSource } : {}),
|
|
46
50
|
...(resource ? { resource: { ...resource, snapshot: undefined } } : {}),
|
|
47
51
|
};
|
|
48
52
|
event.clipboardData.setData('text/plain', text);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactElement } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import type {
|
|
6
|
+
NotisHtmlFrameProps, NotisRecordPropertiesProps, NotisReportFrameProps, NotisShareControlProps, NotisSpacePageFrameProps,
|
|
7
|
+
} from '../runtime';
|
|
8
|
+
import { pick, type Locale, type Words } from '../words/types';
|
|
9
|
+
|
|
10
|
+
/** The words a record component says outside Notis, in English and French. */
|
|
11
|
+
export const RECORD_COMPONENT_WORDS = Object.freeze({
|
|
12
|
+
unavailable: { en: 'Open this record in Notis to use this component.', fr: 'Ouvre cet enregistrement dans Notis pour utiliser ce composant.' },
|
|
13
|
+
/** Short enough for a toolbar (C11); the full sentence is its tooltip. */
|
|
14
|
+
shareUnavailable: { en: 'Share unavailable', fr: 'Partage indisponible' },
|
|
15
|
+
shareUnavailableHint: { en: 'Open this record in Notis to share it.', fr: 'Ouvre cet enregistrement dans Notis pour le partager.' },
|
|
16
|
+
spacePageTitle: { en: 'Space page', fr: 'Page du Space' },
|
|
17
|
+
spacePagePreview: { en: 'This page shows here live when the Space opens in Notis.', fr: 'Cette page s’affiche ici en direct quand le Space est ouvert dans Notis.' },
|
|
18
|
+
spacePageOpen: { en: 'Open page', fr: 'Ouvrir la page' },
|
|
19
|
+
}) satisfies Readonly<Record<string, Words>>;
|
|
20
|
+
|
|
21
|
+
function useLocale(): Locale {
|
|
22
|
+
return useNotisRuntime()?.context?.locale === 'fr' ? 'fr' : 'en';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** What a record component shows when the host does not draw it: one sentence, in the Space's language. */
|
|
26
|
+
export function RecordComponentUnavailable({ className }: { className?: string }): ReactElement {
|
|
27
|
+
return <div role="status" className={className}>{pick(RECORD_COMPONENT_WORDS.unavailable, useLocale())}</div>;
|
|
28
|
+
}
|
|
29
|
+
/** The record's properties, drawn by the host. */
|
|
30
|
+
export function RecordProperties(props: NotisRecordPropertiesProps): ReactElement {
|
|
31
|
+
const Host = useNotisRuntime()?.ui?.RecordProperties;
|
|
32
|
+
return Host ? <Host {...props} /> : <RecordComponentUnavailable className={props.className} />;
|
|
33
|
+
}
|
|
34
|
+
/** The host applies its viewer CSP and an opaque-origin sandbox; source cannot relax either. */
|
|
35
|
+
export function HtmlFrame(props: NotisHtmlFrameProps): ReactElement {
|
|
36
|
+
const Host = useNotisRuntime()?.ui?.HtmlFrame;
|
|
37
|
+
return Host ? <Host {...props} /> : <RecordComponentUnavailable className={props.className} />;
|
|
38
|
+
}
|
|
39
|
+
/** A saved report's frame, drawn by the host. */
|
|
40
|
+
export function ReportFrame(props: NotisReportFrameProps): ReactElement {
|
|
41
|
+
const Host = useNotisRuntime()?.ui?.ReportFrame;
|
|
42
|
+
return Host ? <Host {...props} /> : <RecordComponentUnavailable className={props.className} />;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Private/shared state, copy link and revoke, backed by this record's Site. Outside Notis it sits in a toolbar as a
|
|
46
|
+
* short muted label whose tooltip says where sharing works (C11), never a sentence that fills the bar.
|
|
47
|
+
*/
|
|
48
|
+
export function ShareControl(props: NotisShareControlProps): ReactElement {
|
|
49
|
+
const Host = useNotisRuntime()?.ui?.ShareControl;
|
|
50
|
+
const locale = useLocale();
|
|
51
|
+
if (Host) return <Host {...props} />;
|
|
52
|
+
const hint = pick(RECORD_COMPONENT_WORDS.shareUnavailableHint, locale);
|
|
53
|
+
return <span role="status" className={props.className} title={hint} aria-label={hint} data-notis-share-unavailable=""
|
|
54
|
+
style={{ display: 'inline-flex', alignItems: 'center', height: 28, padding: '0 8px', fontSize: 13, whiteSpace: 'nowrap',
|
|
55
|
+
color: 'hsl(var(--muted-foreground))' }}>
|
|
56
|
+
{pick(RECORD_COMPONENT_WORDS.shareUnavailable, locale)}
|
|
57
|
+
</span>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The path a Space page link names and the link the fallback card may open: a site-relative path, or an http(s) URL
|
|
62
|
+
* without credentials. Anything else (script URLs, protocol-relative links) opens nothing.
|
|
63
|
+
*/
|
|
64
|
+
export function spacePageLink(href: string): { path: string; openHref: string | null } {
|
|
65
|
+
const value = typeof href === 'string' ? href.trim() : '';
|
|
66
|
+
// Control characters and backslashes change how browsers read a URL; such a link never opens.
|
|
67
|
+
if (!value || /[\u0000-\u001f\u007f\\]/.test(value)) return { path: value, openHref: null };
|
|
68
|
+
if (value.startsWith('/') && !value.startsWith('//')) {
|
|
69
|
+
const path = value.split(/[?#]/, 1)[0] || '/';
|
|
70
|
+
return { path, openHref: value };
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const url = new URL(value);
|
|
74
|
+
if ((url.protocol === 'https:' || url.protocol === 'http:') && !url.username && !url.password) {
|
|
75
|
+
return { path: url.pathname, openHref: url.href };
|
|
76
|
+
}
|
|
77
|
+
} catch { /* not a URL */ }
|
|
78
|
+
return { path: value, openHref: null };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** What a Space page frame shows when the host does not draw it (previews, verify): its title, its path and Open page. */
|
|
82
|
+
export function SpacePageCard({ href, title, className, notice }: Pick<NotisSpacePageFrameProps, 'href' | 'title' | 'className'> & {
|
|
83
|
+
/** One sentence under the path; default: the page shows live once the Space opens in Notis. */
|
|
84
|
+
notice?: string;
|
|
85
|
+
}): ReactElement {
|
|
86
|
+
const locale = useLocale();
|
|
87
|
+
const { path, openHref } = spacePageLink(href);
|
|
88
|
+
const muted = 'hsl(var(--muted-foreground))';
|
|
89
|
+
return <div role="group" aria-label={title || pick(RECORD_COMPONENT_WORDS.spacePageTitle, locale)} className={className}
|
|
90
|
+
data-notis-space-page-card=""
|
|
91
|
+
style={{ display: 'grid', alignContent: 'start', gap: 8, padding: 16, borderRadius: 10, minWidth: 0,
|
|
92
|
+
background: 'hsl(var(--muted) / .5)', color: 'hsl(var(--foreground))' }}>
|
|
93
|
+
<div style={{ fontSize: 14, fontWeight: 500, overflowWrap: 'anywhere' }}>{title || pick(RECORD_COMPONENT_WORDS.spacePageTitle, locale)}</div>
|
|
94
|
+
{path ? <div style={{ fontSize: 12, color: muted, overflowWrap: 'anywhere',
|
|
95
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace' }}>{path}</div> : null}
|
|
96
|
+
<div style={{ fontSize: 13, color: muted }}>{notice ?? pick(RECORD_COMPONENT_WORDS.spacePagePreview, locale)}</div>
|
|
97
|
+
{openHref ? <div><a className="nui-btn" href={openHref} style={{ textDecoration: 'none' }}>
|
|
98
|
+
{pick(RECORD_COMPONENT_WORDS.spacePageOpen, locale)}
|
|
99
|
+
</a></div> : null}
|
|
100
|
+
</div>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Another Space page shown live inside this view. The host loads only a Space page of its own Notis origin, with the
|
|
105
|
+
* viewer's own access; any other link (and every host without the frame) shows a card with the title, the path and
|
|
106
|
+
* Open page. Not interactive by default, so a deck around it keeps its keys and clicks.
|
|
107
|
+
*/
|
|
108
|
+
export function SpacePageFrame(props: NotisSpacePageFrameProps): ReactElement {
|
|
109
|
+
const Host = useNotisRuntime()?.ui?.SpacePageFrame;
|
|
110
|
+
return Host ? <Host {...props} /> : <SpacePageCard href={props.href} title={props.title} className={props.className} />;
|
|
111
|
+
}
|