JavaScript-optional public web frontend for Bluesky anartia.kelinci.net
sveltekit atcute bluesky typescript svelte
7
fork

Configure Feed

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

refactor: at-uri validation changes

Mary 8a17ad6d 6ba5bea4

+167 -131
+2 -2
src/lib/components/embeds/embeds.svelte
··· 2 2 import type { AppBskyFeedDefs } from '@atcute/client/lexicons'; 3 3 4 4 import { findLabel, FlagsBlurMedia } from '$lib/moderation'; 5 - import { parseAtUri } from '$lib/types/at-uri'; 5 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 6 6 import { unwrapEmbedView, type MediaEmbed, type RecordEmbed } from '$lib/utils/bluesky/embeds'; 7 7 import { collectionToLabel } from '$lib/utils/bluesky/records'; 8 8 ··· 67 67 {:else if record.$type === 'app.bsky.graph.defs#starterPackViewBasic'} 68 68 <StarterpackEmbed embed={record} {large} /> 69 69 {:else} 70 - {@const uri = parseAtUri(record.uri)} 70 + {@const uri = parseAddressedAtUri(record.uri)} 71 71 72 72 {#if uri.collection === 'app.bsky.feed.post' && (record.$type === 'app.bsky.embed.record#viewBlocked' || record.$type === 'app.bsky.embed.record#viewDetached')} 73 73 <QuoteBlockedEmbed embed={record} {uri} />
+2 -2
src/lib/components/embeds/feed-embed.svelte
··· 4 4 import { base } from '$app/paths'; 5 5 6 6 import { findLabel, FlagsBlurMedia } from '$lib/moderation'; 7 - import { parseAtUri } from '$lib/types/at-uri'; 7 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 8 8 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 9 9 import { trimRichText } from '$lib/utils/bluesky/richtext'; 10 10 import { truncateRight } from '$lib/utils/strings'; ··· 22 22 const blurAvi = $derived(!!findLabel(feed.labels, creator.did, FlagsBlurMedia)); 23 23 </script> 24 24 25 - <a href="{base}/{creator.did}/feeds/{parseAtUri(feed.uri).rkey}" class="feed-embed"> 25 + <a href="{base}/{creator.did}/feeds/{parseAddressedAtUri(feed.uri).rkey}" class="feed-embed"> 26 26 <div class="main"> 27 27 <Avatar type="generator" src={feed.avatar} blur={blurAvi} /> 28 28
+2 -2
src/lib/components/embeds/list-embed.svelte
··· 4 4 import { base } from '$app/paths'; 5 5 6 6 import { findLabel, FlagsBlurMedia } from '$lib/moderation'; 7 - import { parseAtUri } from '$lib/types/at-uri'; 7 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 8 8 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 9 9 import { purposeToLabel } from '$lib/utils/bluesky/lists'; 10 10 import { trimRichText } from '$lib/utils/bluesky/richtext'; ··· 23 23 const blurAvi = $derived(!!findLabel(list.labels, creator.did, FlagsBlurMedia)); 24 24 </script> 25 25 26 - <a href="{base}/{creator.did}/lists/{parseAtUri(list.uri).rkey}" class="list-embed"> 26 + <a href="{base}/{creator.did}/lists/{parseAddressedAtUri(list.uri).rkey}" class="list-embed"> 27 27 <div class="main"> 28 28 <Avatar type="list" src={list.avatar} blur={blurAvi} /> 29 29
+2 -2
src/lib/components/embeds/quote-embed.svelte
··· 4 4 import { base } from '$app/paths'; 5 5 6 6 import { findLabel, FlagsBlurContent, FlagsBlurMedia } from '$lib/moderation'; 7 - import { parseAtUri } from '$lib/types/at-uri'; 7 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 8 8 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 9 9 import { unwrapMediaEmbedView } from '$lib/utils/bluesky/embeds'; 10 10 import { trimRichText } from '$lib/utils/bluesky/richtext'; ··· 39 39 </script> 40 40 41 41 <ContentHider blur={blurContent}> 42 - <a href="{base}/{author.did}/{parseAtUri(quote.uri).rkey}#main" class="quote-embed"> 42 + <a href="{base}/{author.did}/{parseAddressedAtUri(quote.uri).rkey}#main" class="quote-embed"> 43 43 <div class="meta"> 44 44 <Avatar profile={author} src={author.avatar} size="xs" blur={blurAvi} /> 45 45
+2 -2
src/lib/components/embeds/starterpack-embed.svelte
··· 3 3 4 4 import { base } from '$app/paths'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 8 8 import { trimRichText } from '$lib/utils/bluesky/richtext'; 9 9 import { truncateRight } from '$lib/utils/strings'; ··· 21 21 22 22 const creator = $derived(pack.creator); 23 23 24 - const rkey = $derived(parseAtUri(pack.uri).rkey); 24 + const rkey = $derived(parseAddressedAtUri(pack.uri).rkey); 25 25 </script> 26 26 27 27 <a href="{base}/{creator.did}/packs/{rkey}" class="starterpack-embed">
+2 -2
src/lib/components/feeds/feed-item.svelte
··· 3 3 4 4 import { base } from '$app/paths'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 8 8 import { trimRichText } from '$lib/utils/bluesky/richtext'; 9 9 import { formatLongNumber } from '$lib/utils/intl/number'; ··· 19 19 20 20 const creator = $derived(feed.creator); 21 21 22 - const href = $derived(`${base}/${creator.did}/feeds/${parseAtUri(feed.uri).rkey}`); 22 + const href = $derived(`${base}/${creator.did}/feeds/${parseAddressedAtUri(feed.uri).rkey}`); 23 23 </script> 24 24 25 25 <div class="feed-item">
+2 -2
src/lib/components/lists/list-item.svelte
··· 3 3 4 4 import { base } from '$app/paths'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 8 8 import { purposeToLabel } from '$lib/utils/bluesky/lists'; 9 9 import { trimRichText } from '$lib/utils/bluesky/richtext'; ··· 19 19 20 20 const creator = $derived(list.creator); 21 21 22 - const href = $derived(`${base}/${creator.did}/lists/${parseAtUri(list.uri).rkey}`); 22 + const href = $derived(`${base}/${creator.did}/lists/${parseAddressedAtUri(list.uri).rkey}`); 23 23 </script> 24 24 25 25 <div class="list-item">
+2 -2
src/lib/components/starterpacks/starterpack-item.svelte
··· 3 3 4 4 import { base } from '$app/paths'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 8 8 import { trimRichText } from '$lib/utils/bluesky/richtext'; 9 9 import { truncateMiddle } from '$lib/utils/strings'; ··· 19 19 const creator = $derived(pack.creator); 20 20 21 21 const record = $derived(pack.record as AppBskyGraphStarterpack.Record); 22 - const href = $derived(`${base}/${creator.did}/packs/${parseAtUri(pack.uri).rkey}`); 22 + const href = $derived(`${base}/${creator.did}/packs/${parseAddressedAtUri(pack.uri).rkey}`); 23 23 </script> 24 24 25 25 <div class="starterpack-item">
+2 -2
src/lib/components/timeline/post-feed-item.svelte
··· 5 5 6 6 import type { UiTimelineItem } from '$lib/models/timeline'; 7 7 import { findLabel, FlagsBlurContent, FlagsBlurMedia } from '$lib/moderation'; 8 - import { parseAtUri } from '$lib/types/at-uri'; 8 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 9 9 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 10 10 11 11 import ArrowsRepeatRightLeftOutlined from '$lib/components/central-icons/arrows-repeat-right-left-outlined.svelte'; ··· 30 30 const authorUrl = $derived(`${base}/${author.did}`); 31 31 32 32 const record = $derived(post.record as AppBskyFeedPost.Record); 33 - const postUrl = $derived(`${base}/${author.did}/${parseAtUri(post.uri).rkey}#main`); 33 + const postUrl = $derived(`${base}/${author.did}/${parseAddressedAtUri(post.uri).rkey}#main`); 34 34 35 35 const isAviBlurred = $derived(!!findLabel(author.labels, author.did, FlagsBlurMedia)); 36 36 const blur = $derived(findLabel(post.labels, author.did, FlagsBlurContent));
+31 -25
src/lib/redirector.ts
··· 1 1 import { base } from '$app/paths'; 2 - import { ATURI_RE } from './types/at-uri'; 2 + import { parsePartialAtUri, type PartialAtUri } from './types/at-uri'; 3 3 4 4 import { isDid, isHandle } from './types/identity'; 5 5 import { isRecordKey, isTid } from './types/rkey'; 6 6 import { 7 7 BSKY_FEED_LINK_RE, 8 + BSKY_GO_SHORTLINK_RE, 9 + BSKY_HASHTAG_LINK_RE, 8 10 BSKY_LIST_LINK_RE, 9 11 BSKY_POST_LINK_RE, 10 12 BSKY_PROFILE_LINK_RE, 11 - BSKY_STARTERPACK_LINK_RE, 12 - BSKY_GO_SHORTLINK_RE, 13 13 BSKY_SEARCH_LINK_RE, 14 - BSKY_HASHTAG_LINK_RE, 14 + BSKY_STARTERPACK_LINK_RE, 15 15 } from './utils/bluesky/urls'; 16 16 import { safeUrlParse } from './utils/url'; 17 17 ··· 183 183 }; 184 184 185 185 export const redirectAtUri = (raw: string): string | null | undefined => { 186 - const match = ATURI_RE.exec(raw); 187 - if (!match) { 186 + let uri: PartialAtUri; 187 + try { 188 + uri = parsePartialAtUri(raw); 189 + } catch (e) { 188 190 return; 189 191 } 190 192 191 - const [, repo, collection, rkey] = match; 192 - 193 - switch (collection) { 194 - case 'app.bsky.actor.profile': { 195 - return `${base}/${repo}`; 196 - } 197 - case 'app.bsky.feed.post': { 198 - if (!isTid(rkey)) { 199 - return null; 193 + if (uri.rkey) { 194 + switch (uri.collection) { 195 + case 'app.bsky.actor.profile': { 196 + return `${base}/${uri.repo}`; 200 197 } 198 + case 'app.bsky.feed.post': { 199 + if (!isTid(uri.rkey)) { 200 + return null; 201 + } 201 202 202 - return `${base}/${repo}/${rkey}#main`; 203 - } 204 - case 'app.bsky.feed.generator': { 205 - return `${base}/${repo}/feeds/${rkey}`; 206 - } 207 - case 'app.bsky.graph.list': { 208 - return `${base}/${repo}/lists/${rkey}`; 209 - } 210 - case 'app.bsky.graph.starterpack': { 211 - return `${base}/${repo}/packs/${rkey}`; 203 + return `${base}/${uri.repo}/${uri.rkey}#main`; 204 + } 205 + case 'app.bsky.feed.generator': { 206 + return `${base}/${uri.repo}/feeds/${uri.rkey}`; 207 + } 208 + case 'app.bsky.graph.list': { 209 + return `${base}/${uri.repo}/lists/${uri.rkey}`; 210 + } 211 + case 'app.bsky.graph.starterpack': { 212 + return `${base}/${uri.repo}/packs/${uri.rkey}`; 213 + } 212 214 } 215 + } 216 + 217 + if (uri.collection === undefined) { 218 + return `${base}/${uri.repo}`; 213 219 } 214 220 215 221 return null;
+3 -3
src/lib/rss.ts
··· 4 4 import { PUBLIC_APP_URL } from '$env/static/public'; 5 5 6 6 import { findLabel, FlagsBlurMedia } from './moderation'; 7 - import { parseAtUri } from './types/at-uri'; 7 + import { parseAddressedAtUri } from './types/at-uri'; 8 8 import { getQuoteEmbedView, unwrapEmbedView } from './utils/bluesky/embeds'; 9 9 import { assertNever } from './utils/invariant'; 10 10 import type { UnwrapArray } from './utils/types'; ··· 197 197 const author = quote.author; 198 198 const record = quote.value as AppBskyFeedPost.Record; 199 199 200 - const postUrl = `${PUBLIC_APP_URL}/${author.did}/${parseAtUri(quote.uri).rkey}`; 200 + const postUrl = `${PUBLIC_APP_URL}/${author.did}/${parseAddressedAtUri(quote.uri).rkey}`; 201 201 202 202 html += `<blockquote>`; 203 203 html += `<b><a href="${escapeAttribute(postUrl)}">`; ··· 225 225 226 226 return { 227 227 id: `${post.uri}|${post.cid}`, 228 - url: `${PUBLIC_APP_URL}/${author.did}/${parseAtUri(post.uri).rkey}`, 228 + url: `${PUBLIC_APP_URL}/${author.did}/${parseAddressedAtUri(post.uri).rkey}`, 229 229 date: new Date(post.indexedAt), 230 230 description: { html }, 231 231 images:
+44 -16
src/lib/types/at-uri.ts
··· 2 2 3 3 import { assert } from '$lib/utils/invariant'; 4 4 5 - import type { Did, Handle } from './identity'; 6 - import type { Nsid } from './nsid'; 5 + import { isDid, isHandle, type Did, type Handle } from './identity'; 6 + import { isNsid, type Nsid } from './nsid'; 7 + import { isRecordKey, type RecordKey } from './rkey'; 7 8 8 - export type AtUri = `at://${Did | Handle}/${Nsid}/${string}`; 9 + export type AtUri = `at://${Did | Handle}/${Nsid}/${RecordKey}`; 9 10 10 - export const ATURI_RE = 11 - /^at:\/\/(did:[a-z]+:[a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-]|(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z][a-zA-Z0-9-]{0,61}[a-zA-Z])\/([a-zA-Z0-9-.]+)\/((?!\.{1,2}$)[a-zA-Z0-9_~.:-]{1,512})(?:#(\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/; 11 + const ATURI_RE = 12 + /^at:\/\/([a-zA-Z0-9._:%-]+)(?:\/([a-zA-Z0-9-.]+)(?:\/([a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(?:#(\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/; 12 13 13 - export interface ParsedAtUri { 14 - repo: string; 15 - collection: string; 16 - rkey: string; 14 + export type AddressedAtUri = { 15 + repo: Did; 16 + collection: Nsid; 17 + rkey: RecordKey; 17 18 fragment: string | undefined; 18 - } 19 + }; 19 20 20 - export const parseAtUri = (str: string): ParsedAtUri => { 21 + export const parseAddressedAtUri = (str: string): AddressedAtUri => { 21 22 const match = ATURI_RE.exec(str); 22 - assert(match !== null, `failed to parse at-uri for ${str}`); 23 + assert(match !== null, `invalid addressed-at-uri: ${str}`); 24 + 25 + const [, r, c, k, f] = match; 26 + assert(isDid(r), `invalid repo in addressed-at-uri: ${r}`); 27 + assert(isNsid(c), `invalid collection in addressed-at-uri: ${c}`); 28 + assert(isRecordKey(k), `invalid rkey in addressed-at-uri: ${k}`); 23 29 24 30 return { 25 - repo: match[1] as Did, 26 - collection: match[2], 27 - rkey: match[3], 28 - fragment: match[4], 31 + repo: r, 32 + collection: c, 33 + rkey: k, 34 + fragment: f, 35 + }; 36 + }; 37 + 38 + export type PartialAtUri = 39 + | { repo: Did | Handle; collection: undefined; rkey: undefined; fragment: string | undefined } 40 + | { repo: Did | Handle; collection: Nsid; rkey: undefined; fragment: string | undefined } 41 + | { repo: Did | Handle; collection: Nsid; rkey: RecordKey; fragment: string | undefined }; 42 + 43 + export const parsePartialAtUri = (str: string): PartialAtUri => { 44 + const match = ATURI_RE.exec(str); 45 + assert(match !== null, `invalid partial-at-uri: ${str}`); 46 + 47 + const [, r, c, k, f] = match; 48 + assert(isDid(r) || isHandle(r), `invalid repo in partial-at-uri: ${r}`); 49 + assert(c === undefined || isNsid(c), `invalid collection in partial-at-uri: ${c}`); 50 + assert(k === undefined || isRecordKey(k), `invalid rkey in partial-at-uri: ${k}`); 51 + 52 + return { 53 + repo: r, 54 + collection: c, 55 + rkey: k, 56 + fragment: f, 29 57 }; 30 58 }; 31 59
+13 -13
src/lib/types/identity.ts
··· 1 1 export type Handle = `${string}.${string}`; 2 2 3 3 const HANDLE_RE = 4 - /^(?=.{4,253}$)(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+([a-zA-Z][a-zA-Z0-9-]{0,61}[a-zA-Z])$/; 4 + /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+([a-zA-Z][a-zA-Z0-9-]{0,61}[a-zA-Z])$/; 5 5 6 - export const isHandle = (input: string): input is Handle => { 7 - return input.length >= 3 && input.length <= 253 && HANDLE_RE.test(input); 6 + export const isHandle = (input: unknown): input is Handle => { 7 + return typeof input === 'string' && input.length >= 3 && input.length <= 253 && HANDLE_RE.test(input); 8 8 }; 9 9 10 10 export type Did<TMethod extends string = string> = `did:${TMethod}:${string}`; 11 11 export type AtprotoDid = Did<'plc' | 'web'>; 12 12 13 - export const DID_RE = /^(?=.{7,2048}$)did:([a-z]+):([a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-])$/; 13 + const DID_RE = /^did:([a-z]+):([a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-])$/; 14 14 15 - export const isDid = (input: string): input is Did => { 16 - return input.length >= 7 && input.length <= 2048 && DID_RE.test(input); 15 + export const isDid = (input: unknown): input is Did => { 16 + return typeof input === 'string' && input.length >= 7 && input.length <= 2048 && DID_RE.test(input); 17 17 }; 18 18 19 - export const ATPROTO_WEB_DID_RE = 19 + const ATPROTO_WEB_DID_RE = 20 20 /^did:web:([a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(?:\.[a-zA-Z]{2,})|localhost(?:%3[aA]\d+)?)$/; 21 21 22 - export const isAtprotoWebDid = (input: string): input is Did<'web'> => { 23 - return input.length >= 12 && ATPROTO_WEB_DID_RE.test(input); 22 + export const isAtprotoWebDid = (input: unknown): input is Did<'web'> => { 23 + return typeof input === 'string' && input.length >= 12 && ATPROTO_WEB_DID_RE.test(input); 24 24 }; 25 25 26 - export const PLC_DID_RE = /^did:plc:([a-z2-7]{24})$/; 26 + const PLC_DID_RE = /^did:plc:([a-z2-7]{24})$/; 27 27 28 - export const isPlcDid = (input: string): input is Did<'plc'> => { 29 - return input.length === 32 && PLC_DID_RE.test(input); 28 + export const isPlcDid = (input: unknown): input is Did<'plc'> => { 29 + return typeof input === 'string' && input.length === 32 && PLC_DID_RE.test(input); 30 30 }; 31 31 32 - export const isAtprotoDid = (input: string): input is AtprotoDid => { 32 + export const isAtprotoDid = (input: unknown): input is AtprotoDid => { 33 33 return isPlcDid(input) || isAtprotoWebDid(input); 34 34 };
+4 -4
src/lib/types/nsid.ts
··· 1 1 export type Nsid = `${string}.${string}.${string}`; 2 2 3 - export const NSID_RE = 4 - /^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z]{0,61}[a-zA-Z])?)$/; 3 + const NSID_RE = 4 + /^[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?:\.[a-zA-Z](?:[a-zA-Z0-9]{0,62})?)$/; 5 5 6 - export const isNsid = (str: string): str is Nsid => { 7 - return str.length >= 5 && str.length <= 317 && NSID_RE.test(str); 6 + export const isNsid = (input: unknown): input is Nsid => { 7 + return typeof input === 'string' && input.length >= 5 && input.length <= 317 && NSID_RE.test(input); 8 8 };
+6 -4
src/lib/types/rkey.ts
··· 1 - export const RECORD_KEY_RE = /(?!\.{1,2}$)[a-zA-Z0-9_~.:-]{1,512}/; 1 + const RECORD_KEY_RE = /^(?!\.{1,2}$)[a-zA-Z0-9_~.:-]{1,512}$/; 2 2 3 - export const isRecordKey = (input: string) => { 4 - return input.length >= 1 && input.length <= 512 && RECORD_KEY_RE.test(input); 3 + export type RecordKey = string; 4 + 5 + export const isRecordKey = (input: unknown): input is RecordKey => { 6 + return typeof input === 'string' && input.length >= 1 && input.length <= 512 && RECORD_KEY_RE.test(input); 5 7 }; 6 8 7 - export const TID_RE = /^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$/; 9 + const TID_RE = /^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$/; 8 10 9 11 export const isTid = (input: string) => { 10 12 return input.length === 13 && TID_RE.test(input);
+2 -2
src/lib/utils/bluesky/embeds.ts
··· 1 1 import type { AppBskyEmbedRecordWithMedia, AppBskyFeedDefs } from '@atcute/client/lexicons'; 2 2 3 - import { parseAtUri } from '$lib/types/at-uri'; 3 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 4 4 5 5 export interface Embed { 6 6 media?: AppBskyEmbedRecordWithMedia.View['media']; ··· 49 49 case 'app.bsky.embed.record#viewNotFound': 50 50 case 'app.bsky.embed.record#viewDetached': 51 51 case 'app.bsky.embed.record#viewBlocked': { 52 - const uri = parseAtUri(record.uri); 52 + const uri = parseAddressedAtUri(record.uri); 53 53 if (uri.collection === 'app.bsky.feed.post') { 54 54 return record; 55 55 }
+5 -5
src/routes/(app)/[actor=didOrHandle]/[rkey=tid]/+page.svelte
··· 5 5 import { PUBLIC_APP_NAME } from '$env/static/public'; 6 6 import type { PageProps } from './$types'; 7 7 8 - import { parseAtUri } from '$lib/types/at-uri'; 8 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 9 9 import { truncateMiddle, truncateRight } from '$lib/utils/strings'; 10 10 11 11 import BlockedAscendantItem from './components/blocked-ascendant-item.svelte'; ··· 22 22 const { data }: PageProps = $props(); 23 23 24 24 const main = $derived(data.thread.post); 25 - const uri = $derived(parseAtUri(main.uri)); 25 + const uri = $derived(parseAddressedAtUri(main.uri)); 26 26 27 27 const title = $derived.by(() => { 28 28 const author = `@${truncateMiddle(main.author.handle, 29)}`; ··· 52 52 {#if item.type === 'post'} 53 53 <PostAscendantItem {item} /> 54 54 {:else if item.type === 'overflow'} 55 - {@const uri = parseAtUri(item.uri)} 55 + {@const uri = parseAddressedAtUri(item.uri)} 56 56 57 57 <OverflowAscendantItem postUrl="{base}/{uri.repo}/{uri.rkey}#main" /> 58 58 {:else if item.type === 'blocked'} 59 - {@const uri = parseAtUri(item.uri)} 59 + {@const uri = parseAddressedAtUri(item.uri)} 60 60 61 61 <BlockedAscendantItem postUrl="{base}/{uri.repo}/{uri.rkey}#main" /> 62 62 {:else if item.type === 'nonexistent'} 63 - {@const uri = parseAtUri(item.uri)} 63 + {@const uri = parseAddressedAtUri(item.uri)} 64 64 65 65 <NonexistentAscendantPost postUrl="{base}/{uri.repo}/{uri.rkey}#main" /> 66 66 {/if}
+2 -2
src/routes/(app)/[actor=didOrHandle]/[rkey=tid]/components/descendants.svelte
··· 3 3 4 4 import { base } from '$app/paths'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 8 8 import { createReplyCollator } from '../utils'; 9 9 ··· 59 59 </div> 60 60 {/if} 61 61 62 - <OverflowDescendantItem postUrl="{base}/{post.author.did}/{parseAtUri(post.uri).rkey}#main" /> 62 + <OverflowDescendantItem postUrl="{base}/{post.author.did}/{parseAddressedAtUri(post.uri).rkey}#main" /> 63 63 {/if} 64 64 {/each} 65 65 {/snippet}
+2 -2
src/routes/(app)/[actor=didOrHandle]/[rkey=tid]/components/interaction-state.svelte
··· 7 7 import Group_2Outlined from '$lib/components/central-icons/group-2-outlined.svelte'; 8 8 import CircleBanSignOutlined from '$lib/components/central-icons/circle-ban-sign-outlined.svelte'; 9 9 import { base } from '$app/paths'; 10 - import { parseAtUri } from '$lib/types/at-uri'; 10 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 11 11 12 12 interface Props { 13 13 threadgate: AppBskyFeedDefs.ThreadgateView | undefined; ··· 99 99 {@const hydrated = threadgate!.lists?.find((list) => list.uri === rule.list)} 100 100 101 101 {#if hydrated} 102 - {@const uri = parseAtUri(rule.list)} 102 + {@const uri = parseAddressedAtUri(rule.list)} 103 103 104 104 <li> 105 105 Users in <a class="link" href="{base}/{uri.repo}/lists/{uri.rkey}">{hydrated.name}</a> list
+2 -2
src/routes/(app)/[actor=didOrHandle]/[rkey=tid]/components/main-post-metrics.svelte
··· 3 3 4 4 import { base } from '$app/paths'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 import { formatCompactNumber } from '$lib/utils/intl/number'; 8 8 9 9 interface Props { ··· 12 12 13 13 const { post }: Props = $props(); 14 14 15 - const baseUrl = $derived(`${base}/${post.author.did}/${parseAtUri(post.uri).rkey}`); 15 + const baseUrl = $derived(`${base}/${post.author.did}/${parseAddressedAtUri(post.uri).rkey}`); 16 16 </script> 17 17 18 18 {#snippet Stat(count: number | undefined, one: string, many: string, href: string)}
+2 -2
src/routes/(app)/[actor=didOrHandle]/[rkey=tid]/components/main-post.svelte
··· 4 4 import { base } from '$app/paths'; 5 5 6 6 import { findLabel, FlagsBlurContent, FlagsBlurMedia } from '$lib/moderation'; 7 - import { parseAtUri } from '$lib/types/at-uri'; 7 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 8 8 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 9 9 10 10 import Avatar from '$lib/components/avatar.svelte'; ··· 27 27 28 28 const { post, threadgate, prev = false }: Props = $props(); 29 29 30 - const uri = $derived(parseAtUri(post.uri)); 30 + const uri = $derived(parseAddressedAtUri(post.uri)); 31 31 32 32 const author = $derived(post.author); 33 33 const authorName = $derived(normalizeDisplayName(author.displayName ?? ''));
+2 -2
src/routes/(app)/[actor=didOrHandle]/[rkey=tid]/components/missing-descendant-item.svelte
··· 3 3 4 4 import { base } from '$app/paths'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 8 8 interface Props { 9 9 count: number; ··· 15 15 16 16 <div class="missing-descendant-item"> 17 17 <span class="label">{count === 1 ? `${count} missing reply` : `${count} missing replies`}</span> 18 - <a href="{base}/{post.author.did}/{parseAtUri(post.uri).rkey}/all-replies" class="link">View</a> 18 + <a href="{base}/{post.author.did}/{parseAddressedAtUri(post.uri).rkey}/all-replies" class="link">View</a> 19 19 </div> 20 20 21 21 <style>
+2 -2
src/routes/(app)/[actor=didOrHandle]/[rkey=tid]/components/post-ascendant-item.svelte
··· 4 4 import { base } from '$app/paths'; 5 5 6 6 import { findLabel, FlagsBlurContent, FlagsBlurMedia } from '$lib/moderation'; 7 - import { parseAtUri } from '$lib/types/at-uri'; 7 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 8 8 9 9 import Avatar from '$lib/components/avatar.svelte'; 10 10 import RichtextRenderer from '$lib/components/richtext-renderer.svelte'; ··· 28 28 const authorUrl = $derived(`${base}/${author.did}`); 29 29 30 30 const record = $derived(post.record as AppBskyFeedPost.Record); 31 - const postUrl = $derived(`${base}/${author.did}/${parseAtUri(post.uri).rkey}#main`); 31 + const postUrl = $derived(`${base}/${author.did}/${parseAddressedAtUri(post.uri).rkey}#main`); 32 32 33 33 const isAviBlurred = $derived(!!findLabel(author.labels, author.did, FlagsBlurMedia)); 34 34 const blur = $derived(findLabel(post.labels, author.did, FlagsBlurContent));
+2 -2
src/routes/(app)/[actor=didOrHandle]/[rkey=tid]/components/post-descendant-item.svelte
··· 6 6 import { base } from '$app/paths'; 7 7 8 8 import { findLabel, FlagsBlurContent, FlagsBlurMedia } from '$lib/moderation'; 9 - import { parseAtUri } from '$lib/types/at-uri'; 9 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 10 10 11 11 import Avatar from '$lib/components/avatar.svelte'; 12 12 import ContentHider from '$lib/components/content-hider.svelte'; ··· 29 29 const authorUrl = $derived(`${base}/${author.did}`); 30 30 31 31 const record = $derived(post.record as AppBskyFeedPost.Record); 32 - const postUrl = $derived(`${base}/${author.did}/${parseAtUri(post.uri).rkey}#main`); 32 + const postUrl = $derived(`${base}/${author.did}/${parseAddressedAtUri(post.uri).rkey}#main`); 33 33 34 34 const isAviBlurred = $derived(!!findLabel(author.labels, author.did, FlagsBlurMedia)); 35 35 const blur = $derived(findLabel(post.labels, author.did, FlagsBlurContent));
+3 -3
src/routes/(app)/[actor=didOrHandle]/[rkey=tid]/components/post-meta-tags.svelte
··· 3 3 4 4 import { PUBLIC_APP_NAME, PUBLIC_APP_URL } from '$env/static/public'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 8 8 import { unwrapEmbedView } from '$lib/utils/bluesky/embeds'; 9 9 import { collectionToLabel } from '$lib/utils/bluesky/records'; ··· 16 16 17 17 const { post }: Props = $props(); 18 18 19 - const uri = $derived(parseAtUri(post.uri)); 19 + const uri = $derived(parseAddressedAtUri(post.uri)); 20 20 21 21 const author = $derived(post.author); 22 22 const displayName = $derived(normalizeDisplayName(author.displayName ?? '')); ··· 65 65 break; 66 66 } 67 67 default: { 68 - const uri = parseAtUri(view.uri); 68 + const uri = parseAddressedAtUri(view.uri); 69 69 const resource = collectionToLabel(uri.collection); 70 70 71 71 const isUnavailable =
+2 -2
src/routes/(app)/[actor=didOrHandle]/feeds/[rkey=rkey]/+page.svelte
··· 4 4 import { PUBLIC_APP_NAME } from '$env/static/public'; 5 5 import type { PageProps } from './$types'; 6 6 7 - import { parseAtUri } from '$lib/types/at-uri'; 7 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 8 8 import { paginate } from '$lib/utils/pagination'; 9 9 10 10 import PageListing from '$lib/components/page/page-listing.svelte'; ··· 14 14 15 15 const { data }: PageProps = $props(); 16 16 17 - const rkey = $derived(parseAtUri(data.feed.uri).rkey); 17 + const rkey = $derived(parseAddressedAtUri(data.feed.uri).rkey); 18 18 19 19 const { rootUrl, nextUrl } = $derived( 20 20 paginate(page.url, data.timeline.cursor, `${base}/${data.feed.creator.did}/feeds/${rkey}`),
+2 -2
src/routes/(app)/[actor=didOrHandle]/feeds/[rkey=rkey]/components/feed-aside.svelte
··· 3 3 4 4 import { base } from '$app/paths'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 8 8 import { trimRichText } from '$lib/utils/bluesky/richtext'; 9 9 import { formatLongNumber } from '$lib/utils/intl/number'; ··· 20 20 21 21 const { feed }: Props = $props(); 22 22 23 - const uri = $derived(parseAtUri(feed.uri)); 23 + const uri = $derived(parseAddressedAtUri(feed.uri)); 24 24 25 25 const creatorUrl = $derived(`${base}/${feed.creator.did}`); 26 26 const feedUrl = $derived(`${creatorUrl}/feeds/${uri.rkey}`);
+2 -2
src/routes/(app)/[actor=didOrHandle]/feeds/[rkey=rkey]/components/feed-meta-tags.svelte
··· 3 3 4 4 import { PUBLIC_APP_NAME, PUBLIC_APP_URL } from '$env/static/public'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 8 8 import { trimRichText } from '$lib/utils/bluesky/richtext'; 9 9 import { truncateMiddle } from '$lib/utils/strings'; ··· 14 14 15 15 const { feed }: Props = $props(); 16 16 17 - const uri = $derived(parseAtUri(feed.uri)); 17 + const uri = $derived(parseAddressedAtUri(feed.uri)); 18 18 19 19 const description = $derived.by(() => { 20 20 const desc = trimRichText(feed.description ?? '');
+2 -2
src/routes/(app)/[actor=didOrHandle]/lists/[rkey=rkey]/+layout.svelte
··· 5 5 import { page } from '$app/state'; 6 6 import type { LayoutProps } from './$types'; 7 7 8 - import { parseAtUri } from '$lib/types/at-uri'; 8 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 9 9 10 10 import ListAside from './components/list-aside.svelte'; 11 11 import ListMetaTags from './components/list-meta-tags.svelte'; ··· 14 14 15 15 const currentRouteId = $derived(page.route.id); 16 16 17 - const uri = $derived(parseAtUri(data.list.uri)); 17 + const uri = $derived(parseAddressedAtUri(data.list.uri)); 18 18 const listUrl = $derived.by(() => { 19 19 return `${base}/${uri.repo}/lists/${uri.rkey}`; 20 20 });
+2 -2
src/routes/(app)/[actor=didOrHandle]/lists/[rkey=rkey]/components/list-aside.svelte
··· 3 3 4 4 import { base } from '$app/paths'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 8 8 import { trimRichText } from '$lib/utils/bluesky/richtext'; 9 9 import { formatLongNumber } from '$lib/utils/intl/number'; ··· 20 20 21 21 const { list }: Props = $props(); 22 22 23 - const uri = $derived(parseAtUri(list.uri)); 23 + const uri = $derived(parseAddressedAtUri(list.uri)); 24 24 25 25 const creatorUrl = $derived(`${base}/${list.creator.did}`); 26 26 </script>
+2 -2
src/routes/(app)/[actor=didOrHandle]/lists/[rkey=rkey]/components/list-meta-tags.svelte
··· 3 3 4 4 import { PUBLIC_APP_NAME, PUBLIC_APP_URL } from '$env/static/public'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 8 8 import { purposeToLabel } from '$lib/utils/bluesky/lists'; 9 9 import { trimRichText } from '$lib/utils/bluesky/richtext'; ··· 15 15 16 16 const { list }: Props = $props(); 17 17 18 - const uri = $derived(parseAtUri(list.uri)); 18 + const uri = $derived(parseAddressedAtUri(list.uri)); 19 19 20 20 const description = $derived.by(() => { 21 21 const desc = trimRichText(list.description ?? '');
+2 -2
src/routes/(app)/[actor=didOrHandle]/lists/[rkey=rkey]/posts/+page.svelte
··· 8 8 9 9 import PageListing from '$lib/components/page/page-listing.svelte'; 10 10 import PostFeedItem from '$lib/components/timeline/post-feed-item.svelte'; 11 - import { parseAtUri } from '$lib/types/at-uri'; 11 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 12 12 13 13 const { data }: PageProps = $props(); 14 14 15 - const uri = $derived(parseAtUri(data.list.uri)); 15 + const uri = $derived(parseAddressedAtUri(data.list.uri)); 16 16 17 17 const { rootUrl, nextUrl } = $derived( 18 18 paginate(page.url, data.timeline.cursor, `${base}/${uri.repo}/lists/${uri.rkey}/posts`),
+2 -2
src/routes/(app)/[actor=didOrHandle]/packs/[rkey=rkey]/+layout.svelte
··· 8 8 import { PUBLIC_APP_NAME } from '$env/static/public'; 9 9 import type { LayoutProps } from './$types'; 10 10 11 - import { parseAtUri } from '$lib/types/at-uri'; 11 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 12 12 13 13 import PackAside from './components/pack-aside.svelte'; 14 14 import PackMetaTags from './components/pack-meta-tags.svelte'; ··· 18 18 const currentRouteId = $derived(page.route.id); 19 19 20 20 const record = $derived(data.pack.record as AppBskyGraphStarterpack.Record); 21 - const uri = $derived(parseAtUri(data.pack.uri)); 21 + const uri = $derived(parseAddressedAtUri(data.pack.uri)); 22 22 23 23 const listUrl = $derived.by(() => { 24 24 return `${base}/${uri.repo}/packs/${uri.rkey}`;
+2 -2
src/routes/(app)/[actor=didOrHandle]/packs/[rkey=rkey]/components/pack-aside.svelte
··· 3 3 4 4 import { base } from '$app/paths'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 8 8 import { trimRichText } from '$lib/utils/bluesky/richtext'; 9 9 ··· 19 19 20 20 const { pack }: Props = $props(); 21 21 22 - const uri = $derived(parseAtUri(pack.uri)); 22 + const uri = $derived(parseAddressedAtUri(pack.uri)); 23 23 const record = $derived(pack.record as AppBskyGraphStarterpack.Record); 24 24 25 25 const creatorUrl = $derived(`${base}/${pack.creator.did}`);
+2 -2
src/routes/(app)/[actor=didOrHandle]/packs/[rkey=rkey]/components/pack-meta-tags.svelte
··· 3 3 4 4 import { PUBLIC_APP_NAME, PUBLIC_APP_URL } from '$env/static/public'; 5 5 6 - import { parseAtUri } from '$lib/types/at-uri'; 6 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 7 7 import { normalizeDisplayName } from '$lib/utils/bluesky/display'; 8 8 import { trimRichText } from '$lib/utils/bluesky/richtext'; 9 9 import { truncateMiddle } from '$lib/utils/strings'; ··· 14 14 15 15 const { pack }: Props = $props(); 16 16 17 - const uri = $derived(parseAtUri(pack.uri)); 17 + const uri = $derived(parseAddressedAtUri(pack.uri)); 18 18 const record = $derived(pack.record as AppBskyGraphStarterpack.Record); 19 19 20 20 const description = $derived.by(() => {
+2 -2
src/routes/(app)/[actor=didOrHandle]/packs/[rkey=rkey]/posts/+page.svelte
··· 7 7 8 8 import PageListing from '$lib/components/page/page-listing.svelte'; 9 9 import PostFeedItem from '$lib/components/timeline/post-feed-item.svelte'; 10 - import { parseAtUri } from '$lib/types/at-uri'; 10 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 11 11 12 12 const { data }: PageProps = $props(); 13 13 14 - const uri = $derived(parseAtUri(data.pack.uri)); 14 + const uri = $derived(parseAddressedAtUri(data.pack.uri)); 15 15 16 16 const { rootUrl, nextUrl } = $derived( 17 17 paginate(page.url, data.timeline.cursor, `${base}/${uri.repo}/packs/${uri.rkey}/posts`),
+2 -2
src/routes/(app)/[actor=did]/[rkey=tid]/unroll/+page.svelte
··· 7 7 import type { PageProps } from './$types'; 8 8 9 9 import { findLabel, FlagsBlurMedia } from '$lib/moderation'; 10 - import { parseAtUri } from '$lib/types/at-uri'; 10 + import { parseAddressedAtUri } from '$lib/types/at-uri'; 11 11 import { truncateMiddle, truncateRight } from '$lib/utils/strings'; 12 12 13 13 import Avatar from '$lib/components/avatar.svelte'; ··· 25 25 const author = $derived(main.author); 26 26 const authorName = $derived(author.displayName?.trim()); 27 27 28 - const uri = $derived(parseAtUri(main.uri)); 28 + const uri = $derived(parseAddressedAtUri(main.uri)); 29 29 const postUrl = $derived(`${base}/${uri.repo}/${uri.rkey}#main`); 30 30 const authorUrl = $derived(`${base}/${uri.repo}`); 31 31