@flamingo-stack/openframe-frontend-core 0.0.254 → 0.0.255-snapshot.20260611213621
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-ELF4OOHC.cjs → chunk-3DC6MHW6.cjs} +33 -33
- package/dist/{chunk-ELF4OOHC.cjs.map → chunk-3DC6MHW6.cjs.map} +1 -1
- package/dist/{chunk-QCRQNKRO.js → chunk-46DHPQY6.js} +4 -4
- package/dist/{chunk-ZITMX2IQ.cjs → chunk-6F5CGBVT.cjs} +745 -767
- package/dist/chunk-6F5CGBVT.cjs.map +1 -0
- package/dist/{chunk-4RDJZIHW.js → chunk-6GBA6YJN.js} +3 -3
- package/dist/{chunk-4HHYUEMD.js → chunk-7RLV3U5C.js} +2 -2
- package/dist/{chunk-SGJBUL2Y.cjs → chunk-7VMEOFEA.cjs} +45 -2
- package/dist/chunk-7VMEOFEA.cjs.map +1 -0
- package/dist/{chunk-TSY72CQ6.cjs → chunk-CMWRZECW.cjs} +77 -42
- package/dist/chunk-CMWRZECW.cjs.map +1 -0
- package/dist/{chunk-CAPVFI5A.js → chunk-HCMVLEC3.js} +4 -4
- package/dist/{chunk-LZL6P7SN.cjs → chunk-IG4QRNYZ.cjs} +10 -10
- package/dist/{chunk-LZL6P7SN.cjs.map → chunk-IG4QRNYZ.cjs.map} +1 -1
- package/dist/{chunk-HYJ5IV4Z.cjs → chunk-ISALYOFX.cjs} +19 -19
- package/dist/{chunk-HYJ5IV4Z.cjs.map → chunk-ISALYOFX.cjs.map} +1 -1
- package/dist/{chunk-MKVKVJQQ.js → chunk-J3QMPOS6.js} +5 -4
- package/dist/chunk-J3QMPOS6.js.map +1 -0
- package/dist/{chunk-BE472J6X.js → chunk-JK25REDL.js} +48 -13
- package/dist/chunk-JK25REDL.js.map +1 -0
- package/dist/{chunk-Z4IBEIWD.cjs → chunk-LGLPNWS6.cjs} +3 -3
- package/dist/{chunk-Z4IBEIWD.cjs.map → chunk-LGLPNWS6.cjs.map} +1 -1
- package/dist/{chunk-VAUI3CND.js → chunk-NR535XIY.js} +44 -1
- package/dist/chunk-NR535XIY.js.map +1 -0
- package/dist/{chunk-CZR7ARBA.js → chunk-PHWQLKVE.js} +6 -2
- package/dist/chunk-PHWQLKVE.js.map +1 -0
- package/dist/{chunk-OZ3GH6OQ.cjs → chunk-PXXS27EE.cjs} +6 -2
- package/dist/chunk-PXXS27EE.cjs.map +1 -0
- package/dist/{chunk-2FCXJTOV.js → chunk-QXSRQVRO.js} +3 -1
- package/dist/chunk-QXSRQVRO.js.map +1 -0
- package/dist/{chunk-NP5U35FA.js → chunk-S7IX7YU4.js} +198 -220
- package/dist/chunk-S7IX7YU4.js.map +1 -0
- package/dist/{chunk-6LK4VEXE.cjs → chunk-TSZBJVYK.cjs} +35 -31
- package/dist/chunk-TSZBJVYK.cjs.map +1 -0
- package/dist/{chunk-TU26IZPO.cjs → chunk-U6UNAC32.cjs} +3 -1
- package/dist/chunk-U6UNAC32.cjs.map +1 -0
- package/dist/{chunk-265KKBV2.js → chunk-V4IIBNTA.js} +3 -3
- package/dist/chunk-V4IIBNTA.js.map +1 -0
- package/dist/{chunk-65JN25VM.cjs → chunk-V4Q5PIBV.cjs} +30 -30
- package/dist/{chunk-65JN25VM.cjs.map → chunk-V4Q5PIBV.cjs.map} +1 -1
- package/dist/{chunk-LKI3YCAX.js → chunk-V5L4D454.js} +10 -6
- package/dist/chunk-V5L4D454.js.map +1 -0
- package/dist/{chunk-3TDLFCCS.cjs → chunk-ZJP2BB3E.cjs} +6 -5
- package/dist/chunk-ZJP2BB3E.cjs.map +1 -0
- package/dist/components/chat/entity-cards/entity-author-card.d.ts +13 -9
- package/dist/components/chat/entity-cards/entity-author-card.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-dialog-subscription.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +10 -8
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +1 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +9 -7
- package/dist/components/chat/types/entities/onboarding-guide.d.ts +2 -7
- package/dist/components/chat/types/entities/onboarding-guide.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +9 -9
- package/dist/components/contact/index.js +8 -8
- package/dist/components/faq/faq-section.d.ts.map +1 -1
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +8 -8
- package/dist/components/features/index.js +7 -7
- package/dist/components/features/notifications/notification-drawer.d.ts.map +1 -1
- package/dist/components/icons/index.cjs +2 -2
- package/dist/components/icons/index.js +1 -1
- package/dist/components/index.cjs +167 -157
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +55 -45
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/app-layout-drawer.d.ts +1 -1
- package/dist/components/navigation/app-layout-drawer.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +8 -8
- package/dist/components/navigation/index.js +7 -7
- package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
- package/dist/components/onboarding-guides/index.cjs +25 -23
- package/dist/components/onboarding-guides/index.cjs.map +1 -1
- package/dist/components/onboarding-guides/index.js +6 -4
- package/dist/components/onboarding-guides/index.js.map +1 -1
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts +4 -1
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
- package/dist/components/related-content/index.cjs +11 -9
- package/dist/components/related-content/index.cjs.map +1 -1
- package/dist/components/related-content/index.d.ts +1 -1
- package/dist/components/related-content/index.d.ts.map +1 -1
- package/dist/components/related-content/index.js +10 -8
- package/dist/components/related-content/related-content-section.d.ts +16 -1
- package/dist/components/related-content/related-content-section.d.ts.map +1 -1
- package/dist/components/shared/product-release/release-detail-page.d.ts +4 -1
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/social-icon-row.d.ts +17 -4
- package/dist/components/social-icon-row.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +81 -81
- package/dist/components/tickets/index.js +9 -9
- package/dist/components/ui/button/button.d.ts.map +1 -1
- package/dist/components/ui/drawer.d.ts +6 -2
- package/dist/components/ui/drawer.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +20 -20
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/index.cjs +10 -8
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +9 -7
- package/dist/hooks/index.cjs +4 -4
- package/dist/hooks/index.js +3 -3
- package/dist/index.cjs +13 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +17 -13
- package/dist/nats/index.cjs +2 -2
- package/dist/nats/index.js +1 -1
- package/dist/nats/nats-provider.d.ts.map +1 -1
- package/dist/nats/nats.d.ts.map +1 -1
- package/dist/types/blog.d.ts +7 -0
- package/dist/types/blog.d.ts.map +1 -1
- package/dist/types/entity-author.d.ts +18 -0
- package/dist/types/entity-author.d.ts.map +1 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/navigation.d.ts +1 -0
- package/dist/types/navigation.d.ts.map +1 -1
- package/dist/types/product-release.d.ts +2 -7
- package/dist/types/product-release.d.ts.map +1 -1
- package/dist/utils/common.d.ts +9 -0
- package/dist/utils/common.d.ts.map +1 -1
- package/dist/utils/index.cjs +6 -2
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +6 -3
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/entity-cards/entity-author-card.tsx +30 -11
- package/src/components/chat/hooks/use-nats-dialog-subscription.ts +6 -4
- package/src/components/chat/index.ts +1 -0
- package/src/components/chat/types/entities/onboarding-guide.ts +3 -7
- package/src/components/faq/faq-section.tsx +4 -1
- package/src/components/features/notifications/notification-drawer.tsx +16 -13
- package/src/components/icons/x-logo.tsx +2 -2
- package/src/components/navigation/navigation-sidebar-item.tsx +14 -1
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +5 -0
- package/src/components/related-content/index.ts +1 -0
- package/src/components/related-content/related-content-section.tsx +84 -8
- package/src/components/shared/product-release/release-detail-page.tsx +18 -21
- package/src/components/social-icon-row.tsx +51 -22
- package/src/components/ui/button/button.tsx +7 -0
- package/src/components/ui/drawer.tsx +10 -3
- package/src/nats/nats-provider.tsx +1 -0
- package/src/nats/nats.ts +5 -1
- package/src/types/blog.ts +9 -1
- package/src/types/entity-author.ts +17 -0
- package/src/types/index.ts +1 -0
- package/src/types/navigation.ts +1 -0
- package/src/types/product-release.ts +2 -7
- package/src/utils/common.ts +12 -0
- package/src/utils/index.ts +1 -1
- package/dist/chunk-265KKBV2.js.map +0 -1
- package/dist/chunk-2FCXJTOV.js.map +0 -1
- package/dist/chunk-3TDLFCCS.cjs.map +0 -1
- package/dist/chunk-6LK4VEXE.cjs.map +0 -1
- package/dist/chunk-BE472J6X.js.map +0 -1
- package/dist/chunk-CZR7ARBA.js.map +0 -1
- package/dist/chunk-LKI3YCAX.js.map +0 -1
- package/dist/chunk-MKVKVJQQ.js.map +0 -1
- package/dist/chunk-NP5U35FA.js.map +0 -1
- package/dist/chunk-OZ3GH6OQ.cjs.map +0 -1
- package/dist/chunk-SGJBUL2Y.cjs.map +0 -1
- package/dist/chunk-TSY72CQ6.cjs.map +0 -1
- package/dist/chunk-TU26IZPO.cjs.map +0 -1
- package/dist/chunk-VAUI3CND.js.map +0 -1
- package/dist/chunk-ZITMX2IQ.cjs.map +0 -1
- /package/dist/{chunk-QCRQNKRO.js.map → chunk-46DHPQY6.js.map} +0 -0
- /package/dist/{chunk-4RDJZIHW.js.map → chunk-6GBA6YJN.js.map} +0 -0
- /package/dist/{chunk-4HHYUEMD.js.map → chunk-7RLV3U5C.js.map} +0 -0
- /package/dist/{chunk-CAPVFI5A.js.map → chunk-HCMVLEC3.js.map} +0 -0
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import { Button } from './ui/button';
|
|
4
|
-
import { GitHubIcon, RedditIcon, XLogo, LinkedInIcon, LumaIcon, WhatsAppIcon, GlobeIcon, MessageCircleIcon, TelegramIcon, YouTubeIcon, InstagramIcon, FacebookIcon, SlackIcon } from './icons';
|
|
4
|
+
import { GitHubIcon, RedditIcon, XLogo, LinkedInIcon, LumaIcon, WhatsAppIcon, GlobeIcon, MessageCircleIcon, TelegramIcon, YouTubeIcon, InstagramIcon, FacebookIcon, SlackIcon, CopyIcon } from './icons';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/** Exactly ONE of `href` (anchor, target _blank) or `onClick` (action
|
|
7
|
+
* button — share popups via window.open inside the click gesture,
|
|
8
|
+
* copy-to-clipboard) — the discriminated union makes a dead no-action
|
|
9
|
+
* entry unrepresentable. */
|
|
10
|
+
type SocialLink = {
|
|
7
11
|
platform: string;
|
|
8
|
-
href: string;
|
|
9
12
|
label?: string;
|
|
10
|
-
}
|
|
13
|
+
} & (
|
|
14
|
+
| { href: string; onClick?: never }
|
|
15
|
+
| { onClick: () => void; href?: never }
|
|
16
|
+
);
|
|
11
17
|
|
|
12
18
|
interface SocialIconRowProps {
|
|
13
19
|
className?: string;
|
|
14
20
|
links?: SocialLink[];
|
|
15
21
|
variant?: "accent" | "outline" | "transparent" | "destructive" | null | undefined;
|
|
22
|
+
/** Natural icon-button sizing (w-fit container, no flex-1 stretch) for
|
|
23
|
+
* page-level rows. Default false: buttons stretch across the container —
|
|
24
|
+
* the original card-width behavior (TMCG member cards, footers). */
|
|
25
|
+
compact?: boolean;
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
const defaultLinks: SocialLink[] = [
|
|
@@ -57,32 +67,51 @@ function renderSocialIcon(platform: string) {
|
|
|
57
67
|
case 'facebook':
|
|
58
68
|
case 'fb':
|
|
59
69
|
return <FacebookIcon className="w-5 h-5" />;
|
|
70
|
+
case 'copy':
|
|
71
|
+
// CopyIcon's default fill is grey and would mismatch its row-mates —
|
|
72
|
+
// force the themed foreground via the ODS token (white on the dark
|
|
73
|
+
// theme, tracking the theme unlike the literal the reddit/slack cases
|
|
74
|
+
// still carry).
|
|
75
|
+
return <CopyIcon className="w-5 h-5" color="var(--ods-text-primary)" />;
|
|
60
76
|
default:
|
|
61
77
|
return <GlobeIcon className="w-5 h-5" />;
|
|
62
78
|
}
|
|
63
79
|
}
|
|
64
80
|
|
|
65
|
-
export function SocialIconRow({ className = '', links = defaultLinks, variant = 'outline' }: SocialIconRowProps) {
|
|
81
|
+
export function SocialIconRow({ className = '', links = defaultLinks, variant = 'outline', compact = false }: SocialIconRowProps) {
|
|
66
82
|
return (
|
|
67
|
-
<div className={`flex flex-row gap-3 w-full ${className}`}>
|
|
68
|
-
{links.map((link, index) =>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
<div className={`flex flex-row gap-3 ${compact ? 'w-fit' : 'w-full'} ${className}`}>
|
|
84
|
+
{links.map((link, index) => {
|
|
85
|
+
const ariaLabel = link.label || link.platform;
|
|
86
|
+
return link.onClick ? (
|
|
87
|
+
<Button
|
|
88
|
+
key={index}
|
|
89
|
+
type="button"
|
|
90
|
+
variant={variant}
|
|
91
|
+
size="icon"
|
|
92
|
+
className={compact ? undefined : 'flex-1'}
|
|
93
|
+
aria-label={ariaLabel}
|
|
94
|
+
onClick={link.onClick}
|
|
95
|
+
>
|
|
96
|
+
{renderSocialIcon(link.platform)}
|
|
97
|
+
</Button>
|
|
98
|
+
) : (
|
|
99
|
+
// Props-based linking — Button renders the anchor itself
|
|
100
|
+
// (openInNewTab carries target="_blank" + rel="noopener noreferrer");
|
|
101
|
+
// no asChild/<a> nesting.
|
|
102
|
+
<Button
|
|
103
|
+
key={index}
|
|
104
|
+
variant={variant}
|
|
105
|
+
size="icon"
|
|
106
|
+
className={compact ? undefined : 'flex-1'}
|
|
77
107
|
href={link.href}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
aria-label={link.label || link.platform}
|
|
108
|
+
openInNewTab
|
|
109
|
+
aria-label={ariaLabel}
|
|
81
110
|
>
|
|
82
111
|
{renderSocialIcon(link.platform)}
|
|
83
|
-
</
|
|
84
|
-
|
|
85
|
-
)
|
|
112
|
+
</Button>
|
|
113
|
+
);
|
|
114
|
+
})}
|
|
86
115
|
</div>
|
|
87
116
|
);
|
|
88
|
-
}
|
|
117
|
+
}
|
|
@@ -235,6 +235,10 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
235
235
|
prefetch={prefetch}
|
|
236
236
|
target={splitAnchor.target}
|
|
237
237
|
rel={splitAnchor.rel}
|
|
238
|
+
// The Link branches don't spread {...props} (button attrs don't
|
|
239
|
+
// belong on an anchor), but the accessible name MUST survive —
|
|
240
|
+
// icon-only link buttons have no text content.
|
|
241
|
+
aria-label={props['aria-label']}
|
|
238
242
|
aria-disabled={isDisabled || undefined}
|
|
239
243
|
tabIndex={isDisabled ? -1 : undefined}
|
|
240
244
|
className={cn(shellClasses, isDisabled && "pointer-events-none")}
|
|
@@ -300,6 +304,9 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
300
304
|
prefetch={prefetch}
|
|
301
305
|
target={anchor.target}
|
|
302
306
|
rel={anchor.rel}
|
|
307
|
+
// See the splitAnchor branch — keep the accessible name on the
|
|
308
|
+
// anchor render (icon-only link buttons have no text content).
|
|
309
|
+
aria-label={props['aria-label']}
|
|
303
310
|
aria-disabled={isDisabled || undefined}
|
|
304
311
|
tabIndex={isDisabled ? -1 : undefined}
|
|
305
312
|
className={cn(classes, isDisabled && "pointer-events-none")}
|
|
@@ -464,10 +464,16 @@ const DrawerHeader = ({
|
|
|
464
464
|
)
|
|
465
465
|
DrawerHeader.displayName = "DrawerHeader"
|
|
466
466
|
|
|
467
|
+
interface DrawerTitleProps
|
|
468
|
+
extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> {
|
|
469
|
+
/** Optional header actions rendered between the title and the close button. */
|
|
470
|
+
actions?: React.ReactNode
|
|
471
|
+
}
|
|
472
|
+
|
|
467
473
|
const DrawerTitle = React.forwardRef<
|
|
468
474
|
React.ComponentRef<typeof DialogPrimitive.Title>,
|
|
469
|
-
|
|
470
|
-
>(({ className, children, ...props }, ref) => (
|
|
475
|
+
DrawerTitleProps
|
|
476
|
+
>(({ className, children, actions, ...props }, ref) => (
|
|
471
477
|
<div className="flex items-start gap-4">
|
|
472
478
|
<DialogPrimitive.Title
|
|
473
479
|
ref={ref}
|
|
@@ -479,6 +485,7 @@ const DrawerTitle = React.forwardRef<
|
|
|
479
485
|
>
|
|
480
486
|
{children}
|
|
481
487
|
</DialogPrimitive.Title>
|
|
488
|
+
{actions}
|
|
482
489
|
<DialogPrimitive.Close className="shrink-0 rounded-sm text-ods-text-secondary transition-colors hover:text-ods-text-primary outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0">
|
|
483
490
|
<X className="size-6" />
|
|
484
491
|
<span className="sr-only">Close</span>
|
|
@@ -538,4 +545,4 @@ export {
|
|
|
538
545
|
DrawerFooter,
|
|
539
546
|
}
|
|
540
547
|
|
|
541
|
-
export type { DrawerContentProps, DrawerSide }
|
|
548
|
+
export type { DrawerContentProps, DrawerSide, DrawerTitleProps }
|
|
@@ -101,6 +101,7 @@ export function NatsProvider({
|
|
|
101
101
|
backoff: reconnectionBackoffRef.current,
|
|
102
102
|
getFreshUrl: () => getWsUrlRef.current(),
|
|
103
103
|
onStatusChange: (newStatus) => {
|
|
104
|
+
if (newStatus === 'error') return
|
|
104
105
|
setStatus(newStatus)
|
|
105
106
|
if (newStatus === 'connected') {
|
|
106
107
|
if (hadConnectionBeforeRef.current) {
|
package/src/nats/nats.ts
CHANGED
|
@@ -327,7 +327,11 @@ export function createNatsClient(options: NatsClientOptions): NatsClient {
|
|
|
327
327
|
} catch (e) {
|
|
328
328
|
if (!signal.aborted) {
|
|
329
329
|
emitStatus({ status: 'error', data: e })
|
|
330
|
-
|
|
330
|
+
|
|
331
|
+
if (nc === conn && conn.isClosed()) {
|
|
332
|
+
nc = null
|
|
333
|
+
emitStatus({ status: 'closed' })
|
|
334
|
+
}
|
|
331
335
|
}
|
|
332
336
|
}
|
|
333
337
|
})().catch(() => {
|
package/src/types/blog.ts
CHANGED
|
@@ -4,12 +4,20 @@ import type { UserProfile } from "./user"
|
|
|
4
4
|
export type Platform = string;
|
|
5
5
|
export type BlogStatus = 'draft' | 'ai_drafted' | 'published' | 'ai_published' | 'scheduled' | 'archived';
|
|
6
6
|
|
|
7
|
-
// Author interface for blog posts
|
|
7
|
+
// Author interface for blog posts (camelCase — the blog contract's own
|
|
8
|
+
// shape; detail-row authors elsewhere use the shared EntityAuthor).
|
|
8
9
|
export interface BlogAuthor {
|
|
9
10
|
id: number;
|
|
10
11
|
name?: string;
|
|
11
12
|
avatar?: string;
|
|
12
13
|
bio?: string;
|
|
14
|
+
jobTitle?: string;
|
|
15
|
+
/** Public author-page slug — present only when the hub's public-author
|
|
16
|
+
* gate passes (is_real profiles only). */
|
|
17
|
+
slug?: string;
|
|
18
|
+
/** Author's LinkedIn from profile_social_links — Article schema sameAs
|
|
19
|
+
* + the author.url fallback for slug-less authors. */
|
|
20
|
+
linkedinUrl?: string;
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
// Database Models
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hydrated content author — THE shared author shape attached to detail rows
|
|
3
|
+
* by the hub's `hydrateAuthor` (product releases, onboarding guides) and the
|
|
4
|
+
* per-entity author joins. One type for every author surface: metadata-grid
|
|
5
|
+
* author cells, bylines, author-page hrefs.
|
|
6
|
+
*/
|
|
7
|
+
export interface EntityAuthor {
|
|
8
|
+
id?: string
|
|
9
|
+
full_name: string | null
|
|
10
|
+
avatar_url: string | null
|
|
11
|
+
job_title?: string | null
|
|
12
|
+
/** Public author-page slug — present only when the hub's public-author
|
|
13
|
+
* gate passes (`is_real` profiles only). Feeds /authors/[slug] links. */
|
|
14
|
+
slug?: string
|
|
15
|
+
/** Present only on server/admin payloads — public DALs strip it. */
|
|
16
|
+
email?: string | null
|
|
17
|
+
}
|
package/src/types/index.ts
CHANGED
package/src/types/navigation.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Following the blog.ts pattern for consistency
|
|
3
3
|
|
|
4
4
|
import type { PlatformRecord } from './platform'
|
|
5
|
+
import type { EntityAuthor } from './entity-author'
|
|
5
6
|
import type { BlogTag, TagAssoc } from './blog'
|
|
6
7
|
import type { VideoTeaser, TranscriptWord, SpeakerMapping } from './video-processing'
|
|
7
8
|
|
|
@@ -128,13 +129,7 @@ export interface ProductRelease {
|
|
|
128
129
|
* `hydrateAuthor` in the hub DAL. Optional so admin form + every existing
|
|
129
130
|
* consumer that doesn't read author stay backward-compatible.
|
|
130
131
|
*/
|
|
131
|
-
author?:
|
|
132
|
-
id: string
|
|
133
|
-
full_name: string
|
|
134
|
-
avatar_url: string | null
|
|
135
|
-
job_title: string | null
|
|
136
|
-
email?: string | null
|
|
137
|
-
}
|
|
132
|
+
author?: EntityAuthor
|
|
138
133
|
|
|
139
134
|
// Timestamps
|
|
140
135
|
created_at: string
|
package/src/utils/common.ts
CHANGED
|
@@ -49,6 +49,18 @@ export function truncateString(str: string, maxLength: number, suffix = "..."):
|
|
|
49
49
|
return str.substring(0, maxLength - suffix.length) + suffix
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Serialize a JSON-LD schema for a `<script type="application/ld+json">`
|
|
54
|
+
* block — THE one home of the escape rule (the hub re-exports this from
|
|
55
|
+
* lib/utils/breadcrumbs). JSON.stringify does NOT HTML-escape: a stored
|
|
56
|
+
* "</script>" inside any admin/user-entered field would terminate the tag
|
|
57
|
+
* early (stored XSS). Every "<" becomes the JSON escape sequence
|
|
58
|
+
* backslash-u003c — still valid JSON, inert in HTML.
|
|
59
|
+
*/
|
|
60
|
+
export function serializeJsonLd(schema: unknown): string {
|
|
61
|
+
return JSON.stringify(schema).replace(/</g, "\\u003c")
|
|
62
|
+
}
|
|
63
|
+
|
|
52
64
|
/**
|
|
53
65
|
* Deep clone an object
|
|
54
66
|
* @param obj - Object to clone
|
package/src/utils/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { formatDate, formatNumber, formatPrice, formatBytes } from './format'
|
|
|
12
12
|
// SVG path constants — re-exported here (server-safe) because icons-v2 has "use client"
|
|
13
13
|
export { PLAY_ICON_PATH } from '../components/icons-v2-generated/media-playback/play-icon'
|
|
14
14
|
export { getPlatformAccentColor, getCurrentPlatform, type ColorCategory, HEX_PATTERN } from './ods-color-utils'
|
|
15
|
-
export { delay, generateRandomString, truncateString, deepClone, getSlackCommunityJoinUrl } from './common'
|
|
15
|
+
export { delay, generateRandomString, truncateString, deepClone, getSlackCommunityJoinUrl, serializeJsonLd } from './common'
|
|
16
16
|
export { getBaseUrl } from '../utils/cn'
|
|
17
17
|
|
|
18
18
|
export * from './platform-config'
|