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

post content hider

+251 -86
+2
src/auto-imports.d.ts
··· 24 24 const IconMdiCheck: typeof import('~icons/mdi/check.jsx').default 25 25 const IconMdiCheckCircle: typeof import('~icons/mdi/check-circle.jsx').default 26 26 const IconMdiCheckboxMultipleMarked: typeof import('~icons/mdi/checkbox-multiple-marked.jsx').default 27 + const IconMdiChevronDown: typeof import('~icons/mdi/chevron-down.jsx').default 27 28 const IconMdiClock: typeof import('~icons/mdi/clock.jsx').default 28 29 const IconMdiClockOutline: typeof import('~icons/mdi/clock-outline.jsx').default 29 30 const IconMdiClose: typeof import('~icons/mdi/close.jsx').default ··· 42 43 const IconMdiShield: typeof import('~icons/mdi/shield.jsx').default 43 44 const IconMdiShieldOutline: typeof import('~icons/mdi/shield-outline.jsx').default 44 45 const IconMdiVerified: typeof import('~icons/mdi/verified.jsx').default 46 + const IconMdiWarning: typeof import('~icons/mdi/warning.jsx').default 45 47 }
+77
src/components/LogoSvg.tsx
··· 76 76 </svg>) 77 77 } 78 78 79 + // candidate for default 80 + export function WheyMadeModernistMonogram2Partial(props: SVGProps<SVGSVGElement>) { 81 + return ( 82 + <svg 83 + viewBox="0 0 512 512" 84 + version="1.1" 85 + xmlns="http://www.w3.org/2000/svg" 86 + {...props} 87 + > 88 + <title>Logotype 2partial</title> 89 + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> 90 + <g id="Logotype-partial" transform="translate(25, 25)" fill="currentColor"> 91 + <path d="M231,2.27373675e-13 C358.577777,2.27373675e-13 462,103.422223 462,231 C462,358.577777 358.577777,462 231,462 Z" id="Half"></path> 92 + <circle id="Oval" cx="115.5" cy="115.5" r="115.5"></circle> 93 + <path d="M0.082,231 L0.083,231.001 L3.82032351,231.030947 C129.607985,233.070408 230.954808,335.652489 230.999823,461.916004 L231.000162,461.999838 L230.877,461.999 L230.999838,461.99916 C230.999838,461.97144 230.999833,461.943722 230.999823,461.916004 L0.083,231.001 L0.00032417037,231 L0.0312715054,234.82001 C2.07117982,360.63597 104.698104,462.000162 230.999838,462.000162 L230.877,461.999 L0,461.999838 L0,231.000324 L0.082,231 Z" id="Combined-Shape"></path> 94 + </g> 95 + <rect id="Rectangle" fill="currentColor" x="25" y="25" width="116" height="231"></rect> 96 + </g> 97 + </svg> 98 + ) 99 + } 100 + 101 + export function WheyMade5Square(props: SVGProps<SVGSVGElement>) { 102 + return ( 103 + <svg 104 + viewBox="0 0 512 512" 105 + version="1.1" 106 + xmlns="http://www.w3.org/2000/svg" 107 + transform='rotate(135)' 108 + {...props} 109 + > 110 + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> 111 + <g id="Logotype-5Square" transform="translate(0.4991, -0.0005)" fill="currentColor"> 112 + <path d="M255.500876,25.0005352 C383.078653,25.0005352 486.500876,128.422758 486.500876,256.000535 C486.500876,356.786979 421.955067,442.497942 331.945007,474.051864 L331.3,474.272 L381.857,512 L0.000430968674,512.000535 L1.42108547e-14,130.312 L37.229,180.2 L37.4495474,179.556404 C68.6040527,90.6857123 152.553212,26.6386865 251.680869,25.0314826 L255.500876,25.0005352 Z M512,288.919 L512.000431,512.000535 L482.724,512 L480.160326,311.436715 L512,288.919 Z M50.543,362.647 L0.0982429603,511.403168 L148.855,460.958 L146.179023,459.545342 C106.285176,438.073726 73.427685,405.216235 51.9560695,365.322388 L50.543,362.647 Z M262.03074,422.579291 L148.855,460.958 L149.343076,461.216735 C181.12821,477.692535 217.227543,487.000535 255.500876,487.000535 C281.016431,487.000535 305.565765,482.863646 328.514742,475.224002 L331.3,474.272 L262.03074,422.579291 Z M37.229,180.2 L36.2774093,182.986669 C28.6377649,205.935647 24.500876,230.48498 24.500876,256.000535 C24.500876,294.273868 33.808876,330.373202 50.2846761,362.158335 L50.543,362.647 L88.9221204,249.470671 L37.229,180.2 Z M512.000431,0.00053522388 L512,91.44 L441.863205,69.6382067 L420.216,-2.84217094e-14 L512.000431,0.00053522388 Z M222.228,-2.84217094e-14 L200.064696,31.3410852 L1.42108547e-14,28.784 L0.000430968674,0.00053522388 L222.228,-2.84217094e-14 Z" id="Combined-Shape"></path> 113 + </g> 114 + </g> 115 + </svg> 116 + ) 117 + } 118 + 119 + 120 + export function WheyMadeBoubaKiki(props: SVGProps<SVGSVGElement>) { 121 + return ( 122 + <svg 123 + viewBox="0 0 1047 1047" 124 + version="1.1" 125 + xmlns="http://www.w3.org/2000/svg" 126 + {...props} 127 + > 128 + <title>Logotype-1.5Nonequal15nonequal</title> 129 + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> 130 + <g id="Logotype-1.5Nonequal" transform="translate(0.6169, 0.861)" fill="currentColor"> 131 + <path d="M700.664553,340.034383 C715.327713,340.002333 729.11121,347.025376 737.704071,358.906959 L799.154445,443.876074 C811.895531,461.493523 834.561151,468.85803 855.224183,462.094237 L954.882173,429.472402 C978.810344,421.639806 1004.55753,434.687847 1012.39013,458.616018 C1016.95178,472.551607 1014.5318,487.830732 1005.88708,499.674639 L944.065863,584.374318 C931.247885,601.935903 931.247885,625.767946 944.065863,643.32953 L1005.88708,728.02921 C1020.73054,748.365847 1016.27743,776.884946 995.940794,791.7284 C984.096887,800.373117 968.817762,802.793093 954.882173,798.231446 L855.224183,765.609612 C834.561151,758.845819 811.895531,766.210325 799.154445,783.827774 L737.704071,868.796889 C722.949658,889.198219 694.450298,893.775951 674.048968,879.021538 C662.167385,870.428678 655.144342,856.64518 655.176392,841.982021 L655.405588,737.120939 C655.425626,727.953131 652.946531,719.22295 648.520961,711.691065 C709.97811,670.185396 750.383113,599.880758 750.383113,520.139025 C750.383113,452.082396 720.952137,390.89974 674.123265,348.624137 C681.586594,343.239367 690.752706,340.056047 700.664553,340.034383 Z M390.074839,115.030765 L389.612789,326.427069 L392.214218,327.263752 C329.654126,368.59486 288.383113,439.547262 288.383113,520.139025 C288.383113,522.478677 288.417896,524.810206 288.486973,527.133121 L390.074839,667.594601 L389.612789,456.198297 L590.805403,391.312683 L392.214218,327.263752 C428.68754,303.167195 472.397099,289.139025 519.383113,289.139025 C578.904262,289.139025 633.167544,311.650627 674.123265,348.624137 C662.616835,356.926725 655.143026,370.456226 655.176392,385.721828 L655.405588,490.582909 C655.453109,512.324748 641.444985,531.605276 620.752583,538.278678 L520.952943,570.464556 C506.997546,574.965241 496.0589,585.903886 491.558216,599.859283 C483.830293,623.821465 496.990761,649.51137 520.952943,657.239293 L620.752583,689.425171 C632.719689,693.284622 642.451108,701.360771 648.520961,711.691065 C611.659126,736.598693 567.219158,751.139025 519.383113,751.139025 C394.144989,751.139025 292.184563,651.475345 288.486973,527.133121 L266.193017,496.299812 L65.2859657,562.064299 L189.915403,391.312683 L65.2859657,220.561067 L266.193017,286.325554 L390.074839,115.030765 Z" id="Combined-Shape"></path> 132 + </g> 133 + </g> 134 + </svg> 135 + ) 136 + } 137 + 138 + export function WheyMadeBoubaKikiLarge(props: SVGProps<SVGSVGElement>) { 139 + return ( 140 + <svg 141 + viewBox="0 100 1047 847" 142 + version="1.1" 143 + xmlns="http://www.w3.org/2000/svg" 144 + {...props} 145 + > 146 + <title>Logotype-Large1.5Nonequall15nonequal</title> 147 + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> 148 + <g id="Logotype-Large1.5Nonequal" transform="translate(0.6169, 0.861)" fill="currentColor"> 149 + <path d="M700.664553,340.034383 C715.327713,340.002333 729.11121,347.025376 737.704071,358.906959 L799.154445,443.876074 C811.895531,461.493523 834.561151,468.85803 855.224183,462.094237 L954.882173,429.472402 C978.810344,421.639806 1004.55753,434.687847 1012.39013,458.616018 C1016.95178,472.551607 1014.5318,487.830732 1005.88708,499.674639 L944.065863,584.374318 C931.247885,601.935903 931.247885,625.767946 944.065863,643.32953 L1005.88708,728.02921 C1020.73054,748.365847 1016.27743,776.884946 995.940794,791.7284 C984.096887,800.373117 968.817762,802.793093 954.882173,798.231446 L855.224183,765.609612 C834.561151,758.845819 811.895531,766.210325 799.154445,783.827774 L737.704071,868.796889 C722.949658,889.198219 694.450298,893.775951 674.048968,879.021538 C662.167385,870.428678 655.144342,856.64518 655.176392,841.982021 L655.405588,737.120939 C655.453109,715.379101 641.444985,696.098573 620.752583,689.425171 L520.952943,657.239293 C496.990761,649.51137 483.830293,623.821465 491.558216,599.859283 C496.0589,585.903886 506.997546,574.965241 520.952943,570.464556 L620.752583,538.278678 C641.444985,531.605276 655.453109,512.324748 655.405588,490.582909 L655.176392,385.721828 C655.121361,360.544379 675.487105,340.089413 700.664553,340.034383 Z M390.074839,115.030765 L389.612789,326.427069 L590.805403,391.312683 L389.612789,456.198297 L390.074839,667.594601 L266.193017,496.299812 L65.2859657,562.064299 L189.915403,391.312683 L65.2859657,220.561067 L266.193017,286.325554 L390.074839,115.030765 Z" id="Combined-Shape"></path> 150 + </g> 151 + </g> 152 + </svg> 153 + ) 154 + } 155 + 79 156 // defaultpfp 80 157 export function DefaultPFP(props: SVGProps<SVGSVGElement>) { 81 158 return (
+172 -86
src/components/UniversalPostRenderer.tsx
··· 21 21 import { useAuth } from "~/providers/UnifiedAuthProvider"; 22 22 import { renderSnack } from "~/routes/__root"; 23 23 //import { ModerationInner } from "~/routes/moderation"; 24 - import { 25 - FollowButton, 26 - Mutual, 27 - } from "~/routes/profile.$did"; 24 + import { FollowButton, Mutual } from "~/routes/profile.$did"; 28 25 import type { LightboxProps } from "~/routes/profile.$did/post.$rkey.image.$i"; 29 26 import type { ContentLabel } from "~/types/moderation"; 30 27 import { ··· 580 577 const { isLoading: authorModLoading, labels: authorLabels } = useModeration( 581 578 post.author.did, 582 579 ); 580 + const { isLoading: contentModLoading, labels: contentLabels } = useModeration( 581 + post.uri, 582 + ); 583 583 const hideAuthorLabels = authorLabels.filter( 584 - label => label.preference === 'hide' 584 + (label) => label.preference === "hide", 585 585 ); 586 586 const warnAuthorLabels = authorLabels.filter( 587 - label => label.preference === 'warn' 587 + (label) => label.preference === "warn", 588 588 ); 589 - 589 + const hideContentLabels = contentLabels.filter( 590 + (label) => label.preference === "hide", 591 + ); 592 + const warnContentLabels = contentLabels.filter( 593 + (label) => label.preference === "warn", 594 + ); 590 595 591 596 const parsed = new AtUri(post.uri); 592 597 const navigate = useNavigate(); ··· 671 676 const isMainItem = false; 672 677 const setMainItem = (any: any) => { }; 673 678 674 - if (hideAuthorLabels.length > 0 ) { 675 - return null 679 + const showContentWarning = warnContentLabels.length > 0; 680 + 681 + const [isOpen, setIsOpen] = useState(!showContentWarning); 682 + 683 + 684 + if (hideAuthorLabels.length > 0 || hideContentLabels.length > 0) { 685 + return null; 676 686 } 677 687 678 688 return ( ··· 917 927 </div> 918 928 </div> 919 929 {/* <ModerationInner subject={post.author.did} /> */} 920 - {authorModLoading ? 921 - ( 930 + {authorModLoading ? ( 922 931 <div className="flex flex-wrap flex-row gap-1 my-1"> 923 - <div 924 - className="text-xs bg-gray-100 dark:bg-gray-800 px-1 py-0.5 rounded-full flex flex-row items-center gap-1" 925 - > 926 - {/* <img 932 + <div className="text-xs bg-gray-100 dark:bg-gray-800 px-1 py-0.5 rounded-full flex flex-row items-center gap-1"> 933 + {/* <img 927 934 src={resolvedpfp || defaultpfp} 928 935 alt="avatar" 929 936 className={`rounded-full object-cover border border-gray-300 dark:border-gray-800 bg-gray-300 dark:bg-gray-600`} ··· 932 939 height: 12, 933 940 }} 934 941 /> */} 935 - <span className="font-medium">loading badges...</span> 936 - </div> 942 + <span className="font-medium">loading badges...</span> 937 943 </div> 938 - ) 939 - : 940 - ( 941 - <div className="flex flex-wrap flex-row gap-1 my-1"> 942 - {warnAuthorLabels.map((label, index) => ( 943 - <SmallAuthorLabelBadge label={label} key={label.cts + label.sourceDid + label.val} /> 944 - ))} 945 - </div> 946 - ) 947 - } 944 + </div> 945 + ) : ( 946 + <div className="flex flex-wrap flex-row gap-1 my-1"> 947 + {warnAuthorLabels.map((label, index) => ( 948 + <SmallAuthorLabelBadge 949 + label={label} 950 + key={label.cts + label.sourceDid + label.val} 951 + /> 952 + ))} 953 + </div> 954 + )} 948 955 {!!feedviewpostreplyhandle && ( 949 956 <div 950 957 style={{ ··· 968 975 </div> 969 976 )} 970 977 {/* <ModerationInner subject={post.uri} /> */} 971 - <div 972 - style={{ 973 - fontSize: 16, 974 - marginBottom: !post.embed || concise ? 0 : 8, 975 - whiteSpace: "pre-wrap", 976 - textAlign: "left", 977 - overflowWrap: "anywhere", 978 - wordBreak: "break-word", 979 - ...(concise && { 980 - display: "-webkit-box", 981 - WebkitBoxOrient: "vertical", 982 - WebkitLineClamp: 2, 983 - overflow: "hidden", 984 - }), 985 - }} 986 - className="text-gray-900 dark:text-gray-100" 987 - > 988 - {fedi ? ( 989 - <> 990 - <span 991 - className="dangerousFediContent" 992 - dangerouslySetInnerHTML={{ 993 - __html: DOMPurify.sanitize(fedi), 994 - }} 995 - /> 996 - </> 997 - ) : ( 978 + {showContentWarning && ( 979 + <ContentWarning 980 + labels={warnContentLabels} 981 + isOpen={isOpen} 982 + onPress={(e) => { 983 + e.stopPropagation(); 984 + setIsOpen(!isOpen) 985 + }} 986 + /> 987 + )} 988 + {isOpen && (<> 989 + <div 990 + style={{ 991 + fontSize: 16, 992 + marginBottom: !post.embed || concise ? 0 : 8, 993 + whiteSpace: "pre-wrap", 994 + textAlign: "left", 995 + overflowWrap: "anywhere", 996 + wordBreak: "break-word", 997 + ...(concise && { 998 + display: "-webkit-box", 999 + WebkitBoxOrient: "vertical", 1000 + WebkitLineClamp: 2, 1001 + overflow: "hidden", 1002 + }), 1003 + }} 1004 + className="text-gray-900 dark:text-gray-100" 1005 + > 1006 + {fedi ? ( 1007 + <> 1008 + <span 1009 + className="dangerousFediContent" 1010 + dangerouslySetInnerHTML={{ 1011 + __html: DOMPurify.sanitize(fedi), 1012 + }} 1013 + /> 1014 + </> 1015 + ) : ( 1016 + <> 1017 + {renderTextWithFacets({ 1018 + text: (post.record as { text?: string }).text ?? "", 1019 + facets: (post.record.facets as Facet[]) ?? [], 1020 + navigate: navigate, 1021 + })} 1022 + </> 1023 + )} 1024 + </div> 1025 + {post.embed && depth < 1 && !concise ? ( 1026 + <PostEmbeds 1027 + embed={post.embed} 1028 + viewContext={PostEmbedViewContext.Feed} 1029 + salt={salt} 1030 + navigate={navigate} 1031 + postid={{ did: post.author.did, rkey: parsed.rkey }} 1032 + nopics={nopics} 1033 + lightboxCallback={lightboxCallback} 1034 + constellationLinks={constellationLinks} 1035 + /> 1036 + ) : null} 1037 + {post.embed && depth > 0 && ( 998 1038 <> 999 - {renderTextWithFacets({ 1000 - text: (post.record as { text?: string }).text ?? "", 1001 - facets: (post.record.facets as Facet[]) ?? [], 1002 - navigate: navigate, 1003 - })} 1039 + <div className="border-gray-300 dark:border-gray-800 p-3 rounded-xl border italic text-gray-400 text-[14px]"> 1040 + (there is an embed here thats too deep to render) 1041 + </div> 1004 1042 </> 1005 1043 )} 1006 - </div> 1007 - {post.embed && depth < 1 && !concise ? ( 1008 - <PostEmbeds 1009 - embed={post.embed} 1010 - viewContext={PostEmbedViewContext.Feed} 1011 - salt={salt} 1012 - navigate={navigate} 1013 - postid={{ did: post.author.did, rkey: parsed.rkey }} 1014 - nopics={nopics} 1015 - lightboxCallback={lightboxCallback} 1016 - constellationLinks={constellationLinks} 1017 - /> 1018 - ) : null} 1019 - {post.embed && depth > 0 && ( 1020 - <> 1021 - <div className="border-gray-300 dark:border-gray-800 p-3 rounded-xl border italic text-gray-400 text-[14px]"> 1022 - (there is an embed here thats too deep to render) 1023 - </div> 1024 - </> 1025 - )} 1044 + </>)} 1026 1045 <div 1027 1046 style={{ 1028 1047 paddingTop: post.embed && !concise && depth < 1 ? 4 : 0, ··· 1078 1097 }} 1079 1098 aria-label="Repost or quote post" 1080 1099 > 1081 - {hasRetweeted ? <IconMdiRepeat color="#5CEFAA" /> : <IconMdiRepeat />} 1100 + {hasRetweeted ? ( 1101 + <IconMdiRepeat color="#5CEFAA" /> 1102 + ) : ( 1103 + <IconMdiRepeat /> 1104 + )} 1082 1105 {post.repostCount ?? 0} 1083 1106 </div> 1084 1107 </DropdownMenu.Trigger> ··· 1123 1146 ...(liked ? { color: "#EC4899" } : {}), 1124 1147 }} 1125 1148 > 1126 - {liked ? <IconMdiCardsHeart /> : <IconMdiCardsHeartOutline />} 1149 + {liked ? ( 1150 + <IconMdiCardsHeart /> 1151 + ) : ( 1152 + <IconMdiCardsHeartOutline /> 1153 + )} 1127 1154 {(post.likeCount || 0) + (liked ? 1 : 0)} 1128 1155 </HitSlopButton> 1129 1156 <div style={{ display: "flex", gap: 8 }}> ··· 1186 1213 FeedEmbedRecordWithMedia = "FeedEmbedRecordWithMedia", 1187 1214 } 1188 1215 1216 + export function ContentWarning({ 1217 + labels, 1218 + isOpen, 1219 + onPress, 1220 + }: { 1221 + labels: ContentLabel[]; 1222 + isOpen: boolean; 1223 + onPress: React.MouseEventHandler<HTMLDivElement>; 1224 + }) { 1225 + const { getLabelInfo } = useLabelInfo(); 1189 1226 1190 - export function SmallAuthorLabelBadge({ label, large }: { label: ContentLabel, large?: boolean }) { 1227 + // Pre-calculate text for cleaner JSX 1228 + const labelText = labels 1229 + .map((label) => getLabelInfo(label.sourceDid, label.val).name) 1230 + .join(", "); 1231 + 1232 + return ( 1233 + <div className="mb-2 w-full select-none" onClick={onPress}> 1234 + <div 1235 + className={` 1236 + group flex items-center justify-between 1237 + w-full px-4 py-3 1238 + rounded-full 1239 + border border-gray-200 dark:border-gray-700 1240 + bg-gray-100 dark:bg-gray-800 1241 + cursor-pointer 1242 + transition-all duration-200 ease-out 1243 + hover:bg-gray-200 dark:hover:bg-gray-700 1244 + `} 1245 + > 1246 + <div className="flex items-center gap-3 overflow-hidden"> 1247 + {/* Icon Container */} 1248 + <div className="flex items-center justify-center text-gray-500 dark:text-gray-400"> 1249 + <IconMdiWarning className="text-xl" /> 1250 + </div> 1251 + 1252 + {/* Label Text */} 1253 + <span className="text-sm font-semibold text-gray-900 dark:text-gray-100 truncate"> 1254 + {labelText} 1255 + </span> 1256 + </div> 1257 + 1258 + {/* Chevron */} 1259 + <div className="flex items-center justify-center text-gray-500 dark:text-gray-400 pl-2 gap-2 text-sm"> 1260 + {isOpen ? "hide" : "show"} 1261 + <IconMdiChevronDown 1262 + className={`text-xl transition-transform duration-300 ease-[cubic-bezier(0.2,0,0,1)] ${isOpen ? "rotate-180" : "" 1263 + }`} 1264 + /> 1265 + </div> 1266 + </div> 1267 + </div> 1268 + ); 1269 + } 1270 + 1271 + export function SmallAuthorLabelBadge({ 1272 + label, 1273 + large, 1274 + }: { 1275 + label: ContentLabel; 1276 + large?: boolean; 1277 + }) { 1191 1278 /* 1192 1279 -{" "} 1193 1280 {label.preference} (from {label.sourceDid}) ··· 1197 1284 1198 1285 const [imgcdn] = useAtom(imgCDNAtom); 1199 1286 1200 - 1201 1287 const { data: opProfile } = useQueryProfile( 1202 1288 `at://${label.sourceDid}/app.bsky.actor.profile/self`, 1203 1289 ); 1204 1290 1205 - const resolvedpfp = getAvatarUrl(opProfile, label.sourceDid, imgcdn) 1291 + const resolvedpfp = getAvatarUrl(opProfile, label.sourceDid, imgcdn); 1206 1292 1207 1293 return ( 1208 1294 <div ··· 1219 1305 /> 1220 1306 <span className="font-medium">{info.name || label.val}</span> 1221 1307 </div> 1222 - ) 1223 - } 1308 + ); 1309 + }