tangled
alpha
login
or
join now
whey.party
/
red-dwarf
82
fork
atom
an independent Bluesky client using Constellation, PDS Queries, and other services
reddwarf.app
frontend
spa
bluesky
reddwarf
microcosm
client
app
82
fork
atom
overview
issues
25
pulls
pipelines
routeless lightbox sidebars
rimar1337
4 months ago
55f010da
833207ed
+534
-432
4 changed files
expand all
collapse all
unified
split
src
components
UniversalPostRenderer.tsx
main.tsx
routes
profile.$did
post.$rkey.tsx
styles
app.css
+471
-400
src/components/UniversalPostRenderer.tsx
···
2
2
import { useAtom } from "jotai";
3
3
import * as React from "react";
4
4
import { type SVGProps } from "react";
5
5
+
import { createPortal } from "react-dom";
5
6
7
7
+
import { ProfilePostComponent } from "~/routes/profile.$did/post.$rkey";
6
8
import { likedPostsAtom } from "~/utils/atoms";
7
9
import { useHydratedEmbed } from "~/utils/useHydrated";
8
10
import {
···
28
30
bottomBorder?: boolean;
29
31
feedviewpost?: boolean;
30
32
repostedby?: string;
33
33
+
style?: React.CSSProperties;
34
34
+
ref?: React.Ref<HTMLDivElement>;
35
35
+
dataIndexPropPass?: number;
36
36
+
nopics?: boolean;
31
37
}
32
38
33
39
// export async function cachedGetRecord({
···
132
138
bottomBorder = true,
133
139
feedviewpost = false,
134
140
repostedby,
141
141
+
style,
142
142
+
ref,
143
143
+
dataIndexPropPass,
144
144
+
nopics,
135
145
}: UniversalPostRendererATURILoaderProps) {
136
146
// /*mass comment*/ console.log("atUri", atUri);
137
147
//const { get, set } = usePersistentStore();
···
406
416
bottomBorder={bottomBorder}
407
417
feedviewpost={feedviewpost}
408
418
repostedby={repostedby}
419
419
+
style={style}
420
420
+
ref={ref}
421
421
+
dataIndexPropPass={dataIndexPropPass}
422
422
+
nopics={nopics}
409
423
/>
410
424
);
411
425
}
···
430
444
bottomBorder = true,
431
445
feedviewpost = false,
432
446
repostedby,
447
447
+
style,
448
448
+
ref,
449
449
+
dataIndexPropPass,
450
450
+
nopics,
433
451
}: {
434
452
postRecord: any;
435
453
profileRecord: any;
···
444
462
bottomBorder?: boolean;
445
463
feedviewpost?: boolean;
446
464
repostedby?: string;
465
465
+
style?: React.CSSProperties;
466
466
+
ref?: React.Ref<HTMLDivElement>;
467
467
+
dataIndexPropPass?: number;
468
468
+
nopics?: boolean;
447
469
}) {
448
470
// /*mass comment*/ console.log(`received aturi: ${aturi} of post content: ${postRecord}`);
449
471
const navigate = useNavigate();
···
638
660
//extraOptionalItemInfo={{reply: postRecord?.value?.reply as AppBskyFeedDefs.ReplyRef, post: fakepost}}
639
661
feedviewpostreplyhandle={feedviewpostreplyhandle}
640
662
repostedby={feedviewpostrepostedbyhandle}
663
663
+
style={style}
664
664
+
ref={ref}
665
665
+
dataIndexPropPass={dataIndexPropPass}
666
666
+
nopics={nopics}
641
667
/>
642
668
</>
643
669
);
···
1079
1105
feedviewpostreplyhandle,
1080
1106
depth = 0,
1081
1107
repostedby,
1108
1108
+
style,
1109
1109
+
ref,
1110
1110
+
dataIndexPropPass,
1111
1111
+
nopics,
1082
1112
}: {
1083
1113
post: PostView;
1084
1114
// optional for now because i havent ported every use to this yet
···
1098
1128
feedviewpostreplyhandle?: string;
1099
1129
depth?: number;
1100
1130
repostedby?: string;
1131
1131
+
style?: React.CSSProperties;
1132
1132
+
ref?: React.Ref<HTMLDivElement>;
1133
1133
+
dataIndexPropPass?: number;
1134
1134
+
nopics?: boolean;
1101
1135
}) {
1136
1136
+
const parsed = new AtUri(post.uri);
1102
1137
const navigate = useNavigate();
1103
1138
const [likedPosts, setLikedPosts] = useAtom(likedPostsAtom);
1104
1139
const [hasRetweeted, setHasRetweeted] = useState<boolean>(
···
1171
1206
/* fuck you */
1172
1207
const isMainItem = false;
1173
1208
const setMainItem = (any: any) => {};
1209
1209
+
// eslint-disable-next-line react-hooks/refs
1210
1210
+
console.log("Received ref in UniversalPostRenderer:", ref);
1174
1211
return (
1175
1175
-
<div
1176
1176
-
key={salt + "-" + (post.uri || emergencySalt)}
1177
1177
-
onClick={
1178
1178
-
isMainItem
1179
1179
-
? onPostClick
1180
1180
-
: setMainItem
1181
1181
-
? onPostClick
1182
1182
-
? (e) => {
1183
1183
-
setMainItem({ post: post });
1184
1184
-
onPostClick(e);
1185
1185
-
}
1186
1186
-
: () => {
1187
1187
-
setMainItem({ post: post });
1188
1188
-
}
1189
1189
-
: undefined
1190
1190
-
}
1191
1191
-
style={{
1192
1192
-
//border: "1px solid #e1e8ed",
1193
1193
-
//borderRadius: 12,
1194
1194
-
opacity: "1 !important",
1195
1195
-
background: "transparent",
1196
1196
-
paddingLeft: isQuote ? 12 : 16,
1197
1197
-
paddingRight: isQuote ? 12 : 16,
1198
1198
-
//paddingTop: 16,
1199
1199
-
paddingTop: isRepost ? 10 : isQuote ? 12 : 16,
1200
1200
-
//paddingBottom: bottomReplyLine ? 0 : 16,
1201
1201
-
paddingBottom: 0,
1202
1202
-
fontFamily: "system-ui, sans-serif",
1203
1203
-
//boxShadow: "0 2px 8px rgba(0,0,0,0.04)",
1204
1204
-
position: "relative",
1205
1205
-
// dont cursor: "pointer",
1206
1206
-
borderBottomWidth: bottomBorder ? (isQuote ? 0 : 1) : 0,
1207
1207
-
}}
1208
1208
-
className="border-gray-300 dark:border-gray-600"
1209
1209
-
>
1210
1210
-
{isRepost && (
1211
1211
-
<div
1212
1212
-
style={{
1213
1213
-
marginLeft: 36,
1214
1214
-
display: "flex",
1215
1215
-
borderRadius: 12,
1216
1216
-
paddingBottom: "calc(22px - 1rem)",
1217
1217
-
fontSize: 14,
1218
1218
-
maxHeight: "1rem",
1219
1219
-
justifyContent: "flex-start",
1220
1220
-
//color: theme.textSecondary,
1221
1221
-
gap: 4,
1222
1222
-
alignItems: "center",
1223
1223
-
}}
1224
1224
-
className="text-gray-500 dark:text-gray-400"
1225
1225
-
>
1226
1226
-
<MdiRepost /> Reposted by @{isRepost}{" "}
1227
1227
-
</div>
1228
1228
-
)}
1229
1229
-
{!isQuote && (
1230
1230
-
<div
1231
1231
-
style={{
1232
1232
-
opacity:
1233
1233
-
topReplyLine || isReply /*&& (true || expanded)*/ ? 0.5 : 0,
1234
1234
-
position: "absolute",
1235
1235
-
top: 0,
1236
1236
-
left: 36, // why 36 ???
1237
1237
-
//left: 16 + (42 / 2),
1238
1238
-
width: 2,
1239
1239
-
//height: "100%",
1240
1240
-
height: isRepost ? "calc(16px + 1rem - 6px)" : 16 - 6,
1241
1241
-
// background: theme.textSecondary,
1242
1242
-
//opacity: 0.5,
1243
1243
-
// no flex here
1244
1244
-
}}
1245
1245
-
className="bg-gray-500 dark:bg-gray-400"
1246
1246
-
/>
1247
1247
-
)}
1212
1212
+
<div ref={ref} style={style} data-index={dataIndexPropPass}>
1248
1213
<div
1214
1214
+
//ref={ref}
1215
1215
+
key={salt + "-" + (post.uri || emergencySalt)}
1216
1216
+
onClick={
1217
1217
+
isMainItem
1218
1218
+
? onPostClick
1219
1219
+
: setMainItem
1220
1220
+
? onPostClick
1221
1221
+
? (e) => {
1222
1222
+
setMainItem({ post: post });
1223
1223
+
onPostClick(e);
1224
1224
+
}
1225
1225
+
: () => {
1226
1226
+
setMainItem({ post: post });
1227
1227
+
}
1228
1228
+
: undefined
1229
1229
+
}
1249
1230
style={{
1250
1250
-
position: "absolute",
1251
1251
-
//top: isRepost ? "calc(16px + 1rem)" : 16,
1252
1252
-
//left: 16,
1253
1253
-
zIndex: 1,
1254
1254
-
top: isRepost ? "calc(16px + 1rem)" : isQuote ? 12 : 16,
1255
1255
-
left: isQuote ? 12 : 16,
1231
1231
+
//...style,
1232
1232
+
//border: "1px solid #e1e8ed",
1233
1233
+
//borderRadius: 12,
1234
1234
+
opacity: "1 !important",
1235
1235
+
background: "transparent",
1236
1236
+
paddingLeft: isQuote ? 12 : 16,
1237
1237
+
paddingRight: isQuote ? 12 : 16,
1238
1238
+
//paddingTop: 16,
1239
1239
+
paddingTop: isRepost ? 10 : isQuote ? 12 : 16,
1240
1240
+
//paddingBottom: bottomReplyLine ? 0 : 16,
1241
1241
+
paddingBottom: 0,
1242
1242
+
fontFamily: "system-ui, sans-serif",
1243
1243
+
//boxShadow: "0 2px 8px rgba(0,0,0,0.04)",
1244
1244
+
position: "relative",
1245
1245
+
// dont cursor: "pointer",
1246
1246
+
borderBottomWidth: bottomBorder ? (isQuote ? 0 : 1) : 0,
1256
1247
}}
1257
1257
-
onClick={onProfileClick}
1248
1248
+
className="border-gray-300 dark:border-gray-600"
1258
1249
>
1259
1259
-
<img
1260
1260
-
src={post.author.avatar || defaultpfp}
1261
1261
-
alt="avatar"
1262
1262
-
// transition={{
1263
1263
-
// type: "spring",
1264
1264
-
// stiffness: 260,
1265
1265
-
// damping: 20,
1266
1266
-
// }}
1267
1267
-
style={{
1268
1268
-
borderRadius: "50%",
1269
1269
-
marginRight: 12,
1270
1270
-
objectFit: "cover",
1271
1271
-
//background: theme.border,
1272
1272
-
//border: `1px solid ${theme.border}`,
1273
1273
-
width: isQuote ? 16 : 42,
1274
1274
-
height: isQuote ? 16 : 42,
1275
1275
-
}}
1276
1276
-
className="border border-gray-300 dark:border-gray-600 bg-gray-300 dark:bg-gray-600"
1277
1277
-
/>
1278
1278
-
</div>
1279
1279
-
<div style={{ display: "flex", alignItems: "flex-start", zIndex: 2 }}>
1250
1250
+
{isRepost && (
1251
1251
+
<div
1252
1252
+
style={{
1253
1253
+
marginLeft: 36,
1254
1254
+
display: "flex",
1255
1255
+
borderRadius: 12,
1256
1256
+
paddingBottom: "calc(22px - 1rem)",
1257
1257
+
fontSize: 14,
1258
1258
+
maxHeight: "1rem",
1259
1259
+
justifyContent: "flex-start",
1260
1260
+
//color: theme.textSecondary,
1261
1261
+
gap: 4,
1262
1262
+
alignItems: "center",
1263
1263
+
}}
1264
1264
+
className="text-gray-500 dark:text-gray-400"
1265
1265
+
>
1266
1266
+
<MdiRepost /> Reposted by @{isRepost}{" "}
1267
1267
+
</div>
1268
1268
+
)}
1269
1269
+
{!isQuote && (
1270
1270
+
<div
1271
1271
+
style={{
1272
1272
+
opacity:
1273
1273
+
topReplyLine || isReply /*&& (true || expanded)*/ ? 0.5 : 0,
1274
1274
+
position: "absolute",
1275
1275
+
top: 0,
1276
1276
+
left: 36, // why 36 ???
1277
1277
+
//left: 16 + (42 / 2),
1278
1278
+
width: 2,
1279
1279
+
//height: "100%",
1280
1280
+
height: isRepost ? "calc(16px + 1rem - 6px)" : 16 - 6,
1281
1281
+
// background: theme.textSecondary,
1282
1282
+
//opacity: 0.5,
1283
1283
+
// no flex here
1284
1284
+
}}
1285
1285
+
className="bg-gray-500 dark:bg-gray-400"
1286
1286
+
/>
1287
1287
+
)}
1280
1288
<div
1281
1289
style={{
1282
1282
-
display: "flex",
1283
1283
-
flexDirection: "column",
1284
1284
-
alignSelf: "stretch",
1285
1285
-
alignItems: "center",
1286
1286
-
overflow: "hidden",
1287
1287
-
width: expanded || isQuote ? 0 : "auto",
1288
1288
-
marginRight: expanded || isQuote ? 0 : 12,
1290
1290
+
position: "absolute",
1291
1291
+
//top: isRepost ? "calc(16px + 1rem)" : 16,
1292
1292
+
//left: 16,
1293
1293
+
zIndex: 1,
1294
1294
+
top: isRepost ? "calc(16px + 1rem)" : isQuote ? 12 : 16,
1295
1295
+
left: isQuote ? 12 : 16,
1289
1296
}}
1297
1297
+
onClick={onProfileClick}
1290
1298
>
1291
1291
-
{/* dummy for later use */}
1292
1292
-
<div style={{ width: 42, height: 42 + 8, minHeight: 42 + 8 }} />
1293
1293
-
{/* reply line !!!! bottomReplyLine */}
1294
1294
-
{bottomReplyLine && (
1295
1295
-
<div
1296
1296
-
style={{
1297
1297
-
width: 2,
1298
1298
-
height: "100%",
1299
1299
-
//background: theme.textSecondary,
1300
1300
-
opacity: 0.5,
1301
1301
-
// no flex here
1302
1302
-
//color: "Red",
1303
1303
-
//zIndex: 99
1304
1304
-
}}
1305
1305
-
className="bg-gray-500 dark:bg-gray-400"
1306
1306
-
/>
1307
1307
-
)}
1308
1308
-
{/* <div
1299
1299
+
<img
1300
1300
+
src={post.author.avatar || defaultpfp}
1301
1301
+
alt="avatar"
1302
1302
+
// transition={{
1303
1303
+
// type: "spring",
1304
1304
+
// stiffness: 260,
1305
1305
+
// damping: 20,
1306
1306
+
// }}
1307
1307
+
style={{
1308
1308
+
borderRadius: "50%",
1309
1309
+
marginRight: 12,
1310
1310
+
objectFit: "cover",
1311
1311
+
//background: theme.border,
1312
1312
+
//border: `1px solid ${theme.border}`,
1313
1313
+
width: isQuote ? 16 : 42,
1314
1314
+
height: isQuote ? 16 : 42,
1315
1315
+
}}
1316
1316
+
className="border border-gray-300 dark:border-gray-600 bg-gray-300 dark:bg-gray-600"
1317
1317
+
/>
1318
1318
+
</div>
1319
1319
+
<div style={{ display: "flex", alignItems: "flex-start", zIndex: 2 }}>
1320
1320
+
<div
1321
1321
+
style={{
1322
1322
+
display: "flex",
1323
1323
+
flexDirection: "column",
1324
1324
+
alignSelf: "stretch",
1325
1325
+
alignItems: "center",
1326
1326
+
overflow: "hidden",
1327
1327
+
width: expanded || isQuote ? 0 : "auto",
1328
1328
+
marginRight: expanded || isQuote ? 0 : 12,
1329
1329
+
}}
1330
1330
+
>
1331
1331
+
{/* dummy for later use */}
1332
1332
+
<div style={{ width: 42, height: 42 + 8, minHeight: 42 + 8 }} />
1333
1333
+
{/* reply line !!!! bottomReplyLine */}
1334
1334
+
{bottomReplyLine && (
1335
1335
+
<div
1336
1336
+
style={{
1337
1337
+
width: 2,
1338
1338
+
height: "100%",
1339
1339
+
//background: theme.textSecondary,
1340
1340
+
opacity: 0.5,
1341
1341
+
// no flex here
1342
1342
+
//color: "Red",
1343
1343
+
//zIndex: 99
1344
1344
+
}}
1345
1345
+
className="bg-gray-500 dark:bg-gray-400"
1346
1346
+
/>
1347
1347
+
)}
1348
1348
+
{/* <div
1309
1349
layout
1310
1350
transition={{ duration: 0.2 }}
1311
1351
animate={{ height: expanded ? 0 : '100%' }}
···
1315
1355
// no flex here
1316
1356
}}
1317
1357
/> */}
1318
1318
-
</div>
1319
1319
-
<div style={{ flex: 1, maxWidth: "100%" }}>
1320
1320
-
<div
1321
1321
-
style={{
1322
1322
-
display: "flex",
1323
1323
-
flexDirection: "row",
1324
1324
-
alignItems: "center",
1325
1325
-
flexWrap: "nowrap",
1326
1326
-
maxWidth: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`,
1327
1327
-
width: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`,
1328
1328
-
marginLeft: !expanded ? (isQuote ? 26 : 0) : 54,
1329
1329
-
marginBottom: !expanded ? 4 : 6,
1330
1330
-
}}
1331
1331
-
>
1358
1358
+
</div>
1359
1359
+
<div style={{ flex: 1, maxWidth: "100%" }}>
1332
1360
<div
1333
1361
style={{
1334
1362
display: "flex",
1335
1335
-
//overflow: "hidden", // hey why is overflow hidden unapplied
1336
1336
-
overflow: "hidden",
1337
1337
-
textOverflow: "ellipsis",
1338
1338
-
flexShrink: 1,
1339
1339
-
flexGrow: 1,
1340
1340
-
flexBasis: 0,
1341
1341
-
width: 0,
1342
1342
-
gap: expanded ? 0 : 6,
1343
1343
-
alignItems: expanded ? "flex-start" : "center",
1344
1344
-
flexDirection: expanded ? "column" : "row",
1345
1345
-
height: expanded ? 42 : "1rem",
1363
1363
+
flexDirection: "row",
1364
1364
+
alignItems: "center",
1365
1365
+
flexWrap: "nowrap",
1366
1366
+
maxWidth: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`,
1367
1367
+
width: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`,
1368
1368
+
marginLeft: !expanded ? (isQuote ? 26 : 0) : 54,
1369
1369
+
marginBottom: !expanded ? 4 : 6,
1346
1370
}}
1347
1371
>
1348
1348
-
<span
1372
1372
+
<div
1349
1373
style={{
1350
1374
display: "flex",
1351
1351
-
fontWeight: 700,
1352
1352
-
fontSize: 16,
1375
1375
+
//overflow: "hidden", // hey why is overflow hidden unapplied
1353
1376
overflow: "hidden",
1354
1377
textOverflow: "ellipsis",
1355
1355
-
whiteSpace: "nowrap",
1356
1378
flexShrink: 1,
1357
1357
-
minWidth: 0,
1358
1358
-
gap: 4,
1359
1359
-
alignItems: "center",
1360
1360
-
//color: theme.text,
1379
1379
+
flexGrow: 1,
1380
1380
+
flexBasis: 0,
1381
1381
+
width: 0,
1382
1382
+
gap: expanded ? 0 : 6,
1383
1383
+
alignItems: expanded ? "flex-start" : "center",
1384
1384
+
flexDirection: expanded ? "column" : "row",
1385
1385
+
height: expanded ? 42 : "1rem",
1361
1386
}}
1362
1362
-
className="text-gray-900 dark:text-gray-100"
1363
1387
>
1364
1364
-
{/* verified checkmark */}
1365
1365
-
{post.author.displayName || post.author.handle}{" "}
1366
1366
-
{post.author.verification?.verifiedStatus == "valid" && (
1367
1367
-
<MdiVerified />
1368
1368
-
)}
1369
1369
-
</span>
1388
1388
+
<span
1389
1389
+
style={{
1390
1390
+
display: "flex",
1391
1391
+
fontWeight: 700,
1392
1392
+
fontSize: 16,
1393
1393
+
overflow: "hidden",
1394
1394
+
textOverflow: "ellipsis",
1395
1395
+
whiteSpace: "nowrap",
1396
1396
+
flexShrink: 1,
1397
1397
+
minWidth: 0,
1398
1398
+
gap: 4,
1399
1399
+
alignItems: "center",
1400
1400
+
//color: theme.text,
1401
1401
+
}}
1402
1402
+
className="text-gray-900 dark:text-gray-100"
1403
1403
+
>
1404
1404
+
{/* verified checkmark */}
1405
1405
+
{post.author.displayName || post.author.handle}{" "}
1406
1406
+
{post.author.verification?.verifiedStatus == "valid" && (
1407
1407
+
<MdiVerified />
1408
1408
+
)}
1409
1409
+
</span>
1370
1410
1371
1371
-
<span
1411
1411
+
<span
1412
1412
+
style={{
1413
1413
+
//color: theme.textSecondary,
1414
1414
+
fontSize: 16,
1415
1415
+
overflowX: "hidden",
1416
1416
+
textOverflow: "ellipsis",
1417
1417
+
whiteSpace: "nowrap",
1418
1418
+
flexShrink: 1,
1419
1419
+
flexGrow: 0,
1420
1420
+
minWidth: 0,
1421
1421
+
}}
1422
1422
+
className="text-gray-500 dark:text-gray-400"
1423
1423
+
>
1424
1424
+
@{post.author.handle}
1425
1425
+
</span>
1426
1426
+
</div>
1427
1427
+
<div
1372
1428
style={{
1373
1373
-
//color: theme.textSecondary,
1374
1374
-
fontSize: 16,
1375
1375
-
overflowX: "hidden",
1376
1376
-
textOverflow: "ellipsis",
1377
1377
-
whiteSpace: "nowrap",
1378
1378
-
flexShrink: 1,
1379
1379
-
flexGrow: 0,
1380
1380
-
minWidth: 0,
1429
1429
+
display: "flex",
1430
1430
+
alignItems: "center",
1431
1431
+
height: "1rem",
1381
1432
}}
1382
1382
-
className="text-gray-500 dark:text-gray-400"
1383
1433
>
1384
1384
-
@{post.author.handle}
1385
1385
-
</span>
1434
1434
+
<span
1435
1435
+
style={{
1436
1436
+
//color: theme.textSecondary,
1437
1437
+
fontSize: 16,
1438
1438
+
marginLeft: 8,
1439
1439
+
whiteSpace: "nowrap",
1440
1440
+
flexShrink: 0,
1441
1441
+
maxWidth: "100%",
1442
1442
+
}}
1443
1443
+
className="text-gray-500 dark:text-gray-400"
1444
1444
+
>
1445
1445
+
· {/* time placeholder */}
1446
1446
+
{shortTimeAgo(post.indexedAt)}
1447
1447
+
</span>
1448
1448
+
</div>
1386
1449
</div>
1387
1387
-
<div
1388
1388
-
style={{
1389
1389
-
display: "flex",
1390
1390
-
alignItems: "center",
1391
1391
-
height: "1rem",
1392
1392
-
}}
1393
1393
-
>
1394
1394
-
<span
1450
1450
+
{/* reply indicator */}
1451
1451
+
{!!feedviewpostreplyhandle && (
1452
1452
+
<div
1395
1453
style={{
1454
1454
+
display: "flex",
1455
1455
+
borderRadius: 12,
1456
1456
+
paddingBottom: 2,
1457
1457
+
fontSize: 14,
1458
1458
+
justifyContent: "flex-start",
1396
1459
//color: theme.textSecondary,
1397
1397
-
fontSize: 16,
1398
1398
-
marginLeft: 8,
1399
1399
-
whiteSpace: "nowrap",
1400
1400
-
flexShrink: 0,
1401
1401
-
maxWidth: "100%",
1460
1460
+
gap: 4,
1461
1461
+
alignItems: "center",
1462
1462
+
//marginLeft: 36,
1463
1463
+
height:
1464
1464
+
!(expanded || isQuote) && !!feedviewpostreplyhandle
1465
1465
+
? "1rem"
1466
1466
+
: 0,
1467
1467
+
opacity:
1468
1468
+
!(expanded || isQuote) && !!feedviewpostreplyhandle ? 1 : 0,
1402
1469
}}
1403
1470
className="text-gray-500 dark:text-gray-400"
1404
1471
>
1405
1405
-
· {/* time placeholder */}
1406
1406
-
{shortTimeAgo(post.indexedAt)}
1407
1407
-
</span>
1408
1408
-
</div>
1409
1409
-
</div>
1410
1410
-
{/* reply indicator */}
1411
1411
-
{!!feedviewpostreplyhandle && (
1472
1472
+
<MdiReply /> Reply to @{feedviewpostreplyhandle}
1473
1473
+
</div>
1474
1474
+
)}
1412
1475
<div
1413
1476
style={{
1414
1414
-
display: "flex",
1415
1415
-
borderRadius: 12,
1416
1416
-
paddingBottom: 2,
1417
1417
-
fontSize: 14,
1418
1418
-
justifyContent: "flex-start",
1419
1419
-
//color: theme.textSecondary,
1420
1420
-
gap: 4,
1421
1421
-
alignItems: "center",
1422
1422
-
//marginLeft: 36,
1423
1423
-
height:
1424
1424
-
!(expanded || isQuote) && !!feedviewpostreplyhandle
1425
1425
-
? "1rem"
1426
1426
-
: 0,
1427
1427
-
opacity:
1428
1428
-
!(expanded || isQuote) && !!feedviewpostreplyhandle ? 1 : 0,
1477
1477
+
fontSize: 16,
1478
1478
+
marginBottom: !post.embed /*|| depth > 0*/ ? 0 : 8,
1479
1479
+
whiteSpace: "pre-wrap",
1480
1480
+
textAlign: "left",
1481
1481
+
overflowWrap: "anywhere",
1482
1482
+
wordBreak: "break-word",
1483
1483
+
//color: theme.text,
1429
1484
}}
1430
1430
-
className="text-gray-500 dark:text-gray-400"
1485
1485
+
className="text-gray-900 dark:text-gray-100"
1431
1486
>
1432
1432
-
<MdiReply /> Reply to @{feedviewpostreplyhandle}
1487
1487
+
{renderTextWithFacets({
1488
1488
+
text: (post.record as { text?: string }).text ?? "",
1489
1489
+
facets: (post.record.facets as Facet[]) ?? [],
1490
1490
+
navigate: navigate,
1491
1491
+
})}
1492
1492
+
{}
1433
1493
</div>
1434
1434
-
)}
1435
1435
-
<div
1436
1436
-
style={{
1437
1437
-
fontSize: 16,
1438
1438
-
marginBottom: !post.embed /*|| depth > 0*/ ? 0 : 8,
1439
1439
-
whiteSpace: "pre-wrap",
1440
1440
-
textAlign: "left",
1441
1441
-
overflowWrap: "anywhere",
1442
1442
-
wordBreak: "break-word",
1443
1443
-
//color: theme.text,
1444
1444
-
}}
1445
1445
-
className="text-gray-900 dark:text-gray-100"
1446
1446
-
>
1447
1447
-
{renderTextWithFacets({
1448
1448
-
text: (post.record as { text?: string }).text ?? "",
1449
1449
-
facets: (post.record.facets as Facet[]) ?? [],
1450
1450
-
navigate: navigate,
1451
1451
-
})}
1452
1452
-
{}
1453
1453
-
</div>
1454
1454
-
{post.embed && depth < 1 ? (
1455
1455
-
<PostEmbeds
1456
1456
-
embed={post.embed}
1457
1457
-
//moderation={moderation}
1458
1458
-
viewContext={PostEmbedViewContext.Feed}
1459
1459
-
salt={salt}
1460
1460
-
navigate={navigate}
1461
1461
-
/>
1462
1462
-
) : null}
1463
1463
-
{post.embed && depth > 0 && (
1464
1464
-
/* pretty bad hack imo. its trying to sync up with how the embed shim doesnt
1494
1494
+
{post.embed && depth < 1 ? (
1495
1495
+
<PostEmbeds
1496
1496
+
embed={post.embed}
1497
1497
+
//moderation={moderation}
1498
1498
+
viewContext={PostEmbedViewContext.Feed}
1499
1499
+
salt={salt}
1500
1500
+
navigate={navigate}
1501
1501
+
postid={{ did: post.author.did, rkey: parsed.rkey }}
1502
1502
+
nopics={nopics}
1503
1503
+
/>
1504
1504
+
) : null}
1505
1505
+
{post.embed && depth > 0 && (
1506
1506
+
/* pretty bad hack imo. its trying to sync up with how the embed shim doesnt
1465
1507
hydrate embeds this deep but the connection here is implicit
1466
1508
todo: idk make this a real part of the embed shim so its not implicit */
1467
1467
-
<>
1468
1468
-
<div className="border-gray-300 dark:border-gray-600 p-3 rounded-xl border italic text-gray-400 text-[14px]">
1469
1469
-
(there is an embed here thats too deep to render)
1470
1470
-
</div>
1471
1471
-
</>
1472
1472
-
)}
1473
1473
-
<div style={{ paddingTop: post.embed && depth < 1 ? 4 : 0 }}>
1474
1474
-
<>
1475
1475
-
{expanded && (
1509
1509
+
<>
1510
1510
+
<div className="border-gray-300 dark:border-gray-600 p-3 rounded-xl border italic text-gray-400 text-[14px]">
1511
1511
+
(there is an embed here thats too deep to render)
1512
1512
+
</div>
1513
1513
+
</>
1514
1514
+
)}
1515
1515
+
<div style={{ paddingTop: post.embed && depth < 1 ? 4 : 0 }}>
1516
1516
+
<>
1517
1517
+
{expanded && (
1518
1518
+
<div
1519
1519
+
style={{
1520
1520
+
overflow: "hidden",
1521
1521
+
//color: theme.textSecondary,
1522
1522
+
fontSize: 14,
1523
1523
+
display: "flex",
1524
1524
+
borderBottomStyle: "solid",
1525
1525
+
//borderBottomColor: theme.border,
1526
1526
+
//background: "#f00",
1527
1527
+
// height: "1rem",
1528
1528
+
paddingTop: 4,
1529
1529
+
paddingBottom: 8,
1530
1530
+
borderBottomWidth: 1,
1531
1531
+
marginBottom: 8,
1532
1532
+
}} // important for height animation
1533
1533
+
className="text-gray-500 dark:text-gray-400 border-gray-200 dark:border-gray-700"
1534
1534
+
>
1535
1535
+
{fullDateTimeFormat(post.indexedAt)}
1536
1536
+
</div>
1537
1537
+
)}
1538
1538
+
</>
1539
1539
+
{!isQuote && (
1476
1540
<div
1477
1541
style={{
1478
1478
-
overflow: "hidden",
1479
1479
-
//color: theme.textSecondary,
1480
1480
-
fontSize: 14,
1481
1542
display: "flex",
1482
1482
-
borderBottomStyle: "solid",
1483
1483
-
//borderBottomColor: theme.border,
1484
1484
-
//background: "#f00",
1485
1485
-
// height: "1rem",
1486
1486
-
paddingTop: 4,
1487
1487
-
paddingBottom: 8,
1488
1488
-
borderBottomWidth: 1,
1489
1489
-
marginBottom: 8,
1490
1490
-
}} // important for height animation
1491
1491
-
className="text-gray-500 dark:text-gray-400 border-gray-200 dark:border-gray-700"
1492
1492
-
>
1493
1493
-
{fullDateTimeFormat(post.indexedAt)}
1494
1494
-
</div>
1495
1495
-
)}
1496
1496
-
</>
1497
1497
-
{!isQuote && (
1498
1498
-
<div
1499
1499
-
style={{
1500
1500
-
display: "flex",
1501
1501
-
gap: 32,
1502
1502
-
paddingTop: 8,
1503
1503
-
//color: theme.textSecondary,
1504
1504
-
fontSize: 15,
1505
1505
-
justifyContent: "space-between",
1506
1506
-
//background: "#0f0",
1507
1507
-
}}
1508
1508
-
className="text-gray-500 dark:text-gray-400"
1509
1509
-
>
1510
1510
-
<span style={btnstyle}>
1511
1511
-
<MdiCommentOutline />
1512
1512
-
{post.replyCount}
1513
1513
-
</span>
1514
1514
-
<HitSlopButton
1515
1515
-
onClick={() => {
1516
1516
-
repostOrUnrepostPost();
1543
1543
+
gap: 32,
1544
1544
+
paddingTop: 8,
1545
1545
+
//color: theme.textSecondary,
1546
1546
+
fontSize: 15,
1547
1547
+
justifyContent: "space-between",
1548
1548
+
//background: "#0f0",
1517
1549
}}
1518
1518
-
style={{
1519
1519
-
...btnstyle,
1520
1520
-
...(hasRetweeted ? { color: "#5CEFAA" } : {}),
1521
1521
-
}}
1522
1522
-
>
1523
1523
-
{hasRetweeted ? <MdiRepeatGreen /> : <MdiRepeat />}
1524
1524
-
{(post.repostCount || 0) + (hasRetweeted ? 1 : 0)}
1525
1525
-
</HitSlopButton>
1526
1526
-
<HitSlopButton
1527
1527
-
onClick={() => {
1528
1528
-
likeOrUnlikePost();
1529
1529
-
}}
1530
1530
-
style={{
1531
1531
-
...btnstyle,
1532
1532
-
...(hasLiked ? { color: "#EC4899" } : {}),
1533
1533
-
}}
1550
1550
+
className="text-gray-500 dark:text-gray-400"
1534
1551
>
1535
1535
-
{hasLiked ? <MdiCardsHeart /> : <MdiCardsHeartOutline />}
1536
1536
-
{(post.likeCount || 0) + (hasLiked ? 1 : 0)}
1537
1537
-
</HitSlopButton>
1538
1538
-
<div style={{ display: "flex", gap: 8 }}>
1552
1552
+
<span style={btnstyle}>
1553
1553
+
<MdiCommentOutline />
1554
1554
+
{post.replyCount}
1555
1555
+
</span>
1556
1556
+
<HitSlopButton
1557
1557
+
onClick={() => {
1558
1558
+
repostOrUnrepostPost();
1559
1559
+
}}
1560
1560
+
style={{
1561
1561
+
...btnstyle,
1562
1562
+
...(hasRetweeted ? { color: "#5CEFAA" } : {}),
1563
1563
+
}}
1564
1564
+
>
1565
1565
+
{hasRetweeted ? <MdiRepeatGreen /> : <MdiRepeat />}
1566
1566
+
{(post.repostCount || 0) + (hasRetweeted ? 1 : 0)}
1567
1567
+
</HitSlopButton>
1539
1568
<HitSlopButton
1540
1540
-
onClick={async (e) => {
1541
1541
-
e.stopPropagation();
1542
1542
-
try {
1543
1543
-
await navigator.clipboard.writeText(
1544
1544
-
"https://bsky.app" +
1545
1545
-
"/profile/" +
1546
1546
-
post.author.handle +
1547
1547
-
"/post/" +
1548
1548
-
post.uri.split("/").pop()
1549
1549
-
);
1550
1550
-
} catch (_e) {
1551
1551
-
// idk
1552
1552
-
}
1569
1569
+
onClick={() => {
1570
1570
+
likeOrUnlikePost();
1553
1571
}}
1554
1572
style={{
1555
1573
...btnstyle,
1574
1574
+
...(hasLiked ? { color: "#EC4899" } : {}),
1556
1575
}}
1557
1576
>
1558
1558
-
<MdiShareVariant />
1577
1577
+
{hasLiked ? <MdiCardsHeart /> : <MdiCardsHeartOutline />}
1578
1578
+
{(post.likeCount || 0) + (hasLiked ? 1 : 0)}
1559
1579
</HitSlopButton>
1560
1560
-
<span style={btnstyle}>
1561
1561
-
<MdiMoreHoriz />
1562
1562
-
</span>
1580
1580
+
<div style={{ display: "flex", gap: 8 }}>
1581
1581
+
<HitSlopButton
1582
1582
+
onClick={async (e) => {
1583
1583
+
e.stopPropagation();
1584
1584
+
try {
1585
1585
+
await navigator.clipboard.writeText(
1586
1586
+
"https://bsky.app" +
1587
1587
+
"/profile/" +
1588
1588
+
post.author.handle +
1589
1589
+
"/post/" +
1590
1590
+
post.uri.split("/").pop()
1591
1591
+
);
1592
1592
+
} catch (_e) {
1593
1593
+
// idk
1594
1594
+
}
1595
1595
+
}}
1596
1596
+
style={{
1597
1597
+
...btnstyle,
1598
1598
+
}}
1599
1599
+
>
1600
1600
+
<MdiShareVariant />
1601
1601
+
</HitSlopButton>
1602
1602
+
<span style={btnstyle}>
1603
1603
+
<MdiMoreHoriz />
1604
1604
+
</span>
1605
1605
+
</div>
1563
1606
</div>
1564
1564
-
</div>
1565
1565
-
)}
1607
1607
+
)}
1608
1608
+
</div>
1609
1609
+
<div
1610
1610
+
style={{
1611
1611
+
//height: bottomReplyLine ? 16 : 0
1612
1612
+
height: isQuote ? 12 : 16,
1613
1613
+
}}
1614
1614
+
/>
1566
1615
</div>
1567
1567
-
<div
1568
1568
-
style={{
1569
1569
-
//height: bottomReplyLine ? 16 : 0
1570
1570
-
height: isQuote ? 12 : 16,
1571
1571
-
}}
1572
1572
-
/>
1573
1616
</div>
1574
1617
</div>
1575
1618
</div>
···
1661
1704
viewContext,
1662
1705
salt,
1663
1706
navigate,
1707
1707
+
postid,
1708
1708
+
nopics,
1664
1709
}: {
1665
1710
embed?: Embed;
1666
1711
moderation?: ModerationDecision;
···
1669
1714
viewContext?: PostEmbedViewContext;
1670
1715
salt: string;
1671
1716
navigate: (_: any) => void;
1717
1717
+
postid?: { did: string; rkey: string };
1718
1718
+
nopics?: boolean;
1672
1719
}) {
1673
1720
const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
1674
1721
if (
···
1704
1751
viewContext={viewContext}
1705
1752
salt={salt}
1706
1753
navigate={navigate}
1754
1754
+
postid={postid}
1755
1755
+
nopics={nopics}
1707
1756
/>
1708
1757
{/* padding empty div of 8px height */}
1709
1758
<div style={{ height: 12 }} />
···
1871
1920
1872
1921
// image embed
1873
1922
// =
1874
1874
-
if (AppBskyEmbedImages.isView(embed)) {
1923
1923
+
if (AppBskyEmbedImages.isView(embed) && !nopics) {
1875
1924
const { images } = embed;
1876
1925
1877
1926
const lightboxImages = images.map((img) => ({
···
1915
1964
index={lightboxIndex}
1916
1965
onClose={() => setLightboxIndex(null)}
1917
1966
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
1967
1967
+
post={postid}
1918
1968
/>
1919
1969
)}
1920
1970
<img
···
1955
2005
index={lightboxIndex}
1956
2006
onClose={() => setLightboxIndex(null)}
1957
2007
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2008
2008
+
post={postid}
1958
2009
/>
1959
2010
)}
1960
2011
{images.map((img, i) => (
···
2004
2055
index={lightboxIndex}
2005
2056
onClose={() => setLightboxIndex(null)}
2006
2057
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2058
2058
+
post={postid}
2007
2059
/>
2008
2060
)}
2009
2061
{/* Left: 1:1 */}
···
2088
2140
index={lightboxIndex}
2089
2141
onClose={() => setLightboxIndex(null)}
2090
2142
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2143
2143
+
post={postid}
2091
2144
/>
2092
2145
)}
2093
2146
{images.map((img, i) => (
···
2180
2233
return <div />;
2181
2234
}
2182
2235
2183
2183
-
import { createPortal } from "react-dom";
2184
2236
type LightboxProps = {
2185
2237
images: { src: string; alt?: string }[];
2186
2238
index: number;
2187
2239
onClose: () => void;
2188
2240
onNavigate?: (newIndex: number) => void;
2241
2241
+
post?: { did: string; rkey: string };
2189
2242
};
2190
2243
export function Lightbox({
2191
2244
images,
2192
2245
index,
2193
2246
onClose,
2194
2247
onNavigate,
2248
2248
+
post,
2195
2249
}: LightboxProps) {
2196
2250
const image = images[index];
2197
2251
···
2208
2262
}, [index, images.length, onClose, onNavigate]);
2209
2263
2210
2264
return createPortal(
2211
2211
-
<div
2212
2212
-
className="fixed inset-0 z-50 flex items-center justify-center bg-black/80"
2213
2213
-
onClick={(e) => {
2214
2214
-
e.stopPropagation();
2215
2215
-
onClose();
2216
2216
-
}}
2217
2217
-
>
2218
2218
-
<img
2219
2219
-
src={image.src}
2220
2220
-
alt={image.alt}
2221
2221
-
className="max-h-[90vh] max-w-[90vw] object-contain rounded-lg shadow-lg"
2222
2222
-
onClick={(e) => e.stopPropagation()}
2223
2223
-
/>
2265
2265
+
<>
2266
2266
+
{post && (
2267
2267
+
<div
2268
2268
+
onClick={(e) => {
2269
2269
+
e.stopPropagation();
2270
2270
+
e.nativeEvent.stopImmediatePropagation();
2271
2271
+
}}
2272
2272
+
className="lightbox-sidebar overscroll-none disablegutter border-l dark:border-gray-700 border-gray-300 fixed z-50 flex top-0 right-0 flex-col max-w-[350px] min-w-[350px] max-h-screen overflow-y-scroll dark:bg-gray-950 bg-white"
2273
2273
+
>
2274
2274
+
<ProfilePostComponent
2275
2275
+
did={post.did}
2276
2276
+
rkey={post.rkey}
2277
2277
+
nopics={onClose}
2278
2278
+
/>
2279
2279
+
</div>
2280
2280
+
)}
2281
2281
+
<div
2282
2282
+
className="lightbox fixed inset-0 z-50 flex items-center justify-center bg-black/80 w-screen lg:w-[calc(100vw-350px-var(--scrollbar-width)*0)] lg:max-w-[calc(100vw-350px-var(--scrollbar-width)*0)]"
2283
2283
+
onClick={(e) => {
2284
2284
+
e.stopPropagation();
2285
2285
+
onClose();
2286
2286
+
}}
2287
2287
+
>
2288
2288
+
<img
2289
2289
+
src={image.src}
2290
2290
+
alt={image.alt}
2291
2291
+
className="max-h-[90%] max-w-[90%] object-contain rounded-lg shadow-lg"
2292
2292
+
onClick={(e) => e.stopPropagation()}
2293
2293
+
/>
2224
2294
2225
2225
-
{images.length > 1 && (
2226
2226
-
<>
2227
2227
-
<button
2228
2228
-
onClick={(e) => {
2229
2229
-
e.stopPropagation();
2230
2230
-
onNavigate?.((index - 1 + images.length) % images.length);
2231
2231
-
}}
2232
2232
-
className="absolute left-4 top-1/2 -translate-y-1/2 text-white text-4xl h-8 w-8 rounded-full bg-gray-900 flex items-center justify-center"
2233
2233
-
>
2234
2234
-
<svg
2235
2235
-
xmlns="http://www.w3.org/2000/svg"
2236
2236
-
width={28}
2237
2237
-
height={28}
2238
2238
-
viewBox="0 0 24 24"
2295
2295
+
{images.length > 1 && (
2296
2296
+
<>
2297
2297
+
<button
2298
2298
+
onClick={(e) => {
2299
2299
+
e.stopPropagation();
2300
2300
+
onNavigate?.((index - 1 + images.length) % images.length);
2301
2301
+
}}
2302
2302
+
className="absolute left-4 top-1/2 -translate-y-1/2 text-white text-4xl h-8 w-8 rounded-full bg-gray-900 flex items-center justify-center"
2239
2303
>
2240
2240
-
<g fill="none" fillRule="evenodd">
2241
2241
-
<path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path>
2242
2242
-
<path
2243
2243
-
fill="currentColor"
2244
2244
-
d="M8.293 12.707a1 1 0 0 1 0-1.414l5.657-5.657a1 1 0 1 1 1.414 1.414L10.414 12l4.95 4.95a1 1 0 0 1-1.414 1.414z"
2245
2245
-
></path>
2246
2246
-
</g>
2247
2247
-
</svg>
2248
2248
-
</button>
2249
2249
-
<button
2250
2250
-
onClick={(e) => {
2251
2251
-
e.stopPropagation();
2252
2252
-
onNavigate?.((index + 1) % images.length);
2253
2253
-
}}
2254
2254
-
className="absolute right-4 top-1/2 -translate-y-1/2 text-white text-4xl h-8 w-8 rounded-full bg-gray-900 flex items-center justify-center"
2255
2255
-
>
2256
2256
-
<svg
2257
2257
-
xmlns="http://www.w3.org/2000/svg"
2258
2258
-
width={28}
2259
2259
-
height={28}
2260
2260
-
viewBox="0 0 24 24"
2304
2304
+
<svg
2305
2305
+
xmlns="http://www.w3.org/2000/svg"
2306
2306
+
width={28}
2307
2307
+
height={28}
2308
2308
+
viewBox="0 0 24 24"
2309
2309
+
>
2310
2310
+
<g fill="none" fillRule="evenodd">
2311
2311
+
<path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path>
2312
2312
+
<path
2313
2313
+
fill="currentColor"
2314
2314
+
d="M8.293 12.707a1 1 0 0 1 0-1.414l5.657-5.657a1 1 0 1 1 1.414 1.414L10.414 12l4.95 4.95a1 1 0 0 1-1.414 1.414z"
2315
2315
+
></path>
2316
2316
+
</g>
2317
2317
+
</svg>
2318
2318
+
</button>
2319
2319
+
<button
2320
2320
+
onClick={(e) => {
2321
2321
+
e.stopPropagation();
2322
2322
+
onNavigate?.((index + 1) % images.length);
2323
2323
+
}}
2324
2324
+
className="absolute right-4 top-1/2 -translate-y-1/2 text-white text-4xl h-8 w-8 rounded-full bg-gray-900 flex items-center justify-center"
2261
2325
>
2262
2262
-
<g fill="none" fillRule="evenodd">
2263
2263
-
<path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path>
2264
2264
-
<path
2265
2265
-
fill="currentColor"
2266
2266
-
d="M15.707 11.293a1 1 0 0 1 0 1.414l-5.657 5.657a1 1 0 1 1-1.414-1.414l4.95-4.95l-4.95-4.95a1 1 0 0 1 1.414-1.414z"
2267
2267
-
></path>
2268
2268
-
</g>
2269
2269
-
</svg>
2270
2270
-
</button>
2271
2271
-
</>
2272
2272
-
)}
2273
2273
-
</div>,
2326
2326
+
<svg
2327
2327
+
xmlns="http://www.w3.org/2000/svg"
2328
2328
+
width={28}
2329
2329
+
height={28}
2330
2330
+
viewBox="0 0 24 24"
2331
2331
+
>
2332
2332
+
<g fill="none" fillRule="evenodd">
2333
2333
+
<path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path>
2334
2334
+
<path
2335
2335
+
fill="currentColor"
2336
2336
+
d="M15.707 11.293a1 1 0 0 1 0 1.414l-5.657 5.657a1 1 0 1 1-1.414-1.414l4.95-4.95l-4.95-4.95a1 1 0 0 1 1.414-1.414z"
2337
2337
+
></path>
2338
2338
+
</g>
2339
2339
+
</svg>
2340
2340
+
</button>
2341
2341
+
</>
2342
2342
+
)}
2343
2343
+
</div>
2344
2344
+
</>,
2274
2345
document.body
2275
2346
);
2276
2347
}
+9
-9
src/main.tsx
···
1
1
-
import { StrictMode } from "react";
2
2
-
import ReactDOM from "react-dom/client";
3
3
-
import { RouterProvider, createRouter } from "@tanstack/react-router";
1
1
+
import "~/styles/app.css";
4
2
5
5
-
// Import the generated route tree
6
6
-
import { routeTree } from "./routeTree.gen";
7
7
-
8
8
-
import "~/styles/app.css";
9
9
-
import reportWebVitals from "./reportWebVitals.ts";
3
3
+
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
10
4
import { QueryClient, QueryClientProvider, } from "@tanstack/react-query";
11
5
import {
12
6
persistQueryClient,
13
7
} from "@tanstack/react-query-persist-client";
14
14
-
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
8
8
+
import { createRouter,RouterProvider } from "@tanstack/react-router";
9
9
+
//import { StrictMode } from "react";
10
10
+
import ReactDOM from "react-dom/client";
11
11
+
12
12
+
import reportWebVitals from "./reportWebVitals.ts";
13
13
+
// Import the generated route tree
14
14
+
import { routeTree } from "./routeTree.gen";
15
15
16
16
17
17
const queryClient = new QueryClient({
+42
-21
src/routes/profile.$did/post.$rkey.tsx
···
1
1
import { useQueryClient } from "@tanstack/react-query";
2
2
-
import { createFileRoute, Link } from "@tanstack/react-router";
2
2
+
import { createFileRoute } from "@tanstack/react-router";
3
3
import React, { useLayoutEffect } from "react";
4
4
5
5
import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer";
···
32
32
);
33
33
}
34
34
35
35
-
function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) {
35
35
+
export function ProfilePostComponent({
36
36
+
did,
37
37
+
rkey,
38
38
+
nopics,
39
39
+
}: {
40
40
+
did: string;
41
41
+
rkey: string;
42
42
+
nopics?: () => void;
43
43
+
}) {
36
44
//const { get, set } = usePersistentStore();
37
45
const queryClient = useQueryClient();
38
46
// const [resolvedDid, setResolvedDid] = React.useState<string | null>(null);
···
201
209
202
210
const scrollAnchor = React.useRef<{ top: number } | null>(null);
203
211
204
204
-
205
212
React.useEffect(() => {
206
213
const onScroll = () => {
207
207
-
208
214
if (window.scrollY > 50) {
209
215
userHasScrolled.current = true;
210
216
···
291
297
return (
292
298
<>
293
299
<div className="flex items-center gap-2 px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700">
294
294
-
<Link
295
295
-
to=".."
296
296
-
className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg"
297
297
-
onClick={(e) => {
298
298
-
e.preventDefault();
299
299
-
if (window.history.length > 1) {
300
300
-
window.history.back();
301
301
-
} else {
302
302
-
window.location.assign("/");
303
303
-
}
304
304
-
}}
305
305
-
aria-label="Go back"
306
306
-
>
307
307
-
←
308
308
-
</Link>
300
300
+
{!nopics ? (
301
301
+
<button
302
302
+
//to=".."
303
303
+
className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg"
304
304
+
onClick={(e) => {
305
305
+
e.preventDefault();
306
306
+
if (window.history.length > 1) {
307
307
+
window.history.back();
308
308
+
} else {
309
309
+
window.location.assign("/");
310
310
+
}
311
311
+
}}
312
312
+
aria-label="Go back"
313
313
+
>
314
314
+
←
315
315
+
</button>
316
316
+
) : (
317
317
+
<button
318
318
+
//to=".."
319
319
+
className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg"
320
320
+
onClick={(e) => {
321
321
+
e.preventDefault();
322
322
+
nopics();
323
323
+
}}
324
324
+
aria-label="Go back"
325
325
+
>
326
326
+
←
327
327
+
</button>
328
328
+
)}
309
329
<span className="text-xl font-bold ml-2">Post</span>
310
330
</div>
311
331
···
322
342
)}
323
343
324
344
{/* we should use the reply lines here thats provided by UPR*/}
325
325
-
<div style={{ maxWidth: 600, margin: "0px auto 0", padding: 0 }}>
345
345
+
<div style={{ maxWidth: 600, padding: 0 }}>
326
346
{parents.map((parent, index) => (
327
347
<UniversalPostRendererATURILoader
328
348
key={parent.uri}
···
338
358
atUri={atUri}
339
359
detailed={true}
340
360
topReplyLine={parentsLoading || parents.length > 0}
361
361
+
nopics={!!nopics}
341
362
/>
342
363
</div>
343
364
<div
344
365
style={{
345
366
maxWidth: 600,
346
346
-
margin: "0px auto 0",
367
367
+
//margin: "0px auto 0",
347
368
padding: 0,
348
369
minHeight: "100dvh",
349
370
}}
+12
-2
src/styles/app.css
···
48
48
}
49
49
50
50
@media (width >= 64rem /* 1024px */) {
51
51
-
html,
52
52
-
body {
51
51
+
html:not(:has(.disablegutter)),
52
52
+
body:not(:has(.disablegutter)) {
53
53
scrollbar-gutter: stable both-edges !important;
54
54
}
55
55
+
html:has(.disablegutter),
56
56
+
body:has(.disablegutter) {
57
57
+
scrollbar-width: none;
58
58
+
overflow-y: hidden;
59
59
+
}
55
60
}
61
61
+
62
62
+
.lightbox:has(+.lightbox-sidebar){
63
63
+
opacity: 0;
64
64
+
}
65
65
+
56
66
.scroll-thin {
57
67
scrollbar-width: thin;
58
68
/*scrollbar-gutter: stable both-edges !important;*/