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