@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.
- package/LICENSE +21 -0
- package/README.md +20 -0
- package/dist/components/blog-entry/BlogEntry.svelte +80 -0
- package/dist/components/blog-entry/BlogEntry.svelte.d.ts +14 -0
- package/dist/components/blog-entry/index.d.ts +1 -0
- package/dist/components/blog-entry/index.js +1 -0
- package/dist/components/bluesky-login/BlueskyLogin.svelte +23 -0
- package/dist/components/bluesky-login/BlueskyLogin.svelte.d.ts +4 -0
- package/dist/components/bluesky-login/BlueskyLoginModal.svelte +141 -0
- package/dist/components/bluesky-login/BlueskyLoginModal.svelte.d.ts +9 -0
- package/dist/components/bluesky-login/index.d.ts +8 -0
- package/dist/components/bluesky-login/index.js +3 -0
- package/dist/components/bluesky-post/BlueskyPost.svelte +36 -0
- package/dist/components/bluesky-post/BlueskyPost.svelte.d.ts +10 -0
- package/dist/components/bluesky-post/index.d.ts +5 -0
- package/dist/components/bluesky-post/index.js +110 -0
- package/dist/components/chat/Chat.svelte +0 -0
- package/dist/components/chat/Chat.svelte.d.ts +26 -0
- package/dist/components/chat/ChatMessage.svelte +0 -0
- package/dist/components/chat/ChatMessage.svelte.d.ts +26 -0
- package/dist/components/chat/index.d.ts +1 -0
- package/dist/components/chat/index.js +1 -0
- package/dist/components/emoji-picker/EmojiPicker.svelte +91 -0
- package/dist/components/emoji-picker/EmojiPicker.svelte.d.ts +11 -0
- package/dist/components/emoji-picker/PopoverEmojiPicker.svelte +24 -0
- package/dist/components/emoji-picker/PopoverEmojiPicker.svelte.d.ts +11 -0
- package/dist/components/emoji-picker/emoji.d.ts +7 -0
- package/dist/components/emoji-picker/emoji.js +56 -0
- package/dist/components/emoji-picker/index.d.ts +2 -0
- package/dist/components/emoji-picker/index.js +2 -0
- package/dist/components/github-corner/GithubCorner.svelte +62 -0
- package/dist/components/github-corner/GithubCorner.svelte.d.ts +4 -0
- package/dist/components/github-corner/index.d.ts +1 -0
- package/dist/components/github-corner/index.js +1 -0
- package/dist/components/index.d.ts +12 -0
- package/dist/components/index.js +20 -0
- package/dist/components/nested-comments/Comment.svelte +151 -0
- package/dist/components/nested-comments/Comment.svelte.d.ts +9 -0
- package/dist/components/nested-comments/NestedComments.svelte +14 -0
- package/dist/components/nested-comments/NestedComments.svelte.d.ts +7 -0
- package/dist/components/nested-comments/index.d.ts +1 -0
- package/dist/components/nested-comments/index.js +1 -0
- package/dist/components/post/Post.svelte +294 -0
- package/dist/components/post/Post.svelte.d.ts +23 -0
- package/dist/components/post/PostAction.svelte +27 -0
- package/dist/components/post/PostAction.svelte.d.ts +9 -0
- package/dist/components/post/embeds/Embed.svelte +24 -0
- package/dist/components/post/embeds/Embed.svelte.d.ts +7 -0
- package/dist/components/post/embeds/External.svelte +39 -0
- package/dist/components/post/embeds/External.svelte.d.ts +7 -0
- package/dist/components/post/embeds/Images.svelte +38 -0
- package/dist/components/post/embeds/Images.svelte.d.ts +7 -0
- package/dist/components/post/embeds/Video.svelte +45 -0
- package/dist/components/post/embeds/Video.svelte.d.ts +7 -0
- package/dist/components/post/index.d.ts +63 -0
- package/dist/components/post/index.js +1 -0
- package/dist/components/social-icons/All.svelte +47 -0
- package/dist/components/social-icons/All.svelte.d.ts +12 -0
- package/dist/components/social-icons/Bluesky.svelte +37 -0
- package/dist/components/social-icons/Bluesky.svelte.d.ts +8 -0
- package/dist/components/social-icons/Discord.svelte +37 -0
- package/dist/components/social-icons/Discord.svelte.d.ts +8 -0
- package/dist/components/social-icons/Facebook.svelte +37 -0
- package/dist/components/social-icons/Facebook.svelte.d.ts +8 -0
- package/dist/components/social-icons/Github.svelte +38 -0
- package/dist/components/social-icons/Github.svelte.d.ts +8 -0
- package/dist/components/social-icons/Twitter.svelte +37 -0
- package/dist/components/social-icons/Twitter.svelte.d.ts +8 -0
- package/dist/components/social-icons/Youtube.svelte +36 -0
- package/dist/components/social-icons/Youtube.svelte.d.ts +8 -0
- package/dist/components/social-icons/index.d.ts +7 -0
- package/dist/components/social-icons/index.js +8 -0
- package/dist/components/star-rating/StarRating.svelte +104 -0
- package/dist/components/star-rating/StarRating.svelte.d.ts +13 -0
- package/dist/components/star-rating/index.d.ts +1 -0
- package/dist/components/star-rating/index.js +1 -0
- package/dist/components/swiper-cards/CardSwiper.svelte +235 -0
- package/dist/components/swiper-cards/CardSwiper.svelte.d.ts +18 -0
- package/dist/components/swiper-cards/index.d.ts +14 -0
- package/dist/components/swiper-cards/index.js +1 -0
- package/dist/components/user-profile/UserProfile.svelte +60 -0
- package/dist/components/user-profile/UserProfile.svelte.d.ts +13 -0
- package/dist/components/user-profile/index.d.ts +1 -0
- package/dist/components/user-profile/index.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +1 -0
- 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,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,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>
|