your personal website on atproto - mirror
blento.app
1import { parseUri, getRecord } from '$lib/atproto';
2import type { CardDefinition } from '../../types';
3import CreateEventCardModal from './CreateEventCardModal.svelte';
4import EventCard from './EventCard.svelte';
5import type { Did } from '@atcute/lexicons';
6
7const EVENT_COLLECTION = 'community.lexicon.calendar.event';
8
9export type EventData = {
10 mode: string;
11 name: string;
12 status: string;
13 startsAt: string;
14 endsAt?: string;
15 description?: string;
16 locations?: Array<{
17 $type: string;
18 address?: {
19 locality?: string;
20 region?: string;
21 country?: string;
22 };
23 }>;
24 media?: Array<{
25 alt?: string;
26 role?: string;
27 content?: {
28 $type: 'blob';
29 ref: {
30 $link: string;
31 };
32 mimeType?: string;
33 };
34 aspect_ratio?: {
35 width: number;
36 height: number;
37 };
38 }>;
39 facets?: Array<{
40 index: { byteStart: number; byteEnd: number };
41 features: Array<{ $type: string; [key: string]: unknown }>;
42 }>;
43 uris?: Array<{
44 uri: string;
45 name?: string;
46 }>;
47 url?: string;
48};
49
50export const EventCardDefinition = {
51 type: 'event',
52 contentComponent: EventCard,
53 creationModalComponent: CreateEventCardModal,
54 createNew: (card) => {
55 card.w = 4;
56 card.h = 4;
57 card.mobileW = 8;
58 card.mobileH = 6;
59 },
60
61 loadData: async (items) => {
62 const eventDataMap: Record<string, EventData> = {};
63
64 for (const item of items) {
65 const uri = item.cardData?.uri;
66 if (!uri) continue;
67
68 const parsedUri = parseUri(uri);
69 if (!parsedUri || !parsedUri.rkey || !parsedUri.repo) continue;
70
71 try {
72 const record = await getRecord({
73 did: parsedUri.repo as Did,
74 collection: EVENT_COLLECTION,
75 rkey: parsedUri.rkey
76 });
77
78 if (record?.value) {
79 eventDataMap[item.id] = record.value as EventData;
80 }
81 } catch (error) {
82 console.error('Failed to fetch event data:', error);
83 }
84 }
85
86 return eventDataMap;
87 },
88
89 onUrlHandler: (url, item) => {
90 // Match smokesignal.events URLs: https://smokesignal.events/{did}/{rkey}
91 const smokesignalMatch = url.match(/^https?:\/\/smokesignal\.events\/(did:[^/]+)\/([^/?#]+)/);
92 if (smokesignalMatch) {
93 const [, did, rkey] = smokesignalMatch;
94 item.w = 4;
95 item.h = 4;
96 item.mobileW = 8;
97 item.mobileH = 6;
98 item.cardType = 'event';
99 item.cardData.uri = `at://${did}/${EVENT_COLLECTION}/${rkey}`;
100 return item;
101 }
102
103 // Match AT URIs: at://{did}/community.lexicon.calendar.event/{rkey}
104 const atUriMatch = url.match(/^at:\/\/(did:[^/]+)\/([^/]+)\/([^/?#]+)/);
105 if (atUriMatch) {
106 const [, did, collection, rkey] = atUriMatch;
107 if (collection === EVENT_COLLECTION) {
108 item.w = 4;
109 item.h = 4;
110 item.mobileW = 8;
111 item.mobileH = 6;
112 item.cardType = 'event';
113 item.cardData.uri = `at://${did}/${collection}/${rkey}`;
114 return item;
115 }
116 }
117
118 return null;
119 },
120
121 urlHandlerPriority: 5,
122
123 name: 'Event',
124
125 keywords: ['calendar', 'meetup', 'schedule', 'date', 'rsvp', 'smokesignal'],
126 groups: ['Social'],
127 icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-4"><path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5" /></svg>`
128} as CardDefinition & { type: 'event' };