@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.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
|