@agent-native/core 0.84.21 → 0.84.23

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 (137) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/model-config.ts +1 -1
  5. package/corpus/core/src/cli/skills.ts +17 -3
  6. package/corpus/core/src/client/AssistantChat.tsx +27 -4
  7. package/corpus/core/src/client/guided-questions.tsx +11 -6
  8. package/corpus/core/src/client/tool-display.ts +8 -0
  9. package/corpus/core/src/file-upload/builder.ts +7 -2
  10. package/corpus/core/src/notifications/channels.ts +235 -38
  11. package/corpus/core/src/notifications/index.ts +1 -0
  12. package/corpus/core/src/notifications/registry.ts +13 -1
  13. package/corpus/core/src/server/analytics.ts +32 -6
  14. package/corpus/core/src/server/ssr-handler.ts +31 -12
  15. package/corpus/core/src/templates/default/.agents/skills/frontend-design/SKILL.md +2 -0
  16. package/corpus/core/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +2 -0
  17. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +2 -0
  18. package/corpus/templates/analytics/AGENTS.md +21 -0
  19. package/corpus/templates/analytics/README.md +35 -0
  20. package/corpus/templates/analytics/actions/delete-analytics-alert-rule.ts +23 -0
  21. package/corpus/templates/analytics/actions/list-analytics-alert-rules.ts +20 -0
  22. package/corpus/templates/analytics/actions/run-analytics-alerts.ts +23 -0
  23. package/corpus/templates/analytics/actions/save-analytics-alert-rule.ts +69 -0
  24. package/corpus/templates/analytics/changelog/2026-07-01-analytics-can-alert-when-first-party-events-spike.md +6 -0
  25. package/corpus/templates/analytics/changelog/2026-07-01-session-replays-play-back-in-production-instead-of-showing.md +6 -0
  26. package/corpus/templates/analytics/netlify.toml +3 -0
  27. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +126 -0
  28. package/corpus/templates/analytics/server/db/schema.ts +55 -0
  29. package/corpus/templates/analytics/server/handlers/session-replay.ts +5 -2
  30. package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +74 -0
  31. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +803 -0
  32. package/corpus/templates/analytics/server/lib/session-replay.ts +10 -3
  33. package/corpus/templates/analytics/server/plugins/analytics-alert-jobs.ts +54 -0
  34. package/corpus/templates/analytics/server/plugins/db.ts +100 -0
  35. package/corpus/templates/analytics/server/routes/api/analytics-alerts/run.post.ts +60 -0
  36. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +2 -0
  37. package/corpus/templates/assets/README.md +30 -8
  38. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +2 -0
  39. package/corpus/templates/brain/README.md +20 -552
  40. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +2 -0
  41. package/corpus/templates/calendar/README.md +22 -28
  42. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +2 -0
  43. package/corpus/templates/chat/README.md +32 -0
  44. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +2 -0
  45. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +30 -1
  46. package/corpus/templates/clips/README.md +37 -0
  47. package/corpus/templates/clips/actions/finalize-recording.ts +84 -4
  48. package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +256 -0
  49. package/corpus/templates/clips/actions/reprocess-recording.ts +160 -0
  50. package/corpus/templates/clips/app/components/library/library-grid.tsx +18 -7
  51. package/corpus/templates/clips/app/components/library/recording-card.tsx +42 -5
  52. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +41 -1
  53. package/corpus/templates/clips/changelog/2026-07-01-move-to-folder-now-opens-folder-choices-directly-instead-of.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-01-shared-clips-now-start-playing-and-scrub-instantly-instead-o.md +6 -0
  55. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  56. package/corpus/templates/clips/server/lib/video-remux.ts +222 -0
  57. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +37 -0
  58. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +2 -0
  59. package/corpus/templates/content/AGENTS.md +7 -3
  60. package/corpus/templates/content/README.md +22 -34
  61. package/corpus/templates/content/actions/list-trashed-content-databases.ts +4 -0
  62. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +58 -6
  63. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +257 -24
  64. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +41 -15
  65. package/corpus/templates/content/app/i18n/zh-TW.ts +25 -7
  66. package/corpus/templates/content/app/i18n-data.ts +278 -40
  67. package/corpus/templates/content/changelog/2026-06-30-database-once-again-creates-an-inline-database-in-the-curren.md +6 -0
  68. package/corpus/templates/content/shared/api.ts +1 -0
  69. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +75 -19
  70. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +2 -0
  71. package/corpus/templates/design/AGENTS.md +7 -6
  72. package/corpus/templates/design/README.md +33 -0
  73. package/corpus/templates/design/actions/delete-file.ts +86 -10
  74. package/corpus/templates/design/actions/get-design-snapshot.ts +13 -0
  75. package/corpus/templates/design/actions/present-design-variants.ts +11 -13
  76. package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
  77. package/corpus/templates/design/app/pages/DesignEditor.tsx +13 -3
  78. package/corpus/templates/design/changelog/2026-07-01-design-chat-no-longer-shows-an-empty-state-selection-chip-in-the-composer.md +6 -0
  79. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +2 -0
  80. package/corpus/templates/dispatch/README.md +34 -0
  81. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +2 -0
  82. package/corpus/templates/forms/README.md +34 -0
  83. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +2 -0
  84. package/corpus/templates/macros/README.md +28 -0
  85. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +2 -0
  86. package/corpus/templates/mail/README.md +35 -0
  87. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +2 -0
  88. package/corpus/templates/plan/README.md +30 -118
  89. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +2 -0
  90. package/corpus/templates/slides/README.md +34 -0
  91. package/corpus/templates/videos/.agents/skills/frontend-design/SKILL.md +2 -0
  92. package/corpus/templates/videos/README.md +22 -201
  93. package/dist/agent/model-config.js +1 -1
  94. package/dist/agent/model-config.js.map +1 -1
  95. package/dist/cli/skills.d.ts.map +1 -1
  96. package/dist/cli/skills.js +17 -3
  97. package/dist/cli/skills.js.map +1 -1
  98. package/dist/client/AssistantChat.d.ts.map +1 -1
  99. package/dist/client/AssistantChat.js +28 -4
  100. package/dist/client/AssistantChat.js.map +1 -1
  101. package/dist/client/guided-questions.d.ts.map +1 -1
  102. package/dist/client/guided-questions.js +10 -6
  103. package/dist/client/guided-questions.js.map +1 -1
  104. package/dist/client/tool-display.d.ts.map +1 -1
  105. package/dist/client/tool-display.js +8 -0
  106. package/dist/client/tool-display.js.map +1 -1
  107. package/dist/collab/routes.d.ts +1 -1
  108. package/dist/file-upload/builder.d.ts.map +1 -1
  109. package/dist/file-upload/builder.js +6 -2
  110. package/dist/file-upload/builder.js.map +1 -1
  111. package/dist/notifications/channels.d.ts +10 -0
  112. package/dist/notifications/channels.d.ts.map +1 -1
  113. package/dist/notifications/channels.js +172 -20
  114. package/dist/notifications/channels.js.map +1 -1
  115. package/dist/notifications/index.d.ts +1 -1
  116. package/dist/notifications/index.d.ts.map +1 -1
  117. package/dist/notifications/index.js +1 -1
  118. package/dist/notifications/index.js.map +1 -1
  119. package/dist/notifications/registry.d.ts +5 -0
  120. package/dist/notifications/registry.d.ts.map +1 -1
  121. package/dist/notifications/registry.js +4 -1
  122. package/dist/notifications/registry.js.map +1 -1
  123. package/dist/progress/routes.d.ts +1 -1
  124. package/dist/resources/handlers.d.ts +1 -1
  125. package/dist/server/analytics.d.ts +16 -0
  126. package/dist/server/analytics.d.ts.map +1 -1
  127. package/dist/server/analytics.js +30 -7
  128. package/dist/server/analytics.js.map +1 -1
  129. package/dist/server/ssr-handler.d.ts.map +1 -1
  130. package/dist/server/ssr-handler.js +28 -12
  131. package/dist/server/ssr-handler.js.map +1 -1
  132. package/dist/server/transcribe-voice.d.ts +1 -1
  133. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +2 -0
  134. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +2 -0
  135. package/package.json +1 -1
  136. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +2 -0
  137. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +2 -0
