@foxui/social 0.4.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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/dist/components/blog-entry/BlogEntry.svelte +80 -0
  4. package/dist/components/blog-entry/BlogEntry.svelte.d.ts +14 -0
  5. package/dist/components/blog-entry/index.d.ts +1 -0
  6. package/dist/components/blog-entry/index.js +1 -0
  7. package/dist/components/bluesky-login/BlueskyLogin.svelte +23 -0
  8. package/dist/components/bluesky-login/BlueskyLogin.svelte.d.ts +4 -0
  9. package/dist/components/bluesky-login/BlueskyLoginModal.svelte +141 -0
  10. package/dist/components/bluesky-login/BlueskyLoginModal.svelte.d.ts +9 -0
  11. package/dist/components/bluesky-login/index.d.ts +8 -0
  12. package/dist/components/bluesky-login/index.js +3 -0
  13. package/dist/components/bluesky-post/BlueskyPost.svelte +36 -0
  14. package/dist/components/bluesky-post/BlueskyPost.svelte.d.ts +10 -0
  15. package/dist/components/bluesky-post/index.d.ts +5 -0
  16. package/dist/components/bluesky-post/index.js +110 -0
  17. package/dist/components/chat/Chat.svelte +0 -0
  18. package/dist/components/chat/Chat.svelte.d.ts +26 -0
  19. package/dist/components/chat/ChatMessage.svelte +0 -0
  20. package/dist/components/chat/ChatMessage.svelte.d.ts +26 -0
  21. package/dist/components/chat/index.d.ts +1 -0
  22. package/dist/components/chat/index.js +1 -0
  23. package/dist/components/emoji-picker/EmojiPicker.svelte +91 -0
  24. package/dist/components/emoji-picker/EmojiPicker.svelte.d.ts +11 -0
  25. package/dist/components/emoji-picker/PopoverEmojiPicker.svelte +24 -0
  26. package/dist/components/emoji-picker/PopoverEmojiPicker.svelte.d.ts +11 -0
  27. package/dist/components/emoji-picker/emoji.d.ts +7 -0
  28. package/dist/components/emoji-picker/emoji.js +56 -0
  29. package/dist/components/emoji-picker/index.d.ts +2 -0
  30. package/dist/components/emoji-picker/index.js +2 -0
  31. package/dist/components/github-corner/GithubCorner.svelte +62 -0
  32. package/dist/components/github-corner/GithubCorner.svelte.d.ts +4 -0
  33. package/dist/components/github-corner/index.d.ts +1 -0
  34. package/dist/components/github-corner/index.js +1 -0
  35. package/dist/components/index.d.ts +12 -0
  36. package/dist/components/index.js +20 -0
  37. package/dist/components/nested-comments/Comment.svelte +151 -0
  38. package/dist/components/nested-comments/Comment.svelte.d.ts +9 -0
  39. package/dist/components/nested-comments/NestedComments.svelte +14 -0
  40. package/dist/components/nested-comments/NestedComments.svelte.d.ts +7 -0
  41. package/dist/components/nested-comments/index.d.ts +1 -0
  42. package/dist/components/nested-comments/index.js +1 -0
  43. package/dist/components/post/Post.svelte +294 -0
  44. package/dist/components/post/Post.svelte.d.ts +23 -0
  45. package/dist/components/post/PostAction.svelte +27 -0
  46. package/dist/components/post/PostAction.svelte.d.ts +9 -0
  47. package/dist/components/post/embeds/Embed.svelte +24 -0
  48. package/dist/components/post/embeds/Embed.svelte.d.ts +7 -0
  49. package/dist/components/post/embeds/External.svelte +39 -0
  50. package/dist/components/post/embeds/External.svelte.d.ts +7 -0
  51. package/dist/components/post/embeds/Images.svelte +38 -0
  52. package/dist/components/post/embeds/Images.svelte.d.ts +7 -0
  53. package/dist/components/post/embeds/Video.svelte +45 -0
  54. package/dist/components/post/embeds/Video.svelte.d.ts +7 -0
  55. package/dist/components/post/index.d.ts +63 -0
  56. package/dist/components/post/index.js +1 -0
  57. package/dist/components/social-icons/All.svelte +47 -0
  58. package/dist/components/social-icons/All.svelte.d.ts +12 -0
  59. package/dist/components/social-icons/Bluesky.svelte +37 -0
  60. package/dist/components/social-icons/Bluesky.svelte.d.ts +8 -0
  61. package/dist/components/social-icons/Discord.svelte +37 -0
  62. package/dist/components/social-icons/Discord.svelte.d.ts +8 -0
  63. package/dist/components/social-icons/Facebook.svelte +37 -0
  64. package/dist/components/social-icons/Facebook.svelte.d.ts +8 -0
  65. package/dist/components/social-icons/Github.svelte +38 -0
  66. package/dist/components/social-icons/Github.svelte.d.ts +8 -0
  67. package/dist/components/social-icons/Twitter.svelte +37 -0
  68. package/dist/components/social-icons/Twitter.svelte.d.ts +8 -0
  69. package/dist/components/social-icons/Youtube.svelte +36 -0
  70. package/dist/components/social-icons/Youtube.svelte.d.ts +8 -0
  71. package/dist/components/social-icons/index.d.ts +7 -0
  72. package/dist/components/social-icons/index.js +8 -0
  73. package/dist/components/star-rating/StarRating.svelte +104 -0
  74. package/dist/components/star-rating/StarRating.svelte.d.ts +13 -0
  75. package/dist/components/star-rating/index.d.ts +1 -0
  76. package/dist/components/star-rating/index.js +1 -0
  77. package/dist/components/swiper-cards/CardSwiper.svelte +235 -0
  78. package/dist/components/swiper-cards/CardSwiper.svelte.d.ts +18 -0
  79. package/dist/components/swiper-cards/index.d.ts +14 -0
  80. package/dist/components/swiper-cards/index.js +1 -0
  81. package/dist/components/user-profile/UserProfile.svelte +60 -0
  82. package/dist/components/user-profile/UserProfile.svelte.d.ts +13 -0
  83. package/dist/components/user-profile/index.d.ts +1 -0
  84. package/dist/components/user-profile/index.js +1 -0
  85. package/dist/index.d.ts +1 -0
  86. package/dist/index.js +1 -0
  87. package/dist/types.d.ts +1 -0
  88. package/package.json +79 -0
