@ewanc26/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/README.md +65 -0
- package/dist/components/layout/ThemeToggle.svelte +50 -0
- package/dist/components/layout/ThemeToggle.svelte.d.ts +4 -0
- package/dist/components/layout/ThemeToggle.svelte.d.ts.map +1 -0
- package/dist/components/layout/WolfToggle.svelte +19 -0
- package/dist/components/layout/WolfToggle.svelte.d.ts +4 -0
- package/dist/components/layout/WolfToggle.svelte.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts +5 -0
- package/dist/components/layout/index.d.ts.map +1 -0
- package/dist/components/layout/index.js +4 -0
- package/dist/components/layout/main/DynamicLinks.svelte +63 -0
- package/dist/components/layout/main/DynamicLinks.svelte.d.ts +7 -0
- package/dist/components/layout/main/DynamicLinks.svelte.d.ts.map +1 -0
- package/dist/components/layout/main/ScrollToTop.svelte +36 -0
- package/dist/components/layout/main/ScrollToTop.svelte.d.ts +4 -0
- package/dist/components/layout/main/ScrollToTop.svelte.d.ts.map +1 -0
- package/dist/components/layout/main/card/BlueskyPostCard.svelte +261 -0
- package/dist/components/layout/main/card/BlueskyPostCard.svelte.d.ts +9 -0
- package/dist/components/layout/main/card/BlueskyPostCard.svelte.d.ts.map +1 -0
- package/dist/components/layout/main/card/KibunStatusCard.svelte +48 -0
- package/dist/components/layout/main/card/KibunStatusCard.svelte.d.ts +8 -0
- package/dist/components/layout/main/card/KibunStatusCard.svelte.d.ts.map +1 -0
- package/dist/components/layout/main/card/LinkCard.svelte +63 -0
- package/dist/components/layout/main/card/LinkCard.svelte.d.ts +17 -0
- package/dist/components/layout/main/card/LinkCard.svelte.d.ts.map +1 -0
- package/dist/components/layout/main/card/MusicStatusCard.svelte +101 -0
- package/dist/components/layout/main/card/MusicStatusCard.svelte.d.ts +8 -0
- package/dist/components/layout/main/card/MusicStatusCard.svelte.d.ts.map +1 -0
- package/dist/components/layout/main/card/PostCard.svelte +46 -0
- package/dist/components/layout/main/card/PostCard.svelte.d.ts +8 -0
- package/dist/components/layout/main/card/PostCard.svelte.d.ts.map +1 -0
- package/dist/components/layout/main/card/ProfileCard.svelte +70 -0
- package/dist/components/layout/main/card/ProfileCard.svelte.d.ts +8 -0
- package/dist/components/layout/main/card/ProfileCard.svelte.d.ts.map +1 -0
- package/dist/components/layout/main/card/TangledRepoCard.svelte +80 -0
- package/dist/components/layout/main/card/TangledRepoCard.svelte.d.ts +11 -0
- package/dist/components/layout/main/card/TangledRepoCard.svelte.d.ts.map +1 -0
- package/dist/components/layout/main/card/index.d.ts +8 -0
- package/dist/components/layout/main/card/index.d.ts.map +1 -0
- package/dist/components/layout/main/card/index.js +7 -0
- package/dist/components/layout/main/index.d.ts +4 -0
- package/dist/components/layout/main/index.d.ts.map +1 -0
- package/dist/components/layout/main/index.js +3 -0
- package/dist/components/seo/MetaTags.svelte +39 -0
- package/dist/components/seo/MetaTags.svelte.d.ts +9 -0
- package/dist/components/seo/MetaTags.svelte.d.ts.map +1 -0
- package/dist/components/seo/index.d.ts +2 -0
- package/dist/components/seo/index.d.ts.map +1 -0
- package/dist/components/seo/index.js +1 -0
- package/dist/components/ui/BlogPostCard.svelte +44 -0
- package/dist/components/ui/BlogPostCard.svelte.d.ts +9 -0
- package/dist/components/ui/BlogPostCard.svelte.d.ts.map +1 -0
- package/dist/components/ui/Card.svelte +144 -0
- package/dist/components/ui/Card.svelte.d.ts +27 -0
- package/dist/components/ui/Card.svelte.d.ts.map +1 -0
- package/dist/components/ui/DocumentCard.svelte +42 -0
- package/dist/components/ui/DocumentCard.svelte.d.ts +9 -0
- package/dist/components/ui/DocumentCard.svelte.d.ts.map +1 -0
- package/dist/components/ui/Dropdown.svelte +36 -0
- package/dist/components/ui/Dropdown.svelte.d.ts +15 -0
- package/dist/components/ui/Dropdown.svelte.d.ts.map +1 -0
- package/dist/components/ui/InternalCard.svelte +41 -0
- package/dist/components/ui/InternalCard.svelte.d.ts +14 -0
- package/dist/components/ui/InternalCard.svelte.d.ts.map +1 -0
- package/dist/components/ui/Pagination.svelte +74 -0
- package/dist/components/ui/Pagination.svelte.d.ts +11 -0
- package/dist/components/ui/Pagination.svelte.d.ts.map +1 -0
- package/dist/components/ui/PostsGroupedView.svelte +40 -0
- package/dist/components/ui/PostsGroupedView.svelte.d.ts +10 -0
- package/dist/components/ui/PostsGroupedView.svelte.d.ts.map +1 -0
- package/dist/components/ui/SearchBar.svelte +26 -0
- package/dist/components/ui/SearchBar.svelte.d.ts +9 -0
- package/dist/components/ui/SearchBar.svelte.d.ts.map +1 -0
- package/dist/components/ui/Tabs.svelte +25 -0
- package/dist/components/ui/Tabs.svelte.d.ts +13 -0
- package/dist/components/ui/Tabs.svelte.d.ts.map +1 -0
- package/dist/components/ui/index.d.ts +11 -0
- package/dist/components/ui/index.d.ts.map +1 -0
- package/dist/components/ui/index.js +10 -0
- package/dist/config/themes.config.d.ts +23 -0
- package/dist/config/themes.config.d.ts.map +1 -0
- package/dist/config/themes.config.js +116 -0
- package/dist/helper/badges.d.ts +9 -0
- package/dist/helper/badges.d.ts.map +1 -0
- package/dist/helper/badges.js +28 -0
- package/dist/helper/posts.d.ts +14 -0
- package/dist/helper/posts.d.ts.map +1 -0
- package/dist/helper/posts.js +47 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/stores/colorTheme.d.ts +12 -0
- package/dist/stores/colorTheme.d.ts.map +1 -0
- package/dist/stores/colorTheme.js +36 -0
- package/dist/stores/dropdownState.d.ts +2 -0
- package/dist/stores/dropdownState.d.ts.map +1 -0
- package/dist/stores/dropdownState.js +2 -0
- package/dist/stores/happyMac.d.ts +11 -0
- package/dist/stores/happyMac.d.ts.map +1 -0
- package/dist/stores/happyMac.js +19 -0
- package/dist/stores/index.d.ts +6 -0
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/index.js +4 -0
- package/dist/stores/wolfMode.d.ts +7 -0
- package/dist/stores/wolfMode.d.ts.map +1 -0
- package/dist/stores/wolfMode.js +130 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/formatNumber.d.ts +3 -0
- package/dist/utils/formatNumber.d.ts.map +1 -0
- package/dist/utils/formatNumber.js +26 -0
- package/dist/utils/locale.d.ts +4 -0
- package/dist/utils/locale.d.ts.map +1 -0
- package/dist/utils/locale.js +32 -0
- package/package.json +45 -0
- package/src/lib/components/layout/ThemeToggle.svelte +50 -0
- package/src/lib/components/layout/WolfToggle.svelte +19 -0
- package/src/lib/components/layout/index.ts +4 -0
- package/src/lib/components/layout/main/DynamicLinks.svelte +63 -0
- package/src/lib/components/layout/main/ScrollToTop.svelte +36 -0
- package/src/lib/components/layout/main/card/BlueskyPostCard.svelte +261 -0
- package/src/lib/components/layout/main/card/KibunStatusCard.svelte +48 -0
- package/src/lib/components/layout/main/card/LinkCard.svelte +63 -0
- package/src/lib/components/layout/main/card/MusicStatusCard.svelte +101 -0
- package/src/lib/components/layout/main/card/PostCard.svelte +46 -0
- package/src/lib/components/layout/main/card/ProfileCard.svelte +70 -0
- package/src/lib/components/layout/main/card/TangledRepoCard.svelte +80 -0
- package/src/lib/components/layout/main/card/index.ts +7 -0
- package/src/lib/components/layout/main/index.ts +3 -0
- package/src/lib/components/seo/MetaTags.svelte +39 -0
- package/src/lib/components/seo/index.ts +1 -0
- package/src/lib/components/ui/BlogPostCard.svelte +44 -0
- package/src/lib/components/ui/Card.svelte +144 -0
- package/src/lib/components/ui/DocumentCard.svelte +42 -0
- package/src/lib/components/ui/Dropdown.svelte +36 -0
- package/src/lib/components/ui/InternalCard.svelte +41 -0
- package/src/lib/components/ui/Pagination.svelte +74 -0
- package/src/lib/components/ui/PostsGroupedView.svelte +40 -0
- package/src/lib/components/ui/SearchBar.svelte +26 -0
- package/src/lib/components/ui/Tabs.svelte +25 -0
- package/src/lib/components/ui/index.ts +10 -0
- package/src/lib/config/themes.config.ts +130 -0
- package/src/lib/helper/badges.ts +44 -0
- package/src/lib/helper/posts.ts +63 -0
- package/src/lib/index.ts +32 -0
- package/src/lib/stores/colorTheme.ts +44 -0
- package/src/lib/stores/dropdownState.ts +3 -0
- package/src/lib/stores/happyMac.ts +28 -0
- package/src/lib/stores/index.ts +5 -0
- package/src/lib/stores/wolfMode.ts +127 -0
- package/src/lib/types/index.ts +19 -0
- package/src/lib/utils/formatNumber.ts +27 -0
- package/src/lib/utils/locale.ts +29 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ExternalLink } from '@lucide/svelte';
|
|
3
|
+
import InternalCard from '../../../ui/InternalCard.svelte';
|
|
4
|
+
|
|
5
|
+
interface Badge { text: string; color?: 'mint' | 'sage'; }
|
|
6
|
+
interface Props {
|
|
7
|
+
url: string;
|
|
8
|
+
title: string;
|
|
9
|
+
emoji?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
badges?: Badge[];
|
|
12
|
+
meta?: string;
|
|
13
|
+
variant?: 'default' | 'button';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let { url, title, emoji, description, badges, meta, variant = 'default' }: Props = $props();
|
|
17
|
+
|
|
18
|
+
function getDomain(url: string): string {
|
|
19
|
+
try { return new URL(url).hostname.replace('www.', ''); } catch { return ''; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let displayDescription = $derived(description || getDomain(url));
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
{#if variant === 'button'}
|
|
26
|
+
<InternalCard href={url} class="flex-row! items-center! justify-center! gap-2!">
|
|
27
|
+
{#snippet children()}
|
|
28
|
+
<span class="font-medium">{title}</span>
|
|
29
|
+
<ExternalLink class="h-4 w-4 shrink-0" aria-hidden="true" />
|
|
30
|
+
{/snippet}
|
|
31
|
+
</InternalCard>
|
|
32
|
+
{:else}
|
|
33
|
+
<InternalCard href={url}>
|
|
34
|
+
{#snippet children()}
|
|
35
|
+
<div class="min-w-0 flex-1 space-y-2">
|
|
36
|
+
{#if emoji || (badges && badges.length > 0)}
|
|
37
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
38
|
+
{#if emoji}<span class="text-lg leading-none">{emoji}</span>{/if}
|
|
39
|
+
{#if badges && badges.length > 0}
|
|
40
|
+
{#each badges as badge}
|
|
41
|
+
{#if badge.color === 'mint'}
|
|
42
|
+
<span class="rounded bg-secondary-100 px-2 py-0.5 text-xs font-medium text-secondary-800 dark:bg-secondary-900 dark:text-secondary-200">{badge.text}</span>
|
|
43
|
+
{:else if badge.color === 'sage'}
|
|
44
|
+
<span class="rounded bg-primary-100 px-2 py-0.5 text-xs font-medium text-primary-800 dark:bg-primary-900 dark:text-primary-200">{badge.text}</span>
|
|
45
|
+
{:else}
|
|
46
|
+
<span class="text-xs font-semibold text-ink-800 uppercase dark:text-ink-100">{badge.text}</span>
|
|
47
|
+
{/if}
|
|
48
|
+
{/each}
|
|
49
|
+
{/if}
|
|
50
|
+
</div>
|
|
51
|
+
{/if}
|
|
52
|
+
<h3 class="overflow-wrap-anywhere font-semibold wrap-break-word text-ink-900 dark:text-ink-50">{title}</h3>
|
|
53
|
+
{#if displayDescription}
|
|
54
|
+
<p class="overflow-wrap-anywhere line-clamp-2 text-sm wrap-break-word text-ink-700 dark:text-ink-200">{displayDescription}</p>
|
|
55
|
+
{/if}
|
|
56
|
+
{#if meta}
|
|
57
|
+
<p class="text-xs font-medium text-ink-800 dark:text-ink-100">{meta}</p>
|
|
58
|
+
{/if}
|
|
59
|
+
</div>
|
|
60
|
+
<ExternalLink class="h-4 w-4 shrink-0 text-ink-700 transition-colors dark:text-ink-200" aria-hidden="true" />
|
|
61
|
+
{/snippet}
|
|
62
|
+
</InternalCard>
|
|
63
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface Badge {
|
|
2
|
+
text: string;
|
|
3
|
+
color?: 'mint' | 'sage';
|
|
4
|
+
}
|
|
5
|
+
interface Props {
|
|
6
|
+
url: string;
|
|
7
|
+
title: string;
|
|
8
|
+
emoji?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
badges?: Badge[];
|
|
11
|
+
meta?: string;
|
|
12
|
+
variant?: 'default' | 'button';
|
|
13
|
+
}
|
|
14
|
+
declare const LinkCard: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type LinkCard = ReturnType<typeof LinkCard>;
|
|
16
|
+
export default LinkCard;
|
|
17
|
+
//# sourceMappingURL=LinkCard.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LinkCard.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/layout/main/card/LinkCard.svelte.ts"],"names":[],"mappings":"AAOC,UAAU,KAAK;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAAE;AAC1D,UAAU,KAAK;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC/B;AA0DF,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Card from '../../../ui/Card.svelte';
|
|
3
|
+
import type { MusicStatusData } from '@ewanc26/atproto';
|
|
4
|
+
import { formatRelativeTime } from '../../../../utils/locale.js';
|
|
5
|
+
import { Music, Disc3, Users, Album, Clock, Radio } from '@lucide/svelte';
|
|
6
|
+
|
|
7
|
+
interface Props { musicStatus?: MusicStatusData | null; }
|
|
8
|
+
let { musicStatus = null }: Props = $props();
|
|
9
|
+
|
|
10
|
+
let artworkError = $state(false);
|
|
11
|
+
|
|
12
|
+
function formatArtists(artists: { artistName: string }[]): string {
|
|
13
|
+
if (!artists?.length) return 'Unknown Artist';
|
|
14
|
+
return artists.map((a) => a.artistName).join(', ');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function formatDuration(seconds?: number): string {
|
|
18
|
+
if (!seconds) return '';
|
|
19
|
+
const minutes = Math.floor(seconds / 60);
|
|
20
|
+
const remainingSeconds = seconds % 60;
|
|
21
|
+
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function formatServiceName(domain?: string): string {
|
|
25
|
+
if (!domain) return '';
|
|
26
|
+
return domain.replace('lastfm', 'Last.fm').replace('last.fm', 'Last.fm');
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<div class="mx-auto w-full max-w-2xl">
|
|
31
|
+
{#if !musicStatus}
|
|
32
|
+
<Card loading={true} variant="elevated" padding="md">
|
|
33
|
+
{#snippet skeleton()}
|
|
34
|
+
<div class="mb-3 flex items-start gap-4">
|
|
35
|
+
<div class="h-20 w-20 shrink-0 rounded-lg bg-canvas-300 dark:bg-canvas-700"></div>
|
|
36
|
+
<div class="flex-1">
|
|
37
|
+
<div class="mb-2 flex items-center gap-2">
|
|
38
|
+
<div class="h-4 w-4 rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
39
|
+
<div class="h-3 w-32 rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="mb-1 h-5 w-3/4 rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
42
|
+
<div class="mb-2 h-4 w-1/2 rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
43
|
+
<div class="h-3 w-40 rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
{/snippet}
|
|
47
|
+
</Card>
|
|
48
|
+
{:else}
|
|
49
|
+
{@const s = musicStatus}
|
|
50
|
+
<Card variant="elevated" padding="md">
|
|
51
|
+
{#snippet children()}
|
|
52
|
+
<div>
|
|
53
|
+
<div class="mb-4 flex items-center gap-2">
|
|
54
|
+
<Music class="h-4 w-4 text-primary-600 dark:text-primary-400" aria-hidden="true" />
|
|
55
|
+
<span class="text-xs font-semibold tracking-wide text-ink-800 uppercase dark:text-ink-100">
|
|
56
|
+
{s.$type === 'fm.teal.alpha.actor.status' ? 'Now Listening' : 'Last Played'}
|
|
57
|
+
</span>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="flex items-start gap-3">
|
|
60
|
+
<div class="shrink-0">
|
|
61
|
+
{#if s.artworkUrl && !artworkError}
|
|
62
|
+
<img src={s.artworkUrl} alt="Album artwork for {s.releaseName || s.trackName}" class="h-20 w-20 rounded-lg object-cover shadow-md" loading="lazy" onerror={() => (artworkError = true)} />
|
|
63
|
+
{:else}
|
|
64
|
+
<div class="flex h-20 w-20 items-center justify-center rounded-lg bg-canvas-200 shadow-md dark:bg-canvas-700">
|
|
65
|
+
<Disc3 class="h-10 w-10 text-ink-500 dark:text-ink-400" aria-hidden="true" />
|
|
66
|
+
</div>
|
|
67
|
+
{/if}
|
|
68
|
+
</div>
|
|
69
|
+
<div class="min-w-0 flex-1">
|
|
70
|
+
<div class="mb-4">
|
|
71
|
+
<a href={s.originUrl || '#'} target="_blank" rel="noopener noreferrer" class="block max-w-full text-lg font-semibold wrap-break-word whitespace-normal text-primary-600 transition-colors hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300" class:pointer-events-none={!s.originUrl} class:cursor-default={!s.originUrl}>
|
|
72
|
+
{s.trackName}
|
|
73
|
+
</a>
|
|
74
|
+
<p class="mt-1 flex max-w-full items-start gap-1.5 text-base wrap-break-word whitespace-normal text-ink-800 dark:text-ink-100">
|
|
75
|
+
<Users class="mt-0.5 h-4 w-4 shrink-0 text-ink-600 dark:text-ink-300" />
|
|
76
|
+
{formatArtists(s.artists)}
|
|
77
|
+
</p>
|
|
78
|
+
{#if s.releaseName}
|
|
79
|
+
<p class="mt-1 flex max-w-full items-start gap-1.5 text-sm wrap-break-word whitespace-normal text-ink-700 dark:text-ink-200">
|
|
80
|
+
<Album class="mt-0.5 h-4 w-4 shrink-0 text-ink-500 dark:text-ink-400" />
|
|
81
|
+
<span>{s.releaseName}{#if s.duration}<span class="ml-1 inline-flex items-center gap-1 text-ink-600 dark:text-ink-300">· <Clock class="h-3 w-3" />{formatDuration(s.duration)}</span>{/if}</span>
|
|
82
|
+
</p>
|
|
83
|
+
{/if}
|
|
84
|
+
</div>
|
|
85
|
+
<div class="flex items-center gap-2 text-xs text-ink-700 dark:text-ink-200">
|
|
86
|
+
<time datetime={s.playedTime}>{formatRelativeTime(s.playedTime)}</time>
|
|
87
|
+
{#if s.musicServiceBaseDomain}
|
|
88
|
+
<span class="text-ink-600 dark:text-ink-300">·</span>
|
|
89
|
+
<a href="https://teal.fm" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-1 transition-colors hover:text-primary-600 dark:hover:text-primary-400" title="Powered by teal.fm">
|
|
90
|
+
<Radio class="h-3 w-3" />
|
|
91
|
+
{formatServiceName(s.musicServiceBaseDomain)} via {s.submissionClientAgent}
|
|
92
|
+
</a>
|
|
93
|
+
{/if}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
{/snippet}
|
|
99
|
+
</Card>
|
|
100
|
+
{/if}
|
|
101
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MusicStatusData } from '@ewanc26/atproto';
|
|
2
|
+
interface Props {
|
|
3
|
+
musicStatus?: MusicStatusData | null;
|
|
4
|
+
}
|
|
5
|
+
declare const MusicStatusCard: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type MusicStatusCard = ReturnType<typeof MusicStatusCard>;
|
|
7
|
+
export default MusicStatusCard;
|
|
8
|
+
//# sourceMappingURL=MusicStatusCard.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MusicStatusCard.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/layout/main/card/MusicStatusCard.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAKvD,UAAU,KAAK;IAAG,WAAW,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAAE;AAwG1D,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Card from '../../../ui/Card.svelte';
|
|
3
|
+
import DocumentCard from '../../../ui/DocumentCard.svelte';
|
|
4
|
+
import type { StandardSiteDocument } from '@ewanc26/atproto';
|
|
5
|
+
|
|
6
|
+
interface Props { documents?: StandardSiteDocument[] | null; }
|
|
7
|
+
let { documents = null }: Props = $props();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<div class="mx-auto w-full max-w-2xl">
|
|
11
|
+
{#if !documents}
|
|
12
|
+
<Card loading={true} variant="elevated" padding="md">
|
|
13
|
+
{#snippet skeleton()}
|
|
14
|
+
<div class="mb-4 h-6 w-32 rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
15
|
+
<div class="space-y-3">
|
|
16
|
+
{#each Array(3) as _}
|
|
17
|
+
<div class="rounded-lg bg-canvas-200 p-4 dark:bg-canvas-800">
|
|
18
|
+
<div class="mb-2 h-5 w-3/4 rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
19
|
+
<div class="mb-2 h-4 w-full rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
20
|
+
<div class="h-3 w-24 rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
21
|
+
</div>
|
|
22
|
+
{/each}
|
|
23
|
+
</div>
|
|
24
|
+
{/snippet}
|
|
25
|
+
</Card>
|
|
26
|
+
{:else if documents.length > 0}
|
|
27
|
+
<Card variant="elevated" padding="md">
|
|
28
|
+
{#snippet children()}
|
|
29
|
+
<h2 class="mb-4 text-2xl font-bold text-ink-900 dark:text-ink-50">Recent Posts</h2>
|
|
30
|
+
<div class="space-y-3">
|
|
31
|
+
{#each documents as document}
|
|
32
|
+
<DocumentCard {document} />
|
|
33
|
+
{/each}
|
|
34
|
+
</div>
|
|
35
|
+
{/snippet}
|
|
36
|
+
</Card>
|
|
37
|
+
{:else}
|
|
38
|
+
<Card variant="flat" padding="lg">
|
|
39
|
+
{#snippet children()}
|
|
40
|
+
<div class="text-center">
|
|
41
|
+
<p class="text-ink-700 dark:text-ink-300">No documents available. Start writing on <a href="https://standard.site/" class="text-primary-600 hover:underline dark:text-primary-400" target="_blank" rel="noopener noreferrer">Standard.site</a> to get started!</p>
|
|
42
|
+
</div>
|
|
43
|
+
{/snippet}
|
|
44
|
+
</Card>
|
|
45
|
+
{/if}
|
|
46
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { StandardSiteDocument } from '@ewanc26/atproto';
|
|
2
|
+
interface Props {
|
|
3
|
+
documents?: StandardSiteDocument[] | null;
|
|
4
|
+
}
|
|
5
|
+
declare const PostCard: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type PostCard = ReturnType<typeof PostCard>;
|
|
7
|
+
export default PostCard;
|
|
8
|
+
//# sourceMappingURL=PostCard.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostCard.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/layout/main/card/PostCard.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG5D,UAAU,KAAK;IAAG,SAAS,CAAC,EAAE,oBAAoB,EAAE,GAAG,IAAI,CAAC;CAAE;AAiD/D,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Card from '../../../ui/Card.svelte';
|
|
3
|
+
import type { ProfileData } from '@ewanc26/atproto';
|
|
4
|
+
import LinkCard from './LinkCard.svelte';
|
|
5
|
+
import { formatCompactNumber } from '../../../../utils/formatNumber.js';
|
|
6
|
+
|
|
7
|
+
interface Props { profile?: ProfileData | null; }
|
|
8
|
+
let { profile = null }: Props = $props();
|
|
9
|
+
|
|
10
|
+
let imageLoaded = $state(false);
|
|
11
|
+
let bannerLoaded = $state(false);
|
|
12
|
+
const locale = typeof navigator !== 'undefined' ? navigator.language || 'en-GB' : 'en-GB';
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<div class="mx-auto w-full max-w-2xl">
|
|
16
|
+
{#if !profile}
|
|
17
|
+
<Card loading={true} variant="elevated" padding="none" class="overflow-hidden">
|
|
18
|
+
{#snippet skeleton()}
|
|
19
|
+
<div class="h-32 w-full rounded-t-xl bg-canvas-300 dark:bg-canvas-700"></div>
|
|
20
|
+
<div class="relative -mt-16 flex justify-center sm:ml-6 sm:justify-start">
|
|
21
|
+
<div class="h-32 w-32 rounded-full border-4 border-white bg-canvas-300 dark:border-canvas-900 dark:bg-canvas-700"></div>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="space-y-2 p-6 pt-2 sm:pt-4">
|
|
24
|
+
<div class="h-6 w-1/2 rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
25
|
+
<div class="h-4 w-1/3 rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
26
|
+
<div class="h-4 rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
27
|
+
<div class="h-4 w-5/6 rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
28
|
+
</div>
|
|
29
|
+
{/snippet}
|
|
30
|
+
</Card>
|
|
31
|
+
{:else}
|
|
32
|
+
{@const p = profile}
|
|
33
|
+
<Card variant="elevated" padding="none" ariaLabel="Profile information">
|
|
34
|
+
{#snippet children()}
|
|
35
|
+
<div class="relative h-32 w-full overflow-hidden rounded-t-xl">
|
|
36
|
+
{#if p.banner}
|
|
37
|
+
<img src={p.banner} alt="" class="h-full w-full object-cover opacity-0 transition-opacity duration-300" class:opacity-100={bannerLoaded} onload={() => (bannerLoaded = true)} loading="lazy" role="presentation" />
|
|
38
|
+
{:else}
|
|
39
|
+
<div class="h-full w-full bg-linear-to-r from-primary-400 to-secondary-400" role="presentation"></div>
|
|
40
|
+
{/if}
|
|
41
|
+
</div>
|
|
42
|
+
<div class="relative -mt-16 flex justify-center sm:ml-6 sm:justify-start">
|
|
43
|
+
<div class="h-32 w-32 overflow-hidden rounded-full border-4 border-white bg-canvas-200 dark:border-canvas-900">
|
|
44
|
+
{#if p.avatar}
|
|
45
|
+
<img src={p.avatar} alt="{p.displayName || p.handle}'s profile picture" class="h-full w-full object-cover opacity-0 transition-opacity duration-300" class:opacity-100={imageLoaded} onload={() => (imageLoaded = true)} loading="lazy" />
|
|
46
|
+
{:else}
|
|
47
|
+
<div class="flex h-full w-full items-center justify-center bg-primary-200 text-3xl font-bold text-primary-800 dark:bg-primary-800 dark:text-primary-200" role="img" aria-label="{p.displayName || p.handle}'s avatar initials">
|
|
48
|
+
{(p.displayName || p.handle).charAt(0).toUpperCase()}
|
|
49
|
+
</div>
|
|
50
|
+
{/if}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="p-6">
|
|
54
|
+
<h2 class="text-2xl font-bold text-ink-900 dark:text-ink-50">{p.displayName || p.handle}</h2>
|
|
55
|
+
<p class="font-medium text-ink-700 dark:text-ink-200">@{p.handle}</p>
|
|
56
|
+
{#if p.pronouns}<p class="text-sm text-ink-600 italic dark:text-ink-300">{p.pronouns}</p>{/if}
|
|
57
|
+
{#if p.description}<p class="wrap-break-words mb-4 break-all whitespace-pre-wrap text-ink-700 dark:text-ink-200">{p.description}</p>{/if}
|
|
58
|
+
<div class="flex gap-6 text-sm font-medium" role="list" aria-label="Profile statistics">
|
|
59
|
+
<div class="flex items-center gap-1" role="listitem"><span class="font-bold text-ink-900 dark:text-ink-50">{formatCompactNumber(p.postsCount, locale)}</span><span class="text-ink-700 dark:text-ink-200">Posts</span></div>
|
|
60
|
+
<div class="flex items-center gap-1" role="listitem"><span class="font-bold text-ink-900 dark:text-ink-50">{formatCompactNumber(p.followersCount, locale)}</span><span class="text-ink-700 dark:text-ink-200">Followers</span></div>
|
|
61
|
+
<div class="flex items-center gap-1" role="listitem"><span class="font-bold text-ink-900 dark:text-ink-50">{formatCompactNumber(p.followsCount, locale)}</span><span class="text-ink-700 dark:text-ink-200">Following</span></div>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="mt-4">
|
|
64
|
+
<LinkCard url="https://witchsky.app/profile/{p.did}" title="View on Bluesky" variant="button" />
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
{/snippet}
|
|
68
|
+
</Card>
|
|
69
|
+
{/if}
|
|
70
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ProfileData } from '@ewanc26/atproto';
|
|
2
|
+
interface Props {
|
|
3
|
+
profile?: ProfileData | null;
|
|
4
|
+
}
|
|
5
|
+
declare const ProfileCard: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type ProfileCard = ReturnType<typeof ProfileCard>;
|
|
7
|
+
export default ProfileCard;
|
|
8
|
+
//# sourceMappingURL=ProfileCard.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProfileCard.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/layout/main/card/ProfileCard.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAKnD,UAAU,KAAK;IAAG,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CAAE;AAyElD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ExternalLink, GitBranch, Server, User } from '@lucide/svelte';
|
|
3
|
+
import Card from '../../../ui/Card.svelte';
|
|
4
|
+
import InternalCard from '../../../ui/InternalCard.svelte';
|
|
5
|
+
import type { TangledReposData, ProfileData } from '@ewanc26/atproto';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
repos?: TangledReposData | null;
|
|
9
|
+
profile?: ProfileData | null;
|
|
10
|
+
/** Fallback DID if profile handle is unavailable */
|
|
11
|
+
did?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let { repos = null, profile = null, did = '' }: Props = $props();
|
|
15
|
+
let handle = $derived(profile?.handle || null);
|
|
16
|
+
|
|
17
|
+
function buildRepoUrl(repoName: string): string {
|
|
18
|
+
const identifier = handle || did;
|
|
19
|
+
return `https://tangled.org/${identifier}/${repoName}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getKnotServerName(knot: string): string {
|
|
23
|
+
if (knot.startsWith('http')) {
|
|
24
|
+
try { return new URL(knot).hostname; } catch { return knot; }
|
|
25
|
+
}
|
|
26
|
+
return knot;
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<div class="mx-auto w-full max-w-2xl">
|
|
31
|
+
{#if !repos}
|
|
32
|
+
<Card loading={true} variant="elevated" padding="md">
|
|
33
|
+
{#snippet skeleton()}
|
|
34
|
+
<div class="mb-4 h-6 w-32 rounded bg-canvas-300 dark:bg-canvas-700"></div>
|
|
35
|
+
<div class="space-y-3">
|
|
36
|
+
{#each Array(3) as _}
|
|
37
|
+
<div class="h-24 rounded-lg bg-canvas-300 dark:bg-canvas-700"></div>
|
|
38
|
+
{/each}
|
|
39
|
+
</div>
|
|
40
|
+
{/snippet}
|
|
41
|
+
</Card>
|
|
42
|
+
{:else if repos.repos.length > 0}
|
|
43
|
+
<Card variant="elevated" padding="md">
|
|
44
|
+
{#snippet children()}
|
|
45
|
+
<h2 class="mb-4 text-2xl font-bold text-ink-900 dark:text-ink-50">Tangled Repositories</h2>
|
|
46
|
+
<div class="space-y-3">
|
|
47
|
+
{#each repos.repos as repo}
|
|
48
|
+
<InternalCard href={buildRepoUrl(repo.name)}>
|
|
49
|
+
{#snippet children()}
|
|
50
|
+
<GitBranch class="h-5 w-5 shrink-0 text-primary-600 dark:text-primary-400" aria-hidden="true" />
|
|
51
|
+
<div class="min-w-0 flex-1 space-y-2">
|
|
52
|
+
<h3 class="overflow-wrap-anywhere font-semibold wrap-break-word text-ink-900 dark:text-ink-50">{repo.name}</h3>
|
|
53
|
+
<div class="flex flex-wrap items-center gap-3 text-xs text-ink-700 dark:text-ink-200">
|
|
54
|
+
<div class="flex min-w-0 items-center gap-1">
|
|
55
|
+
<Server class="h-3 w-3 shrink-0" aria-hidden="true" />
|
|
56
|
+
<span class="truncate">{getKnotServerName(repo.knot)}</span>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="flex min-w-0 items-center gap-1">
|
|
59
|
+
<User class="h-3 w-3 shrink-0" aria-hidden="true" />
|
|
60
|
+
<span class="truncate">{handle || did}</span>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<ExternalLink class="h-4 w-4 shrink-0 text-ink-700 transition-colors dark:text-ink-200" aria-hidden="true" />
|
|
65
|
+
{/snippet}
|
|
66
|
+
</InternalCard>
|
|
67
|
+
{/each}
|
|
68
|
+
</div>
|
|
69
|
+
{/snippet}
|
|
70
|
+
</Card>
|
|
71
|
+
{:else}
|
|
72
|
+
<Card variant="flat" padding="lg">
|
|
73
|
+
{#snippet children()}
|
|
74
|
+
<div class="text-center">
|
|
75
|
+
<p class="text-ink-700 dark:text-ink-300">No Tangled repositories found.</p>
|
|
76
|
+
</div>
|
|
77
|
+
{/snippet}
|
|
78
|
+
</Card>
|
|
79
|
+
{/if}
|
|
80
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TangledReposData, ProfileData } from '@ewanc26/atproto';
|
|
2
|
+
interface Props {
|
|
3
|
+
repos?: TangledReposData | null;
|
|
4
|
+
profile?: ProfileData | null;
|
|
5
|
+
/** Fallback DID if profile handle is unavailable */
|
|
6
|
+
did?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const TangledRepoCard: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type TangledRepoCard = ReturnType<typeof TangledRepoCard>;
|
|
10
|
+
export default TangledRepoCard;
|
|
11
|
+
//# sourceMappingURL=TangledRepoCard.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TangledRepoCard.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/layout/main/card/TangledRepoCard.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGrE,UAAU,KAAK;IACd,KAAK,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AA8EF,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { default as LinkCard } from './LinkCard.svelte';
|
|
2
|
+
export { default as ProfileCard } from './ProfileCard.svelte';
|
|
3
|
+
export { default as PostCard } from './PostCard.svelte';
|
|
4
|
+
export { default as BlueskyPostCard } from './BlueskyPostCard.svelte';
|
|
5
|
+
export { default as TangledRepoCard } from './TangledRepoCard.svelte';
|
|
6
|
+
export { default as MusicStatusCard } from './MusicStatusCard.svelte';
|
|
7
|
+
export { default as KibunStatusCard } from './KibunStatusCard.svelte';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/layout/main/card/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as LinkCard } from './LinkCard.svelte';
|
|
2
|
+
export { default as ProfileCard } from './ProfileCard.svelte';
|
|
3
|
+
export { default as PostCard } from './PostCard.svelte';
|
|
4
|
+
export { default as BlueskyPostCard } from './BlueskyPostCard.svelte';
|
|
5
|
+
export { default as TangledRepoCard } from './TangledRepoCard.svelte';
|
|
6
|
+
export { default as MusicStatusCard } from './MusicStatusCard.svelte';
|
|
7
|
+
export { default as KibunStatusCard } from './KibunStatusCard.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/layout/main/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { SiteMetadata } from '../../types/index.js';
|
|
3
|
+
|
|
4
|
+
interface Props { meta: SiteMetadata; siteMeta: SiteMetadata; }
|
|
5
|
+
let { meta, siteMeta }: Props = $props();
|
|
6
|
+
|
|
7
|
+
const finalMeta = $derived({
|
|
8
|
+
title: meta.title || siteMeta.title,
|
|
9
|
+
description: meta.description || siteMeta.description,
|
|
10
|
+
keywords: meta.keywords || siteMeta.keywords,
|
|
11
|
+
url: meta.url || siteMeta.url,
|
|
12
|
+
image: meta.image || siteMeta.image,
|
|
13
|
+
imageWidth: meta.imageWidth || siteMeta.imageWidth,
|
|
14
|
+
imageHeight: meta.imageHeight || siteMeta.imageHeight
|
|
15
|
+
});
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<svelte:head>
|
|
19
|
+
<title>{finalMeta.title}</title>
|
|
20
|
+
<meta name="description" content={finalMeta.description} />
|
|
21
|
+
<meta name="keywords" content={finalMeta.keywords} />
|
|
22
|
+
<meta property="og:type" content="website" />
|
|
23
|
+
<meta property="og:url" content={finalMeta.url} />
|
|
24
|
+
<meta property="og:title" content={finalMeta.title} />
|
|
25
|
+
<meta property="og:description" content={finalMeta.description} />
|
|
26
|
+
<meta property="og:site_name" content={siteMeta.title} />
|
|
27
|
+
<meta property="og:image" content={finalMeta.image} />
|
|
28
|
+
{#if finalMeta.imageWidth}
|
|
29
|
+
<meta property="og:image:width" content={finalMeta.imageWidth.toString()} />
|
|
30
|
+
{/if}
|
|
31
|
+
{#if finalMeta.imageHeight}
|
|
32
|
+
<meta property="og:image:height" content={finalMeta.imageHeight.toString()} />
|
|
33
|
+
{/if}
|
|
34
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
35
|
+
<meta name="twitter:url" content={finalMeta.url} />
|
|
36
|
+
<meta name="twitter:title" content={finalMeta.title} />
|
|
37
|
+
<meta name="twitter:description" content={finalMeta.description} />
|
|
38
|
+
<meta name="twitter:image" content={finalMeta.image} />
|
|
39
|
+
</svelte:head>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SiteMetadata } from '../../types/index.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
meta: SiteMetadata;
|
|
4
|
+
siteMeta: SiteMetadata;
|
|
5
|
+
}
|
|
6
|
+
declare const MetaTags: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type MetaTags = ReturnType<typeof MetaTags>;
|
|
8
|
+
export default MetaTags;
|
|
9
|
+
//# sourceMappingURL=MetaTags.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MetaTags.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/seo/MetaTags.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGxD,UAAU,KAAK;IAAG,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,YAAY,CAAC;CAAE;AA0ChE,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/components/seo/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as MetaTags } from './MetaTags.svelte';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ExternalLink, Tag } from '@lucide/svelte';
|
|
3
|
+
import type { BlogPost } from '@ewanc26/atproto';
|
|
4
|
+
import InternalCard from './InternalCard.svelte';
|
|
5
|
+
import { getPostBadges, getBadgeClasses } from '../../helper/badges.js';
|
|
6
|
+
import { formatLocalizedDate } from '../../utils/locale.js';
|
|
7
|
+
|
|
8
|
+
interface Props { post: BlogPost; locale?: string; }
|
|
9
|
+
let { post, locale }: Props = $props();
|
|
10
|
+
const badges = $derived(getPostBadges(post));
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<InternalCard href={post.url}>
|
|
14
|
+
{#snippet children()}
|
|
15
|
+
{#if post.coverImage}
|
|
16
|
+
<div class="mb-3 overflow-hidden rounded-lg">
|
|
17
|
+
<img src={post.coverImage} alt={post.title} class="h-48 w-full object-cover transition-transform duration-300 hover:scale-105" />
|
|
18
|
+
</div>
|
|
19
|
+
{/if}
|
|
20
|
+
<div class="relative min-w-0 flex-1 space-y-2">
|
|
21
|
+
{#if badges.length > 0}
|
|
22
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
23
|
+
{#each badges as badge}<span class={getBadgeClasses(badge)}>{badge.text}</span>{/each}
|
|
24
|
+
</div>
|
|
25
|
+
{/if}
|
|
26
|
+
<h4 class="overflow-wrap-anywhere font-semibold wrap-break-word text-ink-900 dark:text-ink-50">{post.title}</h4>
|
|
27
|
+
{#if post.description}
|
|
28
|
+
<p class="overflow-wrap-anywhere line-clamp-2 text-sm wrap-break-word text-ink-700 dark:text-ink-200">{post.description}</p>
|
|
29
|
+
{/if}
|
|
30
|
+
<div class="pt-1">
|
|
31
|
+
<p class="text-xs font-medium text-ink-800 dark:text-ink-100">{formatLocalizedDate(post.createdAt, locale)}</p>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="flex shrink-0 flex-col items-end justify-between gap-2 self-stretch">
|
|
35
|
+
<ExternalLink class="h-4 w-4 text-ink-700 transition-colors dark:text-ink-200" aria-hidden="true" />
|
|
36
|
+
{#if post.tags && post.tags.length > 0}
|
|
37
|
+
<div class="flex items-center gap-1.5 rounded bg-ink-100 px-2 py-0.5 dark:bg-ink-800">
|
|
38
|
+
<Tag class="h-3 w-3 text-ink-700 dark:text-ink-200" aria-hidden="true" />
|
|
39
|
+
<span class="text-xs font-medium text-ink-800 dark:text-ink-100">{post.tags.length}</span>
|
|
40
|
+
</div>
|
|
41
|
+
{/if}
|
|
42
|
+
</div>
|
|
43
|
+
{/snippet}
|
|
44
|
+
</InternalCard>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BlogPost } from '@ewanc26/atproto';
|
|
2
|
+
interface Props {
|
|
3
|
+
post: BlogPost;
|
|
4
|
+
locale?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const BlogPostCard: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type BlogPostCard = ReturnType<typeof BlogPostCard>;
|
|
8
|
+
export default BlogPostCard;
|
|
9
|
+
//# sourceMappingURL=BlogPostCard.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlogPostCard.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/ui/BlogPostCard.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAMhD,UAAU,KAAK;IAAG,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAAE;AA+CrD,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|