@flamingo-stack/openframe-frontend-core 0.0.533 → 0.0.534-snapshot.20260812085314

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.
Files changed (136) hide show
  1. package/dist/{chunk-2MYDGLJS.cjs → chunk-2P4J2YLI.cjs} +98 -98
  2. package/dist/{chunk-2MYDGLJS.cjs.map → chunk-2P4J2YLI.cjs.map} +1 -1
  3. package/dist/{chunk-GS4HJA2G.js → chunk-4A736Z6S.js} +74 -20
  4. package/dist/chunk-4A736Z6S.js.map +1 -0
  5. package/dist/{chunk-LIUARTOZ.js → chunk-4DTUUM5V.js} +150 -309
  6. package/dist/chunk-4DTUUM5V.js.map +1 -0
  7. package/dist/{chunk-RKITTO5V.cjs → chunk-4HQFV4PK.cjs} +14 -14
  8. package/dist/{chunk-RKITTO5V.cjs.map → chunk-4HQFV4PK.cjs.map} +1 -1
  9. package/dist/{chunk-GYONSC3U.cjs → chunk-4QYUNDS3.cjs} +3 -3
  10. package/dist/{chunk-GYONSC3U.cjs.map → chunk-4QYUNDS3.cjs.map} +1 -1
  11. package/dist/{chunk-GNOK5J34.cjs → chunk-5OLF6WXF.cjs} +40 -40
  12. package/dist/{chunk-GNOK5J34.cjs.map → chunk-5OLF6WXF.cjs.map} +1 -1
  13. package/dist/{chunk-SXLEPNY2.cjs → chunk-5RJUHUCI.cjs} +106 -52
  14. package/dist/chunk-5RJUHUCI.cjs.map +1 -0
  15. package/dist/{chunk-OLOKQQAP.cjs → chunk-7MHYRBBQ.cjs} +5 -5
  16. package/dist/{chunk-OLOKQQAP.cjs.map → chunk-7MHYRBBQ.cjs.map} +1 -1
  17. package/dist/{chunk-UPHN2VRZ.cjs → chunk-E3YB55RR.cjs} +954 -1113
  18. package/dist/chunk-E3YB55RR.cjs.map +1 -0
  19. package/dist/{chunk-5U67WTAS.js → chunk-EHPQKXXW.js} +3 -3
  20. package/dist/{chunk-JDW2IL7A.cjs → chunk-GV4FDDFV.cjs} +18 -18
  21. package/dist/{chunk-JDW2IL7A.cjs.map → chunk-GV4FDDFV.cjs.map} +1 -1
  22. package/dist/{chunk-6W3ULZ4X.js → chunk-JVO5CYMK.js} +8 -8
  23. package/dist/{chunk-4QKXNWH5.js → chunk-K6566PEB.js} +3 -3
  24. package/dist/{chunk-YLB6XXO6.js → chunk-KLFGP6PR.js} +2 -2
  25. package/dist/{chunk-6YXN5RHW.js → chunk-LIQJAC6Z.js} +2 -2
  26. package/dist/{chunk-46FKXLNE.js → chunk-LIX5OUHH.js} +2 -2
  27. package/dist/{chunk-RCBRCZUF.js → chunk-LQPHB2YF.js} +2 -2
  28. package/dist/{chunk-2XGHEJZK.js → chunk-MZVFZMQ6.js} +6 -6
  29. package/dist/{chunk-6SMLYZLH.js → chunk-N3AEJTCK.js} +5 -5
  30. package/dist/{chunk-LE3BJ5H2.js → chunk-NB6BP6JB.js} +3 -3
  31. package/dist/{chunk-QZ2NAPLF.cjs → chunk-P65EZEBW.cjs} +12 -12
  32. package/dist/{chunk-QZ2NAPLF.cjs.map → chunk-P65EZEBW.cjs.map} +1 -1
  33. package/dist/{chunk-MVGL5NNJ.js → chunk-PLGITXT7.js} +2 -2
  34. package/dist/{chunk-KMB66NYX.cjs → chunk-PSBL3S2B.cjs} +7 -7
  35. package/dist/{chunk-KMB66NYX.cjs.map → chunk-PSBL3S2B.cjs.map} +1 -1
  36. package/dist/{chunk-U555TMPC.js → chunk-QUVIT4JP.js} +6 -6
  37. package/dist/{chunk-4ZRHVOEB.cjs → chunk-RO67OOKJ.cjs} +40 -40
  38. package/dist/{chunk-4ZRHVOEB.cjs.map → chunk-RO67OOKJ.cjs.map} +1 -1
  39. package/dist/{chunk-NAV4FRNL.cjs → chunk-TS2EHMEX.cjs} +29 -29
  40. package/dist/{chunk-NAV4FRNL.cjs.map → chunk-TS2EHMEX.cjs.map} +1 -1
  41. package/dist/{chunk-Z3NOI7X7.cjs → chunk-U2Q7GEX2.cjs} +32 -32
  42. package/dist/{chunk-Z3NOI7X7.cjs.map → chunk-U2Q7GEX2.cjs.map} +1 -1
  43. package/dist/{chunk-XN4JKIJY.js → chunk-VPDTDKO5.js} +2 -2
  44. package/dist/{chunk-ENPGPZIB.cjs → chunk-Y55XQFJK.cjs} +75 -75
  45. package/dist/{chunk-ENPGPZIB.cjs.map → chunk-Y55XQFJK.cjs.map} +1 -1
  46. package/dist/{chunk-2TUOBFPK.js → chunk-Y72MXG5N.js} +4 -4
  47. package/dist/{chunk-DFQLUK75.cjs → chunk-ZKKEKG73.cjs} +3 -3
  48. package/dist/{chunk-DFQLUK75.cjs.map → chunk-ZKKEKG73.cjs.map} +1 -1
  49. package/dist/{chunk-J4BE4FIR.cjs → chunk-ZR5CJTNR.cjs} +4 -4
  50. package/dist/{chunk-J4BE4FIR.cjs.map → chunk-ZR5CJTNR.cjs.map} +1 -1
  51. package/dist/components/case-studies/index.cjs +9 -9
  52. package/dist/components/case-studies/index.js +3 -3
  53. package/dist/components/chat/chat-input.d.ts.map +1 -1
  54. package/dist/components/chat/index.cjs +3 -3
  55. package/dist/components/chat/index.js +2 -2
  56. package/dist/components/contact/index.cjs +4 -4
  57. package/dist/components/contact/index.js +3 -3
  58. package/dist/components/docs/index.cjs +6 -6
  59. package/dist/components/docs/index.js +5 -5
  60. package/dist/components/embeds/index.cjs +4 -4
  61. package/dist/components/embeds/index.js +3 -3
  62. package/dist/components/faq/index.cjs +5 -5
  63. package/dist/components/faq/index.js +4 -4
  64. package/dist/components/features/index.cjs +3 -3
  65. package/dist/components/features/index.js +2 -2
  66. package/dist/components/help-center-pages/index.cjs +24 -24
  67. package/dist/components/help-center-pages/index.js +15 -15
  68. package/dist/components/index.cjs +138 -138
  69. package/dist/components/index.js +13 -13
  70. package/dist/components/meeting-scheduler/index.cjs +40 -40
  71. package/dist/components/meeting-scheduler/index.js +4 -4
  72. package/dist/components/navigation/index.cjs +3 -3
  73. package/dist/components/navigation/index.js +2 -2
  74. package/dist/components/onboarding-guides/index.cjs +7 -7
  75. package/dist/components/onboarding-guides/index.js +6 -6
  76. package/dist/components/popover.d.ts.map +1 -1
  77. package/dist/components/related-content/index.cjs +5 -5
  78. package/dist/components/related-content/index.js +4 -4
  79. package/dist/components/tickets/index.cjs +7 -7
  80. package/dist/components/tickets/index.js +6 -6
  81. package/dist/components/ui/autocomplete.d.ts.map +1 -1
  82. package/dist/components/ui/file-upload.d.ts.map +1 -1
  83. package/dist/components/ui/index.cjs +3 -3
  84. package/dist/components/ui/index.js +2 -2
  85. package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +1 -1
  86. package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +1 -1
  87. package/dist/components/ui/search-input.d.ts.map +1 -1
  88. package/dist/components/ui/searchable-select.d.ts.map +1 -1
  89. package/dist/components/ui/tag-select-dropdown.d.ts.map +1 -1
  90. package/dist/components/ui/tags-manager.d.ts.map +1 -1
  91. package/dist/hooks/index.cjs +4 -2
  92. package/dist/hooks/index.cjs.map +1 -1
  93. package/dist/hooks/index.js +3 -1
  94. package/dist/hooks/ui/index.d.ts +1 -0
  95. package/dist/hooks/ui/index.d.ts.map +1 -1
  96. package/dist/hooks/ui/use-keyboard-collision-padding.d.ts +2 -0
  97. package/dist/hooks/ui/use-keyboard-collision-padding.d.ts.map +1 -0
  98. package/dist/index.cjs +5 -3
  99. package/dist/index.cjs.map +1 -1
  100. package/dist/index.js +4 -2
  101. package/dist/utils/social-embed-cache.d.ts +16 -16
  102. package/dist/utils/social-embed-cache.d.ts.map +1 -1
  103. package/package.json +1 -1
  104. package/src/components/chat/chat-input.tsx +84 -32
  105. package/src/components/popover.tsx +22 -14
  106. package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +1 -5
  107. package/src/components/ui/autocomplete.tsx +9 -2
  108. package/src/components/ui/file-upload.tsx +5 -1
  109. package/src/components/ui/markdown/rich/__tests__/shortcodes.test.ts +58 -0
  110. package/src/components/ui/markdown/rich/embed-overrides.tsx +13 -7
  111. package/src/components/ui/markdown/rich/shortcodes.ts +15 -0
  112. package/src/components/ui/search-input.tsx +9 -2
  113. package/src/components/ui/searchable-select.tsx +9 -2
  114. package/src/components/ui/tag-select-dropdown.tsx +8 -2
  115. package/src/components/ui/tags-manager.tsx +9 -2
  116. package/src/hooks/ui/index.ts +1 -0
  117. package/src/hooks/ui/use-keyboard-collision-padding.ts +101 -0
  118. package/src/utils/social-embed-cache.ts +63 -336
  119. package/dist/chunk-GS4HJA2G.js.map +0 -1
  120. package/dist/chunk-LIUARTOZ.js.map +0 -1
  121. package/dist/chunk-SXLEPNY2.cjs.map +0 -1
  122. package/dist/chunk-UPHN2VRZ.cjs.map +0 -1
  123. /package/dist/{chunk-5U67WTAS.js.map → chunk-EHPQKXXW.js.map} +0 -0
  124. /package/dist/{chunk-6W3ULZ4X.js.map → chunk-JVO5CYMK.js.map} +0 -0
  125. /package/dist/{chunk-4QKXNWH5.js.map → chunk-K6566PEB.js.map} +0 -0
  126. /package/dist/{chunk-YLB6XXO6.js.map → chunk-KLFGP6PR.js.map} +0 -0
  127. /package/dist/{chunk-6YXN5RHW.js.map → chunk-LIQJAC6Z.js.map} +0 -0
  128. /package/dist/{chunk-46FKXLNE.js.map → chunk-LIX5OUHH.js.map} +0 -0
  129. /package/dist/{chunk-RCBRCZUF.js.map → chunk-LQPHB2YF.js.map} +0 -0
  130. /package/dist/{chunk-2XGHEJZK.js.map → chunk-MZVFZMQ6.js.map} +0 -0
  131. /package/dist/{chunk-6SMLYZLH.js.map → chunk-N3AEJTCK.js.map} +0 -0
  132. /package/dist/{chunk-LE3BJ5H2.js.map → chunk-NB6BP6JB.js.map} +0 -0
  133. /package/dist/{chunk-MVGL5NNJ.js.map → chunk-PLGITXT7.js.map} +0 -0
  134. /package/dist/{chunk-U555TMPC.js.map → chunk-QUVIT4JP.js.map} +0 -0
  135. /package/dist/{chunk-XN4JKIJY.js.map → chunk-VPDTDKO5.js.map} +0 -0
  136. /package/dist/{chunk-2TUOBFPK.js.map → chunk-Y72MXG5N.js.map} +0 -0
