@flamingo-stack/openframe-frontend-core 0.0.847 → 0.0.849
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/build/export-map.json +1 -1
- package/dist/{case-study-card-DJOHIHYU.cjs → case-study-card-X2HKPKT7.cjs} +5 -5
- package/dist/{chunk-CNMXP77I.cjs → chunk-36DUCHI7.cjs} +4 -4
- package/dist/{chunk-MPVOPSU6.cjs → chunk-36XWCVSM.cjs} +96 -96
- package/dist/{chunk-J2IXYCEH.cjs → chunk-3W4ASAXN.cjs} +15 -15
- package/dist/{chunk-6LDL3UZE.cjs → chunk-4YBS4HIO.cjs} +20 -20
- package/dist/{chunk-ABL5OBSF.cjs → chunk-533QK4R6.cjs} +296 -250
- package/dist/{chunk-QK2A334V.cjs → chunk-5CBI2ZYV.cjs} +12 -12
- package/dist/{chunk-D5DMWXF2.cjs → chunk-5PIR77V5.cjs} +4 -4
- package/dist/{chunk-HPXET3LH.cjs → chunk-6AJYGXXS.cjs} +27 -27
- package/dist/{chunk-4R4HAFXI.cjs → chunk-ALRYVZ3E.cjs} +6 -6
- package/dist/{chunk-FLW4WULE.cjs → chunk-BU2PSRAS.cjs} +17 -61
- package/dist/{chunk-HBDLJUI3.cjs → chunk-C3P7IUTU.cjs} +39 -14
- package/dist/{chunk-SSSI2RBV.cjs → chunk-FNQWL4W4.cjs} +14 -14
- package/dist/{chunk-TBOWFTEN.cjs → chunk-FRG724DU.cjs} +22 -22
- package/dist/{chunk-SIFRIUPW.cjs → chunk-FXLROQUT.cjs} +345 -356
- package/dist/{chunk-3V5JWG27.cjs → chunk-GG466XFQ.cjs} +2 -2
- package/dist/{chunk-XCVH5BCZ.cjs → chunk-GRCOEICK.cjs} +6 -6
- package/dist/{chunk-5GBLXY5F.cjs → chunk-HTYJUMFB.cjs} +7 -7
- package/dist/{chunk-URP6GYTD.cjs → chunk-IG6ROHPS.cjs} +56 -56
- package/dist/{chunk-DGQDGCQH.cjs → chunk-IHY7E4EX.cjs} +2 -2
- package/dist/{chunk-WCK5T3UD.cjs → chunk-IKOL6NJY.cjs} +101 -69
- package/dist/{chunk-RAJ5FYFL.cjs → chunk-IN3LA2FU.cjs} +14 -14
- package/dist/{chunk-ZTO7KNYV.cjs → chunk-KXBZCUDW.cjs} +35 -35
- package/dist/{chunk-57JYB3E2.cjs → chunk-MC3L525K.cjs} +3 -3
- package/dist/{chunk-BIOSHDXQ.cjs → chunk-MDLT6D46.cjs} +61 -61
- package/dist/{chunk-3TOWCFIK.cjs → chunk-PMUCEUFQ.cjs} +13 -1
- package/dist/{chunk-PV3NKXWS.cjs → chunk-PV2XQJ2R.cjs} +99 -178
- package/dist/{chunk-J2ZSGOUC.cjs → chunk-R5JPZ6TY.cjs} +27 -27
- package/dist/{chunk-744JXTDP.cjs → chunk-SA2NNWIJ.cjs} +23 -23
- package/dist/{chunk-DEEQSVLX.cjs → chunk-SHQY4GOE.cjs} +90 -90
- package/dist/{chunk-BHOVF4KP.cjs → chunk-SOSIXRQ3.cjs} +2 -2
- package/dist/{chunk-Y2OEC7UW.cjs → chunk-TEKN6TJ5.cjs} +40 -40
- package/dist/{chunk-PZNWIJRZ.cjs → chunk-TIVPOVIM.cjs} +2 -2
- package/dist/{chunk-BZZE4KIV.cjs → chunk-TX4NTH22.cjs} +155 -136
- package/dist/{chunk-NFNSPLGC.cjs → chunk-W7OFAROY.cjs} +14 -9
- package/dist/{chunk-N2A24FDF.cjs → chunk-YLETC7LP.cjs} +994 -953
- package/dist/{chunk-YC52LSPP.cjs → chunk-YYHQY3K6.cjs} +40 -40
- package/dist/{chunk-2FUTZKLB.cjs → chunk-Z5UCDEKZ.cjs} +6 -6
- package/dist/{chunk-SWF6TNZ3.cjs → chunk-ZGEWT3SF.cjs} +2 -2
- package/dist/{chunk-PQDCSZ3W.cjs → chunk-ZY34R37A.cjs} +85 -85
- package/dist/{chunk-5SZ4ILU3.cjs → chunk-ZZDFMUEU.cjs} +3 -3
- package/dist/{cloud-tenants-XEUUMABT.cjs → cloud-tenants-HKUUEUAY.cjs} +21 -21
- package/dist/components/case-studies/index.cjs +36 -36
- package/dist/components/chat/chat-composer.d.ts.map +1 -1
- package/dist/components/chat/chat-composer.js +4 -3
- package/dist/components/chat/chat-container.d.ts.map +1 -1
- package/dist/components/chat/chat-container.js +2 -2
- package/dist/components/chat/entity-cards/design-doc-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/design-doc-card.js +5 -17
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/dispatch.js +5 -7
- package/dist/components/chat/index.cjs +15 -15
- package/dist/components/contact/index.cjs +22 -22
- package/dist/components/docs/doc-viewer.d.ts +7 -7
- package/dist/components/docs/doc-viewer.d.ts.map +1 -1
- package/dist/components/docs/doc-viewer.js +29 -16
- package/dist/components/docs/docs-hub-page.d.ts +1 -1
- package/dist/components/docs/docs-hub-page.d.ts.map +1 -1
- package/dist/components/docs/docs-hub-page.js +4 -11
- package/dist/components/docs/index.cjs +25 -25
- package/dist/components/docs/use-document-tree.d.ts.map +1 -1
- package/dist/components/docs/use-document-tree.js +24 -24
- package/dist/components/embeds/claude-embed.d.ts +2 -2
- package/dist/components/embeds/claude-embed.d.ts.map +1 -1
- package/dist/components/embeds/claude-embed.js +3 -3
- package/dist/components/embeds/embed-iframe.d.ts +5 -3
- package/dist/components/embeds/embed-iframe.d.ts.map +1 -1
- package/dist/components/embeds/embed-iframe.js +4 -3
- package/dist/components/embeds/embed-viewer-frame.d.ts +12 -9
- package/dist/components/embeds/embed-viewer-frame.d.ts.map +1 -1
- package/dist/components/embeds/embed-viewer-frame.js +21 -7
- package/dist/components/embeds/figma-embed.d.ts +4 -2
- package/dist/components/embeds/figma-embed.d.ts.map +1 -1
- package/dist/components/embeds/figma-embed.js +4 -3
- package/dist/components/embeds/file-download-card.d.ts +3 -1
- package/dist/components/embeds/file-download-card.d.ts.map +1 -1
- package/dist/components/embeds/file-download-card.js +18 -3
- package/dist/components/embeds/google-sheets-viewer.d.ts +3 -1
- package/dist/components/embeds/google-sheets-viewer.d.ts.map +1 -1
- package/dist/components/embeds/google-sheets-viewer.js +7 -4
- package/dist/components/embeds/index.cjs +22 -22
- package/dist/components/embeds/pdf-viewer.d.ts +3 -1
- package/dist/components/embeds/pdf-viewer.d.ts.map +1 -1
- package/dist/components/embeds/pdf-viewer.js +6 -2
- package/dist/components/faq/index.cjs +23 -23
- package/dist/components/features/index.cjs +21 -21
- package/dist/components/features/status-filter-component.d.ts +7 -1
- package/dist/components/features/status-filter-component.d.ts.map +1 -1
- package/dist/components/features/status-filter-component.js +9 -3
- package/dist/components/help-center-pages/index.cjs +140 -140
- package/dist/components/index.cjs +137 -135
- package/dist/components/layout/back-button.d.ts +7 -1
- package/dist/components/layout/back-button.d.ts.map +1 -1
- package/dist/components/layout/back-button.js +34 -2
- package/dist/components/loading/unified-skeleton.d.ts +6 -1
- package/dist/components/loading/unified-skeleton.d.ts.map +1 -1
- package/dist/components/loading/unified-skeleton.js +13 -1
- package/dist/components/meeting-scheduler/index.cjs +119 -119
- package/dist/components/navigation/index.cjs +24 -24
- package/dist/components/onboarding-guides/index.cjs +24 -24
- package/dist/components/product-demo/index.cjs +33 -33
- package/dist/components/related-content/index.cjs +11 -11
- package/dist/components/tickets/index.cjs +28 -28
- package/dist/components/ui/file-manager/file-manager-table.d.ts.map +1 -1
- package/dist/components/ui/file-manager/file-manager-table.js +59 -50
- package/dist/components/ui/file-manager/index.cjs +71 -62
- package/dist/components/ui/index.cjs +23 -21
- package/dist/components/ui/markdown/engine.d.ts +5 -4
- package/dist/components/ui/markdown/engine.d.ts.map +1 -1
- package/dist/components/ui/markdown/engine.js +15 -15
- package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +1 -1
- package/dist/components/ui/markdown/rich/rich-markdown-renderer.js +7 -3
- package/dist/components/ui/people-cell.d.ts +9 -2
- package/dist/components/ui/people-cell.d.ts.map +1 -1
- package/dist/components/ui/people-cell.js +39 -23
- package/dist/components/ui/progress-bar.d.ts +6 -0
- package/dist/components/ui/progress-bar.d.ts.map +1 -1
- package/dist/components/ui/progress-bar.js +2 -0
- package/dist/components/ui/tag.d.ts +8 -0
- package/dist/components/ui/tag.d.ts.map +1 -1
- package/dist/components/ui/tag.js +5 -0
- package/dist/{devices-7YDIHVVP.cjs → devices-P7M4Z7VP.cjs} +22 -22
- package/dist/embed-shims/index.cjs +3 -3
- package/dist/embed-shims/next-image.cjs +3 -3
- package/dist/{embeddable-chat-Z3S37SSM.cjs → embeddable-chat-322NDFP5.cjs} +94 -94
- package/dist/hooks/index.cjs +6 -6
- package/dist/index.cjs +32 -30
- package/dist/{knowledge-SQVPQSKM.cjs → knowledge-TC7JLOWI.cjs} +22 -22
- package/dist/{logs-M5DFGUBC.cjs → logs-GPOYCP4B.cjs} +28 -28
- package/dist/{policies-T3VQEJUD.cjs → policies-ZUHOUMDD.cjs} +25 -25
- package/dist/{remote-session-EE6477O3.cjs → remote-session-NT4NG7EF.cjs} +10 -10
- package/dist/{scripts-JFRFJKPH.cjs → scripts-FOEVDGTH.cjs} +28 -28
- package/dist/{software-update-E7W3AAIY.cjs → software-update-XGTMNFST.cjs} +25 -25
- package/dist/{tickets-board-44HO3LR4.cjs → tickets-board-7FLGQEHF.cjs} +10 -10
- package/dist/types/design-doc.d.ts +45 -8
- package/dist/types/design-doc.d.ts.map +1 -1
- package/dist/types/doc-source.d.ts +2 -2
- package/dist/types/doc-source.d.ts.map +1 -1
- package/dist/utils/design-doc-readiness.d.ts +17 -15
- package/dist/utils/design-doc-readiness.d.ts.map +1 -1
- package/dist/utils/design-doc-readiness.js +5 -11
- package/dist/utils/doc-tree-nav.d.ts +7 -0
- package/dist/utils/doc-tree-nav.d.ts.map +1 -1
- package/dist/utils/doc-tree-nav.js +19 -0
- package/dist/utils/index.cjs +30 -13
- package/dist/utils/list-url.d.ts.map +1 -1
- package/dist/utils/list-url.js +2 -1
- package/dist/utils/source-grouping.d.ts +2 -2
- package/dist/utils/source-grouping.d.ts.map +1 -1
- package/dist/utils/source-grouping.js +1 -1
- package/dist/utils/source-icons.d.ts.map +1 -1
- package/dist/utils/source-icons.js +3 -0
- package/package.json +1 -1
- package/src/components/chat/chat-composer.tsx +10 -3
- package/src/components/chat/chat-container.tsx +6 -4
- package/src/components/chat/entity-cards/design-doc-card.tsx +12 -17
- package/src/components/chat/entity-cards/dispatch.tsx +10 -10
- package/src/components/docs/doc-viewer.tsx +56 -35
- package/src/components/docs/docs-hub-page.tsx +13 -37
- package/src/components/docs/use-document-tree.ts +33 -23
- package/src/components/embeds/claude-embed.tsx +4 -4
- package/src/components/embeds/embed-iframe.tsx +8 -5
- package/src/components/embeds/embed-viewer-frame.tsx +35 -15
- package/src/components/embeds/figma-embed.tsx +9 -4
- package/src/components/embeds/file-download-card.tsx +28 -4
- package/src/components/embeds/google-sheets-viewer.tsx +9 -3
- package/src/components/embeds/pdf-viewer.tsx +9 -2
- package/src/components/features/status-filter-component.tsx +15 -0
- package/src/components/layout/back-button.tsx +54 -2
- package/src/components/loading/unified-skeleton.tsx +18 -2
- package/src/components/ui/file-manager/file-manager-table.tsx +8 -3
- package/src/components/ui/markdown/engine.tsx +21 -30
- package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +4 -0
- package/src/components/ui/people-cell.tsx +53 -17
- package/src/components/ui/progress-bar.tsx +8 -0
- package/src/components/ui/tag.tsx +14 -0
- package/src/types/design-doc.ts +59 -10
- package/src/types/doc-source.ts +2 -2
- package/src/utils/design-doc-readiness.ts +22 -23
- package/src/utils/doc-tree-nav.ts +27 -0
- package/src/utils/list-url.ts +2 -1
- package/src/utils/source-grouping.ts +2 -2
- package/src/utils/source-icons.ts +3 -0
- package/dist/components/docs/skeletons.d.ts +0 -25
- package/dist/components/docs/skeletons.d.ts.map +0 -1
- package/dist/components/docs/skeletons.js +0 -83
- package/src/components/docs/skeletons.tsx +0 -131
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
import type { ChatSource } from '../components/chat/types/message.types';
|
|
25
25
|
/**
|
|
26
26
|
* Doc-table documentTypes: whole documents with their own viewer (markdown =
|
|
27
|
-
* product docs, data_room_doc = data room). They carry an in-app `path` (handed
|
|
27
|
+
* product docs, data_room_doc = data room, policy_doc = company policies). They carry an in-app `path` (handed
|
|
28
28
|
* to the host's composer as the slug), and a server never folds them
|
|
29
29
|
* into one citation (`groupsByTable`); the strip still draws them as one chip
|
|
30
30
|
* per table. The hub's per-source contract test pins this list to its
|
|
31
31
|
* doc-table configs.
|
|
32
32
|
*/
|
|
33
|
-
export declare const DOC_TABLE_TYPES: readonly ["markdown", "data_room_doc"];
|
|
33
|
+
export declare const DOC_TABLE_TYPES: readonly ["markdown", "data_room_doc", "policy_doc"];
|
|
34
34
|
/**
|
|
35
35
|
* Which tables a SERVER folds into one citation: rows of a knowledge table
|
|
36
36
|
* share one number (and reach the model as one table) unless they are whole
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-grouping.d.ts","sourceRoot":"","sources":["../../src/utils/source-grouping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wCAAwC,CAAC;AAGzE;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"source-grouping.d.ts","sourceRoot":"","sources":["../../src/utils/source-grouping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wCAAwC,CAAC;AAGzE;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,sDAAuD,CAAC;AAEpF;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE9E;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,KAAK,WAAW,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D;2EAC2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC;AAE3C;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,gBAAgB,EAAE,CAAC;IACzB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,GAAG,UAAU,CAcb;AA0DD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAqCvE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAU/D"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defaultDocumentTypeForTableId, getSourceLabel } from "./source-icons.js";
|
|
2
|
-
const DOC_TABLE_TYPES = ["markdown", "data_room_doc"];
|
|
2
|
+
const DOC_TABLE_TYPES = ["markdown", "data_room_doc", "policy_doc"];
|
|
3
3
|
function groupsByTable(documentType) {
|
|
4
4
|
return !DOC_TABLE_TYPES.includes(documentType ?? "");
|
|
5
5
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-icons.d.ts","sourceRoot":"","sources":["../../src/utils/source-icons.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"source-icons.d.ts","sourceRoot":"","sources":["../../src/utils/source-icons.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAsFpD,CAAC;AAEF;;6DAE6D;AAC7D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGxF;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA6EzD,CAAC;AAEF;;kEAEkE;AAClE,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,iCAAiC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAuEpE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEjF;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK5E"}
|
|
@@ -2,6 +2,7 @@ const SOURCE_ICON_NAMES = {
|
|
|
2
2
|
// Doc tables
|
|
3
3
|
"openframe-docs": "openframe",
|
|
4
4
|
"data-room-docs": "shield",
|
|
5
|
+
"company-policies": "book-open",
|
|
5
6
|
// CMS / programs
|
|
6
7
|
"blog-posts": "newspaper",
|
|
7
8
|
"product-releases": "rocket",
|
|
@@ -81,6 +82,7 @@ const SOURCE_LABELS_BY_TABLE = {
|
|
|
81
82
|
// Doc tables
|
|
82
83
|
"openframe-docs": "OpenFrame Docs",
|
|
83
84
|
"data-room-docs": "Data Room",
|
|
85
|
+
"company-policies": "Company Policies",
|
|
84
86
|
// CMS / programs
|
|
85
87
|
"blog-posts": "Blog Posts",
|
|
86
88
|
"product-releases": "Product Releases",
|
|
@@ -151,6 +153,7 @@ const DEFAULT_DOCUMENT_TYPE_TO_TABLE_ID = {
|
|
|
151
153
|
// Doc tables
|
|
152
154
|
markdown: "openframe-docs",
|
|
153
155
|
data_room_doc: "data-room-docs",
|
|
156
|
+
policy_doc: "company-policies",
|
|
154
157
|
// CMS / programs
|
|
155
158
|
blog_post: "blog-posts",
|
|
156
159
|
product_release: "product-releases",
|
package/package.json
CHANGED
|
@@ -14,6 +14,13 @@ import { ModelDisplay } from './model-display';
|
|
|
14
14
|
import type { ChatInputRef, ModelDisplayProps } from './types/component.types';
|
|
15
15
|
import type { ChatContextItem, ChatContextPickerConfig } from './types/context-item.types';
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* The top strip's corners: the card's radius (`rounded-md`, `--radius` - 2px) less the card's 1px border, so the strip
|
|
19
|
+
* meets the border's inner curve. At the card's own radius it sits 1px inside that curve and leaves a sliver of the
|
|
20
|
+
* card's fill at each corner. The card cannot clip it instead: the picker and the memory popover float out of it.
|
|
21
|
+
*/
|
|
22
|
+
const TOP_STRIP_ROUNDING_CLASS = 'rounded-t-[calc(var(--radius)_-_3px)]';
|
|
23
|
+
|
|
17
24
|
/** Why the composer takes no input, in the user's words — shown where the prompt would be. */
|
|
18
25
|
export interface ChatComposerLock {
|
|
19
26
|
placeholder: string;
|
|
@@ -248,7 +255,7 @@ export function ChatComposer({
|
|
|
248
255
|
// `border-b`) stack flush above the input; the card owns the border
|
|
249
256
|
// / radius / focus ring. No `overflow-hidden` so the picker and the
|
|
250
257
|
// memory popover can float above the input (whichever strip comes
|
|
251
|
-
// first gets `
|
|
258
|
+
// first gets `TOP_STRIP_ROUNDING_CLASS`).
|
|
252
259
|
// `has-[[data-editor]:focus]` (not `focus-within`) so the accent
|
|
253
260
|
// border reacts ONLY to the composer editor — not the picker's search
|
|
254
261
|
// input or item buttons, which also live inside this card. The editor
|
|
@@ -264,7 +271,7 @@ export function ChatComposer({
|
|
|
264
271
|
resolveIcon={resolveContextIcon}
|
|
265
272
|
disabled={sending || locked}
|
|
266
273
|
leading={contextLeading}
|
|
267
|
-
className=
|
|
274
|
+
className={TOP_STRIP_ROUNDING_CLASS}
|
|
268
275
|
/>
|
|
269
276
|
{selected.length > 0 && (
|
|
270
277
|
<ChatContextChipStrip
|
|
@@ -275,7 +282,7 @@ export function ChatComposer({
|
|
|
275
282
|
className={cn(
|
|
276
283
|
'border-b border-ods-border bg-ods-bg p-2',
|
|
277
284
|
// Only the TOP strip rounds into the card corner.
|
|
278
|
-
memory.length === 0 && !contextLeading &&
|
|
285
|
+
memory.length === 0 && !contextLeading && TOP_STRIP_ROUNDING_CLASS,
|
|
279
286
|
)}
|
|
280
287
|
/>
|
|
281
288
|
)}
|
|
@@ -111,7 +111,9 @@ const ChatHeader = forwardRef<HTMLDivElement, ChatHeaderProps>(
|
|
|
111
111
|
className={cn(cardClasses, 'absolute right-full top-0 mr-[var(--spacing-system-s)] hover:bg-ods-bg-hover')}
|
|
112
112
|
/>
|
|
113
113
|
)}
|
|
114
|
-
|
|
114
|
+
{/* The card clips its content at the border's inner curve, so a filled section at its edge (the MSP card)
|
|
115
|
+
meets the border; nothing in it floats out. */}
|
|
116
|
+
<div className={cn(cardClasses, !bare && 'overflow-hidden')}>
|
|
115
117
|
<div className="flex items-center justify-between gap-4 px-4 py-3">
|
|
116
118
|
<div className="flex items-center gap-3">
|
|
117
119
|
{userIcon && !isLoading ? (
|
|
@@ -176,8 +178,8 @@ const ChatHeader = forwardRef<HTMLDivElement, ChatHeaderProps>(
|
|
|
176
178
|
</div>
|
|
177
179
|
{/* MSP branding slot — home-screen only; `ticketInfo` (open chat)
|
|
178
180
|
claims this space, so it wins when both are provided. The card
|
|
179
|
-
already draws the chrome
|
|
180
|
-
|
|
181
|
+
already draws the chrome and clips its corners: strip the
|
|
182
|
+
section's own ring and rounding. */}
|
|
181
183
|
{!ticketInfo && (mspLoading || mspOrganization) && (
|
|
182
184
|
<>
|
|
183
185
|
<div className="h-px bg-ods-border" />
|
|
@@ -185,7 +187,7 @@ const ChatHeader = forwardRef<HTMLDivElement, ChatHeaderProps>(
|
|
|
185
187
|
name=""
|
|
186
188
|
{...mspOrganization}
|
|
187
189
|
loading={mspLoading || !mspOrganization}
|
|
188
|
-
className={cn('rounded-none ring-0',
|
|
190
|
+
className={cn('rounded-none ring-0', mspOrganization?.className)}
|
|
189
191
|
/>
|
|
190
192
|
</>
|
|
191
193
|
)}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* DesignDocCard (pure presentation) — a design doc as the entity card every surface shows it with: the full card
|
|
5
5
|
* (`default`, the `RoadmapCard` layout without its image slot, since a doc has no image of its own and a constant glyph
|
|
6
|
-
* says nothing: title, DRI and date,
|
|
6
|
+
* says nothing: title, DRI and date, its status (the status a person set, else its readiness: the hub's own wording, `design-doc-readiness`), summary, sign-off caption and the people) for admin surfaces, and the chat's compact card (`DesignDocChatCard` in `dispatch.tsx`, which reads
|
|
7
7
|
* the same readiness and meta). The card writes no click logic beyond its `href` anchor.
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -12,14 +12,11 @@ import { TEXT_LINES_MIN } from '../../../styles/component-sizes';
|
|
|
12
12
|
import type { DesignDoc } from '../../../types/design-doc';
|
|
13
13
|
import { cn } from '../../../utils/cn';
|
|
14
14
|
import { formatDateShort } from '../../../utils/date-formatters';
|
|
15
|
-
import {
|
|
16
|
-
DESIGN_DOC_READINESS_DISPLAY,
|
|
17
|
-
designDocReadiness,
|
|
18
|
-
formatCompletionLabelWithBlockers,
|
|
19
|
-
} from '../../../utils/design-doc-readiness';
|
|
15
|
+
import { designDocStatusDisplay, formatCompletionLabelWithBlockers } from '../../../utils/design-doc-readiness';
|
|
20
16
|
import { TextLineSkeleton } from '../../loading/unified-skeleton';
|
|
21
17
|
import { AvatarStack, type AvatarStackPerson } from '../../ui/avatar-stack';
|
|
22
18
|
import { useLoadingScope } from '../../ui/loading-scope';
|
|
19
|
+
import { PaletteBadge } from '../../ui/palette-badge';
|
|
23
20
|
import { StatusBadge } from '../../ui/status-badge';
|
|
24
21
|
import { safeHref } from '../utils/compact-card-classes';
|
|
25
22
|
import { CONTENT_CARD_FRAME_CLASS, CONTENT_CARD_SKELETON_FRAME_CLASS } from './content-card-frame';
|
|
@@ -102,7 +99,7 @@ function DesignDocCardSkeleton({ className }: { className?: string } = {}) {
|
|
|
102
99
|
}
|
|
103
100
|
|
|
104
101
|
export function DesignDocCard({ doc, href, target, rel, className }: DesignDocCardProps) {
|
|
105
|
-
const
|
|
102
|
+
const status = designDocStatusDisplay(doc);
|
|
106
103
|
const people = designDocPeople(doc);
|
|
107
104
|
const link = safeHref(href ?? null);
|
|
108
105
|
if (useLoadingScope()) return <DesignDocCardSkeleton className={className} />;
|
|
@@ -117,17 +114,15 @@ export function DesignDocCard({ doc, href, target, rel, className }: DesignDocCa
|
|
|
117
114
|
<p className="truncate text-ods-text-secondary text-h5">{designDocMetaLine(doc)}</p>
|
|
118
115
|
</div>
|
|
119
116
|
</div>
|
|
120
|
-
|
|
121
|
-
text={
|
|
122
|
-
|
|
123
|
-
variant="button"
|
|
124
|
-
singleLine
|
|
125
|
-
className="hidden md:inline-flex"
|
|
126
|
-
/>
|
|
127
|
-
</div>
|
|
128
|
-
<div className="md:hidden">
|
|
129
|
-
<StatusBadge text={readiness.label} colorScheme={readiness.scheme} variant="button" singleLine />
|
|
117
|
+
{status ? (
|
|
118
|
+
<PaletteBadge text={status.label} color={status.color_scheme} className="hidden md:inline-flex" />
|
|
119
|
+
) : null}
|
|
130
120
|
</div>
|
|
121
|
+
{status ? (
|
|
122
|
+
<div className="md:hidden">
|
|
123
|
+
<PaletteBadge text={status.label} color={status.color_scheme} />
|
|
124
|
+
</div>
|
|
125
|
+
) : null}
|
|
131
126
|
<div className={DESIGN_DOC_CARD_SUMMARY_ROW}>
|
|
132
127
|
<p className="line-clamp-3 text-ods-text-secondary text-h4">{doc.summary || ''}</p>
|
|
133
128
|
</div>
|
|
@@ -48,11 +48,7 @@ import {
|
|
|
48
48
|
} from '../../../types/trust-center';
|
|
49
49
|
import { isAppOriginUrl } from '../../../utils/app-origins';
|
|
50
50
|
import { formatDateShort } from '../../../utils/date-formatters';
|
|
51
|
-
import {
|
|
52
|
-
DESIGN_DOC_READINESS_DISPLAY,
|
|
53
|
-
designDocReadiness,
|
|
54
|
-
formatCompletionLabel,
|
|
55
|
-
} from '../../../utils/design-doc-readiness';
|
|
51
|
+
import { designDocStatusDisplay, formatCompletionLabel } from '../../../utils/design-doc-readiness';
|
|
56
52
|
import { faqItemAnchor } from '../../../utils/faq-anchor';
|
|
57
53
|
import { formatDateUTC as formatDate } from '../../../utils/format';
|
|
58
54
|
import { programMetaFormatters, programMetaLine } from '../../../utils/program-instant';
|
|
@@ -88,6 +84,7 @@ import type { LoadedProductReleaseCardProps } from '../../shared/product-release
|
|
|
88
84
|
import { ProductReleaseCard } from '../../shared/product-release/product-release-card';
|
|
89
85
|
import type { ActionsMenuGroup } from '../../ui/actions-menu';
|
|
90
86
|
import { ExternalMediaNotice } from '../../ui/markdown/external-media-notice';
|
|
87
|
+
import { tagVariantForBadgeColor } from '../../ui/tag';
|
|
91
88
|
import { VendorIcon } from '../../vendor-icon';
|
|
92
89
|
import { useChatPanel } from '../chat-panel-context';
|
|
93
90
|
import type { ChatRef } from '../chat-ref.types';
|
|
@@ -1046,8 +1043,8 @@ function CustomerInterviewChatCard({
|
|
|
1046
1043
|
}
|
|
1047
1044
|
|
|
1048
1045
|
/**
|
|
1049
|
-
* Design doc → the doc glyph, its
|
|
1050
|
-
*
|
|
1046
|
+
* Design doc → the doc glyph, its status as the pill (the status a person set, else its readiness, both vocabulary rows the hub resolves onto the
|
|
1047
|
+
* doc: `designDocStatusDisplay`) and "DRI · updated · n/m signed off" under the title. The hub's card route attaches the doc's list row as
|
|
1051
1048
|
* `item.doc`; a row without it (an older hub) renders the ref's own title and preview.
|
|
1052
1049
|
*/
|
|
1053
1050
|
function DesignDocChatCard({
|
|
@@ -1063,7 +1060,7 @@ function DesignDocChatCard({
|
|
|
1063
1060
|
}) {
|
|
1064
1061
|
const displayRef = fetchedItemDisplayRef(item, chatRef);
|
|
1065
1062
|
const doc = item?.doc;
|
|
1066
|
-
const
|
|
1063
|
+
const status = doc ? designDocStatusDisplay(doc) : null;
|
|
1067
1064
|
return (
|
|
1068
1065
|
<MingoInfoCard
|
|
1069
1066
|
title={doc?.title ?? displayRef.title}
|
|
@@ -1074,8 +1071,8 @@ function DesignDocChatCard({
|
|
|
1074
1071
|
}
|
|
1075
1072
|
icon={<FileContentIcon size={24} />}
|
|
1076
1073
|
status={
|
|
1077
|
-
|
|
1078
|
-
? { label:
|
|
1074
|
+
status
|
|
1075
|
+
? { label: status.label, variant: tagVariantForBadgeColor(status.color_scheme) }
|
|
1079
1076
|
: { label: 'Design doc', variant: 'grey' }
|
|
1080
1077
|
}
|
|
1081
1078
|
anchorProps={buildAnchorProps(displayRef.url, isNewTab, displayRef.targetPlatform)}
|
|
@@ -1738,6 +1735,9 @@ export const CHAT_CARD_REGISTRY: Record<string, ChatCardRegistryEntry> = {
|
|
|
1738
1735
|
data_room_doc: refHydratedEntry('data_room_doc', 'Data-room doc', (displayRef, opts) => (
|
|
1739
1736
|
<DataRoomDocChatCard chatRef={displayRef} isNewTab={opts.isNewTab} discuss={opts.discuss} />
|
|
1740
1737
|
)),
|
|
1738
|
+
policy_doc: refHydratedEntry('policy_doc', 'Company policy', (displayRef, opts) => (
|
|
1739
|
+
<DataRoomDocChatCard chatRef={displayRef} isNewTab={opts.isNewTab} discuss={opts.discuss} />
|
|
1740
|
+
)),
|
|
1741
1741
|
markdown: refHydratedEntry('markdown', 'Doc page (markdown)', (displayRef, opts) => (
|
|
1742
1742
|
<DataRoomDocChatCard chatRef={displayRef} isNewTab={opts.isNewTab} discuss={opts.discuss} />
|
|
1743
1743
|
)),
|
|
@@ -4,15 +4,15 @@ import type React from 'react';
|
|
|
4
4
|
import { useMemo } from 'react';
|
|
5
5
|
import { useRouter } from '../../embed-shims/next-navigation';
|
|
6
6
|
import type { DocContent, DocNode, DocRenderHandlers, DocSourceId } from '../../types/doc-source';
|
|
7
|
-
import { findDocNodeByPath } from '../../utils/doc-tree-nav';
|
|
7
|
+
import { findDocNodeByPath, rootDocumentPath } from '../../utils/doc-tree-nav';
|
|
8
8
|
import { PageShell } from '../layout/article-detail-layout';
|
|
9
9
|
import { PageLayout } from '../layout/page-layout';
|
|
10
|
-
import { UnifiedSkeleton } from '../loading/unified-skeleton';
|
|
11
10
|
import { MultiLevelNavigation, MobileNavigationDropdown } from '../navigation/multi-level-navigation';
|
|
12
11
|
import { StickySectionNav } from '../navigation/sticky-section-nav';
|
|
13
12
|
import { PersistentSidebar, PersistentMobileDropdown } from '../persistent-filter-controls';
|
|
14
13
|
import { DocSearchBar, useDocSearch } from '../shared/doc-search';
|
|
15
14
|
import { DataSyncLine } from '../ui/data-attribution';
|
|
15
|
+
import { LoadingScope } from '../ui/loading-scope';
|
|
16
16
|
import { useDocNavigation } from './doc-navigation-context';
|
|
17
17
|
import { useDocsResolveLink } from './use-docs-resolve-link';
|
|
18
18
|
import { useDocumentTree } from './use-document-tree';
|
|
@@ -46,13 +46,6 @@ export interface DocViewerProps {
|
|
|
46
46
|
*/
|
|
47
47
|
renderContent: (content: DocContent, handlers: DocRenderHandlers) => React.ReactNode;
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
* Render the loading skeleton. Receives the selected node's `documentType`
|
|
51
|
-
* (undefined while structure is still loading) so the caller can return a
|
|
52
|
-
* markdown-shaped skeleton vs an embed-shaped skeleton.
|
|
53
|
-
*/
|
|
54
|
-
renderSkeleton: (documentType: DocNode['documentType']) => React.ReactNode;
|
|
55
|
-
|
|
56
49
|
/**
|
|
57
50
|
* Chat-source identifier — passed in by the page shell from server-side
|
|
58
51
|
* `currentPlatform()`. Lib has no platform context; the page shell is the
|
|
@@ -88,6 +81,13 @@ export interface DocViewerProps {
|
|
|
88
81
|
/** Initial doc path (URL `[...path]`). */
|
|
89
82
|
docPath?: string;
|
|
90
83
|
|
|
84
|
+
/**
|
|
85
|
+
* The type of the document at `docPath`, when the page knows it before the tree is read (the host read it on the
|
|
86
|
+
* server). The content's loading state takes its shape from it on a page's first load (a Figma deck draws the embed
|
|
87
|
+
* skeleton, not a text page); once the tree is read, the selected node's own type decides.
|
|
88
|
+
*/
|
|
89
|
+
initialDocumentType?: DocNode['documentType'];
|
|
90
|
+
|
|
91
91
|
/** Sidebar header copy (`'DOCUMENTATION'`, `'DATA ROOM'`). */
|
|
92
92
|
sidebarLabel?: string;
|
|
93
93
|
|
|
@@ -113,7 +113,7 @@ export interface DocViewerProps {
|
|
|
113
113
|
/** Base route path for URL navigation. */
|
|
114
114
|
baseRoute: string;
|
|
115
115
|
|
|
116
|
-
/**
|
|
116
|
+
/** What the viewer says when the source holds no documents yet (default "No documents yet."). */
|
|
117
117
|
emptyStateText?: string;
|
|
118
118
|
|
|
119
119
|
/** Whether to render the doc-search bar (bound to chat). */
|
|
@@ -141,12 +141,13 @@ export function DocViewer(props: DocViewerProps) {
|
|
|
141
141
|
|
|
142
142
|
/** The "On this page" column's heading: static copy, drawn for real loaded and loading. */
|
|
143
143
|
const ON_THIS_PAGE_LABEL = 'ON THIS PAGE';
|
|
144
|
+
/** The loading section list's click handler: it has no sections to click. */
|
|
145
|
+
const NO_SECTION_CLICK = () => {};
|
|
144
146
|
const ON_THIS_PAGE_HEADING_CLASS = 'mb-4 font-semibold text-ods-text-secondary text-h5';
|
|
145
147
|
|
|
146
148
|
function DocViewerContent({
|
|
147
149
|
sourceId,
|
|
148
150
|
renderContent,
|
|
149
|
-
renderSkeleton,
|
|
150
151
|
chatSource,
|
|
151
152
|
title,
|
|
152
153
|
subtitle,
|
|
@@ -154,6 +155,7 @@ function DocViewerContent({
|
|
|
154
155
|
className = '',
|
|
155
156
|
shell = true,
|
|
156
157
|
docPath,
|
|
158
|
+
initialDocumentType,
|
|
157
159
|
sidebarLabel = 'DOCUMENTATION',
|
|
158
160
|
structureEndpoint,
|
|
159
161
|
contentEndpoint,
|
|
@@ -246,11 +248,31 @@ function DocViewerContent({
|
|
|
246
248
|
// - the article max-width + sticky-nav visibility (markdown only)
|
|
247
249
|
// `undefined` documentType is treated as `'markdown'` (per the DocNode
|
|
248
250
|
// discriminator's documented default).
|
|
251
|
+
// Before the tree is read, the host's `initialDocumentType` (the page's own document) stands in. Once it is read, the
|
|
252
|
+
// root takes the type of the document it shows (`rootDocumentPath`), so a root Figma or PDF never flips to the text
|
|
253
|
+
// layout between the tree arriving and the document arriving.
|
|
254
|
+
const selectedNodePath =
|
|
255
|
+
structure.length > 0 && !selectedPath ? rootDocumentPath(structure, folderIndexFile) : selectedPath;
|
|
249
256
|
const selectedNodeDocType =
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
257
|
+
structure.length > 0
|
|
258
|
+
? selectedNodePath
|
|
259
|
+
? findDocNodeByPath(selectedNodePath, structure)?.documentType
|
|
260
|
+
: undefined
|
|
261
|
+
: initialDocumentType;
|
|
262
|
+
// While a document loads, `content` is still the PREVIOUS document's: the layout (the "On this page" column, the
|
|
263
|
+
// article width) follows the document being opened, never the one being left, or opening a Figma deck from a text
|
|
264
|
+
// page draws the text page's column beside the deck's skeleton. Loaded, the content's own type decides.
|
|
265
|
+
const isLoadingDocument = isLoadingContent || isLoadingStructure;
|
|
266
|
+
// While a document loads: its renderer with an EMPTY document of its type (the selected node's, or the host's
|
|
267
|
+
// `initialDocumentType` before the tree is read), drawn in a loading scope, so every renderer shows its own loading
|
|
268
|
+
// form.
|
|
269
|
+
const loadingContent = isLoadingDocument
|
|
270
|
+
? renderContent(
|
|
271
|
+
{ content: '', sections: [], path: selectedPath ?? '', documentType: selectedNodeDocType },
|
|
272
|
+
{ onInternalLinkClick: navigateToDoc, currentPath: selectedPath ?? '', sourceId },
|
|
273
|
+
)
|
|
274
|
+
: null;
|
|
275
|
+
const activeDocType = isLoadingDocument ? selectedNodeDocType : (content?.documentType ?? selectedNodeDocType);
|
|
254
276
|
const isMarkdownContent = !activeDocType || activeDocType === 'markdown';
|
|
255
277
|
const showStickyNav = isMarkdownContent;
|
|
256
278
|
|
|
@@ -266,11 +288,10 @@ function DocViewerContent({
|
|
|
266
288
|
const containerBgStyle =
|
|
267
289
|
colorPalette.containerBackground !== 'transparent' ? { backgroundColor: colorPalette.containerBackground } : {};
|
|
268
290
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const resolvedEmptyText = emptyStateText || defaultEmptyText;
|
|
291
|
+
// A source with documents and none open asks for one; an empty source says so in the host's words (a reader may
|
|
292
|
+
// not be the one who adds documents, so the default names no admin screen).
|
|
293
|
+
const resolvedEmptyText =
|
|
294
|
+
structure.length > 0 ? 'Select a document from the sidebar to view' : emptyStateText || 'No documents yet.';
|
|
274
295
|
|
|
275
296
|
// Unified header: title/subtitle route through the canonical (frozen)
|
|
276
297
|
// `PageLayout` `TitleBlock` (text-h2) — same as every other help-center page —
|
|
@@ -365,8 +386,10 @@ function DocViewerContent({
|
|
|
365
386
|
(app-globals.css) so content wraps instead of overflowing. */}
|
|
366
387
|
<div className={`w-full min-w-0 ${isMarkdownContent ? 'mx-auto max-w-4xl' : ''}`}>
|
|
367
388
|
<article className="space-y-2">
|
|
368
|
-
{
|
|
369
|
-
|
|
389
|
+
{isLoadingDocument ? (
|
|
390
|
+
// The document being opened draws its own loading form: its renderer, in a loading scope,
|
|
391
|
+
// with an empty document of its type (no words, no address), never a skeleton picked here.
|
|
392
|
+
<LoadingScope>{loadingContent}</LoadingScope>
|
|
370
393
|
) : !content ? (
|
|
371
394
|
<div className="py-16 text-center">
|
|
372
395
|
<p className="text-ods-text-secondary text-h4">{resolvedEmptyText}</p>
|
|
@@ -377,22 +400,20 @@ function DocViewerContent({
|
|
|
377
400
|
</article>
|
|
378
401
|
</div>
|
|
379
402
|
|
|
380
|
-
{isMarkdownContent &&
|
|
403
|
+
{isMarkdownContent && isLoadingDocument && (
|
|
381
404
|
<div className="hidden content-lg:block">
|
|
382
405
|
<div className="sticky top-24">
|
|
383
406
|
<h3 className={ON_THIS_PAGE_HEADING_CLASS}>{ON_THIS_PAGE_LABEL}</h3>
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
))}
|
|
395
|
-
</div>
|
|
407
|
+
{/* The section list's own loading form (`StickySectionNav` with no sections, in a scope). */}
|
|
408
|
+
<LoadingScope>
|
|
409
|
+
<StickySectionNav
|
|
410
|
+
sections={[]}
|
|
411
|
+
activeSection=""
|
|
412
|
+
onSectionClick={NO_SECTION_CLICK}
|
|
413
|
+
ribbonPosition="left"
|
|
414
|
+
ribbonColor="var(--ods-accent)"
|
|
415
|
+
/>
|
|
416
|
+
</LoadingScope>
|
|
396
417
|
</div>
|
|
397
418
|
</div>
|
|
398
419
|
)}
|
|
@@ -5,7 +5,6 @@ import { FileDownloadCard } from '../embeds/file-download-card';
|
|
|
5
5
|
import { GoogleSheetsViewer } from '../embeds/google-sheets-viewer';
|
|
6
6
|
import { PdfViewer } from '../embeds/pdf-viewer';
|
|
7
7
|
import { DocViewer, type DocViewerProps } from './doc-viewer';
|
|
8
|
-
import { MarkdownSkeleton, EmbedSkeleton } from './skeletons';
|
|
9
8
|
|
|
10
9
|
type DocRenderer = (content: DocContent, handlers: DocRenderHandlers) => React.ReactNode;
|
|
11
10
|
|
|
@@ -20,7 +19,7 @@ type DocRenderer = (content: DocContent, handlers: DocRenderHandlers) => React.R
|
|
|
20
19
|
*/
|
|
21
20
|
export type DocumentTypeRenderers = { markdown: DocRenderer } & Partial<Record<DocumentType, DocRenderer>>;
|
|
22
21
|
|
|
23
|
-
export interface DocsHubPageProps extends Omit<DocViewerProps, 'renderContent' | '
|
|
22
|
+
export interface DocsHubPageProps extends Omit<DocViewerProps, 'renderContent' | 'showAIChat'> {
|
|
24
23
|
/** Per-document-type renderer map. `markdown` is REQUIRED. */
|
|
25
24
|
documentTypeRenderers: DocumentTypeRenderers;
|
|
26
25
|
|
|
@@ -42,32 +41,20 @@ const defaultFallbackRenderer: DocRenderer = () => (
|
|
|
42
41
|
</div>
|
|
43
42
|
);
|
|
44
43
|
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// the
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const defaultPdfRenderer: DocRenderer = (content, handlers) =>
|
|
52
|
-
content.fileUrl ? (
|
|
53
|
-
<PdfViewer src={content.fileUrl} fileName={content.fileName} />
|
|
54
|
-
) : (
|
|
55
|
-
defaultFallbackRenderer(content, handlers)
|
|
56
|
-
);
|
|
44
|
+
// Each viewer owns both its states without a document: in a loading scope (the document is being read) it draws its
|
|
45
|
+
// own loading form, otherwise its "not available" state when the DAL left the URL empty. So the renderers always
|
|
46
|
+
// render the viewer, and the viewer never mounts a frame without a source.
|
|
47
|
+
const defaultPdfRenderer: DocRenderer = content => (
|
|
48
|
+
<PdfViewer src={content.fileUrl ?? ''} fileName={content.fileName} />
|
|
49
|
+
);
|
|
57
50
|
|
|
58
|
-
const defaultGoogleSheetRenderer: DocRenderer =
|
|
59
|
-
content.externalUrl
|
|
60
|
-
|
|
61
|
-
) : (
|
|
62
|
-
defaultFallbackRenderer(content, handlers)
|
|
63
|
-
);
|
|
51
|
+
const defaultGoogleSheetRenderer: DocRenderer = content => (
|
|
52
|
+
<GoogleSheetsViewer externalUrl={content.externalUrl ?? ''} fileName={content.fileName} />
|
|
53
|
+
);
|
|
64
54
|
|
|
65
|
-
const defaultFigmaRenderer: DocRenderer =
|
|
66
|
-
content.externalUrl
|
|
67
|
-
|
|
68
|
-
) : (
|
|
69
|
-
defaultFallbackRenderer(content, handlers)
|
|
70
|
-
);
|
|
55
|
+
const defaultFigmaRenderer: DocRenderer = content => (
|
|
56
|
+
<FigmaEmbed url={content.externalUrl ?? ''} title={content.fileName} iframeLoading="eager" />
|
|
57
|
+
);
|
|
71
58
|
|
|
72
59
|
const defaultFileRenderer: DocRenderer = content => (
|
|
73
60
|
<FileDownloadCard
|
|
@@ -78,16 +65,6 @@ const defaultFileRenderer: DocRenderer = content => (
|
|
|
78
65
|
/>
|
|
79
66
|
);
|
|
80
67
|
|
|
81
|
-
/** The page's own loading form per document type: the markdown article's, else the embed viewer's. */
|
|
82
|
-
const documentLoadingForm = (documentType: DocumentType | undefined) =>
|
|
83
|
-
!documentType || documentType === 'markdown' ? (
|
|
84
|
-
<MarkdownSkeleton />
|
|
85
|
-
) : (
|
|
86
|
-
// Forward the documentType so the embed skeleton renders the right
|
|
87
|
-
// shape (PDF=2 buttons, sheets/figma=1 button, file=centered card).
|
|
88
|
-
<EmbedSkeleton documentType={documentType} />
|
|
89
|
-
);
|
|
90
|
-
|
|
91
68
|
/**
|
|
92
69
|
* Embeddable docs-hub page. Bundles `<DocViewer>` with safe defaults so the
|
|
93
70
|
* minimum embed is a one-line mount (consumer only has to supply
|
|
@@ -132,7 +109,6 @@ export function DocsHubPage({
|
|
|
132
109
|
className={className}
|
|
133
110
|
sidebarLabel={sidebarLabel}
|
|
134
111
|
renderContent={renderContent}
|
|
135
|
-
renderSkeleton={documentLoadingForm}
|
|
136
112
|
/>
|
|
137
113
|
);
|
|
138
114
|
}
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
findDocNodeByPath,
|
|
10
10
|
getDocAncestorNodeIds,
|
|
11
11
|
DEFAULT_FOLDER_INDEX_FILE,
|
|
12
|
+
findFirstDocPath,
|
|
13
|
+
rootDocumentPath,
|
|
12
14
|
} from '../../utils/doc-tree-nav';
|
|
13
15
|
import { contentFetch } from '../../utils/embed-content-fetch';
|
|
14
16
|
import { navigateSamePageHash, HUB_HEADER_OFFSET_PX } from '../../utils/same-page-hash-nav';
|
|
@@ -34,21 +36,6 @@ function scrollToContent() {
|
|
|
34
36
|
* its README) instead of a blank panel or a redundant in-page listing — the
|
|
35
37
|
* sidebar tree is the directory browser. Returns null for a folder with no docs.
|
|
36
38
|
*/
|
|
37
|
-
function findFirstDocPath(folder: DocNode): string | null {
|
|
38
|
-
const children = folder.children ?? [];
|
|
39
|
-
for (const child of children) {
|
|
40
|
-
if (child.type === 'file' && !child.path.toLowerCase().endsWith('.mmd')) {
|
|
41
|
-
return child.path;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
for (const child of children) {
|
|
45
|
-
if (child.type === 'folder') {
|
|
46
|
-
const nested = findFirstDocPath(child);
|
|
47
|
-
if (nested) return nested;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
39
|
|
|
53
40
|
// ---------------------------------------------------------------------------
|
|
54
41
|
// Wire decoding for the two doc-source endpoints
|
|
@@ -220,6 +207,11 @@ export function useDocumentTree(config: UseDocumentTreeConfig, initialPath?: str
|
|
|
220
207
|
const cleanInitialPath = stripFolderIndexFromPath(initialPath?.replace(/\/$/, '') || '', folderIndexFile);
|
|
221
208
|
|
|
222
209
|
const [structure, setStructure] = useState<DocNode[]>([]);
|
|
210
|
+
// The latest committed tree, for a request that settles after the tree arrived (its closure holds the old one).
|
|
211
|
+
const structureRef = useRef(structure);
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
structureRef.current = structure;
|
|
214
|
+
}, [structure]);
|
|
223
215
|
const [selectedPath, setSelectedPath] = useState<string>(cleanInitialPath);
|
|
224
216
|
const [content, setContent] = useState<DocContent | null>(null);
|
|
225
217
|
const [isLoadingStructure, setIsLoadingStructure] = useState(true);
|
|
@@ -409,12 +401,16 @@ export function useDocumentTree(config: UseDocumentTreeConfig, initialPath?: str
|
|
|
409
401
|
// No-README FOLDER → stay silent: the content effect resolves it to
|
|
410
402
|
// the folder's first child, so the folder-path 404 from the direct-
|
|
411
403
|
// load speculative fetch is expected, not an error to flash.
|
|
412
|
-
|
|
404
|
+
// Classified against the LATEST tree (`structureRef`), never the one captured when this request went out: the tree
|
|
405
|
+
// often arrives while the request is in flight, and judged against the empty tree of the call a missing
|
|
406
|
+
// document read as "not classified yet, wait", so nothing settled it and the page stayed a skeleton.
|
|
407
|
+
const latestStructure = structureRef.current;
|
|
408
|
+
const probe = findDocNodeByPath(stripFolderIndexFromPath(path, folderIndexFile), latestStructure);
|
|
413
409
|
const probeIsNoReadmeFolder = !!probe && probe.type === 'folder' && !probe.hasReadme;
|
|
414
410
|
// Before the structure has loaded to classify the path (`structure`
|
|
415
411
|
// is [] in the speculative call's closure), only silence FOLDER-LIKE
|
|
416
412
|
// paths (no `.md`) — a genuinely missing `*.md` leaf must still error.
|
|
417
|
-
const preStructureFolderLike =
|
|
413
|
+
const preStructureFolderLike = latestStructure.length === 0 && !path.endsWith('.md');
|
|
418
414
|
if (probeIsNoReadmeFolder || preStructureFolderLike) {
|
|
419
415
|
// Superseded by the targeted fetch (first-child / reclassified path)
|
|
420
416
|
// the structure effect fires. Null the request id so the `finally`
|
|
@@ -507,7 +503,15 @@ export function useDocumentTree(config: UseDocumentTreeConfig, initialPath?: str
|
|
|
507
503
|
let pathToFetch: string | null = null;
|
|
508
504
|
|
|
509
505
|
if (selectedPath === '') {
|
|
510
|
-
|
|
506
|
+
// The root shows its landing document (the folder index); a source with none shows its FIRST document, the
|
|
507
|
+
// rule a no-README folder follows. Asking for the missing index again would 404 for ever.
|
|
508
|
+
const rootPath = rootDocumentPath(structure, folderIndexFile);
|
|
509
|
+
if (!rootPath) {
|
|
510
|
+
// A tree with folders and no document at all: nothing to fetch; the empty state is derived below.
|
|
511
|
+
lastFetchedPath.current = null;
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
pathToFetch = rootPath;
|
|
511
515
|
} else {
|
|
512
516
|
const node = findDocNodeByPath(selectedPath, structure);
|
|
513
517
|
|
|
@@ -731,19 +735,25 @@ export function useDocumentTree(config: UseDocumentTreeConfig, initialPath?: str
|
|
|
731
735
|
// the content effect, which reached the same conclusion one commit later and
|
|
732
736
|
// meanwhile left the PREVIOUS document's body on screen.
|
|
733
737
|
const isEmptyFolder = useMemo(() => {
|
|
734
|
-
if (
|
|
738
|
+
if (structure.length === 0) return false;
|
|
739
|
+
// The root of a source whose tree holds no document at all is an empty folder too.
|
|
740
|
+
if (!selectedPath) return !rootDocumentPath(structure, folderIndexFile);
|
|
735
741
|
const node = findDocNodeByPath(selectedPath, structure);
|
|
736
742
|
return !!node && node.type === 'folder' && !node.hasReadme && !findFirstDocPath(node);
|
|
737
|
-
}, [selectedPath, structure]);
|
|
743
|
+
}, [selectedPath, structure, folderIndexFile]);
|
|
744
|
+
// A source with no documents at all (its tree is read and empty): there is nothing to load, so the viewer shows the
|
|
745
|
+
// source's empty state instead of waiting for a landing document that does not exist.
|
|
746
|
+
const isEmptySource = !isLoadingStructure && structure.length === 0;
|
|
747
|
+
const settled = isEmptyFolder || isEmptySource;
|
|
738
748
|
|
|
739
749
|
return {
|
|
740
750
|
structure,
|
|
741
751
|
selectedPath,
|
|
742
752
|
setSelectedPath,
|
|
743
|
-
content:
|
|
753
|
+
content: settled ? null : content,
|
|
744
754
|
isLoadingStructure,
|
|
745
|
-
isLoadingContent:
|
|
746
|
-
error:
|
|
755
|
+
isLoadingContent: settled ? false : isLoadingContent,
|
|
756
|
+
error: settled ? null : error,
|
|
747
757
|
expandedNodes,
|
|
748
758
|
toggleNode,
|
|
749
759
|
selectNode,
|