@flamingo-stack/openframe-frontend-core 0.0.533 → 0.0.535-1762.4618.1

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 (104) hide show
  1. package/dist/{chunk-NAV4FRNL.cjs → chunk-2AMYXNQ2.cjs} +26 -26
  2. package/dist/{chunk-NAV4FRNL.cjs.map → chunk-2AMYXNQ2.cjs.map} +1 -1
  3. package/dist/{chunk-2XGHEJZK.js → chunk-5OPVJBHQ.js} +4 -4
  4. package/dist/{chunk-4QKXNWH5.js → chunk-6OZWDOP5.js} +2 -2
  5. package/dist/{chunk-6YXN5RHW.js → chunk-7TQDW3PE.js} +2 -2
  6. package/dist/{chunk-U555TMPC.js → chunk-A5EA3YO7.js} +5 -5
  7. package/dist/{chunk-DFQLUK75.cjs → chunk-CMIHA6TT.cjs} +3 -3
  8. package/dist/{chunk-DFQLUK75.cjs.map → chunk-CMIHA6TT.cjs.map} +1 -1
  9. package/dist/{chunk-QZ2NAPLF.cjs → chunk-DK6IYFLD.cjs} +9 -9
  10. package/dist/{chunk-QZ2NAPLF.cjs.map → chunk-DK6IYFLD.cjs.map} +1 -1
  11. package/dist/{chunk-LE3BJ5H2.js → chunk-DRJC7TTW.js} +2 -2
  12. package/dist/{chunk-ENPGPZIB.cjs → chunk-ENOER2QA.cjs} +61 -61
  13. package/dist/{chunk-ENPGPZIB.cjs.map → chunk-ENOER2QA.cjs.map} +1 -1
  14. package/dist/{chunk-LIUARTOZ.js → chunk-GBWS62LR.js} +106 -285
  15. package/dist/chunk-GBWS62LR.js.map +1 -0
  16. package/dist/{chunk-2MYDGLJS.cjs → chunk-GF5A23J3.cjs} +87 -87
  17. package/dist/{chunk-2MYDGLJS.cjs.map → chunk-GF5A23J3.cjs.map} +1 -1
  18. package/dist/{chunk-4ZRHVOEB.cjs → chunk-GRGFMXNO.cjs} +31 -31
  19. package/dist/{chunk-4ZRHVOEB.cjs.map → chunk-GRGFMXNO.cjs.map} +1 -1
  20. package/dist/{chunk-46FKXLNE.js → chunk-GS4R5UQ7.js} +2 -2
  21. package/dist/{chunk-2TUOBFPK.js → chunk-HNZDQ4SH.js} +2 -2
  22. package/dist/{chunk-KMB66NYX.cjs → chunk-HSRHPF32.cjs} +7 -7
  23. package/dist/{chunk-KMB66NYX.cjs.map → chunk-HSRHPF32.cjs.map} +1 -1
  24. package/dist/{chunk-RKITTO5V.cjs → chunk-JW4BTXVH.cjs} +14 -14
  25. package/dist/{chunk-RKITTO5V.cjs.map → chunk-JW4BTXVH.cjs.map} +1 -1
  26. package/dist/{chunk-MVGL5NNJ.js → chunk-JWJQHURJ.js} +2 -2
  27. package/dist/{chunk-5U67WTAS.js → chunk-JWWZFEFI.js} +2 -2
  28. package/dist/{chunk-JDW2IL7A.cjs → chunk-JZMQQBUC.cjs} +11 -11
  29. package/dist/{chunk-JDW2IL7A.cjs.map → chunk-JZMQQBUC.cjs.map} +1 -1
  30. package/dist/{chunk-XN4JKIJY.js → chunk-QHOVACZK.js} +2 -2
  31. package/dist/{chunk-UPHN2VRZ.cjs → chunk-T4GNFVT3.cjs} +800 -979
  32. package/dist/chunk-T4GNFVT3.cjs.map +1 -0
  33. package/dist/{chunk-GNOK5J34.cjs → chunk-T743Z3KN.cjs} +37 -37
  34. package/dist/{chunk-GNOK5J34.cjs.map → chunk-T743Z3KN.cjs.map} +1 -1
  35. package/dist/{chunk-J4BE4FIR.cjs → chunk-TOWXHZF5.cjs} +4 -4
  36. package/dist/{chunk-J4BE4FIR.cjs.map → chunk-TOWXHZF5.cjs.map} +1 -1
  37. package/dist/{chunk-6SMLYZLH.js → chunk-URBYMQ4Z.js} +4 -4
  38. package/dist/{chunk-Z3NOI7X7.cjs → chunk-WL3PP4D6.cjs} +26 -26
  39. package/dist/{chunk-Z3NOI7X7.cjs.map → chunk-WL3PP4D6.cjs.map} +1 -1
  40. package/dist/{chunk-YLB6XXO6.js → chunk-WXRQYGE5.js} +2 -2
  41. package/dist/{chunk-6W3ULZ4X.js → chunk-XPKCOZQE.js} +6 -6
  42. package/dist/{chunk-OLOKQQAP.cjs → chunk-ZUVBTDLG.cjs} +5 -5
  43. package/dist/{chunk-OLOKQQAP.cjs.map → chunk-ZUVBTDLG.cjs.map} +1 -1
  44. package/dist/components/case-studies/index.cjs +8 -8
  45. package/dist/components/case-studies/index.js +2 -2
  46. package/dist/components/chat/chat-input.d.ts.map +1 -1
  47. package/dist/components/chat/index.cjs +2 -2
  48. package/dist/components/chat/index.js +1 -1
  49. package/dist/components/contact/index.cjs +3 -3
  50. package/dist/components/contact/index.js +2 -2
  51. package/dist/components/docs/index.cjs +5 -5
  52. package/dist/components/docs/index.js +4 -4
  53. package/dist/components/embeds/index.cjs +3 -3
  54. package/dist/components/embeds/index.js +2 -2
  55. package/dist/components/faq/index.cjs +3 -3
  56. package/dist/components/faq/index.js +2 -2
  57. package/dist/components/features/index.cjs +2 -2
  58. package/dist/components/features/index.js +1 -1
  59. package/dist/components/help-center-pages/index.cjs +22 -22
  60. package/dist/components/help-center-pages/index.js +13 -13
  61. package/dist/components/index.cjs +132 -132
  62. package/dist/components/index.js +11 -11
  63. package/dist/components/meeting-scheduler/index.cjs +34 -34
  64. package/dist/components/meeting-scheduler/index.js +3 -3
  65. package/dist/components/navigation/index.cjs +2 -2
  66. package/dist/components/navigation/index.js +1 -1
  67. package/dist/components/onboarding-guides/index.cjs +5 -5
  68. package/dist/components/onboarding-guides/index.js +4 -4
  69. package/dist/components/related-content/index.cjs +3 -3
  70. package/dist/components/related-content/index.js +2 -2
  71. package/dist/components/tickets/index.cjs +6 -6
  72. package/dist/components/tickets/index.js +5 -5
  73. package/dist/components/ui/file-upload.d.ts.map +1 -1
  74. package/dist/components/ui/index.cjs +2 -2
  75. package/dist/components/ui/index.js +1 -1
  76. package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +1 -1
  77. package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +1 -1
  78. package/dist/index.cjs +2 -2
  79. package/dist/index.js +1 -1
  80. package/dist/utils/social-embed-cache.d.ts +16 -16
  81. package/dist/utils/social-embed-cache.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/components/chat/chat-input.tsx +84 -32
  84. package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +1 -5
  85. package/src/components/ui/file-upload.tsx +5 -1
  86. package/src/components/ui/markdown/rich/__tests__/shortcodes.test.ts +58 -0
  87. package/src/components/ui/markdown/rich/embed-overrides.tsx +13 -7
  88. package/src/components/ui/markdown/rich/shortcodes.ts +15 -0
  89. package/src/utils/social-embed-cache.ts +63 -336
  90. package/dist/chunk-LIUARTOZ.js.map +0 -1
  91. package/dist/chunk-UPHN2VRZ.cjs.map +0 -1
  92. /package/dist/{chunk-2XGHEJZK.js.map → chunk-5OPVJBHQ.js.map} +0 -0
  93. /package/dist/{chunk-4QKXNWH5.js.map → chunk-6OZWDOP5.js.map} +0 -0
  94. /package/dist/{chunk-6YXN5RHW.js.map → chunk-7TQDW3PE.js.map} +0 -0
  95. /package/dist/{chunk-U555TMPC.js.map → chunk-A5EA3YO7.js.map} +0 -0
  96. /package/dist/{chunk-LE3BJ5H2.js.map → chunk-DRJC7TTW.js.map} +0 -0
  97. /package/dist/{chunk-46FKXLNE.js.map → chunk-GS4R5UQ7.js.map} +0 -0
  98. /package/dist/{chunk-2TUOBFPK.js.map → chunk-HNZDQ4SH.js.map} +0 -0
  99. /package/dist/{chunk-MVGL5NNJ.js.map → chunk-JWJQHURJ.js.map} +0 -0
  100. /package/dist/{chunk-5U67WTAS.js.map → chunk-JWWZFEFI.js.map} +0 -0
  101. /package/dist/{chunk-XN4JKIJY.js.map → chunk-QHOVACZK.js.map} +0 -0
  102. /package/dist/{chunk-6SMLYZLH.js.map → chunk-URBYMQ4Z.js.map} +0 -0
  103. /package/dist/{chunk-YLB6XXO6.js.map → chunk-WXRQYGE5.js.map} +0 -0
  104. /package/dist/{chunk-6W3ULZ4X.js.map → chunk-XPKCOZQE.js.map} +0 -0
