@atproto-ui/core 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/backlinks.d.ts +18 -0
  2. package/dist/backlinks.js +15 -0
  3. package/dist/blob-fetch.d.ts +42 -0
  4. package/dist/blob-fetch.js +53 -0
  5. package/dist/cache.d.ts +155 -0
  6. package/dist/cache.js +345 -0
  7. package/dist/client.d.ts +45 -0
  8. package/dist/client.js +118 -0
  9. package/dist/fetch-and-cache-handler.d.ts +84 -0
  10. package/dist/fetch-and-cache-handler.js +63 -0
  11. package/dist/grain.d.ts +22 -0
  12. package/dist/grain.js +30 -0
  13. package/dist/icons.d.ts +11 -0
  14. package/dist/icons.js +11 -0
  15. package/dist/identity.d.ts +28 -0
  16. package/dist/identity.js +55 -0
  17. package/dist/index.d.ts +27 -0
  18. package/dist/index.js +22 -0
  19. package/dist/leaflet.d.ts +22 -0
  20. package/dist/leaflet.js +85 -0
  21. package/dist/music.d.ts +35 -0
  22. package/dist/music.js +88 -0
  23. package/dist/paginated.d.ts +61 -0
  24. package/dist/paginated.js +111 -0
  25. package/dist/records.d.ts +171 -0
  26. package/dist/records.js +321 -0
  27. package/dist/rpc.d.ts +21 -0
  28. package/dist/rpc.js +24 -0
  29. package/dist/styles.d.ts +40 -0
  30. package/dist/styles.js +1728 -0
  31. package/dist/tangled-fetch.d.ts +9 -0
  32. package/dist/tangled-fetch.js +15 -0
  33. package/dist/types/blob.d.ts +14 -0
  34. package/dist/types/bluesky.d.ts +4 -0
  35. package/dist/types/grain.d.ts +31 -0
  36. package/dist/types/leaflet.d.ts +173 -0
  37. package/dist/types/record.d.ts +28 -0
  38. package/dist/types/record.js +7 -0
  39. package/dist/types/tangled.d.ts +19 -0
  40. package/dist/types/teal.d.ts +36 -0
  41. package/dist/utils/at-uri.d.ts +10 -0
  42. package/dist/utils/at-uri.js +33 -0
  43. package/dist/utils/blob.d.ts +5 -0
  44. package/dist/utils/blob.js +26 -0
  45. package/dist/utils/profile.d.ts +2 -0
  46. package/dist/utils/profile.js +5 -0
  47. package/dist/utils/richtext.d.ts +28 -0
  48. package/dist/utils/richtext.js +95 -0
  49. package/dist/utils/time.d.ts +2 -0
  50. package/dist/utils/time.js +41 -0
  51. package/package.json +41 -0
