@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License Copyright (c) 2025 Florian
2
+
3
+ Permission is hereby granted, free of
4
+ charge, to any person obtaining a copy of this software and associated
5
+ documentation files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use, copy, modify, merge,
7
+ publish, distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice
12
+ (including the next paragraph) shall be included in all copies or substantial
13
+ portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
18
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # 🦊 fox ui
2
+
3
+ svelte 5 + tailwind 4 ui kit, social components
4
+
5
+ - [Bluesky Login](https://flo-bit.dev/ui-kit/components/social/bluesky-login)
6
+ - [Card Swiper](https://flo-bit.dev/ui-kit/components/social/card-swiper)
7
+ - [Emoji Picker](https://flo-bit.dev/ui-kit/components/social/emoji-picker)
8
+ - [Github Corner](https://flo-bit.dev/ui-kit/components/social/github-corner)
9
+ - [Star Rating](https://flo-bit.dev/ui-kit/components/social/star-rating)
10
+ - [User Profile](https://flo-bit.dev/ui-kit/components/social/user-profile)
11
+
12
+ > **This is a public alpha release. Expect bugs and breaking changes.**
13
+
14
+ [See all components here](https://flo-bit.dev/ui-kit)
15
+
16
+ For a guide on how to use this ui kit, see the [Quickstart Guide](https://flo-bit.dev/ui-kit/docs/quick-start).
17
+
18
+ Read more about [the philosophy/aim of this project here](https://flo-bit.dev/ui-kit/docs/philosophy).
19
+
20
+ For more information about development, contributing and the like, see the main [README](https://github.com/flo-bit/ui-kit/blob/main/README.md).
@@ -0,0 +1,80 @@
1
+ <script lang="ts">
2
+ import { Badge, Button } from '@foxui/core';
3
+
4
+ const {
5
+ title,
6
+ description,
7
+ date,
8
+ image,
9
+ tags,
10
+ href
11
+ }: {
12
+ title: string;
13
+ description: string;
14
+ date: Date;
15
+ image?: string;
16
+ tags?: (
17
+ | {
18
+ tag: string;
19
+ href: string;
20
+ }
21
+ | string
22
+ )[];
23
+ href: string;
24
+ } = $props();
25
+ </script>
26
+
27
+ <article class="group relative isolate flex w-full flex-col gap-8 md:flex-row">
28
+ {#if image}
29
+ <div class="relative aspect-[16/9] md:w-64 md:shrink-0">
30
+ <img
31
+ src={image}
32
+ alt=""
33
+ class="bg-base-50 dark:bg-base-900 absolute inset-0 h-full w-full rounded-2xl object-cover"
34
+ />
35
+
36
+ <div
37
+ class="ring-base-900/10 dark:ring-base-100/10 absolute inset-0 rounded-2xl ring-1 ring-inset"
38
+ ></div>
39
+ </div>
40
+ {/if}
41
+ <div>
42
+ <div class={'flex max-h-8.5 flex-wrap items-center gap-2 overflow-hidden p-1 text-xs'}>
43
+ <time datetime={date.toISOString()} class="text-base-500 mr-2 shrink-0">
44
+ {date.toLocaleDateString('en-us', {
45
+ year: 'numeric',
46
+ month: 'short',
47
+ day: 'numeric'
48
+ })}
49
+ </time>
50
+ {#if tags}
51
+ {#each tags as tag}
52
+ {#if typeof tag === 'object'}
53
+ <Button href={tag.href} size="sm" class="z-10 max-w-64 justify-start overflow-hidden">
54
+ {tag.tag}
55
+ </Button>
56
+ {:else}
57
+ <Badge size="sm" class="z-10 max-w-64 justify-start overflow-hidden">
58
+ {tag}
59
+ </Badge>
60
+ {/if}
61
+ {/each}
62
+ {/if}
63
+ </div>
64
+ <div class="max-w-xl">
65
+ <div class="text-base-900 dark:text-base-50 mt-3 text-lg leading-6 font-semibold">
66
+ <a {href}>
67
+ <span class="absolute inset-0"></span>
68
+ {title}
69
+ </a>
70
+
71
+ <div
72
+ class="bg-base-200/30 dark:bg-base-800/30 absolute -inset-2 -z-10 scale-95 opacity-0 transition-all duration-150 group-hover:scale-100 group-hover:opacity-100 sm:rounded-2xl md:-inset-4"
73
+ ></div>
74
+ </div>
75
+ <p class="text-base-600 dark:text-base-400 mt-5 line-clamp-2 text-sm leading-6">
76
+ {@html description}
77
+ </p>
78
+ </div>
79
+ </div>
80
+ </article>
@@ -0,0 +1,14 @@
1
+ type $$ComponentProps = {
2
+ title: string;
3
+ description: string;
4
+ date: Date;
5
+ image?: string;
6
+ tags?: ({
7
+ tag: string;
8
+ href: string;
9
+ } | string)[];
10
+ href: string;
11
+ };
12
+ declare const BlogEntry: import("svelte").Component<$$ComponentProps, {}, "">;
13
+ type BlogEntry = ReturnType<typeof BlogEntry>;
14
+ export default BlogEntry;
@@ -0,0 +1 @@
1
+ export { default as BlogEntry } from './BlogEntry.svelte';
@@ -0,0 +1 @@
1
+ export { default as BlogEntry } from './BlogEntry.svelte';
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { Button } from '@foxui/core';
3
+ import { BlueskyLoginModal, blueskyLoginModalState, type BlueskyLoginProps } from '.';
4
+
5
+ let { login, formAction, formMethod }: BlueskyLoginProps = $props();
6
+ </script>
7
+
8
+ <Button onclick={() => blueskyLoginModalState.show()}>
9
+ <svg
10
+ fill="currentColor"
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ viewBox="-40 -40 680 620"
13
+ version="1.1"
14
+ aria-hidden="true"
15
+ >
16
+ <path
17
+ d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z"
18
+ />
19
+ </svg>
20
+ Login
21
+ </Button>
22
+
23
+ <BlueskyLoginModal {login} {formAction} {formMethod} />
@@ -0,0 +1,4 @@
1
+ import { type BlueskyLoginProps } from '.';
2
+ declare const BlueskyLogin: import("svelte").Component<BlueskyLoginProps, {}, "">;
3
+ type BlueskyLogin = ReturnType<typeof BlueskyLogin>;
4
+ export default BlueskyLogin;
@@ -0,0 +1,141 @@
1
+ <script lang="ts" module>
2
+ export const blueskyLoginModalState = $state({
3
+ open: false,
4
+ show: () => (blueskyLoginModalState.open = true),
5
+ hide: () => (blueskyLoginModalState.open = false)
6
+ });
7
+ </script>
8
+
9
+ <script lang="ts">
10
+ import { Button, Modal, Subheading, Label, Input, Avatar } from '@foxui/core';
11
+ import type { BlueskyLoginProps } from '.';
12
+
13
+ let value = $state('');
14
+ let error: string | null = $state(null);
15
+ let loading = $state(false);
16
+
17
+ let { login, formAction, formMethod = 'get' }: BlueskyLoginProps = $props();
18
+
19
+ async function onLogin(handle: string) {
20
+ if (loading || !login) return;
21
+
22
+ loading = true;
23
+ error = null;
24
+
25
+ try {
26
+ const hide = await login(handle);
27
+
28
+ if (hide) {
29
+ blueskyLoginModalState.hide();
30
+ value = '';
31
+ }
32
+ } catch (err) {
33
+ error = err instanceof Error ? err.message : String(err);
34
+ } finally {
35
+ loading = false;
36
+ }
37
+ }
38
+
39
+ async function onSubmit(evt: Event) {
40
+ if (formAction || !login) return;
41
+ evt.preventDefault();
42
+
43
+ await onLogin(value);
44
+ }
45
+
46
+ let input: HTMLInputElement | null = $state(null);
47
+
48
+ let lastLogin: { handle: string; avatar: string } | null = $state(null);
49
+
50
+ $effect(() => {
51
+ let lastLoginDid = localStorage.getItem('last-login');
52
+
53
+ if (lastLoginDid) {
54
+ let profile = localStorage.getItem(`profile-${lastLoginDid}`);
55
+
56
+ if (profile) {
57
+ lastLogin = JSON.parse(profile)
58
+ }
59
+ }
60
+ });
61
+ </script>
62
+
63
+ <Modal
64
+ bind:open={blueskyLoginModalState.open}
65
+ class="max-w-sm gap-2 p-4 sm:p-6"
66
+ onOpenAutoFocus={(e: Event) => {
67
+ e.preventDefault();
68
+ input?.focus();
69
+ }}
70
+ >
71
+ <form onsubmit={onSubmit} action={formAction} method={formMethod} class="flex flex-col gap-2">
72
+ <Subheading class="mb-1 inline-flex items-center gap-2 text-xl font-bold">
73
+ <svg
74
+ fill="currentColor"
75
+ xmlns="http://www.w3.org/2000/svg"
76
+ viewBox="-40 -40 680 620"
77
+ version="1.1"
78
+ class={['text-accent-600 dark:text-accent-400 size-6']}
79
+ aria-hidden="true"
80
+ >
81
+ <path
82
+ d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z"
83
+ />
84
+ </svg>
85
+ Login with Bluesky</Subheading
86
+ >
87
+
88
+ <div class="text-base-600 dark:text-base-400 text-xs leading-5">
89
+ Don't have an account?
90
+ <br />
91
+ <a
92
+ href="https://bsky.app"
93
+ target="_blank"
94
+ class="text-accent-600 dark:text-accent-400 dark:hover:text-accent-500 hover:text-accent-500 font-medium transition-colors"
95
+ >
96
+ Sign up on bluesky
97
+ </a>, then come back here.
98
+ </div>
99
+
100
+ {#if lastLogin}
101
+ <Label for="bluesky-handle" class="mt-4 text-sm">Recent login:</Label>
102
+ <Button
103
+ class="max-w-xs overflow-x-hidden justify-start truncate"
104
+ variant="primary"
105
+ onclick={() => onLogin(lastLogin?.handle ?? '')}
106
+ disabled={loading}
107
+ >
108
+ <Avatar src={lastLogin.avatar} class="size-6" />
109
+
110
+ <div
111
+ class="text-accent-600 dark:text-accent-400 text-md max-w-full truncate overflow-x-hidden font-semibold"
112
+ >
113
+ <p>{loading ? 'Loading...' : lastLogin.handle}</p>
114
+ </div>
115
+ </Button>
116
+ {/if}
117
+
118
+ <div class="mt-4 w-full">
119
+ <Label for="bluesky-handle" class="text-sm">Your handle</Label>
120
+ <div class="mt-2">
121
+ <Input
122
+ bind:ref={input}
123
+ type="text"
124
+ name="bluesky-handle"
125
+ id="bluesky-handle"
126
+ placeholder="yourname.bsky.social"
127
+ class="w-full"
128
+ bind:value
129
+ />
130
+ </div>
131
+ </div>
132
+
133
+ {#if error}
134
+ <p class="text-accent-500 mt-2 text-sm font-medium">{error}</p>
135
+ {/if}
136
+
137
+ <Button type="submit" class="mt-2 ml-auto w-full md:w-auto" disabled={loading}
138
+ >{loading ? 'Loading...' : 'Login'}</Button
139
+ >
140
+ </form>
141
+ </Modal>
@@ -0,0 +1,9 @@
1
+ export declare const blueskyLoginModalState: {
2
+ open: boolean;
3
+ show: () => true;
4
+ hide: () => false;
5
+ };
6
+ import type { BlueskyLoginProps } from '.';
7
+ declare const BlueskyLoginModal: import("svelte").Component<BlueskyLoginProps, {}, "">;
8
+ type BlueskyLoginModal = ReturnType<typeof BlueskyLoginModal>;
9
+ export default BlueskyLoginModal;
@@ -0,0 +1,8 @@
1
+ export { default as BlueskyLoginModal } from './BlueskyLoginModal.svelte';
2
+ export { blueskyLoginModalState } from './BlueskyLoginModal.svelte';
3
+ export { default as BlueskyLogin } from './BlueskyLogin.svelte';
4
+ export type BlueskyLoginProps = {
5
+ login?: (handle: string) => Promise<boolean | undefined>;
6
+ formAction?: string;
7
+ formMethod?: 'dialog' | 'get' | 'post' | 'DIALOG' | 'GET' | 'POST' | null;
8
+ };
@@ -0,0 +1,3 @@
1
+ export { default as BlueskyLoginModal } from './BlueskyLoginModal.svelte';
2
+ export { blueskyLoginModalState } from './BlueskyLoginModal.svelte';
3
+ export { default as BlueskyLogin } from './BlueskyLogin.svelte';
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import type { FeedViewPost } from '@atproto/api/dist/client/types/app/bsky/feed/defs';
3
+ import { Post } from '../post';
4
+ import { blueskyPostToPostData } from '.';
5
+ import type { Snippet } from 'svelte';
6
+
7
+ let {
8
+ feedViewPost,
9
+ children,
10
+ showLogo = false,
11
+ ...restProps
12
+ }: { feedViewPost?: FeedViewPost; children?: Snippet; showLogo?: boolean } = $props();
13
+
14
+ const postData = $derived(feedViewPost ? blueskyPostToPostData(feedViewPost) : undefined);
15
+ </script>
16
+
17
+ {#snippet logo()}
18
+ <a
19
+ class="text-accent-700 dark:text-accent-400 hover:text-accent-600 dark:hover:text-accent-500"
20
+ href={postData?.href}
21
+ >
22
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" class="size-4" viewBox="0 0 600 530">
23
+ <path
24
+ d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z"
25
+ fill="currentColor"
26
+ />
27
+ </svg>
28
+ <span class="sr-only">Bluesky</span>
29
+ </a>
30
+ {/snippet}
31
+
32
+ {#if postData}
33
+ <Post data={postData} logo={showLogo ? logo : undefined} {...restProps}>
34
+ {@render children?.()}
35
+ </Post>
36
+ {/if}
@@ -0,0 +1,10 @@
1
+ import type { FeedViewPost } from '@atproto/api/dist/client/types/app/bsky/feed/defs';
2
+ import type { Snippet } from 'svelte';
3
+ type $$ComponentProps = {
4
+ feedViewPost?: FeedViewPost;
5
+ children?: Snippet;
6
+ showLogo?: boolean;
7
+ };
8
+ declare const BlueskyPost: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type BlueskyPost = ReturnType<typeof BlueskyPost>;
10
+ export default BlueskyPost;
@@ -0,0 +1,5 @@
1
+ import type { PostData } from '../post';
2
+ import type { PostView } from '@atproto/api/dist/client/types/app/bsky/feed/defs';
3
+ export declare function blueskyPostToPostData(data: PostView, baseUrl?: string): PostData;
4
+ export declare function blueskyPostToHTML(post: any, baseUrl?: string): string;
5
+ export { default as BlueskyPost } from './BlueskyPost.svelte';
@@ -0,0 +1,110 @@
1
+ import { RichText } from '@atproto/api';
2
+ function blueskyEmbedTypeToEmbedType(type) {
3
+ switch (type) {
4
+ case 'app.bsky.embed.external#view':
5
+ case 'app.bsky.embed.external':
6
+ return 'external';
7
+ case 'app.bsky.embed.images#view':
8
+ case 'app.bsky.embed.images':
9
+ return 'images';
10
+ case 'app.bsky.embed.video#view':
11
+ case 'app.bsky.embed.video':
12
+ return 'video';
13
+ default:
14
+ return 'unknown';
15
+ }
16
+ }
17
+ export function blueskyPostToPostData(data, baseUrl = 'https://bsky.app') {
18
+ console.log(data);
19
+ const post = data;
20
+ // const reason = data.reason;
21
+ // const reply = data.reply?.parent;
22
+ // const replyId = reply?.uri?.split('/').pop();
23
+ const id = post.uri.split('/').pop();
24
+ return {
25
+ id,
26
+ href: `${baseUrl}/profile/${post.author.handle}/post/${id}`,
27
+ // reposted:
28
+ // reason && reason.$type === 'app.bsky.feed.defs#reasonRepost'
29
+ // ? {
30
+ // handle: reason.by.handle,
31
+ // href: `${baseUrl}/profile/${reason.by.handle}`
32
+ // }
33
+ // : undefined,
34
+ // replyTo:
35
+ // reply && replyId
36
+ // ? {
37
+ // handle: reply.author.handle,
38
+ // href: `${baseUrl}/profile/${reply.author.handle}/post/${replyId}`
39
+ // }
40
+ // : undefined,
41
+ author: {
42
+ displayName: post.author.displayName,
43
+ handle: post.author.handle,
44
+ avatar: post.author.avatar,
45
+ href: `${baseUrl}/profile/${post.author.did}`
46
+ },
47
+ replyCount: post.replyCount ?? 0,
48
+ repostCount: post.repostCount ?? 0,
49
+ likeCount: post.likeCount ?? 0,
50
+ createdAt: post.record.createdAt ?? 0,
51
+ embed: post.embed
52
+ ? {
53
+ type: blueskyEmbedTypeToEmbedType(post.embed?.$type),
54
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
+ images: post.embed?.images?.map((image) => ({
56
+ alt: image.alt,
57
+ thumb: image.thumb,
58
+ aspectRatio: image.aspectRatio,
59
+ fullsize: image.fullsize
60
+ })),
61
+ external: post.embed?.external
62
+ ? {
63
+ href: post.embed.external.uri,
64
+ title: post.embed.external.title,
65
+ description: post.embed.external.description,
66
+ thumb: post.embed.external.thumb
67
+ }
68
+ : undefined,
69
+ video: post.embed
70
+ ? {
71
+ playlist: post.embed.playlist,
72
+ thumb: post.embed.thumbnail,
73
+ alt: post.embed.alt,
74
+ aspectRatio: post.embed.aspectRatio
75
+ }
76
+ : undefined
77
+ }
78
+ : undefined,
79
+ htmlContent: blueskyPostToHTML(post, baseUrl)
80
+ };
81
+ }
82
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
+ export function blueskyPostToHTML(post, baseUrl = 'https://bsky.app') {
84
+ if (!post?.record) {
85
+ return '';
86
+ }
87
+ const rt = new RichText(post.record);
88
+ let html = '';
89
+ const createLink = (href, text) => {
90
+ return `<a target="_blank" rel="noopener noreferrer nofollow" href="${encodeURI(href)}">${encodeURI(text)}</a>`;
91
+ };
92
+ for (const segment of rt.segments()) {
93
+ if (!segment)
94
+ continue;
95
+ if (segment.isLink() && segment.link?.uri) {
96
+ html += createLink(segment.link?.uri, segment.text);
97
+ }
98
+ else if (segment.isMention() && segment.mention?.did) {
99
+ html += createLink(`${baseUrl}/profile/${segment.mention?.did}`, segment.text);
100
+ }
101
+ else if (segment.isTag() && segment.tag?.tag) {
102
+ html += createLink(`${baseUrl}/hashtag/${segment.tag?.tag}`, segment.text);
103
+ }
104
+ else {
105
+ html += segment.text;
106
+ }
107
+ }
108
+ return html.replace(/\n/g, '<br>');
109
+ }
110
+ export { default as BlueskyPost } from './BlueskyPost.svelte';
File without changes
@@ -0,0 +1,26 @@
1
+ export default Chat;
2
+ type Chat = SvelteComponent<{
3
+ [x: string]: never;
4
+ }, {
5
+ [evt: string]: CustomEvent<any>;
6
+ }, {}> & {
7
+ $$bindings?: string | undefined;
8
+ };
9
+ declare const Chat: $$__sveltets_2_IsomorphicComponent<{
10
+ [x: string]: never;
11
+ }, {
12
+ [evt: string]: CustomEvent<any>;
13
+ }, {}, {}, string>;
14
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
15
+ new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
16
+ $$bindings?: Bindings;
17
+ } & Exports;
18
+ (internal: unknown, props: {
19
+ $$events?: Events;
20
+ $$slots?: Slots;
21
+ }): Exports & {
22
+ $set?: any;
23
+ $on?: any;
24
+ };
25
+ z_$$bindings?: Bindings;
26
+ }
File without changes
@@ -0,0 +1,26 @@
1
+ export default ChatMessage;
2
+ type ChatMessage = SvelteComponent<{
3
+ [x: string]: never;
4
+ }, {
5
+ [evt: string]: CustomEvent<any>;
6
+ }, {}> & {
7
+ $$bindings?: string | undefined;
8
+ };
9
+ declare const ChatMessage: $$__sveltets_2_IsomorphicComponent<{
10
+ [x: string]: never;
11
+ }, {
12
+ [evt: string]: CustomEvent<any>;
13
+ }, {}, {}, string>;
14
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
15
+ new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
16
+ $$bindings?: Bindings;
17
+ } & Exports;
18
+ (internal: unknown, props: {
19
+ $$events?: Events;
20
+ $$slots?: Slots;
21
+ }): Exports & {
22
+ $set?: any;
23
+ $on?: any;
24
+ };
25
+ z_$$bindings?: Bindings;
26
+ }
@@ -0,0 +1 @@
1
+ export { default as Chat } from './Chat.svelte';
@@ -0,0 +1 @@
1
+ export { default as Chat } from './Chat.svelte';
@@ -0,0 +1,91 @@
1
+ <script lang="ts">
2
+ import { cn, ScrollArea } from '@foxui/core';
3
+ import { isEmojiSupported } from 'is-emoji-supported';
4
+ import { allGroups } from './emoji';
5
+ import Database from 'emoji-picker-element/database';
6
+ import type { NativeEmoji } from 'emoji-picker-element/shared';
7
+ import { fade } from 'svelte/transition';
8
+
9
+ let currentGroup = $state(allGroups[0].id);
10
+ let db: Database | undefined = $state();
11
+
12
+ let {
13
+ onpicked,
14
+ height = 300,
15
+ width = 344,
16
+ columns = 8,
17
+ class: className
18
+ }: {
19
+ onpicked?: (emoji: NativeEmoji) => void;
20
+ height?: number;
21
+ width?: number;
22
+ columns?: number;
23
+ class?: string;
24
+ } = $props();
25
+
26
+ $effect(() => {
27
+ if (db) return;
28
+ import('emoji-picker-element').then(({ Database }) => {
29
+ db = new Database();
30
+
31
+ // go through all groups and check if the emoji is supported (will be cached)
32
+ // so that things appear faster when we select a group
33
+ for (const group of allGroups) {
34
+ db.getEmojiByGroup(group.id).then((emojis) => {
35
+ for (const emoji of emojis) {
36
+ isEmojiSupported(emoji.unicode);
37
+ }
38
+ });
39
+ }
40
+ });
41
+ });
42
+ </script>
43
+
44
+ <div class={cn('flex flex-col', className)} style="height: {height}px; width: {width}px;">
45
+ <ScrollArea
46
+ class="grid w-full select-none space-y-0 px-2"
47
+ style="height: {height}px; grid-template-columns: repeat({columns}, minmax(0, 1fr));"
48
+ >
49
+ {#await db?.getEmojiByGroup(currentGroup) then emojis}
50
+ {#if emojis}
51
+ {#each emojis as emoji}
52
+ {#if isEmojiSupported(emoji.unicode)}
53
+ <button
54
+ onclick={() => {
55
+ onpicked?.(emoji);
56
+ }}
57
+ class="hover:bg-accent-300/20 dark:hover:bg-accent-700/20 size-10 cursor-pointer rounded-full text-center text-xl transition-transform duration-150 hover:scale-110"
58
+ >{emoji.unicode}</button
59
+ >
60
+ {/if}
61
+ {/each}
62
+ {/if}
63
+ {/await}
64
+ </ScrollArea>
65
+ <div
66
+ class="border-base-300/50 dark:border-base-700/50 flex justify-between gap-2 border-t px-3"
67
+ style="width: {width}px;"
68
+ >
69
+ {#each allGroups as group}
70
+ <button
71
+ onclick={() => (currentGroup = group.id)}
72
+ class={cn(
73
+ '[&>svg]:size-4.5 relative cursor-pointer [&>svg]:transition-all [&>svg]:duration-100 [&>svg]:hover:scale-105 py-2',
74
+ group.id === currentGroup
75
+ ? 'text-accent-600 dark:text-accent-400'
76
+ : 'hover:text-accent-700 dark:hover:text-accent-300'
77
+ )}
78
+ >
79
+ {@html group.svg}
80
+ <span class="sr-only">{group.name}</span>
81
+
82
+ {#if group.id === currentGroup}
83
+ <span
84
+ transition:fade
85
+ class="from-accent-500/0 via-accent-500/60 to-accent-500/0 dark:from-accent-400/0 dark:via-accent-400/60 dark:to-accent-400/0 absolute -inset-x-1 -top-px h-px bg-gradient-to-r"
86
+ ></span>
87
+ {/if}
88
+ </button>
89
+ {/each}
90
+ </div>
91
+ </div>