@postrun/react 2.8.0 → 2.9.0

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/index.d.cts CHANGED
@@ -1631,29 +1631,31 @@ declare function useTikTokCreatorInfo(connectionId: string | null): _tanstack_re
1631
1631
  /** The identity fields our `Connection` stores — the SDK-driven base every
1632
1632
  * preview author derives from (so the field names + nullability match the API). */
1633
1633
  type ConnectionIdentity = Pick<Connection, 'username' | 'avatar_url'>;
1634
- /** The author identity rendered in an X preview header. */
1635
- interface XPreviewAuthor {
1636
- /** Display name, e.g. "Acme Studio". */
1634
+ /**
1635
+ * The author identity rendered in an X preview header. `username` (the `@handle`)
1636
+ * and `avatar_url` are DERIVED from the SDK `Connection`; `name` (the display
1637
+ * name) and `verified` are presentation extras our API doesn't store.
1638
+ */
1639
+ type XPreviewAuthor = ConnectionIdentity & {
1640
+ /** Display name, e.g. "Acme Studio". NOT on Connection — caller-supplied. */
1637
1641
  name: string;
1638
- /** Handle WITHOUT the leading `@`, e.g. "acmestudio". */
1639
- handle: string;
1640
- /** Avatar image URL. Omit for a neutral placeholder. */
1641
- avatarUrl?: string;
1642
- /** Show the verified badge. Default false. */
1642
+ /** Show the verified badge. NOT on Connection — caller-supplied. */
1643
1643
  verified?: boolean;
1644
- }
1645
- /** The author identity rendered in a LinkedIn preview header. LinkedIn shows a
1646
- * name + a one-line headline (role/tagline), not an `@handle`. */
1647
- interface LinkedInPreviewAuthor {
1648
- /** Display name, e.g. "Acme Studio". */
1644
+ };
1645
+ /**
1646
+ * The author identity rendered in a LinkedIn preview header. LinkedIn shows a
1647
+ * display `name` + a one-line `headline`. `username`/`avatar_url` are DERIVED
1648
+ * from the SDK `Connection`; `name`/`headline`/`verified` are presentation extras
1649
+ * our API doesn't store (LinkedIn's `username` is also documented as NULL today).
1650
+ */
1651
+ type LinkedInPreviewAuthor = ConnectionIdentity & {
1652
+ /** Display name, e.g. "Acme Studio". NOT on Connection — caller-supplied. */
1649
1653
  name: string;
1650
- /** One-line headline under the name, e.g. "Founder & CEO at Acme". */
1654
+ /** One-line headline under the name. NOT on Connection — caller-supplied. */
1651
1655
  headline?: string;
1652
- /** Avatar image URL. Omit for a neutral placeholder. */
1653
- avatarUrl?: string;
1654
- /** Show the verified badge. Default false. */
1656
+ /** Show the verified badge. NOT on Connection — caller-supplied. */
1655
1657
  verified?: boolean;
1656
- }
1658
+ };
1657
1659
  /**
1658
1660
  * The author identity rendered in an Instagram preview header. Instagram shows
1659
1661
  * the `username` (no separate display name) + an optional verified badge.
@@ -1744,7 +1746,7 @@ interface XPostPreviewProps {
1744
1746
  * preview. Fully customizable: restyle via `className`/`style` (CSS variables)
1745
1747
  * or swap internals via `components`; theme light/dark/auto.
1746
1748
  */
1747
- declare function XPostPreviewImpl({ variant, author, media, quotedTweet, replyToHandle, theme, showActions, className, style, components, }: XPostPreviewProps): react.JSX.Element;
1749
+ declare function XPostPreviewImpl({ variant, author, media, quotedTweet, replyToHandle, theme, showActions, className, style, components: componentsProp, }: XPostPreviewProps): react.JSX.Element;
1748
1750
  /** Memoized: re-renders only when its props change (the resolved-media hook
1749
1751
  * already absorbs unstable media arrays). */
1750
1752
  declare const XPostPreview: react.MemoExoticComponent<typeof XPostPreviewImpl>;
