an independent Bluesky client using Constellation, PDS Queries, and other services reddwarf.app
frontend spa bluesky reddwarf microcosm client app
92
fork

Configure Feed

Select the types of activity you want to include in your feed.

routeless lightbox sidebars

rimar1337 55f010da 833207ed

+534 -432
+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 + import { createPortal } from "react-dom"; 5 6 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 + style?: React.CSSProperties; 34 + ref?: React.Ref<HTMLDivElement>; 35 + dataIndexPropPass?: number; 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 + style, 142 + ref, 143 + dataIndexPropPass, 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 + style={style} 420 + ref={ref} 421 + dataIndexPropPass={dataIndexPropPass} 422 + nopics={nopics} 409 423 /> 410 424 ); 411 425 } ··· 430 444 bottomBorder = true, 431 445 feedviewpost = false, 432 446 repostedby, 447 + style, 448 + ref, 449 + dataIndexPropPass, 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 + style?: React.CSSProperties; 466 + ref?: React.Ref<HTMLDivElement>; 467 + dataIndexPropPass?: number; 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 + style={style} 664 + ref={ref} 665 + dataIndexPropPass={dataIndexPropPass} 666 + nopics={nopics} 641 667 /> 642 668 </> 643 669 ); ··· 1079 1105 feedviewpostreplyhandle, 1080 1106 depth = 0, 1081 1107 repostedby, 1108 + style, 1109 + ref, 1110 + dataIndexPropPass, 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 + style?: React.CSSProperties; 1132 + ref?: React.Ref<HTMLDivElement>; 1133 + dataIndexPropPass?: number; 1134 + nopics?: boolean; 1101 1135 }) { 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 + // eslint-disable-next-line react-hooks/refs 1210 + console.log("Received ref in UniversalPostRenderer:", ref); 1174 1211 return ( 1175 - <div 1176 - key={salt + "-" + (post.uri || emergencySalt)} 1177 - onClick={ 1178 - isMainItem 1179 - ? onPostClick 1180 - : setMainItem 1181 - ? onPostClick 1182 - ? (e) => { 1183 - setMainItem({ post: post }); 1184 - onPostClick(e); 1185 - } 1186 - : () => { 1187 - setMainItem({ post: post }); 1188 - } 1189 - : undefined 1190 - } 1191 - style={{ 1192 - //border: "1px solid #e1e8ed", 1193 - //borderRadius: 12, 1194 - opacity: "1 !important", 1195 - background: "transparent", 1196 - paddingLeft: isQuote ? 12 : 16, 1197 - paddingRight: isQuote ? 12 : 16, 1198 - //paddingTop: 16, 1199 - paddingTop: isRepost ? 10 : isQuote ? 12 : 16, 1200 - //paddingBottom: bottomReplyLine ? 0 : 16, 1201 - paddingBottom: 0, 1202 - fontFamily: "system-ui, sans-serif", 1203 - //boxShadow: "0 2px 8px rgba(0,0,0,0.04)", 1204 - position: "relative", 1205 - // dont cursor: "pointer", 1206 - borderBottomWidth: bottomBorder ? (isQuote ? 0 : 1) : 0, 1207 - }} 1208 - className="border-gray-300 dark:border-gray-600" 1209 - > 1210 - {isRepost && ( 1211 - <div 1212 - style={{ 1213 - marginLeft: 36, 1214 - display: "flex", 1215 - borderRadius: 12, 1216 - paddingBottom: "calc(22px - 1rem)", 1217 - fontSize: 14, 1218 - maxHeight: "1rem", 1219 - justifyContent: "flex-start", 1220 - //color: theme.textSecondary, 1221 - gap: 4, 1222 - alignItems: "center", 1223 - }} 1224 - className="text-gray-500 dark:text-gray-400" 1225 - > 1226 - <MdiRepost /> Reposted by @{isRepost}{" "} 1227 - </div> 1228 - )} 1229 - {!isQuote && ( 1230 - <div 1231 - style={{ 1232 - opacity: 1233 - topReplyLine || isReply /*&& (true || expanded)*/ ? 0.5 : 0, 1234 - position: "absolute", 1235 - top: 0, 1236 - left: 36, // why 36 ??? 1237 - //left: 16 + (42 / 2), 1238 - width: 2, 1239 - //height: "100%", 1240 - height: isRepost ? "calc(16px + 1rem - 6px)" : 16 - 6, 1241 - // background: theme.textSecondary, 1242 - //opacity: 0.5, 1243 - // no flex here 1244 - }} 1245 - className="bg-gray-500 dark:bg-gray-400" 1246 - /> 1247 - )} 1212 + <div ref={ref} style={style} data-index={dataIndexPropPass}> 1248 1213 <div 1214 + //ref={ref} 1215 + key={salt + "-" + (post.uri || emergencySalt)} 1216 + onClick={ 1217 + isMainItem 1218 + ? onPostClick 1219 + : setMainItem 1220 + ? onPostClick 1221 + ? (e) => { 1222 + setMainItem({ post: post }); 1223 + onPostClick(e); 1224 + } 1225 + : () => { 1226 + setMainItem({ post: post }); 1227 + } 1228 + : undefined 1229 + } 1249 1230 style={{ 1250 - position: "absolute", 1251 - //top: isRepost ? "calc(16px + 1rem)" : 16, 1252 - //left: 16, 1253 - zIndex: 1, 1254 - top: isRepost ? "calc(16px + 1rem)" : isQuote ? 12 : 16, 1255 - left: isQuote ? 12 : 16, 1231 + //...style, 1232 + //border: "1px solid #e1e8ed", 1233 + //borderRadius: 12, 1234 + opacity: "1 !important", 1235 + background: "transparent", 1236 + paddingLeft: isQuote ? 12 : 16, 1237 + paddingRight: isQuote ? 12 : 16, 1238 + //paddingTop: 16, 1239 + paddingTop: isRepost ? 10 : isQuote ? 12 : 16, 1240 + //paddingBottom: bottomReplyLine ? 0 : 16, 1241 + paddingBottom: 0, 1242 + fontFamily: "system-ui, sans-serif", 1243 + //boxShadow: "0 2px 8px rgba(0,0,0,0.04)", 1244 + position: "relative", 1245 + // dont cursor: "pointer", 1246 + borderBottomWidth: bottomBorder ? (isQuote ? 0 : 1) : 0, 1256 1247 }} 1257 - onClick={onProfileClick} 1248 + className="border-gray-300 dark:border-gray-600" 1258 1249 > 1259 - <img 1260 - src={post.author.avatar || defaultpfp} 1261 - alt="avatar" 1262 - // transition={{ 1263 - // type: "spring", 1264 - // stiffness: 260, 1265 - // damping: 20, 1266 - // }} 1267 - style={{ 1268 - borderRadius: "50%", 1269 - marginRight: 12, 1270 - objectFit: "cover", 1271 - //background: theme.border, 1272 - //border: `1px solid ${theme.border}`, 1273 - width: isQuote ? 16 : 42, 1274 - height: isQuote ? 16 : 42, 1275 - }} 1276 - className="border border-gray-300 dark:border-gray-600 bg-gray-300 dark:bg-gray-600" 1277 - /> 1278 - </div> 1279 - <div style={{ display: "flex", alignItems: "flex-start", zIndex: 2 }}> 1250 + {isRepost && ( 1251 + <div 1252 + style={{ 1253 + marginLeft: 36, 1254 + display: "flex", 1255 + borderRadius: 12, 1256 + paddingBottom: "calc(22px - 1rem)", 1257 + fontSize: 14, 1258 + maxHeight: "1rem", 1259 + justifyContent: "flex-start", 1260 + //color: theme.textSecondary, 1261 + gap: 4, 1262 + alignItems: "center", 1263 + }} 1264 + className="text-gray-500 dark:text-gray-400" 1265 + > 1266 + <MdiRepost /> Reposted by @{isRepost}{" "} 1267 + </div> 1268 + )} 1269 + {!isQuote && ( 1270 + <div 1271 + style={{ 1272 + opacity: 1273 + topReplyLine || isReply /*&& (true || expanded)*/ ? 0.5 : 0, 1274 + position: "absolute", 1275 + top: 0, 1276 + left: 36, // why 36 ??? 1277 + //left: 16 + (42 / 2), 1278 + width: 2, 1279 + //height: "100%", 1280 + height: isRepost ? "calc(16px + 1rem - 6px)" : 16 - 6, 1281 + // background: theme.textSecondary, 1282 + //opacity: 0.5, 1283 + // no flex here 1284 + }} 1285 + className="bg-gray-500 dark:bg-gray-400" 1286 + /> 1287 + )} 1280 1288 <div 1281 1289 style={{ 1282 - display: "flex", 1283 - flexDirection: "column", 1284 - alignSelf: "stretch", 1285 - alignItems: "center", 1286 - overflow: "hidden", 1287 - width: expanded || isQuote ? 0 : "auto", 1288 - marginRight: expanded || isQuote ? 0 : 12, 1290 + position: "absolute", 1291 + //top: isRepost ? "calc(16px + 1rem)" : 16, 1292 + //left: 16, 1293 + zIndex: 1, 1294 + top: isRepost ? "calc(16px + 1rem)" : isQuote ? 12 : 16, 1295 + left: isQuote ? 12 : 16, 1289 1296 }} 1297 + onClick={onProfileClick} 1290 1298 > 1291 - {/* dummy for later use */} 1292 - <div style={{ width: 42, height: 42 + 8, minHeight: 42 + 8 }} /> 1293 - {/* reply line !!!! bottomReplyLine */} 1294 - {bottomReplyLine && ( 1295 - <div 1296 - style={{ 1297 - width: 2, 1298 - height: "100%", 1299 - //background: theme.textSecondary, 1300 - opacity: 0.5, 1301 - // no flex here 1302 - //color: "Red", 1303 - //zIndex: 99 1304 - }} 1305 - className="bg-gray-500 dark:bg-gray-400" 1306 - /> 1307 - )} 1308 - {/* <div 1299 + <img 1300 + src={post.author.avatar || defaultpfp} 1301 + alt="avatar" 1302 + // transition={{ 1303 + // type: "spring", 1304 + // stiffness: 260, 1305 + // damping: 20, 1306 + // }} 1307 + style={{ 1308 + borderRadius: "50%", 1309 + marginRight: 12, 1310 + objectFit: "cover", 1311 + //background: theme.border, 1312 + //border: `1px solid ${theme.border}`, 1313 + width: isQuote ? 16 : 42, 1314 + height: isQuote ? 16 : 42, 1315 + }} 1316 + className="border border-gray-300 dark:border-gray-600 bg-gray-300 dark:bg-gray-600" 1317 + /> 1318 + </div> 1319 + <div style={{ display: "flex", alignItems: "flex-start", zIndex: 2 }}> 1320 + <div 1321 + style={{ 1322 + display: "flex", 1323 + flexDirection: "column", 1324 + alignSelf: "stretch", 1325 + alignItems: "center", 1326 + overflow: "hidden", 1327 + width: expanded || isQuote ? 0 : "auto", 1328 + marginRight: expanded || isQuote ? 0 : 12, 1329 + }} 1330 + > 1331 + {/* dummy for later use */} 1332 + <div style={{ width: 42, height: 42 + 8, minHeight: 42 + 8 }} /> 1333 + {/* reply line !!!! bottomReplyLine */} 1334 + {bottomReplyLine && ( 1335 + <div 1336 + style={{ 1337 + width: 2, 1338 + height: "100%", 1339 + //background: theme.textSecondary, 1340 + opacity: 0.5, 1341 + // no flex here 1342 + //color: "Red", 1343 + //zIndex: 99 1344 + }} 1345 + className="bg-gray-500 dark:bg-gray-400" 1346 + /> 1347 + )} 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 - </div> 1319 - <div style={{ flex: 1, maxWidth: "100%" }}> 1320 - <div 1321 - style={{ 1322 - display: "flex", 1323 - flexDirection: "row", 1324 - alignItems: "center", 1325 - flexWrap: "nowrap", 1326 - maxWidth: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`, 1327 - width: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`, 1328 - marginLeft: !expanded ? (isQuote ? 26 : 0) : 54, 1329 - marginBottom: !expanded ? 4 : 6, 1330 - }} 1331 - > 1358 + </div> 1359 + <div style={{ flex: 1, maxWidth: "100%" }}> 1332 1360 <div 1333 1361 style={{ 1334 1362 display: "flex", 1335 - //overflow: "hidden", // hey why is overflow hidden unapplied 1336 - overflow: "hidden", 1337 - textOverflow: "ellipsis", 1338 - flexShrink: 1, 1339 - flexGrow: 1, 1340 - flexBasis: 0, 1341 - width: 0, 1342 - gap: expanded ? 0 : 6, 1343 - alignItems: expanded ? "flex-start" : "center", 1344 - flexDirection: expanded ? "column" : "row", 1345 - height: expanded ? 42 : "1rem", 1363 + flexDirection: "row", 1364 + alignItems: "center", 1365 + flexWrap: "nowrap", 1366 + maxWidth: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`, 1367 + width: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`, 1368 + marginLeft: !expanded ? (isQuote ? 26 : 0) : 54, 1369 + marginBottom: !expanded ? 4 : 6, 1346 1370 }} 1347 1371 > 1348 - <span 1372 + <div 1349 1373 style={{ 1350 1374 display: "flex", 1351 - fontWeight: 700, 1352 - fontSize: 16, 1375 + //overflow: "hidden", // hey why is overflow hidden unapplied 1353 1376 overflow: "hidden", 1354 1377 textOverflow: "ellipsis", 1355 - whiteSpace: "nowrap", 1356 1378 flexShrink: 1, 1357 - minWidth: 0, 1358 - gap: 4, 1359 - alignItems: "center", 1360 - //color: theme.text, 1379 + flexGrow: 1, 1380 + flexBasis: 0, 1381 + width: 0, 1382 + gap: expanded ? 0 : 6, 1383 + alignItems: expanded ? "flex-start" : "center", 1384 + flexDirection: expanded ? "column" : "row", 1385 + height: expanded ? 42 : "1rem", 1361 1386 }} 1362 - className="text-gray-900 dark:text-gray-100" 1363 1387 > 1364 - {/* verified checkmark */} 1365 - {post.author.displayName || post.author.handle}{" "} 1366 - {post.author.verification?.verifiedStatus == "valid" && ( 1367 - <MdiVerified /> 1368 - )} 1369 - </span> 1388 + <span 1389 + style={{ 1390 + display: "flex", 1391 + fontWeight: 700, 1392 + fontSize: 16, 1393 + overflow: "hidden", 1394 + textOverflow: "ellipsis", 1395 + whiteSpace: "nowrap", 1396 + flexShrink: 1, 1397 + minWidth: 0, 1398 + gap: 4, 1399 + alignItems: "center", 1400 + //color: theme.text, 1401 + }} 1402 + className="text-gray-900 dark:text-gray-100" 1403 + > 1404 + {/* verified checkmark */} 1405 + {post.author.displayName || post.author.handle}{" "} 1406 + {post.author.verification?.verifiedStatus == "valid" && ( 1407 + <MdiVerified /> 1408 + )} 1409 + </span> 1370 1410 1371 - <span 1411 + <span 1412 + style={{ 1413 + //color: theme.textSecondary, 1414 + fontSize: 16, 1415 + overflowX: "hidden", 1416 + textOverflow: "ellipsis", 1417 + whiteSpace: "nowrap", 1418 + flexShrink: 1, 1419 + flexGrow: 0, 1420 + minWidth: 0, 1421 + }} 1422 + className="text-gray-500 dark:text-gray-400" 1423 + > 1424 + @{post.author.handle} 1425 + </span> 1426 + </div> 1427 + <div 1372 1428 style={{ 1373 - //color: theme.textSecondary, 1374 - fontSize: 16, 1375 - overflowX: "hidden", 1376 - textOverflow: "ellipsis", 1377 - whiteSpace: "nowrap", 1378 - flexShrink: 1, 1379 - flexGrow: 0, 1380 - minWidth: 0, 1429 + display: "flex", 1430 + alignItems: "center", 1431 + height: "1rem", 1381 1432 }} 1382 - className="text-gray-500 dark:text-gray-400" 1383 1433 > 1384 - @{post.author.handle} 1385 - </span> 1434 + <span 1435 + style={{ 1436 + //color: theme.textSecondary, 1437 + fontSize: 16, 1438 + marginLeft: 8, 1439 + whiteSpace: "nowrap", 1440 + flexShrink: 0, 1441 + maxWidth: "100%", 1442 + }} 1443 + className="text-gray-500 dark:text-gray-400" 1444 + > 1445 + · {/* time placeholder */} 1446 + {shortTimeAgo(post.indexedAt)} 1447 + </span> 1448 + </div> 1386 1449 </div> 1387 - <div 1388 - style={{ 1389 - display: "flex", 1390 - alignItems: "center", 1391 - height: "1rem", 1392 - }} 1393 - > 1394 - <span 1450 + {/* reply indicator */} 1451 + {!!feedviewpostreplyhandle && ( 1452 + <div 1395 1453 style={{ 1454 + display: "flex", 1455 + borderRadius: 12, 1456 + paddingBottom: 2, 1457 + fontSize: 14, 1458 + justifyContent: "flex-start", 1396 1459 //color: theme.textSecondary, 1397 - fontSize: 16, 1398 - marginLeft: 8, 1399 - whiteSpace: "nowrap", 1400 - flexShrink: 0, 1401 - maxWidth: "100%", 1460 + gap: 4, 1461 + alignItems: "center", 1462 + //marginLeft: 36, 1463 + height: 1464 + !(expanded || isQuote) && !!feedviewpostreplyhandle 1465 + ? "1rem" 1466 + : 0, 1467 + opacity: 1468 + !(expanded || isQuote) && !!feedviewpostreplyhandle ? 1 : 0, 1402 1469 }} 1403 1470 className="text-gray-500 dark:text-gray-400" 1404 1471 > 1405 - · {/* time placeholder */} 1406 - {shortTimeAgo(post.indexedAt)} 1407 - </span> 1408 - </div> 1409 - </div> 1410 - {/* reply indicator */} 1411 - {!!feedviewpostreplyhandle && ( 1472 + <MdiReply /> Reply to @{feedviewpostreplyhandle} 1473 + </div> 1474 + )} 1412 1475 <div 1413 1476 style={{ 1414 - display: "flex", 1415 - borderRadius: 12, 1416 - paddingBottom: 2, 1417 - fontSize: 14, 1418 - justifyContent: "flex-start", 1419 - //color: theme.textSecondary, 1420 - gap: 4, 1421 - alignItems: "center", 1422 - //marginLeft: 36, 1423 - height: 1424 - !(expanded || isQuote) && !!feedviewpostreplyhandle 1425 - ? "1rem" 1426 - : 0, 1427 - opacity: 1428 - !(expanded || isQuote) && !!feedviewpostreplyhandle ? 1 : 0, 1477 + fontSize: 16, 1478 + marginBottom: !post.embed /*|| depth > 0*/ ? 0 : 8, 1479 + whiteSpace: "pre-wrap", 1480 + textAlign: "left", 1481 + overflowWrap: "anywhere", 1482 + wordBreak: "break-word", 1483 + //color: theme.text, 1429 1484 }} 1430 - className="text-gray-500 dark:text-gray-400" 1485 + className="text-gray-900 dark:text-gray-100" 1431 1486 > 1432 - <MdiReply /> Reply to @{feedviewpostreplyhandle} 1487 + {renderTextWithFacets({ 1488 + text: (post.record as { text?: string }).text ?? "", 1489 + facets: (post.record.facets as Facet[]) ?? [], 1490 + navigate: navigate, 1491 + })} 1492 + {} 1433 1493 </div> 1434 - )} 1435 - <div 1436 - style={{ 1437 - fontSize: 16, 1438 - marginBottom: !post.embed /*|| depth > 0*/ ? 0 : 8, 1439 - whiteSpace: "pre-wrap", 1440 - textAlign: "left", 1441 - overflowWrap: "anywhere", 1442 - wordBreak: "break-word", 1443 - //color: theme.text, 1444 - }} 1445 - className="text-gray-900 dark:text-gray-100" 1446 - > 1447 - {renderTextWithFacets({ 1448 - text: (post.record as { text?: string }).text ?? "", 1449 - facets: (post.record.facets as Facet[]) ?? [], 1450 - navigate: navigate, 1451 - })} 1452 - {} 1453 - </div> 1454 - {post.embed && depth < 1 ? ( 1455 - <PostEmbeds 1456 - embed={post.embed} 1457 - //moderation={moderation} 1458 - viewContext={PostEmbedViewContext.Feed} 1459 - salt={salt} 1460 - navigate={navigate} 1461 - /> 1462 - ) : null} 1463 - {post.embed && depth > 0 && ( 1464 - /* pretty bad hack imo. its trying to sync up with how the embed shim doesnt 1494 + {post.embed && depth < 1 ? ( 1495 + <PostEmbeds 1496 + embed={post.embed} 1497 + //moderation={moderation} 1498 + viewContext={PostEmbedViewContext.Feed} 1499 + salt={salt} 1500 + navigate={navigate} 1501 + postid={{ did: post.author.did, rkey: parsed.rkey }} 1502 + nopics={nopics} 1503 + /> 1504 + ) : null} 1505 + {post.embed && depth > 0 && ( 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 - <> 1468 - <div className="border-gray-300 dark:border-gray-600 p-3 rounded-xl border italic text-gray-400 text-[14px]"> 1469 - (there is an embed here thats too deep to render) 1470 - </div> 1471 - </> 1472 - )} 1473 - <div style={{ paddingTop: post.embed && depth < 1 ? 4 : 0 }}> 1474 - <> 1475 - {expanded && ( 1509 + <> 1510 + <div className="border-gray-300 dark:border-gray-600 p-3 rounded-xl border italic text-gray-400 text-[14px]"> 1511 + (there is an embed here thats too deep to render) 1512 + </div> 1513 + </> 1514 + )} 1515 + <div style={{ paddingTop: post.embed && depth < 1 ? 4 : 0 }}> 1516 + <> 1517 + {expanded && ( 1518 + <div 1519 + style={{ 1520 + overflow: "hidden", 1521 + //color: theme.textSecondary, 1522 + fontSize: 14, 1523 + display: "flex", 1524 + borderBottomStyle: "solid", 1525 + //borderBottomColor: theme.border, 1526 + //background: "#f00", 1527 + // height: "1rem", 1528 + paddingTop: 4, 1529 + paddingBottom: 8, 1530 + borderBottomWidth: 1, 1531 + marginBottom: 8, 1532 + }} // important for height animation 1533 + className="text-gray-500 dark:text-gray-400 border-gray-200 dark:border-gray-700" 1534 + > 1535 + {fullDateTimeFormat(post.indexedAt)} 1536 + </div> 1537 + )} 1538 + </> 1539 + {!isQuote && ( 1476 1540 <div 1477 1541 style={{ 1478 - overflow: "hidden", 1479 - //color: theme.textSecondary, 1480 - fontSize: 14, 1481 1542 display: "flex", 1482 - borderBottomStyle: "solid", 1483 - //borderBottomColor: theme.border, 1484 - //background: "#f00", 1485 - // height: "1rem", 1486 - paddingTop: 4, 1487 - paddingBottom: 8, 1488 - borderBottomWidth: 1, 1489 - marginBottom: 8, 1490 - }} // important for height animation 1491 - className="text-gray-500 dark:text-gray-400 border-gray-200 dark:border-gray-700" 1492 - > 1493 - {fullDateTimeFormat(post.indexedAt)} 1494 - </div> 1495 - )} 1496 - </> 1497 - {!isQuote && ( 1498 - <div 1499 - style={{ 1500 - display: "flex", 1501 - gap: 32, 1502 - paddingTop: 8, 1503 - //color: theme.textSecondary, 1504 - fontSize: 15, 1505 - justifyContent: "space-between", 1506 - //background: "#0f0", 1507 - }} 1508 - className="text-gray-500 dark:text-gray-400" 1509 - > 1510 - <span style={btnstyle}> 1511 - <MdiCommentOutline /> 1512 - {post.replyCount} 1513 - </span> 1514 - <HitSlopButton 1515 - onClick={() => { 1516 - repostOrUnrepostPost(); 1543 + gap: 32, 1544 + paddingTop: 8, 1545 + //color: theme.textSecondary, 1546 + fontSize: 15, 1547 + justifyContent: "space-between", 1548 + //background: "#0f0", 1517 1549 }} 1518 - style={{ 1519 - ...btnstyle, 1520 - ...(hasRetweeted ? { color: "#5CEFAA" } : {}), 1521 - }} 1522 - > 1523 - {hasRetweeted ? <MdiRepeatGreen /> : <MdiRepeat />} 1524 - {(post.repostCount || 0) + (hasRetweeted ? 1 : 0)} 1525 - </HitSlopButton> 1526 - <HitSlopButton 1527 - onClick={() => { 1528 - likeOrUnlikePost(); 1529 - }} 1530 - style={{ 1531 - ...btnstyle, 1532 - ...(hasLiked ? { color: "#EC4899" } : {}), 1533 - }} 1550 + className="text-gray-500 dark:text-gray-400" 1534 1551 > 1535 - {hasLiked ? <MdiCardsHeart /> : <MdiCardsHeartOutline />} 1536 - {(post.likeCount || 0) + (hasLiked ? 1 : 0)} 1537 - </HitSlopButton> 1538 - <div style={{ display: "flex", gap: 8 }}> 1552 + <span style={btnstyle}> 1553 + <MdiCommentOutline /> 1554 + {post.replyCount} 1555 + </span> 1556 + <HitSlopButton 1557 + onClick={() => { 1558 + repostOrUnrepostPost(); 1559 + }} 1560 + style={{ 1561 + ...btnstyle, 1562 + ...(hasRetweeted ? { color: "#5CEFAA" } : {}), 1563 + }} 1564 + > 1565 + {hasRetweeted ? <MdiRepeatGreen /> : <MdiRepeat />} 1566 + {(post.repostCount || 0) + (hasRetweeted ? 1 : 0)} 1567 + </HitSlopButton> 1539 1568 <HitSlopButton 1540 - onClick={async (e) => { 1541 - e.stopPropagation(); 1542 - try { 1543 - await navigator.clipboard.writeText( 1544 - "https://bsky.app" + 1545 - "/profile/" + 1546 - post.author.handle + 1547 - "/post/" + 1548 - post.uri.split("/").pop() 1549 - ); 1550 - } catch (_e) { 1551 - // idk 1552 - } 1569 + onClick={() => { 1570 + likeOrUnlikePost(); 1553 1571 }} 1554 1572 style={{ 1555 1573 ...btnstyle, 1574 + ...(hasLiked ? { color: "#EC4899" } : {}), 1556 1575 }} 1557 1576 > 1558 - <MdiShareVariant /> 1577 + {hasLiked ? <MdiCardsHeart /> : <MdiCardsHeartOutline />} 1578 + {(post.likeCount || 0) + (hasLiked ? 1 : 0)} 1559 1579 </HitSlopButton> 1560 - <span style={btnstyle}> 1561 - <MdiMoreHoriz /> 1562 - </span> 1580 + <div style={{ display: "flex", gap: 8 }}> 1581 + <HitSlopButton 1582 + onClick={async (e) => { 1583 + e.stopPropagation(); 1584 + try { 1585 + await navigator.clipboard.writeText( 1586 + "https://bsky.app" + 1587 + "/profile/" + 1588 + post.author.handle + 1589 + "/post/" + 1590 + post.uri.split("/").pop() 1591 + ); 1592 + } catch (_e) { 1593 + // idk 1594 + } 1595 + }} 1596 + style={{ 1597 + ...btnstyle, 1598 + }} 1599 + > 1600 + <MdiShareVariant /> 1601 + </HitSlopButton> 1602 + <span style={btnstyle}> 1603 + <MdiMoreHoriz /> 1604 + </span> 1605 + </div> 1563 1606 </div> 1564 - </div> 1565 - )} 1607 + )} 1608 + </div> 1609 + <div 1610 + style={{ 1611 + //height: bottomReplyLine ? 16 : 0 1612 + height: isQuote ? 12 : 16, 1613 + }} 1614 + /> 1566 1615 </div> 1567 - <div 1568 - style={{ 1569 - //height: bottomReplyLine ? 16 : 0 1570 - height: isQuote ? 12 : 16, 1571 - }} 1572 - /> 1573 1616 </div> 1574 1617 </div> 1575 1618 </div> ··· 1661 1704 viewContext, 1662 1705 salt, 1663 1706 navigate, 1707 + postid, 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 + postid?: { did: string; rkey: string }; 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 + postid={postid} 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 - if (AppBskyEmbedImages.isView(embed)) { 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 + 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 + 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 + 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 + post={postid} 2091 2144 /> 2092 2145 )} 2093 2146 {images.map((img, i) => ( ··· 2180 2233 return <div />; 2181 2234 } 2182 2235 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 + 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 + 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 - <div 2212 - className="fixed inset-0 z-50 flex items-center justify-center bg-black/80" 2213 - onClick={(e) => { 2214 - e.stopPropagation(); 2215 - onClose(); 2216 - }} 2217 - > 2218 - <img 2219 - src={image.src} 2220 - alt={image.alt} 2221 - className="max-h-[90vh] max-w-[90vw] object-contain rounded-lg shadow-lg" 2222 - onClick={(e) => e.stopPropagation()} 2223 - /> 2265 + <> 2266 + {post && ( 2267 + <div 2268 + onClick={(e) => { 2269 + e.stopPropagation(); 2270 + e.nativeEvent.stopImmediatePropagation(); 2271 + }} 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 + > 2274 + <ProfilePostComponent 2275 + did={post.did} 2276 + rkey={post.rkey} 2277 + nopics={onClose} 2278 + /> 2279 + </div> 2280 + )} 2281 + <div 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 + onClick={(e) => { 2284 + e.stopPropagation(); 2285 + onClose(); 2286 + }} 2287 + > 2288 + <img 2289 + src={image.src} 2290 + alt={image.alt} 2291 + className="max-h-[90%] max-w-[90%] object-contain rounded-lg shadow-lg" 2292 + onClick={(e) => e.stopPropagation()} 2293 + /> 2224 2294 2225 - {images.length > 1 && ( 2226 - <> 2227 - <button 2228 - onClick={(e) => { 2229 - e.stopPropagation(); 2230 - onNavigate?.((index - 1 + images.length) % images.length); 2231 - }} 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 - > 2234 - <svg 2235 - xmlns="http://www.w3.org/2000/svg" 2236 - width={28} 2237 - height={28} 2238 - viewBox="0 0 24 24" 2295 + {images.length > 1 && ( 2296 + <> 2297 + <button 2298 + onClick={(e) => { 2299 + e.stopPropagation(); 2300 + onNavigate?.((index - 1 + images.length) % images.length); 2301 + }} 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 - <g fill="none" fillRule="evenodd"> 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 - <path 2243 - fill="currentColor" 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 - ></path> 2246 - </g> 2247 - </svg> 2248 - </button> 2249 - <button 2250 - onClick={(e) => { 2251 - e.stopPropagation(); 2252 - onNavigate?.((index + 1) % images.length); 2253 - }} 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 - > 2256 - <svg 2257 - xmlns="http://www.w3.org/2000/svg" 2258 - width={28} 2259 - height={28} 2260 - viewBox="0 0 24 24" 2304 + <svg 2305 + xmlns="http://www.w3.org/2000/svg" 2306 + width={28} 2307 + height={28} 2308 + viewBox="0 0 24 24" 2309 + > 2310 + <g fill="none" fillRule="evenodd"> 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 + <path 2313 + fill="currentColor" 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 + ></path> 2316 + </g> 2317 + </svg> 2318 + </button> 2319 + <button 2320 + onClick={(e) => { 2321 + e.stopPropagation(); 2322 + onNavigate?.((index + 1) % images.length); 2323 + }} 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 - <g fill="none" fillRule="evenodd"> 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 - <path 2265 - fill="currentColor" 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 - ></path> 2268 - </g> 2269 - </svg> 2270 - </button> 2271 - </> 2272 - )} 2273 - </div>, 2326 + <svg 2327 + xmlns="http://www.w3.org/2000/svg" 2328 + width={28} 2329 + height={28} 2330 + viewBox="0 0 24 24" 2331 + > 2332 + <g fill="none" fillRule="evenodd"> 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 + <path 2335 + fill="currentColor" 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 + ></path> 2338 + </g> 2339 + </svg> 2340 + </button> 2341 + </> 2342 + )} 2343 + </div> 2344 + </>, 2274 2345 document.body 2275 2346 ); 2276 2347 }
+9 -9
src/main.tsx
··· 1 - import { StrictMode } from "react"; 2 - import ReactDOM from "react-dom/client"; 3 - import { RouterProvider, createRouter } from "@tanstack/react-router"; 1 + import "~/styles/app.css"; 4 2 5 - // Import the generated route tree 6 - import { routeTree } from "./routeTree.gen"; 7 - 8 - import "~/styles/app.css"; 9 - import reportWebVitals from "./reportWebVitals.ts"; 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 - import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; 8 + import { createRouter,RouterProvider } from "@tanstack/react-router"; 9 + //import { StrictMode } from "react"; 10 + import ReactDOM from "react-dom/client"; 11 + 12 + import reportWebVitals from "./reportWebVitals.ts"; 13 + // Import the generated route tree 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 - import { createFileRoute, Link } from "@tanstack/react-router"; 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 - function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) { 35 + export function ProfilePostComponent({ 36 + did, 37 + rkey, 38 + nopics, 39 + }: { 40 + did: string; 41 + rkey: string; 42 + nopics?: () => void; 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 - 205 212 React.useEffect(() => { 206 213 const onScroll = () => { 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 - <Link 295 - to=".." 296 - className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg" 297 - onClick={(e) => { 298 - e.preventDefault(); 299 - if (window.history.length > 1) { 300 - window.history.back(); 301 - } else { 302 - window.location.assign("/"); 303 - } 304 - }} 305 - aria-label="Go back" 306 - > 307 - 308 - </Link> 300 + {!nopics ? ( 301 + <button 302 + //to=".." 303 + className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg" 304 + onClick={(e) => { 305 + e.preventDefault(); 306 + if (window.history.length > 1) { 307 + window.history.back(); 308 + } else { 309 + window.location.assign("/"); 310 + } 311 + }} 312 + aria-label="Go back" 313 + > 314 + 315 + </button> 316 + ) : ( 317 + <button 318 + //to=".." 319 + className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg" 320 + onClick={(e) => { 321 + e.preventDefault(); 322 + nopics(); 323 + }} 324 + aria-label="Go back" 325 + > 326 + 327 + </button> 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 - <div style={{ maxWidth: 600, margin: "0px auto 0", padding: 0 }}> 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 + nopics={!!nopics} 341 362 /> 342 363 </div> 343 364 <div 344 365 style={{ 345 366 maxWidth: 600, 346 - margin: "0px auto 0", 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 - html, 52 - body { 51 + html:not(:has(.disablegutter)), 52 + body:not(:has(.disablegutter)) { 53 53 scrollbar-gutter: stable both-edges !important; 54 54 } 55 + html:has(.disablegutter), 56 + body:has(.disablegutter) { 57 + scrollbar-width: none; 58 + overflow-y: hidden; 59 + } 55 60 } 61 + 62 + .lightbox:has(+.lightbox-sidebar){ 63 + opacity: 0; 64 + } 65 + 56 66 .scroll-thin { 57 67 scrollbar-width: thin; 58 68 /*scrollbar-gutter: stable both-edges !important;*/