@agent-native/core 0.114.0 → 0.114.2

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.
Files changed (223) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +15 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/create.ts +23 -0
  5. package/corpus/core/src/cli/skills.ts +24 -0
  6. package/corpus/core/src/client/build-compatibility.ts +77 -0
  7. package/corpus/core/src/client/route-chunk-recovery/index.ts +1 -0
  8. package/corpus/core/src/client/use-action.ts +32 -1
  9. package/corpus/core/src/deploy/build.ts +4 -1
  10. package/corpus/core/src/org/context.ts +56 -30
  11. package/corpus/core/src/server/action-routes.ts +52 -2
  12. package/corpus/core/src/server/auth-plugin.ts +9 -4
  13. package/corpus/core/src/server/better-auth-instance.ts +5 -1
  14. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  15. package/corpus/core/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  16. package/corpus/core/src/templates/default/AGENTS.md +4 -1
  17. package/corpus/core/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  18. package/corpus/core/src/templates/headless/AGENTS.md +5 -1
  19. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +26 -0
  20. package/corpus/core/src/templates/workspace-core/AGENTS.md +3 -1
  21. package/corpus/core/src/templates/workspace-root/AGENTS.md +3 -1
  22. package/corpus/core/src/vite/client.ts +17 -0
  23. package/corpus/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  24. package/corpus/templates/analytics/AGENTS.md +5 -0
  25. package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  26. package/corpus/templates/assets/AGENTS.md +5 -0
  27. package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  28. package/corpus/templates/brain/AGENTS.md +5 -0
  29. package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  30. package/corpus/templates/calendar/AGENTS.md +5 -0
  31. package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  32. package/corpus/templates/chat/AGENTS.md +4 -1
  33. package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  34. package/corpus/templates/clips/AGENTS.md +5 -0
  35. package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  36. package/corpus/templates/content/AGENTS.md +7 -0
  37. package/corpus/templates/content/actions/_content-database-personal-view.ts +49 -5
  38. package/corpus/templates/content/actions/_content-document-access.ts +21 -0
  39. package/corpus/templates/content/actions/_content-favorites.ts +91 -0
  40. package/corpus/templates/content/actions/_content-files.ts +79 -23
  41. package/corpus/templates/content/actions/_content-sidebar-state.ts +24 -0
  42. package/corpus/templates/content/actions/_content-space-access.ts +15 -17
  43. package/corpus/templates/content/actions/_content-space-catalog-guards.ts +20 -0
  44. package/corpus/templates/content/actions/_content-spaces.ts +364 -167
  45. package/corpus/templates/content/actions/_database-source-utils.ts +8 -0
  46. package/corpus/templates/content/actions/_database-utils.ts +249 -15
  47. package/corpus/templates/content/actions/_delete-content-space.ts +84 -0
  48. package/corpus/templates/content/actions/_files-system-properties.ts +499 -0
  49. package/corpus/templates/content/actions/_local-file-documents.ts +0 -1
  50. package/corpus/templates/content/actions/_property-utils.ts +18 -9
  51. package/corpus/templates/content/actions/bind-content-database-source-field.ts +3 -0
  52. package/corpus/templates/content/actions/configure-document-property.ts +3 -0
  53. package/corpus/templates/content/actions/create-content-database.ts +2 -2
  54. package/corpus/templates/content/actions/create-content-space.ts +34 -0
  55. package/corpus/templates/content/actions/create-document.ts +4 -1
  56. package/corpus/templates/content/actions/delete-content-space.ts +19 -0
  57. package/corpus/templates/content/actions/delete-database-items.ts +28 -1
  58. package/corpus/templates/content/actions/delete-document-property.ts +3 -0
  59. package/corpus/templates/content/actions/delete-document.ts +39 -2
  60. package/corpus/templates/content/actions/duplicate-database-item.ts +6 -0
  61. package/corpus/templates/content/actions/duplicate-database-items.ts +6 -0
  62. package/corpus/templates/content/actions/duplicate-document-property.ts +3 -0
  63. package/corpus/templates/content/actions/get-content-database.ts +11 -2
  64. package/corpus/templates/content/actions/get-content-sidebar-state.ts +23 -0
  65. package/corpus/templates/content/actions/get-document.ts +32 -7
  66. package/corpus/templates/content/actions/import-content-source.ts +33 -1
  67. package/corpus/templates/content/actions/list-content-spaces.ts +34 -2
  68. package/corpus/templates/content/actions/list-documents.ts +39 -7
  69. package/corpus/templates/content/actions/reorder-document-property.ts +9 -0
  70. package/corpus/templates/content/actions/set-document-property.ts +41 -23
  71. package/corpus/templates/content/actions/share-local-file-document.ts +15 -0
  72. package/corpus/templates/content/actions/submit-content-database-form.ts +7 -0
  73. package/corpus/templates/content/actions/sync-local-folder-source.ts +14 -0
  74. package/corpus/templates/content/actions/update-content-database-view.ts +1 -0
  75. package/corpus/templates/content/actions/update-content-sidebar-state.ts +27 -0
  76. package/corpus/templates/content/actions/update-document.ts +158 -66
  77. package/corpus/templates/content/app/components/EmptyState.tsx +4 -57
  78. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +8 -1
  79. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +519 -178
  80. package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +39 -0
  81. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
  82. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +266 -7
  83. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +430 -152
  84. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +467 -66
  85. package/corpus/templates/content/app/components/editor/database/view-config.ts +9 -3
  86. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +720 -196
  87. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +173 -1
  88. package/corpus/templates/content/app/components/sidebar/document-sidebar-sections.ts +1 -1
  89. package/corpus/templates/content/app/components/sidebar/select-content-space.ts +91 -16
  90. package/corpus/templates/content/app/entry.client.tsx +5 -1
  91. package/corpus/templates/content/app/hooks/use-content-database.ts +84 -3
  92. package/corpus/templates/content/app/hooks/use-content-spaces.ts +60 -2
  93. package/corpus/templates/content/app/hooks/use-create-page.ts +30 -0
  94. package/corpus/templates/content/app/hooks/use-documents.ts +270 -19
  95. package/corpus/templates/content/app/i18n-data.ts +243 -20
  96. package/corpus/templates/content/app/lib/local-folder-picker-safety.ts +54 -0
  97. package/corpus/templates/content/app/lib/local-folder-picker-support.ts +18 -0
  98. package/corpus/templates/content/app/routes/_app.favorites.tsx +30 -0
  99. package/corpus/templates/content/app/routes/_app.local-files.tsx +18 -6
  100. package/corpus/templates/content/changelog/2026-07-17-workspace-files-now-stay-in-a-loading-state-until-automatic-.md +6 -0
  101. package/corpus/templates/content/changelog/2026-07-17-workspace-switching-now-opens-the-selected-files-database-pr.md +6 -0
  102. package/corpus/templates/content/changelog/2026-07-17-workspaces-now-expand-above-their-own-files-list-without-dup.md +6 -0
  103. package/corpus/templates/content/changelog/2026-07-18-create-named-workspaces-each-with-its-own-private-files-data.md +6 -0
  104. package/corpus/templates/content/changelog/2026-07-18-favorites-align-with-workspace-and-file-rows-in-the-sidebar.md +6 -0
  105. package/corpus/templates/content/changelog/2026-07-18-local-folder-selection-no-longer-invokes-the-unsafe-native-p.md +6 -0
  106. package/corpus/templates/content/changelog/2026-07-18-made-content-workspaces-independently-expandable-restored-or.md +6 -0
  107. package/corpus/templates/content/changelog/2026-07-18-page-details-now-live-in-a-shared-info-and-comments-rail-dat.md +6 -0
  108. package/corpus/templates/content/changelog/2026-07-18-workspace-database-views-now-control-sidebar-workspace-navig.md +6 -0
  109. package/corpus/templates/content/changelog/2026-07-18-workspace-navigation-now-aligns-favorites-with-workspace-pag.md +6 -0
  110. package/corpus/templates/content/changelog/2026-07-19-favorites-now-open-as-a-table-and-compact-breadcrumbs-make-w.md +6 -0
  111. package/corpus/templates/content/changelog/2026-07-19-files-databases-now-start-unfiltered-sidebar-rows-exactly-fo.md +6 -0
  112. package/corpus/templates/content/changelog/2026-07-19-sidebar-page-trees-now-open-only-when-expanded-remember-thei.md +6 -0
  113. package/corpus/templates/content/changelog/2026-07-19-workspace-file-databases-now-use-the-workspace-name-open-as-.md +6 -0
  114. package/corpus/templates/content/changelog/2026-07-19-workspace-navigation-now-stays-stable-on-hover-uses-folder-i.md +6 -0
  115. package/corpus/templates/content/changelog/2026-07-19-workspace-rows-now-use-folder-icons-and-deleting-a-user-crea.md +6 -0
  116. package/corpus/templates/content/changelog/2026-07-19-workspaces-can-be-created-without-leaving-the-workspaces-dat.md +6 -0
  117. package/corpus/templates/content/e2e/global-setup.ts +3 -2
  118. package/corpus/templates/content/parity/matrix.md +28 -28
  119. package/corpus/templates/content/parity/matrix.ts +8 -2
  120. package/corpus/templates/content/server/db/index.ts +1 -0
  121. package/corpus/templates/content/server/db/schema.ts +10 -0
  122. package/corpus/templates/content/server/lib/documents.ts +12 -7
  123. package/corpus/templates/content/server/plugins/db.ts +17 -0
  124. package/corpus/templates/content/shared/api.ts +9 -0
  125. package/corpus/templates/content/shared/database-form.ts +11 -2
  126. package/corpus/templates/content/vite.config.ts +1 -0
  127. package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  128. package/corpus/templates/design/AGENTS.md +5 -0
  129. package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  130. package/corpus/templates/dispatch/AGENTS.md +5 -0
  131. package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  132. package/corpus/templates/forms/AGENTS.md +5 -0
  133. package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  134. package/corpus/templates/macros/AGENTS.md +5 -0
  135. package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  136. package/corpus/templates/mail/AGENTS.md +5 -0
  137. package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  138. package/corpus/templates/plan/AGENTS.md +5 -0
  139. package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  140. package/corpus/templates/slides/AGENTS.md +5 -0
  141. package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  142. package/corpus/templates/tasks/AGENTS.md +5 -0
  143. package/dist/cli/create.d.ts.map +1 -1
  144. package/dist/cli/create.js +21 -0
  145. package/dist/cli/create.js.map +1 -1
  146. package/dist/cli/skills.d.ts.map +1 -1
  147. package/dist/cli/skills.js +19 -0
  148. package/dist/cli/skills.js.map +1 -1
  149. package/dist/client/build-compatibility.d.ts +11 -0
  150. package/dist/client/build-compatibility.d.ts.map +1 -0
  151. package/dist/client/build-compatibility.js +56 -0
  152. package/dist/client/build-compatibility.js.map +1 -0
  153. package/dist/client/route-chunk-recovery/index.d.ts +1 -0
  154. package/dist/client/route-chunk-recovery/index.d.ts.map +1 -1
  155. package/dist/client/route-chunk-recovery/index.js +1 -0
  156. package/dist/client/route-chunk-recovery/index.js.map +1 -1
  157. package/dist/client/use-action.d.ts.map +1 -1
  158. package/dist/client/use-action.js +19 -1
  159. package/dist/client/use-action.js.map +1 -1
  160. package/dist/collab/routes.d.ts +1 -1
  161. package/dist/deploy/build.js +2 -1
  162. package/dist/deploy/build.js.map +1 -1
  163. package/dist/notifications/routes.d.ts +1 -1
  164. package/dist/observability/routes.d.ts +3 -3
  165. package/dist/org/context.d.ts +4 -2
  166. package/dist/org/context.d.ts.map +1 -1
  167. package/dist/org/context.js +44 -22
  168. package/dist/org/context.js.map +1 -1
  169. package/dist/progress/routes.d.ts +1 -1
  170. package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
  171. package/dist/provider-api/actions/provider-api.d.ts +12 -12
  172. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  173. package/dist/resources/handlers.d.ts +1 -1
  174. package/dist/server/action-routes.d.ts.map +1 -1
  175. package/dist/server/action-routes.js +32 -2
  176. package/dist/server/action-routes.js.map +1 -1
  177. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  178. package/dist/server/auth-plugin.d.ts.map +1 -1
  179. package/dist/server/auth-plugin.js +9 -5
  180. package/dist/server/auth-plugin.js.map +1 -1
  181. package/dist/server/better-auth-instance.d.ts.map +1 -1
  182. package/dist/server/better-auth-instance.js +4 -1
  183. package/dist/server/better-auth-instance.js.map +1 -1
  184. package/dist/server/transcribe-voice.d.ts +1 -1
  185. package/dist/shared/mcp-embed-headers.d.ts +1 -1
  186. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  187. package/dist/shared/mcp-embed-headers.js +1 -1
  188. package/dist/shared/mcp-embed-headers.js.map +1 -1
  189. package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  190. package/dist/templates/chat/AGENTS.md +4 -1
  191. package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  192. package/dist/templates/default/AGENTS.md +4 -1
  193. package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  194. package/dist/templates/headless/AGENTS.md +5 -1
  195. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +26 -0
  196. package/dist/templates/workspace-core/AGENTS.md +3 -1
  197. package/dist/templates/workspace-root/AGENTS.md +3 -1
  198. package/dist/vite/client.d.ts +6 -0
  199. package/dist/vite/client.d.ts.map +1 -1
  200. package/dist/vite/client.js +8 -0
  201. package/dist/vite/client.js.map +1 -1
  202. package/package.json +1 -1
  203. package/src/cli/create.ts +23 -0
  204. package/src/cli/skills.ts +24 -0
  205. package/src/client/build-compatibility.ts +77 -0
  206. package/src/client/route-chunk-recovery/index.ts +1 -0
  207. package/src/client/use-action.ts +32 -1
  208. package/src/deploy/build.ts +4 -1
  209. package/src/org/context.ts +56 -30
  210. package/src/server/action-routes.ts +52 -2
  211. package/src/server/auth-plugin.ts +9 -4
  212. package/src/server/better-auth-instance.ts +5 -1
  213. package/src/shared/mcp-embed-headers.ts +1 -1
  214. package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  215. package/src/templates/chat/AGENTS.md +4 -1
  216. package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  217. package/src/templates/default/AGENTS.md +4 -1
  218. package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  219. package/src/templates/headless/AGENTS.md +5 -1
  220. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +26 -0
  221. package/src/templates/workspace-core/AGENTS.md +3 -1
  222. package/src/templates/workspace-root/AGENTS.md +3 -1
  223. package/src/vite/client.ts +17 -0
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import { Button } from "@agent-native/toolkit/ui/button";
2
3
  import {
3
4
  Collapsible,
@@ -15,12 +16,39 @@ import type {
15
16
  import {
16
17
  IconChevronDown,
17
18
  IconChevronRight,
19
+ IconDatabase,
20
+ IconDots,
18
21
  IconFileText,
19
22
  IconLoader2,
23
+ IconPlus,
24
+ IconStar,
25
+ IconTrash,
20
26
  } from "@tabler/icons-react";
21
- import { useEffect, useState, type MouseEvent } from "react";
27
+ import { useEffect, useState, type MouseEvent, type ReactNode } from "react";
22
28
  import { Link } from "react-router";
23
29
 
30
+ import {
31
+ AlertDialog,
32
+ AlertDialogAction,
33
+ AlertDialogCancel,
34
+ AlertDialogContent,
35
+ AlertDialogDescription,
36
+ AlertDialogFooter,
37
+ AlertDialogHeader,
38
+ AlertDialogTitle,
39
+ } from "@/components/ui/alert-dialog";
40
+ import {
41
+ DropdownMenu,
42
+ DropdownMenuContent,
43
+ DropdownMenuItem,
44
+ DropdownMenuSeparator,
45
+ DropdownMenuTrigger,
46
+ } from "@/components/ui/dropdown-menu";
47
+ import {
48
+ Tooltip,
49
+ TooltipContent,
50
+ TooltipTrigger,
51
+ } from "@/components/ui/tooltip";
24
52
  import { cn } from "@/lib/utils";
25
53
 
26
54
  import { applyDatabaseView } from "./filter-sort";
@@ -68,13 +96,33 @@ export function ContentFilesSidebarView({
68
96
  data,
69
97
  overrides,
70
98
  isLoading,
99
+ activeDocumentId,
71
100
  labels,
72
101
  onSelectView,
102
+ onOpenItem,
103
+ onCreateChildPage,
104
+ onCreateChildDatabase,
105
+ onDeleteItem,
106
+ onToggleFavorite,
107
+ expandedDocumentIds,
108
+ onDocumentExpandedChange,
109
+ renderItem,
110
+ scroll = true,
73
111
  }: {
74
112
  data: ContentDatabaseResponse | undefined;
75
113
  overrides: ContentDatabasePersonalViewOverrides | null | undefined;
76
114
  isLoading: boolean;
115
+ activeDocumentId?: string | null;
77
116
  onSelectView?: (viewId: string) => void;
117
+ onOpenItem?: (item: ContentDatabaseItem) => boolean;
118
+ onCreateChildPage?: (item: ContentDatabaseItem) => void;
119
+ onCreateChildDatabase?: (item: ContentDatabaseItem) => void;
120
+ onDeleteItem?: (item: ContentDatabaseItem) => void;
121
+ onToggleFavorite?: (item: ContentDatabaseItem) => void;
122
+ expandedDocumentIds?: ReadonlySet<string>;
123
+ onDocumentExpandedChange?: (documentId: string, expanded: boolean) => void;
124
+ renderItem?: (item: ContentDatabaseItem) => ReactNode;
125
+ scroll?: boolean;
78
126
  labels: Omit<
79
127
  Parameters<typeof DatabaseSidebarView>[0],
80
128
  | "groups"
@@ -84,6 +132,8 @@ export function ContentFilesSidebarView({
84
132
  | "openPagesIn"
85
133
  | "onClearResultConstraints"
86
134
  | "onPreview"
135
+ | "renderItem"
136
+ | "scroll"
87
137
  >;
88
138
  }) {
89
139
  const viewConfig = applyPersonalSidebarViewOverrides(
@@ -99,12 +149,17 @@ export function ContentFilesSidebarView({
99
149
  const activeView =
100
150
  viewConfig.views.find((view) => view.id === selectedViewId) ??
101
151
  activeDatabaseView(viewConfig);
152
+ const [constraintsCleared, setConstraintsCleared] = useState(false);
153
+ const activeFilterKey = JSON.stringify(activeView.filters);
154
+ useEffect(() => {
155
+ setConstraintsCleared(false);
156
+ }, [activeFilterKey, activeView.id]);
102
157
  const items = data
103
158
  ? applyDatabaseView(
104
159
  data.items,
105
160
  data.properties,
106
161
  "",
107
- activeView.filters,
162
+ constraintsCleared ? [] : activeView.filters,
108
163
  activeView.sorts,
109
164
  activeView.filterMode ?? "and",
110
165
  )
@@ -117,6 +172,11 @@ export function ContentFilesSidebarView({
117
172
  ),
118
173
  activeView.hideEmptyGroups === true,
119
174
  );
175
+ const hierarchyItems = data?.properties.some(
176
+ (property) => property.definition.systemRole === "files_parent",
177
+ )
178
+ ? items
179
+ : undefined;
120
180
  return (
121
181
  <div className="min-w-0">
122
182
  {viewConfig.views.length > 1 && (
@@ -147,10 +207,23 @@ export function ContentFilesSidebarView({
147
207
  !!databaseViewGroupingProperty(activeView, data?.properties ?? [])
148
208
  }
149
209
  isLoading={isLoading}
150
- hasActiveConstraints={false}
210
+ hasActiveConstraints={
211
+ !constraintsCleared && activeView.filters.length > 0
212
+ }
151
213
  openPagesIn="full_page"
152
- onClearResultConstraints={() => {}}
214
+ onClearResultConstraints={() => setConstraintsCleared(true)}
153
215
  onPreview={() => {}}
216
+ onOpenItem={onOpenItem}
217
+ activeDocumentId={activeDocumentId}
218
+ onCreateChildPage={onCreateChildPage}
219
+ onCreateChildDatabase={onCreateChildDatabase}
220
+ onDeleteItem={onDeleteItem}
221
+ onToggleFavorite={onToggleFavorite}
222
+ expandedDocumentIds={expandedDocumentIds}
223
+ onDocumentExpandedChange={onDocumentExpandedChange}
224
+ renderItem={renderItem}
225
+ hierarchyItems={hierarchyItems}
226
+ scroll={scroll}
154
227
  />
155
228
  </div>
156
229
  );
@@ -164,6 +237,17 @@ export function DatabaseSidebarView({
164
237
  openPagesIn,
165
238
  onClearResultConstraints,
166
239
  onPreview,
240
+ onOpenItem,
241
+ activeDocumentId,
242
+ onCreateChildPage,
243
+ onCreateChildDatabase,
244
+ onDeleteItem,
245
+ onToggleFavorite,
246
+ expandedDocumentIds,
247
+ onDocumentExpandedChange,
248
+ renderItem,
249
+ hierarchyItems,
250
+ scroll = true,
167
251
  loadingLabel,
168
252
  noMatchesLabel,
169
253
  clearLabel,
@@ -177,6 +261,17 @@ export function DatabaseSidebarView({
177
261
  openPagesIn: ContentDatabaseOpenPagesIn;
178
262
  onClearResultConstraints: () => void;
179
263
  onPreview: (item: ContentDatabaseItem) => void;
264
+ onOpenItem?: (item: ContentDatabaseItem) => boolean;
265
+ activeDocumentId?: string | null;
266
+ onCreateChildPage?: (item: ContentDatabaseItem) => void;
267
+ onCreateChildDatabase?: (item: ContentDatabaseItem) => void;
268
+ onDeleteItem?: (item: ContentDatabaseItem) => void;
269
+ onToggleFavorite?: (item: ContentDatabaseItem) => void;
270
+ expandedDocumentIds?: ReadonlySet<string>;
271
+ onDocumentExpandedChange?: (documentId: string, expanded: boolean) => void;
272
+ renderItem?: (item: ContentDatabaseItem) => ReactNode;
273
+ hierarchyItems?: ContentDatabaseItem[];
274
+ scroll?: boolean;
180
275
  loadingLabel: string;
181
276
  noMatchesLabel: string;
182
277
  clearLabel: string;
@@ -186,7 +281,14 @@ export function DatabaseSidebarView({
186
281
  const [collapsedGroupIds, setCollapsedGroupIds] = useState<Set<string>>(
187
282
  () => new Set(),
188
283
  );
284
+ const [localExpandedDocumentIds, setLocalExpandedDocumentIds] = useState<
285
+ Set<string>
286
+ >(() => new Set());
189
287
  const items = groups.flatMap((group) => group.items);
288
+ const itemTree =
289
+ !grouped && hierarchyItems
290
+ ? databaseSidebarItemTree(items, hierarchyItems)
291
+ : null;
190
292
 
191
293
  function setGroupOpen(groupId: string, open: boolean) {
192
294
  setCollapsedGroupIds((current) => {
@@ -197,6 +299,52 @@ export function DatabaseSidebarView({
197
299
  });
198
300
  }
199
301
 
302
+ function setDocumentOpen(documentId: string, open: boolean) {
303
+ if (onDocumentExpandedChange) {
304
+ onDocumentExpandedChange(documentId, open);
305
+ return;
306
+ }
307
+ setLocalExpandedDocumentIds((current) => {
308
+ const next = new Set(current);
309
+ if (open) next.add(documentId);
310
+ else next.delete(documentId);
311
+ return next;
312
+ });
313
+ }
314
+
315
+ function renderTreeNode(node: DatabaseSidebarItemTreeNode, depth: number) {
316
+ const open = (expandedDocumentIds ?? localExpandedDocumentIds).has(
317
+ node.item.document.id,
318
+ );
319
+ return (
320
+ <div key={node.item.id} className="min-w-0">
321
+ <DatabaseSidebarRow
322
+ item={node.item}
323
+ openPagesIn={openPagesIn}
324
+ onPreview={onPreview}
325
+ onOpenItem={onOpenItem}
326
+ active={node.item.document.id === activeDocumentId}
327
+ onCreateChildPage={onCreateChildPage}
328
+ onCreateChildDatabase={onCreateChildDatabase}
329
+ onDeleteItem={onDeleteItem}
330
+ onToggleFavorite={onToggleFavorite}
331
+ untitledLabel={untitledLabel}
332
+ depth={depth}
333
+ hasChildren={node.children.length > 0}
334
+ expanded={open}
335
+ onToggleExpanded={(nextOpen) =>
336
+ setDocumentOpen(node.item.document.id, nextOpen)
337
+ }
338
+ />
339
+ {open && node.children.length > 0 ? (
340
+ <div>
341
+ {node.children.map((child) => renderTreeNode(child, depth + 1))}
342
+ </div>
343
+ ) : null}
344
+ </div>
345
+ );
346
+ }
347
+
200
348
  if (isLoading) {
201
349
  return (
202
350
  <div className="flex h-16 items-center gap-2 px-2 text-sm text-muted-foreground">
@@ -222,56 +370,86 @@ export function DatabaseSidebarView({
222
370
  );
223
371
  }
224
372
 
225
- return (
226
- <ScrollArea className="max-h-[32rem] w-full">
227
- <nav aria-label={navigationLabel} className="grid gap-1 p-1">
228
- {grouped
229
- ? groups.map((group) => {
230
- const open = !collapsedGroupIds.has(group.id);
231
- return (
232
- <Collapsible
233
- key={group.id}
234
- open={open}
235
- onOpenChange={(nextOpen) => setGroupOpen(group.id, nextOpen)}
236
- >
237
- <CollapsibleTrigger className="group flex h-7 w-full items-center gap-1 rounded px-1.5 text-left text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
238
- {open ? (
239
- <IconChevronDown className="size-3.5 shrink-0" />
373
+ const navigation = (
374
+ <nav
375
+ aria-label={navigationLabel}
376
+ className="grid min-w-0 gap-1 overflow-x-hidden p-1"
377
+ >
378
+ {grouped
379
+ ? groups.map((group) => {
380
+ const open = !collapsedGroupIds.has(group.id);
381
+ return (
382
+ <Collapsible
383
+ key={group.id}
384
+ open={open}
385
+ onOpenChange={(nextOpen) => setGroupOpen(group.id, nextOpen)}
386
+ >
387
+ <CollapsibleTrigger className="group flex h-7 w-full items-center gap-1 rounded px-1.5 text-left text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
388
+ {open ? (
389
+ <IconChevronDown className="size-3.5 shrink-0" />
390
+ ) : (
391
+ <IconChevronRight className="size-3.5 shrink-0" />
392
+ )}
393
+ <span className="min-w-0 flex-1 truncate">{group.label}</span>
394
+ <span className="text-[11px] font-normal text-muted-foreground/75">
395
+ {group.items.length}
396
+ </span>
397
+ </CollapsibleTrigger>
398
+ <CollapsibleContent className="grid gap-0.5 pl-2">
399
+ {group.items.map((item) =>
400
+ renderItem ? (
401
+ <div key={item.id} className="min-w-0">
402
+ {renderItem(item)}
403
+ </div>
240
404
  ) : (
241
- <IconChevronRight className="size-3.5 shrink-0" />
242
- )}
243
- <span className="min-w-0 flex-1 truncate">
244
- {group.label}
245
- </span>
246
- <span className="text-[11px] font-normal text-muted-foreground/75">
247
- {group.items.length}
248
- </span>
249
- </CollapsibleTrigger>
250
- <CollapsibleContent className="grid gap-0.5 pl-2">
251
- {group.items.map((item) => (
252
405
  <DatabaseSidebarRow
253
406
  key={item.id}
254
407
  item={item}
255
408
  openPagesIn={openPagesIn}
256
409
  onPreview={onPreview}
410
+ onOpenItem={onOpenItem}
411
+ active={item.document.id === activeDocumentId}
412
+ onCreateChildPage={onCreateChildPage}
413
+ onCreateChildDatabase={onCreateChildDatabase}
414
+ onDeleteItem={onDeleteItem}
415
+ onToggleFavorite={onToggleFavorite}
257
416
  untitledLabel={untitledLabel}
258
417
  />
259
- ))}
260
- </CollapsibleContent>
261
- </Collapsible>
262
- );
263
- })
264
- : items.map((item) => (
265
- <DatabaseSidebarRow
266
- key={item.id}
267
- item={item}
268
- openPagesIn={openPagesIn}
269
- onPreview={onPreview}
270
- untitledLabel={untitledLabel}
271
- />
272
- ))}
273
- </nav>
274
- </ScrollArea>
418
+ ),
419
+ )}
420
+ </CollapsibleContent>
421
+ </Collapsible>
422
+ );
423
+ })
424
+ : itemTree
425
+ ? itemTree.map((node) => renderTreeNode(node, 0))
426
+ : items.map((item) =>
427
+ renderItem ? (
428
+ <div key={item.id} className="min-w-0">
429
+ {renderItem(item)}
430
+ </div>
431
+ ) : (
432
+ <DatabaseSidebarRow
433
+ key={item.id}
434
+ item={item}
435
+ openPagesIn={openPagesIn}
436
+ onPreview={onPreview}
437
+ onOpenItem={onOpenItem}
438
+ active={item.document.id === activeDocumentId}
439
+ onCreateChildPage={onCreateChildPage}
440
+ onCreateChildDatabase={onCreateChildDatabase}
441
+ onDeleteItem={onDeleteItem}
442
+ onToggleFavorite={onToggleFavorite}
443
+ untitledLabel={untitledLabel}
444
+ />
445
+ ),
446
+ )}
447
+ </nav>
448
+ );
449
+ return scroll ? (
450
+ <ScrollArea className="max-h-[32rem] w-full">{navigation}</ScrollArea>
451
+ ) : (
452
+ navigation
275
453
  );
276
454
  }
277
455
 
@@ -279,16 +457,46 @@ function DatabaseSidebarRow({
279
457
  item,
280
458
  openPagesIn,
281
459
  onPreview,
460
+ onOpenItem,
461
+ active,
462
+ onCreateChildPage,
463
+ onCreateChildDatabase,
464
+ onDeleteItem,
465
+ onToggleFavorite,
282
466
  untitledLabel,
467
+ depth = 0,
468
+ hasChildren = false,
469
+ expanded = false,
470
+ onToggleExpanded,
283
471
  }: {
284
472
  item: ContentDatabaseItem;
285
473
  openPagesIn: ContentDatabaseOpenPagesIn;
286
474
  onPreview: (item: ContentDatabaseItem) => void;
475
+ onOpenItem?: (item: ContentDatabaseItem) => boolean;
476
+ active: boolean;
477
+ onCreateChildPage?: (item: ContentDatabaseItem) => void;
478
+ onCreateChildDatabase?: (item: ContentDatabaseItem) => void;
479
+ onDeleteItem?: (item: ContentDatabaseItem) => void;
480
+ onToggleFavorite?: (item: ContentDatabaseItem) => void;
287
481
  untitledLabel: string;
482
+ depth?: number;
483
+ hasChildren?: boolean;
484
+ expanded?: boolean;
485
+ onToggleExpanded?: (open: boolean) => void;
288
486
  }) {
487
+ const t = useT();
488
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
489
+ const canEdit = item.document.canEdit !== false;
490
+ const canManage =
491
+ item.document.canManage === true ||
492
+ item.document.accessRole === "owner" ||
493
+ item.document.accessRole === "admin";
494
+ const canCreateChild = canEdit && Boolean(onCreateChildPage);
495
+ const hasMenuActions =
496
+ (canEdit && Boolean(onToggleFavorite)) ||
497
+ (canManage && Boolean(onDeleteItem));
289
498
  function handleClick(event: MouseEvent<HTMLAnchorElement>) {
290
499
  if (
291
- openPagesIn !== "preview" ||
292
500
  event.defaultPrevented ||
293
501
  event.button !== 0 ||
294
502
  event.metaKey ||
@@ -298,33 +506,226 @@ function DatabaseSidebarRow({
298
506
  ) {
299
507
  return;
300
508
  }
509
+ if (onOpenItem?.(item)) {
510
+ event.preventDefault();
511
+ return;
512
+ }
513
+ if (openPagesIn !== "preview") return;
301
514
  event.preventDefault();
302
515
  onPreview(item);
303
516
  }
304
517
 
518
+ const title = item.document.title || untitledLabel;
519
+
305
520
  return (
306
- <Link
307
- to={`/page/${item.document.id}`}
308
- className={cn(
309
- "flex h-7 min-w-0 items-center gap-1.5 rounded px-1.5 text-sm text-foreground/85 hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
310
- item.document.icon ? "pl-1" : "pl-1.5",
311
- )}
312
- onClick={handleClick}
313
- >
314
- {item.document.icon ? (
315
- <span aria-hidden="true" className="shrink-0 text-sm leading-none">
316
- {item.document.icon}
317
- </span>
318
- ) : (
319
- <IconFileText className="size-3.5 shrink-0 text-muted-foreground" />
320
- )}
321
- <span className="min-w-0 flex-1 truncate">
322
- {item.document.title || untitledLabel}
323
- </span>
324
- </Link>
521
+ <>
522
+ <div className="group relative min-w-0">
523
+ {hasChildren ? (
524
+ <button
525
+ type="button"
526
+ className="pointer-events-none absolute top-0 z-10 flex size-7 items-center justify-center rounded text-muted-foreground opacity-0 hover:bg-muted hover:text-foreground group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
527
+ style={{
528
+ insetInlineStart: `${databaseSidebarRowIndent(depth, hasChildren)}px`,
529
+ }}
530
+ aria-label={`${expanded ? "Collapse" : "Expand"} ${title}`}
531
+ aria-expanded={expanded}
532
+ onPointerUp={(event) => event.currentTarget.blur()}
533
+ onClick={() => onToggleExpanded?.(!expanded)}
534
+ >
535
+ <IconChevronRight
536
+ className={cn(
537
+ "size-3.5 transition-transform",
538
+ expanded && "rotate-90",
539
+ )}
540
+ />
541
+ </button>
542
+ ) : null}
543
+ <Link
544
+ to={`/page/${item.document.id}`}
545
+ className={cn(
546
+ "flex h-7 min-w-0 items-center gap-1.5 rounded pe-1.5 text-sm text-foreground/85 hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
547
+ active && "font-semibold text-foreground",
548
+ )}
549
+ style={{
550
+ paddingInlineStart: `${databaseSidebarRowIndent(depth, hasChildren)}px`,
551
+ }}
552
+ onClick={handleClick}
553
+ onPointerUp={(event) => event.currentTarget.blur()}
554
+ aria-current={active ? "page" : undefined}
555
+ >
556
+ <span
557
+ className={cn(
558
+ "flex size-7 shrink-0 items-center justify-center",
559
+ hasChildren &&
560
+ "group-hover:opacity-0 group-focus-within:opacity-0",
561
+ )}
562
+ aria-hidden="true"
563
+ >
564
+ {item.document.icon ? (
565
+ <span className="text-sm leading-none">{item.document.icon}</span>
566
+ ) : (
567
+ <IconFileText className="size-3.5 text-muted-foreground" />
568
+ )}
569
+ </span>
570
+ <span
571
+ className={cn(
572
+ "min-w-0 flex-1 truncate",
573
+ (hasMenuActions || canCreateChild) && "pe-12",
574
+ )}
575
+ >
576
+ {title}
577
+ </span>
578
+ </Link>
579
+
580
+ {(hasMenuActions || canCreateChild) && (
581
+ <div className="pointer-events-none absolute end-1 top-1/2 z-10 flex -translate-y-1/2 items-center gap-0.5 rounded bg-sidebar px-0.5 opacity-0 group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100">
582
+ {hasMenuActions && (
583
+ <DropdownMenu>
584
+ <DropdownMenuTrigger asChild>
585
+ <button
586
+ type="button"
587
+ className="flex size-6 items-center justify-center rounded text-foreground hover:bg-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
588
+ aria-label={`More actions for ${title}`}
589
+ >
590
+ <IconDots size={14} />
591
+ </button>
592
+ </DropdownMenuTrigger>
593
+ <DropdownMenuContent align="start" className="w-48">
594
+ {canEdit && onToggleFavorite ? (
595
+ <DropdownMenuItem onSelect={() => onToggleFavorite(item)}>
596
+ <IconStar
597
+ className={cn(
598
+ "me-2 size-4",
599
+ item.document.isFavorite && "fill-current",
600
+ )}
601
+ />
602
+ {item.document.isFavorite
603
+ ? "Remove from favorites"
604
+ : "Add to favorites"}
605
+ </DropdownMenuItem>
606
+ ) : null}
607
+ {canEdit && onToggleFavorite && canManage && onDeleteItem ? (
608
+ <DropdownMenuSeparator />
609
+ ) : null}
610
+ {canManage && onDeleteItem ? (
611
+ <DropdownMenuItem
612
+ className="text-destructive focus:text-destructive"
613
+ onSelect={() => setDeleteDialogOpen(true)}
614
+ >
615
+ <IconTrash className="me-2 size-4" />
616
+ {t("database.delete")}
617
+ </DropdownMenuItem>
618
+ ) : null}
619
+ </DropdownMenuContent>
620
+ </DropdownMenu>
621
+ )}
622
+
623
+ {canCreateChild && (
624
+ <DropdownMenu>
625
+ <Tooltip>
626
+ <TooltipTrigger asChild>
627
+ <DropdownMenuTrigger asChild>
628
+ <button
629
+ type="button"
630
+ className="flex size-6 items-center justify-center rounded text-foreground hover:bg-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
631
+ aria-label={t("sidebar.addChildTo", { title })}
632
+ >
633
+ <IconPlus size={14} />
634
+ </button>
635
+ </DropdownMenuTrigger>
636
+ </TooltipTrigger>
637
+ <TooltipContent>{t("sidebar.addChild")}</TooltipContent>
638
+ </Tooltip>
639
+ <DropdownMenuContent align="start" className="w-44">
640
+ <DropdownMenuItem onSelect={() => onCreateChildPage?.(item)}>
641
+ <IconFileText className="me-2 size-4" />
642
+ {t("sidebar.page")}
643
+ </DropdownMenuItem>
644
+ {onCreateChildDatabase ? (
645
+ <DropdownMenuItem
646
+ onSelect={() => onCreateChildDatabase(item)}
647
+ >
648
+ <IconDatabase className="me-2 size-4" />
649
+ {t("sidebar.database")}
650
+ </DropdownMenuItem>
651
+ ) : null}
652
+ </DropdownMenuContent>
653
+ </DropdownMenu>
654
+ )}
655
+ </div>
656
+ )}
657
+ </div>
658
+
659
+ <AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
660
+ <AlertDialogContent>
661
+ <AlertDialogHeader>
662
+ <AlertDialogTitle>
663
+ {t("sidebar.deletePageQuestion")}
664
+ </AlertDialogTitle>
665
+ <AlertDialogDescription>
666
+ {t("sidebar.deletePageDescription", { title })}
667
+ </AlertDialogDescription>
668
+ </AlertDialogHeader>
669
+ <AlertDialogFooter>
670
+ <AlertDialogCancel>{t("comments.cancel")}</AlertDialogCancel>
671
+ <AlertDialogAction
672
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
673
+ onClick={() => onDeleteItem?.(item)}
674
+ >
675
+ {t("database.delete")}
676
+ </AlertDialogAction>
677
+ </AlertDialogFooter>
678
+ </AlertDialogContent>
679
+ </AlertDialog>
680
+ </>
325
681
  );
326
682
  }
327
683
 
328
684
  export function databaseSidebarRows(groups: DatabaseBoardGroup[]) {
329
685
  return groups.flatMap((group) => group.items);
330
686
  }
687
+
688
+ export interface DatabaseSidebarItemTreeNode {
689
+ item: ContentDatabaseItem;
690
+ children: DatabaseSidebarItemTreeNode[];
691
+ }
692
+
693
+ export function databaseSidebarRowIndent(depth: number, _hasChildren: boolean) {
694
+ return depth * 18;
695
+ }
696
+
697
+ export function databaseSidebarItemTree(
698
+ rootItems: ContentDatabaseItem[],
699
+ allItems: ContentDatabaseItem[],
700
+ ): DatabaseSidebarItemTreeNode[] {
701
+ const childrenByParentId = new Map<string, ContentDatabaseItem[]>();
702
+ for (const item of allItems) {
703
+ const parentId = item.document.parentId;
704
+ if (!parentId) continue;
705
+ childrenByParentId.set(parentId, [
706
+ ...(childrenByParentId.get(parentId) ?? []),
707
+ item,
708
+ ]);
709
+ }
710
+ const emitted = new Set<string>();
711
+ const visit = (
712
+ item: ContentDatabaseItem,
713
+ ancestors: Set<string>,
714
+ ): DatabaseSidebarItemTreeNode | null => {
715
+ const documentId = item.document.id;
716
+ if (emitted.has(documentId) || ancestors.has(documentId)) return null;
717
+ emitted.add(documentId);
718
+ const nextAncestors = new Set(ancestors).add(documentId);
719
+ return {
720
+ item,
721
+ children: (childrenByParentId.get(documentId) ?? []).flatMap((child) => {
722
+ const node = visit(child, nextAncestors);
723
+ return node ? [node] : [];
724
+ }),
725
+ };
726
+ };
727
+ return rootItems.flatMap((item) => {
728
+ const node = visit(item, new Set());
729
+ return node ? [node] : [];
730
+ });
731
+ }