@agent-native/core 0.135.0 → 0.135.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/sharing/access.ts +44 -2
- package/corpus/templates/content/actions/_database-utils.ts +113 -4
- package/corpus/templates/content/actions/get-document.ts +84 -3
- package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +9 -2
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +29 -13
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +5 -12
- package/corpus/templates/content/app/components/editor/body-hydration.ts +12 -6
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2 -3
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +5 -12
- package/corpus/templates/content/app/hooks/use-content-database.ts +16 -27
- package/corpus/templates/content/app/hooks/use-create-page.ts +3 -6
- package/corpus/templates/content/app/hooks/use-document-properties.ts +9 -16
- package/corpus/templates/content/app/hooks/use-document-versions.ts +3 -3
- package/corpus/templates/content/app/hooks/use-documents.ts +85 -29
- package/corpus/templates/content/app/hooks/use-notion.ts +15 -13
- package/corpus/templates/content/app/i18n-data.ts +32 -1
- package/corpus/templates/content/app/lib/document-query.ts +36 -0
- package/corpus/templates/content/changelog/2026-08-02-pages-opened-from-a-database-now-keep-that-database-s-fields.md +6 -0
- package/corpus/templates/content/server/lib/document-context.ts +11 -6
- package/corpus/templates/content/shared/api.ts +8 -0
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +32 -2
- package/dist/sharing/access.js.map +1 -1
- package/package.json +1 -1
- package/src/sharing/access.ts +44 -2
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.135.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ed51b3d: Grant org-visibility access on shareable resources based on the caller's real organization membership, instead of only their currently active organization. Fixes real org members being denied access to org-shared resources (e.g. recordings) when a different org happened to be active in their session.
|
|
8
|
+
|
|
3
9
|
## 0.135.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.135.
|
|
3
|
+
"version": "0.135.1",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { and, eq, or, sql, type SQL } from "drizzle-orm";
|
|
17
17
|
|
|
18
|
+
import { orgMembers } from "../org/schema.js";
|
|
18
19
|
import {
|
|
19
20
|
getRequestAuthCapability,
|
|
20
21
|
getRequestUserEmail,
|
|
@@ -87,6 +88,35 @@ function emailColumnMatches(column: any, email: string): SQL {
|
|
|
87
88
|
return sql`lower(${column}) = ${email}`;
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Real `org_members` membership, independent of the caller's currently
|
|
93
|
+
* active org (`ctx.orgId`). `org`-visibility access must key off actual
|
|
94
|
+
* membership — a user's active-org selection is a UI convenience, not a
|
|
95
|
+
* statement of which orgs they belong to.
|
|
96
|
+
*
|
|
97
|
+
* Queries through the resource's own `reg.getDb()` — the same connection
|
|
98
|
+
* every other lookup in this file uses — not the ambient `getDbExec()`,
|
|
99
|
+
* since `org_members` lives in that same app database.
|
|
100
|
+
*/
|
|
101
|
+
async function isOrgMember(
|
|
102
|
+
reg: ShareableResourceRegistration,
|
|
103
|
+
memberOrgId: string,
|
|
104
|
+
email: string,
|
|
105
|
+
): Promise<boolean> {
|
|
106
|
+
const db = reg.getDb() as any;
|
|
107
|
+
const rows = await db
|
|
108
|
+
.select({ id: orgMembers.id })
|
|
109
|
+
.from(orgMembers)
|
|
110
|
+
.where(
|
|
111
|
+
and(
|
|
112
|
+
eq(orgMembers.orgId, memberOrgId),
|
|
113
|
+
emailColumnMatches(orgMembers.email, email),
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
.limit(1);
|
|
117
|
+
return rows.length > 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
90
120
|
/**
|
|
91
121
|
* Build a Drizzle `WHERE` clause that admits rows the current user can see.
|
|
92
122
|
* Pass the ownable resource table and its shares table; optional min role
|
|
@@ -470,7 +500,7 @@ async function resolveAccessImpl(
|
|
|
470
500
|
const resource = await loadResourceForAccess(reg, resourceId, options);
|
|
471
501
|
if (!resource) return null;
|
|
472
502
|
|
|
473
|
-
const { userEmail
|
|
503
|
+
const { userEmail } = ctx;
|
|
474
504
|
const normalizedUserEmail = normalizeEmailForAccess(userEmail);
|
|
475
505
|
|
|
476
506
|
if (
|
|
@@ -491,7 +521,19 @@ async function resolveAccessImpl(
|
|
|
491
521
|
// `visibility === "public"` on an `allowPublic: false` resource is treated
|
|
492
522
|
// as private: only owner + explicit shares grant access. Falls through to
|
|
493
523
|
// the explicit-share lookup below.
|
|
494
|
-
|
|
524
|
+
//
|
|
525
|
+
// Membership in the resource's own org, not equality with the caller's
|
|
526
|
+
// currently active org: a caller can be a genuine member of the
|
|
527
|
+
// resource's org while a *different* org is their active selection, and
|
|
528
|
+
// `org` visibility should still admit them. Still requires some active
|
|
529
|
+
// org to be set at all (`orgId`), matching the pre-existing behavior for
|
|
530
|
+
// a caller with no active org.
|
|
531
|
+
if (
|
|
532
|
+
resource.visibility === "org" &&
|
|
533
|
+
resource.orgId &&
|
|
534
|
+
normalizedUserEmail &&
|
|
535
|
+
(await isOrgMember(reg, resource.orgId, normalizedUserEmail))
|
|
536
|
+
) {
|
|
495
537
|
const role = await highestShareRole(reg, resourceId, ctx, resource);
|
|
496
538
|
return { role: role ?? "viewer", resource };
|
|
497
539
|
}
|
|
@@ -1234,10 +1234,15 @@ export async function getDatabaseByDocumentId(
|
|
|
1234
1234
|
|
|
1235
1235
|
export async function getDatabaseItemByDocumentId(
|
|
1236
1236
|
documentId: string,
|
|
1237
|
-
options: { includeDeleted?: boolean } = {},
|
|
1237
|
+
options: { includeDeleted?: boolean; databaseId?: string } = {},
|
|
1238
1238
|
db = getDb(),
|
|
1239
1239
|
) {
|
|
1240
1240
|
const clauses = [eq(schema.contentDatabaseItems.documentId, documentId)];
|
|
1241
|
+
if (options.databaseId) {
|
|
1242
|
+
clauses.push(
|
|
1243
|
+
eq(schema.contentDatabaseItems.databaseId, options.databaseId),
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1241
1246
|
if (!options.includeDeleted) {
|
|
1242
1247
|
clauses.push(isNull(schema.contentDatabases.deletedAt));
|
|
1243
1248
|
}
|
|
@@ -1262,13 +1267,33 @@ export async function getDatabaseItemByDocumentId(
|
|
|
1262
1267
|
)
|
|
1263
1268
|
.leftJoin(
|
|
1264
1269
|
schema.contentDatabaseBodyHydrationQueue,
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1270
|
+
and(
|
|
1271
|
+
eq(
|
|
1272
|
+
schema.contentDatabaseBodyHydrationQueue.databaseItemId,
|
|
1273
|
+
schema.contentDatabaseItems.id,
|
|
1274
|
+
),
|
|
1275
|
+
eq(
|
|
1276
|
+
schema.contentDatabaseBodyHydrationQueue.sourceId,
|
|
1277
|
+
schema.contentDatabaseSourceRows.sourceId,
|
|
1278
|
+
),
|
|
1279
|
+
eq(
|
|
1280
|
+
schema.contentDatabaseBodyHydrationQueue.sourceRowId,
|
|
1281
|
+
schema.contentDatabaseSourceRows.sourceRowId,
|
|
1282
|
+
),
|
|
1268
1283
|
),
|
|
1269
1284
|
)
|
|
1270
1285
|
.where(and(...clauses))
|
|
1271
1286
|
.orderBy(
|
|
1287
|
+
sql`CASE
|
|
1288
|
+
WHEN ${schema.contentDatabaseSourceRows.sourceId} IS NOT NULL
|
|
1289
|
+
AND (
|
|
1290
|
+
${schema.contentDatabaseItems.bodyHydrationStatus} IN ('pending', 'hydrating')
|
|
1291
|
+
OR ${schema.contentDatabaseBodyHydrationQueue.id} IS NOT NULL
|
|
1292
|
+
) THEN 0
|
|
1293
|
+
WHEN ${schema.contentDatabaseSourceRows.sourceId} IS NOT NULL
|
|
1294
|
+
AND ${schema.contentDatabaseItems.bodyHydrationStatus} = 'error' THEN 1
|
|
1295
|
+
ELSE 2
|
|
1296
|
+
END`,
|
|
1272
1297
|
sql`CASE WHEN ${schema.contentDatabaseSourceRows.sourceId} IS NOT NULL THEN 0 ELSE 1 END`,
|
|
1273
1298
|
sql`CASE WHEN ${schema.contentDatabases.systemRole} IS NULL THEN 0 ELSE 1 END`,
|
|
1274
1299
|
sql`CASE WHEN ${schema.contentDatabases.systemRole} = 'files' THEN 0 ELSE 1 END`,
|
|
@@ -1277,6 +1302,90 @@ export async function getDatabaseItemByDocumentId(
|
|
|
1277
1302
|
return row ?? null;
|
|
1278
1303
|
}
|
|
1279
1304
|
|
|
1305
|
+
export async function getBuilderBodyHydrationMembershipByDocumentId(
|
|
1306
|
+
documentId: string,
|
|
1307
|
+
db = getDb(),
|
|
1308
|
+
) {
|
|
1309
|
+
const rows = await db
|
|
1310
|
+
.select({
|
|
1311
|
+
item: schema.contentDatabaseItems,
|
|
1312
|
+
database: schema.contentDatabases,
|
|
1313
|
+
sourceId: schema.contentDatabaseSourceRows.sourceId,
|
|
1314
|
+
sourceRowId: schema.contentDatabaseSourceRows.sourceRowId,
|
|
1315
|
+
bodyHydrationQueueId: schema.contentDatabaseBodyHydrationQueue.id,
|
|
1316
|
+
queueSourceId: schema.contentDatabaseBodyHydrationQueue.sourceId,
|
|
1317
|
+
queueSourceRowId: schema.contentDatabaseBodyHydrationQueue.sourceRowId,
|
|
1318
|
+
})
|
|
1319
|
+
.from(schema.contentDatabaseSourceRows)
|
|
1320
|
+
.innerJoin(
|
|
1321
|
+
schema.contentDatabaseSources,
|
|
1322
|
+
eq(
|
|
1323
|
+
schema.contentDatabaseSources.id,
|
|
1324
|
+
schema.contentDatabaseSourceRows.sourceId,
|
|
1325
|
+
),
|
|
1326
|
+
)
|
|
1327
|
+
.innerJoin(
|
|
1328
|
+
schema.contentDatabaseItems,
|
|
1329
|
+
eq(
|
|
1330
|
+
schema.contentDatabaseItems.id,
|
|
1331
|
+
schema.contentDatabaseSourceRows.databaseItemId,
|
|
1332
|
+
),
|
|
1333
|
+
)
|
|
1334
|
+
.innerJoin(
|
|
1335
|
+
schema.contentDatabases,
|
|
1336
|
+
eq(schema.contentDatabases.id, schema.contentDatabaseItems.databaseId),
|
|
1337
|
+
)
|
|
1338
|
+
.leftJoin(
|
|
1339
|
+
schema.contentDatabaseBodyHydrationQueue,
|
|
1340
|
+
eq(
|
|
1341
|
+
schema.contentDatabaseBodyHydrationQueue.databaseItemId,
|
|
1342
|
+
schema.contentDatabaseItems.id,
|
|
1343
|
+
),
|
|
1344
|
+
)
|
|
1345
|
+
.where(
|
|
1346
|
+
and(
|
|
1347
|
+
eq(schema.contentDatabaseSourceRows.documentId, documentId),
|
|
1348
|
+
eq(schema.contentDatabaseItems.documentId, documentId),
|
|
1349
|
+
eq(schema.contentDatabaseSources.sourceType, "builder-cms"),
|
|
1350
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
1351
|
+
),
|
|
1352
|
+
);
|
|
1353
|
+
|
|
1354
|
+
const boundRows = rows.filter(
|
|
1355
|
+
(row) =>
|
|
1356
|
+
!row.bodyHydrationQueueId ||
|
|
1357
|
+
(row.queueSourceId === row.sourceId &&
|
|
1358
|
+
row.queueSourceRowId === row.sourceRowId),
|
|
1359
|
+
);
|
|
1360
|
+
if (boundRows.length === 0) return null;
|
|
1361
|
+
|
|
1362
|
+
const priority = (row: (typeof boundRows)[number]) => {
|
|
1363
|
+
if (
|
|
1364
|
+
row.bodyHydrationQueueId ||
|
|
1365
|
+
row.item.bodyHydrationStatus === "pending" ||
|
|
1366
|
+
row.item.bodyHydrationStatus === "hydrating"
|
|
1367
|
+
) {
|
|
1368
|
+
return 0;
|
|
1369
|
+
}
|
|
1370
|
+
if (row.item.bodyHydrationStatus === "error") return 1;
|
|
1371
|
+
return 2;
|
|
1372
|
+
};
|
|
1373
|
+
const topPriority = Math.min(...boundRows.map(priority));
|
|
1374
|
+
const candidates = boundRows
|
|
1375
|
+
.filter((row) => priority(row) === topPriority)
|
|
1376
|
+
.sort((left, right) =>
|
|
1377
|
+
`${left.database.id}:${left.sourceId}:${left.sourceRowId}`.localeCompare(
|
|
1378
|
+
`${right.database.id}:${right.sourceId}:${right.sourceRowId}`,
|
|
1379
|
+
),
|
|
1380
|
+
);
|
|
1381
|
+
const sourceIds = new Set(candidates.map((row) => row.sourceId));
|
|
1382
|
+
|
|
1383
|
+
return {
|
|
1384
|
+
membership: candidates[0]!,
|
|
1385
|
+
hydrationSourceId: sourceIds.size === 1 ? candidates[0]!.sourceId : null,
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1280
1389
|
export async function deleteDatabaseDataForDocument(
|
|
1281
1390
|
documentId: string,
|
|
1282
1391
|
ownerEmail: string,
|
|
@@ -11,6 +11,7 @@ import { favoriteDocumentIds } from "./_content-favorites.js";
|
|
|
11
11
|
import { resolveContentSpaceAccess } from "./_content-space-access.js";
|
|
12
12
|
import {
|
|
13
13
|
getDatabaseByDocumentId,
|
|
14
|
+
getBuilderBodyHydrationMembershipByDocumentId,
|
|
14
15
|
getDocumentContextPath,
|
|
15
16
|
getDatabaseItemByDocumentId,
|
|
16
17
|
isSoftDeletedDatabaseDocument,
|
|
@@ -18,7 +19,9 @@ import {
|
|
|
18
19
|
} from "./_database-utils.js";
|
|
19
20
|
import { serializeDocumentSource } from "./_document-source.js";
|
|
20
21
|
import {
|
|
22
|
+
getDatabaseById,
|
|
21
23
|
listPropertiesForDocument,
|
|
24
|
+
resolvePropertyDatabaseForDocument,
|
|
22
25
|
serializeDatabase,
|
|
23
26
|
} from "./_property-utils.js";
|
|
24
27
|
|
|
@@ -54,6 +57,14 @@ export default defineAction({
|
|
|
54
57
|
description: "Get a single document by ID with full content.",
|
|
55
58
|
schema: z.object({
|
|
56
59
|
id: z.string().optional().describe("Document ID (required)"),
|
|
60
|
+
databaseId: z
|
|
61
|
+
.string()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("Exact Database context for membership-local data"),
|
|
64
|
+
databaseDocumentId: z
|
|
65
|
+
.string()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Backing document ID for the exact Database context"),
|
|
57
68
|
}),
|
|
58
69
|
http: { method: "GET" },
|
|
59
70
|
readOnly: true,
|
|
@@ -79,8 +90,54 @@ export default defineAction({
|
|
|
79
90
|
});
|
|
80
91
|
}
|
|
81
92
|
const doc = access.resource;
|
|
93
|
+
if (args.databaseDocumentId && !args.databaseId) {
|
|
94
|
+
throw Object.assign(new Error("databaseDocumentId requires databaseId"), {
|
|
95
|
+
statusCode: 404,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
82
99
|
const database = await getDatabaseByDocumentId(doc.id);
|
|
83
|
-
const databaseMembership =
|
|
100
|
+
const databaseMembership = args.databaseId
|
|
101
|
+
? await getDatabaseItemByDocumentId(doc.id, {
|
|
102
|
+
databaseId: args.databaseId,
|
|
103
|
+
})
|
|
104
|
+
: await getDatabaseItemByDocumentId(doc.id);
|
|
105
|
+
const propertyDatabase = args.databaseId
|
|
106
|
+
? await getDatabaseById(args.databaseId)
|
|
107
|
+
: await resolvePropertyDatabaseForDocument(doc);
|
|
108
|
+
const propertyDatabaseAccess =
|
|
109
|
+
args.databaseId && propertyDatabase
|
|
110
|
+
? await resolveDocumentAccess(propertyDatabase.documentId)
|
|
111
|
+
: null;
|
|
112
|
+
if (
|
|
113
|
+
args.databaseId &&
|
|
114
|
+
(!propertyDatabase ||
|
|
115
|
+
!propertyDatabaseAccess ||
|
|
116
|
+
(propertyDatabase.documentId !== doc.id && !databaseMembership))
|
|
117
|
+
) {
|
|
118
|
+
throw Object.assign(new Error("Database context not found"), {
|
|
119
|
+
statusCode: 404,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (
|
|
123
|
+
args.databaseDocumentId &&
|
|
124
|
+
propertyDatabase?.documentId !== args.databaseDocumentId
|
|
125
|
+
) {
|
|
126
|
+
throw Object.assign(new Error("Database context not found"), {
|
|
127
|
+
statusCode: 404,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const bodyHydrationTarget =
|
|
131
|
+
await getBuilderBodyHydrationMembershipByDocumentId(doc.id);
|
|
132
|
+
const bodyHydrationMembership = bodyHydrationTarget?.membership;
|
|
133
|
+
const bodyHydrationAccess = bodyHydrationTarget?.hydrationSourceId
|
|
134
|
+
? await resolveDocumentAccess(
|
|
135
|
+
bodyHydrationMembership!.database.documentId,
|
|
136
|
+
)
|
|
137
|
+
: null;
|
|
138
|
+
const bodyHydration = bodyHydrationMembership
|
|
139
|
+
? serializeDatabaseMembership(bodyHydrationMembership).bodyHydration
|
|
140
|
+
: null;
|
|
84
141
|
const userEmail = getRequestUserEmail();
|
|
85
142
|
const favoriteIds = userEmail
|
|
86
143
|
? await favoriteDocumentIds(getDb(), userEmail, [doc.id])
|
|
@@ -112,10 +169,34 @@ export default defineAction({
|
|
|
112
169
|
databaseMembership: databaseMembership
|
|
113
170
|
? serializeDatabaseMembership(databaseMembership)
|
|
114
171
|
: undefined,
|
|
172
|
+
bodyHydration: bodyHydrationMembership
|
|
173
|
+
? {
|
|
174
|
+
hydration: bodyHydrationAccess
|
|
175
|
+
? bodyHydration!
|
|
176
|
+
: {
|
|
177
|
+
status: bodyHydration!.status,
|
|
178
|
+
attemptedAt: null,
|
|
179
|
+
error: null,
|
|
180
|
+
version: null,
|
|
181
|
+
},
|
|
182
|
+
...(bodyHydrationAccess &&
|
|
183
|
+
canEditRole(bodyHydrationAccess.role) &&
|
|
184
|
+
bodyHydrationTarget?.hydrationSourceId
|
|
185
|
+
? {
|
|
186
|
+
provider: "builder" as const,
|
|
187
|
+
sourceId: bodyHydrationTarget.hydrationSourceId,
|
|
188
|
+
databaseDocumentId:
|
|
189
|
+
bodyHydrationMembership.database.documentId,
|
|
190
|
+
}
|
|
191
|
+
: {}),
|
|
192
|
+
}
|
|
193
|
+
: undefined,
|
|
115
194
|
createdAt: doc.createdAt,
|
|
116
195
|
updatedAt: doc.updatedAt,
|
|
117
|
-
properties: await listPropertiesForDocument(doc),
|
|
118
|
-
contextPath: await getDocumentContextPath(doc
|
|
196
|
+
properties: await listPropertiesForDocument(doc, args.databaseId),
|
|
197
|
+
contextPath: await getDocumentContextPath(doc, {
|
|
198
|
+
databaseId: args.databaseId,
|
|
199
|
+
}),
|
|
119
200
|
};
|
|
120
201
|
},
|
|
121
202
|
link: ({ result }) => {
|
|
@@ -8,9 +8,16 @@ export function BuilderBodySyncingNotice({
|
|
|
8
8
|
description: string;
|
|
9
9
|
}) {
|
|
10
10
|
return (
|
|
11
|
-
<div
|
|
11
|
+
<div
|
|
12
|
+
className="rounded-lg border border-dashed border-border bg-muted/35 px-4 py-5 text-sm"
|
|
13
|
+
role="status"
|
|
14
|
+
aria-live="polite"
|
|
15
|
+
>
|
|
12
16
|
<div className="flex items-center gap-2 font-medium text-foreground">
|
|
13
|
-
<IconLoader2
|
|
17
|
+
<IconLoader2
|
|
18
|
+
className="size-4 animate-spin text-muted-foreground"
|
|
19
|
+
aria-hidden="true"
|
|
20
|
+
/>
|
|
14
21
|
{title}
|
|
15
22
|
</div>
|
|
16
23
|
<p className="mt-2 max-w-2xl leading-6 text-muted-foreground">
|
|
@@ -62,8 +62,10 @@ import {
|
|
|
62
62
|
type ContentSpaceSummary,
|
|
63
63
|
} from "@/hooks/use-content-spaces";
|
|
64
64
|
import {
|
|
65
|
+
mergeDocumentIntoDocumentCache,
|
|
65
66
|
isDocumentUpdateConflict,
|
|
66
67
|
patchDocumentCaches,
|
|
68
|
+
documentQueryFilter,
|
|
67
69
|
useDocument,
|
|
68
70
|
useDeleteDocument,
|
|
69
71
|
useDocuments,
|
|
@@ -247,7 +249,10 @@ export function DocumentEditor({
|
|
|
247
249
|
databaseId,
|
|
248
250
|
databaseDocumentId,
|
|
249
251
|
}: DocumentEditorProps) {
|
|
250
|
-
const documentQuery = useDocument(documentId
|
|
252
|
+
const documentQuery = useDocument(documentId, {
|
|
253
|
+
databaseId,
|
|
254
|
+
databaseDocumentId,
|
|
255
|
+
});
|
|
251
256
|
const {
|
|
252
257
|
data: queriedDocument,
|
|
253
258
|
isError,
|
|
@@ -539,7 +544,7 @@ function DocumentEditorBody({
|
|
|
539
544
|
const deleteDocument = useDeleteDocument();
|
|
540
545
|
const queryClient = useQueryClient();
|
|
541
546
|
const processBuilderBodies = useProcessBuilderBodyHydration(
|
|
542
|
-
document.
|
|
547
|
+
document.bodyHydration?.databaseDocumentId ?? documentId,
|
|
543
548
|
);
|
|
544
549
|
const canEdit = document.canEdit ?? true;
|
|
545
550
|
const canEditRef = useRef(canEdit);
|
|
@@ -664,24 +669,30 @@ function DocumentEditorBody({
|
|
|
664
669
|
);
|
|
665
670
|
|
|
666
671
|
useEffect(() => {
|
|
667
|
-
const
|
|
668
|
-
const hydration =
|
|
672
|
+
const hydrationContext = document.bodyHydration;
|
|
673
|
+
const hydration = hydrationContext?.hydration;
|
|
669
674
|
if (
|
|
670
|
-
!
|
|
675
|
+
!canEdit ||
|
|
676
|
+
!hydrationContext?.sourceId ||
|
|
671
677
|
!hydration ||
|
|
672
678
|
(hydration.status !== "pending" && hydration.status !== "error")
|
|
673
679
|
) {
|
|
674
680
|
return;
|
|
675
681
|
}
|
|
676
|
-
const promotionKey = `${
|
|
682
|
+
const promotionKey = `${hydrationContext.sourceId}:${documentId}:${hydration.status}:${hydration.version ?? ""}`;
|
|
677
683
|
if (promotedBuilderBodyRef.current === promotionKey) return;
|
|
678
684
|
promotedBuilderBodyRef.current = promotionKey;
|
|
679
685
|
processBuilderBodies.mutate({
|
|
680
|
-
sourceId:
|
|
686
|
+
sourceId: hydrationContext.sourceId,
|
|
681
687
|
documentId,
|
|
682
688
|
limit: 1,
|
|
683
689
|
});
|
|
684
|
-
}, [
|
|
690
|
+
}, [
|
|
691
|
+
canEdit,
|
|
692
|
+
document.bodyHydration,
|
|
693
|
+
documentId,
|
|
694
|
+
processBuilderBodies.mutate,
|
|
695
|
+
]);
|
|
685
696
|
const titleFocusedRef = useRef(false);
|
|
686
697
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
687
698
|
const titleInputRef = useRef<HTMLTextAreaElement>(null);
|
|
@@ -976,9 +987,8 @@ function DocumentEditorBody({
|
|
|
976
987
|
description:
|
|
977
988
|
error instanceof Error ? error.message : t("empty.genericError"),
|
|
978
989
|
});
|
|
979
|
-
queryClient.
|
|
980
|
-
|
|
981
|
-
fileFirstDocument,
|
|
990
|
+
queryClient.setQueriesData(documentQueryFilter(documentId), (old) =>
|
|
991
|
+
mergeDocumentIntoDocumentCache(old, fileFirstDocument),
|
|
982
992
|
);
|
|
983
993
|
queryClient.invalidateQueries({
|
|
984
994
|
queryKey: ["action", "list-documents"],
|
|
@@ -1890,9 +1900,15 @@ function DocumentEditorBody({
|
|
|
1890
1900
|
if (bodyHydrationPending) {
|
|
1891
1901
|
return (
|
|
1892
1902
|
<BuilderBodySyncingNotice
|
|
1893
|
-
title={t(
|
|
1903
|
+
title={t(
|
|
1904
|
+
document.bodyHydration?.provider === "builder"
|
|
1905
|
+
? "editor.builderBodySyncing"
|
|
1906
|
+
: "editor.pageBodySyncing",
|
|
1907
|
+
)}
|
|
1894
1908
|
description={t(
|
|
1895
|
-
|
|
1909
|
+
document.bodyHydration?.provider === "builder"
|
|
1910
|
+
? "editor.builderBodySyncingDescription"
|
|
1911
|
+
: "editor.pageBodySyncingDescription",
|
|
1896
1912
|
)}
|
|
1897
1913
|
/>
|
|
1898
1914
|
);
|
|
@@ -90,6 +90,7 @@ import {
|
|
|
90
90
|
useSearchNotionPages,
|
|
91
91
|
useCreateAndLinkNotionPage,
|
|
92
92
|
} from "@/hooks/use-notion";
|
|
93
|
+
import { documentQueryFilter } from "@/lib/document-query";
|
|
93
94
|
import {
|
|
94
95
|
localSourceAbsolutePath,
|
|
95
96
|
revealLinkedLocalSourceFile,
|
|
@@ -572,12 +573,8 @@ export function DocumentToolbar({
|
|
|
572
573
|
const previous = hideFromSearch;
|
|
573
574
|
setPendingHideFromSearch(next);
|
|
574
575
|
|
|
575
|
-
queryClient.
|
|
576
|
-
|
|
577
|
-
(old: any) =>
|
|
578
|
-
old && typeof old === "object"
|
|
579
|
-
? { ...old, hideFromSearch: next }
|
|
580
|
-
: old,
|
|
576
|
+
queryClient.setQueriesData(documentQueryFilter(documentId), (old: any) =>
|
|
577
|
+
old && typeof old === "object" ? { ...old, hideFromSearch: next } : old,
|
|
581
578
|
);
|
|
582
579
|
queryClient.setQueryData(
|
|
583
580
|
["action", "list-documents", undefined],
|
|
@@ -601,9 +598,7 @@ export function DocumentToolbar({
|
|
|
601
598
|
});
|
|
602
599
|
} catch (err) {
|
|
603
600
|
setPendingHideFromSearch(previous);
|
|
604
|
-
queryClient.invalidateQueries(
|
|
605
|
-
queryKey: ["action", "get-document", { id: documentId }],
|
|
606
|
-
});
|
|
601
|
+
queryClient.invalidateQueries(documentQueryFilter(documentId));
|
|
607
602
|
queryClient.invalidateQueries({
|
|
608
603
|
queryKey: ["action", "list-documents"],
|
|
609
604
|
});
|
|
@@ -614,9 +609,7 @@ export function DocumentToolbar({
|
|
|
614
609
|
throw err;
|
|
615
610
|
} finally {
|
|
616
611
|
setPendingHideFromSearch(null);
|
|
617
|
-
queryClient.invalidateQueries(
|
|
618
|
-
queryKey: ["action", "get-document", { id: documentId }],
|
|
619
|
-
});
|
|
612
|
+
queryClient.invalidateQueries(documentQueryFilter(documentId));
|
|
620
613
|
queryClient.invalidateQueries({
|
|
621
614
|
queryKey: ["action", "list-documents"],
|
|
622
615
|
});
|
|
@@ -54,12 +54,12 @@ export function databaseItemBodyHydrationIsPending(
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export function documentBodyHydrationIsPending(
|
|
57
|
-
document: Pick<Document, "content" | "
|
|
57
|
+
document: Pick<Document, "content" | "bodyHydration">,
|
|
58
58
|
) {
|
|
59
|
-
const hydration = document.
|
|
59
|
+
const hydration = document.bodyHydration?.hydration;
|
|
60
60
|
if (
|
|
61
61
|
sourceBackedEmptyBodyNeedsHydration({
|
|
62
|
-
sourceId: document.
|
|
62
|
+
sourceId: document.bodyHydration ? "source-backed" : undefined,
|
|
63
63
|
content: document.content,
|
|
64
64
|
hydration,
|
|
65
65
|
})
|
|
@@ -81,7 +81,10 @@ export function newDocumentPageChoiceIsDisabled(args: {
|
|
|
81
81
|
|
|
82
82
|
export function previewBodyHydrationIsPending(args: {
|
|
83
83
|
item: Pick<ContentDatabaseItem, "bodyHydration" | "document">;
|
|
84
|
-
document:
|
|
84
|
+
document:
|
|
85
|
+
| Pick<Document, "content" | "databaseMembership" | "bodyHydration">
|
|
86
|
+
| null
|
|
87
|
+
| undefined;
|
|
85
88
|
}) {
|
|
86
89
|
const membership =
|
|
87
90
|
args.document?.databaseMembership ?? args.item.document.databaseMembership;
|
|
@@ -128,11 +131,14 @@ export function previewDraftConflictsWithHydratedBody(args: {
|
|
|
128
131
|
|
|
129
132
|
export function previewBodyHydrationIsTerminalError(args: {
|
|
130
133
|
item: Pick<ContentDatabaseItem, "bodyHydration" | "document">;
|
|
131
|
-
document:
|
|
134
|
+
document:
|
|
135
|
+
| Pick<Document, "databaseMembership" | "bodyHydration">
|
|
136
|
+
| null
|
|
137
|
+
| undefined;
|
|
132
138
|
}) {
|
|
133
139
|
return (
|
|
134
140
|
builderBodyHydrationIsTerminalError(
|
|
135
|
-
args.document?.
|
|
141
|
+
args.document?.bodyHydration?.hydration,
|
|
136
142
|
) ||
|
|
137
143
|
builderBodyHydrationIsTerminalError(
|
|
138
144
|
args.item.bodyHydration ??
|
|
@@ -204,6 +204,7 @@ import {
|
|
|
204
204
|
} from "@/hooks/use-document-properties";
|
|
205
205
|
import {
|
|
206
206
|
isDocumentUpdateConflict,
|
|
207
|
+
documentQueryFilter,
|
|
207
208
|
type DocumentUpdateResult,
|
|
208
209
|
useDeleteDocument,
|
|
209
210
|
useDocument,
|
|
@@ -4853,9 +4854,7 @@ function DatabaseItemPreview({
|
|
|
4853
4854
|
void queryClient.invalidateQueries({
|
|
4854
4855
|
queryKey: contentDatabaseQueryKey(databaseDocumentId),
|
|
4855
4856
|
});
|
|
4856
|
-
void queryClient.invalidateQueries(
|
|
4857
|
-
queryKey: ["action", "get-document", { id: document.id }],
|
|
4858
|
-
});
|
|
4857
|
+
void queryClient.invalidateQueries(documentQueryFilter(document.id));
|
|
4859
4858
|
void queryClient.invalidateQueries({
|
|
4860
4859
|
queryKey: ["action", "list-documents"],
|
|
4861
4860
|
});
|
|
@@ -126,6 +126,7 @@ import {
|
|
|
126
126
|
useUpdateDocument,
|
|
127
127
|
buildDocumentTree,
|
|
128
128
|
filterDocumentTreeDocuments,
|
|
129
|
+
documentQueryFilter,
|
|
129
130
|
} from "@/hooks/use-documents";
|
|
130
131
|
import { useLocalStorage } from "@/hooks/use-local-storage";
|
|
131
132
|
import {
|
|
@@ -1191,16 +1192,12 @@ export function DocumentSidebar({
|
|
|
1191
1192
|
created,
|
|
1192
1193
|
);
|
|
1193
1194
|
if (nextId !== id) {
|
|
1194
|
-
queryClient.removeQueries(
|
|
1195
|
-
queryKey: ["action", "get-document", { id }],
|
|
1196
|
-
});
|
|
1195
|
+
queryClient.removeQueries(documentQueryFilter(id));
|
|
1197
1196
|
navigateToDocument(nextId);
|
|
1198
1197
|
}
|
|
1199
1198
|
// Replace optimistic doc with real server doc + clear any 404 error
|
|
1200
1199
|
// state from the in-flight fetch that ran before create completed.
|
|
1201
|
-
queryClient.invalidateQueries(
|
|
1202
|
-
queryKey: ["action", "get-document", { id: nextId }],
|
|
1203
|
-
});
|
|
1200
|
+
queryClient.invalidateQueries(documentQueryFilter(nextId));
|
|
1204
1201
|
queryClient.invalidateQueries({
|
|
1205
1202
|
queryKey: ["action", "list-documents"],
|
|
1206
1203
|
});
|
|
@@ -1214,9 +1211,7 @@ export function DocumentSidebar({
|
|
|
1214
1211
|
queryClient.invalidateQueries({
|
|
1215
1212
|
queryKey: ["action", "list-documents"],
|
|
1216
1213
|
});
|
|
1217
|
-
queryClient.removeQueries(
|
|
1218
|
-
queryKey: ["action", "get-document", { id }],
|
|
1219
|
-
});
|
|
1214
|
+
queryClient.removeQueries(documentQueryFilter(id));
|
|
1220
1215
|
if (rootFilesDatabaseId) {
|
|
1221
1216
|
queryClient.setQueryData<ContentDatabaseResponse>(
|
|
1222
1217
|
contentDatabaseByIdQueryKey(rootFilesDatabaseId),
|
|
@@ -1300,9 +1295,7 @@ export function DocumentSidebar({
|
|
|
1300
1295
|
);
|
|
1301
1296
|
});
|
|
1302
1297
|
for (const deletedId of deletedIds) {
|
|
1303
|
-
queryClient.removeQueries(
|
|
1304
|
-
queryKey: ["action", "get-document", { id: deletedId }],
|
|
1305
|
-
});
|
|
1298
|
+
queryClient.removeQueries(documentQueryFilter(deletedId));
|
|
1306
1299
|
}
|
|
1307
1300
|
|
|
1308
1301
|
if (activeDeleted) {
|