@agent-native/core-corpus 0.201.0-nightly-20261005153421 → 0.201.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +14 -4
- package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/analytics/CHANGELOG.md +6 -0
- package/corpus/templates/analytics/README.md +2 -2
- package/corpus/templates/analytics/changelog/2026-10-02-events-with-a-very-long-name-app-or-page-path-no-longer-caus.md +6 -0
- package/corpus/templates/analytics/package.json +1 -0
- package/corpus/templates/analytics/server/handlers/first-party-analytics.ts +9 -19
- package/corpus/templates/analytics/server/handlers/session-replay.ts +34 -47
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +9 -6
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +57 -36
- package/corpus/templates/analytics/server/lib/indexed-text.ts +53 -0
- package/corpus/templates/analytics/server/lib/request-errors.ts +73 -0
- package/corpus/templates/analytics/server/lib/session-event-index.ts +6 -16
- package/corpus/templates/analytics/server/lib/session-replay.ts +12 -2
- package/corpus/templates/analytics/server/plugins/auth.ts +2 -1
- package/corpus/templates/analytics/server/plugins/otel.ts +6 -0
- package/corpus/templates/assets/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/assets/CHANGELOG.md +7 -0
- package/corpus/templates/assets/README.md +2 -2
- package/corpus/templates/assets/package.json +1 -0
- package/corpus/templates/assets/server/plugins/auth.ts +2 -1
- package/corpus/templates/assets/server/plugins/otel.ts +6 -0
- package/corpus/templates/brain/.agents/skills/brain/RUNBOOK.md +2 -1
- package/corpus/templates/brain/.agents/skills/brain/SKILL.md +5 -4
- package/corpus/templates/brain/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/brain/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/brain/CHANGELOG.md +6 -0
- package/corpus/templates/brain/README.md +12 -8
- package/corpus/templates/brain/changelog/2026-10-05-private-slack-channels-are-searchable-org-wide.md +6 -0
- package/corpus/templates/brain/package.json +1 -0
- package/corpus/templates/brain/server/lib/audiences.ts +1 -98
- package/corpus/templates/brain/server/lib/connectors.ts +2 -166
- package/corpus/templates/brain/server/plugins/otel.ts +6 -0
- package/corpus/templates/calendar/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/calendar/CHANGELOG.md +7 -0
- package/corpus/templates/calendar/README.md +2 -2
- package/corpus/templates/calendar/package.json +1 -0
- package/corpus/templates/calendar/server/plugins/auth.ts +2 -1
- package/corpus/templates/calendar/server/plugins/otel.ts +6 -0
- package/corpus/templates/chat/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/chat/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/chat/README.md +2 -2
- package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +9 -0
- package/corpus/templates/clips/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/clips/CHANGELOG.md +24 -0
- package/corpus/templates/clips/README.md +2 -2
- package/corpus/templates/clips/actions/lib/audio-chunk-list.ts +23 -0
- package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +187 -292
- package/corpus/templates/clips/actions/lib/chunked-transcription.ts +151 -0
- package/corpus/templates/clips/actions/lib/recording-audio-source.ts +191 -0
- package/corpus/templates/clips/actions/request-transcript.ts +81 -135
- package/corpus/templates/clips/actions/search-recordings.ts +19 -5
- package/corpus/templates/clips/app/components/clips-command-menu.tsx +5 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +3 -1
- package/corpus/templates/clips/app/components/library/search-bar.tsx +5 -0
- package/corpus/templates/clips/app/hooks/use-library.ts +1 -0
- package/corpus/templates/clips/app/lib/local-recording-upload.ts +13 -6
- package/corpus/templates/clips/app/lib/recording-section.ts +1 -1
- package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +7 -5
- package/corpus/templates/clips/app/routes/record.tsx +16 -6
- package/corpus/templates/clips/changelog/2026-10-02-long-uploads-an-hour-or-more-now-transcribe-reliably-instead.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-05-confirming-discard-recording-now-returns-you-to-your-library.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-05-discarding-a-recording-now-removes-its-failed-upload-card-fr.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-05-discarding-a-recovered-recording-no-longer-warns-that-it-s-o.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-05-search-results-identify-recordings-in-trash-and-open-them-wi.md +6 -0
- package/corpus/templates/clips/package.json +1 -0
- package/corpus/templates/clips/server/lib/builder-cdn.ts +5 -0
- package/corpus/templates/clips/server/lib/builder-media-compression.ts +2 -2
- package/corpus/templates/clips/server/plugins/auth.ts +2 -1
- package/corpus/templates/clips/server/plugins/otel.ts +6 -0
- package/corpus/templates/clips/shared/transcript-failure.ts +5 -0
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +8 -2
- package/corpus/templates/content/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/content/CHANGELOG.md +41 -0
- package/corpus/templates/content/README.md +2 -2
- package/corpus/templates/content/actions/_database-navigation.ts +523 -344
- package/corpus/templates/content/actions/_database-row-mutation.ts +44 -10
- package/corpus/templates/content/actions/_database-setup-discovery.ts +9 -3
- package/corpus/templates/content/actions/_database-utils.ts +299 -271
- package/corpus/templates/content/actions/_property-utils.ts +293 -190
- package/corpus/templates/content/actions/_relation-values.ts +3 -1
- package/corpus/templates/content/actions/get-content-database.ts +1 -0
- package/corpus/templates/content/actions/query-content-database-items.ts +32 -5
- package/corpus/templates/content/actions/set-document-property.ts +17 -11
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +21 -6
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +127 -43
- package/corpus/templates/content/app/components/editor/DocumentEditorSkeleton.tsx +159 -50
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +12 -28
- package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +6 -1
- package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +139 -70
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +16 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +180 -75
- package/corpus/templates/content/app/components/editor/database/DatabaseViewSkeleton.tsx +76 -0
- package/corpus/templates/content/app/components/editor/database/navigation-state.ts +60 -2
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +41 -42
- package/corpus/templates/content/app/components/editor/document-editor-layout.ts +18 -0
- package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +11 -3
- package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +15 -7
- package/corpus/templates/content/app/components/editor/suggestions/model.ts +13 -0
- package/corpus/templates/content/app/components/editor/suggestions/read-only-blocks.ts +136 -0
- package/corpus/templates/content/app/components/editor/table-view.ts +73 -0
- package/corpus/templates/content/app/components/layout/ContentStartupShell.tsx +1 -1
- package/corpus/templates/content/app/components/layout/Layout.tsx +5 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +57 -16
- package/corpus/templates/content/app/components/sidebar/MovePageDialog.tsx +4 -4
- package/corpus/templates/content/app/components/sidebar/SidebarNavigationRow.tsx +34 -10
- package/corpus/templates/content/app/global.css +16 -8
- package/corpus/templates/content/app/hooks/content-action-refresh.ts +14 -13
- package/corpus/templates/content/app/hooks/use-content-database.ts +72 -9
- package/corpus/templates/content/app/hooks/use-content-personal-navigation.ts +7 -3
- package/corpus/templates/content/app/hooks/use-documents.ts +27 -0
- package/corpus/templates/content/app/i18n-data.ts +3 -2
- package/corpus/templates/content/app/lib/content-landing.ts +3 -31
- package/corpus/templates/content/app/lib/files-navigation.ts +182 -10
- package/corpus/templates/content/app/lib/files-root-hint.ts +20 -32
- package/corpus/templates/content/app/lib/page-startup-hints.ts +108 -0
- package/corpus/templates/content/app/lib/private-link-redirect.ts +25 -0
- package/corpus/templates/content/app/lib/sidebar-layout-hint.ts +34 -1
- package/corpus/templates/content/app/lib/startup-timing.ts +2 -0
- package/corpus/templates/content/app/root.tsx +3 -1
- package/corpus/templates/content/app/routes/_app.home.tsx +10 -19
- package/corpus/templates/content/app/routes/p.$id.tsx +23 -5
- package/corpus/templates/content/changelog/2026-10-02-a-page-link-you-cant-open-now-says-so-instead-of-opening-an.md +6 -0
- package/corpus/templates/content/changelog/2026-10-02-after-a-refresh-the-files-sidebar-shows-the-open-page-s-fold.md +6 -0
- package/corpus/templates/content/changelog/2026-10-02-large-collections-open-faster.md +6 -0
- package/corpus/templates/content/changelog/2026-10-02-pages-stay-inside-the-window-at-every-width-long-inline-code.md +6 -0
- package/corpus/templates/content/changelog/2026-10-02-pages-with-open-comments-and-collections-with-sorted-or-filt.md +6 -0
- package/corpus/templates/content/changelog/2026-10-02-suggest-edits-works-on-pages-written-by-agents-including-pag.md +6 -0
- package/corpus/templates/content/changelog/2026-10-02-while-you-suggest-edits-tables-and-other-blocks-that-suggest.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-collection-view-context-now-survives-quick-page-changes-when.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-collection-views-keep-their-saved-rows-accurate-during-refre.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-exact-saved-view-links-keep-collection-retry-available.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-expanding-a-files-folder-now-shares-one-navigation-read-acro.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-files-navigation-retries-temporary-failures-consistently.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-shared-page-owners-keep-edit-and-manage-controls-in-files-an.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-tagging-an-ai-in-a-comment-works-again-instead-of-failing-ri.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-the-home-loading-shell-keeps-the-layout-of-your-last-opened-.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-typing-and-a-space-or-inserting-a-toggle-or-callout-no-longe.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.workspace.session-restore.md +4 -1
- package/corpus/templates/content/e2e/playwright.config.ts +1 -1
- package/corpus/templates/content/package.json +2 -1
- package/corpus/templates/content/scripts/seed-perf-database.mjs +600 -0
- package/corpus/templates/content/scripts/trace-startup.mjs +25 -3
- package/corpus/templates/content/server/db/index.ts +1 -1
- package/corpus/templates/content/server/lib/suggested-edits.ts +23 -26
- package/corpus/templates/content/server/plugins/auth.ts +2 -1
- package/corpus/templates/content/server/plugins/otel.ts +6 -0
- package/corpus/templates/content/shared/api.ts +17 -0
- package/corpus/templates/content/shared/suggestion-diff.ts +124 -38
- package/corpus/templates/content/shared/suggestion-formatting.ts +134 -3
- package/corpus/templates/crm/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/crm/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/crm/package.json +1 -0
- package/corpus/templates/crm/server/plugins/otel.ts +6 -0
- package/corpus/templates/design/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/design/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/design/CHANGELOG.md +41 -0
- package/corpus/templates/design/README.md +2 -2
- package/corpus/templates/design/actions/list-designs.ts +11 -4
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +110 -32
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +28 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +1 -0
- package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +79 -0
- package/corpus/templates/design/app/components/layout/Header.tsx +2 -4
- package/corpus/templates/design/app/components/ui/tabs.tsx +59 -1
- package/corpus/templates/design/app/global.css +1 -34
- package/corpus/templates/design/app/i18n/en-US.ts +4 -0
- package/corpus/templates/design/app/lib/design-filter.ts +4 -2
- package/corpus/templates/design/app/pages/Index.tsx +91 -59
- package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +58 -17
- package/corpus/templates/design/changelog/2026-10-01-filter-designs-by-owner.md +6 -0
- package/corpus/templates/design/changelog/2026-10-03-layers-dropped-into-visible-canvas-space-beyond-a-screen-now.md +6 -0
- package/corpus/templates/design/changelog/2026-10-03-visible-screen-overflow-no-longer-routes-drops-to-the-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-10-05-the-recent-tab-shows-loading-placeholders-until-your-designs.md +6 -0
- package/corpus/templates/design/package.json +1 -0
- package/corpus/templates/design/server/plugins/auth.ts +2 -1
- package/corpus/templates/design/server/plugins/otel.ts +6 -0
- package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/dispatch/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/dispatch/README.md +2 -2
- package/corpus/templates/dispatch/package.json +1 -0
- package/corpus/templates/dispatch/server/plugins/otel.ts +6 -0
- package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +2 -1
- package/corpus/templates/factory/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/factory/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/factory/package.json +1 -0
- package/corpus/templates/factory/server/plugins/otel.ts +6 -0
- package/corpus/templates/forms/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/forms/README.md +2 -2
- package/corpus/templates/forms/package.json +1 -0
- package/corpus/templates/forms/server/plugins/auth.ts +2 -1
- package/corpus/templates/forms/server/plugins/otel.ts +6 -0
- package/corpus/templates/mail/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/mail/CHANGELOG.md +6 -0
- package/corpus/templates/mail/README.md +2 -2
- package/corpus/templates/mail/package.json +1 -0
- package/corpus/templates/mail/server/plugins/auth.ts +2 -1
- package/corpus/templates/mail/server/plugins/otel.ts +6 -0
- package/corpus/templates/plan/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/plan/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/plan/CHANGELOG.md +1 -0
- package/corpus/templates/plan/README.md +2 -2
- package/corpus/templates/plan/package.json +1 -0
- package/corpus/templates/plan/server/plugins/auth.ts +2 -1
- package/corpus/templates/plan/server/plugins/otel.ts +6 -0
- package/corpus/templates/slides/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/slides/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/slides/CHANGELOG.md +23 -0
- package/corpus/templates/slides/README.md +2 -2
- package/corpus/templates/slides/actions/list-decks.ts +11 -4
- package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +44 -36
- package/corpus/templates/slides/app/components/home/SlidesHomeLibrary.tsx +79 -0
- package/corpus/templates/slides/app/components/layout/Header.tsx +2 -15
- package/corpus/templates/slides/app/components/ui/tabs.tsx +59 -0
- package/corpus/templates/slides/app/global.css +3 -54
- package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
- package/corpus/templates/slides/app/lib/deck-filter.ts +5 -2
- package/corpus/templates/slides/app/pages/Index.tsx +102 -88
- package/corpus/templates/slides/changelog/2026-10-01-filter-decks-by-owner.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-02-hide-recent-until-decks-available.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-05-the-recent-tab-shows-loading-placeholders-until-your-decks-a.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/server/handlers/request-auth-context.ts +8 -0
- package/corpus/templates/slides/server/plugins/auth.ts +2 -1
- package/corpus/templates/slides/server/plugins/otel.ts +6 -0
- package/corpus/templates/tasks/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/tasks/.agents/skills/storing-data/SKILL.md +12 -7
- package/package.json +1 -1
|
@@ -1,11 +1,35 @@
|
|
|
1
|
+
import { callActionWithRetry } from "@agent-native/core/client/hooks";
|
|
1
2
|
import type {
|
|
2
|
-
|
|
3
|
+
ContentDatabaseNavigationPageResponse,
|
|
3
4
|
ContentDatabasePersonalViewOverrides,
|
|
5
|
+
ContentDatabaseUnavailableResponse,
|
|
4
6
|
ContentSidebarViewOrder,
|
|
5
7
|
} from "@shared/api";
|
|
8
|
+
import {
|
|
9
|
+
useQuery,
|
|
10
|
+
useQueryClient,
|
|
11
|
+
type QueryClient,
|
|
12
|
+
} from "@tanstack/react-query";
|
|
13
|
+
import { useEffect, useRef } from "react";
|
|
6
14
|
|
|
7
15
|
export const FILES_NAVIGATION_PAGE_SIZE = 20;
|
|
8
16
|
|
|
17
|
+
// Most expanded folders one read asks for; the server caps what it returns.
|
|
18
|
+
const MAX_EXPANDED_IDS_PER_READ = 100;
|
|
19
|
+
const pendingExpansionReads = new WeakMap<QueryClient, Set<string>>();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The folders the Files tree draws open, most important first: both caps on
|
|
23
|
+
* the folders one read opens keep the first ones, so the open page's
|
|
24
|
+
* ancestors come before the folders a person expanded.
|
|
25
|
+
*/
|
|
26
|
+
export function openFilesFolderIds(
|
|
27
|
+
activeAncestorIds: Iterable<string>,
|
|
28
|
+
expandedDocumentIds: Iterable<string>,
|
|
29
|
+
): ReadonlySet<string> {
|
|
30
|
+
return new Set([...activeAncestorIds, ...expandedDocumentIds]);
|
|
31
|
+
}
|
|
32
|
+
|
|
9
33
|
/** The Files tree order a person chose, as the paged navigation query reads it. */
|
|
10
34
|
export function filesNavigationOrder(
|
|
11
35
|
overrides: ContentDatabasePersonalViewOverrides | null | undefined,
|
|
@@ -19,22 +43,170 @@ export function filesNavigationOrder(
|
|
|
19
43
|
}
|
|
20
44
|
|
|
21
45
|
// Every reader of one Files branch must build the same params so the sidebar
|
|
22
|
-
// tree and breadcrumb menus share one cached request per page.
|
|
46
|
+
// tree and breadcrumb menus share one cached request per page. The server
|
|
47
|
+
// orders a branch by the person's saved view, so the order is not part of
|
|
48
|
+
// the key: a branch can be read before that view arrives.
|
|
23
49
|
export function filesNavigationPageParams(args: {
|
|
24
50
|
databaseId: string;
|
|
25
51
|
parentId: string | null;
|
|
26
|
-
sort: ContentDatabaseNavigationSort;
|
|
27
|
-
viewId?: string;
|
|
28
52
|
cursor?: string;
|
|
29
53
|
}) {
|
|
30
54
|
return {
|
|
31
55
|
databaseId: args.databaseId,
|
|
32
56
|
limit: FILES_NAVIGATION_PAGE_SIZE,
|
|
33
|
-
navigation: {
|
|
34
|
-
parentId: args.parentId,
|
|
35
|
-
sort: args.sort,
|
|
36
|
-
viewId: args.viewId,
|
|
37
|
-
cursor: args.cursor,
|
|
38
|
-
},
|
|
57
|
+
navigation: { parentId: args.parentId, cursor: args.cursor },
|
|
39
58
|
};
|
|
40
59
|
}
|
|
60
|
+
|
|
61
|
+
export type FilesNavigationPageParams = ReturnType<
|
|
62
|
+
typeof filesNavigationPageParams
|
|
63
|
+
>;
|
|
64
|
+
|
|
65
|
+
export function filesNavigationQueryKey(params: FilesNavigationPageParams) {
|
|
66
|
+
return ["action", "query-content-database-items", params] as const;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type FilesNavigationRead =
|
|
70
|
+
| ContentDatabaseNavigationPageResponse
|
|
71
|
+
| ContentDatabaseUnavailableResponse;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Reads one Files page and asks for any expanded folders that do not have a
|
|
75
|
+
* cached page yet, so opening a cached root can still load its new branches.
|
|
76
|
+
*/
|
|
77
|
+
export async function readFilesNavigationPage(
|
|
78
|
+
queryClient: QueryClient,
|
|
79
|
+
params: FilesNavigationPageParams,
|
|
80
|
+
expanded: Iterable<string>,
|
|
81
|
+
signal?: AbortSignal,
|
|
82
|
+
): Promise<FilesNavigationRead> {
|
|
83
|
+
const expand = [...expanded]
|
|
84
|
+
.filter((parentId) => {
|
|
85
|
+
const key = filesNavigationQueryKey(
|
|
86
|
+
filesNavigationPageParams({
|
|
87
|
+
databaseId: params.databaseId,
|
|
88
|
+
parentId,
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
return queryClient.getQueryData(key) === undefined;
|
|
92
|
+
})
|
|
93
|
+
.slice(0, MAX_EXPANDED_IDS_PER_READ);
|
|
94
|
+
const expansionKey =
|
|
95
|
+
expand.length > 0 ? JSON.stringify([params.databaseId, expand]) : null;
|
|
96
|
+
let pending: Set<string> | undefined;
|
|
97
|
+
if (expansionKey) {
|
|
98
|
+
pending = pendingExpansionReads.get(queryClient);
|
|
99
|
+
if (!pending) {
|
|
100
|
+
pending = new Set();
|
|
101
|
+
pendingExpansionReads.set(queryClient, pending);
|
|
102
|
+
}
|
|
103
|
+
pending.add(expansionKey);
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const response = await callActionWithRetry<FilesNavigationRead>(
|
|
107
|
+
"query-content-database-items",
|
|
108
|
+
expand.length
|
|
109
|
+
? { ...params, navigation: { ...params.navigation, expand } }
|
|
110
|
+
: params,
|
|
111
|
+
{ method: "GET", signal },
|
|
112
|
+
);
|
|
113
|
+
if ("available" in response) return response;
|
|
114
|
+
// A folder left out of `branches`, whether the cap dropped it
|
|
115
|
+
// (`branchesTruncated`) or it has no children to show, is not seeded as
|
|
116
|
+
// empty: it reads its own page when it draws.
|
|
117
|
+
const { branches, branchesTruncated, ...page } = response;
|
|
118
|
+
for (const [parentId, branch] of Object.entries(branches ?? {})) {
|
|
119
|
+
const key = filesNavigationQueryKey(
|
|
120
|
+
filesNavigationPageParams({
|
|
121
|
+
databaseId: params.databaseId,
|
|
122
|
+
parentId,
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
// A folder that already has its own page keeps it; that read is the
|
|
126
|
+
// one its later changes refresh.
|
|
127
|
+
if (queryClient.getQueryData(key) === undefined) {
|
|
128
|
+
queryClient.setQueryData(key, branch);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return page;
|
|
132
|
+
} finally {
|
|
133
|
+
if (expansionKey && pending) pending.delete(expansionKey);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isExpansionReadPending(
|
|
138
|
+
queryClient: QueryClient,
|
|
139
|
+
databaseId: string,
|
|
140
|
+
expanded: Iterable<string>,
|
|
141
|
+
) {
|
|
142
|
+
const expand = [...expanded]
|
|
143
|
+
.filter((parentId) => {
|
|
144
|
+
const key = filesNavigationQueryKey(
|
|
145
|
+
filesNavigationPageParams({ databaseId, parentId }),
|
|
146
|
+
);
|
|
147
|
+
return queryClient.getQueryData(key) === undefined;
|
|
148
|
+
})
|
|
149
|
+
.slice(0, MAX_EXPANDED_IDS_PER_READ);
|
|
150
|
+
if (expand.length === 0) return false;
|
|
151
|
+
return (
|
|
152
|
+
pendingExpansionReads
|
|
153
|
+
.get(queryClient)
|
|
154
|
+
?.has(JSON.stringify([databaseId, expand])) ?? false
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function useFilesNavigationPage(
|
|
159
|
+
params: FilesNavigationPageParams,
|
|
160
|
+
expanded: ReadonlySet<string>,
|
|
161
|
+
) {
|
|
162
|
+
const queryClient = useQueryClient();
|
|
163
|
+
const expansionKey = JSON.stringify([
|
|
164
|
+
params.databaseId,
|
|
165
|
+
params.navigation.parentId,
|
|
166
|
+
params.navigation.cursor ?? null,
|
|
167
|
+
[...expanded],
|
|
168
|
+
]);
|
|
169
|
+
const rootReadExpansion = useRef<string | null>(null);
|
|
170
|
+
const query = useQuery({
|
|
171
|
+
queryKey: filesNavigationQueryKey(params),
|
|
172
|
+
queryFn: ({ signal }) => {
|
|
173
|
+
rootReadExpansion.current = expansionKey;
|
|
174
|
+
return readFilesNavigationPage(queryClient, params, expanded, signal);
|
|
175
|
+
},
|
|
176
|
+
retry: false,
|
|
177
|
+
});
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (
|
|
180
|
+
!query.data ||
|
|
181
|
+
"available" in query.data ||
|
|
182
|
+
query.isFetching ||
|
|
183
|
+
isExpansionReadPending(queryClient, params.databaseId, expanded) ||
|
|
184
|
+
rootReadExpansion.current === expansionKey
|
|
185
|
+
) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const hasMissingBranch = [...expanded].some((parentId) => {
|
|
189
|
+
const key = filesNavigationQueryKey(
|
|
190
|
+
filesNavigationPageParams({
|
|
191
|
+
databaseId: params.databaseId,
|
|
192
|
+
parentId,
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
return queryClient.getQueryData(key) === undefined;
|
|
196
|
+
});
|
|
197
|
+
if (!hasMissingBranch) {
|
|
198
|
+
rootReadExpansion.current = expansionKey;
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
void query.refetch();
|
|
202
|
+
}, [
|
|
203
|
+
expanded,
|
|
204
|
+
expansionKey,
|
|
205
|
+
params,
|
|
206
|
+
query.data,
|
|
207
|
+
query.isFetching,
|
|
208
|
+
query.refetch,
|
|
209
|
+
queryClient,
|
|
210
|
+
]);
|
|
211
|
+
return query;
|
|
212
|
+
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { callAction } from "@agent-native/core/client/hooks";
|
|
2
|
-
import type { ContentDatabaseNavigationSort } from "@shared/api";
|
|
3
1
|
import type { QueryClient } from "@tanstack/react-query";
|
|
4
2
|
|
|
5
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
filesNavigationPageParams,
|
|
5
|
+
filesNavigationQueryKey,
|
|
6
|
+
readFilesNavigationPage,
|
|
7
|
+
} from "@/lib/files-navigation";
|
|
6
8
|
|
|
7
|
-
// The Files tree's root page is keyed by the space's Files database
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// The Files tree's root page is keyed by the space's Files database, which
|
|
10
|
+
// arrives from another read. The database this browser last used for the same
|
|
11
|
+
// person in the same active organization lets the root page start alongside
|
|
12
|
+
// that read; when it turns out different, the tree reads again with the
|
|
13
|
+
// confirmed one and the early read is unused.
|
|
12
14
|
const FILES_ROOT_HINT_STORAGE_KEY = "content-sidebar-files-root-v1";
|
|
13
15
|
|
|
14
16
|
export function filesRootHintScope(
|
|
@@ -19,11 +21,7 @@ export function filesRootHintScope(
|
|
|
19
21
|
return account ? JSON.stringify([account, orgId ?? null]) : null;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
export type PagedFilesRoot = {
|
|
23
|
-
databaseId: string;
|
|
24
|
-
sort: ContentDatabaseNavigationSort;
|
|
25
|
-
viewId?: string;
|
|
26
|
-
};
|
|
24
|
+
export type PagedFilesRoot = { databaseId: string };
|
|
27
25
|
|
|
28
26
|
export function readPagedFilesRootHint(scope: string): PagedFilesRoot | null {
|
|
29
27
|
let raw: string | null;
|
|
@@ -35,21 +33,11 @@ export function readPagedFilesRootHint(scope: string): PagedFilesRoot | null {
|
|
|
35
33
|
}
|
|
36
34
|
if (!raw) return null;
|
|
37
35
|
try {
|
|
38
|
-
const hint = JSON.parse(raw) as
|
|
39
|
-
|
|
40
|
-
};
|
|
41
|
-
if (
|
|
42
|
-
hint.scope !== scope ||
|
|
43
|
-
typeof hint.databaseId !== "string" ||
|
|
44
|
-
typeof hint.sort !== "string"
|
|
45
|
-
) {
|
|
36
|
+
const hint = JSON.parse(raw) as { scope?: unknown; databaseId?: unknown };
|
|
37
|
+
if (hint.scope !== scope || typeof hint.databaseId !== "string") {
|
|
46
38
|
return null;
|
|
47
39
|
}
|
|
48
|
-
return {
|
|
49
|
-
databaseId: hint.databaseId,
|
|
50
|
-
sort: hint.sort,
|
|
51
|
-
...(typeof hint.viewId === "string" ? { viewId: hint.viewId } : {}),
|
|
52
|
-
};
|
|
40
|
+
return { databaseId: hint.databaseId };
|
|
53
41
|
} catch {
|
|
54
42
|
// coercion-ok: a malformed hint is ignored and replaced by the next write.
|
|
55
43
|
return null;
|
|
@@ -67,17 +55,17 @@ export function rememberPagedFilesRoot(scope: string, root: PagedFilesRoot) {
|
|
|
67
55
|
}
|
|
68
56
|
}
|
|
69
57
|
|
|
58
|
+
/** Starts the root page, with the first pages of `expanded` folders. */
|
|
70
59
|
export function prefetchPagedFilesRoot(
|
|
71
60
|
queryClient: QueryClient,
|
|
72
61
|
root: PagedFilesRoot,
|
|
62
|
+
expanded: readonly string[] = [],
|
|
73
63
|
) {
|
|
74
|
-
const
|
|
64
|
+
const params = filesNavigationPageParams({ ...root, parentId: null });
|
|
75
65
|
void queryClient.prefetchQuery({
|
|
76
|
-
queryKey:
|
|
66
|
+
queryKey: filesNavigationQueryKey(params),
|
|
77
67
|
queryFn: ({ signal }) =>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
signal,
|
|
81
|
-
}),
|
|
68
|
+
readFilesNavigationPage(queryClient, params, expanded, signal),
|
|
69
|
+
retry: false,
|
|
82
70
|
});
|
|
83
71
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DocumentEditorIconRow,
|
|
3
|
+
DocumentEditorShape,
|
|
4
|
+
} from "@/components/editor/document-editor-layout";
|
|
5
|
+
|
|
6
|
+
import { LAST_LOCATION_HINT_STORAGE_KEY } from "./last-location-hint";
|
|
7
|
+
|
|
8
|
+
// Before a page loads, nothing says what it will draw: whether it has an icon,
|
|
9
|
+
// whether this person can edit it, whether it is a collection, or whether its
|
|
10
|
+
// open comments hold the review margin open. What this browser last drew for a
|
|
11
|
+
// page lets its placeholder hold the same boxes. Only page ids and these flags
|
|
12
|
+
// are kept, and only for pages that differ from the default.
|
|
13
|
+
const PAGE_ICON_ROWS_STORAGE_KEY = "content-page-icon-rows-v1";
|
|
14
|
+
const PAGE_SHAPES_STORAGE_KEY = "content-page-shapes-v1";
|
|
15
|
+
const MAX_REMEMBERED_PAGES = 200;
|
|
16
|
+
|
|
17
|
+
export const STARTUP_PAGE_ICON_ROW_ATTRIBUTE = "data-content-page-icon-row";
|
|
18
|
+
export const STARTUP_PAGE_SHAPE_ATTRIBUTE = "data-content-page-shape";
|
|
19
|
+
|
|
20
|
+
const REMEMBERED_SHAPES = [
|
|
21
|
+
"review",
|
|
22
|
+
"database",
|
|
23
|
+
"database-constrained",
|
|
24
|
+
] as const satisfies readonly DocumentEditorShape[];
|
|
25
|
+
|
|
26
|
+
function readStoredHints(key: string): Record<string, unknown> {
|
|
27
|
+
try {
|
|
28
|
+
const raw = localStorage.getItem(key);
|
|
29
|
+
const parsed: unknown = raw ? JSON.parse(raw) : null;
|
|
30
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
31
|
+
? (parsed as Record<string, unknown>)
|
|
32
|
+
: {};
|
|
33
|
+
} catch {
|
|
34
|
+
// coercion-ok: an unreadable hint only means the page may move once.
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function rememberHint(
|
|
40
|
+
key: string,
|
|
41
|
+
documentId: string,
|
|
42
|
+
value: string,
|
|
43
|
+
defaultValue: string,
|
|
44
|
+
) {
|
|
45
|
+
const hints = readStoredHints(key);
|
|
46
|
+
if (value === defaultValue && hints[documentId] === undefined) return;
|
|
47
|
+
delete hints[documentId];
|
|
48
|
+
if (value !== defaultValue) hints[documentId] = value;
|
|
49
|
+
const ids = Object.keys(hints);
|
|
50
|
+
const overflow = Math.max(0, ids.length - MAX_REMEMBERED_PAGES);
|
|
51
|
+
for (const id of ids.slice(0, overflow)) delete hints[id];
|
|
52
|
+
try {
|
|
53
|
+
localStorage.setItem(key, JSON.stringify(hints));
|
|
54
|
+
} catch {
|
|
55
|
+
// coercion-ok: without storage the next load holds the default boxes.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function readPageIconRowHint(documentId: string): DocumentEditorIconRow {
|
|
60
|
+
const row = readStoredHints(PAGE_ICON_ROWS_STORAGE_KEY)[documentId];
|
|
61
|
+
return row === "icon" || row === "none" ? row : "add";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function rememberPageIconRow(
|
|
65
|
+
documentId: string,
|
|
66
|
+
row: DocumentEditorIconRow,
|
|
67
|
+
) {
|
|
68
|
+
rememberHint(PAGE_ICON_ROWS_STORAGE_KEY, documentId, row, "add");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function readPageShapeHint(documentId: string): DocumentEditorShape {
|
|
72
|
+
const shape = readStoredHints(PAGE_SHAPES_STORAGE_KEY)[documentId];
|
|
73
|
+
return REMEMBERED_SHAPES.find((remembered) => remembered === shape) ?? "page";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// A loaded page knows whether it is a collection; the hint still says which
|
|
77
|
+
// boxes it last drew below the title.
|
|
78
|
+
export function readDocumentShapeHint(document: {
|
|
79
|
+
id: string;
|
|
80
|
+
database?: unknown;
|
|
81
|
+
}): DocumentEditorShape {
|
|
82
|
+
const shape = readPageShapeHint(document.id);
|
|
83
|
+
if (document.database) {
|
|
84
|
+
return shape === "database-constrained" ? shape : "database";
|
|
85
|
+
}
|
|
86
|
+
return shape === "review" ? shape : "page";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function rememberPageShape(
|
|
90
|
+
documentId: string,
|
|
91
|
+
shape: DocumentEditorShape,
|
|
92
|
+
) {
|
|
93
|
+
rememberHint(PAGE_SHAPES_STORAGE_KEY, documentId, shape, "page");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Runs in <head> before the first paint, so the server-rendered placeholder
|
|
97
|
+
// holds the boxes a page last drew. The app may sit under a base path; on
|
|
98
|
+
// `/home`, use the last-location id unless the URL selects a specific space.
|
|
99
|
+
// coercion-ok: an unreadable hint only means the page may move once.
|
|
100
|
+
export const CONTENT_STARTUP_PAGE_HINTS_SCRIPT = `(function(){try{function h(k){try{return JSON.parse(localStorage.getItem(k)||"{}")[id]}catch(e){}}var p=location.pathname.split("/").filter(Boolean),i=p.indexOf("page"),id=i>=0&&p[i+1]?decodeURIComponent(p[i+1]):null;if(!id&&p[p.length-1]==="home"&&!/[?&]spaceId=/.test(location.search))id=(JSON.parse(localStorage.getItem(${JSON.stringify(
|
|
101
|
+
LAST_LOCATION_HINT_STORAGE_KEY,
|
|
102
|
+
)})||"null")||{}).documentId;if(typeof id!=="string"||!id)return;var d=document.documentElement,r=h(${JSON.stringify(
|
|
103
|
+
PAGE_ICON_ROWS_STORAGE_KEY,
|
|
104
|
+
)}),s=h(${JSON.stringify(PAGE_SHAPES_STORAGE_KEY)});if(r==="icon"||r==="none")d.setAttribute(${JSON.stringify(
|
|
105
|
+
STARTUP_PAGE_ICON_ROW_ATTRIBUTE,
|
|
106
|
+
)},r);if(${JSON.stringify(REMEMBERED_SHAPES)}.indexOf(s)>=0)d.setAttribute(${JSON.stringify(
|
|
107
|
+
STARTUP_PAGE_SHAPE_ATTRIBUTE,
|
|
108
|
+
)},s)}catch(e){}})();`; // coercion-ok: without storage the shell holds the default boxes.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { safeJsonForHtml } from "@agent-native/core/shared";
|
|
2
|
+
|
|
3
|
+
export const PRIVATE_LINK_REDIRECT_ATTRIBUTE =
|
|
4
|
+
"data-content-private-link-redirect";
|
|
5
|
+
|
|
6
|
+
// Tailwind only finds whole class strings, so the attribute is spelled out
|
|
7
|
+
// here; a test keeps it equal to PRIVATE_LINK_REDIRECT_ATTRIBUTE.
|
|
8
|
+
export const HIDDEN_WHILE_PRIVATE_LINK_REDIRECTS =
|
|
9
|
+
"[html[data-content-private-link-redirect]_&]:invisible";
|
|
10
|
+
|
|
11
|
+
// Every visitor gets the same cached share page, so it can't know whether this
|
|
12
|
+
// browser may open the document. Browsers leave for the app page before first
|
|
13
|
+
// paint, which opens the document or says why it can't; the private notice is
|
|
14
|
+
// for fetchers that don't run scripts.
|
|
15
|
+
export function privateDocumentRedirectScript(pageHref: string) {
|
|
16
|
+
return `document.documentElement.setAttribute(${JSON.stringify(
|
|
17
|
+
PRIVATE_LINK_REDIRECT_ATTRIBUTE,
|
|
18
|
+
)},"");location.replace(${safeJsonForHtml(pageHref)});`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The script has already started the move, and starting it again cancels the
|
|
22
|
+
// request in flight.
|
|
23
|
+
export function privateLinkRedirectStarted() {
|
|
24
|
+
return document.documentElement.hasAttribute(PRIVATE_LINK_REDIRECT_ATTRIBUTE);
|
|
25
|
+
}
|
|
@@ -7,12 +7,15 @@ import {
|
|
|
7
7
|
// that land in any order. What this browser last drew for the same person,
|
|
8
8
|
// organization, and space lets the first frame reserve each section at that
|
|
9
9
|
// size, so later rows fill in place instead of pushing the sections below.
|
|
10
|
-
//
|
|
10
|
+
// The Files folders that were open are kept by ID, so the next load reads
|
|
11
|
+
// them with the tree. Only counts, IDs, and section settings are kept, never
|
|
12
|
+
// titles.
|
|
11
13
|
const SIDEBAR_LAYOUT_HINT_STORAGE_KEY = "content-sidebar-layout-v1";
|
|
12
14
|
|
|
13
15
|
// Rows each section shows before "Show more", and the most a hint reserves.
|
|
14
16
|
export const SIDEBAR_SECTION_ROW_LIMIT = 5;
|
|
15
17
|
const MAX_HINTED_FILES_ROWS = 100;
|
|
18
|
+
const MAX_HINTED_FILES_BRANCHES = 32;
|
|
16
19
|
|
|
17
20
|
export type SidebarRowsHint = { rows: number; more: boolean };
|
|
18
21
|
|
|
@@ -21,6 +24,8 @@ export type SidebarLayoutHint = {
|
|
|
21
24
|
pinned?: SidebarRowsHint;
|
|
22
25
|
recent?: SidebarRowsHint;
|
|
23
26
|
files?: SidebarRowsHint;
|
|
27
|
+
/** Rows each open Files folder drew, by folder document ID. */
|
|
28
|
+
branches?: Record<string, SidebarRowsHint>;
|
|
24
29
|
};
|
|
25
30
|
|
|
26
31
|
function parseRows(value: unknown, max: number): SidebarRowsHint | undefined {
|
|
@@ -32,6 +37,33 @@ function parseRows(value: unknown, max: number): SidebarRowsHint | undefined {
|
|
|
32
37
|
return { rows: Math.min(rows, max), more: more === true };
|
|
33
38
|
}
|
|
34
39
|
|
|
40
|
+
// Folders are stored oldest first, so the cap keeps the most recently drawn.
|
|
41
|
+
function parseBranches(
|
|
42
|
+
value: unknown,
|
|
43
|
+
): Record<string, SidebarRowsHint> | undefined {
|
|
44
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return Object.fromEntries(
|
|
48
|
+
Object.entries(value)
|
|
49
|
+
.slice(-MAX_HINTED_FILES_BRANCHES)
|
|
50
|
+
.flatMap(([documentId, rows]) => {
|
|
51
|
+
const parsed = parseRows(rows, MAX_HINTED_FILES_ROWS);
|
|
52
|
+
return parsed ? [[documentId, parsed]] : [];
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Open folders with `documentId` moved last, as the most recently drawn. */
|
|
58
|
+
export function withShownFilesBranch(
|
|
59
|
+
branches: Readonly<Record<string, SidebarRowsHint>> | undefined,
|
|
60
|
+
documentId: string,
|
|
61
|
+
rows: SidebarRowsHint,
|
|
62
|
+
): Record<string, SidebarRowsHint> {
|
|
63
|
+
const { [documentId]: _previous, ...others } = branches ?? {};
|
|
64
|
+
return { ...others, [documentId]: rows };
|
|
65
|
+
}
|
|
66
|
+
|
|
35
67
|
function readStoredHint(): {
|
|
36
68
|
scope?: unknown;
|
|
37
69
|
spaceId?: unknown;
|
|
@@ -75,6 +107,7 @@ export function readSidebarLayoutHint(
|
|
|
75
107
|
pinned: parseRows(stored.hint.pinned, SIDEBAR_SECTION_ROW_LIMIT),
|
|
76
108
|
recent: parseRows(stored.hint.recent, SIDEBAR_SECTION_ROW_LIMIT),
|
|
77
109
|
files: parseRows(stored.hint.files, MAX_HINTED_FILES_ROWS),
|
|
110
|
+
branches: parseBranches(stored.hint.branches),
|
|
78
111
|
};
|
|
79
112
|
}
|
|
80
113
|
|
|
@@ -66,6 +66,7 @@ import { CONTENT_COMMAND_MENU_OPEN_EVENT } from "./lib/content-command-menu";
|
|
|
66
66
|
import { isPersonalLanding } from "./lib/content-landing";
|
|
67
67
|
import { readLastLocationHintForAnyAccount } from "./lib/last-location-hint";
|
|
68
68
|
import { CONTENT_STARTUP_PAGE_ICON_ROW_SCRIPT } from "./lib/page-icon-row-hint";
|
|
69
|
+
import { CONTENT_STARTUP_PAGE_HINTS_SCRIPT } from "./lib/page-startup-hints";
|
|
69
70
|
|
|
70
71
|
import stylesheet from "./global.css?url";
|
|
71
72
|
import katexStylesheet from "katex/dist/katex.min.css?url";
|
|
@@ -216,7 +217,8 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
|
|
216
217
|
dangerouslySetInnerHTML={{
|
|
217
218
|
__html:
|
|
218
219
|
CONTENT_STARTUP_SIDEBAR_SCRIPT +
|
|
219
|
-
CONTENT_STARTUP_PAGE_ICON_ROW_SCRIPT
|
|
220
|
+
CONTENT_STARTUP_PAGE_ICON_ROW_SCRIPT +
|
|
221
|
+
CONTENT_STARTUP_PAGE_HINTS_SCRIPT,
|
|
220
222
|
}}
|
|
221
223
|
/>
|
|
222
224
|
<meta name="theme-color" content="#10B981" />
|
|
@@ -28,17 +28,17 @@ import {
|
|
|
28
28
|
startPageOpenDocumentReads,
|
|
29
29
|
} from "@/hooks/use-documents";
|
|
30
30
|
import { useLastLocationTitleHint } from "@/hooks/use-optimistic-document-title";
|
|
31
|
-
import {
|
|
32
|
-
isPersonalLanding,
|
|
33
|
-
readContentLandingRecovery,
|
|
34
|
-
} from "@/lib/content-landing";
|
|
31
|
+
import { isPersonalLanding } from "@/lib/content-landing";
|
|
35
32
|
import {
|
|
36
33
|
landingOptimisticTitle,
|
|
37
34
|
stashLandingTitleHint,
|
|
38
35
|
} from "@/lib/document-title-hint";
|
|
39
36
|
import { filesRootHintScope } from "@/lib/files-root-hint";
|
|
40
37
|
import { readLastLocationHint } from "@/lib/last-location-hint";
|
|
41
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
readPageIconRowHint,
|
|
40
|
+
readPageShapeHint,
|
|
41
|
+
} from "@/lib/page-startup-hints";
|
|
42
42
|
|
|
43
43
|
const SEO_TITLE = "Content - Open Source, agent-friendly Obsidian alternative";
|
|
44
44
|
const SEO_DESCRIPTION =
|
|
@@ -119,8 +119,6 @@ export default function HomeRoute() {
|
|
|
119
119
|
const lastLocationHint = useLastLocationTitleHint();
|
|
120
120
|
const lastLocationHintRef = useRef(lastLocationHint);
|
|
121
121
|
lastLocationHintRef.current = lastLocationHint;
|
|
122
|
-
const recoveredDocumentId =
|
|
123
|
-
readContentLandingRecovery(location.state)?.unavailableDocumentId ?? null;
|
|
124
122
|
const queryClient = useQueryClient();
|
|
125
123
|
const { session } = useSession();
|
|
126
124
|
const scope = filesRootHintScope(session?.email, session?.orgId);
|
|
@@ -181,9 +179,7 @@ export default function HomeRoute() {
|
|
|
181
179
|
void navigate(contentRecentHref(result.target), { replace: true });
|
|
182
180
|
return;
|
|
183
181
|
}
|
|
184
|
-
if (
|
|
185
|
-
toast.info(t("landing.requestedPageUnavailable"));
|
|
186
|
-
} else if (result.fallbackReason === "saved-document-unavailable") {
|
|
182
|
+
if (result.fallbackReason === "saved-document-unavailable") {
|
|
187
183
|
toast.info(t("landing.previousPageUnavailable"));
|
|
188
184
|
}
|
|
189
185
|
const hint = lastLocationHintRef.current;
|
|
@@ -201,15 +197,7 @@ export default function HomeRoute() {
|
|
|
201
197
|
} catch (error) {
|
|
202
198
|
console.error("Failed to resolve the Content landing page", error);
|
|
203
199
|
}
|
|
204
|
-
}, [
|
|
205
|
-
location.hash,
|
|
206
|
-
location.search,
|
|
207
|
-
navigate,
|
|
208
|
-
recoveredDocumentId,
|
|
209
|
-
resolveLanding,
|
|
210
|
-
spaceId,
|
|
211
|
-
t,
|
|
212
|
-
]);
|
|
200
|
+
}, [location.hash, location.search, navigate, resolveLanding, spaceId, t]);
|
|
213
201
|
|
|
214
202
|
useEffect(() => {
|
|
215
203
|
void openLanding();
|
|
@@ -249,6 +237,9 @@ export default function HomeRoute() {
|
|
|
249
237
|
iconRow={
|
|
250
238
|
likelyDocumentId ? readPageIconRowHint(likelyDocumentId) : undefined
|
|
251
239
|
}
|
|
240
|
+
shape={
|
|
241
|
+
likelyDocumentId ? readPageShapeHint(likelyDocumentId) : undefined
|
|
242
|
+
}
|
|
252
243
|
/>
|
|
253
244
|
</>
|
|
254
245
|
);
|
|
@@ -28,6 +28,11 @@ import type {
|
|
|
28
28
|
import { data, redirect, useLoaderData } from "react-router";
|
|
29
29
|
|
|
30
30
|
import { VisualEditor } from "@/components/editor/VisualEditor";
|
|
31
|
+
import {
|
|
32
|
+
HIDDEN_WHILE_PRIVATE_LINK_REDIRECTS,
|
|
33
|
+
privateDocumentRedirectScript,
|
|
34
|
+
privateLinkRedirectStarted,
|
|
35
|
+
} from "@/lib/private-link-redirect";
|
|
31
36
|
|
|
32
37
|
import { getDb, schema } from "../../server/db";
|
|
33
38
|
import {
|
|
@@ -93,7 +98,7 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
|
|
|
93
98
|
const userEmail = getRequestUserEmail();
|
|
94
99
|
if (userEmail) {
|
|
95
100
|
const access = await resolveAccess("document", id);
|
|
96
|
-
if (access) throw redirect(withBase(`/page/${id}`));
|
|
101
|
+
if (access) throw redirect(withBase(`/page/${encodeURIComponent(id)}`));
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
const [doc] = await getDb()
|
|
@@ -330,13 +335,24 @@ function PrivateDocumentNotice({
|
|
|
330
335
|
origin?: string;
|
|
331
336
|
}) {
|
|
332
337
|
const t = useT();
|
|
338
|
+
const pageHref = id
|
|
339
|
+
? `${basePath ?? ""}/page/${encodeURIComponent(id)}`
|
|
340
|
+
: null;
|
|
333
341
|
useEffect(() => {
|
|
334
|
-
if (!
|
|
335
|
-
window.location.replace(
|
|
336
|
-
}, [
|
|
342
|
+
if (!pageHref || privateLinkRedirectStarted()) return;
|
|
343
|
+
window.location.replace(pageHref);
|
|
344
|
+
}, [pageHref]);
|
|
337
345
|
|
|
338
346
|
return (
|
|
339
347
|
<main className="min-h-screen bg-background text-foreground">
|
|
348
|
+
{pageHref ? (
|
|
349
|
+
<script
|
|
350
|
+
suppressHydrationWarning
|
|
351
|
+
dangerouslySetInnerHTML={{
|
|
352
|
+
__html: privateDocumentRedirectScript(pageHref),
|
|
353
|
+
}}
|
|
354
|
+
/>
|
|
355
|
+
) : null}
|
|
340
356
|
{id ? (
|
|
341
357
|
<AgentReadableDocumentDiscovery
|
|
342
358
|
document={{ id }}
|
|
@@ -345,7 +361,9 @@ function PrivateDocumentNotice({
|
|
|
345
361
|
accessState="authentication-required"
|
|
346
362
|
/>
|
|
347
363
|
) : null}
|
|
348
|
-
<section
|
|
364
|
+
<section
|
|
365
|
+
className={`mx-auto flex min-h-screen max-w-md flex-col items-center justify-center px-6 text-center ${HIDDEN_WHILE_PRIVATE_LINK_REDIRECTS}`}
|
|
366
|
+
>
|
|
349
367
|
<div className="mb-5 flex h-12 w-12 items-center justify-center rounded-xl border border-border bg-muted text-muted-foreground">
|
|
350
368
|
<IconLock size={22} />
|
|
351
369
|
</div>
|