@@ -1,5 +1,7 @@
1
1
  import { useSendToAgentChat, useT } from "@agent-native/core/client";
2
+ import type { CreateInlineDatabaseResponse } from "@shared/api";
2
3
  import { collapseExactRepeatedNfm, docToNfm } from "@shared/nfm";
4
+ import { serializeRegistryBlockToMdx } from "@shared/nfm-registry";
3
5
  import {
4
6
  IconTypography,
5
7
  IconH1,
@@ -33,7 +35,7 @@ import {
33
35
  PopoverContent,
34
36
  PopoverTrigger,
35
37
  } from "@/components/ui/popover";
36
- import { useCreateContentDatabase } from "@/hooks/use-content-database";
38
+ import { useCreateInlineContentDatabase } from "@/hooks/use-content-database";
37
39
  import { useCreatePage } from "@/hooks/use-create-page";
38
40
  import { cn } from "@/lib/utils";
39
41
  import { localContentComponents } from "@/local-components";
@@ -146,6 +148,36 @@ export function parseSlashCommandQuery(textBeforeCursor: string) {
146
148
  return textBeforeCursor.match(/^\s*\/([a-zA-Z0-9]*)$/)?.[1] ?? null;
147
149
  }
148
150
 
151
+ export function inlineDatabaseBlockContent(
152
+ block: CreateInlineDatabaseResponse["block"],
153
+ ) {
154
+ return {
155
+ type: "registryBlock",
156
+ attrs: {
157
+ blockType: "inline-database",
158
+ blockId: block.ownerBlockId,
159
+ title: null,
160
+ summary: null,
161
+ __raw: serializeRegistryBlockToMdx("inline-database", {
162
+ id: block.ownerBlockId,
163
+ data: block,
164
+ }),
165
+ },
166
+ };
167
+ }
168
+
169
+ export function insertInlineDatabaseBlock(
170
+ editor: Editor,
171
+ block: CreateInlineDatabaseResponse["block"],
172
+ position?: number | { from: number; to: number } | null,
173
+ ) {
174
+ const content = inlineDatabaseBlockContent(block);
175
+ const chain = editor.chain().focus();
176
+ return position != null
177
+ ? chain.insertContentAt(position, content).run()
178
+ : chain.insertContent(content).run();
179
+ }
180
+
149
181
  const commands: CommandTemplate[] = [
150
182
  {
151
183
  titleKey: "editor.slash.text",
@@ -407,7 +439,9 @@ export function SlashCommandMenu({
407
439
  const { send } = useSendToAgentChat();
408
440
  const navigate = useNavigate();
409
441
  const createPage = useCreatePage({ navigate: false, awaitPersist: true });
410
- const createDatabase = useCreateContentDatabase(documentId ?? null);
442
+ const createInlineDatabase = useCreateInlineContentDatabase(
443
+ documentId ?? null,
444
+ );
411
445
 
412
446
  const [isOpen, setIsOpen] = useState(false);
413
447
  const [isTurnInto, setIsTurnInto] = useState(false);
@@ -609,18 +643,36 @@ export function SlashCommandMenu({
609
643
  title: t("editor.slash.database"),
610
644
  description: t("editor.slash.databaseDescription"),
611
645
  icon: IconDatabase,
612
- action: async () => {
646
+ preserveSlashRange: true,
647
+ action: async (editor, { slashRange }) => {
613
648
  if (!documentId) {
614
649
  toast.error(t("editor.noDocumentSelected"));
615
650
  return;
616
651
  }
652
+ if (slashRange) {
653
+ editor.chain().focus().deleteRange(slashRange).run();
654
+ }
617
655
  const toastId = toast.loading(t("editor.creatingDatabase"));
618
656
  try {
619
- const result = await createDatabase.mutateAsync({
620
- parentId: documentId,
657
+ const result = await createInlineDatabase.mutateAsync({
658
+ hostDocumentId: documentId,
621
659
  title: t("editor.untitledDatabase"),
622
660
  });
623
- navigate(`/page/${result.database.documentId}`, { flushSync: true });
661
+ const inserted = insertInlineDatabaseBlock(editor, result.block);
662
+ if (!inserted) throw new Error(t("empty.genericError"));
663
+ await waitForEditorUpdateFrame();
664
+ const content = collapseExactRepeatedNfm(
665
+ docToNfm(editor.getJSON() as any),
666
+ {
667
+ requiredText: result.block.ownerBlockId,
668
+ },
669
+ );
670
+ if (onDraftPersisted) {
671
+ const persisted = await onDraftPersisted(content);
672
+ if (!persisted) throw new Error(t("empty.genericError"));
673
+ } else {
674
+ await onDraftCommitted?.();
675
+ }
624
676
  toast.success(t("editor.databaseCreated"), { id: toastId });
625
677
  } catch (error) {
626
678
  toast.error(t("editor.failedToCreateDatabase"), {
@@ -27,10 +27,14 @@ import {
27
27
  } from "@dnd-kit/sortable";
28
28
  import type { Document, DocumentTreeNode } from "@shared/api";
29
29
  import {
30
+ IconDatabase,
31
+ IconFileText,
30
32
  IconPlus,
33
+ IconRestore,
31
34
  IconSearch,
32
35
  IconStar,
33
36
  IconSettings,
37
+ IconTrashX,
34
38
  IconLayoutSidebarLeftCollapse,
35
39
  IconLayoutSidebarLeftExpand,
36
40
  IconFolderOpen,
@@ -42,12 +46,34 @@ import { useLocation, useNavigate } from "react-router";
42
46
  import { toast } from "sonner";
43
47
 
44
48
  import { ThemeToggle } from "@/components/ThemeToggle";
49
+ import {
50
+ AlertDialog,
51
+ AlertDialogAction,
52
+ AlertDialogCancel,
53
+ AlertDialogContent,
54
+ AlertDialogDescription,
55
+ AlertDialogFooter,
56
+ AlertDialogHeader,
57
+ AlertDialogTitle,
58
+ AlertDialogTrigger,
59
+ } from "@/components/ui/alert-dialog";
60
+ import {
61
+ DropdownMenu,
62
+ DropdownMenuContent,
63
+ DropdownMenuItem,
64
+ DropdownMenuTrigger,
65
+ } from "@/components/ui/dropdown-menu";
45
66
  import { ScrollArea } from "@/components/ui/scroll-area";
46
67
  import {
47
68
  Tooltip,
48
69
  TooltipContent,
49
70
  TooltipTrigger,
50
71
  } from "@/components/ui/tooltip";
72
+ import {
73
+ useCreateContentDatabase,
74
+ useRestoreContentDatabase,
75
+ useTrashedContentDatabases,
76
+ } from "@/hooks/use-content-database";
51
77
  import {
52
78
  useDocuments,
53
79
  useCreateDocument,
@@ -122,7 +148,8 @@ type SidebarSectionId =
122
148
  | "local-files"
123
149
  | "shared-copies"
124
150
  | "private"
125
- | "organization";
151
+ | "organization"
152
+ | "trash";
126
153
 
127
154
  export function DocumentSidebar({
128
155
  activeDocumentId,
@@ -138,8 +165,11 @@ export function DocumentSidebar({
138
165
  const t = useT();
139
166
  const { data: documents = [], isLoading } = useDocuments();
140
167
  const createDocument = useCreateDocument();
168
+ const createDatabase = useCreateContentDatabase(null);
141
169
  const deleteDocument = useDeleteDocument();
142
170
  const moveDocument = useMoveDocument();
171
+ const restoreContentDatabase = useRestoreContentDatabase();
172
+ const { data: trashedDatabases } = useTrashedContentDatabases();
143
173
  const { isCodeMode } = useCodeMode();
144
174
  const updateDocument = useUpdateDocument();
145
175
  const [searchQuery, setSearchQuery] = useState("");
@@ -156,6 +186,7 @@ export function DocumentSidebar({
156
186
  "shared-copies": false,
157
187
  private: false,
158
188
  organization: false,
189
+ trash: false,
159
190
  });
160
191
  const localFilesActive = location.pathname.startsWith("/local-files");
161
192
  const settingsActive = location.pathname.startsWith("/settings");
@@ -218,6 +249,7 @@ export function DocumentSidebar({
218
249
  const activeDocument = activeDocumentId
219
250
  ? documents.find((doc) => doc.id === activeDocumentId)
220
251
  : null;
252
+ const trashItems = trashedDatabases?.databases ?? [];
221
253
  const parentByDocumentId = useMemo(
222
254
  () => new Map(documents.map((doc) => [doc.id, doc.parentId])),
223
255
  [documents],
@@ -364,6 +396,25 @@ export function DocumentSidebar({
364
396
  ],
365
397
  );
366
398
 
399
+ const handleCreateDatabase = useCallback(
400
+ async (parentId?: string | null) => {
401
+ try {
402
+ const result = await createDatabase.mutateAsync({
403
+ parentId: parentId ?? null,
404
+ title: t("editor.untitledDatabase"),
405
+ });
406
+ navigateToDocument(result.database.documentId);
407
+ onNavigate?.();
408
+ } catch (err) {
409
+ toast.error(t("sidebar.failedCreateDatabase"), {
410
+ description:
411
+ err instanceof Error ? err.message : t("empty.genericError"),
412
+ });
413
+ }
414
+ },
415
+ [createDatabase, navigateToDocument, onNavigate, t],
416
+ );
417
+
367
418
  const handleDelete = useCallback(
368
419
  async (id: string) => {
369
420
  const deletedIds = collectDocumentSubtreeIds(documents, id);
@@ -524,6 +575,42 @@ export function DocumentSidebar({
524
575
  [updateDocument],
525
576
  );
526
577
 
578
+ const handleRestoreDatabase = useCallback(
579
+ async (databaseId: string) => {
580
+ try {
581
+ await restoreContentDatabase.mutateAsync({ databaseId });
582
+ toast.success(t("sidebar.databaseRestored"));
583
+ } catch (err) {
584
+ toast.error(t("sidebar.failedRestoreDatabase"), {
585
+ description:
586
+ err instanceof Error ? err.message : t("empty.genericError"),
587
+ });
588
+ }
589
+ },
590
+ [restoreContentDatabase, t],
591
+ );
592
+
593
+ const handlePermanentDeleteDatabase = useCallback(
594
+ async (documentId: string) => {
595
+ try {
596
+ await deleteDocument.mutateAsync({ id: documentId });
597
+ queryClient.invalidateQueries({
598
+ queryKey: ["action", "list-documents"],
599
+ });
600
+ queryClient.invalidateQueries({
601
+ queryKey: ["action", "list-trashed-content-databases"],
602
+ });
603
+ toast.success(t("sidebar.databasePermanentlyDeleted"));
604
+ } catch (err) {
605
+ toast.error(t("sidebar.failedPermanentDeleteDatabase"), {
606
+ description:
607
+ err instanceof Error ? err.message : t("empty.genericError"),
608
+ });
609
+ }
610
+ },
611
+ [deleteDocument, queryClient, t],
612
+ );
613
+
527
614
  const filteredDocuments = searchQuery
528
615
  ? documents.filter((d) =>
529
616
  d.title.toLowerCase().includes(searchQuery.toLowerCase()),
@@ -548,7 +635,8 @@ export function DocumentSidebar({
548
635
  navigateToDocument(id);
549
636
  onNavigate?.();
550
637
  }}
551
- onCreateChild={(parentId) => handleCreatePage(parentId)}
638
+ onCreateChildPage={(parentId) => handleCreatePage(parentId)}
639
+ onCreateChildDatabase={(parentId) => handleCreateDatabase(parentId)}
552
640
  onDelete={handleDelete}
553
641
  onToggleFavorite={handleToggleFavorite}
554
642
  />
@@ -556,14 +644,70 @@ export function DocumentSidebar({
556
644
  </SortableContext>
557
645
  );
558
646
 
559
- const renderNewPageButton = () => (
560
- <button
561
- className="flex w-full items-center gap-2 rounded-md px-3 py-[5px] text-sm text-muted-foreground hover:bg-accent/50 hover:text-foreground"
562
- onClick={() => handleCreatePage()}
563
- >
564
- <IconPlus size={14} className="shrink-0" />
565
- <span>{t("sidebar.newPage")}</span>
566
- </button>
647
+ const renderNewButton = () => (
648
+ <DropdownMenu>
649
+ <DropdownMenuTrigger asChild>
650
+ <button
651
+ type="button"
652
+ className="flex w-full items-center gap-2 rounded-md px-3 py-[5px] text-sm text-muted-foreground hover:bg-accent/50 hover:text-foreground"
653
+ disabled={createDocument.isPending || createDatabase.isPending}
654
+ >
655
+ <IconPlus size={14} className="shrink-0" />
656
+ <span>{t("sidebar.new")}</span>
657
+ </button>
658
+ </DropdownMenuTrigger>
659
+ <DropdownMenuContent align="start" className="w-44">
660
+ <DropdownMenuItem
661
+ disabled={createDocument.isPending}
662
+ onClick={() => void handleCreatePage()}
663
+ >
664
+ <IconFileText className="me-2 size-4" />
665
+ {t("sidebar.page")}
666
+ </DropdownMenuItem>
667
+ <DropdownMenuItem
668
+ disabled={createDatabase.isPending}
669
+ onClick={() => void handleCreateDatabase(null)}
670
+ >
671
+ <IconDatabase className="me-2 size-4" />
672
+ {t("sidebar.database")}
673
+ </DropdownMenuItem>
674
+ </DropdownMenuContent>
675
+ </DropdownMenu>
676
+ );
677
+
678
+ const renderCollapsedNewButton = () => (
679
+ <DropdownMenu>
680
+ <Tooltip>
681
+ <TooltipTrigger asChild>
682
+ <DropdownMenuTrigger asChild>
683
+ <button
684
+ type="button"
685
+ className="w-10 h-10 flex items-center justify-center rounded-lg hover:bg-accent text-muted-foreground hover:text-foreground"
686
+ disabled={createDocument.isPending || createDatabase.isPending}
687
+ >
688
+ <IconPlus size={16} />
689
+ </button>
690
+ </DropdownMenuTrigger>
691
+ </TooltipTrigger>
692
+ <TooltipContent>{t("sidebar.new")}</TooltipContent>
693
+ </Tooltip>
694
+ <DropdownMenuContent align="start" className="w-44">
695
+ <DropdownMenuItem
696
+ disabled={createDocument.isPending}
697
+ onClick={() => void handleCreatePage()}
698
+ >
699
+ <IconFileText className="me-2 size-4" />
700
+ {t("sidebar.page")}
701
+ </DropdownMenuItem>
702
+ <DropdownMenuItem
703
+ disabled={createDatabase.isPending}
704
+ onClick={() => void handleCreateDatabase(null)}
705
+ >
706
+ <IconDatabase className="me-2 size-4" />
707
+ {t("sidebar.database")}
708
+ </DropdownMenuItem>
709
+ </DropdownMenuContent>
710
+ </DropdownMenu>
567
711
  );
568
712
 
569
713
  const renderLocalFilesNavButton = () => (
@@ -686,6 +830,103 @@ export function DocumentSidebar({
686
830
  );
687
831
  };
688
832
 
833
+ const renderTrashSection = () => {
834
+ if (trashItems.length === 0) return null;
835
+ const collapsed = collapsedSections.trash;
836
+
837
+ return (
838
+ <div className="mt-3 border-t border-border/60 pt-2">
839
+ {renderSectionHeader("trash", t("sidebar.trash"))}
840
+ {!collapsed && (
841
+ <div className="px-1 py-1">
842
+ {trashItems.map((database) => {
843
+ const title = database.title || t("editor.untitledDatabase");
844
+ return (
845
+ <div
846
+ key={database.databaseId}
847
+ className="group flex min-w-0 items-center gap-1 rounded-md px-2 py-1.5 text-sm text-muted-foreground hover:bg-accent/50 hover:text-foreground"
848
+ >
849
+ <span className="min-w-0 flex-1 truncate">{title}</span>
850
+ <Tooltip>
851
+ <TooltipTrigger asChild>
852
+ <button
853
+ type="button"
854
+ aria-label={t("sidebar.restoreDatabaseNamed", {
855
+ title,
856
+ })}
857
+ className="flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-background hover:text-foreground disabled:opacity-50"
858
+ disabled={restoreContentDatabase.isPending}
859
+ onClick={() =>
860
+ void handleRestoreDatabase(database.databaseId)
861
+ }
862
+ >
863
+ <IconRestore size={14} />
864
+ </button>
865
+ </TooltipTrigger>
866
+ <TooltipContent>
867
+ {t("sidebar.restoreDatabase")}
868
+ </TooltipContent>
869
+ </Tooltip>
870
+ {database.canPermanentlyDelete && (
871
+ <AlertDialog>
872
+ <Tooltip>
873
+ <TooltipTrigger asChild>
874
+ <AlertDialogTrigger asChild>
875
+ <button
876
+ type="button"
877
+ aria-label={t(
878
+ "sidebar.deleteDatabaseNamedPermanently",
879
+ { title },
880
+ )}
881
+ className="flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-destructive/10 hover:text-destructive disabled:opacity-50"
882
+ disabled={deleteDocument.isPending}
883
+ >
884
+ <IconTrashX size={14} />
885
+ </button>
886
+ </AlertDialogTrigger>
887
+ </TooltipTrigger>
888
+ <TooltipContent>
889
+ {t("sidebar.deletePermanently")}
890
+ </TooltipContent>
891
+ </Tooltip>
892
+ <AlertDialogContent>
893
+ <AlertDialogHeader>
894
+ <AlertDialogTitle>
895
+ {t("sidebar.deleteDatabasePermanentlyQuestion")}
896
+ </AlertDialogTitle>
897
+ <AlertDialogDescription>
898
+ {t("sidebar.deleteDatabasePermanentlyDescription", {
899
+ title,
900
+ })}
901
+ </AlertDialogDescription>
902
+ </AlertDialogHeader>
903
+ <AlertDialogFooter>
904
+ <AlertDialogCancel>
905
+ {t("comments.cancel")}
906
+ </AlertDialogCancel>
907
+ <AlertDialogAction
908
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
909
+ onClick={() =>
910
+ void handlePermanentDeleteDatabase(
911
+ database.documentId,
912
+ )
913
+ }
914
+ >
915
+ {t("sidebar.deletePermanently")}
916
+ </AlertDialogAction>
917
+ </AlertDialogFooter>
918
+ </AlertDialogContent>
919
+ </AlertDialog>
920
+ )}
921
+ </div>
922
+ );
923
+ })}
924
+ </div>
925
+ )}
926
+ </div>
927
+ );
928
+ };
929
+
689
930
  if (collapsed) {
690
931
  return (
691
932
  <div className="flex h-full w-12 flex-col items-center gap-1 border-e border-border bg-muted/30 py-3 transition-[width] duration-200 ease-out">
@@ -700,17 +941,7 @@ export function DocumentSidebar({
700
941
  </TooltipTrigger>
701
942
  <TooltipContent>{t("sidebar.expand")}</TooltipContent>
702
943
  </Tooltip>
703
- <Tooltip>
704
- <TooltipTrigger asChild>
705
- <button
706
- className="w-10 h-10 flex items-center justify-center rounded-lg hover:bg-accent text-muted-foreground hover:text-foreground"
707
- onClick={() => handleCreatePage()}
708
- >
709
- <IconPlus size={16} />
710
- </button>
711
- </TooltipTrigger>
712
- <TooltipContent>{t("sidebar.newPage")}</TooltipContent>
713
- </Tooltip>
944
+ {renderCollapsedNewButton()}
714
945
  <Tooltip>
715
946
  <TooltipTrigger asChild>
716
947
  <button
@@ -860,7 +1091,7 @@ export function DocumentSidebar({
860
1091
  ))
861
1092
  )}
862
1093
  </div>
863
- {renderNewPageButton()}
1094
+ {renderNewButton()}
864
1095
  </>
865
1096
  ) : (
866
1097
  <>
@@ -911,7 +1142,7 @@ export function DocumentSidebar({
911
1142
  label: t("sidebar.localFiles"),
912
1143
  nodes: localFileTree,
913
1144
  emptyLabel: t("sidebar.noFilesYet"),
914
- footer: renderNewPageButton(),
1145
+ footer: renderNewButton(),
915
1146
  })}
916
1147
  {databaseTree.length > 0
917
1148
  ? renderTreeSection({
@@ -922,6 +1153,7 @@ export function DocumentSidebar({
922
1153
  className: "mt-3",
923
1154
  })
924
1155
  : null}
1156
+ {renderTrashSection()}
925
1157
  </>
926
1158
  ) : (
927
1159
  <>
@@ -939,7 +1171,7 @@ export function DocumentSidebar({
939
1171
  label: t("sidebar.private"),
940
1172
  nodes: privateTree,
941
1173
  emptyLabel: t("sidebar.noPrivatePagesYet"),
942
- footer: renderNewPageButton(),
1174
+ footer: renderNewButton(),
943
1175
  })}
944
1176
 
945
1177
  {!isLoading &&
@@ -950,6 +1182,7 @@ export function DocumentSidebar({
950
1182
  emptyLabel: t("sidebar.noOrganizationPagesYet"),
951
1183
  className: "mt-3",
952
1184
  })}
1185
+ {renderTrashSection()}
953
1186
  </>
954
1187
  )}
955
1188
  </>
@@ -50,7 +50,8 @@ interface DocumentTreeItemProps {
50
50
  expandedIds: Set<string>;
51
51
  onToggleExpanded: (id: string) => void;
52
52
  onSelect: (id: string) => void;
53
- onCreateChild: (parentId: string) => void;
53
+ onCreateChildPage: (parentId: string) => void;
54
+ onCreateChildDatabase: (parentId: string) => void;
54
55
  onDelete: (id: string) => void;
55
56
  onToggleFavorite: (id: string, isFavorite: boolean) => void;
56
57
  }
@@ -94,7 +95,8 @@ export function DocumentTreeItem({
94
95
  expandedIds,
95
96
  onToggleExpanded,
96
97
  onSelect,
97
- onCreateChild,
98
+ onCreateChildPage,
99
+ onCreateChildDatabase,
98
100
  onDelete,
99
101
  onToggleFavorite,
100
102
  }: DocumentTreeItemProps) {
@@ -262,22 +264,45 @@ export function DocumentTreeItem({
262
264
  )}
263
265
 
264
266
  {canCreateChild && (
265
- <Tooltip>
266
- <TooltipTrigger asChild>
267
- <button
268
- type="button"
269
- className="flex h-7 w-7 items-center justify-center rounded text-current hover:bg-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
270
- aria-label={`Add sub-page to ${node.title || "Untitled"}`}
267
+ <DropdownMenu>
268
+ <Tooltip>
269
+ <TooltipTrigger asChild>
270
+ <DropdownMenuTrigger asChild>
271
+ <button
272
+ type="button"
273
+ className="flex h-7 w-7 items-center justify-center rounded text-current hover:bg-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
274
+ aria-label={t("sidebar.addChildTo", {
275
+ title: node.title || t("sidebar.untitled"),
276
+ })}
277
+ onClick={(e) => e.stopPropagation()}
278
+ >
279
+ <IconPlus size={14} />
280
+ </button>
281
+ </DropdownMenuTrigger>
282
+ </TooltipTrigger>
283
+ <TooltipContent>{t("sidebar.addChild")}</TooltipContent>
284
+ </Tooltip>
285
+ <DropdownMenuContent align="start" className="w-44">
286
+ <DropdownMenuItem
271
287
  onClick={(e) => {
272
288
  e.stopPropagation();
273
- onCreateChild(node.id);
289
+ onCreateChildPage(node.id);
274
290
  }}
275
291
  >
276
- <IconPlus size={14} />
277
- </button>
278
- </TooltipTrigger>
279
- <TooltipContent>{t("sidebar.addSubPage")}</TooltipContent>
280
- </Tooltip>
292
+ <IconFileText className="me-2 size-4" />
293
+ {t("sidebar.page")}
294
+ </DropdownMenuItem>
295
+ <DropdownMenuItem
296
+ onClick={(e) => {
297
+ e.stopPropagation();
298
+ onCreateChildDatabase(node.id);
299
+ }}
300
+ >
301
+ <IconDatabase className="me-2 size-4" />
302
+ {t("sidebar.database")}
303
+ </DropdownMenuItem>
304
+ </DropdownMenuContent>
305
+ </DropdownMenu>
281
306
  )}
282
307
  </div>
283
308
  </div>
@@ -297,7 +322,8 @@ export function DocumentTreeItem({
297
322
  expandedIds={expandedIds}
298
323
  onToggleExpanded={onToggleExpanded}
299
324
  onSelect={onSelect}
300
- onCreateChild={onCreateChild}
325
+ onCreateChildPage={onCreateChildPage}
326
+ onCreateChildDatabase={onCreateChildDatabase}
301
327
  onDelete={onDelete}
302
328
  onToggleFavorite={onToggleFavorite}
303
329
  />
@@ -98,11 +98,11 @@ const messages = {
98
98
  reorderField: "重新排序 {{name}}",
99
99
  title: "標題",
100
100
  toggleField: "切換 {{name}}",
101
- creatingDatabase: "正在建立資料庫...",
102
- databaseCreated: "資料庫已建立",
101
+ creatingDatabase: "正在建立內嵌資料庫...",
102
+ databaseCreated: "內嵌資料庫已建立",
103
103
  describeWhatToGenerate: "描述要生成什麼...",
104
104
  enterToSubmit: "輸入提交",
105
- failedToCreateDatabase: "建立資料庫失敗",
105
+ failedToCreateDatabase: "建立內嵌資料庫失敗",
106
106
  failedToCreatePage: "建立頁面失敗",
107
107
  generateWithAi: "用人工智慧生成",
108
108
  dropMedia: "拖放媒體",
@@ -386,7 +386,7 @@ const messages = {
386
386
  codeBlockDescription: "程式碼片段",
387
387
  collapsibleBlockDescription: "可折疊塊",
388
388
  database: "資料庫",
389
- databaseDescription: "建立子資料庫頁面",
389
+ databaseDescription: "此頁面中的內嵌資料庫",
390
390
  divider: "分頻器",
391
391
  dividerDescription: "水平尺",
392
392
  generate: "生成",
@@ -939,7 +939,9 @@ const messages = {
939
939
  },
940
940
  sidebar: {
941
941
  cannotReorderPages: "無法重新排序頁面",
942
- addSubPage: "Agregar subpágina",
942
+ addChild: "新增子項目",
943
+ addChildTo: "新增子項目至 {{title}}",
944
+ addSubPage: "新增子頁面",
943
945
  collapse: "折疊側邊欄",
944
946
  completeStepsAboveFirst: "請先完成上面的步驟",
945
947
  connectWorkspace: "連線工作區",
@@ -949,13 +951,25 @@ const messages = {
949
951
  copy: "複製",
950
952
  disconnectWorkspace: "斷開工作區連線",
951
953
  expand: "展開側邊欄",
954
+ database: "資料庫",
955
+ databasePermanentlyDeleted: "資料庫已永久刪除",
956
+ databaseRestored: "資料庫已還原",
957
+ deleteDatabaseNamedPermanently: "永久刪除 {{title}}",
958
+ deleteDatabasePermanentlyDescription:
959
+ "這會永久刪除「{{title}}」及其頁面。此操作無法復原。",
960
+ deleteDatabasePermanentlyQuestion: "永久刪除資料庫?",
961
+ deletePermanently: "永久刪除",
962
+ failedCreateDatabase: "建立資料庫失敗",
952
963
  failedCreatePage: "建立頁面失敗",
953
964
  failedDeletePage: "刪除頁面失敗",
965
+ failedPermanentDeleteDatabase: "永久刪除資料庫失敗",
954
966
  failedMovePage: "行動頁面失敗",
967
+ failedRestoreDatabase: "還原資料庫失敗",
955
968
  deletePageDescription:
956
- "{{title}}” y todas sus subpáginas se eliminarán permanentemente. Esto no se puede deshacer.",
957
- deletePageQuestion: "¿Eliminar página?",
969
+ "{{title}}」及其所有子頁面將被永久刪除。此操作無法復原。",
970
+ deletePageQuestion: "刪除頁面?",
958
971
  localFiles: "本機檔案",
972
+ new: "新增",
959
973
  newPage: "新頁面",
960
974
  nextStep: "下一步",
961
975
  notionConfigureOAuthAuthorize: "設定 OAuth,然後授權你的工作區。",
@@ -991,14 +1005,18 @@ const messages = {
991
1005
  noSharedCopiesYet: "還沒有共用副本",
992
1006
  oneAffectedPageReadOnly: "受影響的頁面之一是唯讀的。",
993
1007
  organization: "組織",
1008
+ page: "頁面",
994
1009
  private: "私人",
995
1010
  results: "結果",
996
1011
  search: "搜尋",
997
1012
  searchPages: "搜尋頁面...",
998
1013
  refreshConnection: "重新整理連線",
1014
+ restoreDatabase: "還原",
1015
+ restoreDatabaseNamed: "還原 {{title}}",
999
1016
  saving: "正在儲存...",
1000
1017
  sharedCopies: "共用副本",
1001
1018
  synced: "已同步",
1019
+ trash: "垃圾桶",
1002
1020
  favorites: "釘選",
1003
1021
  untitled: "無標題",
1004
1022
  },