@drawnagency/primitives 0.1.117 → 0.1.119
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/dist/{chunk-O37AV27P.js → chunk-5JYHTHVR.js} +28 -0
- package/dist/{chunk-W72BPGR3.js → chunk-BZKHHUIN.js} +44 -8
- package/dist/{chunk-CEFFP3CH.js → chunk-G6O3TYPJ.js} +297 -83
- package/dist/{chunk-L7WTERU5.js → chunk-HFHM4MNK.js} +33 -10
- package/dist/{chunk-RIITGTV3.js → chunk-LQHBC7C6.js} +37 -2
- package/dist/{chunk-VY3IFGE3.js → chunk-QAMAIDXZ.js} +1 -1
- package/dist/{chunk-LNQUUC2S.js → chunk-RIN2QCYJ.js} +9 -11
- package/dist/{chunk-HUOW3ZV2.js → chunk-UNFPATDE.js} +8 -10
- package/dist/{chunk-MSBHMY3C.js → chunk-YKKGVSJH.js} +35 -37
- package/dist/{chunk-67P34WNW.js → chunk-ZKVQMIMJ.js} +3 -20
- package/dist/collab/rebase.js +4 -5
- package/dist/components/chat/ChatComposer.d.ts +0 -4
- package/dist/components/chat/ChatComposer.d.ts.map +1 -1
- package/dist/components/chat/ChatPanel.d.ts +2 -3
- package/dist/components/chat/ChatPanel.d.ts.map +1 -1
- package/dist/components/chat/markdown.d.ts +2 -3
- package/dist/components/chat/markdown.d.ts.map +1 -1
- package/dist/components/editor/PagesModal.d.ts +0 -2
- package/dist/components/editor/PagesModal.d.ts.map +1 -1
- package/dist/components/editor/index.js +15 -19
- package/dist/components/primitives/EditablePlainText.d.ts.map +1 -1
- package/dist/components/primitives/index.js +11 -15
- package/dist/components/sections/brand-guide.js +10 -13
- package/dist/components/sections/register-schemas.js +16 -21
- package/dist/components/sections/register.js +16 -21
- package/dist/components/shell/AudienceChips.d.ts +1 -3
- package/dist/components/shell/AudienceChips.d.ts.map +1 -1
- package/dist/components/shell/AudienceDetailsPanel.d.ts +3 -1
- package/dist/components/shell/AudienceDetailsPanel.d.ts.map +1 -1
- package/dist/components/shell/EditorShell.d.ts +0 -34
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
- package/dist/hooks/useSortableNavRow.d.ts +1 -1
- package/dist/hooks/useSortableRow.d.ts +1 -1
- package/dist/index.js +83 -90
- package/dist/lib/index-ops.js +5 -6
- package/dist/lib/index.js +27 -32
- package/dist/lib/pages.d.ts +0 -2
- package/dist/lib/pages.d.ts.map +1 -1
- package/dist/lib/sidecars.js +2 -2
- package/dist/lib/site-validation.js +13 -14
- package/dist/lib/storage-tier.js +4 -4
- package/dist/schemas/index.js +42 -45
- package/dist/schemas/site-config.d.ts +1 -277
- package/dist/schemas/site-config.d.ts.map +1 -1
- package/package.json +2 -42
- package/src/components/chat/ChatComposer.tsx +0 -4
- package/src/components/chat/ChatPanel.tsx +2 -3
- package/src/components/chat/markdown.ts +2 -3
- package/src/components/editor/PagesModal.tsx +1 -15
- package/src/components/editor/StatusIndicator.tsx +4 -4
- package/src/components/primitives/EditablePlainText.tsx +27 -3
- package/src/components/shell/AudienceChips.tsx +1 -3
- package/src/components/shell/AudienceDetailsPanel.tsx +6 -4
- package/src/components/shell/EditorShell.tsx +18 -114
- package/src/components/shell/SiteSettingsModal.tsx +0 -11
- package/src/lib/pages.ts +0 -22
- package/src/schemas/index.ts +0 -4
- package/src/schemas/site-config.ts +4 -32
- package/dist/chunk-4TZJZU2T.js +0 -92
- package/dist/chunk-DIMDRBFK.js +0 -11
- package/dist/chunk-FV7JQRCN.js +0 -94
- package/dist/chunk-IIDKW7AV.js +0 -41
- package/dist/chunk-JCDZAFX2.js +0 -224
- package/dist/chunk-MM7KZS5C.js +0 -30
- package/dist/chunk-QQ3S3P5P.js +0 -38
- package/dist/chunk-TEOAKGZA.js +0 -784
- package/dist/chunk-VAYZJKVR.js +0 -209
- package/dist/components/shell/DocInspector.d.ts +0 -18
- package/dist/components/shell/DocInspector.d.ts.map +0 -1
- package/dist/components/shell/DocumentEditorOverlay.d.ts +0 -29
- package/dist/components/shell/DocumentEditorOverlay.d.ts.map +0 -1
- package/dist/components/shell/DocumentPresentOverlay.d.ts +0 -30
- package/dist/components/shell/DocumentPresentOverlay.d.ts.map +0 -1
- package/dist/components/shell/SiteSettingsDecks.d.ts +0 -13
- package/dist/components/shell/SiteSettingsDecks.d.ts.map +0 -1
- package/dist/deck/DeckComponentView.d.ts +0 -13
- package/dist/deck/DeckComponentView.d.ts.map +0 -1
- package/dist/deck/DeckPlayer.d.ts +0 -26
- package/dist/deck/DeckPlayer.d.ts.map +0 -1
- package/dist/deck/DeckSlideView.d.ts +0 -17
- package/dist/deck/DeckSlideView.d.ts.map +0 -1
- package/dist/deck/DeckStage.d.ts +0 -16
- package/dist/deck/DeckStage.d.ts.map +0 -1
- package/dist/deck/components/DeckImage.d.ts +0 -14
- package/dist/deck/components/DeckImage.d.ts.map +0 -1
- package/dist/deck/components/DeckImageGrid.d.ts +0 -27
- package/dist/deck/components/DeckImageGrid.d.ts.map +0 -1
- package/dist/deck/components/DeckRichText.d.ts +0 -22
- package/dist/deck/components/DeckRichText.d.ts.map +0 -1
- package/dist/deck/components/DeckVideo.d.ts +0 -15
- package/dist/deck/components/DeckVideo.d.ts.map +0 -1
- package/dist/deck/editing.d.ts +0 -50
- package/dist/deck/editing.d.ts.map +0 -1
- package/dist/deck/flip.d.ts +0 -11
- package/dist/deck/flip.d.ts.map +0 -1
- package/dist/deck/index.d.ts +0 -24
- package/dist/deck/index.d.ts.map +0 -1
- package/dist/deck/index.js +0 -51
- package/dist/deck/layouts/CenteredTextLayout.d.ts +0 -12
- package/dist/deck/layouts/CenteredTextLayout.d.ts.map +0 -1
- package/dist/deck/layouts/ColumnSplitLayout.d.ts +0 -14
- package/dist/deck/layouts/ColumnSplitLayout.d.ts.map +0 -1
- package/dist/deck/layouts/FullBleedImageLayout.d.ts +0 -14
- package/dist/deck/layouts/FullBleedImageLayout.d.ts.map +0 -1
- package/dist/deck/media.d.ts +0 -33
- package/dist/deck/media.d.ts.map +0 -1
- package/dist/deck/tokens.d.ts +0 -22
- package/dist/deck/tokens.d.ts.map +0 -1
- package/dist/deck/useStageScale.d.ts +0 -10
- package/dist/deck/useStageScale.d.ts.map +0 -1
- package/dist/deck-builder/DeckBuilderApp.d.ts +0 -30
- package/dist/deck-builder/DeckBuilderApp.d.ts.map +0 -1
- package/dist/deck-builder/DeckBuilderTopBar.d.ts +0 -24
- package/dist/deck-builder/DeckBuilderTopBar.d.ts.map +0 -1
- package/dist/deck-builder/DeckChatPanel.d.ts +0 -45
- package/dist/deck-builder/DeckChatPanel.d.ts.map +0 -1
- package/dist/deck-builder/DeckEditor.d.ts +0 -37
- package/dist/deck-builder/DeckEditor.d.ts.map +0 -1
- package/dist/deck-builder/DeckLibrary.d.ts +0 -22
- package/dist/deck-builder/DeckLibrary.d.ts.map +0 -1
- package/dist/deck-builder/DeckSlideSurface.d.ts +0 -31
- package/dist/deck-builder/DeckSlideSurface.d.ts.map +0 -1
- package/dist/deck-builder/deck-api.d.ts +0 -76
- package/dist/deck-builder/deck-api.d.ts.map +0 -1
- package/dist/deck-builder/deck-chat-client.d.ts +0 -44
- package/dist/deck-builder/deck-chat-client.d.ts.map +0 -1
- package/dist/deck-builder/deck-chat-history.d.ts +0 -10
- package/dist/deck-builder/deck-chat-history.d.ts.map +0 -1
- package/dist/deck-builder/index.d.ts +0 -13
- package/dist/deck-builder/index.d.ts.map +0 -1
- package/dist/deck-builder/index.js +0 -1498
- package/dist/deck-builder/starter-deck.d.ts +0 -23
- package/dist/deck-builder/starter-deck.d.ts.map +0 -1
- package/dist/deck-builder/types.d.ts +0 -29
- package/dist/deck-builder/types.d.ts.map +0 -1
- package/dist/deck-builder/useSortableSlide.d.ts +0 -29
- package/dist/deck-builder/useSortableSlide.d.ts.map +0 -1
- package/dist/document/assets/fonts/albra-grotesk-bold.woff +0 -0
- package/dist/document/assets/fonts/albra-grotesk-regular.woff +0 -0
- package/dist/document/assets/fonts/kapra-neue-expanded-medium.woff +0 -0
- package/dist/document/assets/fonts/kapra-neue-expanded-regular.woff +0 -0
- package/dist/document/components/DocCoverPage.d.ts +0 -11
- package/dist/document/components/DocCoverPage.d.ts.map +0 -1
- package/dist/document/components/DocPage.d.ts +0 -10
- package/dist/document/components/DocPage.d.ts.map +0 -1
- package/dist/document/components/DocRail.d.ts +0 -28
- package/dist/document/components/DocRail.d.ts.map +0 -1
- package/dist/document/components/DocSectionPage.d.ts +0 -12
- package/dist/document/components/DocSectionPage.d.ts.map +0 -1
- package/dist/document/components/DocViewer.d.ts +0 -21
- package/dist/document/components/DocViewer.d.ts.map +0 -1
- package/dist/document/components/Wordmark.d.ts +0 -4
- package/dist/document/components/Wordmark.d.ts.map +0 -1
- package/dist/document/components/measure.d.ts +0 -7
- package/dist/document/components/measure.d.ts.map +0 -1
- package/dist/document/components/useDocPages.d.ts +0 -12
- package/dist/document/components/useDocPages.d.ts.map +0 -1
- package/dist/document/engine/blocks.d.ts +0 -2
- package/dist/document/engine/blocks.d.ts.map +0 -1
- package/dist/document/engine/harvest.d.ts +0 -3
- package/dist/document/engine/harvest.d.ts.map +0 -1
- package/dist/document/engine/masonry.d.ts +0 -26
- package/dist/document/engine/masonry.d.ts.map +0 -1
- package/dist/document/engine/paginate.d.ts +0 -43
- package/dist/document/engine/paginate.d.ts.map +0 -1
- package/dist/document/engine/settings.d.ts +0 -91
- package/dist/document/engine/settings.d.ts.map +0 -1
- package/dist/document/engine/types.d.ts +0 -44
- package/dist/document/engine/types.d.ts.map +0 -1
- package/dist/document/index.d.ts +0 -13
- package/dist/document/index.d.ts.map +0 -1
- package/dist/document/index.js +0 -1030
- package/dist/document/styles/document.css +0 -217
- package/dist/lib/deck-access.d.ts +0 -86
- package/dist/lib/deck-access.d.ts.map +0 -1
- package/dist/lib/deck-access.js +0 -58
- package/dist/lib/deck-dexie.d.ts +0 -88
- package/dist/lib/deck-dexie.d.ts.map +0 -1
- package/dist/lib/deck-dexie.js +0 -22
- package/dist/lib/deck-edit.d.ts +0 -67
- package/dist/lib/deck-edit.d.ts.map +0 -1
- package/dist/lib/deck-media-assets.d.ts +0 -27
- package/dist/lib/deck-media-assets.d.ts.map +0 -1
- package/dist/lib/deck-media-assets.js +0 -25
- package/dist/lib/deck-media-refs.d.ts +0 -35
- package/dist/lib/deck-media-refs.d.ts.map +0 -1
- package/dist/lib/deck-media-refs.js +0 -36
- package/dist/lib/deck-tool.d.ts +0 -25
- package/dist/lib/deck-tool.d.ts.map +0 -1
- package/dist/lib/deck-tool.js +0 -58
- package/dist/lib/document-input.d.ts +0 -25
- package/dist/lib/document-input.d.ts.map +0 -1
- package/dist/schemas/deck-meta.d.ts +0 -85
- package/dist/schemas/deck-meta.d.ts.map +0 -1
- package/dist/schemas/deck-meta.js +0 -64
- package/dist/schemas/deck.d.ts +0 -954
- package/dist/schemas/deck.d.ts.map +0 -1
- package/dist/schemas/deck.js +0 -65
- package/src/components/shell/DocInspector.tsx +0 -314
- package/src/components/shell/DocumentEditorOverlay.tsx +0 -179
- package/src/components/shell/DocumentPresentOverlay.tsx +0 -87
- package/src/components/shell/SiteSettingsDecks.tsx +0 -41
- package/src/deck/DeckComponentView.tsx +0 -28
- package/src/deck/DeckPlayer.tsx +0 -166
- package/src/deck/DeckSlideView.tsx +0 -38
- package/src/deck/DeckStage.tsx +0 -39
- package/src/deck/components/DeckImage.tsx +0 -38
- package/src/deck/components/DeckImageGrid.tsx +0 -254
- package/src/deck/components/DeckRichText.tsx +0 -111
- package/src/deck/components/DeckVideo.tsx +0 -81
- package/src/deck/editing.tsx +0 -201
- package/src/deck/flip.ts +0 -70
- package/src/deck/index.ts +0 -30
- package/src/deck/layouts/CenteredTextLayout.tsx +0 -29
- package/src/deck/layouts/ColumnSplitLayout.tsx +0 -69
- package/src/deck/layouts/FullBleedImageLayout.tsx +0 -58
- package/src/deck/media.tsx +0 -46
- package/src/deck/tokens.ts +0 -57
- package/src/deck/useStageScale.ts +0 -44
- package/src/deck-builder/DeckBuilderApp.tsx +0 -572
- package/src/deck-builder/DeckBuilderTopBar.tsx +0 -62
- package/src/deck-builder/DeckChatPanel.tsx +0 -334
- package/src/deck-builder/DeckEditor.tsx +0 -258
- package/src/deck-builder/DeckLibrary.tsx +0 -195
- package/src/deck-builder/DeckSlideSurface.tsx +0 -55
- package/src/deck-builder/deck-api.ts +0 -177
- package/src/deck-builder/deck-chat-client.ts +0 -129
- package/src/deck-builder/deck-chat-history.ts +0 -62
- package/src/deck-builder/index.ts +0 -12
- package/src/deck-builder/starter-deck.ts +0 -86
- package/src/deck-builder/types.ts +0 -24
- package/src/deck-builder/useSortableSlide.ts +0 -102
- package/src/document/assets/fonts/albra-grotesk-bold.woff +0 -0
- package/src/document/assets/fonts/albra-grotesk-regular.woff +0 -0
- package/src/document/assets/fonts/kapra-neue-expanded-medium.woff +0 -0
- package/src/document/assets/fonts/kapra-neue-expanded-regular.woff +0 -0
- package/src/document/components/DocCoverPage.tsx +0 -45
- package/src/document/components/DocPage.tsx +0 -40
- package/src/document/components/DocRail.tsx +0 -189
- package/src/document/components/DocSectionPage.tsx +0 -48
- package/src/document/components/DocViewer.tsx +0 -313
- package/src/document/components/Wordmark.tsx +0 -18
- package/src/document/components/measure.tsx +0 -133
- package/src/document/components/useDocPages.ts +0 -95
- package/src/document/engine/blocks.ts +0 -116
- package/src/document/engine/harvest.ts +0 -95
- package/src/document/engine/masonry.ts +0 -158
- package/src/document/engine/paginate.ts +0 -232
- package/src/document/engine/settings.ts +0 -125
- package/src/document/engine/types.ts +0 -64
- package/src/document/index.ts +0 -18
- package/src/document/styles/document.css +0 -217
- package/src/lib/deck-access.ts +0 -132
- package/src/lib/deck-dexie.ts +0 -190
- package/src/lib/deck-edit.ts +0 -196
- package/src/lib/deck-media-assets.ts +0 -57
- package/src/lib/deck-media-refs.ts +0 -67
- package/src/lib/deck-tool.ts +0 -95
- package/src/lib/document-input.ts +0 -57
- package/src/schemas/deck-meta.ts +0 -88
- package/src/schemas/deck.ts +0 -478
- package/dist/{chunk-DJABADOO.js → chunk-AJ2QOAZI.js} +3 -3
|
@@ -63,9 +63,8 @@ const ERROR_COPY: Record<string, string> = {
|
|
|
63
63
|
};
|
|
64
64
|
const RETRYABLE = new Set(["rate_limited", "upstream_error"]);
|
|
65
65
|
|
|
66
|
-
/** Re-exported from ./markdown, where the rendering rules
|
|
67
|
-
*
|
|
68
|
-
* this module. */
|
|
66
|
+
/** Re-exported from ./markdown, where the rendering rules live. Kept here
|
|
67
|
+
* because tests and callers import it from this module. */
|
|
69
68
|
export { assistantHtml };
|
|
70
69
|
|
|
71
70
|
/** Parse our /api/chat SSE frames out of a fetch body. Exported for tests. */
|
|
@@ -2,9 +2,8 @@ import { marked } from "marked";
|
|
|
2
2
|
import { sanitizeHtml } from "../../lib/sanitize";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* The assistant-message treatment, shared by every chat surface
|
|
6
|
-
*
|
|
7
|
-
* chat pane is a second CALLER, not a second copy of the rendering rules —
|
|
5
|
+
* The assistant-message treatment, shared by every chat surface. A new chat
|
|
6
|
+
* pane is a second CALLER, not a second copy of the rendering rules —
|
|
8
7
|
* particularly the sanitizer gate below, which is a security property.
|
|
9
8
|
*/
|
|
10
9
|
|
|
@@ -2,10 +2,6 @@ import { useCallback, useState, useEffect, useId } from "react";
|
|
|
2
2
|
import { Check, ChevronDown, ChevronUp, Home } from "lucide-react";
|
|
3
3
|
import type { SiteIndex, Page } from "../../schemas/site-config";
|
|
4
4
|
import type { Audience } from "../../auth/types";
|
|
5
|
-
// Sanctioned outside->inside import: document/ is a sealed subtree, but the
|
|
6
|
-
// Document mode toggle needs the patch shape it edits (mirrors the same
|
|
7
|
-
// sanctioned import in schemas/site-config.ts and lib/pages.ts).
|
|
8
|
-
import type { DocumentOptions } from "../../document/engine/settings";
|
|
9
5
|
import { Button } from "../shared/Button";
|
|
10
6
|
import { ConfirmDialog } from "../shared/ConfirmDialog";
|
|
11
7
|
import { IconButton } from "../shared/IconButton";
|
|
@@ -37,7 +33,6 @@ export interface PagesModalProps {
|
|
|
37
33
|
onSetArchived: (pageId: string, archived: boolean) => void;
|
|
38
34
|
onSetFields: (pageId: string, patch: Partial<Pick<Page, "title" | "slug" | "showInNav">>) => void;
|
|
39
35
|
onSetAudience: (pageId: string, access: string[]) => void;
|
|
40
|
-
onSetDocument: (pageId: string, patch: Partial<DocumentOptions>) => void;
|
|
41
36
|
onConfirmDelete: (pageId: string) => void;
|
|
42
37
|
}
|
|
43
38
|
|
|
@@ -114,7 +109,6 @@ export function PagesModal(props: PagesModalProps) {
|
|
|
114
109
|
}}
|
|
115
110
|
onSetFields={(patch) => props.onSetFields(page.id, patch)}
|
|
116
111
|
onSetAudience={(access) => props.onSetAudience(page.id, access)}
|
|
117
|
-
onSetDocument={(patch) => props.onSetDocument(page.id, patch)}
|
|
118
112
|
onRequestDelete={() =>
|
|
119
113
|
page.order.length === 0 ? props.onConfirmDelete(page.id) : setDeletingId(page.id)
|
|
120
114
|
}
|
|
@@ -222,7 +216,7 @@ export function PagesModal(props: PagesModalProps) {
|
|
|
222
216
|
}
|
|
223
217
|
|
|
224
218
|
function PageRow({
|
|
225
|
-
page, audiences, expanded, isNew, otherSlugs, location, entryIndex, liveSiblingRawIndexes, onMove, onToggleExpand, onSetHome, onSetArchived, onSetFields, onSetAudience,
|
|
219
|
+
page, audiences, expanded, isNew, otherSlugs, location, entryIndex, liveSiblingRawIndexes, onMove, onToggleExpand, onSetHome, onSetArchived, onSetFields, onSetAudience, onRequestDelete,
|
|
226
220
|
}: {
|
|
227
221
|
page: Page; audiences: Audience[]; expanded: boolean;
|
|
228
222
|
isNew: boolean; otherSlugs: string[];
|
|
@@ -236,7 +230,6 @@ function PageRow({
|
|
|
236
230
|
onToggleExpand: () => void; onSetHome: () => void; onSetArchived: (v: boolean) => void;
|
|
237
231
|
onSetFields: (patch: Partial<Pick<Page, "title" | "slug" | "showInNav">>) => void;
|
|
238
232
|
onSetAudience: (access: string[]) => void;
|
|
239
|
-
onSetDocument: (patch: Partial<DocumentOptions>) => void;
|
|
240
233
|
onRequestDelete: () => void;
|
|
241
234
|
}) {
|
|
242
235
|
const draggable = location !== undefined;
|
|
@@ -433,13 +426,6 @@ function PageRow({
|
|
|
433
426
|
</div>
|
|
434
427
|
<Toggle label="Show in menu" checked={page.showInNav} onChange={(v) => onSetFields({ showInNav: v })} />
|
|
435
428
|
</div>
|
|
436
|
-
<div className="flex items-center justify-between gap-4 px-3 py-2.5">
|
|
437
|
-
<div className="min-w-0">
|
|
438
|
-
<div className="text-sm text-base-contrast">Document mode</div>
|
|
439
|
-
<div className="text-xs text-base-contrast/70">Adds a presentation view of this page.</div>
|
|
440
|
-
</div>
|
|
441
|
-
<Toggle label="Document mode" checked={page.document?.enabled ?? false} onChange={(enabled) => onSetDocument({ enabled })} />
|
|
442
|
-
</div>
|
|
443
429
|
</div>
|
|
444
430
|
</div>
|
|
445
431
|
<div className="flex items-center gap-2">
|
|
@@ -74,10 +74,10 @@ export function deriveStatusDisplay({ mainStatus, savedStatus, contentDiffers, i
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
const dotColorClasses: Record<StatusColor, string> = {
|
|
77
|
-
live: "bg-status-live-
|
|
78
|
-
draft: "bg-status-draft-
|
|
79
|
-
archived: "bg-white border-status-
|
|
80
|
-
modified: "bg-status-modified-
|
|
77
|
+
live: "bg-status-live-dot",
|
|
78
|
+
draft: "bg-status-draft-dot",
|
|
79
|
+
archived: "bg-white border-status-archived-dot-border",
|
|
80
|
+
modified: "bg-status-modified-dot",
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
|
|
@@ -18,6 +18,25 @@ interface EditablePlainTextProps
|
|
|
18
18
|
placeholder?: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function caretOffset(el: HTMLElement): number | null {
|
|
22
|
+
const sel = window.getSelection();
|
|
23
|
+
if (!sel || sel.rangeCount === 0) return null;
|
|
24
|
+
const range = sel.getRangeAt(0);
|
|
25
|
+
if (!el.contains(range.endContainer)) return null;
|
|
26
|
+
const before = document.createRange();
|
|
27
|
+
before.selectNodeContents(el);
|
|
28
|
+
before.setEnd(range.endContainer, range.endOffset);
|
|
29
|
+
return before.toString().length;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function placeCaret(el: HTMLElement, offset: number) {
|
|
33
|
+
const sel = window.getSelection();
|
|
34
|
+
if (!sel) return;
|
|
35
|
+
// textContent leaves a single text node, or none for an empty string.
|
|
36
|
+
if (el.firstChild) sel.collapse(el.firstChild, offset);
|
|
37
|
+
else sel.collapse(el, 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
21
40
|
export function EditablePlainText({
|
|
22
41
|
tag,
|
|
23
42
|
value,
|
|
@@ -48,9 +67,14 @@ export function EditablePlainText({
|
|
|
48
67
|
|
|
49
68
|
// Set textContent via ref (safe — plain text only, no HTML injection)
|
|
50
69
|
useEffect(() => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
70
|
+
const el = ref.current;
|
|
71
|
+
if (!el || el.textContent === localValue) return;
|
|
72
|
+
// A value that differs from the DOM while the field is focused (a peer
|
|
73
|
+
// save, a rebase or a 409 recovery landing mid-typing) replaces the text
|
|
74
|
+
// node, which collapses the caret to offset 0. Put it back, clamped.
|
|
75
|
+
const caret = document.activeElement === el ? caretOffset(el) : null;
|
|
76
|
+
el.textContent = localValue;
|
|
77
|
+
if (caret !== null) placeCaret(el, Math.min(caret, localValue.length));
|
|
54
78
|
}, [localValue]);
|
|
55
79
|
|
|
56
80
|
const onInput = useCallback(() => {
|
|
@@ -2,9 +2,7 @@ import { cn } from "../../lib/cn";
|
|
|
2
2
|
import { contrastColor } from "../../lib/contrast";
|
|
3
3
|
import type { Audience } from "../../auth/types";
|
|
4
4
|
|
|
5
|
-
/** Multi-select audience chips — the site-settings idiom for "who sees this".
|
|
6
|
-
* Shared by the chatbot and deck-builder opt-ins so the two gates, which have
|
|
7
|
-
* identical strict-opt-in semantics, cannot come to look like different rules. */
|
|
5
|
+
/** Multi-select audience chips — the site-settings idiom for "who sees this". */
|
|
8
6
|
export function AudienceChips({
|
|
9
7
|
audiences,
|
|
10
8
|
selected,
|
|
@@ -27,6 +27,8 @@ interface Props {
|
|
|
27
27
|
* an audience-filtered site.
|
|
28
28
|
*/
|
|
29
29
|
canImpersonate?: boolean;
|
|
30
|
+
/** Open the view in a new tab — set by the editor, so it keeps the editor tab. */
|
|
31
|
+
viewAsInNewTab?: boolean;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const PLACEHOLDER_COLOR = "#94a3b8";
|
|
@@ -174,7 +176,7 @@ function PageRow({ page }: { page: OverviewPage }) {
|
|
|
174
176
|
);
|
|
175
177
|
}
|
|
176
178
|
|
|
177
|
-
function AudienceBlock({ audience, revealSupported, canImpersonate }: { audience: AudienceDetail; revealSupported: boolean; canImpersonate: boolean }) {
|
|
179
|
+
function AudienceBlock({ audience, revealSupported, canImpersonate, viewAsInNewTab }: { audience: AudienceDetail; revealSupported: boolean; canImpersonate: boolean; viewAsInNewTab: boolean }) {
|
|
178
180
|
const [open, setOpen] = useState(false);
|
|
179
181
|
const { overview } = audience;
|
|
180
182
|
|
|
@@ -208,7 +210,7 @@ function AudienceBlock({ audience, revealSupported, canImpersonate }: { audience
|
|
|
208
210
|
</button>
|
|
209
211
|
{canImpersonate && (
|
|
210
212
|
// A plain form POST: the route sets the cookie and 303s to the viewer site.
|
|
211
|
-
<form method="post" action="/api/auth/impersonate" className="shrink-0">
|
|
213
|
+
<form method="post" action="/api/auth/impersonate" target={viewAsInNewTab ? "_blank" : undefined} className="shrink-0">
|
|
212
214
|
<input type="hidden" name="audience" value={audience.name} />
|
|
213
215
|
<Button type="submit" variant="secondary" size="sm" aria-label={`View the site as ${audience.displayName}`}>
|
|
214
216
|
<span className="inline-flex items-center gap-1.5">
|
|
@@ -260,7 +262,7 @@ function AudienceBlock({ audience, revealSupported, canImpersonate }: { audience
|
|
|
260
262
|
* Reachable by editors and the site's default audience (the client), so the
|
|
261
263
|
* client can hand a new partner access without going through us.
|
|
262
264
|
*/
|
|
263
|
-
export function AudienceDetailsPanel({ audiences, revealSupported, canImpersonate = false }: Props) {
|
|
265
|
+
export function AudienceDetailsPanel({ audiences, revealSupported, canImpersonate = false, viewAsInNewTab = false }: Props) {
|
|
264
266
|
if (audiences.length === 0) {
|
|
265
267
|
return (
|
|
266
268
|
<p className="text-sm text-base-contrast-light">
|
|
@@ -272,7 +274,7 @@ export function AudienceDetailsPanel({ audiences, revealSupported, canImpersonat
|
|
|
272
274
|
return (
|
|
273
275
|
<div className="flex flex-col gap-3">
|
|
274
276
|
{audiences.map((a) => (
|
|
275
|
-
<AudienceBlock key={a.name} audience={a} revealSupported={revealSupported} canImpersonate={canImpersonate} />
|
|
277
|
+
<AudienceBlock key={a.name} audience={a} revealSupported={revealSupported} canImpersonate={canImpersonate} viewAsInNewTab={viewAsInNewTab} />
|
|
276
278
|
))}
|
|
277
279
|
</div>
|
|
278
280
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fragment,
|
|
1
|
+
import { Fragment, useContext, useState, useCallback, useEffect, useRef, useMemo, type ReactNode } from "react";
|
|
2
2
|
|
|
3
3
|
import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
4
4
|
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
|
@@ -15,10 +15,6 @@ import { composePreviewImage, PREVIEW_COMPOSE_VERSION } from "../../media/compos
|
|
|
15
15
|
import { derivePreviewInputs, servedPreviewSize } from "../../media/preview-inputs";
|
|
16
16
|
import { resolveMedia } from "../../media/resolve";
|
|
17
17
|
import { getMediaProvider } from "../../media";
|
|
18
|
-
// Sanctioned outside->inside import: document/ is a sealed subtree, but the
|
|
19
|
-
// Document mode toggle wiring needs the patch shape it edits (mirrors the same
|
|
20
|
-
// sanctioned import in schemas/site-config.ts and lib/pages.ts).
|
|
21
|
-
import type { DocumentOptions } from "../../document/engine/settings";
|
|
22
18
|
import { SiteConfigSchema } from "../../schemas/site-config";
|
|
23
19
|
import { ensureSanitizer } from "../../lib/sanitize";
|
|
24
20
|
import { resolveChatbotName } from "../../lib/chatbot";
|
|
@@ -45,17 +41,6 @@ import { buildInsertMenuOptions, resolveInsertedContent } from "../../lib/insert
|
|
|
45
41
|
|
|
46
42
|
ensureSectionsRegistered();
|
|
47
43
|
import { BugReportFAB } from "./BugReportFAB";
|
|
48
|
-
// Lazy boundary: the document editor/presentation overlays (and the whole
|
|
49
|
-
// document engine they pull in) must not weigh /edit on sites that never use
|
|
50
|
-
// document mode — they load only when a document-enabled page opens the overlay.
|
|
51
|
-
// Named exports → adapt to the default-export shape React.lazy expects (mirrors
|
|
52
|
-
// the ChatPanel lazy import convention in ChatWidget).
|
|
53
|
-
const DocumentEditorOverlay = lazy(() =>
|
|
54
|
-
import("./DocumentEditorOverlay").then((m) => ({ default: m.DocumentEditorOverlay })),
|
|
55
|
-
);
|
|
56
|
-
const DocumentPresentOverlay = lazy(() =>
|
|
57
|
-
import("./DocumentPresentOverlay").then((m) => ({ default: m.DocumentPresentOverlay })),
|
|
58
|
-
);
|
|
59
44
|
import { SectionWrapper } from "../editor/SectionWrapper";
|
|
60
45
|
import { SectionOrderingModal } from "../editor/SectionOrderingModal";
|
|
61
46
|
import { MoveSectionModal } from "../editor/MoveSectionModal";
|
|
@@ -103,7 +88,7 @@ import type { ViewerCredential } from "../../schemas/audience";
|
|
|
103
88
|
import {
|
|
104
89
|
homePage, pageById, pageBySlug, pageDisplayTitle, addSectionToPage, removeSectionFromPages,
|
|
105
90
|
reorderVisibleSectionInPage, audiencePasses,
|
|
106
|
-
setHomePage, setPageArchived, setPageFields, setPageAudience,
|
|
91
|
+
setHomePage, setPageArchived, setPageFields, setPageAudience, deletePage, addPage,
|
|
107
92
|
moveSection as moveSectionReducer,
|
|
108
93
|
} from "../../lib/pages";
|
|
109
94
|
import { PagesContext } from "../shared/PagesContext";
|
|
@@ -128,7 +113,7 @@ import { FabStack } from "../shared/FabStack";
|
|
|
128
113
|
import { Tooltip } from "../shared/Tooltip";
|
|
129
114
|
import { SegmentedControl } from "../shared/SegmentedControl";
|
|
130
115
|
import { computeDisplaySections } from "./view-branch";
|
|
131
|
-
import { ImageIcon, ListOrderedIcon, FilesIcon, JoystickIcon, Settings, X,
|
|
116
|
+
import { ImageIcon, ListOrderedIcon, FilesIcon, JoystickIcon, Settings, X, Eye, Pencil } from "lucide-react";
|
|
132
117
|
import { ErrorBoundary } from "../shared/ErrorBoundary";
|
|
133
118
|
import { HistoryToolbar } from "./HistoryToolbar";
|
|
134
119
|
import { RestoreModal } from "./RestoreModal";
|
|
@@ -299,7 +284,6 @@ export default function EditorShell({
|
|
|
299
284
|
const [showRestoreModal, setShowRestoreModal] = useState(false);
|
|
300
285
|
const [showOrderingModal, setShowOrderingModal] = useState(false);
|
|
301
286
|
const [showPagesModal, setShowPagesModal] = useState(false);
|
|
302
|
-
const [showDocumentEditor, setShowDocumentEditor] = useState(false);
|
|
303
287
|
const [isRestoring, setIsRestoring] = useState(false);
|
|
304
288
|
const [rejectedUploads, setRejectedUploads] = useState<{ name: string; reason: "size" | "type" }[]>([]);
|
|
305
289
|
// Surfaces useMediaPipeline's onUploadError (oversized/disallowed files,
|
|
@@ -328,6 +312,9 @@ export default function EditorShell({
|
|
|
328
312
|
// null = the auth provider has no named sign-ins (`.env` mode → route 501s),
|
|
329
313
|
// which is also what turns the reveal buttons off.
|
|
330
314
|
const [viewerCredentials, setViewerCredentials] = useState<ViewerCredential[] | null>(null);
|
|
315
|
+
// Gates "View as" in Audience Details: without password protection no visitor
|
|
316
|
+
// sees an audience-filtered site, and the impersonate route refuses.
|
|
317
|
+
const [passwordEnabled, setPasswordEnabled] = useState(false);
|
|
331
318
|
const [movingSectionId, setMovingSectionId] = useState<string | null>(null);
|
|
332
319
|
// Set by recoverConflict when a rebase surfaces overlapping edits; drives the
|
|
333
320
|
// keep-mine/take-theirs dialog. `resolve` is the promise resolver recovery
|
|
@@ -485,26 +472,6 @@ export default function EditorShell({
|
|
|
485
472
|
return audiences.filter((a) => resolvedActivePage.access.includes(a.name));
|
|
486
473
|
}, [resolvedActivePage, audiences]);
|
|
487
474
|
|
|
488
|
-
// The sections shown on the canvas when previewing the published ("live")
|
|
489
|
-
// branch — the SAME list computeDisplaySections renders for viewBranch "live".
|
|
490
|
-
// Only meaningful while viewSections !== null (branch view); the read-only
|
|
491
|
-
// document presentation overlay renders exactly these so it mirrors the canvas.
|
|
492
|
-
// (viewSections is set only for the live branch, so its non-null-ness IS the
|
|
493
|
-
// "branch view" signal at this level.)
|
|
494
|
-
const branchDisplaySections = useMemo(
|
|
495
|
-
() =>
|
|
496
|
-
viewSections && resolvedActivePage
|
|
497
|
-
? computeDisplaySections({
|
|
498
|
-
isEditMode: false,
|
|
499
|
-
viewBranch: "live",
|
|
500
|
-
sections,
|
|
501
|
-
viewSections,
|
|
502
|
-
activePageSectionIds: resolvedActivePage.order,
|
|
503
|
-
})
|
|
504
|
-
: [],
|
|
505
|
-
[viewSections, resolvedActivePage, sections],
|
|
506
|
-
);
|
|
507
|
-
|
|
508
475
|
const getPageHeadings = useCallback(
|
|
509
476
|
(pageId: string) => {
|
|
510
477
|
const page = pageById(siteIndex, pageId);
|
|
@@ -1221,6 +1188,15 @@ export default function EditorShell({
|
|
|
1221
1188
|
if (!cancelled) setViewerCredentials(null);
|
|
1222
1189
|
}
|
|
1223
1190
|
})();
|
|
1191
|
+
(async () => {
|
|
1192
|
+
try {
|
|
1193
|
+
const res = await fetch("/api/auth/audiences");
|
|
1194
|
+
const enabled = res.ok && (await res.json()).passwordEnabled === true;
|
|
1195
|
+
if (!cancelled) setPasswordEnabled(enabled);
|
|
1196
|
+
} catch {
|
|
1197
|
+
if (!cancelled) setPasswordEnabled(false);
|
|
1198
|
+
}
|
|
1199
|
+
})();
|
|
1224
1200
|
return () => { cancelled = true; };
|
|
1225
1201
|
}, [showAudienceDetails]);
|
|
1226
1202
|
|
|
@@ -1855,11 +1831,6 @@ export default function EditorShell({
|
|
|
1855
1831
|
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1856
1832
|
}, [persistence]);
|
|
1857
1833
|
|
|
1858
|
-
const handleSetPageDocument = useCallback((id: string, patch: Partial<DocumentOptions>) => {
|
|
1859
|
-
setSiteIndex((prev) => setPageDocument(prev, id, patch));
|
|
1860
|
-
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1861
|
-
}, [persistence]);
|
|
1862
|
-
|
|
1863
1834
|
const handleSetPageAudience = useCallback((id: string, access: string[]) => {
|
|
1864
1835
|
const { index: next, resetSectionIds } = setPageAudience(siteIndexRef.current, id, access);
|
|
1865
1836
|
setSiteIndex(next);
|
|
@@ -2164,55 +2135,12 @@ export default function EditorShell({
|
|
|
2164
2135
|
audiences={audiences}
|
|
2165
2136
|
authoringDefaults={authoringDefaults}
|
|
2166
2137
|
onAuthoringDefaultsChange={onAuthoringDefaultsChange}
|
|
2167
|
-
docEditEnabled={Boolean(resolvedActivePage?.document?.enabled)}
|
|
2168
|
-
onDocEditClick={() => setShowDocumentEditor(true)}
|
|
2169
2138
|
// Bug reports persist via Supabase. On the password-only
|
|
2170
2139
|
// (zero-Supabase) path there's no backend and the editor session
|
|
2171
2140
|
// has userId: null, so the button would always 401 — hide it there.
|
|
2172
2141
|
showBugReport={!capabilities.passwordOnly}
|
|
2173
2142
|
/>
|
|
2174
2143
|
|
|
2175
|
-
{/* Deck Builder (#34). Editors are super-admins over decks and are
|
|
2176
|
-
never gated by deckBuilder.audiences, so this is unconditional.
|
|
2177
|
-
An <a>, not a click handler: builder mode is its own route.
|
|
2178
|
-
Standalone (not in EditorToolbar's FabStack): bottom-60 clears
|
|
2179
|
-
the stack's three members with room to spare. Taking over the
|
|
2180
|
-
editor top bar in place is #38 — this just goes there. */}
|
|
2181
|
-
<Fab href="/decks?next=%2Fedit" label="Build a deck" column="editor" className="bottom-60">
|
|
2182
|
-
<Presentation size={20} aria-hidden="true" />
|
|
2183
|
-
</Fab>
|
|
2184
|
-
{showDocumentEditor && resolvedActivePage?.document?.enabled && (
|
|
2185
|
-
<Suspense fallback={<DocumentOverlayFallback />}>
|
|
2186
|
-
{/* Branch/history view (viewSections !== null → previewing the
|
|
2187
|
-
published branch): presentation only, never the editor. Drew's
|
|
2188
|
-
product decision — old versions are the record, and we have no
|
|
2189
|
-
way to edit them, so opening the editor over one would be a lie.
|
|
2190
|
-
The draft view (viewSections === null) still opens the real
|
|
2191
|
-
editor. NOTE: viewSections drives the "Published" branch preview
|
|
2192
|
-
only; the separate historySelectEvent commit view
|
|
2193
|
-
(EditorContext.historyState) is the OTHER read-only case — the
|
|
2194
|
-
document FAB in EditorToolbar is gated on !historyState, which
|
|
2195
|
-
keeps this overlay from ever being opened onto it in the
|
|
2196
|
-
first place. */}
|
|
2197
|
-
{viewSections !== null ? (
|
|
2198
|
-
<DocumentPresentOverlay
|
|
2199
|
-
page={resolvedActivePage}
|
|
2200
|
-
sections={branchDisplaySections}
|
|
2201
|
-
manifest={mediaManifest}
|
|
2202
|
-
onClose={() => setShowDocumentEditor(false)}
|
|
2203
|
-
/>
|
|
2204
|
-
) : (
|
|
2205
|
-
<DocumentEditorOverlay
|
|
2206
|
-
page={resolvedActivePage}
|
|
2207
|
-
sections={activeSections}
|
|
2208
|
-
manifest={mediaManifest}
|
|
2209
|
-
onUpdateDocument={(patch) => handleSetPageDocument(resolvedActivePage.id, patch)}
|
|
2210
|
-
onClose={() => setShowDocumentEditor(false)}
|
|
2211
|
-
/>
|
|
2212
|
-
)}
|
|
2213
|
-
</Suspense>
|
|
2214
|
-
)}
|
|
2215
|
-
|
|
2216
2144
|
{/* Editor chat FAB: chat while editing, against the DRAFT chatbot
|
|
2217
2145
|
config (tasks + prompt reflect unsaved changes; grounding is
|
|
2218
2146
|
published content). Gated only on the platform switch — editors
|
|
@@ -2308,7 +2236,7 @@ export default function EditorShell({
|
|
|
2308
2236
|
current working copy — add or remove sign-ins under Site settings → Audiences.
|
|
2309
2237
|
</p>
|
|
2310
2238
|
<div className="mt-6">
|
|
2311
|
-
<AudienceDetailsPanel audiences={audienceDetails} revealSupported={viewerCredentials !== null} />
|
|
2239
|
+
<AudienceDetailsPanel audiences={audienceDetails} revealSupported={viewerCredentials !== null} canImpersonate={passwordEnabled} viewAsInNewTab />
|
|
2312
2240
|
</div>
|
|
2313
2241
|
</div>
|
|
2314
2242
|
) : (
|
|
@@ -2456,7 +2384,6 @@ export default function EditorShell({
|
|
|
2456
2384
|
onSetArchived={handleSetArchived}
|
|
2457
2385
|
onSetFields={handleSetPageFields}
|
|
2458
2386
|
onSetAudience={handleSetPageAudience}
|
|
2459
|
-
onSetDocument={handleSetPageDocument}
|
|
2460
2387
|
onConfirmDelete={handleDeletePage}
|
|
2461
2388
|
/>
|
|
2462
2389
|
</EditorModal>
|
|
@@ -2963,15 +2890,6 @@ function EditorContent({
|
|
|
2963
2890
|
);
|
|
2964
2891
|
}
|
|
2965
2892
|
|
|
2966
|
-
// Suspense fallback while the lazy document overlay chunk loads.
|
|
2967
|
-
function DocumentOverlayFallback() {
|
|
2968
|
-
return (
|
|
2969
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-base" role="status" aria-live="polite">
|
|
2970
|
-
<span className="text-sm text-base-contrast-light">Loading document editor…</span>
|
|
2971
|
-
</div>
|
|
2972
|
-
);
|
|
2973
|
-
}
|
|
2974
|
-
|
|
2975
2893
|
function GlobalModal() {
|
|
2976
2894
|
const { modalState, closeModal } = useEditorModal();
|
|
2977
2895
|
|
|
@@ -3099,8 +3017,6 @@ function EditorToolbar({
|
|
|
3099
3017
|
audiences,
|
|
3100
3018
|
authoringDefaults,
|
|
3101
3019
|
onAuthoringDefaultsChange,
|
|
3102
|
-
docEditEnabled,
|
|
3103
|
-
onDocEditClick,
|
|
3104
3020
|
showBugReport,
|
|
3105
3021
|
}: {
|
|
3106
3022
|
buttonState: "synced" | "publish" | "saveAndPublish";
|
|
@@ -3127,11 +3043,6 @@ function EditorToolbar({
|
|
|
3127
3043
|
audiences: Audience[];
|
|
3128
3044
|
authoringDefaults: AuthoringDefaults;
|
|
3129
3045
|
onAuthoringDefaultsChange: (next: AuthoringDefaults) => void;
|
|
3130
|
-
/** Whether the active page has document mode on — gates the "Edit as
|
|
3131
|
-
* document" FAB this toolbar stacks alongside its own mode-toggle FAB.
|
|
3132
|
-
* Owned by EditorShell (resolvedActivePage lives above the Provider). */
|
|
3133
|
-
docEditEnabled: boolean;
|
|
3134
|
-
onDocEditClick: () => void;
|
|
3135
3046
|
/** Whether to render BugReportFAB in this toolbar's FabStack — false on
|
|
3136
3047
|
* the password-only (zero-Supabase) path, which has no backend for it. */
|
|
3137
3048
|
showBugReport: boolean;
|
|
@@ -3255,17 +3166,10 @@ function EditorToolbar({
|
|
|
3255
3166
|
<FabStack>
|
|
3256
3167
|
{/* Bug reports persist via Supabase; hidden on the password-only
|
|
3257
3168
|
path (showBugReport, threaded from EditorShell's capabilities).
|
|
3258
|
-
History is presentation only, never editing
|
|
3259
|
-
already routes to DocumentPresentOverlay; !historyState covers
|
|
3260
|
-
the separate historySelectEvent commit view) — FabStack
|
|
3169
|
+
History is presentation only, never editing — FabStack
|
|
3261
3170
|
unmounts with the rest of this block whenever history is
|
|
3262
|
-
active, which hides
|
|
3171
|
+
active, which hides its FABs together. */}
|
|
3263
3172
|
{showBugReport && <BugReportFAB stacked />}
|
|
3264
|
-
{docEditEnabled && (
|
|
3265
|
-
<Fab stacked label="Edit as document" column="editor" onClick={onDocEditClick}>
|
|
3266
|
-
<FileText size={20} aria-hidden="true" />
|
|
3267
|
-
</Fab>
|
|
3268
|
-
)}
|
|
3269
3173
|
<Fab
|
|
3270
3174
|
stacked
|
|
3271
3175
|
label={isEditMode ? "Switch to view mode" : "Switch to edit mode"}
|
|
@@ -6,7 +6,6 @@ import { SiteSettingsViewerAccess } from "./SiteSettingsViewerAccess";
|
|
|
6
6
|
import { SiteSettingsDisplay } from "./SiteSettingsDisplay";
|
|
7
7
|
import { SiteSettingsUsers } from "./SiteSettingsUsers";
|
|
8
8
|
import { SiteSettingsChatbot } from "./SiteSettingsChatbot";
|
|
9
|
-
import { SiteSettingsDecks } from "./SiteSettingsDecks";
|
|
10
9
|
import { SiteSettingsTags } from "./SiteSettingsTags";
|
|
11
10
|
import type { SiteTagging } from "../../lib/registry";
|
|
12
11
|
import type { SiteConfig } from "../../schemas/site-config";
|
|
@@ -88,16 +87,6 @@ export function SiteSettingsModal({
|
|
|
88
87
|
</div>
|
|
89
88
|
),
|
|
90
89
|
},
|
|
91
|
-
{
|
|
92
|
-
id: "decks",
|
|
93
|
-
label: "Decks",
|
|
94
|
-
show: true,
|
|
95
|
-
content: (
|
|
96
|
-
<div data-testid="site-settings-tab-panel" className="flex flex-1 flex-col overflow-y-auto px-6 py-4">
|
|
97
|
-
<SiteSettingsDecks siteConfig={siteConfig} onSiteConfigChange={onSiteConfigChange} audiences={audiences} />
|
|
98
|
-
</div>
|
|
99
|
-
),
|
|
100
|
-
},
|
|
101
90
|
{
|
|
102
91
|
id: "tags",
|
|
103
92
|
label: "Tags",
|
package/src/lib/pages.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import type { Page, SiteIndex, SectionMeta } from "../schemas/site-config";
|
|
2
2
|
import { RESERVED_SLUGS } from "../schemas/site-config";
|
|
3
|
-
// Sanctioned outside->inside import: document/ is a sealed subtree, but pages.ts
|
|
4
|
-
// is allowed to reach into its engine to write the page-level document options
|
|
5
|
-
// block (mirrors the same sanctioned import in schemas/site-config.ts).
|
|
6
|
-
import { documentOptionsSchema, type DocumentOptions } from "../document/engine/settings";
|
|
7
3
|
import { navEntries } from "./nav-tree";
|
|
8
4
|
import { slugifyPageSlug } from "./slug";
|
|
9
5
|
|
|
@@ -101,24 +97,6 @@ export function setPageFields(
|
|
|
101
97
|
return mapPages(index, (p) => (p.id === pageId ? { ...p, ...patch } : p));
|
|
102
98
|
}
|
|
103
99
|
|
|
104
|
-
export function setPageDocument(index: SiteIndex, pageId: string, patch: Partial<DocumentOptions>): SiteIndex {
|
|
105
|
-
return {
|
|
106
|
-
...index,
|
|
107
|
-
pages: index.pages.map((p) => {
|
|
108
|
-
if (p.id !== pageId) return p;
|
|
109
|
-
const current = p.document ?? documentOptionsSchema.parse({});
|
|
110
|
-
return {
|
|
111
|
-
...p,
|
|
112
|
-
document: documentOptionsSchema.parse({
|
|
113
|
-
...current,
|
|
114
|
-
...patch,
|
|
115
|
-
sections: { ...current.sections, ...(patch.sections ?? {}) },
|
|
116
|
-
}),
|
|
117
|
-
};
|
|
118
|
-
}),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
100
|
export function setPageAudience(
|
|
123
101
|
index: SiteIndex,
|
|
124
102
|
pageId: string,
|
package/src/schemas/index.ts
CHANGED
|
@@ -9,7 +9,3 @@ export * from "./media-grid-options";
|
|
|
9
9
|
export * from "./auth";
|
|
10
10
|
export * from "./link";
|
|
11
11
|
export * from "./documents";
|
|
12
|
-
// ./deck is deliberately NOT re-exported here: this barrel is reached from the
|
|
13
|
-
// primitives root export, which viewer pages pull in. Decks are not site
|
|
14
|
-
// content and not a section type — reach the deck schema through its own
|
|
15
|
-
// subpath export instead (see tests/deck/viewer-isolation.test.ts).
|
|
@@ -2,10 +2,6 @@ import { z } from "zod";
|
|
|
2
2
|
import { MediaConfigSchema } from "./media";
|
|
3
3
|
import { HexColorSchema, FontNameSchema } from "./shared";
|
|
4
4
|
import { CORNER_RADIUS_OPTIONS } from "../lib/corner-radius";
|
|
5
|
-
// Sanctioned outside->inside import: document/ is a sealed subtree, but
|
|
6
|
-
// site-config.ts is allowed to reach into its engine to wire the page-level
|
|
7
|
-
// document options block onto PageSchema.
|
|
8
|
-
import { documentOptionsSchema } from "../document/engine/settings";
|
|
9
5
|
import type { NavEntry } from "./nav";
|
|
10
6
|
import type { TagEntry } from "./tags";
|
|
11
7
|
|
|
@@ -21,19 +17,19 @@ export const SectionMetaSchema = z.object({
|
|
|
21
17
|
|
|
22
18
|
export type SectionMeta = z.infer<typeof SectionMetaSchema>;
|
|
23
19
|
|
|
24
|
-
// Page slugs that would shadow a platform route. KEEP IN SYNC with the root
|
|
25
|
-
//
|
|
20
|
+
// Page slugs that would shadow a platform route. KEEP IN SYNC with the root
|
|
21
|
+
// and /edit/* route patterns injected in
|
|
26
22
|
// packages/core/src/integration.ts — if a new root-level route is added there,
|
|
27
23
|
// add its first segment here (the test in Step 1 guards the known set). This is
|
|
28
24
|
// the single source of truth for reserved slugs; the schema, uniquePageSlug, and
|
|
29
25
|
// /api/save all import it.
|
|
30
|
-
export const RESERVED_SLUGS = ["edit", "api", "login", "set-password", "404", "
|
|
26
|
+
export const RESERVED_SLUGS = ["edit", "api", "login", "set-password", "404", "audiences"] as const;
|
|
31
27
|
|
|
32
28
|
// Subset of RESERVED_SLUGS the index schema hard-rejects at parse time. Only
|
|
33
29
|
// slugs reserved since before client sites existed may hard-fail here: parse-
|
|
34
30
|
// rejecting a newly-reserved slug would brick an already-deployed index that
|
|
35
31
|
// uses it (viewer AND editor refuse to load, leaving no in-product rename
|
|
36
|
-
// path). Newly reserved slugs ("
|
|
32
|
+
// path). Newly reserved slugs ("audiences") are enforced at authoring time only —
|
|
37
33
|
// PagesModal and uniquePageSlug block new uses via RESERVED_SLUGS, and the
|
|
38
34
|
// injected platform route simply shadows a grandfathered page until renamed.
|
|
39
35
|
const PARSE_REJECTED_SLUGS = ["edit", "api", "login", "set-password", "404"] as const;
|
|
@@ -49,7 +45,6 @@ export const PageSchema = z.object({
|
|
|
49
45
|
status: PageStatusSchema,
|
|
50
46
|
access: z.array(z.string()),
|
|
51
47
|
order: z.array(z.string()),
|
|
52
|
-
document: documentOptionsSchema.optional(),
|
|
53
48
|
});
|
|
54
49
|
|
|
55
50
|
export type Page = z.infer<typeof PageSchema>;
|
|
@@ -238,26 +233,6 @@ export const ChatbotConfigSchema = z
|
|
|
238
233
|
});
|
|
239
234
|
export type ChatbotConfig = z.infer<typeof ChatbotConfigSchema>;
|
|
240
235
|
|
|
241
|
-
/**
|
|
242
|
-
* Deck Builder config (ADR 0001/0002). An OBJECT rather than a bare audience
|
|
243
|
-
* array because `publicSharing` (#37) lands beside `audiences` — the same shape
|
|
244
|
-
* ChatbotConfigSchema has, for the same reason.
|
|
245
|
-
*/
|
|
246
|
-
export const DeckBuilderConfigSchema = z.object({
|
|
247
|
-
// Audience names whose members may CREATE decks — a Deck Creator. Strict
|
|
248
|
-
// opt-in exactly like chatbot.audiences: [] = no viewer may build a deck.
|
|
249
|
-
// Editors are super-admins over decks and are never gated by this list.
|
|
250
|
-
// Gates: [...slug].astro's FAB, /decks, and POST /api/decks (canCreateDeck).
|
|
251
|
-
audiences: z
|
|
252
|
-
.array(z.string())
|
|
253
|
-
.default([])
|
|
254
|
-
.describe(
|
|
255
|
-
"Audience names allowed to create decks in the Deck Builder. Strict opt-in — an empty list means no viewer can. Editors always can.",
|
|
256
|
-
),
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
export type DeckBuilderConfig = z.infer<typeof DeckBuilderConfigSchema>;
|
|
260
|
-
|
|
261
236
|
export const SiteConfigSchema = z.object({
|
|
262
237
|
siteName: z.string().default("Brand Portal"),
|
|
263
238
|
primaryColor: HexColorSchema.default("#009ca6"),
|
|
@@ -328,9 +303,6 @@ export const SiteConfigSchema = z.object({
|
|
|
328
303
|
// sites table, not here). Every field defaults so pre-existing
|
|
329
304
|
// site-config.json files keep parsing.
|
|
330
305
|
chatbot: ChatbotConfigSchema.default({ enabled: false, name: "Portal Assistant", audienceNames: {}, thinkingPhrases: [], systemPrompt: "", audiences: [], tasks: [], audiencePrompts: {} }),
|
|
331
|
-
// Deck Builder config (editor-owned). Defaults so pre-existing
|
|
332
|
-
// site-config.json files keep parsing — and the default is closed.
|
|
333
|
-
deckBuilder: DeckBuilderConfigSchema.default({ audiences: [] }),
|
|
334
306
|
});
|
|
335
307
|
|
|
336
308
|
export type SiteConfig = z.infer<typeof SiteConfigSchema>;
|