@@ -39,6 +39,7 @@ import {
39
39
  useImageEdgeColor,
40
40
  useIsTruncated,
41
41
  useIsomorphicLayoutEffect,
42
+ useKeyboardCollisionPadding,
42
43
  useLgUp,
43
44
  useLocalStorage,
44
45
  useMarqueeEngine,
@@ -51,7 +52,7 @@ import {
51
52
  useResetOnPageHidden,
52
53
  useSuppressCloneFocus,
53
54
  useToast
54
- } from "./chunk-GS4HJA2G.js";
55
+ } from "./chunk-4A736Z6S.js";
55
56
  import {
56
57
  init_next_navigation,
57
58
  usePathname,
@@ -6530,6 +6531,7 @@ var ChatInput = forwardRef13((allProps, ref) => {
6530
6531
  // contenteditable div, not a <textarea>, so spreading them would warn.
6531
6532
  } = rest;
6532
6533
  const [value, setValue] = useState10("");
6534
+ const [hasChips, setHasChips] = useState10(false);
6533
6535
  const [focused, setFocused] = useState10(false);
6534
6536
  const [isStopping, setIsStopping] = useState10(false);
6535
6537
  const [previewDismissed, setPreviewDismissed] = useState10(false);
@@ -6565,26 +6567,32 @@ var ChatInput = forwardRef13((allProps, ref) => {
6565
6567
  if (typeof caret === "number") placeCaretAtOffset(el, caret);
6566
6568
  else placeCaretAtEnd(el);
6567
6569
  }
6570
+ setHasChips(el.querySelector("[data-token]") !== null);
6568
6571
  }
6572
+ valueRef.current = next;
6569
6573
  setValue(next);
6570
6574
  }, []);