@@ -0,0 +1,9 @@
1
+ import type { RepoLanguagesResponse } from "./types/tangled";
2
+ export interface FetchRepoLanguagesOptions {
3
+ knot: string;
4
+ did: string;
5
+ repoName: string;
6
+ branch?: string;
7
+ fetch?: typeof fetch;
8
+ }
9
+ export declare function fetchRepoLanguages({ knot, did, repoName, branch, fetch: fetchImpl, }: FetchRepoLanguagesOptions): Promise<RepoLanguagesResponse>;
@@ -0,0 +1,15 @@
1
+ import { normalizeAtIdentifier } from "./identity.js";
2
+ async function fetchRepoLanguages({ knot: t, did: n, repoName: r, branch: i, fetch: a = fetch }) {
3
+ let o = i ? [i] : ["main", "master"];
4
+ for (let i of o) {
5
+ let o = normalizeAtIdentifier(t);
6
+ if (!o) break;
7
+ let s = `${(o.match(/^[a-zA-Z][a-zA-Z\d+\-.]*:/) ? o : `https://${o}`).replace(/\/+$/, "")}/xrpc/sh.tangled.repo.languages?repo=${encodeURIComponent(`${n}/${r}`)}&ref=${encodeURIComponent(i)}`;
8
+ try {
9
+ let e = await a(s);
10
+ if (e.ok) return await e.json();
11
+ } catch {}
12
+ }
13
+ throw Error(i ? `Failed to fetch languages for branch: ${i}` : "Failed to fetch languages for main or master branch");
14
+ }
15
+ export { fetchRepoLanguages };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Extended blob reference that includes a CDN URL from appview responses.
3
+ * Used when fetching from Bluesky appview to avoid extra blob fetches.
4
+ */
5
+ export interface BlobWithCdn {
6
+ $type: "blob";
7
+ ref: {
8
+ $link: string;
9
+ };
10
+ mimeType: string;
11
+ size: number;
12
+ /** CDN URL from Bluesky appview (e.g., https://cdn.bsky.app/img/avatar/plain/did:plc:xxx/bafkreixxx@jpeg) */
13
+ cdnUrl?: string;
14
+ }
@@ -0,0 +1,4 @@
1
+ import type { AppBskyFeedPost, AppBskyActorProfile } from "@atcute/bluesky";
2
+ export type { AppBskyFeedPost, AppBskyActorProfile };
3
+ export type FeedPostRecord = AppBskyFeedPost.Main;
4
+ export type ProfileRecord = AppBskyActorProfile.Main;
@@ -0,0 +1,31 @@
1
+ import type { Blob } from "@atcute/lexicons/interfaces";
2
+ import type { BlobWithCdn } from "./blob";
3
+ export interface GrainGalleryRecord {
4
+ $type: "social.grain.gallery";
5
+ title: string;
6
+ description?: string;
7
+ labels?: {
8
+ $type: "com.atproto.label.defs#selfLabels";
9
+ values: Array<{
10
+ val: string;
11
+ }>;
12
+ };
13
+ createdAt: string;
14
+ }
15
+ export interface GrainGalleryItemRecord {
16
+ $type: "social.grain.gallery.item";
17
+ item: string;
18
+ gallery: string;
19
+ position?: number;
20
+ createdAt: string;
21
+ }
22
+ export interface GrainPhotoRecord {
23
+ $type: "social.grain.photo";
24
+ alt: string;
25
+ photo: Blob<`image/${string}`> | BlobWithCdn;
26
+ createdAt?: string;
27
+ aspectRatio?: {
28
+ width: number;
29
+ height: number;
30
+ };
31
+ }
@@ -0,0 +1,173 @@
1
+ export interface StrongRef {
2
+ uri: string;
3
+ cid: string;
4
+ }
5
+ export interface LeafletDocumentRecord {
6
+ $type?: "pub.leaflet.document";
7
+ title: string;
8
+ postRef?: StrongRef;
9
+ description?: string;
10
+ publishedAt?: string;
11
+ publication: string;
12
+ author: string;
13
+ pages: LeafletDocumentPage[];
14
+ }
15
+ export type LeafletDocumentPage = LeafletLinearDocumentPage;
16
+ export interface LeafletLinearDocumentPage {
17
+ $type?: "pub.leaflet.pages.linearDocument";
18
+ blocks?: LeafletLinearDocumentBlock[];
19
+ }
20
+ export type LeafletAlignmentValue = "#textAlignLeft" | "#textAlignCenter" | "#textAlignRight" | "#textAlignJustify" | "textAlignLeft" | "textAlignCenter" | "textAlignRight" | "textAlignJustify";
21
+ export interface LeafletLinearDocumentBlock {
22
+ block: LeafletBlock;
23
+ alignment?: LeafletAlignmentValue;
24
+ }
25
+ export type LeafletBlock = LeafletTextBlock | LeafletHeaderBlock | LeafletBlockquoteBlock | LeafletImageBlock | LeafletUnorderedListBlock | LeafletWebsiteBlock | LeafletIFrameBlock | LeafletMathBlock | LeafletCodeBlock | LeafletHorizontalRuleBlock | LeafletBskyPostBlock;
26
+ export interface LeafletBaseTextBlock {
27
+ plaintext: string;
28
+ facets?: LeafletRichTextFacet[];
29
+ }
30
+ export interface LeafletTextBlock extends LeafletBaseTextBlock {
31
+ $type?: "pub.leaflet.blocks.text";
32
+ }
33
+ export interface LeafletHeaderBlock extends LeafletBaseTextBlock {
34
+ $type?: "pub.leaflet.blocks.header";
35
+ level?: number;
36
+ }
37
+ export interface LeafletBlockquoteBlock extends LeafletBaseTextBlock {
38
+ $type?: "pub.leaflet.blocks.blockquote";
39
+ }
40
+ export interface LeafletImageBlock {
41
+ $type?: "pub.leaflet.blocks.image";
42
+ image: LeafletBlobRef;
43
+ alt?: string;
44
+ aspectRatio: {
45
+ width: number;
46
+ height: number;
47
+ };
48
+ }
49
+ export interface LeafletUnorderedListBlock {
50
+ $type?: "pub.leaflet.blocks.unorderedList";
51
+ children: LeafletListItem[];
52
+ }
53
+ export interface LeafletListItem {
54
+ content: LeafletListContent;
55
+ children?: LeafletListItem[];
56
+ }
57
+ export type LeafletListContent = LeafletTextBlock | LeafletHeaderBlock | LeafletImageBlock;
58
+ export interface LeafletWebsiteBlock {
59
+ $type?: "pub.leaflet.blocks.website";
60
+ src: string;
61
+ title?: string;
62
+ description?: string;
63
+ previewImage?: LeafletBlobRef;
64
+ }
65
+ export interface LeafletIFrameBlock {
66
+ $type?: "pub.leaflet.blocks.iframe";
67
+ url: string;
68
+ height?: number;
69
+ }
70
+ export interface LeafletMathBlock {
71
+ $type?: "pub.leaflet.blocks.math";
72
+ tex: string;
73
+ }
74
+ export interface LeafletCodeBlock {
75
+ $type?: "pub.leaflet.blocks.code";
76
+ plaintext: string;
77
+ language?: string;
78
+ syntaxHighlightingTheme?: string;
79
+ }
80
+ export interface LeafletHorizontalRuleBlock {
81
+ $type?: "pub.leaflet.blocks.horizontalRule";
82
+ }
83
+ export interface LeafletBskyPostBlock {
84
+ $type?: "pub.leaflet.blocks.bskyPost";
85
+ postRef: StrongRef;
86
+ }
87
+ export interface LeafletRichTextFacet {
88
+ index: LeafletByteSlice;
89
+ features: LeafletRichTextFeature[];
90
+ }
91
+ export interface LeafletByteSlice {
92
+ byteStart: number;
93
+ byteEnd: number;
94
+ }
95
+ export type LeafletRichTextFeature = LeafletRichTextLinkFeature | LeafletRichTextCodeFeature | LeafletRichTextHighlightFeature | LeafletRichTextUnderlineFeature | LeafletRichTextStrikethroughFeature | LeafletRichTextIdFeature | LeafletRichTextBoldFeature | LeafletRichTextItalicFeature;
96
+ export interface LeafletRichTextLinkFeature {
97
+ $type: "pub.leaflet.richtext.facet#link";
98
+ uri: string;
99
+ }
100
+ export interface LeafletRichTextCodeFeature {
101
+ $type: "pub.leaflet.richtext.facet#code";
102
+ }
103
+ export interface LeafletRichTextHighlightFeature {
104
+ $type: "pub.leaflet.richtext.facet#highlight";
105
+ }
106
+ export interface LeafletRichTextUnderlineFeature {
107
+ $type: "pub.leaflet.richtext.facet#underline";
108
+ }
109
+ export interface LeafletRichTextStrikethroughFeature {
110
+ $type: "pub.leaflet.richtext.facet#strikethrough";
111
+ }
112
+ export interface LeafletRichTextIdFeature {
113
+ $type: "pub.leaflet.richtext.facet#id";
114
+ id?: string;
115
+ }
116
+ export interface LeafletRichTextBoldFeature {
117
+ $type: "pub.leaflet.richtext.facet#bold";
118
+ }
119
+ export interface LeafletRichTextItalicFeature {
120
+ $type: "pub.leaflet.richtext.facet#italic";
121
+ }
122
+ export interface LeafletBlobRef {
123
+ $type?: string;
124
+ ref?: {
125
+ $link?: string;
126
+ };
127
+ cid?: string;
128
+ mimeType?: string;
129
+ size?: number;
130
+ }
131
+ export interface LeafletPublicationRecord {
132
+ $type?: "pub.leaflet.publication";
133
+ name: string;
134
+ base_path?: string;
135
+ description?: string;
136
+ icon?: LeafletBlobRef;
137
+ theme?: LeafletTheme;
138
+ preferences?: LeafletPublicationPreferences;
139
+ }
140
+ export interface LeafletPublicationPreferences {
141
+ showInDiscover?: boolean;
142
+ showComments?: boolean;
143
+ }
144
+ export interface LeafletTheme {
145
+ backgroundColor?: LeafletThemeColor;
146
+ backgroundImage?: LeafletThemeBackgroundImage;
147
+ primary?: LeafletThemeColor;
148
+ pageBackground?: LeafletThemeColor;
149
+ showPageBackground?: boolean;
150
+ accentBackground?: LeafletThemeColor;
151
+ accentText?: LeafletThemeColor;
152
+ }
153
+ export type LeafletThemeColor = LeafletThemeColorRgb | LeafletThemeColorRgba;
154
+ export interface LeafletThemeColorRgb {
155
+ $type?: "pub.leaflet.theme.color#rgb";
156
+ r: number;
157
+ g: number;
158
+ b: number;
159
+ }
160
+ export interface LeafletThemeColorRgba {
161
+ $type?: "pub.leaflet.theme.color#rgba";
162
+ r: number;
163
+ g: number;
164
+ b: number;
165
+ a: number;
166
+ }
167
+ export interface LeafletThemeBackgroundImage {
168
+ $type?: "pub.leaflet.theme.backgroundImage";
169
+ image: LeafletBlobRef;
170
+ width?: number;
171
+ repeat?: boolean;
172
+ }
173
+ export type LeafletInlineRenderable = LeafletTextBlock | LeafletHeaderBlock | LeafletBlockquoteBlock;
@@ -0,0 +1,28 @@
1
+ import type { BaseSchema, InferInput } from "@atcute/lexicons/validations";
2
+ export type RecordTypeName<T> = T extends {
3
+ $type?: infer TypeName extends string;
4
+ } ? TypeName : never;
5
+ export type KnownRecordCollection = "app.bsky.actor.profile" | "app.bsky.feed.post" | "fm.teal.alpha.actor.status" | "fm.teal.alpha.feed.play" | "pub.leaflet.document" | "pub.leaflet.publication" | "sh.tangled.repo" | "sh.tangled.string" | "social.grain.gallery" | "social.grain.gallery.item" | "social.grain.photo";
6
+ export type InferableRecordCollection<T> = Extract<RecordTypeName<T>, KnownRecordCollection>;
7
+ export type InferableLatestRecordCollection<T> = InferableRecordCollection<T>;
8
+ export interface AtcuteRecordRuntimeShape {
9
+ mainSchema?: {
10
+ object?: {
11
+ shape?: {
12
+ $type?: {
13
+ expected?: unknown;
14
+ };
15
+ };
16
+ };
17
+ };
18
+ }
19
+ export type AtcuteRecordNamespace<TSchema extends BaseSchema = BaseSchema> = AtcuteRecordRuntimeShape & {
20
+ mainSchema: TSchema;
21
+ };
22
+ export type AtcuteRecordMain<TNamespace> = TNamespace extends {
23
+ mainSchema: infer Schema extends BaseSchema;
24
+ } ? InferInput<Schema> : never;
25
+ export type RecordCollectionInput = string | AtcuteRecordNamespace;
26
+ export declare const DEFAULT_RECORD_COLLECTION = "app.bsky.feed.post";
27
+ export declare const DEFAULT_LATEST_RECORD_COLLECTION = "app.bsky.feed.post";
28
+ export declare function resolveRecordCollection(collection: RecordCollectionInput | undefined, fallback?: string): string | undefined;
@@ -0,0 +1,7 @@
1
+ const DEFAULT_RECORD_COLLECTION = "app.bsky.feed.post", DEFAULT_LATEST_RECORD_COLLECTION = DEFAULT_RECORD_COLLECTION;
2
+ function resolveRecordCollection(e, t) {
3
+ if (typeof e == "string") return e;
4
+ let n = e?.mainSchema?.object?.shape?.$type?.expected;
5
+ return typeof n == "string" ? n : t;
6
+ }
7
+ export { DEFAULT_LATEST_RECORD_COLLECTION, DEFAULT_RECORD_COLLECTION, resolveRecordCollection };
@@ -0,0 +1,19 @@
1
+ import type { ShTangledRepo, ShTangledString } from "@atcute/tangled";
2
+ export type TangledRepoRecord = ShTangledRepo.Main;
3
+ export type TangledStringRecord = ShTangledString.Main;
4
+ /** Language information from sh.tangled.repo.languages endpoint */
5
+ export interface RepoLanguage {
6
+ name: string;
7
+ percentage: number;
8
+ size: number;
9
+ }
10
+ /**
11
+ * Response from sh.tangled.repo.languages endpoint from tangled knot
12
+ */
13
+ export interface RepoLanguagesResponse {
14
+ languages: RepoLanguage[];
15
+ /** Branch name */
16
+ ref: string;
17
+ totalFiles: number;
18
+ totalSize: number;
19
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * teal.fm record types for music listening history
3
+ * Specification: fm.teal.alpha.actor.status and fm.teal.alpha.feed.play
4
+ */
5
+ export interface TealArtist {
6
+ artistName: string;
7
+ artistMbId?: string;
8
+ }
9
+ export interface TealPlayItem {
10
+ artists: TealArtist[];
11
+ originUrl?: string;
12
+ trackName: string;
13
+ playedTime: string;
14
+ releaseName?: string;
15
+ recordingMbId?: string;
16
+ releaseMbId?: string;
17
+ submissionClientAgent?: string;
18
+ musicServiceBaseDomain?: string;
19
+ isrc?: string;
20
+ duration?: number;
21
+ }
22
+ /**
23
+ * fm.teal.alpha.actor.status - The last played song
24
+ */
25
+ export interface TealActorStatusRecord {
26
+ $type: "fm.teal.alpha.actor.status";
27
+ item: TealPlayItem;
28
+ time: string;
29
+ expiry?: string;
30
+ }
31
+ /**
32
+ * fm.teal.alpha.feed.play - A single play record
33
+ */
34
+ export interface TealFeedPlayRecord extends TealPlayItem {
35
+ $type: "fm.teal.alpha.feed.play";
36
+ }
@@ -0,0 +1,10 @@
1
+ export interface ParsedAtUri {
2
+ did: string;
3
+ collection: string;
4
+ rkey: string;
5
+ }
6
+ export declare function parseAtUri(uri?: string): ParsedAtUri | undefined;
7
+ export declare function toBlueskyPostUrl(target: ParsedAtUri): string | undefined;
8
+ export declare function formatDidForLabel(did: string): string;
9
+ export declare function normalizeLeafletBasePath(basePath?: string): string | undefined;
10
+ export declare function leafletRkeyUrl(basePath: string | undefined, rkey: string): string | undefined;
@@ -0,0 +1,33 @@
1
+ function parseAtUri(e) {
2
+ if (!e || !e.startsWith("at://")) return;
3
+ let [r, i, a] = e.slice(5).split("/");
4
+ if (!(!r || !i || !a)) return {
5
+ did: r,
6
+ collection: i,
7
+ rkey: a
8
+ };
9
+ }
10
+ function toBlueskyPostUrl(e) {
11
+ if (e.collection === "app.bsky.feed.post") return `https://bsky.app/profile/${e.did}/post/${e.rkey}`;
12
+ }
13
+ function formatDidForLabel(e) {
14
+ return e.replace(/^did:(plc:)?/, "");
15
+ }
16
+ var ABSOLUTE_URL_PATTERN = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
17
+ function normalizeLeafletBasePath(e) {
18
+ if (!e) return;
19
+ let r = e.trim();
20
+ if (!r) return;
21
+ let i = ABSOLUTE_URL_PATTERN.test(r) ? r : `https://${r}`;
22
+ try {
23
+ let e = new URL(i);
24
+ return e.hash = "", e.href.replace(/\/?$/, "");
25
+ } catch {
26
+ return;
27
+ }
28
+ }
29
+ function leafletRkeyUrl(e, r) {
30
+ let i = normalizeLeafletBasePath(e);
31
+ if (i) return `${i}/${encodeURIComponent(r)}`;
32
+ }
33
+ export { formatDidForLabel, leafletRkeyUrl, normalizeLeafletBasePath, parseAtUri, toBlueskyPostUrl };
@@ -0,0 +1,5 @@
1
+ import type { BlobWithCdn } from "../types/blob";
2
+ export declare function isBlobWithCdn(value: unknown): value is BlobWithCdn;
3
+ export declare function getBlobImageUrl(value: unknown): string | undefined;
4
+ export declare function extractCidFromBlob(blob: unknown): string | undefined;
5
+ export declare function extractCidFromCdnUrl(url: string | undefined): string | undefined;
@@ -0,0 +1,26 @@
1
+ var CID_PATTERN = /(?:^|\/)(bafk[a-z0-9]+|bafyb[a-z0-9]+|Qm[1-9A-HJ-NP-Za-km-z]+)(?:@[^/?#]+)?(?:[/?#]|$)/i;
2
+ function isBlobWithCdn(e) {
3
+ if (typeof e != "object" || !e) return !1;
4
+ let r = e;
5
+ return r.$type === "blob" && typeof r.cdnUrl == "string" && typeof r.ref == "object" && r.ref !== null && typeof r.ref.$link == "string";
6
+ }
7
+ function getBlobImageUrl(e) {
8
+ if (isBlobWithCdn(e)) return e.cdnUrl;
9
+ if (typeof e != "object" || !e) return;
10
+ let i = e;
11
+ if (i.$type === "blob") return typeof i.cdnUrl == "string" ? i.cdnUrl : void 0;
12
+ }
13
+ function extractCidFromBlob(e) {
14
+ if (typeof e == "string") return e;
15
+ if (typeof e != "object" || !e) return;
16
+ let r = e;
17
+ if (typeof r.cid == "string") return r.cid;
18
+ if (typeof r.ref == "object" && r.ref !== null) {
19
+ let e = r.ref.$link;
20
+ if (typeof e == "string") return e;
21
+ }
22
+ }
23
+ function extractCidFromCdnUrl(r) {
24
+ if (r) return r.match(CID_PATTERN)?.[1];
25
+ }
26
+ export { extractCidFromBlob, extractCidFromCdnUrl, getBlobImageUrl, isBlobWithCdn };
@@ -0,0 +1,2 @@
1
+ import type { ProfileRecord } from "../types/bluesky";
2
+ export declare function getAvatarCid(record: ProfileRecord | undefined): string | undefined;
@@ -0,0 +1,5 @@
1
+ function getAvatarCid(e) {
2
+ let t = e?.avatar;
3
+ if (t) return typeof t.cid == "string" ? t.cid : t.ref?.$link;
4
+ }
5
+ export { getAvatarCid };
@@ -0,0 +1,28 @@
1
+ import type { AppBskyRichtextFacet } from "@atcute/bluesky";
2
+ export type BlueskyRichTextFacet = AppBskyRichtextFacet.Main;
3
+ export interface TextSegment {
4
+ text: string;
5
+ facet?: BlueskyRichTextFacet;
6
+ }
7
+ export type RichTextSegmentKind = "text" | "link" | "mention" | "tag";
8
+ export interface RichTextSegment {
9
+ text: string;
10
+ kind: RichTextSegmentKind;
11
+ href?: string;
12
+ facet?: BlueskyRichTextFacet;
13
+ feature?: BlueskyRichTextFacet["features"][number];
14
+ }
15
+ export interface RichTextSegmentOptions {
16
+ blueskyAppBaseUrl?: string;
17
+ }
18
+ /**
19
+ * Converts a text string with facets into segments that can be rendered
20
+ * with appropriate styling and interactivity.
21
+ */
22
+ export declare function createTextSegments(text: string, facets?: BlueskyRichTextFacet[]): TextSegment[];
23
+ /**
24
+ * Converts Bluesky richtext facets into render-ready segments.
25
+ * Framework packages should map linked segments to anchors and plain segments
26
+ * to text nodes; feature interpretation stays centralized here.
27
+ */
28
+ export declare function createRichTextSegments(text: string, facets?: BlueskyRichTextFacet[], options?: RichTextSegmentOptions): RichTextSegment[];
@@ -0,0 +1,95 @@
1
+ function createTextSegments(e, t) {
2
+ if (!t || t.length === 0) return [{ text: e }];
3
+ let a = buildBytePrefix(e), o = [...t].sort((e, t) => e.index.byteStart - t.index.byteStart), s = [], c = 0;
4
+ for (let t of o) {
5
+ let n = byteOffsetToCharIndex(a, t.index.byteStart), o = byteOffsetToCharIndex(a, t.index.byteEnd);
6
+ n > c && s.push({ text: sliceByCharRange(e, c, n) }), s.push({
7
+ text: sliceByCharRange(e, n, o),
8
+ facet: t
9
+ }), c = o;
10
+ }
11
+ return c < e.length && s.push({ text: sliceByCharRange(e, c, e.length) }), s;
12
+ }
13
+ function createRichTextSegments(t, n, r = {}) {
14
+ let i = (r.blueskyAppBaseUrl ?? "https://bsky.app").replace(/\/+$/, "");
15
+ return createTextSegments(t, n).map((e) => {
16
+ let t = e.facet?.features?.[0];
17
+ if (!t) return {
18
+ ...e,
19
+ kind: "text"
20
+ };
21
+ let n = t.$type;
22
+ if (n === "app.bsky.richtext.facet#link") {
23
+ let n = t.uri;
24
+ return n ? {
25
+ ...e,
26
+ kind: "link",
27
+ href: n,
28
+ feature: t
29
+ } : {
30
+ ...e,
31
+ kind: "text",
32
+ feature: t
33
+ };
34
+ }
35
+ if (n === "app.bsky.richtext.facet#mention") {
36
+ let n = t.did;
37
+ return n ? {
38
+ ...e,
39
+ kind: "mention",
40
+ href: `${i}/profile/${n}`,
41
+ feature: t
42
+ } : {
43
+ ...e,
44
+ kind: "text",
45
+ feature: t
46
+ };
47
+ }
48
+ if (n === "app.bsky.richtext.facet#tag") {
49
+ let n = t.tag;
50
+ return n ? {
51
+ ...e,
52
+ kind: "tag",
53
+ href: `${i}/hashtag/${encodeURIComponent(n)}`,
54
+ feature: t
55
+ } : {
56
+ ...e,
57
+ kind: "text",
58
+ feature: t
59
+ };
60
+ }
61
+ return {
62
+ ...e,
63
+ kind: "text",
64
+ feature: t
65
+ };
66
+ });
67
+ }
68
+ function buildBytePrefix(e) {
69
+ let t = new TextEncoder(), n = [0], r = 0;
70
+ for (let i = 0; i < e.length;) {
71
+ let a = e.codePointAt(i);
72
+ if (a === void 0) break;
73
+ let o = String.fromCodePoint(a), s = t.encode(o);
74
+ r += s.length, n.push(r), i += a > 65535 ? 2 : 1;
75
+ }
76
+ return n;
77
+ }
78
+ function byteOffsetToCharIndex(e, t) {
79
+ for (let n = 0; n < e.length; n++) {
80
+ if (e[n] === t) return n;
81
+ if (e[n] > t) return Math.max(0, n - 1);
82
+ }
83
+ return e.length - 1;
84
+ }
85
+ function sliceByCharRange(e, t, n) {
86
+ if (t <= 0 && n >= e.length) return e;
87
+ let r = "", i = 0;
88
+ for (let a = 0; a < e.length && i < n;) {
89
+ let o = e.codePointAt(a);
90
+ if (o === void 0) break;
91
+ i >= t && i < n && (r += String.fromCodePoint(o)), a += o > 65535 ? 2 : 1, i++;
92
+ }
93
+ return r;
94
+ }
95
+ export { createRichTextSegments, createTextSegments };
@@ -0,0 +1,2 @@
1
+ /** Formats an ISO timestamp as a localized relative time (e.g. "3 hours ago"). */
2
+ export declare function formatRelativeTime(iso: string): string;
@@ -0,0 +1,41 @@
1
+ function formatRelativeTime(e) {
2
+ let t = (new Date(e).getTime() - Date.now()) / 1e3, n = Math.abs(t), r = [
3
+ {
4
+ limit: 60,
5
+ unit: "second",
6
+ divisor: 1
7
+ },
8
+ {
9
+ limit: 3600,
10
+ unit: "minute",
11
+ divisor: 60
12
+ },
13
+ {
14
+ limit: 86400,
15
+ unit: "hour",
16
+ divisor: 3600
17
+ },
18
+ {
19
+ limit: 604800,
20
+ unit: "day",
21
+ divisor: 86400
22
+ },
23
+ {
24
+ limit: 2629800,
25
+ unit: "week",
26
+ divisor: 604800
27
+ },
28
+ {
29
+ limit: 31557600,
30
+ unit: "month",
31
+ divisor: 2629800
32
+ },
33
+ {
34
+ limit: Infinity,
35
+ unit: "year",
36
+ divisor: 31557600
37
+ }
38
+ ], i = r.find((e) => n < e.limit) ?? r[r.length - 1], a = t / i.divisor;
39
+ return new Intl.RelativeTimeFormat("en", { numeric: "auto" }).format(Math.round(a), i.unit);
40
+ }
41
+ export { formatRelativeTime };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@atproto-ui/core",
3
+ "version": "0.13.0",
4
+ "type": "module",
5
+ "description": "Framework-agnostic AT Protocol utilities: cache, client, types, and helpers.",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "scripts": {
23
+ "build": "vite build && tsc -p tsconfig.json --emitDeclarationOnly",
24
+ "lint": "tsc -p tsconfig.json --noEmit"
25
+ },
26
+ "dependencies": {
27
+ "@atcute/atproto": "^4.0.0",
28
+ "@atcute/bluesky": "^4.0.2",
29
+ "@atcute/client": "^5.0.0",
30
+ "@atcute/identity": "^2.0.0",
31
+ "@atcute/identity-resolver": "^2.0.0",
32
+ "@atcute/lexicons": "^2.0.0",
33
+ "@atcute/tangled": "^2.0.2"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^24.12.4",
37
+ "typescript": "~5.9.3",
38
+ "unplugin-dts": "^1.0.1",
39
+ "vite": "npm:rolldown-vite@7.1.14"
40
+ }
41
+ }