···11<script lang="ts">
22- import type { FeedViewPost } from '@atproto/api/dist/client/types/app/bsky/feed/defs';
32 import { Post } from '../post';
43 import { blueskyPostToPostData } from '.';
54 import type { Snippet } from 'svelte';
55+ import type { PostView } from '@atcute/bluesky/types/app/feed/defs';
6677 let {
88 feedViewPost,
99 children,
1010 showLogo = false,
1111 ...restProps
1212- }: { feedViewPost?: FeedViewPost; children?: Snippet; showLogo?: boolean } = $props();
1212+ }: { feedViewPost?: PostView; children?: Snippet; showLogo?: boolean } = $props();
13131414 const postData = $derived(feedViewPost ? blueskyPostToPostData(feedViewPost) : undefined);
1515</script>
+35-22
src/lib/components/bluesky-post/index.ts
···11-import { RichText } from '@atproto/api';
21import type { PostData, PostEmbed } from '../post';
33-import type { PostView } from '@atproto/api/dist/client/types/app/bsky/feed/defs';
22+import type { PostView } from '@atcute/bluesky/types/app/feed/defs';
33+import { segmentize, type Facet, type RichtextSegment } from '@atcute/bluesky-richtext-segmenter';
4455function blueskyEmbedTypeToEmbedType(type: string) {
66 switch (type) {
···4848 // }
4949 // : undefined,
5050 author: {
5151- displayName: post.author.displayName,
5151+ displayName: post.author.displayName || '',
5252 handle: post.author.handle,
5353 avatar: post.author.avatar,
5454 href: `${baseUrl}/profile/${post.author.did}`
···5656 replyCount: post.replyCount ?? 0,
5757 repostCount: post.repostCount ?? 0,
5858 likeCount: post.likeCount ?? 0,
5959- createdAt: post.record.createdAt ?? 0,
5959+ createdAt: post.record.createdAt as string,
60606161 embed: post.embed
6262 ? ({
···9191 };
9292}
93939494-// eslint-disable-next-line @typescript-eslint/no-explicit-any
9595-export function blueskyPostToHTML(post: any, baseUrl: string = 'https://bsky.app') {
9696- if (!post?.record) {
9797- return '';
9494+const renderSegment = (segment: RichtextSegment, baseUrl: string) => {
9595+ const { text, features } = segment;
9696+9797+ if (!features) {
9898+ return `<span>${text}</span>`;
9899 }
9999- const rt = new RichText(post.record);
100100- let html = '';
100100+101101+ // segments can have multiple features, use the first one
102102+ const feature = features[0];
101103102104 const createLink = (href: string, text: string) => {
103103- return `<a target="_blank" rel="noopener noreferrer nofollow" href="${encodeURI(href)}">${encodeURI(text)}</a>`;
105105+ return `<a target="_blank" rel="noopener noreferrer nofollow" href="${encodeURI(href)}">${text}</a>`;
104106 };
105107106106- for (const segment of rt.segments()) {
107107- if (!segment) continue;
108108- if (segment.isLink() && segment.link?.uri) {
109109- html += createLink(segment.link?.uri, segment.text);
110110- } else if (segment.isMention() && segment.mention?.did) {
111111- html += createLink(`${baseUrl}/profile/${segment.mention?.did}`, segment.text);
112112- } else if (segment.isTag() && segment.tag?.tag) {
113113- html += createLink(`${baseUrl}/hashtag/${segment.tag?.tag}`, segment.text);
114114- } else {
115115- html += segment.text;
116116- }
108108+ switch (feature.$type) {
109109+ case 'app.bsky.richtext.facet#mention':
110110+ return createLink(`${baseUrl}/profile/${segment.handle}`, segment.text);
111111+ case 'app.bsky.richtext.facet#link':
112112+ return createLink(feature.uri, segment.text);
113113+ case 'app.bsky.richtext.facet#tag':
114114+ return createLink(`${baseUrl}/hashtag/${segment.tag}`, segment.text);
115115+ default:
116116+ return `<span>${text}</span>`;
117117 }
118118+};;
119119+120120+const RichText = ({ text, facets }: { text: string; facets?: Facet[] }, baseUrl: string) => {
121121+ const segments = segmentize(text, facets);
122122+ return segments.map((v) => renderSegment(v, baseUrl)).join('');
123123+};
124124+125125+export function blueskyPostToHTML(post: PostView, baseUrl: string = 'https://bsky.app') {
126126+ if (!post?.record) {
127127+ return '';
128128+ }
129129+130130+ const html = RichText({ text: post.record.text, facets: post.record.facets }, baseUrl);
118131119132 return html.replace(/\n/g, '<br>');
120133}
+2-2
src/lib/helper.ts
···33import { CardDefinitionsByType } from './cards';
44import { deleteRecord, putRecord } from '$lib/atproto';
55import { toast } from '@foxui/core';
66-import { TID } from '@atproto/common-web';
66+import * as TID from '@atcute/tid';
7788export function clamp(value: number, min: number, max: number): number {
99 return Math.min(Math.max(value, min), max);
···498498499499export function createEmptyCard(page: string) {
500500 return {
501501- id: TID.nextStr(),
501501+ id: TID.now(),
502502 x: 0,
503503 y: 0,
504504 w: 2,
+5-4
src/lib/types.ts
···11-import type { At } from '@atcute/client/lexicons';
22-import type { ProfileViewDetailed } from '@atproto/api/dist/client/types/app/bsky/actor/defs';
11+import type { Blob } from '@atcute/lexicons';
22+import type { AppBskyActorDefs } from '@atcute/bluesky';
3344export type Item = {
55 id: string;
···3939 url?: string;
4040 name?: string;
4141 description?: string;
4242- icon?: At.Blob;
4242+ icon?: Blob;
4343 preferences?: {
4444 /**
4545 * @deprecated
···4747 * use hideProfileSection instead
4848 */
4949 hideProfile?: boolean;
5050+5051 // use this instead
5152 hideProfileSection?: boolean;
5253 };
5354 }
5455 | undefined;
5555- profile: ProfileViewDetailed;
5656+ profile: AppBskyActorDefs.ProfileViewDetailed;
56575758 additionalData: Record<string, unknown>;
5859 updatedAt: number;
+6-7
src/lib/website/load.ts
···11import { getDetailedProfile, listRecords, resolveHandle, parseUri } from '$lib/atproto';
22-import type { Record as ListRecord } from '@atproto/api/dist/client/types/com/atproto/repo/listRecords';
32import { CardDefinitionsByType } from '$lib/cards';
43import type { Item, UserCache, WebsiteData } from '$lib/types';
54import { compactItems, fixAllCollisions } from '$lib/helper';
···31303231 result.page = 'blento.' + page;
33323434- result.publication = (result.publications as ListRecord[]).find(
3333+ result.publication = (result.publications as Awaited<ReturnType<typeof listRecords>>).find(
3534 (v) => parseUri(v.uri).rkey === result.page
3635 )?.value;
3736···65646665 const cards = await listRecords({ did, collection: 'app.blento.card' }).catch(() => {
6766 console.error('error getting records for collection app.blento.card');
6868- return [] as ListRecord[];
6767+ return [] as Awaited<ReturnType<typeof listRecords>>;
6968 });
70697170 const publications = await listRecords({ did, collection: 'site.standard.publication' }).catch(
7271 () => {
7372 console.error('error getting records for collection site.standard.publication');
7474- return [] as ListRecord[];
7373+ return [] as Awaited<ReturnType<typeof listRecords>>;
7574 }
7675 );
7776···128127 await cache?.put?.(handle, stringifiedResult);
129128130129 const parsedResult = JSON.parse(stringifiedResult);
131131- parsedResult.publication = (parsedResult.publications as ListRecord[]).find(
132132- (v) => parseUri(v.uri).rkey === parsedResult.page
133133- )?.value;
130130+ parsedResult.publication = (
131131+ parsedResult.publications as Awaited<ReturnType<typeof listRecords>>
132132+ ).find((v) => parseUri(v.uri).rkey === parsedResult.page)?.value;
134133135134 delete parsedResult['publications'];
136135
+2-2
src/routes/all/+page.server.ts
···22import type { UserCache, WebsiteData } from '$lib/types.js';
33import { loadData } from '$lib/website/load';
44import type { Handle } from '@atcute/lexicons';
55-import type { ProfileViewDetailed } from '@atproto/api/dist/client/types/app/bsky/actor/defs.js';
55+import type { AppBskyActorDefs } from '@atcute/bluesky';
6677export async function load({ platform }) {
88 const cache = platform?.env?.USER_DATA_CACHE;
991010 const list = await cache?.list();
11111212- const profiles: ProfileViewDetailed[] = [];
1212+ const profiles: AppBskyActorDefs.ProfileViewDetailed[] = [];
1313 for (const value of list?.keys ?? []) {
1414 // check if at least one card
1515 const result = await cache?.get(value.name);