atmosphere explorer pds.ls
tool typescript atproto
434
fork

Configure Feed

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

truncate large fields in record preview

Juliet b931a75a 057f5a4b

+70 -55
+69 -55
src/components/json.tsx
··· 1 1 import { isCid, isDid, isNsid, isResourceUri, Nsid } from "@atcute/lexicons/syntax"; 2 2 import { A, useNavigate, useParams } from "@solidjs/router"; 3 - import { createEffect, createSignal, ErrorBoundary, For, on, Show } from "solid-js"; 3 + import { 4 + createContext, 5 + createEffect, 6 + createSignal, 7 + ErrorBoundary, 8 + For, 9 + on, 10 + Show, 11 + useContext, 12 + } from "solid-js"; 4 13 import { resolveLexiconAuthority } from "../utils/api"; 5 14 import { hideMedia } from "../views/settings"; 6 15 import { pds } from "./navbar"; 7 16 import { addNotification, removeNotification } from "./notification"; 8 17 import VideoPlayer from "./video-player"; 9 18 19 + interface JSONContext { 20 + repo: string; 21 + truncate?: boolean; 22 + parentIsBlob?: boolean; 23 + } 24 + 25 + const JSONCtx = createContext<JSONContext>(); 26 + const useJSONCtx = () => useContext(JSONCtx)!; 27 + 10 28 interface AtBlob { 11 29 $type: string; 12 30 ref: { $link: string }; 13 31 mimeType: string; 14 32 } 15 33 16 - const JSONString = (props: { 17 - data: string; 18 - isType?: boolean; 19 - isLink?: boolean; 20 - parentIsBlob?: boolean; 21 - }) => { 34 + const isURL = 35 + URL.canParse ?? 36 + ((url, base) => { 37 + try { 38 + new URL(url, base); 39 + return true; 40 + } catch { 41 + return false; 42 + } 43 + }); 44 + 45 + const JSONString = (props: { data: string; isType?: boolean; isLink?: boolean }) => { 46 + const ctx = useJSONCtx(); 22 47 const navigate = useNavigate(); 23 48 const params = useParams(); 24 49 25 - const isURL = 26 - URL.canParse ?? 27 - ((url, base) => { 28 - try { 29 - new URL(url, base); 30 - return true; 31 - } catch { 32 - return false; 33 - } 34 - }); 35 - 36 50 const handleClick = async (lex: string) => { 37 51 try { 38 52 const [nsid, anchor] = lex.split("#"); ··· 49 63 setTimeout(() => removeNotification(id), 5000); 50 64 } 51 65 }; 66 + 67 + const MAX_LENGTH = 200; 68 + const isTruncated = () => ctx.truncate && props.data.length > MAX_LENGTH; 69 + const displayData = () => (isTruncated() ? props.data.slice(0, MAX_LENGTH) : props.data); 70 + const remainingChars = () => props.data.length - MAX_LENGTH; 52 71 53 72 return ( 54 73 <span> 55 74 " 56 - <For each={props.data.split(/(\s)/)}> 75 + <For each={displayData().split(/(\s)/)}> 57 76 {(part) => ( 58 77 <> 59 78 {isResourceUri(part) ? ··· 72 91 > 73 92 {part} 74 93 </button> 75 - : isCid(part) && props.isLink && props.parentIsBlob && params.repo ? 94 + : isCid(part) && props.isLink && ctx.parentIsBlob && params.repo ? 76 95 <A 77 96 class="text-blue-400 hover:underline active:underline" 78 97 rel="noopener" ··· 93 112 </> 94 113 )} 95 114 </For> 115 + <Show when={isTruncated()}> 116 + <span>…</span> 117 + </Show> 96 118 " 119 + <Show when={isTruncated()}> 120 + <span class="ml-1 text-neutral-500 dark:text-neutral-400"> 121 + (+{remainingChars().toLocaleString()}) 122 + </span> 123 + </Show> 97 124 </span> 98 125 ); 99 126 }; ··· 110 137 return <span>null</span>; 111 138 }; 112 139 113 - const JSONObject = (props: { 114 - data: { [x: string]: JSONType }; 115 - repo: string; 116 - parentIsBlob?: boolean; 117 - }) => { 140 + const JSONObject = (props: { data: { [x: string]: JSONType } }) => { 141 + const ctx = useJSONCtx(); 118 142 const params = useParams(); 119 143 const [hide, setHide] = createSignal( 120 144 localStorage.hideMedia === "true" || params.rkey === undefined, ··· 136 160 ); 137 161 138 162 const isBlob = props.data.$type === "blob"; 139 - const isBlobContext = isBlob || props.parentIsBlob; 163 + const isBlobContext = isBlob || ctx.parentIsBlob; 140 164 141 165 const Obj = ({ key, value }: { key: string; value: JSONType }) => { 142 166 const [show, setShow] = createSignal(true); ··· 172 196 "invisible h-0": !show(), 173 197 }} 174 198 > 175 - <JSONValue 176 - data={value} 177 - repo={props.repo} 178 - isType={key === "$type"} 179 - isLink={key === "$link"} 180 - parentIsBlob={isBlobContext} 181 - /> 199 + <JSONCtx.Provider value={{ ...ctx, parentIsBlob: isBlobContext }}> 200 + <JSONValueInner data={value} isType={key === "$type"} isLink={key === "$link"} /> 201 + </JSONCtx.Provider> 182 202 </span> 183 203 </span> 184 204 ); ··· 200 220 <Show when={blob.mimeType.startsWith("image/")}> 201 221 <img 202 222 class="h-auto max-h-48 max-w-48 object-contain sm:max-h-64 sm:max-w-64" 203 - src={`https://${pds()}/xrpc/com.atproto.sync.getBlob?did=${props.repo}&cid=${blob.ref.$link}`} 223 + src={`https://${pds()}/xrpc/com.atproto.sync.getBlob?did=${ctx.repo}&cid=${blob.ref.$link}`} 204 224 onLoad={() => setMediaLoaded(true)} 205 225 /> 206 226 </Show> 207 227 <Show when={blob.mimeType === "video/mp4"}> 208 228 <ErrorBoundary fallback={() => <span>Failed to load video</span>}> 209 229 <VideoPlayer 210 - did={props.repo} 230 + did={ctx.repo} 211 231 cid={blob.ref.$link} 212 232 onLoad={() => setMediaLoaded(true)} 213 233 /> ··· 241 261 return rawObj; 242 262 }; 243 263 244 - const JSONArray = (props: { data: JSONType[]; repo: string; parentIsBlob?: boolean }) => { 264 + const JSONArray = (props: { data: JSONType[] }) => { 245 265 return ( 246 266 <For each={props.data}> 247 267 {(value, index) => ( ··· 252 272 }} 253 273 > 254 274 <span class="ml-[1ch] w-full"> 255 - <JSONValue data={value} repo={props.repo} parentIsBlob={props.parentIsBlob} /> 275 + <JSONValueInner data={value} /> 256 276 </span> 257 277 </span> 258 278 )} ··· 260 280 ); 261 281 }; 262 282 263 - export const JSONValue = (props: { 264 - data: JSONType; 265 - repo: string; 266 - isType?: boolean; 267 - isLink?: boolean; 268 - parentIsBlob?: boolean; 269 - }) => { 283 + const JSONValueInner = (props: { data: JSONType; isType?: boolean; isLink?: boolean }) => { 270 284 const data = props.data; 271 285 if (typeof data === "string") 272 - return ( 273 - <JSONString 274 - data={data} 275 - isType={props.isType} 276 - isLink={props.isLink} 277 - parentIsBlob={props.parentIsBlob} 278 - /> 279 - ); 286 + return <JSONString data={data} isType={props.isType} isLink={props.isLink} />; 280 287 if (typeof data === "number") return <JSONNumber data={data} />; 281 288 if (typeof data === "boolean") return <JSONBoolean data={data} />; 282 289 if (data === null) return <JSONNull />; 283 - if (Array.isArray(data)) 284 - return <JSONArray data={data} repo={props.repo} parentIsBlob={props.parentIsBlob} />; 285 - return <JSONObject data={data} repo={props.repo} parentIsBlob={props.parentIsBlob} />; 290 + if (Array.isArray(data)) return <JSONArray data={data} />; 291 + return <JSONObject data={data} />; 292 + }; 293 + 294 + export const JSONValue = (props: { data: JSONType; repo: string; truncate?: boolean }) => { 295 + return ( 296 + <JSONCtx.Provider value={{ repo: props.repo, truncate: props.truncate }}> 297 + <JSONValueInner data={props.data} /> 298 + </JSONCtx.Provider> 299 + ); 286 300 }; 287 301 288 302 export type JSONType = string | number | boolean | null | { [x: string]: JSONType } | JSONType[];
+1
src/views/collection.tsx
··· 67 67 <JSONValue 68 68 data={props.record.record.value as JSONType} 69 69 repo={props.record.record.uri.split("/")[2]} 70 + truncate 70 71 /> 71 72 </span> 72 73 </Show>