@postrun/react 0.1.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.cjs ADDED
@@ -0,0 +1,1325 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+ var reactQuery = require('@tanstack/react-query');
6
+ var js = require('@postrun/js');
7
+ var pRetry = require('p-retry');
8
+ var pWaitFor = require('p-wait-for');
9
+ var axios = require('axios');
10
+ var reactTweet = require('react-tweet');
11
+ var fi = require('react-icons/fi');
12
+ var jsxRuntime = require('react/jsx-runtime');
13
+ var twitterText = require('twitter-text');
14
+ var lu = require('react-icons/lu');
15
+
16
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
17
+
18
+ var pRetry__default = /*#__PURE__*/_interopDefault(pRetry);
19
+ var pWaitFor__default = /*#__PURE__*/_interopDefault(pWaitFor);
20
+ var axios__default = /*#__PURE__*/_interopDefault(axios);
21
+ var twitterText__default = /*#__PURE__*/_interopDefault(twitterText);
22
+
23
+ // src/context.tsx
24
+ function createDefaultQueryClient() {
25
+ return new reactQuery.QueryClient({
26
+ defaultOptions: {
27
+ queries: { staleTime: 3e4 }
28
+ }
29
+ });
30
+ }
31
+ var PostrunContext = react.createContext(null);
32
+ PostrunContext.displayName = "PostrunContext";
33
+ function PostrunProvider({
34
+ getToken,
35
+ baseUrl,
36
+ queryClient,
37
+ children
38
+ }) {
39
+ const getTokenRef = react.useRef(getToken);
40
+ react.useEffect(() => {
41
+ getTokenRef.current = getToken;
42
+ }, [getToken]);
43
+ const client = react.useMemo(
44
+ () => js.createPostrunClient({ getToken: () => getTokenRef.current(), baseUrl }),
45
+ [baseUrl]
46
+ );
47
+ const resolvedQueryClient = react.useMemo(
48
+ () => queryClient ?? createDefaultQueryClient(),
49
+ [queryClient]
50
+ );
51
+ const value = react.useMemo(
52
+ () => ({ client, queryClient: resolvedQueryClient }),
53
+ [client, resolvedQueryClient]
54
+ );
55
+ return react.createElement(PostrunContext.Provider, { value }, children);
56
+ }
57
+ function usePostrun() {
58
+ const value = react.useContext(PostrunContext);
59
+ if (value === null) {
60
+ throw new Error("usePostrun must be used within a <PostrunProvider>.");
61
+ }
62
+ return value;
63
+ }
64
+ function useInfiniteList(args) {
65
+ const { queryClient } = usePostrun();
66
+ const limit = args.limit ?? 20;
67
+ const query = reactQuery.useInfiniteQuery(
68
+ {
69
+ queryKey: args.queryKey,
70
+ queryFn: ({ pageParam }) => args.fetchPage({ limit, offset: pageParam }),
71
+ initialPageParam: 0,
72
+ getNextPageParam: (last) => last.has_more ? last.offset + last.data.length : void 0,
73
+ enabled: args.enabled
74
+ },
75
+ queryClient
76
+ );
77
+ return {
78
+ items: query.data?.pages.flatMap((page) => page.data) ?? [],
79
+ total: query.data?.pages[0]?.total ?? 0,
80
+ loadMore: () => {
81
+ void query.fetchNextPage();
82
+ },
83
+ hasMore: query.hasNextPage,
84
+ isLoading: query.isLoading,
85
+ isLoadingMore: query.isFetchingNextPage,
86
+ error: query.error,
87
+ refetch: () => {
88
+ void query.refetch();
89
+ }
90
+ };
91
+ }
92
+
93
+ // src/keys.ts
94
+ var ROOT = "postrun";
95
+ var profileKeys = {
96
+ all: [ROOT, "profiles"],
97
+ lists: () => [...profileKeys.all, "list"],
98
+ list: (query) => [...profileKeys.lists(), query ?? {}],
99
+ // Nested under lists() so a create/update/delete invalidating lists() also
100
+ // refreshes the infinite cache; distinct tail so the two cache shapes (a
101
+ // single Page vs accumulated pages) never collide on one key.
102
+ infinite: (query) => [...profileKeys.lists(), "infinite", query ?? {}],
103
+ details: () => [...profileKeys.all, "detail"],
104
+ detail: (id) => [...profileKeys.details(), id]
105
+ };
106
+ var postKeys = {
107
+ all: [ROOT, "posts"],
108
+ lists: () => [...postKeys.all, "list"],
109
+ list: (query) => [...postKeys.lists(), query ?? {}],
110
+ // Nested under lists() so a create/update/delete invalidating lists() also
111
+ // refreshes the infinite cache; distinct tail so the two cache shapes (a
112
+ // single Page vs accumulated pages) never collide on one key.
113
+ infinite: (query) => [...postKeys.lists(), "infinite", query ?? {}],
114
+ details: () => [...postKeys.all, "detail"],
115
+ detail: (id) => [...postKeys.details(), id]
116
+ };
117
+ var mediaKeys = {
118
+ all: [ROOT, "media"],
119
+ details: () => [...mediaKeys.all, "detail"],
120
+ detail: (id) => [...mediaKeys.details(), id]
121
+ };
122
+ var connectionKeys = {
123
+ all: [ROOT, "connections"],
124
+ lists: () => [...connectionKeys.all, "list"],
125
+ list: (profileId) => [...connectionKeys.lists(), profileId],
126
+ details: () => [...connectionKeys.all, "detail"],
127
+ detail: (id) => [...connectionKeys.details(), id],
128
+ accounts: (id) => [...connectionKeys.all, "accounts", id]
129
+ };
130
+
131
+ // src/profiles.ts
132
+ function useProfiles(query) {
133
+ const { client, queryClient } = usePostrun();
134
+ return reactQuery.useQuery(
135
+ {
136
+ queryKey: profileKeys.list(query),
137
+ queryFn: async () => (await js.profilesList({ client, query })).data
138
+ },
139
+ queryClient
140
+ );
141
+ }
142
+ function useProfilesInfinite(filters, options) {
143
+ const { client } = usePostrun();
144
+ return useInfiniteList({
145
+ queryKey: profileKeys.infinite(filters),
146
+ limit: options?.pageSize,
147
+ fetchPage: async ({ limit, offset }) => (await js.profilesList({ client, query: { ...filters, limit, offset } })).data
148
+ });
149
+ }
150
+ function useProfile(id) {
151
+ const { client, queryClient } = usePostrun();
152
+ return reactQuery.useQuery(
153
+ {
154
+ queryKey: profileKeys.detail(id),
155
+ queryFn: async () => (await js.profilesGet({ client, path: { id } })).data,
156
+ enabled: Boolean(id)
157
+ },
158
+ queryClient
159
+ );
160
+ }
161
+ function useCreateProfile() {
162
+ const { client, queryClient } = usePostrun();
163
+ return reactQuery.useMutation(
164
+ {
165
+ mutationFn: async (body) => (await js.profilesCreate({ client, body })).data,
166
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: profileKeys.lists() })
167
+ },
168
+ queryClient
169
+ );
170
+ }
171
+ function useUpdateProfile() {
172
+ const { client, queryClient } = usePostrun();
173
+ return reactQuery.useMutation(
174
+ {
175
+ mutationFn: async ({ id, ...body }) => (await js.profilesUpdate({ client, path: { id }, body })).data,
176
+ onSuccess: (_result, { id }) => {
177
+ queryClient.invalidateQueries({ queryKey: profileKeys.lists() });
178
+ queryClient.invalidateQueries({ queryKey: profileKeys.detail(id) });
179
+ }
180
+ },
181
+ queryClient
182
+ );
183
+ }
184
+ function useDeleteProfile() {
185
+ const { client, queryClient } = usePostrun();
186
+ return reactQuery.useMutation(
187
+ {
188
+ mutationFn: async (id) => (await js.profilesDelete({ client, path: { id } })).data,
189
+ onSuccess: (_result, id) => {
190
+ queryClient.invalidateQueries({ queryKey: profileKeys.lists() });
191
+ queryClient.removeQueries({ queryKey: profileKeys.detail(id) });
192
+ }
193
+ },
194
+ queryClient
195
+ );
196
+ }
197
+
198
+ // src/navigate.ts
199
+ function navigate(url) {
200
+ window.location.assign(url);
201
+ }
202
+
203
+ // src/connections.ts
204
+ function useConnect() {
205
+ const { client, queryClient } = usePostrun();
206
+ return reactQuery.useMutation(
207
+ {
208
+ mutationFn: async ({ profileId, platform }) => {
209
+ const session = (await js.connectionsConnect({
210
+ client,
211
+ path: { id: profileId },
212
+ body: { platform }
213
+ })).data;
214
+ navigate(session.connect_url);
215
+ return session;
216
+ }
217
+ },
218
+ queryClient
219
+ );
220
+ }
221
+ function useConnections(profileId) {
222
+ const { client, queryClient } = usePostrun();
223
+ return reactQuery.useQuery(
224
+ {
225
+ queryKey: connectionKeys.list(profileId),
226
+ queryFn: async () => (await js.connectionsListByProfile({ client, path: { id: profileId } })).data,
227
+ enabled: Boolean(profileId)
228
+ },
229
+ queryClient
230
+ );
231
+ }
232
+ function useConnection(id) {
233
+ const { client, queryClient } = usePostrun();
234
+ return reactQuery.useQuery(
235
+ {
236
+ queryKey: connectionKeys.detail(id),
237
+ queryFn: async () => (await js.connectionsGet({ client, path: { id } })).data,
238
+ enabled: Boolean(id)
239
+ },
240
+ queryClient
241
+ );
242
+ }
243
+ function useDiscoverableAccounts(id) {
244
+ const { client, queryClient } = usePostrun();
245
+ return reactQuery.useQuery(
246
+ {
247
+ queryKey: connectionKeys.accounts(id),
248
+ queryFn: async () => (await js.connectionsListAccounts({ client, path: { id } })).data,
249
+ enabled: Boolean(id)
250
+ },
251
+ queryClient
252
+ );
253
+ }
254
+ function useSelectAccount() {
255
+ const { client, queryClient } = usePostrun();
256
+ return reactQuery.useMutation(
257
+ {
258
+ mutationFn: async ({ id, ...body }) => (await js.connectionsSelect({ client, path: { id }, body })).data,
259
+ onSuccess: (_result, { id }) => {
260
+ queryClient.invalidateQueries({ queryKey: connectionKeys.lists() });
261
+ queryClient.invalidateQueries({ queryKey: connectionKeys.detail(id) });
262
+ queryClient.invalidateQueries({ queryKey: connectionKeys.accounts(id) });
263
+ }
264
+ },
265
+ queryClient
266
+ );
267
+ }
268
+ function useDisconnect() {
269
+ const { client, queryClient } = usePostrun();
270
+ return reactQuery.useMutation(
271
+ {
272
+ mutationFn: async (id) => (await js.connectionsDelete({ client, path: { id } })).data,
273
+ onSuccess: (_result, id) => {
274
+ queryClient.invalidateQueries({ queryKey: connectionKeys.lists() });
275
+ queryClient.removeQueries({ queryKey: connectionKeys.detail(id) });
276
+ }
277
+ },
278
+ queryClient
279
+ );
280
+ }
281
+ var UploadError = class extends Error {
282
+ status;
283
+ constructor(status, message) {
284
+ super(message);
285
+ this.name = "UploadError";
286
+ this.status = status;
287
+ }
288
+ };
289
+ async function uploadBytes(target, file, options = {}) {
290
+ const { onProgress, signal } = options;
291
+ try {
292
+ await axios__default.default.request({
293
+ method: target.method,
294
+ url: target.url,
295
+ data: file,
296
+ headers: target.headers,
297
+ signal,
298
+ onUploadProgress: (event) => {
299
+ if (event.total) {
300
+ onProgress?.(event.loaded / event.total);
301
+ }
302
+ }
303
+ });
304
+ onProgress?.(1);
305
+ } catch (error) {
306
+ if (signal?.aborted) {
307
+ throw error;
308
+ }
309
+ if (axios.isAxiosError(error)) {
310
+ const status = error.response?.status ?? 0;
311
+ throw new UploadError(
312
+ status,
313
+ status ? `Upload failed (HTTP ${status}).` : "Upload failed: network error."
314
+ );
315
+ }
316
+ throw error;
317
+ }
318
+ }
319
+
320
+ // src/media.ts
321
+ var DOCUMENT_MIME = /^application\/(pdf|msword|vnd\.(openxmlformats-officedocument\.(wordprocessingml\.document|presentationml\.presentation)|ms-powerpoint))$/;
322
+ function inferKind(contentType) {
323
+ if (contentType === "image/gif") return "gif";
324
+ if (contentType.startsWith("image/")) return "image";
325
+ if (contentType.startsWith("video/")) return "video";
326
+ if (DOCUMENT_MIME.test(contentType)) return "document";
327
+ throw new Error(
328
+ `Could not infer media kind from "${contentType}". Pass { kind } explicitly.`
329
+ );
330
+ }
331
+ async function pollUntilSettled(client, id, signal) {
332
+ let latest;
333
+ await pWaitFor__default.default(
334
+ async () => {
335
+ if (signal.aborted) {
336
+ throw new DOMException("Upload aborted", "AbortError");
337
+ }
338
+ latest = (await js.mediaGet({ client, path: { id } })).data;
339
+ return latest.status === "ready" || latest.status === "failed";
340
+ },
341
+ { interval: 1500, timeout: 3e5 }
342
+ );
343
+ if (!latest) {
344
+ throw new Error("Media polling returned no result.");
345
+ }
346
+ return latest;
347
+ }
348
+ function useMediaUpload() {
349
+ const { client, queryClient } = usePostrun();
350
+ const [status, setStatus] = react.useState("idle");
351
+ const [progress, setProgress] = react.useState(0);
352
+ const [media, setMedia] = react.useState(null);
353
+ const [error, setError] = react.useState(null);
354
+ const abortRef = react.useRef(null);
355
+ const upload = react.useCallback(
356
+ async (file, options) => {
357
+ const contentType = options.contentType || file.type;
358
+ if (!contentType) {
359
+ throw new Error(
360
+ "Could not determine the file's content type. Pass { contentType } explicitly."
361
+ );
362
+ }
363
+ const kind = options.kind ?? inferKind(contentType);
364
+ abortRef.current?.abort();
365
+ const controller = new AbortController();
366
+ abortRef.current = controller;
367
+ setStatus("uploading");
368
+ setProgress(0);
369
+ setMedia(null);
370
+ setError(null);
371
+ try {
372
+ const created = (await js.mediaCreate({
373
+ client,
374
+ body: {
375
+ profile_id: options.profileId,
376
+ kind,
377
+ content_type: contentType,
378
+ targets: options.targets,
379
+ raw: options.raw,
380
+ alt_text: options.altText,
381
+ external_id: options.externalId,
382
+ metadata: options.metadata
383
+ }
384
+ })).data;
385
+ if (created.upload) {
386
+ const target = created.upload;
387
+ await pRetry__default.default(
388
+ async () => {
389
+ try {
390
+ await uploadBytes(target, file, {
391
+ onProgress: setProgress,
392
+ signal: controller.signal
393
+ });
394
+ } catch (uploadError) {
395
+ if (uploadError instanceof UploadError && uploadError.status >= 400 && uploadError.status < 500) {
396
+ throw new pRetry.AbortError(uploadError);
397
+ }
398
+ throw uploadError;
399
+ }
400
+ },
401
+ { retries: 3, signal: controller.signal }
402
+ );
403
+ }
404
+ setStatus("processing");
405
+ const settled = await pollUntilSettled(
406
+ client,
407
+ created.id,
408
+ controller.signal
409
+ );
410
+ queryClient.setQueryData(mediaKeys.detail(created.id), settled);
411
+ setMedia(settled);
412
+ setStatus(settled.status === "failed" ? "failed" : "ready");
413
+ return settled;
414
+ } catch (caught) {
415
+ setError(caught);
416
+ setStatus("failed");
417
+ throw caught;
418
+ } finally {
419
+ if (abortRef.current === controller) {
420
+ abortRef.current = null;
421
+ }
422
+ }
423
+ },
424
+ [client, queryClient]
425
+ );
426
+ const cancel = react.useCallback(() => abortRef.current?.abort(), []);
427
+ const reset = react.useCallback(() => {
428
+ setStatus("idle");
429
+ setProgress(0);
430
+ setMedia(null);
431
+ setError(null);
432
+ }, []);
433
+ return { upload, cancel, reset, status, progress, media, error };
434
+ }
435
+ function useMedia(id) {
436
+ const { client, queryClient } = usePostrun();
437
+ return reactQuery.useQuery(
438
+ {
439
+ queryKey: mediaKeys.detail(id),
440
+ queryFn: async () => (await js.mediaGet({ client, path: { id } })).data,
441
+ enabled: Boolean(id),
442
+ refetchInterval: (query) => {
443
+ const current = query.state.data;
444
+ return current?.status === "uploading" || current?.status === "processing" ? 2e3 : false;
445
+ }
446
+ },
447
+ queryClient
448
+ );
449
+ }
450
+ function useUpdateMedia() {
451
+ const { client, queryClient } = usePostrun();
452
+ return reactQuery.useMutation(
453
+ {
454
+ mutationFn: async ({ id, ...body }) => (await js.mediaUpdate({ client, path: { id }, body })).data,
455
+ onSuccess: (result, { id }) => queryClient.setQueryData(mediaKeys.detail(id), result)
456
+ },
457
+ queryClient
458
+ );
459
+ }
460
+ function useDeleteMedia() {
461
+ const { client, queryClient } = usePostrun();
462
+ return reactQuery.useMutation(
463
+ {
464
+ mutationFn: async (id) => (await js.mediaDelete({ client, path: { id } })).data,
465
+ onSuccess: (_result, id) => queryClient.removeQueries({ queryKey: mediaKeys.detail(id) })
466
+ },
467
+ queryClient
468
+ );
469
+ }
470
+ var IN_FLIGHT = /* @__PURE__ */ new Set([
471
+ "scheduled",
472
+ "publishing"
473
+ ]);
474
+ var isLivePostStatus = (status) => IN_FLIGHT.has(status);
475
+ function usePosts(query) {
476
+ const { client, queryClient } = usePostrun();
477
+ return reactQuery.useQuery(
478
+ {
479
+ queryKey: postKeys.list(query),
480
+ queryFn: async () => (await js.postsList({ client, query })).data
481
+ },
482
+ queryClient
483
+ );
484
+ }
485
+ function usePostsInfinite(filters, options) {
486
+ const { client } = usePostrun();
487
+ return useInfiniteList({
488
+ queryKey: postKeys.infinite(filters),
489
+ limit: options?.pageSize,
490
+ fetchPage: async ({ limit, offset }) => (await js.postsList({ client, query: { ...filters, limit, offset } })).data
491
+ });
492
+ }
493
+ function useCalendar(filters, options) {
494
+ const { client, queryClient } = usePostrun();
495
+ return reactQuery.useQuery(
496
+ {
497
+ queryKey: postKeys.list(filters),
498
+ queryFn: async () => (await js.postsList({ client, query: filters })).data,
499
+ refetchInterval: (query) => {
500
+ if (options?.live === false) return false;
501
+ const posts = query.state.data?.data ?? [];
502
+ return posts.some((post) => isLivePostStatus(post.status)) ? 5e3 : false;
503
+ }
504
+ },
505
+ queryClient
506
+ );
507
+ }
508
+ function usePost(id, options) {
509
+ const { client, queryClient } = usePostrun();
510
+ return reactQuery.useQuery(
511
+ {
512
+ queryKey: postKeys.detail(id),
513
+ queryFn: async () => (await js.postsGet({ client, path: { id } })).data,
514
+ enabled: Boolean(id),
515
+ refetchInterval: (query) => {
516
+ if (options?.live === false) return false;
517
+ const status = query.state.data?.status;
518
+ return status && isLivePostStatus(status) ? 2e3 : false;
519
+ }
520
+ },
521
+ queryClient
522
+ );
523
+ }
524
+ function useCreatePost(profileId) {
525
+ const { client, queryClient } = usePostrun();
526
+ const connections = useConnections(profileId);
527
+ const connected = connections.data?.data ?? [];
528
+ const mutation = reactQuery.useMutation(
529
+ {
530
+ mutationFn: async (input) => (await js.postsCreate({
531
+ client,
532
+ body: js.buildCreatePost({ ...input, profileId }, connected)
533
+ })).data,
534
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: postKeys.lists() })
535
+ },
536
+ queryClient
537
+ );
538
+ return {
539
+ create: mutation.mutateAsync,
540
+ isPending: mutation.isPending,
541
+ error: mutation.error,
542
+ data: mutation.data,
543
+ reset: mutation.reset,
544
+ // The profile's connections must load before `create` can resolve a channel;
545
+ // gate on this so a call during loading isn't mislabeled "not connected".
546
+ isReady: connections.isSuccess,
547
+ connectedChannels: connected.map((connection) => connection.platform).filter(js.isPostPlatform)
548
+ };
549
+ }
550
+ function useUpdatePost(postId) {
551
+ const { client, queryClient } = usePostrun();
552
+ return reactQuery.useMutation(
553
+ {
554
+ mutationFn: async (body) => (await js.postsUpdate({ client, path: { id: postId }, body })).data,
555
+ onSuccess: (result) => {
556
+ queryClient.invalidateQueries({ queryKey: postKeys.lists() });
557
+ queryClient.setQueryData(postKeys.detail(postId), result);
558
+ }
559
+ },
560
+ queryClient
561
+ );
562
+ }
563
+ function useDeletePost() {
564
+ const { client, queryClient } = usePostrun();
565
+ return reactQuery.useMutation(
566
+ {
567
+ mutationFn: async (id) => (await js.postsDelete({ client, path: { id } })).data,
568
+ onSuccess: (_result, id) => {
569
+ queryClient.invalidateQueries({ queryKey: postKeys.lists() });
570
+ queryClient.removeQueries({ queryKey: postKeys.detail(id) });
571
+ }
572
+ },
573
+ queryClient
574
+ );
575
+ }
576
+ function fileKey(file) {
577
+ return file ? `${file.name}:${file.size}:${file.lastModified}` : "";
578
+ }
579
+ function mediaSignature(media) {
580
+ return (media ?? []).map((item) => {
581
+ const source = item.url ?? fileKey(item.file);
582
+ const size = `${item.width ?? ""}x${item.height ?? ""}`;
583
+ return `${item.kind}|${source}|${item.posterUrl ?? ""}|${size}|${item.alt ?? ""}`;
584
+ }).join("\xA7");
585
+ }
586
+ function altSignatureOf(media) {
587
+ return (media ?? []).map((m) => m?.alt_text_override ?? "").join("\xA7");
588
+ }
589
+ function useResolvedMedia(media, altFallbacks, altSignature) {
590
+ const signature = mediaSignature(media);
591
+ const [objectUrls, setObjectUrls] = react.useState({});
592
+ react.useEffect(() => {
593
+ const created = [];
594
+ const next = {};
595
+ (media ?? []).forEach((item, index) => {
596
+ if (!item.url && item.file) {
597
+ const url = URL.createObjectURL(item.file);
598
+ created.push(url);
599
+ next[index] = url;
600
+ }
601
+ });
602
+ setObjectUrls(next);
603
+ return () => {
604
+ for (const url of created) {
605
+ URL.revokeObjectURL(url);
606
+ }
607
+ };
608
+ }, [signature]);
609
+ return react.useMemo(
610
+ () => (media ?? []).flatMap((item, index) => {
611
+ const src = item.url ?? objectUrls[index];
612
+ if (!src) {
613
+ return [];
614
+ }
615
+ return [
616
+ {
617
+ kind: item.kind,
618
+ src,
619
+ width: item.width,
620
+ height: item.height,
621
+ alt: item.alt ?? altFallbacks?.[index]?.alt_text_override ?? void 0,
622
+ posterSrc: item.posterUrl
623
+ }
624
+ ];
625
+ }),
626
+ // `signature` + `altSignature` capture the content; `objectUrls` flips once
627
+ // File blobs resolve. Referencing `media`/`altFallbacks` directly is safe.
628
+ // eslint-disable-next-line react-hooks/exhaustive-deps
629
+ [signature, altSignature, objectUrls]
630
+ );
631
+ }
632
+ var ROW = {
633
+ display: "flex",
634
+ justifyContent: "space-between",
635
+ maxWidth: 425,
636
+ marginTop: 12,
637
+ color: "var(--tweet-color-gray-secondary, #536471)"
638
+ };
639
+ var ACTIONS = [
640
+ { label: "Reply", Icon: fi.FiMessageCircle },
641
+ { label: "Repost", Icon: fi.FiRepeat },
642
+ { label: "Like", Icon: fi.FiHeart },
643
+ { label: "Views", Icon: fi.FiBarChart2 },
644
+ { label: "Share", Icon: fi.FiShare }
645
+ ];
646
+ function XPreviewActions() {
647
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: ROW, children: ACTIONS.map(({ label, Icon }) => /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 18, "aria-label": label, role: "img" }, label)) });
648
+ }
649
+ var {
650
+ extractCashtagsWithIndices,
651
+ extractHashtagsWithIndices,
652
+ extractMentionsWithIndices,
653
+ extractUrlsWithIndices,
654
+ modifyIndicesFromUTF16ToUnicode
655
+ } = twitterText__default.default;
656
+ function extractEntities(text) {
657
+ const hashtags = extractHashtagsWithIndices(text);
658
+ const mentions = extractMentionsWithIndices(text);
659
+ const urls = extractUrlsWithIndices(text);
660
+ const cashtags = extractCashtagsWithIndices(text);
661
+ modifyIndicesFromUTF16ToUnicode(text, [
662
+ ...hashtags,
663
+ ...mentions,
664
+ ...urls,
665
+ ...cashtags
666
+ ]);
667
+ return {
668
+ hashtags: hashtags.map((h) => ({ text: h.hashtag, indices: h.indices })),
669
+ user_mentions: mentions.map((m) => ({
670
+ id_str: "",
671
+ name: m.screenName,
672
+ screen_name: m.screenName,
673
+ indices: m.indices
674
+ })),
675
+ urls: urls.map((u) => ({
676
+ display_url: u.url,
677
+ expanded_url: u.url,
678
+ url: u.url,
679
+ indices: u.indices
680
+ })),
681
+ symbols: cashtags.map((c) => ({ text: c.cashtag, indices: c.indices }))
682
+ };
683
+ }
684
+
685
+ // src/preview/x/to-tweet.ts
686
+ var PLACEHOLDER_AVATAR = "data:image/svg+xml;utf8," + encodeURIComponent(
687
+ '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><circle cx="24" cy="24" r="24" fill="#cfd9de"/></svg>'
688
+ );
689
+ var FALLBACK_WIDTH = 1200;
690
+ var FALLBACK_HEIGHT = 800;
691
+ function codepointLength(text) {
692
+ return Array.from(text).length;
693
+ }
694
+ function buildUser(author) {
695
+ const verified = author.verified ?? false;
696
+ return {
697
+ id_str: "",
698
+ name: author.name,
699
+ profile_image_url_https: author.avatarUrl ?? PLACEHOLDER_AVATAR,
700
+ profile_image_shape: "Circle",
701
+ screen_name: author.handle,
702
+ verified: false,
703
+ is_blue_verified: verified
704
+ };
705
+ }
706
+ function mediaBase(src, width, height) {
707
+ const w = width ?? FALLBACK_WIDTH;
708
+ const h = height ?? FALLBACK_HEIGHT;
709
+ const size = { h, w, resize: "fit" };
710
+ return {
711
+ display_url: "",
712
+ expanded_url: "",
713
+ ext_media_availability: { status: "available" },
714
+ ext_media_color: { palette: [] },
715
+ indices: [0, 0],
716
+ media_url_https: src,
717
+ original_info: { height: h, width: w, focus_rects: [] },
718
+ sizes: { large: size, medium: size, small: size, thumb: size },
719
+ url: src
720
+ };
721
+ }
722
+ function buildPhoto(media) {
723
+ return {
724
+ ...mediaBase(media.src, media.width, media.height),
725
+ type: "photo",
726
+ ext_alt_text: media.alt
727
+ };
728
+ }
729
+ function buildVideo(media) {
730
+ const poster = media.posterSrc ?? media.src;
731
+ const videoInfo = {
732
+ aspect_ratio: [media.width ?? 16, media.height ?? 9],
733
+ variants: [{ content_type: "video/mp4", url: media.src }]
734
+ };
735
+ const base = mediaBase(poster, media.width, media.height);
736
+ return media.kind === "gif" ? { ...base, type: "animated_gif", video_info: videoInfo } : { ...base, type: "video", video_info: videoInfo };
737
+ }
738
+ function buildMediaDetails(media) {
739
+ return media.map(
740
+ (item) => item.kind === "image" ? buildPhoto(item) : buildVideo(item)
741
+ );
742
+ }
743
+ function tweetScaffold() {
744
+ return {
745
+ lang: "en",
746
+ created_at: "",
747
+ edit_control: {
748
+ edit_tweet_ids: [],
749
+ editable_until_msecs: "0",
750
+ is_edit_eligible: false,
751
+ edits_remaining: "0"
752
+ },
753
+ isEdited: false,
754
+ isStaleEdit: false,
755
+ favorite_count: 0,
756
+ conversation_count: 0,
757
+ news_action_type: "conversation"
758
+ };
759
+ }
760
+ function buildQuoted(quote, hasQuoteId) {
761
+ if (!quote && !hasQuoteId) {
762
+ return void 0;
763
+ }
764
+ const author = quote?.author ?? { name: "Quoted post", handle: "" };
765
+ const body = quote?.body ?? "";
766
+ const media = quote?.media ?? [];
767
+ return {
768
+ lang: "en",
769
+ created_at: "",
770
+ display_text_range: [0, codepointLength(body)],
771
+ entities: extractEntities(body),
772
+ id_str: "",
773
+ text: body,
774
+ user: buildUser(author),
775
+ edit_control: {
776
+ edit_tweet_ids: [],
777
+ editable_until_msecs: "0",
778
+ is_edit_eligible: false,
779
+ edits_remaining: "0"
780
+ },
781
+ isEdited: false,
782
+ isStaleEdit: false,
783
+ reply_count: 0,
784
+ retweet_count: 0,
785
+ favorite_count: 0,
786
+ self_thread: { id_str: "" },
787
+ ...media.length > 0 ? { mediaDetails: buildMediaDetails(media) } : {}
788
+ };
789
+ }
790
+ function toTweet(input) {
791
+ const { variant, author, media = [], quotedTweet, replyToHandle } = input;
792
+ const text = variant.body ?? "";
793
+ const mediaDetails = buildMediaDetails(media);
794
+ const quoted = buildQuoted(
795
+ quotedTweet,
796
+ variant.settings?.quote_tweet_id !== void 0
797
+ );
798
+ const reply = variant.settings?.reply;
799
+ return {
800
+ __typename: "Tweet",
801
+ ...tweetScaffold(),
802
+ // Codepoint length, not `text.length` (UTF-16) — react-tweet renders the
803
+ // body off `Array.from(text)`, so emoji must not shift the range.
804
+ display_text_range: [0, codepointLength(text)],
805
+ entities: extractEntities(text),
806
+ id_str: "",
807
+ text,
808
+ user: buildUser(author),
809
+ ...mediaDetails.length > 0 ? { mediaDetails } : {},
810
+ ...quoted ? { quoted_tweet: quoted } : {},
811
+ // Both the handle AND the parent id are needed: enrichTweet builds the
812
+ // reply link as `…/${screen_name}/status/${status_id_str}`, so omitting the
813
+ // id yields a `/status/undefined` href.
814
+ ...reply && replyToHandle ? {
815
+ in_reply_to_screen_name: replyToHandle,
816
+ in_reply_to_status_id_str: reply.in_reply_to_tweet_id
817
+ } : {}
818
+ };
819
+ }
820
+ function XPostPreviewImpl({
821
+ variant,
822
+ author,
823
+ media,
824
+ quotedTweet,
825
+ replyToHandle,
826
+ theme = "auto",
827
+ showActions = true,
828
+ className,
829
+ style,
830
+ components
831
+ }) {
832
+ const resolvedMedia = useResolvedMedia(
833
+ media,
834
+ variant.media,
835
+ altSignatureOf(variant.media)
836
+ );
837
+ const resolvedQuotedMedia = useResolvedMedia(quotedTweet?.media, void 0, "");
838
+ const tweet = react.useMemo(() => {
839
+ const resolvedQuoted = quotedTweet ? {
840
+ author: quotedTweet.author,
841
+ body: quotedTweet.body,
842
+ media: resolvedQuotedMedia
843
+ } : void 0;
844
+ return reactTweet.enrichTweet(
845
+ toTweet({
846
+ variant,
847
+ author,
848
+ media: resolvedMedia,
849
+ quotedTweet: resolvedQuoted,
850
+ replyToHandle
851
+ })
852
+ );
853
+ }, [
854
+ variant,
855
+ author,
856
+ resolvedMedia,
857
+ quotedTweet,
858
+ resolvedQuotedMedia,
859
+ replyToHandle
860
+ ]);
861
+ return /* @__PURE__ */ jsxRuntime.jsx(
862
+ "div",
863
+ {
864
+ "data-theme": theme === "auto" ? void 0 : theme,
865
+ className,
866
+ style,
867
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactTweet.TweetContainer, { children: [
868
+ /* @__PURE__ */ jsxRuntime.jsx(reactTweet.TweetHeader, { tweet, components }),
869
+ tweet.in_reply_to_screen_name ? /* @__PURE__ */ jsxRuntime.jsx(reactTweet.TweetInReplyTo, { tweet }) : null,
870
+ /* @__PURE__ */ jsxRuntime.jsx(reactTweet.TweetBody, { tweet }),
871
+ tweet.mediaDetails?.length ? /* @__PURE__ */ jsxRuntime.jsx(reactTweet.TweetMedia, { tweet, components }) : null,
872
+ tweet.quoted_tweet ? /* @__PURE__ */ jsxRuntime.jsx(reactTweet.QuotedTweet, { tweet: tweet.quoted_tweet }) : null,
873
+ showActions ? /* @__PURE__ */ jsxRuntime.jsx(XPreviewActions, {}) : null
874
+ ] })
875
+ }
876
+ );
877
+ }
878
+ var XPostPreview = react.memo(XPostPreviewImpl);
879
+ var LI_VAR = {
880
+ bg: "--pr-li-bg",
881
+ text: "--pr-li-text",
882
+ muted: "--pr-li-muted",
883
+ border: "--pr-li-border",
884
+ accent: "--pr-li-accent"
885
+ };
886
+ var LIGHT = {
887
+ bg: "#ffffff",
888
+ text: "rgba(0,0,0,0.9)",
889
+ muted: "rgba(0,0,0,0.6)",
890
+ border: "rgba(0,0,0,0.08)",
891
+ accent: "rgb(10,102,194)"
892
+ };
893
+ var DARK = {
894
+ bg: "#1b1f23",
895
+ text: "rgba(255,255,255,0.9)",
896
+ muted: "rgba(255,255,255,0.6)",
897
+ border: "rgba(255,255,255,0.15)",
898
+ accent: "rgb(112,181,249)"
899
+ };
900
+ function varRef(name) {
901
+ return `var(${name})`;
902
+ }
903
+ function paletteVars(dark) {
904
+ const p = dark ? DARK : LIGHT;
905
+ return {
906
+ "--pr-li-bg": p.bg,
907
+ "--pr-li-text": p.text,
908
+ "--pr-li-muted": p.muted,
909
+ "--pr-li-border": p.border,
910
+ "--pr-li-accent": p.accent
911
+ };
912
+ }
913
+ function usePrefersDark() {
914
+ const [dark, setDark] = react.useState(false);
915
+ react.useEffect(() => {
916
+ if (typeof window === "undefined" || !window.matchMedia) {
917
+ return;
918
+ }
919
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
920
+ setDark(mq.matches);
921
+ const onChange = (event) => setDark(event.matches);
922
+ mq.addEventListener("change", onChange);
923
+ return () => mq.removeEventListener("change", onChange);
924
+ }, []);
925
+ return dark;
926
+ }
927
+ function useIsDark(theme) {
928
+ const prefersDark = usePrefersDark();
929
+ if (theme === "auto") {
930
+ return prefersDark;
931
+ }
932
+ return theme === "dark";
933
+ }
934
+ var ROW2 = {
935
+ display: "flex",
936
+ justifyContent: "space-around",
937
+ borderTop: `1px solid ${varRef(LI_VAR.border)}`,
938
+ marginTop: 8,
939
+ padding: "4px 8px"
940
+ };
941
+ var ITEM = {
942
+ display: "flex",
943
+ alignItems: "center",
944
+ gap: 6,
945
+ padding: "8px",
946
+ color: varRef(LI_VAR.muted),
947
+ fontSize: 14,
948
+ fontWeight: 600
949
+ };
950
+ var ACTIONS2 = [
951
+ { label: "Like", Icon: fi.FiThumbsUp },
952
+ { label: "Comment", Icon: fi.FiMessageSquare },
953
+ { label: "Repost", Icon: fi.FiRepeat },
954
+ { label: "Send", Icon: fi.FiSend }
955
+ ];
956
+ function EngagementBar() {
957
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: ROW2, children: ACTIONS2.map(({ label, Icon }) => /* @__PURE__ */ jsxRuntime.jsxs("span", { style: ITEM, children: [
958
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 20, "aria-hidden": true }),
959
+ label
960
+ ] }, label)) });
961
+ }
962
+ var PLACEHOLDER_AVATAR2 = "data:image/svg+xml;utf8," + encodeURIComponent(
963
+ '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><circle cx="24" cy="24" r="24" fill="#9aa6b2"/></svg>'
964
+ );
965
+ function Header({ author, visibility, time = "Now" }) {
966
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, padding: "12px 16px 0" }, children: [
967
+ /* @__PURE__ */ jsxRuntime.jsx(
968
+ "img",
969
+ {
970
+ src: author.avatarUrl ?? PLACEHOLDER_AVATAR2,
971
+ alt: "",
972
+ width: 48,
973
+ height: 48,
974
+ style: { width: 48, height: 48, borderRadius: "50%", objectFit: "cover", flex: "0 0 auto" }
975
+ }
976
+ ),
977
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0, display: "flex", flexDirection: "column" }, children: [
978
+ /* @__PURE__ */ jsxRuntime.jsxs(
979
+ "span",
980
+ {
981
+ style: {
982
+ display: "flex",
983
+ alignItems: "center",
984
+ fontSize: 14,
985
+ fontWeight: 600,
986
+ color: varRef(LI_VAR.text),
987
+ lineHeight: 1.3
988
+ },
989
+ children: [
990
+ author.name,
991
+ author.verified ? /* @__PURE__ */ jsxRuntime.jsx(
992
+ lu.LuBadgeCheck,
993
+ {
994
+ size: 16,
995
+ "aria-label": "Verified",
996
+ role: "img",
997
+ style: { color: varRef(LI_VAR.accent), marginLeft: 3, flex: "0 0 auto" }
998
+ }
999
+ ) : null
1000
+ ]
1001
+ }
1002
+ ),
1003
+ author.headline ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: varRef(LI_VAR.muted), lineHeight: 1.3 }, children: author.headline }) : null,
1004
+ /* @__PURE__ */ jsxRuntime.jsxs(
1005
+ "span",
1006
+ {
1007
+ style: {
1008
+ display: "flex",
1009
+ alignItems: "center",
1010
+ gap: 4,
1011
+ fontSize: 12,
1012
+ color: varRef(LI_VAR.muted),
1013
+ lineHeight: 1.3
1014
+ },
1015
+ children: [
1016
+ time,
1017
+ " \u2022",
1018
+ " ",
1019
+ visibility === "PUBLIC" ? /* @__PURE__ */ jsxRuntime.jsx(fi.FiGlobe, { size: 13, "aria-label": "Public", role: "img" }) : /* @__PURE__ */ jsxRuntime.jsx(fi.FiUsers, { size: 13, "aria-label": "Connections", role: "img" })
1020
+ ]
1021
+ }
1022
+ )
1023
+ ] })
1024
+ ] });
1025
+ }
1026
+ var MAX_TILES = 4;
1027
+ var MOSAIC_HEIGHT = 272;
1028
+ var GRID_BASE = {
1029
+ display: "grid",
1030
+ gap: 2,
1031
+ height: MOSAIC_HEIGHT,
1032
+ overflow: "hidden"
1033
+ };
1034
+ function gridStyle(tiles) {
1035
+ if (tiles === 2) {
1036
+ return { ...GRID_BASE, gridTemplateColumns: "1fr 1fr" };
1037
+ }
1038
+ if (tiles === 3) {
1039
+ return {
1040
+ ...GRID_BASE,
1041
+ gridTemplateColumns: "1fr 1fr",
1042
+ gridTemplateRows: "1fr 1fr",
1043
+ gridTemplateAreas: '"a b" "a c"'
1044
+ };
1045
+ }
1046
+ return {
1047
+ ...GRID_BASE,
1048
+ gridTemplateColumns: "1fr 1fr",
1049
+ gridTemplateRows: "1fr 1fr"
1050
+ };
1051
+ }
1052
+ var IMG_STYLE = {
1053
+ width: "100%",
1054
+ height: "100%",
1055
+ objectFit: "cover",
1056
+ display: "block"
1057
+ };
1058
+ var AREAS = ["a", "b", "c"];
1059
+ function Tile({
1060
+ item,
1061
+ area,
1062
+ overlay
1063
+ }) {
1064
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", gridArea: area, overflow: "hidden" }, children: [
1065
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.src, alt: item.alt ?? "", style: IMG_STYLE }),
1066
+ overlay ? /* @__PURE__ */ jsxRuntime.jsx(
1067
+ "div",
1068
+ {
1069
+ "aria-hidden": false,
1070
+ style: {
1071
+ position: "absolute",
1072
+ inset: 0,
1073
+ display: "flex",
1074
+ alignItems: "center",
1075
+ justifyContent: "center",
1076
+ background: "rgba(0,0,0,0.5)",
1077
+ color: "#fff",
1078
+ fontSize: 28,
1079
+ fontWeight: 600
1080
+ },
1081
+ children: `+${overlay}`
1082
+ }
1083
+ ) : null
1084
+ ] });
1085
+ }
1086
+ function ImageMosaic({ media }) {
1087
+ if (media.length === 0) {
1088
+ return null;
1089
+ }
1090
+ if (media.length === 1) {
1091
+ const only = media[0];
1092
+ return /* @__PURE__ */ jsxRuntime.jsx(
1093
+ "img",
1094
+ {
1095
+ src: only.src,
1096
+ alt: only.alt ?? "",
1097
+ style: { width: "100%", height: "auto", display: "block" }
1098
+ }
1099
+ );
1100
+ }
1101
+ const tiles = media.slice(0, MAX_TILES);
1102
+ const hidden = media.length - MAX_TILES;
1103
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: gridStyle(tiles.length), children: tiles.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
1104
+ Tile,
1105
+ {
1106
+ item,
1107
+ area: tiles.length === 3 ? AREAS[index] : void 0,
1108
+ overlay: hidden > 0 && index === MAX_TILES - 1 ? hidden : void 0
1109
+ },
1110
+ item.src + index
1111
+ )) });
1112
+ }
1113
+ function VideoTile({ item }) {
1114
+ return /* @__PURE__ */ jsxRuntime.jsx(
1115
+ "video",
1116
+ {
1117
+ src: item.src,
1118
+ poster: item.posterSrc,
1119
+ controls: true,
1120
+ style: { width: "100%", display: "block", background: "#000" }
1121
+ }
1122
+ );
1123
+ }
1124
+ function Media({ media }) {
1125
+ if (media.length === 0) {
1126
+ return null;
1127
+ }
1128
+ const video = media.find(
1129
+ (item) => item.kind === "video" || item.kind === "gif"
1130
+ );
1131
+ if (video) {
1132
+ return /* @__PURE__ */ jsxRuntime.jsx(VideoTile, { item: video });
1133
+ }
1134
+ const images = media.filter((item) => item.kind === "image");
1135
+ return /* @__PURE__ */ jsxRuntime.jsx(ImageMosaic, { media: images });
1136
+ }
1137
+ var FOLD_CHARS = 200;
1138
+ var BODY_STYLE = {
1139
+ whiteSpace: "pre-wrap",
1140
+ wordBreak: "break-word",
1141
+ fontSize: 14,
1142
+ lineHeight: 1.43
1143
+ };
1144
+ function truncateAtWord(text, max) {
1145
+ if (text.length <= max) {
1146
+ return text;
1147
+ }
1148
+ const slice = text.slice(0, max);
1149
+ const lastSpace = slice.lastIndexOf(" ");
1150
+ return slice.slice(0, lastSpace > 0 ? lastSpace : max);
1151
+ }
1152
+ function escapeRegExp(value) {
1153
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1154
+ }
1155
+ function buildPattern(mentionNames) {
1156
+ const mentionAlt = mentionNames.filter((name) => name.trim().length > 0).map(escapeRegExp).join("|");
1157
+ const parts = [
1158
+ "https?:\\/\\/[^\\s]+",
1159
+ // urls
1160
+ "#[\\p{L}\\p{N}_]+",
1161
+ // hashtags (unicode-aware)
1162
+ ...mentionAlt ? [mentionAlt] : []
1163
+ ];
1164
+ return new RegExp(`(${parts.join("|")})`, "gu");
1165
+ }
1166
+ function linkStyle(color) {
1167
+ return { color, textDecoration: "none", fontWeight: 500 };
1168
+ }
1169
+ function linkify(text, colors, mentionNames) {
1170
+ const pattern = buildPattern(mentionNames);
1171
+ const nodes = [];
1172
+ let lastIndex = 0;
1173
+ let key = 0;
1174
+ for (const match of text.matchAll(pattern)) {
1175
+ const token = match[0];
1176
+ const start = match.index;
1177
+ if (start > lastIndex) {
1178
+ nodes.push(
1179
+ /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: text.slice(lastIndex, start) }, key++)
1180
+ );
1181
+ }
1182
+ if (token.startsWith("http")) {
1183
+ nodes.push(
1184
+ /* @__PURE__ */ jsxRuntime.jsx(
1185
+ "a",
1186
+ {
1187
+ href: token,
1188
+ style: linkStyle(colors.accent),
1189
+ target: "_blank",
1190
+ rel: "noreferrer",
1191
+ children: token
1192
+ },
1193
+ key++
1194
+ )
1195
+ );
1196
+ } else if (token.startsWith("#")) {
1197
+ nodes.push(
1198
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: "#", style: linkStyle(colors.accent), children: token }, key++)
1199
+ );
1200
+ } else {
1201
+ nodes.push(
1202
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: linkStyle(colors.accent), children: token }, key++)
1203
+ );
1204
+ }
1205
+ lastIndex = start + token.length;
1206
+ }
1207
+ if (lastIndex < text.length) {
1208
+ nodes.push(/* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: text.slice(lastIndex) }, key++));
1209
+ }
1210
+ return nodes;
1211
+ }
1212
+ function PostBody({ text, mentionNames = [], colors }) {
1213
+ const [expanded, setExpanded] = react.useState(false);
1214
+ const isLong = text.length > FOLD_CHARS;
1215
+ const shown = isLong && !expanded ? truncateAtWord(text, FOLD_CHARS) : text;
1216
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: BODY_STYLE, children: [
1217
+ linkify(shown, colors, mentionNames),
1218
+ isLong && !expanded ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1219
+ "\u2026",
1220
+ " ",
1221
+ /* @__PURE__ */ jsxRuntime.jsx(
1222
+ "button",
1223
+ {
1224
+ type: "button",
1225
+ onClick: () => setExpanded(true),
1226
+ style: {
1227
+ background: "none",
1228
+ border: "none",
1229
+ padding: 0,
1230
+ cursor: "pointer",
1231
+ color: colors.muted,
1232
+ fontWeight: 600,
1233
+ fontSize: 14
1234
+ },
1235
+ children: "more"
1236
+ }
1237
+ )
1238
+ ] }) : null
1239
+ ] });
1240
+ }
1241
+ var FONT_STACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
1242
+ function LinkedInPostPreviewImpl({
1243
+ variant,
1244
+ author,
1245
+ media,
1246
+ theme = "auto",
1247
+ time,
1248
+ showActions = true,
1249
+ className,
1250
+ style
1251
+ }) {
1252
+ const dark = useIsDark(theme);
1253
+ const resolvedMedia = useResolvedMedia(
1254
+ media,
1255
+ variant.media,
1256
+ altSignatureOf(variant.media)
1257
+ );
1258
+ const visibility = variant.settings?.visibility ?? "PUBLIC";
1259
+ const mentionNames = (variant.settings?.mentions ?? []).map((m) => m.name);
1260
+ const bodyColors = {
1261
+ accent: varRef(LI_VAR.accent),
1262
+ muted: varRef(LI_VAR.muted)
1263
+ };
1264
+ const cardStyle = {
1265
+ ...paletteVars(dark),
1266
+ background: varRef(LI_VAR.bg),
1267
+ color: varRef(LI_VAR.text),
1268
+ border: `1px solid ${varRef(LI_VAR.border)}`,
1269
+ borderRadius: 10,
1270
+ maxWidth: 552,
1271
+ overflow: "hidden",
1272
+ fontFamily: FONT_STACK,
1273
+ ...style
1274
+ };
1275
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: cardStyle, children: [
1276
+ /* @__PURE__ */ jsxRuntime.jsx(Header, { author, visibility, time }),
1277
+ variant.body ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "8px 16px 0" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1278
+ PostBody,
1279
+ {
1280
+ text: variant.body,
1281
+ mentionNames,
1282
+ colors: bodyColors
1283
+ }
1284
+ ) }) : null,
1285
+ resolvedMedia.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 12 }, children: /* @__PURE__ */ jsxRuntime.jsx(Media, { media: resolvedMedia }) }) : null,
1286
+ showActions ? /* @__PURE__ */ jsxRuntime.jsx(EngagementBar, {}) : null
1287
+ ] });
1288
+ }
1289
+ var LinkedInPostPreview = react.memo(LinkedInPostPreviewImpl);
1290
+
1291
+ exports.LinkedInPostPreview = LinkedInPostPreview;
1292
+ exports.PostrunProvider = PostrunProvider;
1293
+ exports.UploadError = UploadError;
1294
+ exports.XPostPreview = XPostPreview;
1295
+ exports.connectionKeys = connectionKeys;
1296
+ exports.mediaKeys = mediaKeys;
1297
+ exports.postKeys = postKeys;
1298
+ exports.profileKeys = profileKeys;
1299
+ exports.useCalendar = useCalendar;
1300
+ exports.useConnect = useConnect;
1301
+ exports.useConnection = useConnection;
1302
+ exports.useConnections = useConnections;
1303
+ exports.useCreatePost = useCreatePost;
1304
+ exports.useCreateProfile = useCreateProfile;
1305
+ exports.useDeleteMedia = useDeleteMedia;
1306
+ exports.useDeletePost = useDeletePost;
1307
+ exports.useDeleteProfile = useDeleteProfile;
1308
+ exports.useDisconnect = useDisconnect;
1309
+ exports.useDiscoverableAccounts = useDiscoverableAccounts;
1310
+ exports.useInfiniteList = useInfiniteList;
1311
+ exports.useMedia = useMedia;
1312
+ exports.useMediaUpload = useMediaUpload;
1313
+ exports.usePost = usePost;
1314
+ exports.usePostrun = usePostrun;
1315
+ exports.usePosts = usePosts;
1316
+ exports.usePostsInfinite = usePostsInfinite;
1317
+ exports.useProfile = useProfile;
1318
+ exports.useProfiles = useProfiles;
1319
+ exports.useProfilesInfinite = useProfilesInfinite;
1320
+ exports.useSelectAccount = useSelectAccount;
1321
+ exports.useUpdateMedia = useUpdateMedia;
1322
+ exports.useUpdatePost = useUpdatePost;
1323
+ exports.useUpdateProfile = useUpdateProfile;
1324
+ //# sourceMappingURL=index.cjs.map
1325
+ //# sourceMappingURL=index.cjs.map