@agent-native/core 0.101.6 → 0.101.10
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 +28 -0
- package/corpus/core/docs/content/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ar-SA/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/de-DE/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/es-ES/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/fr-FR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/hi-IN/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ja-JP/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ko-KR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/pt-BR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/zh-CN/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/zh-TW/deployment.mdx +9 -7
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/artifact-response.ts +255 -12
- package/corpus/core/src/agent/thread-data-builder.ts +84 -11
- package/corpus/core/src/client/review/ReviewCommentComposer.tsx +9 -1
- package/corpus/core/src/client/review/ReviewThreadPanel.tsx +17 -2
- package/corpus/core/src/client/settings/SettingsPanel.tsx +41 -21
- package/corpus/core/src/client/settings/useBuilderStatus.ts +28 -9
- package/corpus/core/src/client/setup-connections/BuilderConnectCard.tsx +2 -7
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +99 -7
- package/corpus/core/src/integrations/adapters/discord.ts +3 -1
- package/corpus/core/src/integrations/adapters/email.ts +4 -1
- package/corpus/core/src/integrations/adapters/google-docs.ts +4 -1
- package/corpus/core/src/integrations/adapters/microsoft-teams.ts +3 -1
- package/corpus/core/src/integrations/adapters/slack.ts +38 -9
- package/corpus/core/src/integrations/adapters/telegram.ts +32 -10
- package/corpus/core/src/integrations/adapters/whatsapp.ts +5 -1
- package/corpus/core/src/integrations/index.ts +1 -0
- package/corpus/core/src/integrations/types.ts +9 -2
- package/corpus/core/src/integrations/webhook-handler.ts +69 -45
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +1 -0
- package/corpus/core/src/server/auth.ts +25 -1
- package/corpus/core/src/server/builder-browser.ts +353 -1
- package/corpus/core/src/server/core-routes-plugin.ts +601 -223
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +80 -1
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +28 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +5 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +30 -3
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +196 -0
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +115 -1
- package/corpus/templates/calendar/app/lib/out-of-office.ts +45 -0
- package/corpus/templates/calendar/changelog/2026-07-14-out-of-office-blocks-now-sit-behind-meetings-and-event-stack.md +6 -0
- package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +35 -21
- package/corpus/templates/content/AGENTS.md +32 -9
- package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +87 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -25
- package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +427 -18
- package/corpus/templates/content/actions/_builder-cms-write-client.ts +17 -73
- package/corpus/templates/content/actions/_builder-cms-write-settings.ts +27 -7
- package/corpus/templates/content/actions/_builder-source-execution-claim.ts +124 -0
- package/corpus/templates/content/actions/_builder-source-execution-preservation.ts +21 -0
- package/corpus/templates/content/actions/_builder-source-timings.ts +57 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +1071 -170
- package/corpus/templates/content/actions/_database-utils.ts +104 -13
- package/corpus/templates/content/actions/_local-file-documents.ts +53 -1
- package/corpus/templates/content/actions/_preview-document-draft.ts +31 -0
- package/corpus/templates/content/actions/_property-utils.ts +7 -1
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +309 -0
- package/corpus/templates/content/actions/configure-document-property.ts +11 -0
- package/corpus/templates/content/actions/create-content-database.ts +11 -0
- package/corpus/templates/content/actions/create-document.ts +9 -0
- package/corpus/templates/content/actions/create-inline-content-database.ts +3 -0
- package/corpus/templates/content/actions/execute-builder-source-batch.ts +120 -49
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +608 -264
- package/corpus/templates/content/actions/export-content-source.ts +1 -0
- package/corpus/templates/content/actions/get-content-database.ts +13 -1
- package/corpus/templates/content/actions/get-document.ts +12 -2
- package/corpus/templates/content/actions/get-preview-document-draft.ts +26 -0
- package/corpus/templates/content/actions/import-content-source.ts +6 -0
- package/corpus/templates/content/actions/list-documents.ts +3 -0
- package/corpus/templates/content/actions/materialize-builder-required-fields.ts +550 -0
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +112 -65
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +463 -170
- package/corpus/templates/content/actions/preview-builder-source-review.ts +99 -0
- package/corpus/templates/content/actions/pull-document.ts +2 -0
- package/corpus/templates/content/actions/search-documents.ts +5 -1
- package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +2 -2
- package/corpus/templates/content/actions/update-document.ts +16 -1
- package/corpus/templates/content/actions/update-preview-document-draft.ts +153 -0
- package/corpus/templates/content/actions/view-screen.ts +15 -2
- package/corpus/templates/content/app/components/editor/DescriptionField.tsx +121 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +14 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +250 -49
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +57 -5
- package/corpus/templates/content/app/components/editor/body-hydration.ts +27 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2414 -589
- package/corpus/templates/content/app/components/editor/database/settings.tsx +13 -10
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +358 -75
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +124 -29
- package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +2 -0
- package/corpus/templates/content/app/global.css +18 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +68 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +53 -4
- package/corpus/templates/content/app/i18n/zh-TW.ts +30 -0
- package/corpus/templates/content/app/i18n-data.ts +339 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-backed-preview-edits-are-preserved-when-article-bodi.md +6 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-cms-sources-can-now-be-enabled-for-guarded-staged-or.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-builder-connection-setup-is-now-available-directly-from-conn.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-builder-reviews-now-hide-already-applied-writes-detect-nativ.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-required-builder-publishing-fields-can-now-be-added-directly.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-you-can-now-cancel-a-prepared-builder-update-safely-before-i.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-account-connection-now-remains-available-when-branch.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-backed-articles-now-preserve-fifth-and-sixth-level-h.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-sources-now-review-and-publish-rich-article-updates-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-writes-now-stop-retries-when-reconciliation-is-needed.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-file-and-media-links-now-validate-before-saving.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-pages-and-databases-can-describe-themselves-with-guidance-fo.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-slack-follow-ups-now-retain-created-content-identity-and-per.md +6 -0
- package/corpus/templates/content/parity/matrix.md +27 -25
- package/corpus/templates/content/parity/matrix.ts +50 -2
- package/corpus/templates/content/server/db/schema.ts +51 -0
- package/corpus/templates/content/server/lib/document-context.ts +76 -0
- package/corpus/templates/content/server/lib/public-documents.ts +3 -0
- package/corpus/templates/content/server/plugins/auth.ts +1 -0
- package/corpus/templates/content/server/plugins/db.ts +59 -0
- package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +5 -0
- package/corpus/templates/content/shared/api.ts +87 -1
- package/corpus/templates/content/shared/builder-mdx.ts +705 -15
- package/corpus/templates/content/shared/content-source.ts +7 -0
- package/corpus/templates/content/shared/nfm.ts +3 -3
- package/corpus/templates/content/shared/properties.ts +2 -0
- package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +52 -7
- package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +36 -4
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
- package/corpus/templates/design/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/design/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/design/app/i18n/en-US.ts +1 -0
- package/corpus/templates/design/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/design/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/design/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/design/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/design/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/design/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/design/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +11 -0
- package/corpus/templates/design/changelog/2026-07-14-reviewers-can-now-pin-comments-from-the-canvas-toolbar-targe.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-viewer-comment-controls-now-sit-inside-the-read-only-notice-.md +6 -0
- package/corpus/templates/design/shared/review-anchor.ts +48 -0
- package/corpus/templates/plan/server/plan-mdx.ts +53 -7
- package/dist/a2a/artifact-response.d.ts +15 -0
- package/dist/a2a/artifact-response.d.ts.map +1 -1
- package/dist/a2a/artifact-response.js +204 -5
- package/dist/a2a/artifact-response.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +6 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +71 -8
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/client/review/ReviewCommentComposer.d.ts +2 -1
- package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
- package/dist/client/review/ReviewCommentComposer.js +3 -3
- package/dist/client/review/ReviewCommentComposer.js.map +1 -1
- package/dist/client/review/ReviewThreadPanel.d.ts +9 -1
- package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
- package/dist/client/review/ReviewThreadPanel.js +6 -4
- package/dist/client/review/ReviewThreadPanel.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts +3 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +11 -4
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +8 -4
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +19 -6
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/setup-connections/BuilderConnectCard.d.ts.map +1 -1
- package/dist/client/setup-connections/BuilderConnectCard.js +2 -5
- package/dist/client/setup-connections/BuilderConnectCard.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +72 -6
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/integrations/adapters/discord.d.ts.map +1 -1
- package/dist/integrations/adapters/discord.js +1 -0
- package/dist/integrations/adapters/discord.js.map +1 -1
- package/dist/integrations/adapters/email.d.ts.map +1 -1
- package/dist/integrations/adapters/email.js +2 -0
- package/dist/integrations/adapters/email.js.map +1 -1
- package/dist/integrations/adapters/google-docs.d.ts.map +1 -1
- package/dist/integrations/adapters/google-docs.js +2 -0
- package/dist/integrations/adapters/google-docs.js.map +1 -1
- package/dist/integrations/adapters/microsoft-teams.d.ts.map +1 -1
- package/dist/integrations/adapters/microsoft-teams.js +1 -0
- package/dist/integrations/adapters/microsoft-teams.js.map +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +21 -6
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/adapters/telegram.d.ts.map +1 -1
- package/dist/integrations/adapters/telegram.js +11 -2
- package/dist/integrations/adapters/telegram.js.map +1 -1
- package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
- package/dist/integrations/adapters/whatsapp.js +3 -0
- package/dist/integrations/adapters/whatsapp.js.map +1 -1
- package/dist/integrations/index.d.ts +1 -1
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/types.d.ts +8 -2
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +40 -43
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +1 -0
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/auth.d.ts +1 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +19 -2
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-browser.d.ts +78 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +246 -1
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +41 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +260 -14
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/docs/content/deployment.mdx +9 -7
- package/docs/content/locales/ar-SA/deployment.mdx +9 -7
- package/docs/content/locales/de-DE/deployment.mdx +9 -7
- package/docs/content/locales/es-ES/deployment.mdx +9 -7
- package/docs/content/locales/fr-FR/deployment.mdx +9 -7
- package/docs/content/locales/hi-IN/deployment.mdx +9 -7
- package/docs/content/locales/ja-JP/deployment.mdx +9 -7
- package/docs/content/locales/ko-KR/deployment.mdx +9 -7
- package/docs/content/locales/pt-BR/deployment.mdx +9 -7
- package/docs/content/locales/zh-CN/deployment.mdx +9 -7
- package/docs/content/locales/zh-TW/deployment.mdx +9 -7
- package/package.json +2 -2
|
@@ -105,6 +105,12 @@ import {
|
|
|
105
105
|
PopoverTrigger,
|
|
106
106
|
} from "@/components/ui/popover";
|
|
107
107
|
import { Spinner } from "@/components/ui/spinner";
|
|
108
|
+
import { Textarea } from "@/components/ui/textarea";
|
|
109
|
+
import {
|
|
110
|
+
Tooltip,
|
|
111
|
+
TooltipContent,
|
|
112
|
+
TooltipTrigger,
|
|
113
|
+
} from "@/components/ui/tooltip";
|
|
108
114
|
import { applySourceFieldPropertyToDatabaseResponse } from "@/hooks/use-content-database";
|
|
109
115
|
import {
|
|
110
116
|
useConfigureDocumentProperty,
|
|
@@ -483,6 +489,24 @@ export function filesMediaEditorValue(value: DocumentProperty["value"]) {
|
|
|
483
489
|
return filesMediaItems(value).join("\n");
|
|
484
490
|
}
|
|
485
491
|
|
|
492
|
+
export function isValidFilesMediaLink(value: string) {
|
|
493
|
+
try {
|
|
494
|
+
const url = new URL(value.trim());
|
|
495
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
496
|
+
} catch {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export function mergeFilesMediaItems(items: string[], pendingLink: string) {
|
|
502
|
+
const trimmed = pendingLink.trim();
|
|
503
|
+
if (!trimmed || !isValidFilesMediaLink(trimmed)) return items;
|
|
504
|
+
if (items.some((item) => item.toLowerCase() === trimmed.toLowerCase())) {
|
|
505
|
+
return items;
|
|
506
|
+
}
|
|
507
|
+
return [...items, trimmed];
|
|
508
|
+
}
|
|
509
|
+
|
|
486
510
|
export function filesMediaLabel(value: string) {
|
|
487
511
|
const trimmed = value.trim();
|
|
488
512
|
if (!trimmed) return "File";
|
|
@@ -551,7 +575,8 @@ export function filterPropertyOptions(
|
|
|
551
575
|
return options.filter(
|
|
552
576
|
(option) =>
|
|
553
577
|
option.name.toLowerCase().includes(normalizedQuery) ||
|
|
554
|
-
option.id.toLowerCase().includes(normalizedQuery)
|
|
578
|
+
option.id.toLowerCase().includes(normalizedQuery) ||
|
|
579
|
+
option.description?.toLowerCase().includes(normalizedQuery),
|
|
555
580
|
);
|
|
556
581
|
}
|
|
557
582
|
|
|
@@ -612,6 +637,76 @@ export function updatePropertyOptionColor(
|
|
|
612
637
|
);
|
|
613
638
|
}
|
|
614
639
|
|
|
640
|
+
export function updatePropertyOptionDescription(
|
|
641
|
+
options: DocumentPropertyOption[],
|
|
642
|
+
optionId: string,
|
|
643
|
+
description: string,
|
|
644
|
+
) {
|
|
645
|
+
return options.map((option) =>
|
|
646
|
+
option.id === optionId ? { ...option, description } : option,
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Keeps successive option edits based on the same local truth until the
|
|
652
|
+
* server catches up. A rename followed immediately by a usage-description
|
|
653
|
+
* edit must not let either request erase the other.
|
|
654
|
+
*/
|
|
655
|
+
export function createPropertyOptionUpdateQueue(
|
|
656
|
+
initialOptions: DocumentPropertyOption[],
|
|
657
|
+
persist: (options: DocumentPropertyOption[]) => Promise<unknown>,
|
|
658
|
+
) {
|
|
659
|
+
let current = initialOptions;
|
|
660
|
+
let tail: Promise<unknown> = Promise.resolve();
|
|
661
|
+
|
|
662
|
+
return {
|
|
663
|
+
replace(options: DocumentPropertyOption[]) {
|
|
664
|
+
current = options;
|
|
665
|
+
},
|
|
666
|
+
enqueue(
|
|
667
|
+
update: (options: DocumentPropertyOption[]) => DocumentPropertyOption[],
|
|
668
|
+
) {
|
|
669
|
+
current = update(current);
|
|
670
|
+
const snapshot = current;
|
|
671
|
+
tail = tail.catch(() => undefined).then(() => persist(snapshot));
|
|
672
|
+
return tail;
|
|
673
|
+
},
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
type PropertyMetadataSnapshot = Pick<
|
|
678
|
+
DocumentProperty["definition"],
|
|
679
|
+
"name" | "type" | "description" | "visibility" | "options"
|
|
680
|
+
>;
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Serializes property-definition edits against one local snapshot. The action
|
|
684
|
+
* accepts the complete definition, so composing each request from render-time
|
|
685
|
+
* props would let a fast description save restore the name from before an
|
|
686
|
+
* overlapping rename completed.
|
|
687
|
+
*/
|
|
688
|
+
export function createPropertyMetadataUpdateQueue(
|
|
689
|
+
initialMetadata: PropertyMetadataSnapshot,
|
|
690
|
+
persist: (metadata: PropertyMetadataSnapshot) => Promise<unknown>,
|
|
691
|
+
) {
|
|
692
|
+
let current = initialMetadata;
|
|
693
|
+
let tail: Promise<unknown> = Promise.resolve();
|
|
694
|
+
|
|
695
|
+
return {
|
|
696
|
+
replace(metadata: PropertyMetadataSnapshot) {
|
|
697
|
+
current = metadata;
|
|
698
|
+
},
|
|
699
|
+
enqueue(
|
|
700
|
+
update: (metadata: PropertyMetadataSnapshot) => PropertyMetadataSnapshot,
|
|
701
|
+
) {
|
|
702
|
+
current = update(current);
|
|
703
|
+
const snapshot = current;
|
|
704
|
+
tail = tail.catch(() => undefined).then(() => persist(snapshot));
|
|
705
|
+
return tail;
|
|
706
|
+
},
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
615
710
|
export function removePropertyOption(
|
|
616
711
|
options: DocumentPropertyOption[],
|
|
617
712
|
optionId: string,
|
|
@@ -833,7 +928,23 @@ function PropertyRow({
|
|
|
833
928
|
) : (
|
|
834
929
|
<div className="flex min-w-0 items-center gap-2 text-muted-foreground">
|
|
835
930
|
<Icon className="size-4 shrink-0" />
|
|
836
|
-
|
|
931
|
+
{property.definition.description ? (
|
|
932
|
+
<Tooltip>
|
|
933
|
+
<TooltipTrigger asChild>
|
|
934
|
+
<button
|
|
935
|
+
type="button"
|
|
936
|
+
className="truncate text-left outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
937
|
+
>
|
|
938
|
+
{property.definition.name}
|
|
939
|
+
</button>
|
|
940
|
+
</TooltipTrigger>
|
|
941
|
+
<TooltipContent className="max-w-64">
|
|
942
|
+
{property.definition.description}
|
|
943
|
+
</TooltipContent>
|
|
944
|
+
</Tooltip>
|
|
945
|
+
) : (
|
|
946
|
+
<span className="truncate">{property.definition.name}</span>
|
|
947
|
+
)}
|
|
837
948
|
</div>
|
|
838
949
|
)}
|
|
839
950
|
{canEdit && property.editable ? (
|
|
@@ -961,7 +1072,25 @@ export function PropertyManagementPopover({
|
|
|
961
1072
|
const [open, setOpen] = useState(false);
|
|
962
1073
|
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
|
963
1074
|
const [name, setName] = useState(property.definition.name);
|
|
1075
|
+
const [description, setDescription] = useState(
|
|
1076
|
+
property.definition.description,
|
|
1077
|
+
);
|
|
964
1078
|
const [newOption, setNewOption] = useState("");
|
|
1079
|
+
const persistMetadataSnapshotRef = useRef<
|
|
1080
|
+
(metadata: PropertyMetadataSnapshot) => Promise<unknown>
|
|
1081
|
+
>(async () => undefined);
|
|
1082
|
+
const metadataUpdateQueueRef = useRef(
|
|
1083
|
+
createPropertyMetadataUpdateQueue(
|
|
1084
|
+
{
|
|
1085
|
+
name: property.definition.name,
|
|
1086
|
+
type: property.definition.type,
|
|
1087
|
+
description: property.definition.description,
|
|
1088
|
+
visibility: property.definition.visibility,
|
|
1089
|
+
options: property.definition.options,
|
|
1090
|
+
},
|
|
1091
|
+
(metadata) => persistMetadataSnapshotRef.current(metadata),
|
|
1092
|
+
),
|
|
1093
|
+
);
|
|
965
1094
|
const propertyNameInputRef = useRef<HTMLInputElement>(null);
|
|
966
1095
|
const typeIsLocked = isComputedPropertyType(property.definition.type);
|
|
967
1096
|
const typeNeedsOptions =
|
|
@@ -971,7 +1100,15 @@ export function PropertyManagementPopover({
|
|
|
971
1100
|
|
|
972
1101
|
function resetDraft() {
|
|
973
1102
|
setName(property.definition.name);
|
|
1103
|
+
setDescription(property.definition.description);
|
|
974
1104
|
setNewOption("");
|
|
1105
|
+
metadataUpdateQueueRef.current.replace({
|
|
1106
|
+
name: property.definition.name,
|
|
1107
|
+
type: property.definition.type,
|
|
1108
|
+
description: property.definition.description,
|
|
1109
|
+
visibility: property.definition.visibility,
|
|
1110
|
+
options: property.definition.options,
|
|
1111
|
+
});
|
|
975
1112
|
}
|
|
976
1113
|
|
|
977
1114
|
useEffect(() => {
|
|
@@ -990,17 +1127,34 @@ export function PropertyManagementPopover({
|
|
|
990
1127
|
type?: DocumentPropertyType;
|
|
991
1128
|
visibility?: DocumentPropertyVisibility;
|
|
992
1129
|
options?: DocumentProperty["definition"]["options"];
|
|
1130
|
+
description?: string;
|
|
993
1131
|
}) {
|
|
994
|
-
|
|
995
|
-
|
|
1132
|
+
await metadataUpdateQueueRef.current.enqueue((current) => ({
|
|
1133
|
+
name: next.name?.trim() || current.name,
|
|
1134
|
+
type: next.type ?? current.type,
|
|
1135
|
+
description: next.description ?? current.description,
|
|
1136
|
+
visibility: next.visibility ?? current.visibility,
|
|
1137
|
+
options: next.options ?? current.options,
|
|
1138
|
+
}));
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
async function updateOptions(
|
|
1142
|
+
update: (options: DocumentPropertyOption[]) => DocumentPropertyOption[],
|
|
1143
|
+
) {
|
|
1144
|
+
await metadataUpdateQueueRef.current.enqueue((current) => ({
|
|
1145
|
+
...current,
|
|
1146
|
+
options: {
|
|
1147
|
+
options: update(current.options.options ?? []),
|
|
1148
|
+
},
|
|
1149
|
+
}));
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
persistMetadataSnapshotRef.current = (metadata) =>
|
|
1153
|
+
configure.mutateAsync({
|
|
996
1154
|
id: property.definition.id,
|
|
997
1155
|
documentId,
|
|
998
|
-
|
|
999
|
-
type: nextType,
|
|
1000
|
-
visibility: next.visibility,
|
|
1001
|
-
options: next.options ?? property.definition.options,
|
|
1156
|
+
...metadata,
|
|
1002
1157
|
});
|
|
1003
|
-
}
|
|
1004
1158
|
|
|
1005
1159
|
async function renameProperty() {
|
|
1006
1160
|
const nextName = name.trim();
|
|
@@ -1008,6 +1162,12 @@ export function PropertyManagementPopover({
|
|
|
1008
1162
|
await configureProperty({ name: nextName });
|
|
1009
1163
|
}
|
|
1010
1164
|
|
|
1165
|
+
async function updateDescription() {
|
|
1166
|
+
const nextDescription = (description ?? "").trim();
|
|
1167
|
+
if (nextDescription === property.definition.description) return;
|
|
1168
|
+
await configureProperty({ description: nextDescription });
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1011
1171
|
async function updateType(nextType: DocumentPropertyType) {
|
|
1012
1172
|
if (nextType === property.definition.type) return;
|
|
1013
1173
|
await configureProperty({
|
|
@@ -1042,39 +1202,39 @@ export function PropertyManagementPopover({
|
|
|
1042
1202
|
async function addOption() {
|
|
1043
1203
|
const optionName = newOption.trim();
|
|
1044
1204
|
if (!optionName) return;
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
});
|
|
1205
|
+
await updateOptions((existing) => [
|
|
1206
|
+
...existing,
|
|
1207
|
+
makeOption(
|
|
1208
|
+
optionName,
|
|
1209
|
+
existing.length,
|
|
1210
|
+
existing.map((item) => item.id),
|
|
1211
|
+
),
|
|
1212
|
+
]);
|
|
1054
1213
|
setNewOption("");
|
|
1055
1214
|
}
|
|
1056
1215
|
|
|
1057
1216
|
async function removeOption(id: string) {
|
|
1058
|
-
await
|
|
1059
|
-
options
|
|
1060
|
-
|
|
1061
|
-
(option) => option.id !== id,
|
|
1062
|
-
),
|
|
1063
|
-
},
|
|
1064
|
-
});
|
|
1217
|
+
await updateOptions((options) =>
|
|
1218
|
+
options.filter((option) => option.id !== id),
|
|
1219
|
+
);
|
|
1065
1220
|
}
|
|
1066
1221
|
|
|
1067
1222
|
async function renameOption(id: string, optionName: string) {
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
await configureProperty({ options: { options: nextOptions } });
|
|
1223
|
+
await updateOptions((options) =>
|
|
1224
|
+
renamePropertyOption(options, id, optionName),
|
|
1225
|
+
);
|
|
1072
1226
|
}
|
|
1073
1227
|
|
|
1074
1228
|
async function recolorOption(id: string, color: DocumentPropertyOptionColor) {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1229
|
+
await updateOptions((options) =>
|
|
1230
|
+
updatePropertyOptionColor(options, id, color),
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
async function describeOption(id: string, description: string) {
|
|
1235
|
+
await updateOptions((options) =>
|
|
1236
|
+
updatePropertyOptionDescription(options, id, description),
|
|
1237
|
+
);
|
|
1078
1238
|
}
|
|
1079
1239
|
|
|
1080
1240
|
return (
|
|
@@ -1092,6 +1252,7 @@ export function PropertyManagementPopover({
|
|
|
1092
1252
|
aria-label={t("editor.properties.propertyMenuFor", {
|
|
1093
1253
|
name: property.definition.name,
|
|
1094
1254
|
})}
|
|
1255
|
+
title={property.definition.description || undefined}
|
|
1095
1256
|
className={cn(
|
|
1096
1257
|
"flex min-w-0 items-center gap-2 rounded px-1 py-0.5 text-left text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1097
1258
|
triggerClassName,
|
|
@@ -1138,6 +1299,21 @@ export function PropertyManagementPopover({
|
|
|
1138
1299
|
/>
|
|
1139
1300
|
</div>
|
|
1140
1301
|
|
|
1302
|
+
<div
|
|
1303
|
+
className="px-2 pb-2 pt-1"
|
|
1304
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
1305
|
+
>
|
|
1306
|
+
<Textarea
|
|
1307
|
+
rows={2}
|
|
1308
|
+
value={description}
|
|
1309
|
+
aria-label={t("editor.properties.description")}
|
|
1310
|
+
placeholder={t("editor.properties.addPropertyDescription")}
|
|
1311
|
+
onChange={(event) => setDescription(event.target.value)}
|
|
1312
|
+
onBlur={() => void updateDescription()}
|
|
1313
|
+
className="block w-full resize-none rounded border border-transparent bg-muted/30 px-2 py-1.5 text-xs leading-5 text-muted-foreground outline-none placeholder:text-muted-foreground/60 focus:border-input focus:bg-background focus:ring-1 focus:ring-ring"
|
|
1314
|
+
/>
|
|
1315
|
+
</div>
|
|
1316
|
+
|
|
1141
1317
|
<DropdownMenuSub>
|
|
1142
1318
|
<DropdownMenuSubTrigger>
|
|
1143
1319
|
<Icon className="mr-2 size-4 text-muted-foreground" />
|
|
@@ -1217,6 +1393,9 @@ export function PropertyManagementPopover({
|
|
|
1217
1393
|
option={option}
|
|
1218
1394
|
disabled={configure.isPending}
|
|
1219
1395
|
onRename={(name) => void renameOption(option.id, name)}
|
|
1396
|
+
onDescriptionChange={(description) =>
|
|
1397
|
+
void describeOption(option.id, description)
|
|
1398
|
+
}
|
|
1220
1399
|
onColorChange={(color) =>
|
|
1221
1400
|
void recolorOption(option.id, color)
|
|
1222
1401
|
}
|
|
@@ -1424,21 +1603,27 @@ function PropertyOptionSettingsRow({
|
|
|
1424
1603
|
option,
|
|
1425
1604
|
disabled,
|
|
1426
1605
|
onRename,
|
|
1606
|
+
onDescriptionChange,
|
|
1427
1607
|
onColorChange,
|
|
1428
1608
|
onRemove,
|
|
1429
1609
|
}: {
|
|
1430
1610
|
option: DocumentPropertyOption;
|
|
1431
1611
|
disabled: boolean;
|
|
1432
1612
|
onRename: (name: string) => void;
|
|
1613
|
+
onDescriptionChange: (description: string) => void;
|
|
1433
1614
|
onColorChange: (color: DocumentPropertyOptionColor) => void;
|
|
1434
1615
|
onRemove: () => void;
|
|
1435
1616
|
}) {
|
|
1436
1617
|
const t = useT();
|
|
1437
1618
|
const [draftName, setDraftName] = useState(option.name);
|
|
1619
|
+
const [draftDescription, setDraftDescription] = useState(
|
|
1620
|
+
option.description ?? "",
|
|
1621
|
+
);
|
|
1438
1622
|
|
|
1439
1623
|
useEffect(() => {
|
|
1440
1624
|
setDraftName(option.name);
|
|
1441
|
-
|
|
1625
|
+
setDraftDescription(option.description ?? "");
|
|
1626
|
+
}, [option.description, option.name]);
|
|
1442
1627
|
|
|
1443
1628
|
function submitRename() {
|
|
1444
1629
|
const nextName = draftName.trim();
|
|
@@ -1528,6 +1713,21 @@ function PropertyOptionSettingsRow({
|
|
|
1528
1713
|
{t("editor.properties.remove")}
|
|
1529
1714
|
</button>
|
|
1530
1715
|
</div>
|
|
1716
|
+
<Textarea
|
|
1717
|
+
rows={1}
|
|
1718
|
+
value={draftDescription}
|
|
1719
|
+
disabled={disabled}
|
|
1720
|
+
aria-label={`${t("editor.properties.description")}: ${option.name}`}
|
|
1721
|
+
placeholder={t("editor.properties.addOptionDescription")}
|
|
1722
|
+
onChange={(event) => setDraftDescription(event.target.value)}
|
|
1723
|
+
onBlur={() => {
|
|
1724
|
+
const nextDescription = draftDescription.trim();
|
|
1725
|
+
if (nextDescription !== (option.description ?? "")) {
|
|
1726
|
+
onDescriptionChange(nextDescription);
|
|
1727
|
+
}
|
|
1728
|
+
}}
|
|
1729
|
+
className="ml-5 block w-[calc(100%-1.25rem)] resize-none rounded border-0 bg-transparent px-1 text-xs leading-5 text-muted-foreground shadow-none placeholder:text-muted-foreground/60 focus:bg-background focus:ring-1 focus:ring-ring"
|
|
1730
|
+
/>
|
|
1531
1731
|
</div>
|
|
1532
1732
|
);
|
|
1533
1733
|
}
|
|
@@ -1878,16 +2078,8 @@ function FilesMediaValueEditor({
|
|
|
1878
2078
|
}, []);
|
|
1879
2079
|
|
|
1880
2080
|
function addItem(value: string) {
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
setItems((current) => {
|
|
1884
|
-
if (
|
|
1885
|
-
current.some((item) => item.toLowerCase() === trimmed.toLowerCase())
|
|
1886
|
-
) {
|
|
1887
|
-
return current;
|
|
1888
|
-
}
|
|
1889
|
-
return [...current, trimmed];
|
|
1890
|
-
});
|
|
2081
|
+
if (!isValidFilesMediaLink(value)) return;
|
|
2082
|
+
setItems((current) => mergeFilesMediaItems(current, value));
|
|
1891
2083
|
setLinkValue("");
|
|
1892
2084
|
}
|
|
1893
2085
|
|
|
@@ -1944,11 +2136,11 @@ function FilesMediaValueEditor({
|
|
|
1944
2136
|
className="grid gap-3"
|
|
1945
2137
|
onSubmit={(event) => {
|
|
1946
2138
|
event.preventDefault();
|
|
1947
|
-
if (linkValue.trim()) {
|
|
1948
|
-
|
|
2139
|
+
if (linkValue.trim() && !isValidFilesMediaLink(linkValue)) {
|
|
2140
|
+
linkInputRef.current?.reportValidity();
|
|
1949
2141
|
return;
|
|
1950
2142
|
}
|
|
1951
|
-
void save();
|
|
2143
|
+
void save(mergeFilesMediaItems(items, linkValue));
|
|
1952
2144
|
}}
|
|
1953
2145
|
>
|
|
1954
2146
|
<div className="grid max-h-48 gap-1 overflow-auto">
|
|
@@ -1992,9 +2184,11 @@ function FilesMediaValueEditor({
|
|
|
1992
2184
|
<div className="flex gap-1">
|
|
1993
2185
|
<Input
|
|
1994
2186
|
ref={linkInputRef}
|
|
1995
|
-
aria-label={t("editor.properties.
|
|
2187
|
+
aria-label={t("editor.properties.editValue", {
|
|
1996
2188
|
name: property.definition.name,
|
|
1997
2189
|
})}
|
|
2190
|
+
type="url"
|
|
2191
|
+
pattern="[hH][tT][tT][pP][sS]?://.*"
|
|
1998
2192
|
value={linkValue}
|
|
1999
2193
|
placeholder={t("editor.properties.pasteFileOrMediaLink")}
|
|
2000
2194
|
onChange={(event) => setLinkValue(event.target.value)}
|
|
@@ -2011,7 +2205,7 @@ function FilesMediaValueEditor({
|
|
|
2011
2205
|
size="sm"
|
|
2012
2206
|
className="shrink-0"
|
|
2013
2207
|
onClick={() => addItem(linkValue)}
|
|
2014
|
-
disabled={!linkValue
|
|
2208
|
+
disabled={!isValidFilesMediaLink(linkValue) || mutation.isPending}
|
|
2015
2209
|
>
|
|
2016
2210
|
<IconPlus className="size-3.5" />
|
|
2017
2211
|
{t("editor.properties.add")}
|
|
@@ -2559,7 +2753,14 @@ function OptionValueEditor({
|
|
|
2559
2753
|
className="flex w-full items-center justify-between gap-2 rounded px-2 py-1.5 text-left text-sm hover:bg-accent"
|
|
2560
2754
|
onClick={() => void chooseOption(option)}
|
|
2561
2755
|
>
|
|
2562
|
-
<
|
|
2756
|
+
<span className="min-w-0 flex-1">
|
|
2757
|
+
<OptionPill option={option} />
|
|
2758
|
+
{option.description ? (
|
|
2759
|
+
<span className="mt-0.5 block truncate text-xs text-muted-foreground">
|
|
2760
|
+
{option.description}
|
|
2761
|
+
</span>
|
|
2762
|
+
) : null}
|
|
2763
|
+
</span>
|
|
2563
2764
|
{checked ? (
|
|
2564
2765
|
<IconCheck className="size-4 text-muted-foreground" />
|
|
2565
2766
|
) : null}
|
|
@@ -924,6 +924,28 @@ export function shouldPersistLocalFileEditorUpdate({
|
|
|
924
924
|
return Boolean(transactionUiEvent);
|
|
925
925
|
}
|
|
926
926
|
|
|
927
|
+
function isEffectivelyEmptyEditorContent(value: string): boolean {
|
|
928
|
+
const normalized = value.trim();
|
|
929
|
+
return normalized === "" || normalized === "<empty-block/>";
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
export function shouldPersistEffectivelyEmptyEditorUpdate({
|
|
933
|
+
nextContent,
|
|
934
|
+
userInitiated,
|
|
935
|
+
}: {
|
|
936
|
+
nextContent: string;
|
|
937
|
+
userInitiated: boolean;
|
|
938
|
+
}): boolean {
|
|
939
|
+
if (!isEffectivelyEmptyEditorContent(nextContent)) return true;
|
|
940
|
+
// Empty editor state is never worth persisting without a user gesture. This
|
|
941
|
+
// also covers the preview remount window where `content` can briefly be an
|
|
942
|
+
// empty list snapshot even though its retained save controller still has a
|
|
943
|
+
// rich confirmed baseline. Comparing only to this render's prop would let
|
|
944
|
+
// that mount-time filler mark the retained controller dirty and its
|
|
945
|
+
// flush-on-release path would then overwrite SQL.
|
|
946
|
+
return userInitiated;
|
|
947
|
+
}
|
|
948
|
+
|
|
927
949
|
function isActiveSlashCommandDraft(editor: CoreEditor): boolean {
|
|
928
950
|
const { state } = editor;
|
|
929
951
|
if (!state.selection.empty) return false;
|
|
@@ -1086,7 +1108,9 @@ function getVisualEditorPlaceholder({
|
|
|
1086
1108
|
if (level === 1) return "Heading 1";
|
|
1087
1109
|
if (level === 2) return "Heading 2";
|
|
1088
1110
|
if (level === 3) return "Heading 3";
|
|
1089
|
-
return "Heading 4";
|
|
1111
|
+
if (level === 4) return "Heading 4";
|
|
1112
|
+
if (level === 5) return "Heading 5";
|
|
1113
|
+
return "Heading 6";
|
|
1090
1114
|
}
|
|
1091
1115
|
|
|
1092
1116
|
if (
|
|
@@ -1300,6 +1324,7 @@ export function createVisualEditorExtensions({
|
|
|
1300
1324
|
starterKit: {
|
|
1301
1325
|
blockquote: false,
|
|
1302
1326
|
paragraph: false,
|
|
1327
|
+
heading: { levels: [1, 2, 3, 4, 5, 6] },
|
|
1303
1328
|
horizontalRule: {},
|
|
1304
1329
|
dropcursor: { color: false, width: 3, class: "notion-dropcursor" },
|
|
1305
1330
|
},
|
|
@@ -1742,7 +1767,11 @@ export function VisualEditor({
|
|
|
1742
1767
|
const persistEditorContent = useCallback(
|
|
1743
1768
|
(
|
|
1744
1769
|
editorToPersist: CoreEditor,
|
|
1745
|
-
options?: {
|
|
1770
|
+
options?: {
|
|
1771
|
+
markdown?: string;
|
|
1772
|
+
immediate?: boolean;
|
|
1773
|
+
userInitiated?: boolean;
|
|
1774
|
+
},
|
|
1746
1775
|
) => {
|
|
1747
1776
|
const guards = guardsRef.current;
|
|
1748
1777
|
if (!guards) return false;
|
|
@@ -1750,6 +1779,20 @@ export function VisualEditor({
|
|
|
1750
1779
|
const normalized =
|
|
1751
1780
|
options?.markdown ?? docToNfm(editorToPersist.getJSON() as any);
|
|
1752
1781
|
if (localFileMode && normalized === content) return true;
|
|
1782
|
+
// TipTap/Yjs can emit a local-looking empty-paragraph transaction while
|
|
1783
|
+
// an editor is mounting or reconciling. Content serializes that filler
|
|
1784
|
+
// as `<empty-block/>`, so the generic whitespace-only collab guard does
|
|
1785
|
+
// not catch it. Never let that lifecycle normalization clear a saved
|
|
1786
|
+
// body. A real Select All/Delete (or Cut) records user intent through
|
|
1787
|
+
// the DOM handlers below and is still allowed to persist normally.
|
|
1788
|
+
if (
|
|
1789
|
+
!shouldPersistEffectivelyEmptyEditorUpdate({
|
|
1790
|
+
nextContent: normalized,
|
|
1791
|
+
userInitiated: options?.userInitiated === true,
|
|
1792
|
+
})
|
|
1793
|
+
) {
|
|
1794
|
+
return true;
|
|
1795
|
+
}
|
|
1753
1796
|
if (options?.immediate && onSaveContentRef.current) {
|
|
1754
1797
|
return onSaveContentRef.current(normalized);
|
|
1755
1798
|
}
|
|
@@ -1923,7 +1966,12 @@ export function VisualEditor({
|
|
|
1923
1966
|
return;
|
|
1924
1967
|
}
|
|
1925
1968
|
if (isActiveSlashCommandDraft(editor)) return;
|
|
1926
|
-
persistEditorContent(editor
|
|
1969
|
+
persistEditorContent(editor, {
|
|
1970
|
+
userInitiated:
|
|
1971
|
+
transaction.getMeta(LOCAL_FILE_USER_EDIT_META) === true ||
|
|
1972
|
+
Date.now() - lastUserEditIntentAtRef.current < 2000 ||
|
|
1973
|
+
Boolean(transaction.getMeta("uiEvent")),
|
|
1974
|
+
});
|
|
1927
1975
|
},
|
|
1928
1976
|
});
|
|
1929
1977
|
|
|
@@ -2239,10 +2287,14 @@ export function VisualEditor({
|
|
|
2239
2287
|
documentId={documentId}
|
|
2240
2288
|
notionPageId={notionPageId}
|
|
2241
2289
|
onDraftCommitted={() => {
|
|
2242
|
-
void persistEditorContent(editor);
|
|
2290
|
+
void persistEditorContent(editor, { userInitiated: true });
|
|
2243
2291
|
}}
|
|
2244
2292
|
onDraftPersisted={(markdown) =>
|
|
2245
|
-
persistEditorContent(editor, {
|
|
2293
|
+
persistEditorContent(editor, {
|
|
2294
|
+
markdown,
|
|
2295
|
+
immediate: true,
|
|
2296
|
+
userInitiated: true,
|
|
2297
|
+
})
|
|
2246
2298
|
}
|
|
2247
2299
|
/>
|
|
2248
2300
|
) : null}
|
|
@@ -89,6 +89,33 @@ export function previewBodyHydrationIsPending(args: {
|
|
|
89
89
|
);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* A draft that began from an empty source snapshot must not automatically
|
|
94
|
+
* replace a non-empty Builder body that arrived while hydration was running.
|
|
95
|
+
* Keep the draft dirty/recoverable and let the user decide which body to keep.
|
|
96
|
+
*/
|
|
97
|
+
export function previewDraftConflictsWithHydratedBody(args: {
|
|
98
|
+
loadedContent: string | null | undefined;
|
|
99
|
+
loadedUpdatedAt: string | null | undefined;
|
|
100
|
+
loadedContentWasEmpty: boolean | undefined;
|
|
101
|
+
pendingContent: string | null | undefined;
|
|
102
|
+
hydratedContent: string | null | undefined;
|
|
103
|
+
hydratedUpdatedAt: string | null | undefined;
|
|
104
|
+
}) {
|
|
105
|
+
const sourceChangedSinceDraftLoaded =
|
|
106
|
+
(!!args.loadedUpdatedAt &&
|
|
107
|
+
!!args.hydratedUpdatedAt &&
|
|
108
|
+
args.loadedUpdatedAt !== args.hydratedUpdatedAt) ||
|
|
109
|
+
args.loadedContent !== args.hydratedContent;
|
|
110
|
+
return (
|
|
111
|
+
!isEffectivelyEmptyDocumentContent(args.pendingContent) &&
|
|
112
|
+
args.pendingContent !== args.hydratedContent &&
|
|
113
|
+
sourceChangedSinceDraftLoaded &&
|
|
114
|
+
(args.loadedContentWasEmpty === true ||
|
|
115
|
+
args.loadedContent !== args.hydratedContent)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
92
119
|
export function previewBodyHydrationIsTerminalError(args: {
|
|
93
120
|
item: Pick<ContentDatabaseItem, "bodyHydration" | "document">;
|
|
94
121
|
document: Pick<Document, "databaseMembership"> | null | undefined;
|