@@ -6530,6 +6530,7 @@ var ChatInput = forwardRef13((allProps, ref) => {
6530
6530
  // contenteditable div, not a <textarea>, so spreading them would warn.
6531
6531
  } = rest;
6532
6532
  const [value, setValue] = useState10("");
6533
+ const [hasChips, setHasChips] = useState10(false);
6533
6534
  const [focused, setFocused] = useState10(false);
6534
6535
  const [isStopping, setIsStopping] = useState10(false);
6535
6536
  const [previewDismissed, setPreviewDismissed] = useState10(false);
@@ -6565,26 +6566,32 @@ var ChatInput = forwardRef13((allProps, ref) => {
6565
6566
  if (typeof caret === "number") placeCaretAtOffset(el, caret);
6566
6567
  else placeCaretAtEnd(el);
6567
6568
  }
6569
+ setHasChips(el.querySelector("[data-token]") !== null);
6568
6570
  }
6571
+ valueRef.current = next;
6569
6572
  setValue(next);
6570
6573
  }, []);
6571
6574
  const fire = useCallback3(
6572
6575
  (message2) => {
6573
6576
  const can = (message2.length > 0 || allowEmptySend) && !sending && !disabled;
6574
6577
  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();
6578
+ const sentMeta = metaRef.current;
6579
+ metaRef.current = /* @__PURE__ */ new Map();
6580
+ applyValue("");
6581
+ shouldRefocusRef.current = true;
6582
+ focusEditor();
6583
+ const restore = () => {
6584
+ if (valueRef.current.length > 0) return;
6585
+ metaRef.current = sentMeta;
6586
+ applyValue(message2);
6581
6587
  };
6588
+ const result = onSend(message2);
6582
6589
  if (result instanceof Promise) {
6583
6590
  void result.then((ok) => {
6584
- if (ok !== false) done();
6591
+ if (ok === false) restore();
6585
6592
  });
6586
- } else if (result !== false) {
6587
- done();
6593
+ } else if (result === false) {
6594
+ restore();
6588
6595
  }
6589
6596
  },
6590
6597
  [allowEmptySend, sending, disabled, onSend, focusEditor, applyValue]
@@ -6673,6 +6680,7 @@ var ChatInput = forwardRef13((allProps, ref) => {
6673
6680
  const syncFromDom = useCallback3(() => {
6674
6681
  const el = editorRef.current;
6675
6682
  if (!el) return;
6683
+ setHasChips(el.querySelector("[data-token]") !== null);
6676
6684
  setValue(serialize(el));
6677
6685
  }, []);
6678
6686
  const handleInput = useCallback3(() => {
@@ -6778,8 +6786,7 @@ var ChatInput = forwardRef13((allProps, ref) => {
6778
6786
  const hasContent2 = value.trim().length > 0 || allowEmptySend;
6779
6787
  const sendDisabled = sending || disabled || !hasContent2;
6780
6788
  const isEmpty = value.length === 0;
6781
- const isDisabled = sending || disabled;
6782
- const showPreview = !isDisabled && !!previewText && !previewDismissed;
6789
+ const showPreview = !sending && !disabled && !!previewText && !previewDismissed;
6783
6790
  return /* @__PURE__ */ jsx30(
6784
6791
  "div",
6785
6792
  {
@@ -6813,23 +6820,24 @@ var ChatInput = forwardRef13((allProps, ref) => {
6813
6820
  },
6814
6821
  className: cn(
6815
6822
  // `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.
6823
+ // vertically centered the same way. With a chip in the box, `py-1.5`
6824
+ // (6px) pairs with the editor's `leading-9` (36px) → that row is
6825
+ // 36 + 2*6 = 48px. The 36px line box is just barely above the 32px
6826
+ // chip (incl. its ~3.5px vertical-align overhead), so there's almost
6827
+ // no slack for the chip to drift in → it reads as centered.
6828
+ // A chipless row is shorter than its content box and the `min-h-*`
6829
+ // here is what sets its height: 48px from `md` up, 44px below.
6822
6830
  "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
6831
  !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",
6832
+ !hideBorder && (focused ? "border-ods-accent" : !disabled && "hover:bg-ods-bg-hover hover:border-ods-border-hover"),
6833
+ !hideBorder && disabled && "!cursor-not-allowed bg-ods-bg",
6826
6834
  hideBorder && "bg-transparent"
6827
6835
  ),
6828
6836
  children: [
6829
6837
  startIcon && /* @__PURE__ */ jsx30("span", { className: "flex h-6 shrink-0 items-center", children: startIcon }),
6830
6838
  /* @__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 }),
6839
+ 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 }),
6840
+ 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
6841
  /* @__PURE__ */ jsx30(
6834
6842
  "div",
6835
6843
  {
@@ -6838,7 +6846,7 @@ var ChatInput = forwardRef13((allProps, ref) => {
6838
6846
  role: "textbox",
6839
6847
  "aria-multiline": "true",
6840
6848
  "aria-label": typeof placeholder === "string" ? placeholder : "Message",
6841
- contentEditable: !isDisabled,
6849
+ contentEditable: !disabled,
6842
6850
  suppressContentEditableWarning: true,
6843
6851
  spellCheck: true,
6844
6852
  onInput: handleInput,
@@ -6861,17 +6869,25 @@ var ChatInput = forwardRef13((allProps, ref) => {
6861
6869
  // see the preview comment above) and is only made transparent;
6862
6870
  // the absolutely-positioned single-line preview paints over it.
6863
6871
  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",
6872
+ // WITH chips, `leading-9` (36px) sits just above the 32px chip's
6873
+ // line-box need (~35.5px incl. vertical-align overhead): the LINE
6874
+ // drives row height (deterministic 48px, no overflow) AND the
6875
+ // near-zero slack keeps the chip centered against the `align-top`
6876
+ // `h-9` wrapper `buildChipEl` gives it. ~4px gap between wrapped
6877
+ // chip rows.
6878
+ // WITHOUT chips there is nothing tall to center, and forcing the
6879
+ // same 36px box on 14px phone text (`text-h4`) put a blank line's
6880
+ // worth of air between every wrapped row — so plain drafts keep
6881
+ // `text-h4`'s own responsive line-height (20px / 24px at md).
6882
+ // `min-h-*` pins the single-line box even when EMPTY: a
6883
+ // non-editable (`contentEditable=false`, i.e. `disabled`) empty div
6884
+ // would otherwise collapse to 0px, and `items-center` would
6885
+ // re-center the shorter wrapper in the row — dropping the `top-0`
6886
+ // placeholder down.
6887
+ "text-h4 text-ods-text-primary",
6888
+ hasChips ? "min-h-9 !leading-9" : "min-h-[var(--font-line-space-h4-body)]",
6873
6889
  "scrollbar-thin scrollbar-track-transparent scrollbar-thumb-ods-border/30 hover:scrollbar-thumb-ods-text-secondary/30",
6874
- isDisabled && "cursor-not-allowed"
6890
+ disabled && "cursor-not-allowed"
6875
6891
  )
6876
6892
  }
6877
6893
  )
@@ -23109,9 +23125,8 @@ function useRichMarkdownRuntime() {
23109
23125
  import { useState as useState50, useEffect as useEffect41, useRef as useRef33 } from "react";
23110
23126
 
23111
23127
  // src/utils/social-embed-cache.ts
23112
- var cachingRequests = /* @__PURE__ */ new Map();
23113
23128
  var dataCache = /* @__PURE__ */ new Map();
23114
- var refreshTimestamps = /* @__PURE__ */ new Map();
23129
+ var inflightRequests = /* @__PURE__ */ new Map();
23115
23130
  var SocialEmbedCache = class _SocialEmbedCache {
23116
23131
  static getInstance() {
23117
23132
  if (!_SocialEmbedCache.instance) {
@@ -23119,269 +23134,64 @@ var SocialEmbedCache = class _SocialEmbedCache {
23119
23134
  }
23120
23135
  return _SocialEmbedCache.instance;
23121
23136
  }
23122
- // Check if data exists in memory cache
23123
23137
  getFromMemory(url) {
23124
23138
  return dataCache.get(url) || null;
23125
23139
  }
23126
- // Store data in memory cache
23127
23140
  setInMemory(url, data) {
23128
23141
  dataCache.set(url, data);
23129
23142
  }
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
23143
+ /**
23144
+ * Memory → proxy. Kept the historical name so the embed clients'
23145
+ * call sites are unchanged.
23146
+ */
23197
23147
  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}`);
23148
+ const { url, platform, apiEndpoint, dataValidator, onDataUpdate, onError, onLoading } = options;
23202
23149
  const memoryData = this.getFromMemory(url);
23203
23150
  if (memoryData && dataValidator(memoryData)) {
23204
- console.log(`\u{1F48E} [${platformName} Cache] Memory cache hit for: ${url}`);
23205
23151
  onDataUpdate(memoryData);
23206
23152
  onLoading(false);
23207
23153
  return;
23208
23154
  }
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
23155
  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}`);
23156
+ const data = await this.fetchFromProxy(url, apiEndpoint, dataValidator);
23157
+ if (data) {
23158
+ this.setInMemory(url, data);
23159
+ onDataUpdate(data);
23160
+ onLoading(false);
23161
+ return;
23284
23162
  }
23163
+ onError(`This ${platform === "reddit" ? "Reddit post" : "tweet"} could not be loaded.`);
23285
23164
  } catch (error) {
23286
- console.log(`\u274C [${platformName} Cache] All fetch attempts failed for: ${url}`, error);
23165
+ console.log(`\u274C [${platform} embed] Proxy fetch failed for: ${url}`, error);
23287
23166
  onError(`Unable to load ${platform} content`);
23288
- onLoading(false);
23289
23167
  }
23168
+ onLoading(false);
23290
23169
  }
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);
23170
+ /**
23171
+ * Returns the validated payload, `null` for a structured "content
23172
+ * unavailable" 404, and throws for everything else. Concurrent calls for
23173
+ * the same URL share one request.
23174
+ */
23175
+ fetchFromProxy(url, apiEndpoint, dataValidator) {
23176
+ const inflight = inflightRequests.get(url);
23177
+ if (inflight) return inflight;
23178
+ const request = (async () => {
23179
+ const response = await fetch(`${apiEndpoint}?url=${encodeURIComponent(url)}`, {
23180
+ signal: AbortSignal.timeout(15e3)
23181
+ });
23182
+ if (response.ok) {
23183
+ const data = await response.json();
23184
+ if (data && dataValidator(data)) return data;
23185
+ throw new Error("Server proxy returned invalid data shape");
23346
23186
  }
23187
+ if (response.status === 404) return null;
23188
+ throw new Error(`Server proxy failed: ${response.status}`);
23347
23189
  })();
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);
23190
+ inflightRequests.set(
23191
+ url,
23192
+ request.finally(() => inflightRequests.delete(url))
23193
+ );
23194
+ return inflightRequests.get(url);
23385
23195
  }
23386
23196
  };
23387
23197
  var socialCache = SocialEmbedCache.getInstance();
@@ -25019,13 +24829,15 @@ function buildRichEmbedOverrides({
25019
24829
  }
25020
24830
  return standard.div(divProps);
25021
24831
  },
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.
24832
+ // Pasted-from-the-platform embed markup lives in published blog posts as
24833
+ // `<blockquote class="reddit-embed-bq">…<a href="post-url">` (58 blocks
24834
+ // across 9 posts) and Twitter's equivalent is
24835
+ // `<blockquote class="twitter-tweet">…<a href="status-url">`. Each pair
24836
+ // ships a `widgets.js` loader script, which `processShortcodes` strips
24837
+ // from the source text (Step 1.5). The composition rehydrates the
24838
+ // blockquote itself: extract the post URL from the first matching link
24839
+ // and render the platform's embed-client SSOT. Other blockquotes fall
24840
+ // through to the engine's base blockquote.
25029
24841
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
25030
24842
  blockquote: (bqProps) => {
25031
24843
  const { node, className } = bqProps;
@@ -25034,6 +24846,10 @@ function buildRichEmbedOverrides({
25034
24846
  const postUrl = findFirstHref(node, /reddit\.com/);
25035
24847
  if (postUrl) return /* @__PURE__ */ jsx169(RedditEmbedClient, { url: postUrl });
25036
24848
  }
24849
+ if (classNames.includes("twitter-tweet")) {
24850
+ const tweetUrl = findFirstHref(node, /(?:twitter\.com|x\.com)\/[^/]+\/status\//);
24851
+ if (tweetUrl) return /* @__PURE__ */ jsx169(TwitterEmbedClient, { url: tweetUrl });
24852
+ }
25037
24853
  return standard.blockquote(bqProps);
25038
24854
  },
25039
24855
  // In-article images: MarkdownImage reads `transformImageSrc` from the
@@ -25120,6 +24936,10 @@ var processShortcodes = (content) => {
25120
24936
  codeBlocks.push(match2);
25121
24937
  return placeholder;
25122
24938
  });
24939
+ processedContent = processedContent.replace(
24940
+ /<script\b[^>]{0,2000}>[\s\S]{0,20000}?<\/script\s{0,10}>/gi,
24941
+ ""
24942
+ );
25123
24943
  const markdownLinks = [];
25124
24944
  processedContent = processedContent.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match2) => {
25125
24945
  const placeholder = `__MARKDOWN_LINK_${markdownLinks.length}__`;
@@ -47303,7 +47123,8 @@ function FileUpload({
47303
47123
  return;
47304
47124
  }
47305
47125
  try {
47306
- handleFiles(await pickFiles());
47126
+ const picked = await pickFiles();
47127
+ if (picked.length > 0) handleFiles(picked);
47307
47128
  } catch (err) {
47308
47129
  setValidationError(err instanceof Error ? err.message : "Could not open the file picker");
47309
47130
  }
@@ -62267,4 +62088,4 @@ export {
62267
62088
  LogsList,
62268
62089
  assets
62269
62090
  };
62270
- //# sourceMappingURL=chunk-LIUARTOZ.js.map
62091
+ //# sourceMappingURL=chunk-GBWS62LR.js.map