@@ -0,0 +1,294 @@
1
+ <script lang="ts">
2
+ import Embed from './embeds/Embed.svelte';
3
+ import { cn, Avatar, Prose } from '@foxui/core';
4
+ import type { WithChildren, WithElementRef } from 'bits-ui';
5
+ import type { HTMLAttributes } from 'svelte/elements';
6
+ import type { PostData } from '.';
7
+ import PostAction from './PostAction.svelte';
8
+ import type { Snippet } from 'svelte';
9
+ import { numberToHumanReadable } from '..';
10
+ import { RelativeTime } from '@foxui/time';
11
+
12
+ let {
13
+ ref = $bindable(),
14
+ data,
15
+ class: className,
16
+ bookmarked = $bindable(false),
17
+ liked = $bindable(false),
18
+
19
+ showReply = $bindable(true),
20
+ showRepost = $bindable(true),
21
+ showLike = $bindable(true),
22
+ showBookmark = $bindable(true),
23
+
24
+ onReplyClick,
25
+ onRepostClick,
26
+ onLikeClick,
27
+ onBookmarkClick,
28
+
29
+ customActions,
30
+
31
+ children,
32
+
33
+ logo
34
+ }: WithElementRef<WithChildren<HTMLAttributes<HTMLDivElement>>> & {
35
+ data: PostData;
36
+ class?: string;
37
+
38
+ bookmarked?: boolean;
39
+ liked?: boolean;
40
+
41
+ showReply?: boolean;
42
+ showRepost?: boolean;
43
+ showLike?: boolean;
44
+ showBookmark?: boolean;
45
+
46
+ onReplyClick?: () => void;
47
+ onRepostClick?: () => void;
48
+ onLikeClick?: () => void;
49
+ onBookmarkClick?: () => void;
50
+
51
+ customActions?: Snippet;
52
+
53
+ logo?: Snippet;
54
+ } = $props();
55
+ </script>
56
+
57
+ <div
58
+ bind:this={ref}
59
+ class={cn('text-base-950 dark:text-base-50 transition-colors duration-200', className)}
60
+ >
61
+ {#if data.reposted}
62
+ <div class="mb-3 inline-flex items-center gap-2 text-xs">
63
+ <svg
64
+ xmlns="http://www.w3.org/2000/svg"
65
+ viewBox="0 0 24 24"
66
+ fill="currentColor"
67
+ class="size-3"
68
+ >
69
+ <path
70
+ fill-rule="evenodd"
71
+ d="M4.755 10.059a7.5 7.5 0 0 1 12.548-3.364l1.903 1.903h-3.183a.75.75 0 1 0 0 1.5h4.992a.75.75 0 0 0 .75-.75V4.356a.75.75 0 0 0-1.5 0v3.18l-1.9-1.9A9 9 0 0 0 3.306 9.67a.75.75 0 1 0 1.45.388Zm15.408 3.352a.75.75 0 0 0-.919.53 7.5 7.5 0 0 1-12.548 3.364l-1.902-1.903h3.183a.75.75 0 0 0 0-1.5H2.984a.75.75 0 0 0-.75.75v4.992a.75.75 0 0 0 1.5 0v-3.18l1.9 1.9a9 9 0 0 0 15.059-4.035.75.75 0 0 0-.53-.918Z"
72
+ clip-rule="evenodd"
73
+ />
74
+ </svg>
75
+
76
+ <div class="inline-flex gap-1">
77
+ reposted by
78
+ <a
79
+ href={data.reposted.href}
80
+ class="hover:text-accent-600 dark:hover:text-accent-400 font-bold"
81
+ >
82
+ @{data.reposted.handle}
83
+ </a>
84
+ </div>
85
+ </div>
86
+ {/if}
87
+ {#if data.replyTo}
88
+ <div class="mb-3 inline-flex items-center gap-2 text-xs">
89
+
90
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-3">
91
+ <path fill-rule="evenodd" d="M14.47 2.47a.75.75 0 0 1 1.06 0l6 6a.75.75 0 0 1 0 1.06l-6 6a.75.75 0 1 1-1.06-1.06l4.72-4.72H9a5.25 5.25 0 1 0 0 10.5h3a.75.75 0 0 1 0 1.5H9a6.75 6.75 0 0 1 0-13.5h10.19l-4.72-4.72a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
92
+ </svg>
93
+
94
+
95
+ <div class="inline-flex gap-1">
96
+ replying to
97
+ <a
98
+ href={data.replyTo.href}
99
+ class="hover:text-accent-600 dark:hover:text-accent-400 font-bold"
100
+ >
101
+ @{data.replyTo.handle}
102
+ </a>
103
+ </div>
104
+ </div>
105
+ {/if}
106
+ <div class="flex gap-4">
107
+ <Avatar src={data.author.avatar} />
108
+
109
+ <div class="w-full">
110
+ <div class="mb-1 flex items-start justify-between gap-2">
111
+ <div class="flex items-start gap-4">
112
+ {#if data.author.href}
113
+ <a
114
+ class="hover:bg-accent-900/5 group/post-author -mx-2 -my-0.5 flex flex-col items-baseline gap-x-2 gap-y-0.5 rounded-xl px-2 py-0.5 sm:flex-row"
115
+ href={data.author.href}
116
+ >
117
+ {#if data.author.displayName}
118
+ <div
119
+ class="text-base-900 group-hover/post-author:text-accent-600 dark:text-base-50 dark:group-hover/post-author:text-accent-300 text-sm font-semibold leading-tight"
120
+ >
121
+ {data.author.displayName}
122
+ </div>
123
+ {/if}
124
+ <div
125
+ class={cn(
126
+ 'group-hover/post-author:text-accent-600 dark:group-hover/post-author:text-accent-400 text-sm',
127
+ !data.author.displayName
128
+ ? 'text-base-900 dark:text-base-50 font-semibold'
129
+ : 'text-base-600 dark:text-base-400'
130
+ )}
131
+ >
132
+ @{data.author.handle}
133
+ </div>
134
+ </a>
135
+ {:else}
136
+ <div
137
+ class="-mx-2 -my-0.5 flex flex-col items-baseline gap-x-2 gap-y-0.5 rounded-xl px-2 py-0.5 sm:flex-row"
138
+ >
139
+ <div class="text-base-900 dark:text-base-50 text-sm font-semibold leading-tight">
140
+ {data.author.displayName}
141
+ </div>
142
+ <div class="text-base-600 dark:text-base-400 text-sm">
143
+ @{data.author.handle}
144
+ </div>
145
+ </div>
146
+ {/if}
147
+
148
+ <div class="text-base-600 dark:text-base-400 block text-sm no-underline">
149
+ <RelativeTime date={new Date(data.createdAt)} locale="en" />
150
+ </div>
151
+ </div>
152
+
153
+ {#if logo}
154
+ {@render logo?.()}
155
+ {/if}
156
+ </div>
157
+
158
+ <Prose size="md">
159
+ {#if data.htmlContent}
160
+ {@html data.htmlContent}
161
+ {:else}
162
+ {@render children?.()}
163
+ {/if}
164
+ </Prose>
165
+
166
+ {#if data.embed}
167
+ <Embed embed={data.embed} />
168
+ {/if}
169
+
170
+ {#if showReply || showRepost || showLike || showBookmark || customActions}
171
+ <div class="text-base-500 dark:text-base-400 mt-4 flex justify-between gap-2">
172
+ {#if showReply}
173
+ <PostAction onclick={onReplyClick}>
174
+ <svg
175
+ xmlns="http://www.w3.org/2000/svg"
176
+ fill="none"
177
+ viewBox="0 0 24 24"
178
+ stroke-width="1.5"
179
+ stroke="currentColor"
180
+ class="group-hover/post-action:bg-accent-500/10 group-hover/post-action:text-accent-700 dark:group-hover/post-action:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100"
181
+ >
182
+ <path
183
+ stroke-linecap="round"
184
+ stroke-linejoin="round"
185
+ d="M12 20.25c4.97 0 9-3.694 9-8.25s-4.03-8.25-9-8.25S3 7.444 3 12c0 2.104.859 4.023 2.273 5.48.432.447.74 1.04.586 1.641a4.483 4.483 0 0 1-.923 1.785A5.969 5.969 0 0 0 6 21c1.282 0 2.47-.402 3.445-1.087.81.22 1.668.337 2.555.337Z"
186
+ />
187
+ </svg>
188
+ {#if data.replyCount}
189
+ {numberToHumanReadable(data.replyCount)}
190
+ {/if}
191
+ </PostAction>
192
+ {/if}
193
+
194
+ {#if showRepost}
195
+ <PostAction onclick={onRepostClick}>
196
+ <svg
197
+ xmlns="http://www.w3.org/2000/svg"
198
+ fill="none"
199
+ viewBox="0 0 24 24"
200
+ stroke-width="1.5"
201
+ stroke="currentColor"
202
+ class="group-hover/post-action:bg-accent-500/10 group-hover/post-action:text-accent-700 dark:group-hover/post-action:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100"
203
+ >
204
+ <path
205
+ stroke-linecap="round"
206
+ stroke-linejoin="round"
207
+ d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
208
+ />
209
+ </svg>
210
+ {#if data.repostCount}
211
+ {numberToHumanReadable(data.repostCount)}
212
+ {/if}
213
+ </PostAction>
214
+ {/if}
215
+ {#if showLike}
216
+ <PostAction
217
+ class={liked ? 'text-accent-700 dark:text-accent-500 font-semibold' : ''}
218
+ onclick={onLikeClick}
219
+ >
220
+ {#if liked}
221
+ <svg
222
+ xmlns="http://www.w3.org/2000/svg"
223
+ viewBox="0 0 24 24"
224
+ fill="currentColor"
225
+ class="group-hover/post-action:bg-accent-500/10 text-accent-700 dark:text-accent-500 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100"
226
+ >
227
+ <path
228
+ d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 0 1-4.244 3.17 15.247 15.247 0 0 1-.383.219l-.022.012-.007.004-.003.001a.752.752 0 0 1-.704 0l-.003-.001Z"
229
+ />
230
+ </svg>
231
+ {:else}
232
+ <svg
233
+ xmlns="http://www.w3.org/2000/svg"
234
+ fill="none"
235
+ viewBox="0 0 24 24"
236
+ stroke-width="1.5"
237
+ stroke="currentColor"
238
+ class="group-hover/post-action:bg-accent-500/10 group-hover/post-action:text-accent-700 dark:group-hover/post-action:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100"
239
+ >
240
+ <path
241
+ stroke-linecap="round"
242
+ stroke-linejoin="round"
243
+ d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z"
244
+ />
245
+ </svg>
246
+ {/if}
247
+ {#if data.likeCount}
248
+ {numberToHumanReadable(data.likeCount)}
249
+ {/if}
250
+ </PostAction>
251
+ {/if}
252
+
253
+ {#if showBookmark}
254
+ <PostAction onclick={onBookmarkClick}>
255
+ <span class="sr-only">Bookmark</span>
256
+
257
+ {#if bookmarked}
258
+ <svg
259
+ xmlns="http://www.w3.org/2000/svg"
260
+ viewBox="0 0 24 24"
261
+ fill="currentColor"
262
+ class="group-hover/post-action:bg-accent-500/10 text-accent-700 dark:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100"
263
+ >
264
+ <path
265
+ fill-rule="evenodd"
266
+ d="M6.32 2.577a49.255 49.255 0 0 1 11.36 0c1.497.174 2.57 1.46 2.57 2.93V21a.75.75 0 0 1-1.085.67L12 18.089l-7.165 3.583A.75.75 0 0 1 3.75 21V5.507c0-1.47 1.073-2.756 2.57-2.93Z"
267
+ clip-rule="evenodd"
268
+ />
269
+ </svg>
270
+ {:else}
271
+ <svg
272
+ xmlns="http://www.w3.org/2000/svg"
273
+ fill="none"
274
+ viewBox="0 0 24 24"
275
+ stroke-width="1.5"
276
+ stroke="currentColor"
277
+ class="group-hover/post-action:bg-accent-500/10 group-hover/post-action:text-accent-700 dark:group-hover/post-action:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100"
278
+ >
279
+ <path
280
+ stroke-linecap="round"
281
+ stroke-linejoin="round"
282
+ d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0 1 11.186 0Z"
283
+ />
284
+ </svg>
285
+ {/if}
286
+ </PostAction>
287
+ {/if}
288
+
289
+ {@render customActions?.()}
290
+ </div>
291
+ {/if}
292
+ </div>
293
+ </div>
294
+ </div>
@@ -0,0 +1,23 @@
1
+ import type { WithChildren, WithElementRef } from 'bits-ui';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { PostData } from '.';
4
+ import type { Snippet } from 'svelte';
5
+ type $$ComponentProps = WithElementRef<WithChildren<HTMLAttributes<HTMLDivElement>>> & {
6
+ data: PostData;
7
+ class?: string;
8
+ bookmarked?: boolean;
9
+ liked?: boolean;
10
+ showReply?: boolean;
11
+ showRepost?: boolean;
12
+ showLike?: boolean;
13
+ showBookmark?: boolean;
14
+ onReplyClick?: () => void;
15
+ onRepostClick?: () => void;
16
+ onLikeClick?: () => void;
17
+ onBookmarkClick?: () => void;
18
+ customActions?: Snippet;
19
+ logo?: Snippet;
20
+ };
21
+ declare const Post: import("svelte").Component<$$ComponentProps, {}, "ref" | "bookmarked" | "liked" | "showReply" | "showRepost" | "showLike" | "showBookmark">;
22
+ type Post = ReturnType<typeof Post>;
23
+ export default Post;
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import { cn } from '@foxui/core';
3
+ import type { Snippet } from 'svelte';
4
+
5
+ let {
6
+ onclick,
7
+ children,
8
+ class: className
9
+ }: {
10
+ onclick?: () => void;
11
+ children: Snippet;
12
+ class?: string;
13
+ } = $props();
14
+ </script>
15
+
16
+ {#if onclick}
17
+ <button
18
+ class={cn('group/post-action inline-flex cursor-pointer items-center gap-2 text-sm', className)}
19
+ {onclick}
20
+ >
21
+ {@render children?.()}
22
+ </button>
23
+ {:else}
24
+ <div class={cn('inline-flex items-center gap-2 text-sm', className)}>
25
+ {@render children?.()}
26
+ </div>
27
+ {/if}
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ onclick?: () => void;
4
+ children: Snippet;
5
+ class?: string;
6
+ };
7
+ declare const PostAction: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type PostAction = ReturnType<typeof PostAction>;
9
+ export default PostAction;
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import type { PostEmbed } from '../';
3
+ import External from './External.svelte';
4
+ import Images from './Images.svelte';
5
+ import Video from './Video.svelte';
6
+
7
+ const { embed }: { embed: PostEmbed } = $props();
8
+ </script>
9
+
10
+ <div class="flex flex-col gap-2 pt-3 text-sm">
11
+ {#if embed.type === 'images'}
12
+ <Images data={embed} />
13
+ {:else if embed.type === 'external' && embed.external}
14
+ <External data={embed} />
15
+ {:else if embed.type === 'video' && embed.video}
16
+ <Video data={embed} />
17
+ {:else if embed.type === 'unknown'}
18
+ <div
19
+ class="text-base-700 dark:text-base-300 bg-base-200/50 dark:bg-base-900/50 border-base-300 dark:border-base-600/30 rounded-2xl border p-4 text-sm"
20
+ >
21
+ Unknown embed type
22
+ </div>
23
+ {/if}
24
+ </div>
@@ -0,0 +1,7 @@
1
+ import type { PostEmbed } from '../';
2
+ type $$ComponentProps = {
3
+ embed: PostEmbed;
4
+ };
5
+ declare const Embed: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Embed = ReturnType<typeof Embed>;
7
+ export default Embed;
@@ -0,0 +1,39 @@
1
+ <script lang="ts">
2
+ import type { PostEmbedExternal } from '..';
3
+
4
+ const { data }: { data: PostEmbedExternal } = $props();
5
+
6
+ const domain = new URL(data.external.href).hostname.replace('www.', '');
7
+ </script>
8
+
9
+ <article
10
+ class={[
11
+ 'group dark:bg-base-900 bg-base-200 border-base-300 dark:border-base-600/30 max-w-md relative isolate flex flex-col justify-end overflow-hidden rounded-2xl border p-4',
12
+ data.external.thumb ? 'aspect-[16/9]' : ''
13
+ ]}
14
+ >
15
+ {#if data.external.thumb}
16
+ <img
17
+ src={data.external.thumb}
18
+ alt={data.external.description}
19
+ class="absolute inset-0 -z-10 size-full object-cover transition-transform duration-500 ease-in-out group-hover:scale-102"
20
+ />
21
+ {/if}
22
+ <div
23
+ class="dark:from-base-950/90 dark:via-base-950/40 from-base-50/90 via-base-50/40 absolute inset-0 -z-10 bg-gradient-to-t"
24
+ ></div>
25
+
26
+ <div
27
+ class="text-base-700 dark:text-base-300 flex flex-wrap items-center gap-y-1 overflow-hidden text-sm"
28
+ >
29
+ <div>{domain}</div>
30
+ </div>
31
+ <h3
32
+ class="dark:text-base-50 text-base-900 group-hover:text-accent-600 dark:group-hover:text-accent-400 mt-1 text-lg/6 font-semibold transition-colors duration-200"
33
+ >
34
+ <a href={data.external.href} target="_blank" rel="noopener noreferrer nofollow">
35
+ <span class="absolute inset-0"></span>
36
+ {data.external.title}
37
+ </a>
38
+ </h3>
39
+ </article>
@@ -0,0 +1,7 @@
1
+ import type { PostEmbedExternal } from '..';
2
+ type $$ComponentProps = {
3
+ data: PostEmbedExternal;
4
+ };
5
+ declare const External: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type External = ReturnType<typeof External>;
7
+ export default External;
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import type { PostEmbedImage } from '..';
3
+
4
+ const { data }: { data: PostEmbedImage } = $props();
5
+ </script>
6
+
7
+ {#snippet imageSnippet(
8
+ image: {
9
+ alt: string;
10
+ thumb: string;
11
+ fullsize: string;
12
+ aspectRatio?: { width: number; height: number };
13
+ },
14
+ className?: string
15
+ )}
16
+ <img
17
+ loading="lazy"
18
+ src={image.thumb}
19
+ alt={image.alt}
20
+ style={image.aspectRatio
21
+ ? `aspect-ratio: ${image.aspectRatio.width} / ${image.aspectRatio.height}`
22
+ : 'aspect-ratio: 1 / 1'}
23
+ class={[
24
+ 'border-base-500/20 dark:border-base-400/20 w-fit max-w-full rounded-2xl border max-h-[40rem] object-contain',
25
+ className
26
+ ]}
27
+ />
28
+ {/snippet}
29
+
30
+ {#if data.images.length === 1}
31
+ {@render imageSnippet(data.images[0])}
32
+ {:else}
33
+ <div class="columns-2 gap-4">
34
+ {#each data.images as image}
35
+ {@render imageSnippet(image, 'mb-4')}
36
+ {/each}
37
+ </div>
38
+ {/if}
@@ -0,0 +1,7 @@
1
+ import type { PostEmbedImage } from '..';
2
+ type $$ComponentProps = {
3
+ data: PostEmbedImage;
4
+ };
5
+ declare const Images: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Images = ReturnType<typeof Images>;
7
+ export default Images;
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import Hls from 'hls.js';
4
+ import type { PostEmbedVideo } from '..';
5
+
6
+ const { data }: { data: PostEmbedVideo } = $props();
7
+
8
+ onMount(async () => {
9
+ const Plyr = (await import('plyr')).default;
10
+ if (Hls.isSupported()) {
11
+ var hls = new Hls();
12
+ hls.loadSource(data.video.playlist);
13
+ hls.attachMedia(element);
14
+ }
15
+
16
+ new Plyr(element, {
17
+ controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'fullscreen'],
18
+ ratio: data.video.aspectRatio
19
+ ? `${data.video.aspectRatio.width}:${data.video.aspectRatio.height}`
20
+ : '16:9'
21
+ });
22
+ });
23
+
24
+ let element: HTMLMediaElement;
25
+ </script>
26
+
27
+ <svelte:head>
28
+ <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
29
+ </svelte:head>
30
+
31
+ <div
32
+ style={data.video.aspectRatio
33
+ ? `aspect-ratio: ${data.video.aspectRatio.width} / ${data.video.aspectRatio.height}`
34
+ : 'aspect-ratio: 16 / 9'}
35
+ class="border-base-300 dark:border-base-400/40 w-full max-w-full overflow-hidden rounded-2xl border"
36
+ >
37
+ <!-- svelte-ignore a11y_media_has_caption -->
38
+ <video bind:this={element} class="h-full w-full" aria-label={data.video.alt}></video>
39
+ </div>
40
+
41
+ <style>
42
+ * {
43
+ --plyr-color-main: var(--color-accent-500);
44
+ }
45
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { PostEmbedVideo } from '..';
2
+ type $$ComponentProps = {
3
+ data: PostEmbedVideo;
4
+ };
5
+ declare const Video: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Video = ReturnType<typeof Video>;
7
+ export default Video;
@@ -0,0 +1,63 @@
1
+ export type PostEmbedImage = {
2
+ type: 'images';
3
+ images: {
4
+ alt: string;
5
+ thumb: string;
6
+ fullsize: string;
7
+ aspectRatio?: {
8
+ width: number;
9
+ height: number;
10
+ };
11
+ }[];
12
+ };
13
+ export type PostEmbedExternal = {
14
+ type: 'external';
15
+ external: {
16
+ href: string;
17
+ thumb: string;
18
+ title: string;
19
+ description: string;
20
+ };
21
+ };
22
+ export type PostEmbedVideo = {
23
+ type: 'video';
24
+ video: {
25
+ playlist: string;
26
+ thumb: string;
27
+ alt: string;
28
+ aspectRatio?: {
29
+ width: number;
30
+ height: number;
31
+ };
32
+ };
33
+ };
34
+ export type UnknownEmbed = {
35
+ type: 'unknown';
36
+ } & Record<string, unknown>;
37
+ export type PostEmbed = PostEmbedImage | PostEmbedExternal | PostEmbedVideo | UnknownEmbed;
38
+ export type PostData = {
39
+ href?: string;
40
+ id?: string;
41
+ reposted?: {
42
+ handle: string;
43
+ href: string;
44
+ };
45
+ replyTo?: {
46
+ handle: string;
47
+ href: string;
48
+ };
49
+ author: {
50
+ displayName: string;
51
+ handle: string;
52
+ avatar?: string;
53
+ href?: string;
54
+ };
55
+ replyCount: number;
56
+ repostCount: number;
57
+ likeCount: number;
58
+ createdAt: string;
59
+ embed?: PostEmbed;
60
+ htmlContent?: string;
61
+ replies?: PostData[];
62
+ };
63
+ export { default as Post } from './Post.svelte';
@@ -0,0 +1 @@
1
+ export { default as Post } from './Post.svelte';
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import Discord from './Discord.svelte';
3
+ import Github from './Github.svelte';
4
+ import Twitter from './Twitter.svelte';
5
+ import Youtube from './Youtube.svelte';
6
+ import Bluesky from './Bluesky.svelte';
7
+ import Facebook from './Facebook.svelte';
8
+
9
+ const {
10
+ discord,
11
+ github,
12
+ twitter,
13
+ youtube,
14
+ bluesky,
15
+ facebook,
16
+ svgClasses
17
+ }: {
18
+ discord?: string;
19
+ github?: string;
20
+ twitter?: string;
21
+ youtube?: string;
22
+ bluesky?: string;
23
+ facebook?: string;
24
+ svgClasses?: string;
25
+ } = $props();
26
+ </script>
27
+
28
+ <div class="flex flex-wrap items-center gap-4">
29
+ {#if discord}
30
+ <Discord href={discord} {svgClasses} />
31
+ {/if}
32
+ {#if github}
33
+ <Github href={github} {svgClasses} />
34
+ {/if}
35
+ {#if twitter}
36
+ <Twitter href={twitter} {svgClasses} />
37
+ {/if}
38
+ {#if youtube}
39
+ <Youtube href={youtube} {svgClasses} />
40
+ {/if}
41
+ {#if bluesky}
42
+ <Bluesky href={bluesky} {svgClasses} />
43
+ {/if}
44
+ {#if facebook}
45
+ <Facebook href={facebook} {svgClasses} />
46
+ {/if}
47
+ </div>
@@ -0,0 +1,12 @@
1
+ type $$ComponentProps = {
2
+ discord?: string;
3
+ github?: string;
4
+ twitter?: string;
5
+ youtube?: string;
6
+ bluesky?: string;
7
+ facebook?: string;
8
+ svgClasses?: string;
9
+ };
10
+ declare const All: import("svelte").Component<$$ComponentProps, {}, "">;
11
+ type All = ReturnType<typeof All>;
12
+ export default All;