@@ -1893,20 +1895,23 @@ declare function InstagramPostPreviewImpl({ variant, author, media, theme, class
1893
1895
  /** Memoized: the resolved-media hook absorbs unstable media arrays. */
1894
1896
  declare const InstagramPostPreview: react.MemoExoticComponent<typeof InstagramPostPreviewImpl>;
1895
1897
 
1896
- /**
1897
- * An Instagram Reel preview — the 9:16 vertical card: full-bleed video, the
1898
- * username/verified + caption overlaid bottom-left, an audio row ("♪ <audio>"),
1899
- * and the right action rail (like/comment/share/save/more — no fabricated
1900
- * counts). Always dark, like the real Reels surface.
1901
- */
1902
- declare function ReelPreview({ author, body, media, audioName, className, style, }: {
1898
+ interface ReelPreviewProps {
1903
1899
  author: InstagramPreviewAuthor;
1904
1900
  body: string;
1905
1901
  media: readonly ResolvedMedia[];
1902
+ /** The reel's audio label (`settings.audio_name`); defaults to "Original audio". */
1906
1903
  audioName?: string;
1907
1904
  className?: string;
1908
1905
  style?: CSSProperties;
1909
- }): react.JSX.Element;
1906
+ }
1907
+ /**
1908
+ * An Instagram Reel preview — the 9:16 vertical card: full-bleed video, the
1909
+ * username/verified + caption overlaid bottom-left, an audio row ("♪ <audio>"),
1910
+ * and the right action rail (like/comment/share/save/more — no fabricated
1911
+ * counts). Always dark, like the real Reels surface. The caption uses the shared
1912
+ * `ExpandableText` fold so the full text is readable (no silent truncation).
1913
+ */
1914
+ declare function ReelPreview({ author, body, media, audioName, className, style, }: ReelPreviewProps): react.JSX.Element;
1910
1915
 
1911
1916
  /**
1912
1917
  * Public props for the TikTok post preview — a faithful render of how a post will
@@ -2041,4 +2046,4 @@ declare function Declaration({ brandedContent }: {
2041
2046
  brandedContent: boolean;
2042
2047
  }): react.JSX.Element;
2043
2048
 
2044
- export { ArticleCard, type ArticleCardProps, AudienceSelect, type CalendarFilters, CommercialDisclosure, Connect, type ConnectErrorReason, type ConnectOutcome, type ConnectProps, type ConnectRenderApi, type ConnectState, type ConnectionsFilter, Declaration, type DiscoverableAccount, DocumentCard, type DocumentCardProps, type InfiniteList, InstagramPostPreview, type InstagramPostPreviewProps, type InstagramPreviewAuthor, type InstagramTheme, InteractionToggles, LinkedInPostPreview, type LinkedInPostPreviewProps, type LinkedInPreviewAuthor, type LiveOptions, type MediaUploadItem, type MediaUploadOptions, type MediaUploadStatus, Poll, type PollProps, type PostrunContextValue, PostrunProvider, type PostrunProviderProps, type PreviewMedia, type PreviewMediaKind, ReelPreview, TikTokCaptionField, type TikTokCaptionFieldProps, TikTokPostPreview, type TikTokPostPreviewProps, TikTokPublishPanel, type TikTokPublishPanelProps, type TikTokTheme, UploadError, type UseConnectParams, type UseConnectResult, type UseMediaUploadOptions, type UseMediaUploadResult, XPoll, type XPollProps, XPostPreview, type XPostPreviewProps, type XPreviewAuthor, type XPreviewMedia, type XPreviewQuotedTweet, connectionKeys, mediaKeys, postKeys, profileKeys, tiktokKeys, toPreviewMedia, useCalendar, useConnect, useConnection, useConnections, useCreatePost, useCreateProfile, useDeleteMedia, useDeletePost, useDeleteProfile, useDisconnect, useDiscoverableAccounts, useInfiniteList, useMedia, useMediaInfinite, useMediaList, useMediaUpload, usePost, usePostrun, usePosts, usePostsInfinite, useProfile, useProfiles, useProfilesInfinite, useSelectAccount, useTikTokCreatorInfo, useUpdateMedia, useUpdatePost, useUpdateProfile, useValidatePost };
2049
+ export { ArticleCard, type ArticleCardProps, AudienceSelect, type CalendarFilters, CommercialDisclosure, Connect, type ConnectErrorReason, type ConnectOutcome, type ConnectProps, type ConnectRenderApi, type ConnectState, type ConnectionIdentity, type ConnectionsFilter, Declaration, type DiscoverableAccount, DocumentCard, type DocumentCardProps, type InfiniteList, InstagramPostPreview, type InstagramPostPreviewProps, type InstagramPreviewAuthor, type InstagramTheme, InteractionToggles, LinkedInPostPreview, type LinkedInPostPreviewProps, type LinkedInPreviewAuthor, type LiveOptions, type MediaUploadItem, type MediaUploadOptions, type MediaUploadStatus, Poll, type PollProps, type PostrunContextValue, PostrunProvider, type PostrunProviderProps, type PreviewMedia, type PreviewMediaKind, ReelPreview, type ReelPreviewProps, type ResolvedMedia, TikTokCaptionField, type TikTokCaptionFieldProps, TikTokPostPreview, type TikTokPostPreviewProps, TikTokPublishPanel, type TikTokPublishPanelProps, type TikTokTheme, UploadError, type UseConnectParams, type UseConnectResult, type UseMediaUploadOptions, type UseMediaUploadResult, XPoll, type XPollProps, XPostPreview, type XPostPreviewProps, type XPreviewAuthor, type XPreviewMedia, type XPreviewQuotedTweet, connectionKeys, mediaKeys, postKeys, profileKeys, tiktokKeys, toPreviewMedia, useCalendar, useConnect, useConnection, useConnections, useCreatePost, useCreateProfile, useDeleteMedia, useDeletePost, useDeleteProfile, useDisconnect, useDiscoverableAccounts, useInfiniteList, useMedia, useMediaInfinite, useMediaList, useMediaUpload, usePost, usePostrun, usePosts, usePostsInfinite, useProfile, useProfiles, useProfilesInfinite, useSelectAccount, useTikTokCreatorInfo, useUpdateMedia, useUpdatePost, useUpdateProfile, useValidatePost };
package/dist/index.d.ts CHANGED
@@ -1631,29 +1631,31 @@ declare function useTikTokCreatorInfo(connectionId: string | null): _tanstack_re
1631
1631
  /** The identity fields our `Connection` stores — the SDK-driven base every
1632
1632
  * preview author derives from (so the field names + nullability match the API). */
1633
1633
  type ConnectionIdentity = Pick<Connection, 'username' | 'avatar_url'>;
1634
- /** The author identity rendered in an X preview header. */
1635
- interface XPreviewAuthor {
1636
- /** Display name, e.g. "Acme Studio". */
1634
+ /**
1635
+ * The author identity rendered in an X preview header. `username` (the `@handle`)
1636
+ * and `avatar_url` are DERIVED from the SDK `Connection`; `name` (the display
1637
+ * name) and `verified` are presentation extras our API doesn't store.
1638
+ */
1639
+ type XPreviewAuthor = ConnectionIdentity & {
1640
+ /** Display name, e.g. "Acme Studio". NOT on Connection — caller-supplied. */
1637
1641
  name: string;
1638
- /** Handle WITHOUT the leading `@`, e.g. "acmestudio". */
1639
- handle: string;
1640
- /** Avatar image URL. Omit for a neutral placeholder. */
1641
- avatarUrl?: string;
1642
- /** Show the verified badge. Default false. */
1642
+ /** Show the verified badge. NOT on Connection — caller-supplied. */
1643
1643
  verified?: boolean;
1644
- }
1645
- /** The author identity rendered in a LinkedIn preview header. LinkedIn shows a
1646
- * name + a one-line headline (role/tagline), not an `@handle`. */
1647
- interface LinkedInPreviewAuthor {
1648
- /** Display name, e.g. "Acme Studio". */
1644
+ };
1645
+ /**
1646
+ * The author identity rendered in a LinkedIn preview header. LinkedIn shows a
1647
+ * display `name` + a one-line `headline`. `username`/`avatar_url` are DERIVED
1648
+ * from the SDK `Connection`; `name`/`headline`/`verified` are presentation extras
1649
+ * our API doesn't store (LinkedIn's `username` is also documented as NULL today).
1650
+ */
1651
+ type LinkedInPreviewAuthor = ConnectionIdentity & {
1652
+ /** Display name, e.g. "Acme Studio". NOT on Connection — caller-supplied. */
1649
1653
  name: string;
1650
- /** One-line headline under the name, e.g. "Founder & CEO at Acme". */
1654
+ /** One-line headline under the name. NOT on Connection — caller-supplied. */
1651
1655
  headline?: string;
1652
- /** Avatar image URL. Omit for a neutral placeholder. */
1653
- avatarUrl?: string;
1654
- /** Show the verified badge. Default false. */
1656
+ /** Show the verified badge. NOT on Connection — caller-supplied. */
1655
1657
  verified?: boolean;
1656
- }
1658
+ };
1657
1659
  /**
1658
1660
  * The author identity rendered in an Instagram preview header. Instagram shows
1659
1661
  * the `username` (no separate display name) + an optional verified badge.
@@ -1744,7 +1746,7 @@ interface XPostPreviewProps {
1744
1746
  * preview. Fully customizable: restyle via `className`/`style` (CSS variables)
1745
1747
  * or swap internals via `components`; theme light/dark/auto.
1746
1748
  */
1747
- declare function XPostPreviewImpl({ variant, author, media, quotedTweet, replyToHandle, theme, showActions, className, style, components, }: XPostPreviewProps): react.JSX.Element;
1749
+ declare function XPostPreviewImpl({ variant, author, media, quotedTweet, replyToHandle, theme, showActions, className, style, components: componentsProp, }: XPostPreviewProps): react.JSX.Element;
1748
1750
  /** Memoized: re-renders only when its props change (the resolved-media hook
1749
1751
  * already absorbs unstable media arrays). */
1750
1752
  declare const XPostPreview: react.MemoExoticComponent<typeof XPostPreviewImpl>;
@@ -1893,20 +1895,23 @@ declare function InstagramPostPreviewImpl({ variant, author, media, theme, class
1893
1895
  /** Memoized: the resolved-media hook absorbs unstable media arrays. */
1894
1896
  declare const InstagramPostPreview: react.MemoExoticComponent<typeof InstagramPostPreviewImpl>;
1895
1897
 
1896
- /**
1897
- * An Instagram Reel preview — the 9:16 vertical card: full-bleed video, the
1898
- * username/verified + caption overlaid bottom-left, an audio row ("♪ <audio>"),
1899
- * and the right action rail (like/comment/share/save/more — no fabricated
1900
- * counts). Always dark, like the real Reels surface.
1901
- */
1902
- declare function ReelPreview({ author, body, media, audioName, className, style, }: {
1898
+ interface ReelPreviewProps {
1903
1899
  author: InstagramPreviewAuthor;
1904
1900
  body: string;
1905
1901
  media: readonly ResolvedMedia[];
1902
+ /** The reel's audio label (`settings.audio_name`); defaults to "Original audio". */
1906
1903
  audioName?: string;
1907
1904
  className?: string;
1908
1905
  style?: CSSProperties;
1909
- }): react.JSX.Element;
1906
+ }
1907
+ /**
1908
+ * An Instagram Reel preview — the 9:16 vertical card: full-bleed video, the
1909
+ * username/verified + caption overlaid bottom-left, an audio row ("♪ <audio>"),
1910
+ * and the right action rail (like/comment/share/save/more — no fabricated
1911
+ * counts). Always dark, like the real Reels surface. The caption uses the shared
1912
+ * `ExpandableText` fold so the full text is readable (no silent truncation).
1913
+ */
1914
+ declare function ReelPreview({ author, body, media, audioName, className, style, }: ReelPreviewProps): react.JSX.Element;
1910
1915
 
1911
1916
  /**
1912
1917
  * Public props for the TikTok post preview — a faithful render of how a post will
@@ -2041,4 +2046,4 @@ declare function Declaration({ brandedContent }: {
2041
2046
  brandedContent: boolean;
2042
2047
  }): react.JSX.Element;
2043
2048
 
2044
- export { ArticleCard, type ArticleCardProps, AudienceSelect, type CalendarFilters, CommercialDisclosure, Connect, type ConnectErrorReason, type ConnectOutcome, type ConnectProps, type ConnectRenderApi, type ConnectState, type ConnectionsFilter, Declaration, type DiscoverableAccount, DocumentCard, type DocumentCardProps, type InfiniteList, InstagramPostPreview, type InstagramPostPreviewProps, type InstagramPreviewAuthor, type InstagramTheme, InteractionToggles, LinkedInPostPreview, type LinkedInPostPreviewProps, type LinkedInPreviewAuthor, type LiveOptions, type MediaUploadItem, type MediaUploadOptions, type MediaUploadStatus, Poll, type PollProps, type PostrunContextValue, PostrunProvider, type PostrunProviderProps, type PreviewMedia, type PreviewMediaKind, ReelPreview, TikTokCaptionField, type TikTokCaptionFieldProps, TikTokPostPreview, type TikTokPostPreviewProps, TikTokPublishPanel, type TikTokPublishPanelProps, type TikTokTheme, UploadError, type UseConnectParams, type UseConnectResult, type UseMediaUploadOptions, type UseMediaUploadResult, XPoll, type XPollProps, XPostPreview, type XPostPreviewProps, type XPreviewAuthor, type XPreviewMedia, type XPreviewQuotedTweet, connectionKeys, mediaKeys, postKeys, profileKeys, tiktokKeys, toPreviewMedia, useCalendar, useConnect, useConnection, useConnections, useCreatePost, useCreateProfile, useDeleteMedia, useDeletePost, useDeleteProfile, useDisconnect, useDiscoverableAccounts, useInfiniteList, useMedia, useMediaInfinite, useMediaList, useMediaUpload, usePost, usePostrun, usePosts, usePostsInfinite, useProfile, useProfiles, useProfilesInfinite, useSelectAccount, useTikTokCreatorInfo, useUpdateMedia, useUpdatePost, useUpdateProfile, useValidatePost };
2049
+ export { ArticleCard, type ArticleCardProps, AudienceSelect, type CalendarFilters, CommercialDisclosure, Connect, type ConnectErrorReason, type ConnectOutcome, type ConnectProps, type ConnectRenderApi, type ConnectState, type ConnectionIdentity, type ConnectionsFilter, Declaration, type DiscoverableAccount, DocumentCard, type DocumentCardProps, type InfiniteList, InstagramPostPreview, type InstagramPostPreviewProps, type InstagramPreviewAuthor, type InstagramTheme, InteractionToggles, LinkedInPostPreview, type LinkedInPostPreviewProps, type LinkedInPreviewAuthor, type LiveOptions, type MediaUploadItem, type MediaUploadOptions, type MediaUploadStatus, Poll, type PollProps, type PostrunContextValue, PostrunProvider, type PostrunProviderProps, type PreviewMedia, type PreviewMediaKind, ReelPreview, type ReelPreviewProps, type ResolvedMedia, TikTokCaptionField, type TikTokCaptionFieldProps, TikTokPostPreview, type TikTokPostPreviewProps, TikTokPublishPanel, type TikTokPublishPanelProps, type TikTokTheme, UploadError, type UseConnectParams, type UseConnectResult, type UseMediaUploadOptions, type UseMediaUploadResult, XPoll, type XPollProps, XPostPreview, type XPostPreviewProps, type XPreviewAuthor, type XPreviewMedia, type XPreviewQuotedTweet, connectionKeys, mediaKeys, postKeys, profileKeys, tiktokKeys, toPreviewMedia, useCalendar, useConnect, useConnection, useConnections, useCreatePost, useCreateProfile, useDeleteMedia, useDeletePost, useDeleteProfile, useDisconnect, useDiscoverableAccounts, useInfiniteList, useMedia, useMediaInfinite, useMediaList, useMediaUpload, usePost, usePostrun, usePosts, usePostsInfinite, useProfile, useProfiles, useProfilesInfinite, useSelectAccount, useTikTokCreatorInfo, useUpdateMedia, useUpdatePost, useUpdateProfile, useValidatePost };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { createContext, memo, useMemo, useState, useEffect, Fragment as Fragment$1, useCallback, useRef, useLayoutEffect, createElement, useContext, useId } from 'react';
2
+ import { createContext, memo, useMemo, useState, useEffect, Fragment as Fragment$1, useRef, useLayoutEffect, useCallback, createElement, useContext, useId } from 'react';
3
3
  import { useInfiniteQuery, useQuery, useMutation, QueryClient } from '@tanstack/react-query';
4
4
  import { xPollDurationLabel, linkedinPollDurationLabel, createPostrunClient, profilesList, profilesGet, profilesCreate, profilesUpdate, profilesDelete, connectionsConnect, connectionsListByProfile, connectionsGet, connectionsListAccounts, connectionsSelect, connectionsDelete, mediaGet, mediaList, mediaUpdate, mediaDelete, postsList, postsGet, postsCreate, buildCreatePost, isPostPlatform, failedVariants, isPublished, postsValidate, postsUpdate, postsDelete, tiktokCreatorInfo, audiencePrivacyOptions, audienceUnselected, tiktokPrivacyLabel, parsePrivacyLevel, TIKTOK_AUDIENCE_REQUIRED_HINT, captionMaxFor, commercialLabelNotice, commercialDisclosureIncomplete, setCommercialDisclosure, setBrandKind, TIKTOK_DISCLOSURE_REQUIRED_HINT, TIKTOK_BRANDED_CONTENT_POLICY_URL, TIKTOK_MUSIC_CONFIRMATION_URL, interactionRows, interactionValueKey, toggleInteraction, tiktokOptionsReady, TIKTOK_PROCESSING_NOTICE, brandedContentDeclared, mediaCreate, PostrunError } from '@postrun/js';
5
5
  export { TIKTOK_CAPTION_MAX, captionMaxFor } from '@postrun/js';
@@ -1114,6 +1114,38 @@ var ACTIONS = [
1114
1114
  function XPreviewActions() {
1115
1115
  return /* @__PURE__ */ jsx("div", { style: ROW, children: ACTIONS.map(({ label, Icon: Icon2 }) => /* @__PURE__ */ jsx(Icon2, { size: 18, "aria-label": label, role: "img" }, label)) });
1116
1116
  }
1117
+ function twitterMediaUrl(src) {
1118
+ try {
1119
+ const url = new URL(src);
1120
+ const extension = url.pathname.split(".").pop();
1121
+ if (!extension) {
1122
+ return src;
1123
+ }
1124
+ url.pathname = url.pathname.replace(`.${extension}`, "");
1125
+ url.searchParams.set("format", extension);
1126
+ url.searchParams.set("name", "small");
1127
+ return url.toString();
1128
+ } catch {
1129
+ return src;
1130
+ }
1131
+ }
1132
+ function rawSrcLookup(...lists) {
1133
+ const map = /* @__PURE__ */ new Map();
1134
+ for (const list of lists) {
1135
+ for (const item of list) {
1136
+ const poster = item.posterSrc ?? item.src;
1137
+ map.set(twitterMediaUrl(item.src), item.src);
1138
+ map.set(twitterMediaUrl(poster), poster);
1139
+ }
1140
+ }
1141
+ return map;
1142
+ }
1143
+ function makeRawMediaImg(rawBySrc) {
1144
+ return function RawMediaImg(props) {
1145
+ const raw = typeof props.src === "string" ? rawBySrc.get(props.src) ?? props.src : props.src;
1146
+ return /* @__PURE__ */ jsx("img", { ...props, src: raw });
1147
+ };
1148
+ }
1117
1149
  var {
1118
1150
  extractCashtagsWithIndices,
1119
1151
  extractHashtagsWithIndices,
@@ -1164,9 +1196,12 @@ function buildUser(author) {
1164
1196
  return {
1165
1197
  id_str: "",
1166
1198
  name: author.name,
1167
- profile_image_url_https: author.avatarUrl ?? PLACEHOLDER_AVATAR,
1199
+ profile_image_url_https: author.avatar_url ?? PLACEHOLDER_AVATAR,
1168
1200
  profile_image_shape: "Circle",
1169
- screen_name: author.handle,
1201
+ // `username`/`avatar_url` derive from the SDK Connection and are nullable.
1202
+ screen_name: author.username ?? "",
1203
+ // Legacy (pre-X-era) verification flag — always false for organic posts; the
1204
+ // X Blue checkmark rides on `is_blue_verified`.
1170
1205
  verified: false,
1171
1206
  is_blue_verified: verified
1172
1207
  };
@@ -1229,7 +1264,11 @@ function buildQuoted(quote, hasQuoteId) {
1229
1264
  if (!quote && !hasQuoteId) {
1230
1265
  return void 0;
1231
1266
  }
1232
- const author = quote?.author ?? { name: "Quoted post", handle: "" };
1267
+ const author = quote?.author ?? {
1268
+ name: "Quoted post",
1269
+ username: "",
1270
+ avatar_url: null
1271
+ };
1233
1272
  const body = quote?.body ?? "";
1234
1273
  const media = quote?.media ?? [];
1235
1274
  return {
@@ -1295,7 +1334,7 @@ function XPostPreviewImpl({
1295
1334
  showActions = true,
1296
1335
  className,
1297
1336
  style,
1298
- components
1337
+ components: componentsProp
1299
1338
  }) {
1300
1339
  const resolvedMedia = useResolvedMedia(
1301
1340
  media,
@@ -1303,6 +1342,15 @@ function XPostPreviewImpl({
1303
1342
  altSignatureOf(variant.media)
1304
1343
  );
1305
1344
  const resolvedQuotedMedia = useResolvedMedia(quotedTweet?.media, void 0, "");
1345
+ const components = useMemo(
1346
+ () => ({
1347
+ MediaImg: makeRawMediaImg(
1348
+ rawSrcLookup(resolvedMedia, resolvedQuotedMedia)
1349
+ ),
1350
+ ...componentsProp
1351
+ }),
1352
+ [resolvedMedia, resolvedQuotedMedia, componentsProp]
1353
+ );
1306
1354
  const tweet = useMemo(() => {
1307
1355
  const resolvedQuoted = quotedTweet ? {
1308
1356
  author: quotedTweet.author,
@@ -1357,14 +1405,15 @@ var LI_VAR = {
1357
1405
  function varRef(name) {
1358
1406
  return `var(${name})`;
1359
1407
  }
1408
+ var PALETTE_VARS = {
1409
+ [LI_VAR.bg]: "light-dark(#ffffff, #1b1f23)",
1410
+ [LI_VAR.text]: "light-dark(rgba(0,0,0,0.9), rgba(255,255,255,0.9))",
1411
+ [LI_VAR.muted]: "light-dark(rgba(0,0,0,0.6), rgba(255,255,255,0.6))",
1412
+ [LI_VAR.border]: "light-dark(rgba(0,0,0,0.08), rgba(255,255,255,0.15))",
1413
+ [LI_VAR.accent]: "light-dark(rgb(10,102,194), rgb(112,181,249))"
1414
+ };
1360
1415
  function paletteVars() {
1361
- return {
1362
- [LI_VAR.bg]: "light-dark(#ffffff, #1b1f23)",
1363
- [LI_VAR.text]: "light-dark(rgba(0,0,0,0.9), rgba(255,255,255,0.9))",
1364
- [LI_VAR.muted]: "light-dark(rgba(0,0,0,0.6), rgba(255,255,255,0.6))",
1365
- [LI_VAR.border]: "light-dark(rgba(0,0,0,0.08), rgba(255,255,255,0.15))",
1366
- [LI_VAR.accent]: "light-dark(rgb(10,102,194), rgb(112,181,249))"
1367
- };
1416
+ return PALETTE_VARS;
1368
1417
  }
1369
1418
  function colorSchemeFor(theme) {
1370
1419
  return theme === "auto" ? "light dark" : theme;
@@ -1510,7 +1559,7 @@ function Header({ author, visibility, time = "Now" }) {
1510
1559
  /* @__PURE__ */ jsx(
1511
1560
  "img",
1512
1561
  {
1513
- src: author.avatarUrl ?? PLACEHOLDER_AVATAR2,
1562
+ src: author.avatar_url ?? PLACEHOLDER_AVATAR2,
1514
1563
  alt: "",
1515
1564
  width: 48,
1516
1565
  height: 48,
@@ -1609,7 +1658,6 @@ function Tile({
1609
1658
  overlay ? /* @__PURE__ */ jsx(
1610
1659
  "div",
1611
1660
  {
1612
- "aria-hidden": false,
1613
1661
  style: {
1614
1662
  position: "absolute",
1615
1663
  inset: 0,
@@ -1780,7 +1828,18 @@ function linkify(text, colors, mentionNames) {
1780
1828
  );
1781
1829
  } else if (token.startsWith("#")) {
1782
1830
  nodes.push(
1783
- /* @__PURE__ */ jsx("a", { href: "#", style: linkStyle(colors.accent), children: token }, key++)
1831
+ // Preview-only hashtag: no real destination, so prevent the host SPA from
1832
+ // navigating / scrolling to top on click.
1833
+ /* @__PURE__ */ jsx(
1834
+ "a",
1835
+ {
1836
+ href: "#",
1837
+ style: linkStyle(colors.accent),
1838
+ onClick: (e) => e.preventDefault(),
1839
+ children: token
1840
+ },
1841
+ key++
1842
+ )
1784
1843
  );
1785
1844
  } else {
1786
1845
  nodes.push(
@@ -1798,32 +1857,41 @@ function PostBody({ text, mentionNames = [], colors }) {
1798
1857
  const [expanded, setExpanded] = useState(false);
1799
1858
  const isLong = text.length > FOLD_CHARS;
1800
1859
  const shown = isLong && !expanded ? truncateAtWord(text, FOLD_CHARS) : text;
1860
+ const nodes = useMemo(
1861
+ () => linkify(shown, colors, mentionNames),
1862
+ [shown, colors, mentionNames]
1863
+ );
1864
+ const toggleStyle2 = {
1865
+ background: "none",
1866
+ border: "none",
1867
+ padding: 0,
1868
+ cursor: "pointer",
1869
+ color: colors.muted,
1870
+ fontWeight: 600,
1871
+ fontSize: 14
1872
+ };
1801
1873
  return /* @__PURE__ */ jsxs("div", { style: BODY_STYLE, children: [
1802
- linkify(shown, colors, mentionNames),
1803
- isLong && !expanded ? /* @__PURE__ */ jsxs(Fragment, { children: [
1804
- "\u2026",
1805
- " ",
1874
+ nodes,
1875
+ isLong ? /* @__PURE__ */ jsxs(Fragment, { children: [
1876
+ !expanded ? "\u2026 " : " ",
1806
1877
  /* @__PURE__ */ jsx(
1807
1878
  "button",
1808
1879
  {
1809
1880
  type: "button",
1810
- onClick: () => setExpanded(true),
1811
- style: {
1812
- background: "none",
1813
- border: "none",
1814
- padding: 0,
1815
- cursor: "pointer",
1816
- color: colors.muted,
1817
- fontWeight: 600,
1818
- fontSize: 14
1819
- },
1820
- children: "more"
1881
+ "aria-expanded": expanded,
1882
+ onClick: () => setExpanded((v) => !v),
1883
+ style: toggleStyle2,
1884
+ children: expanded ? "see less" : "more"
1821
1885
  }
1822
1886
  )
1823
1887
  ] }) : null
1824
1888
  ] });
1825
1889
  }
1826
1890
  var FONT_STACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
1891
+ var BODY_COLORS = {
1892
+ accent: varRef(LI_VAR.accent),
1893
+ muted: varRef(LI_VAR.muted)
1894
+ };
1827
1895
  function LinkedInPostPreviewImpl({
1828
1896
  variant,
1829
1897
  author,
@@ -1840,11 +1908,10 @@ function LinkedInPostPreviewImpl({
1840
1908
  altSignatureOf(variant.media)
1841
1909
  );
1842
1910
  const visibility = variant.settings?.visibility ?? "PUBLIC";
1843
- const mentionNames = (variant.settings?.mentions ?? []).map((m) => m.name);
1844
- const bodyColors = {
1845
- accent: varRef(LI_VAR.accent),
1846
- muted: varRef(LI_VAR.muted)
1847
- };
1911
+ const mentionNames = useMemo(
1912
+ () => (variant.settings?.mentions ?? []).map((m) => m.name),
1913
+ [variant.settings?.mentions]
1914
+ );
1848
1915
  const cardStyle5 = {
1849
1916
  ...paletteVars(),
1850
1917
  colorScheme: colorSchemeFor(theme),
@@ -1864,7 +1931,7 @@ function LinkedInPostPreviewImpl({
1864
1931
  {
1865
1932
  text: variant.body,
1866
1933
  mentionNames,
1867
- colors: bodyColors
1934
+ colors: BODY_COLORS
1868
1935
  }
1869
1936
  ) }) : null,
1870
1937
  renderContent(variant, resolvedMedia),
@@ -1900,14 +1967,15 @@ var IG_VAR = {
1900
1967
  function varRef2(name) {
1901
1968
  return `var(${name})`;
1902
1969
  }
1970
+ var PALETTE_VARS2 = {
1971
+ [IG_VAR.bg]: "light-dark(#ffffff, #000000)",
1972
+ [IG_VAR.text]: "light-dark(#000000, #f5f5f5)",
1973
+ [IG_VAR.muted]: "light-dark(#737373, #a8a8a8)",
1974
+ [IG_VAR.border]: "light-dark(#dbdbdb, #262626)",
1975
+ [IG_VAR.accent]: "light-dark(#00376b, #e0f1ff)"
1976
+ };
1903
1977
  function paletteVars2() {
1904
- return {
1905
- [IG_VAR.bg]: "light-dark(#ffffff, #000000)",
1906
- [IG_VAR.text]: "light-dark(#000000, #f5f5f5)",
1907
- [IG_VAR.muted]: "light-dark(#737373, #a8a8a8)",
1908
- [IG_VAR.border]: "light-dark(#dbdbdb, #262626)",
1909
- [IG_VAR.accent]: "light-dark(#00376b, #e0f1ff)"
1910
- };
1978
+ return PALETTE_VARS2;
1911
1979
  }
1912
1980
  function colorSchemeFor2(theme) {
1913
1981
  return theme === "auto" ? "light dark" : theme;
@@ -1948,7 +2016,14 @@ function ExpandableText({
1948
2016
  if (!el || expanded) {
1949
2017
  return;
1950
2018
  }
1951
- setOverflowing(el.scrollHeight > el.clientHeight + 1);
2019
+ const measure = () => setOverflowing(el.scrollHeight > el.clientHeight + 1);
2020
+ measure();
2021
+ if (typeof ResizeObserver === "undefined") {
2022
+ return;
2023
+ }
2024
+ const ro = new ResizeObserver(measure);
2025
+ ro.observe(el);
2026
+ return () => ro.disconnect();
1952
2027
  }, [expanded, children]);
1953
2028
  const textStyle = expanded ? FULL : { ...FULL, ...clampStyle(lines) };
1954
2029
  return /* @__PURE__ */ jsxs("div", { style, children: [
@@ -2002,10 +2077,10 @@ function Caption({
2002
2077
  toggleColor: varRef2(IG_VAR.muted),
2003
2078
  style: wrapStyle3,
2004
2079
  children: [
2005
- /* @__PURE__ */ jsxs("span", { style: { fontWeight: 600 }, children: [
2080
+ username ? /* @__PURE__ */ jsxs("span", { style: { fontWeight: 600 }, children: [
2006
2081
  username,
2007
2082
  " "
2008
- ] }),
2083
+ ] }) : null,
2009
2084
  parts.map(
2010
2085
  (part, i) => IS_TOKEN.test(part) ? /* @__PURE__ */ jsx("span", { style: { color: varRef2(IG_VAR.accent) }, children: part }, i) : /* @__PURE__ */ jsx("span", { children: part }, i)
2011
2086
  )
@@ -2048,7 +2123,7 @@ function Carousel({ media }) {
2048
2123
  };
2049
2124
  }, [emblaApi, sync]);
2050
2125
  return /* @__PURE__ */ jsxs("div", { style: frameStyle, children: [
2051
- /* @__PURE__ */ jsx("div", { ref: emblaRef, style: { position: "absolute", inset: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { style: { display: "flex", height: "100%" }, children: media.map((item, i) => /* @__PURE__ */ jsx("div", { style: slideStyle, children: /* @__PURE__ */ jsx(Tile2, { item }) }, `${item.src}-${i}`)) }) }),
2126
+ /* @__PURE__ */ jsx("div", { ref: emblaRef, style: { position: "absolute", inset: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { style: { display: "flex", height: "100%" }, children: media.map((item) => /* @__PURE__ */ jsx("div", { style: slideStyle, children: /* @__PURE__ */ jsx(Tile2, { item }) }, item.src)) }) }),
2052
2127
  /* @__PURE__ */ jsxs("div", { style: pillStyle, children: [
2053
2128
  selected + 1,
2054
2129
  "/",
@@ -2059,7 +2134,7 @@ function Carousel({ media }) {
2059
2134
  {
2060
2135
  style: { ...dotStyle, opacity: i === selected ? 1 : 0.5 }
2061
2136
  },
2062
- `dot-${item.src}-${i}`
2137
+ `dot-${item.src}`
2063
2138
  )) })
2064
2139
  ] });
2065
2140
  }
@@ -2189,7 +2264,7 @@ function ReelPreview({
2189
2264
  style
2190
2265
  }) {
2191
2266
  const first = media[0];
2192
- const username = author.username ?? "";
2267
+ const username = author.username;
2193
2268
  return /* @__PURE__ */ jsxs("div", { className, style: { ...cardStyle3, ...style }, children: [
2194
2269
  first ? first.kind === "video" ? /* @__PURE__ */ jsx(
2195
2270
  "video",
@@ -2206,10 +2281,18 @@ function ReelPreview({
2206
2281
  /* @__PURE__ */ jsx("div", { style: scrimStyle }),
2207
2282
  /* @__PURE__ */ jsxs("div", { style: infoStyle, children: [
2208
2283
  /* @__PURE__ */ jsxs("div", { style: authorRowStyle, children: [
2209
- /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, fontSize: 14 }, children: username }),
2284
+ username ? /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, fontSize: 14 }, children: username }) : null,
2210
2285
  author.verified ? /* @__PURE__ */ jsx(MdVerified, { size: 13, "aria-label": "Verified", role: "img", style: { color: "#fff" } }) : null
2211
2286
  ] }),
2212
- body ? /* @__PURE__ */ jsx("div", { style: captionStyle, children: body }) : null,
2287
+ body ? /* @__PURE__ */ jsx(
2288
+ ExpandableText,
2289
+ {
2290
+ lines: 2,
2291
+ toggleColor: "rgba(255,255,255,0.7)",
2292
+ style: captionStyle,
2293
+ children: body
2294
+ }
2295
+ ) : null,
2213
2296
  /* @__PURE__ */ jsxs("div", { style: audioStyle, children: [
2214
2297
  /* @__PURE__ */ jsx(MdMusicNote, { size: 14, "aria-hidden": true }),
2215
2298
  /* @__PURE__ */ jsx("span", { children: audioName ?? "Original audio" })
@@ -2265,11 +2348,7 @@ var authorRowStyle = {
2265
2348
  };
2266
2349
  var captionStyle = {
2267
2350
  fontSize: 13,
2268
- lineHeight: 1.35,
2269
- display: "-webkit-box",
2270
- WebkitLineClamp: 2,
2271
- WebkitBoxOrient: "vertical",
2272
- overflow: "hidden"
2351
+ lineHeight: 1.35
2273
2352
  };
2274
2353
  var audioStyle = {
2275
2354
  display: "flex",
@@ -2302,7 +2381,7 @@ function InstagramPostPreviewImpl({
2302
2381
  altSignatureOf(variant.media)
2303
2382
  );
2304
2383
  const settings = variant.settings;
2305
- const isReel = variant.post_type === "reel" || settings?.media_type === "REELS";
2384
+ const isReel = variant.post_type === "reel";
2306
2385
  if (isReel) {
2307
2386
  return /* @__PURE__ */ jsx(
2308
2387
  ReelPreview,
@@ -2333,7 +2412,7 @@ function InstagramPostPreviewImpl({
2333
2412
  /* @__PURE__ */ jsx(Header2, { author, collaborators }),
2334
2413
  /* @__PURE__ */ jsx(FeedMedia, { media: resolvedMedia }),
2335
2414
  /* @__PURE__ */ jsx(Actions, {}),
2336
- variant.body ? /* @__PURE__ */ jsx("div", { style: { paddingBottom: 12 }, children: /* @__PURE__ */ jsx(Caption, { username: author.username ?? "", body: variant.body }) }) : null
2415
+ variant.body ? /* @__PURE__ */ jsx("div", { style: { paddingBottom: 12 }, children: /* @__PURE__ */ jsx(Caption, { username: author.username, body: variant.body }) }) : null
2337
2416
  ] });
2338
2417
  }
2339
2418
  var InstagramPostPreview = memo(InstagramPostPreviewImpl);
@@ -2410,10 +2489,12 @@ function EmptyMedia() {
2410
2489
  }
2411
2490
  function ProcessingMedia() {
2412
2491
  return /* @__PURE__ */ jsxs("div", { style: emptyStyle, children: [
2492
+ /* @__PURE__ */ jsx("style", { children: SHIMMER_KEYFRAME }),
2413
2493
  /* @__PURE__ */ jsx("div", { style: shimmerStyle }),
2414
2494
  /* @__PURE__ */ jsx("span", { style: { position: "relative", zIndex: 1 }, children: "Processing media\u2026" })
2415
2495
  ] });
2416
2496
  }
2497
+ var SHIMMER_KEYFRAME = `@keyframes pr-tt-shimmer{0%{background-position:-200% 0}100%{background-position:200% 0}}`;
2417
2498
  var coverStyle3 = {
2418
2499
  position: "absolute",
2419
2500
  inset: 0,
@@ -2731,14 +2812,15 @@ var TT_VAR = {
2731
2812
  function varRef3(name) {
2732
2813
  return `var(${name})`;
2733
2814
  }
2815
+ var PALETTE_VARS3 = {
2816
+ [TT_VAR.text]: "light-dark(rgba(0,0,0,0.9), rgba(255,255,255,0.92))",
2817
+ [TT_VAR.muted]: "light-dark(rgba(0,0,0,0.55), #8a8a92)",
2818
+ [TT_VAR.border]: "light-dark(rgba(0,0,0,0.12), rgba(255,255,255,0.14))",
2819
+ [TT_VAR.accent]: "#fe2c55",
2820
+ [TT_VAR.switchOff]: "light-dark(rgba(0,0,0,0.18), rgba(255,255,255,0.22))"
2821
+ };
2734
2822
  function paletteVars3() {
2735
- return {
2736
- [TT_VAR.text]: "light-dark(rgba(0,0,0,0.9), rgba(255,255,255,0.92))",
2737
- [TT_VAR.muted]: "light-dark(rgba(0,0,0,0.55), #8a8a92)",
2738
- [TT_VAR.border]: "light-dark(rgba(0,0,0,0.12), rgba(255,255,255,0.14))",
2739
- [TT_VAR.accent]: "#fe2c55",
2740
- [TT_VAR.switchOff]: "light-dark(rgba(0,0,0,0.18), rgba(255,255,255,0.22))"
2741
- };
2823
+ return PALETTE_VARS3;
2742
2824
  }
2743
2825
  function colorSchemeFor3(theme) {
2744
2826
  return theme === "auto" ? "light dark" : theme;
@@ -2754,11 +2836,17 @@ function Row({
2754
2836
  control,
2755
2837
  htmlFor
2756
2838
  }) {
2839
+ const text = /* @__PURE__ */ jsxs(Fragment, { children: [
2840
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 14 }, children: label }),
2841
+ sublabel ? /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: MUTED2 }, children: sublabel }) : null
2842
+ ] });
2843
+ const textStyle = {
2844
+ display: "flex",
2845
+ flexDirection: "column",
2846
+ gap: 2
2847
+ };
2757
2848
  return /* @__PURE__ */ jsxs("div", { style: rowStyle3, children: [
2758
- /* @__PURE__ */ jsxs("label", { htmlFor, style: { display: "flex", flexDirection: "column", gap: 2 }, children: [
2759
- /* @__PURE__ */ jsx("span", { style: { fontSize: 14 }, children: label }),
2760
- sublabel ? /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: MUTED2 }, children: sublabel }) : null
2761
- ] }),
2849
+ htmlFor ? /* @__PURE__ */ jsx("label", { htmlFor, style: textStyle, children: text }) : /* @__PURE__ */ jsx("span", { style: textStyle, children: text }),
2762
2850
  control
2763
2851
  ] });
2764
2852
  }
@@ -2880,7 +2968,6 @@ function AudienceSelect({
2880
2968
  value,
2881
2969
  onChange
2882
2970
  }) {
2883
- const id = useId();
2884
2971
  const options = audiencePrivacyOptions(creatorInfo.privacy_options, value);
2885
2972
  const unselected = audienceUnselected(value);
2886
2973
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
@@ -2888,7 +2975,7 @@ function AudienceSelect({
2888
2975
  /* @__PURE__ */ jsxs(
2889
2976
  "select",
2890
2977
  {
2891
- id,
2978
+ "aria-label": "Who can view this post",
2892
2979
  value: value.privacy_level ?? "",
2893
2980
  onChange: (e) => onChange({
2894
2981
  ...value,