6571
6575
  const fire = useCallback3(
6572
6576
  (message2) => {
6573
6577
  const can = (message2.length > 0 || allowEmptySend) && !sending && !disabled;
6574
6578
  if (!can || !onSend) return;
6575
- const result = onSend(message2);
6576
- const done = () => {
6577
- metaRef.current = /* @__PURE__ */ new Map();
6578
- applyValue("");
6579
- shouldRefocusRef.current = true;
6580
- focusEditor();
6579
+ const sentMeta = metaRef.current;
6580
+ metaRef.current = /* @__PURE__ */ new Map();
6581
+ applyValue("");
6582
+ shouldRefocusRef.current = true;
6583
+ focusEditor();
6584
+ const restore = () => {
6585
+ if (valueRef.current.length > 0) return;
6586
+ metaRef.current = sentMeta;
6587
+ applyValue(message2);
6581
6588
  };
6589
+ const result = onSend(message2);
6582
6590
  if (result instanceof Promise) {
6583
6591
  void result.then((ok) => {
6584
- if (ok !== false) done();
6592
+ if (ok === false) restore();
6585
6593
  });
6586
- } else if (result !== false) {
6587
- done();
6594
+ } else if (result === false) {
6595
+ restore();
6588
6596
  }
6589
6597
  },
6590
6598
  [allowEmptySend, sending, disabled, onSend, focusEditor, applyValue]
@@ -6673,6 +6681,7 @@ var ChatInput = forwardRef13((allProps, ref) => {
6673
6681
  const syncFromDom = useCallback3(() => {
6674
6682
  const el = editorRef.current;
6675
6683
  if (!el) return;
6684
+ setHasChips(el.querySelector("[data-token]") !== null);
6676
6685
  setValue(serialize(el));
6677
6686
  }, []);
6678
6687
  const handleInput = useCallback3(() => {
@@ -6778,8 +6787,7 @@ var ChatInput = forwardRef13((allProps, ref) => {
6778
6787
  const hasContent2 = value.trim().length > 0 || allowEmptySend;
6779
6788
  const sendDisabled = sending || disabled || !hasContent2;
6780
6789
  const isEmpty = value.length === 0;
6781
- const isDisabled = sending || disabled;
6782
- const showPreview = !isDisabled && !!previewText && !previewDismissed;
6790
+ const showPreview = !sending && !disabled && !!previewText && !previewDismissed;
6783
6791
  return /* @__PURE__ */ jsx30(
6784
6792
  "div",
6785
6793
  {
@@ -6813,23 +6821,24 @@ var ChatInput = forwardRef13((allProps, ref) => {
6813
6821
  },
6814
6822
  className: cn(
6815
6823
  // `items-center` keeps the empty single-line text AND a mention chip
6816
- // vertically centered the same way. `py-1.5` (6px) pairs with the
6817
- // editor's `leading-9` (36px) → a single line is exactly 48px
6818
- // (36 + 2*6). The 36px line box is just barely above the 32px chip
6819
- // (incl. its ~3.5px vertical-align overhead), so there's almost no
6820
- // slack for the chip to drift in → it reads as centered, and the row
6821
- // never overflows past 48px.
6824
+ // vertically centered the same way. With a chip in the box, `py-1.5`
6825
+ // (6px) pairs with the editor's `leading-9` (36px) → that row is
6826
+ // 36 + 2*6 = 48px. The 36px line box is just barely above the 32px
6827
+ // chip (incl. its ~3.5px vertical-align overhead), so there's almost
6828
+ // no slack for the chip to drift in → it reads as centered.
6829
+ // A chipless row is shorter than its content box and the `min-h-*`
6830
+ // here is what sets its height: 48px from `md` up, 44px below.
6822
6831
  "flex w-full items-center gap-2 px-3 py-1.5 min-h-11 md:min-h-12 cursor-text group transition-colors duration-200",
6823
6832
  !hideBorder && "rounded-[6px] border bg-ods-card border-ods-border",
6824
- !hideBorder && (focused ? "border-ods-accent" : !isDisabled && "hover:bg-ods-bg-hover hover:border-ods-border-hover"),
6825
- !hideBorder && isDisabled && "!cursor-not-allowed bg-ods-bg",
6833
+ !hideBorder && (focused ? "border-ods-accent" : !disabled && "hover:bg-ods-bg-hover hover:border-ods-border-hover"),
6834
+ !hideBorder && disabled && "!cursor-not-allowed bg-ods-bg",
6826
6835
  hideBorder && "bg-transparent"
6827
6836
  ),
6828
6837
  children: [
6829
6838
  startIcon && /* @__PURE__ */ jsx30("span", { className: "flex h-6 shrink-0 items-center", children: startIcon }),
6830
6839
  /* @__PURE__ */ jsxs23("div", { className: "relative flex-1 min-w-0", children: [
6831
- isEmpty && !showPreview && /* @__PURE__ */ jsx30("span", { className: "pointer-events-none absolute left-0 top-0 select-none text-h4 !leading-9 text-ods-text-secondary", children: disabled ? "Connection lost. Waiting to reconnect..." : placeholder }),
6832
- showPreview && /* @__PURE__ */ jsx30("p", { className: "pointer-events-none absolute inset-x-0 top-0 m-0 select-none truncate text-h4 !leading-9 text-ods-text-secondary", children: previewText }),
6840
+ isEmpty && !showPreview && /* @__PURE__ */ jsx30("span", { className: "pointer-events-none absolute left-0 top-0 select-none text-h4 text-ods-text-secondary", children: disabled ? "Connection lost. Waiting to reconnect..." : placeholder }),
6841
+ showPreview && /* @__PURE__ */ jsx30("p", { className: cn("pointer-events-none absolute inset-x-0 top-0 m-0 select-none truncate text-h4 text-ods-text-secondary", hasChips && "!leading-9"), children: previewText }),
6833
6842
  /* @__PURE__ */ jsx30(
6834
6843
  "div",
6835
6844
  {
@@ -6838,7 +6847,7 @@ var ChatInput = forwardRef13((allProps, ref) => {
6838
6847
  role: "textbox",
6839
6848
  "aria-multiline": "true",
6840
6849
  "aria-label": typeof placeholder === "string" ? placeholder : "Message",
6841
- contentEditable: !isDisabled,
6850
+ contentEditable: !disabled,
6842
6851
  suppressContentEditableWarning: true,
6843
6852
  spellCheck: true,
6844
6853
  onInput: handleInput,
@@ -6861,17 +6870,25 @@ var ChatInput = forwardRef13((allProps, ref) => {
6861
6870
  // see the preview comment above) and is only made transparent;
6862
6871
  // the absolutely-positioned single-line preview paints over it.
6863
6872
  showPreview && "opacity-0",
6864
- // `leading-9` (36px) sits just above the 32px chip's line-box need
6865
- // (~35.5px incl. vertical-align overhead): the LINE drives row
6866
- // height (deterministic 48px, no overflow) AND the near-zero slack
6867
- // keeps the chip centered. ~4px gap between wrapped chip rows.
6868
- // `min-h-9` pins that single-line box even when EMPTY: a non-editable
6869
- // (`contentEditable=false`, i.e. disabled/sending) empty div would
6870
- // otherwise collapse to 0px, and `items-center` would re-center the
6871
- // shorter wrapper in the row — dropping the `top-0` placeholder down.
6872
- "min-h-9 text-h4 text-ods-text-primary !leading-9",
6873
+ // WITH chips, `leading-9` (36px) sits just above the 32px chip's
6874
+ // line-box need (~35.5px incl. vertical-align overhead): the LINE
6875
+ // drives row height (deterministic 48px, no overflow) AND the
6876
+ // near-zero slack keeps the chip centered against the `align-top`
6877
+ // `h-9` wrapper `buildChipEl` gives it. ~4px gap between wrapped
6878
+ // chip rows.
6879
+ // WITHOUT chips there is nothing tall to center, and forcing the
6880
+ // same 36px box on 14px phone text (`text-h4`) put a blank line's
6881
+ // worth of air between every wrapped row — so plain drafts keep
6882
+ // `text-h4`'s own responsive line-height (20px / 24px at md).
6883
+ // `min-h-*` pins the single-line box even when EMPTY: a
6884
+ // non-editable (`contentEditable=false`, i.e. `disabled`) empty div
6885
+ // would otherwise collapse to 0px, and `items-center` would
6886
+ // re-center the shorter wrapper in the row — dropping the `top-0`
6887
+ // placeholder down.
6888
+ "text-h4 text-ods-text-primary",
6889
+ hasChips ? "min-h-9 !leading-9" : "min-h-[var(--font-line-space-h4-body)]",
6873
6890
  "scrollbar-thin scrollbar-track-transparent scrollbar-thumb-ods-border/30 hover:scrollbar-thumb-ods-text-secondary/30",
6874
- isDisabled && "cursor-not-allowed"
6891
+ disabled && "cursor-not-allowed"
6875
6892
  )
6876
6893
  }
6877
6894
  )
@@ -17422,6 +17439,7 @@ function AutocompleteInner(props, ref) {
17422
17439
  };
17423
17440
  const [isOpen, setIsOpen] = useState34(false);
17424
17441
  const [highlightedIndex, setHighlightedIndex] = useState34(-1);
17442
+ const keyboardPadding = useKeyboardCollisionPadding();
17425
17443
  const containerRef = useRef24(null);
17426
17444
  const hiddenTagsPopupRef = useRef24(null);
17427
17445
  const isInvalid = invalid || !!error;
@@ -17728,12 +17746,14 @@ function AutocompleteInner(props, ref) {
17728
17746
  className: cn(
17729
17747
  "z-50 w-[var(--radix-popover-trigger-width)] mt-1",
17730
17748
  "bg-ods-card border border-ods-border rounded-[4px]",
17749
+ "flex flex-col overflow-hidden max-h-[var(--radix-popper-available-height)]",
17731
17750
  "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
17732
17751
  "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
17733
17752
  dropdownClassName
17734
17753
  ),
17735
17754
  sideOffset: 4,
17736
17755
  align: "start",
17756
+ collisionPadding: { bottom: keyboardPadding },
17737
17757
  onOpenAutoFocus: (e) => {
17738
17758
  e.preventDefault();
17739
17759
  autoLimitTags.inputRef.current?.focus();
@@ -17743,8 +17763,8 @@ function AutocompleteInner(props, ref) {
17743
17763
  e.preventDefault();
17744
17764
  }
17745
17765
  },
17746
- children: /* @__PURE__ */ jsxs84(ScrollAreaPrimitive.Root, { className: "overflow-hidden", children: [
17747
- /* @__PURE__ */ jsx110(ScrollAreaPrimitive.Viewport, { className: "max-h-[240px] w-full [&>div]:!block", children: /* @__PURE__ */ jsx110("div", { role: "listbox", children: loading ? /* @__PURE__ */ jsx110("div", { className: "px-3 py-2 text-ods-text-secondary text-h6", children: loadingText }) : filteredOptions.length === 0 ? showCreateOption ? isCreateTooLong ? /* @__PURE__ */ jsxs84("div", { className: "flex items-center h-11 md:h-12 px-[var(--spacing-system-mf)] text-h5 text-ods-error min-w-0", children: [
17766
+ children: /* @__PURE__ */ jsxs84(ScrollAreaPrimitive.Root, { className: "flex min-h-0 flex-col overflow-hidden", children: [
17767
+ /* @__PURE__ */ jsx110(ScrollAreaPrimitive.Viewport, { className: "min-h-0 max-h-[240px] w-full [&>div]:!block", children: /* @__PURE__ */ jsx110("div", { role: "listbox", children: loading ? /* @__PURE__ */ jsx110("div", { className: "px-3 py-2 text-ods-text-secondary text-h6", children: loadingText }) : filteredOptions.length === 0 ? showCreateOption ? isCreateTooLong ? /* @__PURE__ */ jsxs84("div", { className: "flex items-center h-11 md:h-12 px-[var(--spacing-system-mf)] text-h5 text-ods-error min-w-0", children: [
17748
17768
  "Maximum ",
17749
17769
  maxCreateLength,
17750
17770
  " characters"
@@ -19632,6 +19652,7 @@ function SearchableSelect({
19632
19652
  }) {
19633
19653
  const [isOpen, setIsOpen] = React54.useState(false);
19634
19654
  const [search, setSearch] = React54.useState("");
19655
+ const keyboardPadding = useKeyboardCollisionPadding();
19635
19656
  React54.useEffect(() => {
19636
19657
  if (!isOpen) setSearch("");
19637
19658
  }, [isOpen]);
@@ -19683,8 +19704,10 @@ function SearchableSelect({
19683
19704
  {
19684
19705
  align,
19685
19706
  sideOffset: 6,
19707
+ collisionPadding: { bottom: keyboardPadding },
19686
19708
  className: cn(
19687
19709
  "z-50 min-w-[var(--radix-popover-trigger-width)] bg-ods-card border border-ods-border rounded-md shadow-lg overflow-hidden",
19710
+ "flex flex-col max-h-[var(--radix-popper-available-height)]",
19688
19711
  "data-[state=open]:animate-in data-[state=closed]:animate-out",
19689
19712
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
19690
19713
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
@@ -19692,7 +19715,7 @@ function SearchableSelect({
19692
19715
  contentClassName
19693
19716
  ),
19694
19717
  children: [
19695
- /* @__PURE__ */ jsx124("div", { className: "border-b border-ods-border", children: /* @__PURE__ */ jsx124(
19718
+ /* @__PURE__ */ jsx124("div", { className: "shrink-0 border-b border-ods-border", children: /* @__PURE__ */ jsx124(
19696
19719
  Input,
19697
19720
  {
19698
19721
  autoFocus: true,
@@ -19703,7 +19726,7 @@ function SearchableSelect({
19703
19726
  className: "rounded-none border-0"
19704
19727
  }
19705
19728
  ) }),
19706
- /* @__PURE__ */ jsx124("div", { className: "max-h-80 overflow-y-auto py-[var(--spacing-system-xs)]", role: "listbox", children: isLoading ? /* @__PURE__ */ jsx124("div", { className: "px-[var(--spacing-system-sf)] py-[var(--spacing-system-s)] text-h5 text-ods-text-secondary", children: loadingText }) : filtered.length === 0 ? /* @__PURE__ */ jsx124("div", { className: "px-[var(--spacing-system-sf)] py-[var(--spacing-system-s)] text-h5 text-ods-text-secondary", children: emptyText }) : filtered.map((opt) => {
19729
+ /* @__PURE__ */ jsx124("div", { className: "min-h-0 max-h-80 overflow-y-auto py-[var(--spacing-system-xs)]", role: "listbox", children: isLoading ? /* @__PURE__ */ jsx124("div", { className: "px-[var(--spacing-system-sf)] py-[var(--spacing-system-s)] text-h5 text-ods-text-secondary", children: loadingText }) : filtered.length === 0 ? /* @__PURE__ */ jsx124("div", { className: "px-[var(--spacing-system-sf)] py-[var(--spacing-system-s)] text-h5 text-ods-text-secondary", children: emptyText }) : filtered.map((opt) => {
19707
19730
  const isSelected = value === opt.value;
19708
19731
  return /* @__PURE__ */ jsx124(
19709
19732
  "button",
@@ -20530,6 +20553,7 @@ function TagSelectDropdown({
20530
20553
  }) {
20531
20554
  const [isOpen, setIsOpen] = React63.useState(false);
20532
20555
  const [search, setSearch] = React63.useState("");
20556
+ const keyboardPadding = useKeyboardCollisionPadding();
20533
20557
  React63.useEffect(() => {
20534
20558
  if (!isOpen) setSearch("");
20535
20559
  }, [isOpen]);
@@ -20573,15 +20597,17 @@ function TagSelectDropdown({
20573
20597
  {
20574
20598
  align,
20575
20599
  sideOffset: 6,
20600
+ collisionPadding: { bottom: keyboardPadding },
20576
20601
  className: cn(
20577
20602
  "z-50 w-72 bg-ods-card border border-ods-border rounded-[6px] shadow-lg overflow-hidden",
20603
+ "flex flex-col max-h-[var(--radix-popper-available-height)]",
20578
20604
  "data-[state=open]:animate-in data-[state=closed]:animate-out",
20579
20605
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
20580
20606
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
20581
20607
  "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2"
20582
20608
  ),
20583
20609
  children: [
20584
- /* @__PURE__ */ jsx135("div", { className: "border-b border-ods-border", children: /* @__PURE__ */ jsx135(
20610
+ /* @__PURE__ */ jsx135("div", { className: "shrink-0 border-b border-ods-border", children: /* @__PURE__ */ jsx135(
20585
20611
  Input,
20586
20612
  {
20587
20613
  autoFocus: true,
@@ -20598,7 +20624,7 @@ function TagSelectDropdown({
20598
20624
  className: "rounded-none border-0"
20599
20625
  }
20600
20626
  ) }),
20601
- /* @__PURE__ */ jsxs105("div", { className: "max-h-72 overflow-y-auto py-[var(--spacing-system-xs)]", role: "listbox", children: [
20627
+ /* @__PURE__ */ jsxs105("div", { className: "min-h-0 max-h-72 overflow-y-auto py-[var(--spacing-system-xs)]", role: "listbox", children: [
20602
20628
  ordered.map((opt) => {
20603
20629
  const isSelected = selectedIds.includes(opt.id);
20604
20630
  return /* @__PURE__ */ jsxs105(
@@ -20839,6 +20865,7 @@ function TagsManager({
20839
20865
  }) {
20840
20866
  const [open, setOpen] = React65.useState(false);
20841
20867
  const [search, setSearch] = React65.useState("");
20868
+ const keyboardPadding = useKeyboardCollisionPadding();
20842
20869
  const [editingId, setEditingId] = React65.useState(null);
20843
20870
  const [editingName, setEditingName] = React65.useState("");
20844
20871
  const editInputRef = React65.useRef(null);
@@ -20989,6 +21016,7 @@ function TagsManager({
20989
21016
  className: cn(
20990
21017
  "z-50 w-[var(--radix-popover-trigger-width)] mt-1",
20991
21018
  "bg-ods-card border border-ods-border rounded",
21019
+ "flex flex-col overflow-hidden max-h-[var(--radix-popper-available-height)]",
20992
21020
  "data-[state=open]:animate-in data-[state=closed]:animate-out",
20993
21021
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
20994
21022
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
@@ -20996,6 +21024,7 @@ function TagsManager({
20996
21024
  ),
20997
21025
  sideOffset: 4,
20998
21026
  align: "start",
21027
+ collisionPadding: { bottom: keyboardPadding },
20999
21028
  onOpenAutoFocus: (e) => {
21000
21029
  e.preventDefault();
21001
21030
  inputRef.current?.focus();
@@ -21005,8 +21034,8 @@ function TagsManager({
21005
21034
  e.preventDefault();
21006
21035
  }
21007
21036
  },
21008
- children: /* @__PURE__ */ jsxs107(ScrollAreaPrimitive2.Root, { className: "overflow-hidden", children: [
21009
- /* @__PURE__ */ jsx138(ScrollAreaPrimitive2.Viewport, { className: "max-h-60 w-full", children: /* @__PURE__ */ jsxs107("div", { role: "listbox", children: [
21037
+ children: /* @__PURE__ */ jsxs107(ScrollAreaPrimitive2.Root, { className: "flex min-h-0 flex-col overflow-hidden", children: [
21038
+ /* @__PURE__ */ jsx138(ScrollAreaPrimitive2.Viewport, { className: "min-h-0 max-h-60 w-full", children: /* @__PURE__ */ jsxs107("div", { role: "listbox", children: [
21010
21039
  filtered.map((tag) => {
21011
21040
  const isSelected = selectedIds.includes(tag.id);
21012
21041
  const isEditing = editingId === tag.id;
@@ -23109,9 +23138,8 @@ function useRichMarkdownRuntime() {
23109
23138
  import { useState as useState50, useEffect as useEffect41, useRef as useRef33 } from "react";
23110
23139
 
23111
23140
  // src/utils/social-embed-cache.ts
23112
- var cachingRequests = /* @__PURE__ */ new Map();
23113
23141
  var dataCache = /* @__PURE__ */ new Map();
23114
- var refreshTimestamps = /* @__PURE__ */ new Map();
23142
+ var inflightRequests = /* @__PURE__ */ new Map();
23115
23143
  var SocialEmbedCache = class _SocialEmbedCache {
23116
23144
  static getInstance() {
23117
23145
  if (!_SocialEmbedCache.instance) {
@@ -23119,269 +23147,64 @@ var SocialEmbedCache = class _SocialEmbedCache {
23119
23147
  }
23120
23148
  return _SocialEmbedCache.instance;
23121
23149
  }
23122
- // Check if data exists in memory cache
23123
23150
  getFromMemory(url) {
23124
23151
  return dataCache.get(url) || null;
23125
23152
  }
23126
- // Store data in memory cache
23127
23153
  setInMemory(url, data) {
23128
23154
  dataCache.set(url, data);
23129
23155
  }
23130
- // Check server cache (cache-only mode)
23131
- async getFromServer(options) {
23132
- try {
23133
- console.log(`\u{1F50D} [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Checking server cache for: ${options.url}`);
23134
- const response = await fetch(`${options.apiEndpoint}?url=${encodeURIComponent(options.url)}&cache-only=true`);
23135
- if (response.ok) {
23136
- const data = await response.json();
23137
- console.log(`\u2705 [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Server cache hit: ${options.url}`);
23138
- return data;
23139
- } else if (response.status === 404) {
23140
- console.log(`\u{1F4A8} [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] No server cache available for: ${options.url}`);
23141
- return null;
23142
- }
23143
- } catch (error) {
23144
- console.log(`\u274C [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Server cache check failed for: ${options.url}`, error);
23145
- }
23146
- return null;
23147
- }
23148
- // Helper method to update server cache asynchronously
23149
- async updateServerCache(apiEndpoint, url, data) {
23150
- try {
23151
- console.log(`\u{1F4BE} [Cache] Updating server cache for: ${url}`);
23152
- await fetch(`${apiEndpoint}?url=${encodeURIComponent(url)}`, {
23153
- method: "POST",
23154
- headers: { "Content-Type": "application/json" },
23155
- body: JSON.stringify(data)
23156
- });
23157
- console.log(`\u2705 [Cache] Server cache updated for: ${url}`);
23158
- } catch (error) {
23159
- console.log(`\u274C [Cache] Server cache update failed for: ${url}`, error);
23160
- }
23161
- }
23162
- // Store data in server cache (with deduplication)
23163
- async setInServer(options, data) {
23164
- const cacheKey = `${options.platform}-${options.url}`;
23165
- if (cachingRequests.has(cacheKey)) {
23166
- console.log(`\u23ED\uFE0F [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Server cache update skipped (already in progress): ${options.url}`);
23167
- return;
23168
- }
23169
- console.log(`\u{1F4BE} [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Updating server cache for: ${options.url}`);
23170
- const cachePromise = fetch(`${options.apiEndpoint}?url=${encodeURIComponent(options.url)}`, {
23171
- method: "POST",
23172
- headers: { "Content-Type": "application/json" },
23173
- body: JSON.stringify(data)
23174
- }).then(() => {
23175
- console.log(`\u2705 [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Server cache updated successfully: ${options.url}`);
23176
- cachingRequests.delete(cacheKey);
23177
- }).catch((error) => {
23178
- console.log(`\u274C [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Server cache update failed: ${options.url}`, error);
23179
- cachingRequests.delete(cacheKey);
23180
- });
23181
- cachingRequests.set(cacheKey, cachePromise);
23182
- await cachePromise;
23183
- }
23184
- // Direct fetch with platform-specific logic
23185
- async fetchDirect(options) {
23186
- try {
23187
- console.log(`\u{1F504} [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Direct] Fetching content: ${options.url}`);
23188
- const result = await options.directFetcher();
23189
- console.log(`\u2705 [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Direct] Fetch successful: ${options.url}`);
23190
- return result;
23191
- } catch (error) {
23192
- console.log(`\u274C [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Direct] Fetch failed: ${options.url}`, error);
23193
- return null;
23194
- }
23195
- }
23196
- // Unified cache hierarchy: Memory → Server → Direct
23156
+ /**
23157
+ * Memory → proxy. Kept the historical name so the embed clients'
23158
+ * call sites are unchanged.
23159
+ */
23197
23160
  async fetchWithHierarchy(options) {
23198
- const { url, platform, onDataUpdate, onError, onLoading, dataValidator } = options;
23199
- const platformName = platform.charAt(0).toUpperCase() + platform.slice(1);
23200
- this.scheduleAsyncRefresh(platform, url, dataValidator, options.apiEndpoint);
23201
- console.log(`\u{1F50D} [${platformName} Cache] Checking memory cache for: ${url}`);
23161
+ const { url, platform, apiEndpoint, dataValidator, onDataUpdate, onError, onLoading } = options;
23202
23162
  const memoryData = this.getFromMemory(url);
23203
23163
  if (memoryData && dataValidator(memoryData)) {
23204
- console.log(`\u{1F48E} [${platformName} Cache] Memory cache hit for: ${url}`);
23205
23164
  onDataUpdate(memoryData);
23206
23165
  onLoading(false);
23207
23166
  return;
23208
23167
  }
23209
- console.log(`\u{1F50D} [${platformName} Cache] No memory cache, checking server cache for: ${url}`);
23210
- const serverCacheData = await this.getFromServer(options);
23211
- if (serverCacheData && dataValidator(serverCacheData)) {
23212
- console.log(`\u{1F3AF} [${platformName} Cache] Server cache hit (cached data) for: ${url}`);
23213
- onDataUpdate(serverCacheData);
23214
- this.setInMemory(url, serverCacheData);
23215
- onLoading(false);
23216
- return;
23217
- }
23218
- console.log(`\u{1F50D} [${platformName} Cache] No server cache, attempting direct fetch for: ${url}`);
23219
- if (platform === "reddit") {
23220
- try {
23221
- const directResponse = await fetch(url, {
23222
- method: "GET",
23223
- headers: {
23224
- "Accept": "application/json"
23225
- },
23226
- signal: AbortSignal.timeout(1e4)
23227
- });
23228
- if (directResponse.ok) {
23229
- const directData = await directResponse.json();
23230
- if (directData && dataValidator(directData)) {
23231
- console.log(`\u2705 [${platformName} Direct] Browser direct fetch successful for: ${url}`);
23232
- onDataUpdate(directData);
23233
- this.setInMemory(url, directData);
23234
- onLoading(false);
23235
- setTimeout(() => {
23236
- this.updateServerCache(options.apiEndpoint, url, directData);
23237
- }, 0);
23238
- return;
23239
- }
23240
- }
23241
- console.log(`\u{1F4A8} [${platformName} Direct] Browser direct fetch failed (${directResponse.status}), trying server proxy...`);
23242
- } catch (directError) {
23243
- console.log(`\u{1F4A8} [${platformName} Direct] Browser direct fetch failed, trying server proxy...`, directError);
23244
- }
23245
- }
23246
- console.log(`\u{1F50D} [${platformName} Cache] Trying server proxy for: ${url}`);
23247
23168
  try {
23248
- const response = await fetch(`${options.apiEndpoint}?url=${encodeURIComponent(url)}`);
23249
- if (response.ok) {
23250
- const serverData = await response.json();
23251
- if (serverData && dataValidator(serverData)) {
23252
- const cacheStatus = response.headers.get("x-cache") || "UNKNOWN";
23253
- if (cacheStatus === "HIT") {
23254
- console.log(`\u{1F3AF} [${platformName} Proxy] Server cache hit (cached data) for: ${url}`);
23255
- } else if (cacheStatus === "MISS-FETCHED") {
23256
- console.log(`\u{1F504} [${platformName} Proxy] Server processed fresh fetch (now cached) for: ${url}`);
23257
- } else {
23258
- console.log(`\u2705 [${platformName} Proxy] Server returned data (${cacheStatus}) for: ${url}`);
23259
- }
23260
- onDataUpdate(serverData);
23261
- this.setInMemory(url, serverData);
23262
- onLoading(false);
23263
- return;
23264
- } else {
23265
- console.log(`\u26A0\uFE0F [${platformName} Proxy] Server returned data but validation failed for: ${url}`);
23266
- throw new Error("Server proxy returned invalid data shape");
23267
- }
23268
- } else {
23269
- let errorData = null;
23270
- try {
23271
- errorData = await response.json();
23272
- } catch {
23273
- }
23274
- if (response.status === 404 && errorData?.error && errorData?.message) {
23275
- console.log(`\u{1F6AB} [${platformName} Proxy] Content unavailable: ${errorData.message}`);
23276
- onError(errorData.message);
23277
- onLoading(false);
23278
- return;
23279
- }
23280
- if (errorData?.error) {
23281
- throw new Error(`Server error: ${errorData.error}`);
23282
- }
23283
- throw new Error(`Server proxy failed: ${response.status}`);
23169
+ const data = await this.fetchFromProxy(url, apiEndpoint, dataValidator);
23170
+ if (data) {
23171
+ this.setInMemory(url, data);
23172
+ onDataUpdate(data);
23173
+ onLoading(false);
23174
+ return;
23284
23175
  }
23176
+ onError(`This ${platform === "reddit" ? "Reddit post" : "tweet"} could not be loaded.`);
23285
23177
  } catch (error) {
23286
- console.log(`\u274C [${platformName} Cache] All fetch attempts failed for: ${url}`, error);
23178
+ console.log(`\u274C [${platform} embed] Proxy fetch failed for: ${url}`, error);
23287
23179
  onError(`Unable to load ${platform} content`);
23288
- onLoading(false);
23289
23180
  }
23181
+ onLoading(false);
23290
23182
  }
23291
- // Schedule async background refresh (runs once, 1 second after load)
23292
- //
23293
- // The hub's old version hard-coded `/api/blog/{reddit,twitter}-proxy`. We
23294
- // now require `apiEndpoint` so embedders can route through their own
23295
- // reverse proxy (e.g. `/content/api/blog/...`).
23296
- scheduleAsyncRefresh(platform, url, dataValidator, apiEndpoint) {
23297
- setTimeout(() => {
23298
- const platformName = platform.charAt(0).toUpperCase() + platform.slice(1);
23299
- console.log(`\u{1F504} [${platformName} Async] Background refresh starting for: ${url}`);
23300
- fetch(`${apiEndpoint}?url=${encodeURIComponent(url)}`).then((response) => {
23301
- if (response.ok) {
23302
- return response.json();
23303
- } else {
23304
- console.log(`\u274C [${platformName} Async] Background refresh failed (${response.status}) for: ${url}`);
23305
- return null;
23306
- }
23307
- }).then((data) => {
23308
- if (data && dataValidator(data)) {
23309
- this.setInMemory(url, data);
23310
- console.log(`\u2705 [${platformName} Async] Background refresh completed for: ${url}`);
23311
- } else if (data) {
23312
- console.log(`\u274C [${platformName} Async] Background refresh failed (invalid data) for: ${url}`);
23313
- }
23314
- }).catch((error) => console.log(`\u274C [${platformName} Async] Background refresh failed for: ${url}`, error));
23315
- }, 1e3);
23316
- }
23317
- // Background refresh to keep cache fresh (with request deduplication)
23318
- async backgroundRefresh(options, currentData) {
23319
- const refreshKey = `refresh-${options.platform}-${options.url}`;
23320
- const now = Date.now();
23321
- const lastRefresh = refreshTimestamps.get(refreshKey);
23322
- if (lastRefresh && now - lastRefresh < 3e4) {
23323
- console.log(`\u23ED\uFE0F [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Background] Refresh skipped (recent refresh): ${options.url}`);
23324
- return;
23325
- }
23326
- if (cachingRequests.has(refreshKey)) {
23327
- console.log(`\u23ED\uFE0F [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Background] Refresh skipped (already in progress): ${options.url}`);
23328
- return;
23329
- }
23330
- const refreshPromise = (async () => {
23331
- try {
23332
- console.log(`\u{1F504} [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Background] Starting background refresh for: ${options.url}`);
23333
- refreshTimestamps.set(refreshKey, now);
23334
- const freshData = await this.fetchDirect(options);
23335
- if (freshData && options.dataValidator(freshData)) {
23336
- this.setInMemory(options.url, freshData);
23337
- await this.setInServer(options, freshData);
23338
- console.log(`\u2705 [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Background] Background refresh completed for: ${options.url}`);
23339
- } else {
23340
- console.log(`\u274C [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Background] Background refresh failed (invalid data) for: ${options.url}`);
23341
- }
23342
- } catch (error) {
23343
- console.log(`\u274C [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Background] Background refresh failed for: ${options.url}`, error);
23344
- } finally {
23345
- cachingRequests.delete(refreshKey);
23183
+ /**
23184
+ * Returns the validated payload, `null` for a structured "content
23185
+ * unavailable" 404, and throws for everything else. Concurrent calls for
23186
+ * the same URL share one request.
23187
+ */
23188
+ fetchFromProxy(url, apiEndpoint, dataValidator) {
23189
+ const inflight = inflightRequests.get(url);
23190
+ if (inflight) return inflight;
23191
+ const request = (async () => {
23192
+ const response = await fetch(`${apiEndpoint}?url=${encodeURIComponent(url)}`, {
23193
+ signal: AbortSignal.timeout(15e3)
23194
+ });
23195
+ if (response.ok) {
23196
+ const data = await response.json();
23197
+ if (data && dataValidator(data)) return data;
23198
+ throw new Error("Server proxy returned invalid data shape");
23346
23199
  }
23200
+ if (response.status === 404) return null;
23201
+ throw new Error(`Server proxy failed: ${response.status}`);
23347
23202
  })();
23348
- cachingRequests.set(refreshKey, refreshPromise);
23349
- await refreshPromise;
23350
- }
23351
- // Silent background refresh for UI-sensitive components
23352
- silentBackgroundRefresh(options) {
23353
- const refreshKey = `silent-refresh-${options.platform}-${options.url}`;
23354
- const now = Date.now();
23355
- const lastRefresh = refreshTimestamps.get(refreshKey);
23356
- if (lastRefresh && now - lastRefresh < 3e5) {
23357
- console.log(`\u23ED\uFE0F [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Silent] Silent refresh skipped (recent refresh): ${options.url}`);
23358
- return;
23359
- }
23360
- if (cachingRequests.has(refreshKey)) {
23361
- console.log(`\u23ED\uFE0F [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Silent] Silent refresh skipped (already in progress): ${options.url}`);
23362
- return;
23363
- }
23364
- setTimeout(() => {
23365
- const refreshPromise = (async () => {
23366
- try {
23367
- console.log(`\u{1F504} [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Silent] Starting silent background refresh for: ${options.url}`);
23368
- refreshTimestamps.set(refreshKey, now);
23369
- const freshData = await this.fetchDirect(options);
23370
- if (freshData && options.dataValidator(freshData)) {
23371
- this.setInMemory(options.url, freshData);
23372
- await this.setInServer(options, freshData);
23373
- console.log(`\u2705 [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Silent] Silent refresh completed for: ${options.url}`);
23374
- } else {
23375
- console.log(`\u274C [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Silent] Silent refresh failed (invalid data) for: ${options.url}`);
23376
- }
23377
- } catch (error) {
23378
- console.log(`\u274C [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Silent] Silent refresh failed for: ${options.url}`, error);
23379
- } finally {
23380
- cachingRequests.delete(refreshKey);
23381
- }
23382
- })();
23383
- cachingRequests.set(refreshKey, refreshPromise);
23384
- }, 0);
23203
+ inflightRequests.set(
23204
+ url,
23205
+ request.finally(() => inflightRequests.delete(url))
23206
+ );
23207
+ return inflightRequests.get(url);
23385
23208
  }
23386
23209
  };
23387
23210
  var socialCache = SocialEmbedCache.getInstance();
@@ -25019,13 +24842,15 @@ function buildRichEmbedOverrides({
25019
24842
  }
25020
24843
  return standard.div(divProps);
25021
24844
  },
25022
- // Reddit's OWN embed markup lives in 58 published blog posts as
25023
- // `<blockquote class="reddit-embed-bq">…<a href="post-url">` paired with
25024
- // a `<script src="embed.reddit.com/widgets.js">` loader. The sanitize
25025
- // stack strips the script (correctly), so the composition rehydrates the
25026
- // blockquote itself: extract the post URL from the first reddit link and
25027
- // render the RedditEmbedClient SSOT. Non-reddit blockquotes fall through
25028
- // to the engine's base blockquote.
24845
+ // Pasted-from-the-platform embed markup lives in published blog posts as
24846
+ // `<blockquote class="reddit-embed-bq">…<a href="post-url">` (58 blocks
24847
+ // across 9 posts) — and Twitter's equivalent is
24848
+ // `<blockquote class="twitter-tweet">…<a href="status-url">`. Each pair
24849
+ // ships a `widgets.js` loader script, which `processShortcodes` strips
24850
+ // from the source text (Step 1.5). The composition rehydrates the
24851
+ // blockquote itself: extract the post URL from the first matching link
24852
+ // and render the platform's embed-client SSOT. Other blockquotes fall
24853
+ // through to the engine's base blockquote.
25029
24854
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
25030
24855
  blockquote: (bqProps) => {
25031
24856
  const { node, className } = bqProps;
@@ -25034,6 +24859,10 @@ function buildRichEmbedOverrides({
25034
24859
  const postUrl = findFirstHref(node, /reddit\.com/);
25035
24860
  if (postUrl) return /* @__PURE__ */ jsx169(RedditEmbedClient, { url: postUrl });
25036
24861
  }
24862
+ if (classNames.includes("twitter-tweet")) {
24863
+ const tweetUrl = findFirstHref(node, /(?:twitter\.com|x\.com)\/[^/]+\/status\//);
24864
+ if (tweetUrl) return /* @__PURE__ */ jsx169(TwitterEmbedClient, { url: tweetUrl });
24865
+ }
25037
24866
  return standard.blockquote(bqProps);
25038
24867
  },
25039
24868
  // In-article images: MarkdownImage reads `transformImageSrc` from the
@@ -25120,6 +24949,10 @@ var processShortcodes = (content) => {
25120
24949
  codeBlocks.push(match2);
25121
24950
  return placeholder;
25122
24951
  });
24952
+ processedContent = processedContent.replace(
24953
+ /<script\b[^>]{0,2000}>[\s\S]{0,20000}?<\/script\s{0,10}>/gi,
24954
+ ""
24955
+ );
25123
24956
  const markdownLinks = [];
25124
24957
  processedContent = processedContent.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match2) => {
25125
24958
  const placeholder = `__MARKDOWN_LINK_${markdownLinks.length}__`;
@@ -46188,6 +46021,7 @@ function SearchInput({
46188
46021
  const debouncedValue = useDebounce(currentValue, debounceMs);
46189
46022
  const [isOpen, setIsOpen] = React132.useState(false);
46190
46023
  const [highlightedIndex, setHighlightedIndex] = React132.useState(-1);
46024
+ const keyboardPadding = useKeyboardCollisionPadding();
46191
46025
  const containerRef = React132.useRef(null);
46192
46026
  const currentPlaceholder = filterChips.length > 0 ? "Add filter..." : placeholder;
46193
46027
  const {
@@ -46447,6 +46281,7 @@ function SearchInput({
46447
46281
  className: cn(
46448
46282
  "z-50 w-[var(--radix-popover-trigger-width)] mt-1",
46449
46283
  "bg-ods-card border border-ods-border rounded-[6px] overflow-hidden shadow-lg",
46284
+ "flex flex-col max-h-[var(--radix-popper-available-height)]",
46450
46285
  "data-[state=open]:animate-in data-[state=closed]:animate-out",
46451
46286
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
46452
46287
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
@@ -46455,6 +46290,7 @@ function SearchInput({
46455
46290
  ),
46456
46291
  sideOffset: 4,
46457
46292
  align: "start",
46293
+ collisionPadding: { bottom: keyboardPadding },
46458
46294
  onOpenAutoFocus: (e) => {
46459
46295
  e.preventDefault();
46460
46296
  inputRef.current?.focus();
@@ -46464,8 +46300,8 @@ function SearchInput({
46464
46300
  e.preventDefault();
46465
46301
  }
46466
46302
  },
46467
- children: /* @__PURE__ */ jsxs281(ScrollAreaPrimitive3.Root, { className: "overflow-hidden", children: [
46468
- /* @__PURE__ */ jsx357(ScrollAreaPrimitive3.Viewport, { className: "max-h-[320px] w-full", children: /* @__PURE__ */ jsx357("div", { role: "listbox", children: renderDropdownContent() }) }),
46303
+ children: /* @__PURE__ */ jsxs281(ScrollAreaPrimitive3.Root, { className: "flex min-h-0 flex-col overflow-hidden", children: [
46304
+ /* @__PURE__ */ jsx357(ScrollAreaPrimitive3.Viewport, { className: "min-h-0 max-h-[320px] w-full", children: /* @__PURE__ */ jsx357("div", { role: "listbox", children: renderDropdownContent() }) }),
46469
46305
  /* @__PURE__ */ jsx357(ScrollAreaPrimitive3.Scrollbar, { className: "hidden", orientation: "vertical", children: /* @__PURE__ */ jsx357(ScrollAreaPrimitive3.Thumb, {}) })
46470
46306
  ] })
46471
46307
  }
@@ -47303,7 +47139,8 @@ function FileUpload({
47303
47139
  return;
47304
47140
  }
47305
47141
  try {
47306
- handleFiles(await pickFiles());
47142
+ const picked = await pickFiles();
47143
+ if (picked.length > 0) handleFiles(picked);
47307
47144
  } catch (err) {
47308
47145
  setValidationError(err instanceof Error ? err.message : "Could not open the file picker");
47309
47146
  }
@@ -47657,19 +47494,23 @@ import * as PopoverPrimitive7 from "@radix-ui/react-popover";
47657
47494
  import { jsx as jsx363 } from "react/jsx-runtime";
47658
47495
  var Popover2 = PopoverPrimitive7.Root;
47659
47496
  var PopoverTrigger = PopoverPrimitive7.Trigger;
47660
- var PopoverContent = React137.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx363(PopoverPrimitive7.Portal, { children: /* @__PURE__ */ jsx363(
47661
- PopoverPrimitive7.Content,
47662
- {
47663
- ref,
47664
- align,
47665
- sideOffset,
47666
- className: cn(
47667
- "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
47668
- className
47669
- ),
47670
- ...props
47671
- }
47672
- ) }));
47497
+ var PopoverContent = React137.forwardRef(({ className, align = "center", sideOffset = 4, collisionPadding, ...props }, ref) => {
47498
+ const keyboardPadding = useKeyboardCollisionPadding();
47499
+ return /* @__PURE__ */ jsx363(PopoverPrimitive7.Portal, { children: /* @__PURE__ */ jsx363(
47500
+ PopoverPrimitive7.Content,
47501
+ {
47502
+ ref,
47503
+ align,
47504
+ sideOffset,
47505
+ collisionPadding: collisionPadding ?? { bottom: keyboardPadding },
47506
+ className: cn(
47507
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
47508
+ className
47509
+ ),
47510
+ ...props
47511
+ }
47512
+ ) });
47513
+ });
47673
47514
  PopoverContent.displayName = PopoverPrimitive7.Content.displayName;
47674
47515
 
47675
47516
  // src/components/ui/color-preset-select.tsx
@@ -62267,4 +62108,4 @@ export {
62267
62108
  LogsList,
62268
62109
  assets
62269
62110
  };
62270
- //# sourceMappingURL=chunk-LIUARTOZ.js.map
62111
+ //# sourceMappingURL=chunk-4DTUUM5V.js.map