@atmo-dev/events-ui 0.1.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.
- package/dist/DatePicker.svelte +231 -0
- package/dist/DatePicker.svelte.d.ts +11 -0
- package/dist/DateTimePicker.svelte +101 -0
- package/dist/DateTimePicker.svelte.d.ts +9 -0
- package/dist/EventAttendees.svelte +203 -0
- package/dist/EventAttendees.svelte.d.ts +13 -0
- package/dist/EventCard.svelte +131 -0
- package/dist/EventCard.svelte.d.ts +8 -0
- package/dist/EventComments.svelte +99 -0
- package/dist/EventComments.svelte.d.ts +6 -0
- package/dist/EventEditor.svelte +589 -0
- package/dist/EventEditor.svelte.d.ts +20 -0
- package/dist/EventRsvp.svelte +237 -0
- package/dist/EventRsvp.svelte.d.ts +17 -0
- package/dist/EventView.svelte +433 -0
- package/dist/EventView.svelte.d.ts +16 -0
- package/dist/ImageDropper.svelte +66 -0
- package/dist/ImageDropper.svelte.d.ts +7 -0
- package/dist/Map.svelte +27 -0
- package/dist/Map.svelte.d.ts +8 -0
- package/dist/PostToBlueskyModal.svelte +244 -0
- package/dist/PostToBlueskyModal.svelte.d.ts +22 -0
- package/dist/ShareModal.svelte +160 -0
- package/dist/ShareModal.svelte.d.ts +23 -0
- package/dist/ThemeApply.svelte +50 -0
- package/dist/ThemeApply.svelte.d.ts +7 -0
- package/dist/ThemeBackground.svelte +33 -0
- package/dist/ThemeBackground.svelte.d.ts +7 -0
- package/dist/ThemePicker.svelte +102 -0
- package/dist/ThemePicker.svelte.d.ts +7 -0
- package/dist/ThumbnailPresets.svelte +68 -0
- package/dist/ThumbnailPresets.svelte.d.ts +11 -0
- package/dist/TimePicker.svelte +188 -0
- package/dist/TimePicker.svelte.d.ts +9 -0
- package/dist/TimezonePicker.svelte +132 -0
- package/dist/TimezonePicker.svelte.d.ts +6 -0
- package/dist/VodPlayer.svelte +137 -0
- package/dist/VodPlayer.svelte.d.ts +14 -0
- package/dist/VodTranscript.svelte +72 -0
- package/dist/VodTranscript.svelte.d.ts +8 -0
- package/dist/atproto-helpers.d.ts +21 -0
- package/dist/atproto-helpers.js +61 -0
- package/dist/cal/helper.d.ts +1 -0
- package/dist/cal/helper.js +20 -0
- package/dist/cal/ical.d.ts +22 -0
- package/dist/cal/ical.js +188 -0
- package/dist/cal/sanitize.d.ts +3 -0
- package/dist/cal/sanitize.js +25 -0
- package/dist/contrail.d.ts +54 -0
- package/dist/contrail.js +22 -0
- package/dist/date-format.d.ts +22 -0
- package/dist/date-format.js +43 -0
- package/dist/editor/LinksSection.svelte +144 -0
- package/dist/editor/LinksSection.svelte.d.ts +10 -0
- package/dist/editor/LocationSection.svelte +215 -0
- package/dist/editor/LocationSection.svelte.d.ts +8 -0
- package/dist/editor/RecurringModal.svelte +270 -0
- package/dist/editor/RecurringModal.svelte.d.ts +30 -0
- package/dist/editor/ThemeSection.svelte +39 -0
- package/dist/editor/ThemeSection.svelte.d.ts +7 -0
- package/dist/editor/ThumbnailSection.svelte +219 -0
- package/dist/editor/ThumbnailSection.svelte.d.ts +13 -0
- package/dist/editor/adapter.d.ts +98 -0
- package/dist/editor/adapter.js +9 -0
- package/dist/editor/save.d.ts +42 -0
- package/dist/editor/save.js +154 -0
- package/dist/editor/types.d.ts +39 -0
- package/dist/editor/types.js +9 -0
- package/dist/event-types.d.ts +70 -0
- package/dist/event-types.js +11 -0
- package/dist/event-view/AddToCalendarButton.svelte +42 -0
- package/dist/event-view/AddToCalendarButton.svelte.d.ts +9 -0
- package/dist/event-view/EventBadges.svelte +20 -0
- package/dist/event-view/EventBadges.svelte.d.ts +7 -0
- package/dist/event-view/EventDateBlock.svelte +43 -0
- package/dist/event-view/EventDateBlock.svelte.d.ts +7 -0
- package/dist/event-view/EventHostedBy.svelte +63 -0
- package/dist/event-view/EventHostedBy.svelte.d.ts +16 -0
- package/dist/event-view/EventLinksList.svelte +37 -0
- package/dist/event-view/EventLinksList.svelte.d.ts +9 -0
- package/dist/event-view/EventLocationBlock.svelte +48 -0
- package/dist/event-view/EventLocationBlock.svelte.d.ts +7 -0
- package/dist/event-view/EventLocationMap.svelte +72 -0
- package/dist/event-view/EventLocationMap.svelte.d.ts +8 -0
- package/dist/event-view/ExternalRsvpNotice.svelte +44 -0
- package/dist/event-view/ExternalRsvpNotice.svelte.d.ts +6 -0
- package/dist/event-view/InviteShareFlow.svelte +177 -0
- package/dist/event-view/InviteShareFlow.svelte.d.ts +15 -0
- package/dist/event-view/StreamPlacePlayer.svelte +222 -0
- package/dist/event-view/StreamPlacePlayer.svelte.d.ts +8 -0
- package/dist/event-view/format.d.ts +26 -0
- package/dist/event-view/format.js +145 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +18 -0
- package/dist/profile-url.d.ts +1 -0
- package/dist/profile-url.js +7 -0
- package/dist/theme.d.ts +9 -0
- package/dist/theme.js +22 -0
- package/dist/themes/Blobs.svelte +35 -0
- package/dist/themes/Blobs.svelte.d.ts +26 -0
- package/dist/themes/Butterflies.svelte +185 -0
- package/dist/themes/Butterflies.svelte.d.ts +3 -0
- package/dist/themes/Fireflies.svelte +134 -0
- package/dist/themes/Fireflies.svelte.d.ts +3 -0
- package/dist/themes/Kaleidoscope.svelte +177 -0
- package/dist/themes/Kaleidoscope.svelte.d.ts +3 -0
- package/dist/themes/Matrix.svelte +150 -0
- package/dist/themes/Matrix.svelte.d.ts +3 -0
- package/dist/themes/Stars.svelte +98 -0
- package/dist/themes/Stars.svelte.d.ts +3 -0
- package/dist/thumbnails/designs.d.ts +18 -0
- package/dist/thumbnails/designs.js +316 -0
- package/package.json +95 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getCDNImageBlobUrl } from './atproto-helpers.js';
|
|
3
|
+
import { eventUrl, isEventOngoing, type FlatEventRecord } from './contrail.js';
|
|
4
|
+
import Avatar from 'svelte-boring-avatars';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
event,
|
|
8
|
+
actor
|
|
9
|
+
}: {
|
|
10
|
+
event: FlatEventRecord;
|
|
11
|
+
actor?: string;
|
|
12
|
+
} = $props();
|
|
13
|
+
|
|
14
|
+
function formatDateTime(dateStr: string): string {
|
|
15
|
+
return new Date(dateStr).toLocaleDateString('en-US', {
|
|
16
|
+
weekday: 'short',
|
|
17
|
+
month: 'short',
|
|
18
|
+
day: 'numeric',
|
|
19
|
+
hour: 'numeric',
|
|
20
|
+
minute: '2-digit'
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getModeLabel(mode: string | undefined): string | undefined {
|
|
25
|
+
if (!mode) return undefined;
|
|
26
|
+
if (mode.includes('virtual')) return 'Virtual';
|
|
27
|
+
if (mode.includes('hybrid')) return 'Hybrid';
|
|
28
|
+
if (mode.includes('inperson')) return 'In-Person';
|
|
29
|
+
return 'Event';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getLocationString(locations: FlatEventRecord['locations']): string | undefined {
|
|
33
|
+
if (!locations?.length) return undefined;
|
|
34
|
+
|
|
35
|
+
const loc = locations.find((v) => v.$type === 'community.lexicon.location.address') as
|
|
36
|
+
| { locality?: string; region?: string }
|
|
37
|
+
| undefined;
|
|
38
|
+
if (!loc) return undefined;
|
|
39
|
+
|
|
40
|
+
return [loc.locality, loc.region].filter(Boolean).join(', ') || undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getThumbnail(event: FlatEventRecord): { url: string; alt: string } | null {
|
|
44
|
+
const media = event.media?.find((m) => m.role === 'thumbnail');
|
|
45
|
+
if (media?.content) {
|
|
46
|
+
const url = getCDNImageBlobUrl({ did: event.did, blob: media.content });
|
|
47
|
+
if (url) return { url, alt: media.alt || event.name };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const banner = event.media?.find((m) => m.role === 'header');
|
|
51
|
+
if (banner?.content) {
|
|
52
|
+
const url = getCDNImageBlobUrl({ did: event.did, blob: banner.content });
|
|
53
|
+
if (url) return { url, alt: banner.alt || event.name };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let thumbnail = $derived(getThumbnail(event));
|
|
60
|
+
let location = $derived(getLocationString(event.locations));
|
|
61
|
+
let mode = $derived(getModeLabel(event.mode));
|
|
62
|
+
let isOngoing = $derived(isEventOngoing(event.startsAt, event.endsAt));
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<a
|
|
66
|
+
href={eventUrl(event, actor)}
|
|
67
|
+
class="group grid grid-cols-[4rem_1fr] gap-3 transition-colors sm:grid-cols-[5rem_1fr] sm:gap-4"
|
|
68
|
+
>
|
|
69
|
+
<div class="w-full">
|
|
70
|
+
{#if thumbnail}
|
|
71
|
+
<img
|
|
72
|
+
src={thumbnail.url}
|
|
73
|
+
alt={thumbnail.alt}
|
|
74
|
+
class="border-base-200 dark:border-base-800 aspect-square w-full rounded-2xl border object-cover"
|
|
75
|
+
/>
|
|
76
|
+
{:else}
|
|
77
|
+
<div
|
|
78
|
+
class="border-base-200 dark:border-base-800 aspect-square w-full overflow-hidden rounded-2xl border [&>svg]:h-full [&>svg]:w-full"
|
|
79
|
+
>
|
|
80
|
+
<Avatar
|
|
81
|
+
size={80}
|
|
82
|
+
name={event.rkey}
|
|
83
|
+
variant="marble"
|
|
84
|
+
colors={['#92A1C6', '#146A7C', '#F0AB3D', '#C271B4', '#C20D90']}
|
|
85
|
+
square
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
{/if}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div class="min-w-0 self-center">
|
|
92
|
+
<p class="text-base-500 dark:text-base-400 flex items-center gap-1.5 text-xs font-medium">
|
|
93
|
+
{formatDateTime(event.startsAt)}
|
|
94
|
+
{#if isOngoing}
|
|
95
|
+
<span class="inline-flex items-center gap-1 rounded-full bg-accent-100 px-1.5 py-0.5 text-[10px] font-semibold text-accent-700 dark:bg-accent-900/30 dark:text-accent-400">
|
|
96
|
+
<span class="size-1.5 rounded-full bg-accent-500 animate-pulse"></span>
|
|
97
|
+
Live
|
|
98
|
+
</span>
|
|
99
|
+
{/if}
|
|
100
|
+
</p>
|
|
101
|
+
<h3
|
|
102
|
+
class="text-base-900 dark:text-base-50 group-hover:text-base-700 dark:group-hover:text-base-200 mt-0.5 flex items-start gap-1.5 text-sm leading-snug font-semibold transition-colors sm:text-base"
|
|
103
|
+
>
|
|
104
|
+
{#if event.space}
|
|
105
|
+
<svg
|
|
106
|
+
viewBox="0 0 24 24"
|
|
107
|
+
fill="none"
|
|
108
|
+
stroke="currentColor"
|
|
109
|
+
stroke-width="2"
|
|
110
|
+
stroke-linecap="round"
|
|
111
|
+
stroke-linejoin="round"
|
|
112
|
+
class="text-base-500 dark:text-base-400 mt-1 size-3.5 shrink-0"
|
|
113
|
+
aria-label="Private event"
|
|
114
|
+
>
|
|
115
|
+
<rect width="18" height="11" x="3" y="11" rx="2" ry="2" />
|
|
116
|
+
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
117
|
+
</svg>
|
|
118
|
+
{/if}
|
|
119
|
+
<span class="line-clamp-2">{event.name}</span>
|
|
120
|
+
</h3>
|
|
121
|
+
{#if location || mode}
|
|
122
|
+
<p class="text-base-500 dark:text-base-400 mt-1 text-xs">
|
|
123
|
+
{#if location}{location}{/if}
|
|
124
|
+
{#if location && mode}
|
|
125
|
+
<span class="mx-1">·</span>
|
|
126
|
+
{/if}
|
|
127
|
+
{#if mode}{mode}{/if}
|
|
128
|
+
</p>
|
|
129
|
+
{/if}
|
|
130
|
+
</div>
|
|
131
|
+
</a>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type FlatEventRecord } from './contrail.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
event: FlatEventRecord;
|
|
4
|
+
actor?: string;
|
|
5
|
+
};
|
|
6
|
+
declare const EventCard: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type EventCard = ReturnType<typeof EventCard>;
|
|
8
|
+
export default EventCard;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
NestedComments,
|
|
4
|
+
blueskyPostToPostData,
|
|
5
|
+
type PostData
|
|
6
|
+
} from '@foxui/social';
|
|
7
|
+
|
|
8
|
+
let { postUri }: { postUri: string } = $props();
|
|
9
|
+
|
|
10
|
+
let comments = $state<PostData[]>([]);
|
|
11
|
+
let loading = $state(true);
|
|
12
|
+
let errorMessage = $state<string | null>(null);
|
|
13
|
+
|
|
14
|
+
function threadToComments(replies: unknown[]): PostData[] {
|
|
15
|
+
return (replies as Array<{ $type?: string; post?: unknown; replies?: unknown[] }>)
|
|
16
|
+
.filter((r) => r.$type === 'app.bsky.feed.defs#threadViewPost' && r.post)
|
|
17
|
+
.map((r) => {
|
|
18
|
+
const { postData, embeds } = blueskyPostToPostData(
|
|
19
|
+
r.post as Parameters<typeof blueskyPostToPostData>[0]
|
|
20
|
+
);
|
|
21
|
+
postData.embeds = embeds;
|
|
22
|
+
if (r.replies?.length) {
|
|
23
|
+
postData.replies = threadToComments(r.replies);
|
|
24
|
+
}
|
|
25
|
+
return postData;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function bskyWebUrl(atUri: string): string | null {
|
|
30
|
+
const m = atUri.match(/^at:\/\/([^/]+)\/app\.bsky\.feed\.post\/(.+)$/);
|
|
31
|
+
if (!m) return null;
|
|
32
|
+
return `https://bsky.app/profile/${m[1]}/post/${m[2]}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function loadThread(uri: string) {
|
|
36
|
+
loading = true;
|
|
37
|
+
errorMessage = null;
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetch(
|
|
40
|
+
`https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(uri)}&depth=6`
|
|
41
|
+
);
|
|
42
|
+
if (!res.ok) throw new Error(`Failed to load thread (${res.status})`);
|
|
43
|
+
const data = (await res.json()) as { thread?: { replies?: unknown[] } };
|
|
44
|
+
const thread = data.thread;
|
|
45
|
+
comments = thread?.replies?.length ? threadToComments(thread.replies) : [];
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error('EventComments: load failed', err);
|
|
48
|
+
errorMessage = err instanceof Error ? err.message : 'Failed to load comments';
|
|
49
|
+
comments = [];
|
|
50
|
+
} finally {
|
|
51
|
+
loading = false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
$effect(() => {
|
|
56
|
+
if (postUri) loadThread(postUri);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
let replyUrl = $derived(bskyWebUrl(postUri));
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<style>
|
|
63
|
+
.comments-no-divider :global(> div) {
|
|
64
|
+
border-top-width: 0;
|
|
65
|
+
}
|
|
66
|
+
</style>
|
|
67
|
+
|
|
68
|
+
<div>
|
|
69
|
+
{#if replyUrl}
|
|
70
|
+
<div class="mb-4 text-sm">
|
|
71
|
+
<a
|
|
72
|
+
href={replyUrl}
|
|
73
|
+
target="_blank"
|
|
74
|
+
rel="noopener"
|
|
75
|
+
class="text-accent-600 hover:text-accent-700 dark:text-accent-400 dark:hover:text-accent-300 font-medium"
|
|
76
|
+
>
|
|
77
|
+
Add a comment on bluesky →
|
|
78
|
+
</a>
|
|
79
|
+
</div>
|
|
80
|
+
{/if}
|
|
81
|
+
|
|
82
|
+
{#if loading}
|
|
83
|
+
<p class="text-base-500 dark:text-base-400 text-sm">Loading comments…</p>
|
|
84
|
+
{:else if errorMessage}
|
|
85
|
+
<p class="text-base-500 dark:text-base-400 text-sm">{errorMessage}</p>
|
|
86
|
+
{:else if comments.length === 0}
|
|
87
|
+
<p class="text-base-500 dark:text-base-400 text-sm">No comments yet.</p>
|
|
88
|
+
{:else}
|
|
89
|
+
<div class="not-prose comments-no-divider">
|
|
90
|
+
<NestedComments
|
|
91
|
+
{comments}
|
|
92
|
+
actions={(comment) => ({
|
|
93
|
+
reply: { count: comment.replyCount, href: comment.href },
|
|
94
|
+
like: { count: comment.likeCount, href: comment.href }
|
|
95
|
+
})}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
{/if}
|
|
99
|
+
</div>
|