tangled
alpha
login
or
join now
flo-bit.dev
/
blento
your personal website on atproto - mirror
blento.app
20
fork
atom
overview
issues
pulls
pipelines
commit
Florian
3 weeks ago
1b9377e9
8053b848
+355
-222
22 changed files
expand all
collapse all
unified
split
package.json
pnpm-lock.yaml
src
app.html
lib
EditableWebsite.svelte
Profile.svelte
Website.svelte
cards
BaseCard
BaseCard.svelte
BaseEditingCard.svelte
BigSocialCard
BigSocialCard.svelte
CreateBigSocialCardModal.svelte
SidebarItemBigSocialCard.svelte
index.ts
LinkCard
EditingLinkCard.svelte
LinkCard.svelte
SpecialCards
UpdatedBlentos
UpdatedBlentosCard.svelte
TextCard
EditingTextCard.svelte
TextCard.svelte
TextCardSettings.svelte
index.ts
types.ts
utils
MarkdownTextEditor.svelte
components
ImageDropper.svelte
+1
package.json
···
68
68
"link-preview-js": "^4.0.0",
69
69
"marked": "^15.0.11",
70
70
"plyr": "^3.8.4",
71
71
+
"simple-icons": "^16.5.0",
71
72
"svelte-sonner": "^1.0.7",
72
73
"tailwind-merge": "^3.4.0",
73
74
"tailwind-variants": "^3.2.2",
+9
pnpm-lock.yaml
···
89
89
plyr:
90
90
specifier: ^3.8.4
91
91
version: 3.8.4
92
92
+
simple-icons:
93
93
+
specifier: ^16.5.0
94
94
+
version: 16.5.0
92
95
svelte-sonner:
93
96
specifier: ^1.0.7
94
97
version: 1.0.7(svelte@5.45.8)
···
2668
2671
side-channel@1.1.0:
2669
2672
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, tarball: https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz}
2670
2673
engines: {node: '>= 0.4'}
2674
2674
+
2675
2675
+
simple-icons@16.5.0:
2676
2676
+
resolution: {integrity: sha512-72nn0oHADKx6Hknu7q6M0vfL8LiCUMKABOHane2+4xdqaFBSHfNNBjuZioihiqVQMz7IvVle4NKAM0IlXvl/9A==, tarball: https://registry.npmjs.org/simple-icons/-/simple-icons-16.5.0.tgz}
2677
2677
+
engines: {node: '>=0.12.18'}
2671
2678
2672
2679
simple-swizzle@0.2.4:
2673
2680
resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==, tarball: https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz}
···
5438
5445
side-channel-list: 1.0.0
5439
5446
side-channel-map: 1.0.1
5440
5447
side-channel-weakmap: 1.0.2
5448
5448
+
5449
5449
+
simple-icons@16.5.0: {}
5441
5450
5442
5451
simple-swizzle@0.2.4:
5443
5452
dependencies:
+2
-2
src/app.html
···
1
1
<!doctype html>
2
2
-
<html lang="en" class="stone">
2
2
+
<html lang="en" class="neutral">
3
3
<head>
4
4
<meta charset="utf-8" />
5
5
<meta name="viewport" content="width=device-width, initial-scale=1" />
···
11
11
data-website-id="c55efa23-9abe-4a7e-b8fd-81b9fa7e8052"
12
12
></script>
13
13
</head>
14
14
-
<body data-sveltekit-preload-data="hover" class="bg-base-200/50 dark:bg-base-950">
14
14
+
<body data-sveltekit-preload-data="hover" class="bg-base-50 dark:bg-base-900">
15
15
<div style="display: contents">%sveltekit.body%</div>
16
16
</body>
17
17
</html>
+56
-3
src/lib/EditableWebsite.svelte
···
28
28
import { setDidContext, setHandleContext } from './website/context';
29
29
import BaseEditingCard from './cards/BaseCard/BaseEditingCard.svelte';
30
30
import Settings from './Settings.svelte';
31
31
+
import ImageDropper from './components/ImageDropper.svelte';
31
32
32
33
let {
33
34
handle,
···
234
235
}
235
236
</script>
236
237
238
238
+
<svelte:body
239
239
+
onpaste={(event) => {
240
240
+
const target = event.target;
241
241
+
242
242
+
const active = document.activeElement;
243
243
+
const isEditable =
244
244
+
active instanceof HTMLInputElement ||
245
245
+
active instanceof HTMLTextAreaElement ||
246
246
+
active?.isContentEditable;
247
247
+
248
248
+
if (isEditable) {
249
249
+
// Let normal paste happen
250
250
+
return;
251
251
+
}
252
252
+
253
253
+
const text = event.clipboardData?.getData('text/plain');
254
254
+
255
255
+
if (!text) return;
256
256
+
257
257
+
try {
258
258
+
const url = new URL(text);
259
259
+
260
260
+
let item: Item = {
261
261
+
id: TID.nextStr(),
262
262
+
x: 0,
263
263
+
y: 0,
264
264
+
w: 2,
265
265
+
h: 2,
266
266
+
mobileH: 4,
267
267
+
mobileW: 4,
268
268
+
mobileX: 0,
269
269
+
mobileY: 0,
270
270
+
cardType: '',
271
271
+
cardData: {}
272
272
+
};
273
273
+
274
274
+
newItem.item = item;
275
275
+
276
276
+
for (const cardDef of AllCardDefinitions) {
277
277
+
if (cardDef.onUrlHandler?.(text, item)) {
278
278
+
item.cardType = cardDef.type;
279
279
+
saveNewItem();
280
280
+
}
281
281
+
}
282
282
+
283
283
+
newItem = {};
284
284
+
} catch (e) {
285
285
+
return;
286
286
+
}
287
287
+
}}
288
288
+
/>
289
289
+
290
290
+
<!-- <ImageDropper processImageFile={(file: File) => {}} /> -->
291
291
+
237
292
{#if !dev}
238
293
<div
239
294
class="bg-base-200 dark:bg-base-800 fixed inset-0 z-50 inline-flex h-full w-full items-center justify-center p-4 text-center lg:hidden"
···
270
325
>
271
326
<Profile {handle} {did} {data} />
272
327
273
273
-
<div
274
274
-
class="mx-auto max-w-2xl @5xl/wrapper:grid @5xl/wrapper:max-w-7xl @5xl/wrapper:grid-cols-4"
275
275
-
>
328
328
+
<div class="mx-auto max-w-lg @5xl/wrapper:grid @5xl/wrapper:max-w-7xl @5xl/wrapper:grid-cols-4">
276
329
<div></div>
277
330
<!-- svelte-ignore a11y_no_static_element_interactions -->
278
331
<div
+2
-2
src/lib/Profile.svelte
···
29
29
30
30
<!-- lg:fixed lg:h-screen lg:w-1/4 lg:max-w-none lg:px-12 lg:pt-24 xl:w-1/3 -->
31
31
<div
32
32
-
class="mx-auto flex max-w-2xl flex-col justify-between px-8 @5xl/wrapper:fixed @5xl/wrapper:h-screen @5xl/wrapper:w-1/4 @5xl/wrapper:max-w-none @5xl/wrapper:px-12"
32
32
+
class="mx-auto flex max-w-lg flex-col justify-between px-8 @5xl/wrapper:fixed @5xl/wrapper:h-screen @5xl/wrapper:w-1/4 @5xl/wrapper:max-w-none @5xl/wrapper:px-12"
33
33
>
34
34
<div class="flex flex-col gap-4 pt-16 pb-8 @5xl/wrapper:h-screen @5xl/wrapper:pt-24">
35
35
{#if profileData?.avatar?.ref?.$link}
36
36
<img
37
37
-
class="size-32 rounded-full @5xl/wrapper:size-44"
37
37
+
class="size-32 rounded-full @5xl/wrapper:size-44 border border-base-400 dark:border-base-800"
38
38
src={'https://cdn.bsky.app/img/avatar/plain/' + did + '/' + profileData.avatar.ref.$link}
39
39
alt=""
40
40
/>
+1
-1
src/lib/Website.svelte
···
39
39
<div class="@container/wrapper relative w-full">
40
40
<Profile {handle} {did} {data} showEditButton={true} />
41
41
42
42
-
<div class="mx-auto max-w-2xl lg:grid lg:max-w-none lg:grid-cols-4">
42
42
+
<div class="mx-auto max-w-lg lg:grid lg:max-w-none lg:grid-cols-4">
43
43
<div></div>
44
44
<div
45
45
bind:this={container}
+3
-1
src/lib/cards/BaseCard/BaseCard.svelte
···
7
7
import { getColor } from '..';
8
8
9
9
const colors = {
10
10
-
base: 'bg-base-50 dark:bg-base-900',
10
10
+
base: 'bg-base-200/50 dark:bg-base-950/50',
11
11
accent:
12
12
'bg-accent-400 dark:bg-accent-500 accent',
13
13
transparent: ''
···
27
27
isEditing = false,
28
28
controls,
29
29
showOutline,
30
30
+
class: className,
30
31
...rest
31
32
}: BaseCardProps = $props();
32
33
···
43
44
color ? (colors[color] ?? colors.accent) : colors.base,
44
45
color !== 'accent' && item.color !== 'base' && item.color !== 'transparent' ? color : '',
45
46
showOutline ? 'outline-2' : '',
47
47
+
className
46
48
]}
47
49
style={`
48
50
--mx: ${item.mobileX};
+6
-7
src/lib/cards/BaseCard/BaseEditingCard.svelte
···
106
106
let newW = resizeStartW + gridDeltaW;
107
107
let newH = resizeStartH + gridDeltaH;
108
108
109
109
-
console.log(item.mobileW, newW);
110
109
if (isMobile()) {
111
110
newW = Math.round(newW / 4) * 4;
112
111
} else {
113
112
newW = Math.round(newW / 2) * 2;
114
113
}
115
115
-
console.log(item.mobileW, newW);
114
114
+
let mult = isMobile() ? 2 : 1;
116
115
117
116
// Clamp to min/max
118
118
-
newW = Math.max(minW, Math.min(maxW, newW));
119
119
-
newH = Math.max(minH, Math.min(maxH, newH));
117
117
+
newW = Math.max(minW * mult, Math.min(maxW, newW));
118
118
+
newH = Math.max(minH * mult, Math.min(maxH, newH));
120
119
121
120
// Only call onsetsize if size changed
122
121
const currentW = isMobile() ? (item.mobileW ?? item.w) : item.w;
···
137
136
if (!cardDef) return false;
138
137
139
138
if (isMobile()) {
140
140
-
w *= 2;
141
141
-
h *= 2;
139
139
+
140
140
+
return w >= minW && w*2 <= maxW && h >= minH && h*2 <= maxH;
142
141
}
143
142
144
143
return w >= minW && w <= maxW && h >= minH && h <= maxH;
···
155
154
let settingsPopoverOpen = $state(false);
156
155
</script>
157
156
158
158
-
<BaseCard {item} {...rest} isEditing={true} bind:ref showOutline={isResizing}>
157
157
+
<BaseCard {item} isEditing={true} bind:ref showOutline={isResizing} class="starting:scale-0 scale-100 starting:opacity-0 opacity-100" {...rest} >
159
158
{@render children?.()}
160
159
161
160
{#snippet controls()}
+9
-124
src/lib/cards/BigSocialCard/BigSocialCard.svelte
···
1
1
<script lang="ts">
2
2
+
import { platformsData } from '.';
2
3
import type { ContentComponentProps } from '../types';
3
4
4
5
let { item }: ContentComponentProps = $props();
5
6
6
7
const platform = $derived(item.cardData.platform as string);
8
8
+
9
9
+
$inspect(platformsData[platform].svg)
7
10
</script>
8
11
9
12
<a
10
13
href={item.cardData.href}
11
14
target="_blank"
12
15
rel="noopener noreferrer"
13
13
-
class="flex h-full w-full items-center justify-center p-4"
16
16
+
class="flex h-full w-full items-center justify-center p-10"
17
17
+
style={
18
18
+
`background-color: #${item.cardData.color}`
19
19
+
}
14
20
>
15
15
-
<div class="flex aspect-square max-h-full max-w-full items-center justify-center">
16
16
-
{#if platform === 'instagram'}
17
17
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
18
18
-
<path
19
19
-
d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"
20
20
-
/>
21
21
-
</svg>
22
22
-
{:else if platform === 'facebook'}
23
23
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
24
24
-
<path
25
25
-
d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"
26
26
-
/>
27
27
-
</svg>
28
28
-
{:else if platform === 'twitter' || platform === 'x'}
29
29
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
30
30
-
<path
31
31
-
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
32
32
-
/>
33
33
-
</svg>
34
34
-
{:else if platform === 'youtube'}
35
35
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
36
36
-
<path
37
37
-
d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"
38
38
-
/>
39
39
-
</svg>
40
40
-
{:else if platform === 'tiktok'}
41
41
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
42
42
-
<path
43
43
-
d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"
44
44
-
/>
45
45
-
</svg>
46
46
-
{:else if platform === 'linkedin'}
47
47
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
48
48
-
<path
49
49
-
d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"
50
50
-
/>
51
51
-
</svg>
52
52
-
{:else if platform === 'bluesky'}
53
53
-
<svg class="h-full w-full" viewBox="0 0 568 501" fill="currentColor">
54
54
-
<path
55
55
-
d="M123.121 33.6637C188.241 82.5526 258.281 181.681 284 234.873C309.719 181.681 379.759 82.5526 444.879 33.6637C491.866 -1.61183 568 -28.9064 568 57.9464C568 75.2916 558.055 203.659 552.222 224.501C531.947 296.954 458.067 315.434 392.347 304.249C507.222 323.8 536.444 388.56 473.333 453.32C353.473 576.312 301.061 422.461 287.631 383.039C285.169 374.014 284.017 369.587 284 371.839C283.983 369.587 282.831 374.014 280.369 383.039C266.939 422.461 214.527 576.312 94.6667 453.32C31.5556 388.56 60.7778 323.8 175.653 304.249C109.933 315.434 36.0535 296.954 15.7778 224.501C9.94525 203.659 0 75.2916 0 57.9464C0 -28.9064 76.1345 -1.61183 123.121 33.6637Z"
56
56
-
/>
57
57
-
</svg>
58
58
-
{:else if platform === 'threads'}
59
59
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
60
60
-
<path
61
61
-
d="M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.74-1.752-2.96-.065-1.182.408-2.256 1.33-3.022.88-.732 2.07-1.128 3.446-1.145.875-.01 1.71.097 2.482.32.019-.776-.042-1.49-.184-2.115H9.346v-2.06h6.043c.122.534.19 1.245.195 2.098-.38-.125-.77-.227-1.172-.305-.94-.182-1.935-.227-2.955-.127-1.307.13-2.344.483-3.086 1.051-.655.5-1.01 1.143-.999 1.808.01.663.376 1.23.999 1.549.549.28 1.254.406 2.046.369.958-.046 1.704-.324 2.218-.825.334-.326.56-.74.688-1.242-.29-.083-.61-.153-.96-.207-.942-.148-2.042-.105-3.06.121l-.524-1.993c1.32-.294 2.695-.35 3.884-.159.476.077.912.184 1.312.317a9.9 9.9 0 0 0-.078-1.136l2.043-.284c.085.61.116 1.328.09 2.138.922.499 1.631 1.18 2.104 2.027.593 1.06.854 2.39.712 3.848-.142 1.456-.726 2.78-1.684 3.823-1.724 1.877-4.123 2.835-7.134 2.849z"
62
62
-
/>
63
63
-
</svg>
64
64
-
{:else if platform === 'snapchat'}
65
65
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
66
66
-
<path
67
67
-
d="M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.967-2.911 2.168-2.911 1.024 0 1.518.769 1.518 1.688 0 1.029-.653 2.567-.992 3.992-.285 1.193.6 2.165 1.775 2.165 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.401.165-1.495-.69-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026L12.017 0z"
68
68
-
/>
69
69
-
</svg>
70
70
-
{:else if platform === 'pinterest'}
71
71
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
72
72
-
<path
73
73
-
d="M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.967-2.911 2.168-2.911 1.024 0 1.518.769 1.518 1.688 0 1.029-.653 2.567-.992 3.992-.285 1.193.6 2.165 1.775 2.165 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.401.165-1.495-.69-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026L12.017 0z"
74
74
-
/>
75
75
-
</svg>
76
76
-
{:else if platform === 'twitch'}
77
77
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
78
78
-
<path
79
79
-
d="M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714Z"
80
80
-
/>
81
81
-
</svg>
82
82
-
{:else if platform === 'discord'}
83
83
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
84
84
-
<path
85
85
-
d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"
86
86
-
/>
87
87
-
</svg>
88
88
-
{:else if platform === 'github'}
89
89
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
90
90
-
<path
91
91
-
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
92
92
-
/>
93
93
-
</svg>
94
94
-
{:else if platform === 'spotify'}
95
95
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
96
96
-
<path
97
97
-
d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z"
98
98
-
/>
99
99
-
</svg>
100
100
-
{:else if platform === 'reddit'}
101
101
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
102
102
-
<path
103
103
-
d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z"
104
104
-
/>
105
105
-
</svg>
106
106
-
{:else if platform === 'whatsapp'}
107
107
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
108
108
-
<path
109
109
-
d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z"
110
110
-
/>
111
111
-
</svg>
112
112
-
{:else if platform === 'telegram'}
113
113
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
114
114
-
<path
115
115
-
d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"
116
116
-
/>
117
117
-
</svg>
118
118
-
{:else if platform === 'mastodon'}
119
119
-
<svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor">
120
120
-
<path
121
121
-
d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"
122
122
-
/>
123
123
-
</svg>
124
124
-
{:else}
125
125
-
<svg
126
126
-
class="h-full w-full"
127
127
-
xmlns="http://www.w3.org/2000/svg"
128
128
-
viewBox="0 0 24 24"
129
129
-
fill="currentColor"
130
130
-
>
131
131
-
<path
132
132
-
fill-rule="evenodd"
133
133
-
d="M19.902 4.098a3.75 3.75 0 0 0-5.304 0l-4.5 4.5a3.75 3.75 0 0 0 1.035 6.037.75.75 0 0 1-.646 1.353 5.25 5.25 0 0 1-1.449-8.45l4.5-4.5a5.25 5.25 0 1 1 7.424 7.424l-1.757 1.757a.75.75 0 1 1-1.06-1.06l1.757-1.757a3.75 3.75 0 0 0 0-5.304Zm-7.389 4.267a.75.75 0 0 1 1-.353 5.25 5.25 0 0 1 1.449 8.45l-4.5 4.5a5.25 5.25 0 1 1-7.424-7.424l1.757-1.757a.75.75 0 1 1 1.06 1.06l-1.757 1.757a3.75 3.75 0 1 0 5.304 5.304l4.5-4.5a3.75 3.75 0 0 0-1.035-6.037.75.75 0 0 1-.354-1Z"
134
134
-
clip-rule="evenodd"
135
135
-
/>
136
136
-
</svg>
137
137
-
{/if}
21
21
+
<div class="flex aspect-square max-h-full max-w-full items-center justify-center [&_svg]:size-full [&_svg]:max-w-60 [&_svg]:fill-white">
22
22
+
{@html platformsData[platform].svg}
138
23
</div>
139
24
</a>
+3
-54
src/lib/cards/BigSocialCard/CreateBigSocialCardModal.svelte
···
1
1
<script lang="ts">
2
2
import { Alert, Button, Input, Modal, Subheading } from '@foxui/core';
3
3
import type { CreationModalComponentProps } from '../types';
4
4
+
import { detectPlatform, platformPatterns, platformsData } from '.';
4
5
5
6
let { item = $bindable(), oncreate, oncancel }: CreationModalComponentProps = $props();
6
7
7
8
let errorMessage = $state('');
8
9
9
9
-
const platformPatterns: Record<string, RegExp> = {
10
10
-
instagram: /(?:instagram\.com|instagr\.am)/i,
11
11
-
facebook: /(?:facebook\.com|fb\.com|fb\.me)/i,
12
12
-
twitter: /(?:twitter\.com)/i,
13
13
-
x: /(?:x\.com)/i,
14
14
-
youtube: /(?:youtube\.com|youtu\.be)/i,
15
15
-
tiktok: /(?:tiktok\.com)/i,
16
16
-
linkedin: /(?:linkedin\.com)/i,
17
17
-
bluesky: /(?:bsky\.app|bsky\.social)/i,
18
18
-
threads: /(?:threads\.net)/i,
19
19
-
snapchat: /(?:snapchat\.com)/i,
20
20
-
pinterest: /(?:pinterest\.com|pin\.it)/i,
21
21
-
twitch: /(?:twitch\.tv)/i,
22
22
-
discord: /(?:discord\.gg|discord\.com)/i,
23
23
-
github: /(?:github\.com)/i,
24
24
-
spotify: /(?:spotify\.com|open\.spotify\.com)/i,
25
25
-
reddit: /(?:reddit\.com)/i,
26
26
-
whatsapp: /(?:whatsapp\.com|wa\.me)/i,
27
27
-
telegram: /(?:t\.me|telegram\.org)/i,
28
28
-
mastodon: /(?:mastodon\.social|mastodon\.online|mstdn\.social)/i
29
29
-
};
30
30
-
31
31
-
const platformColors: Record<string, string> = {
32
32
-
instagram: 'pink',
33
33
-
facebook: 'blue',
34
34
-
twitter: 'sky',
35
35
-
x: 'zinc',
36
36
-
youtube: 'red',
37
37
-
tiktok: 'zinc',
38
38
-
linkedin: 'blue',
39
39
-
bluesky: 'sky',
40
40
-
threads: 'zinc',
41
41
-
snapchat: 'yellow',
42
42
-
pinterest: 'red',
43
43
-
twitch: 'purple',
44
44
-
discord: 'indigo',
45
45
-
github: 'zinc',
46
46
-
spotify: 'green',
47
47
-
reddit: 'orange',
48
48
-
whatsapp: 'green',
49
49
-
telegram: 'sky',
50
50
-
mastodon: 'purple'
51
51
-
};
52
52
-
53
53
-
function detectPlatform(url: string): string | null {
54
54
-
for (const [platform, pattern] of Object.entries(platformPatterns)) {
55
55
-
if (pattern.test(url)) {
56
56
-
return platform;
57
57
-
}
58
58
-
}
59
59
-
return null;
60
60
-
}
61
61
-
62
10
function handleCreate() {
63
11
errorMessage = '';
64
12
···
76
24
}
77
25
78
26
item.cardData.platform = platform;
79
79
-
item.color = platformColors[platform] || 'accent';
27
27
+
item.cardData.color = platformsData[platform].hex;
28
28
+
80
29
oncreate();
81
30
}
82
31
</script>
+1
-1
src/lib/cards/BigSocialCard/SidebarItemBigSocialCard.svelte
···
17
17
clip-rule="evenodd"
18
18
/>
19
19
</svg>
20
20
-
Big Social</Button
20
20
+
Big Social Icon</Button
21
21
>
+89
-6
src/lib/cards/BigSocialCard/index.ts
···
16
16
};
17
17
card.w = 2;
18
18
card.h = 2;
19
19
-
card.mobileW = 2;
20
20
-
card.mobileH = 2;
19
19
+
card.mobileW = 4;
20
20
+
card.mobileH = 4;
21
21
},
22
22
-
allowSetColor: true,
23
23
-
defaultColor: 'accent',
24
24
-
minW: 1,
25
25
-
minH: 1
22
22
+
allowSetColor: false,
23
23
+
defaultColor: 'transparent',
24
24
+
minW: 2,
25
25
+
minH: 2,
26
26
+
onUrlHandler: (url, item) => {
27
27
+
const platform = detectPlatform(url);
28
28
+
if (!platform) return null;
29
29
+
30
30
+
item.cardData.platform = platform;
31
31
+
item.cardData.color = platformsData[platform].hex;
32
32
+
item.cardData.href = url;
33
33
+
34
34
+
return item;
35
35
+
}
26
36
} as CardDefinition & { type: 'bigsocial' };
37
37
+
38
38
+
export const platformPatterns: Record<string, RegExp> = {
39
39
+
instagram: /(?:instagram\.com|instagr\.am)/i,
40
40
+
facebook: /(?:facebook\.com|fb\.com|fb\.me)/i,
41
41
+
twitter: /(?:twitter\.com)/i,
42
42
+
x: /(?:x\.com)/i,
43
43
+
youtube: /(?:youtube\.com|youtu\.be)/i,
44
44
+
tiktok: /(?:tiktok\.com)/i,
45
45
+
linkedin: /(?:linkedin\.com)/i,
46
46
+
bluesky: /(?:bsky\.app|bsky\.social)/i,
47
47
+
threads: /(?:threads\.net)/i,
48
48
+
snapchat: /(?:snapchat\.com)/i,
49
49
+
pinterest: /(?:pinterest\.com|pin\.it)/i,
50
50
+
twitch: /(?:twitch\.tv)/i,
51
51
+
discord: /(?:discord\.gg|discord\.com)/i,
52
52
+
github: /(?:github\.com)/i,
53
53
+
spotify: /(?:spotify\.com|open\.spotify\.com)/i,
54
54
+
reddit: /(?:reddit\.com)/i,
55
55
+
whatsapp: /(?:whatsapp\.com|wa\.me)/i,
56
56
+
telegram: /(?:t\.me|telegram\.org)/i,
57
57
+
mastodon: /(?:mastodon\.social|mastodon\.online|mstdn\.social)/i
58
58
+
};
59
59
+
60
60
+
import {
61
61
+
siInstagram,
62
62
+
siFacebook,
63
63
+
siX,
64
64
+
siYoutube,
65
65
+
siTiktok,
66
66
+
siBluesky,
67
67
+
siThreads,
68
68
+
siSnapchat,
69
69
+
siPinterest,
70
70
+
siTwitch,
71
71
+
siDiscord,
72
72
+
siGithub,
73
73
+
siSpotify,
74
74
+
siReddit,
75
75
+
siWhatsapp,
76
76
+
siTelegram,
77
77
+
siMastodon,
78
78
+
type SimpleIcon
79
79
+
} from 'simple-icons';
80
80
+
81
81
+
export const platformsData: Record<string, SimpleIcon> = {
82
82
+
instagram: siInstagram,
83
83
+
facebook: siFacebook,
84
84
+
twitter: siX,
85
85
+
x: siX,
86
86
+
youtube: siYoutube,
87
87
+
tiktok: siTiktok,
88
88
+
bluesky: siBluesky,
89
89
+
threads: siThreads,
90
90
+
snapchat: siSnapchat,
91
91
+
pinterest: siPinterest,
92
92
+
twitch: siTwitch,
93
93
+
discord: siDiscord,
94
94
+
github: siGithub,
95
95
+
spotify: siSpotify,
96
96
+
reddit: siReddit,
97
97
+
whatsapp: siWhatsapp,
98
98
+
telegram: siTelegram,
99
99
+
mastodon: siMastodon
100
100
+
};
101
101
+
102
102
+
export function detectPlatform(url: string): string | null {
103
103
+
for (const [platform, pattern] of Object.entries(platformPatterns)) {
104
104
+
if (pattern.test(url)) {
105
105
+
return platform;
106
106
+
}
107
107
+
}
108
108
+
return null;
109
109
+
}
+2
-1
src/lib/cards/LinkCard/EditingLinkCard.svelte
···
1
1
<script lang="ts">
2
2
+
import { browser } from '$app/environment';
2
3
import { getIsMobile } from '$lib/helper';
3
4
import type { ContentComponentProps } from '../types';
4
5
import PlainTextEditor from '../utils/PlainTextEditor.svelte';
···
57
58
</div>
58
59
</div>
59
60
60
60
-
{#if ((isMobile() && item.mobileH >= 8) || (!isMobile() && item.h >= 4)) && item.cardData.image}
61
61
+
{#if browser && ((isMobile() && item.mobileH >= 8) || (!isMobile() && item.h >= 4)) && item.cardData.image}
61
62
<img class=" mb-2 max-h-32 w-full rounded-xl object-cover" src={item.cardData.image} alt="" />
62
63
{/if}
63
64
</div>
+3
-2
src/lib/cards/LinkCard/LinkCard.svelte
···
1
1
<script lang="ts">
2
2
+
import { browser } from '$app/environment';
2
3
import { getIsMobile } from '$lib/helper';
3
4
import type { ContentComponentProps } from '../types';
4
5
···
56
57
</div>
57
58
</div>
58
59
59
59
-
{#if ((isMobile() && item.mobileH >= 8) || (!isMobile() && item.h >= 4)) && item.cardData.image}
60
60
-
<img class=" mb-2 max-h-32 w-full rounded-xl object-cover" src={item.cardData.image} alt="" />
60
60
+
{#if browser && ((isMobile() && item.mobileH >= 8) || (!isMobile() && item.h >= 4)) && item.cardData.image}
61
61
+
<img class="mb-2 max-h-32 w-full starting:opacity-0 opacity-100 transition-opacity duration-100 rounded-xl object-cover" src={item.cardData.image} alt="" />
61
62
{/if}
62
63
{#if item.cardData.href}
63
64
<a
+1
-1
src/lib/cards/SpecialCards/UpdatedBlentos/UpdatedBlentosCard.svelte
···
22
22
target="_blank"
23
23
>
24
24
<img src={profile.avatar} class="aspect-square size-28 rounded-full" alt="" />
25
25
-
<div class="line-clamp-1 text-lg font-bold">{profile.displayName || profile.handle}</div>
25
25
+
<div class="line-clamp-1 text-md font-bold text-center">{profile.displayName || profile.handle}</div>
26
26
</a>
27
27
{/each}
28
28
</div>
+18
-6
src/lib/cards/TextCard/EditingTextCard.svelte
···
1
1
<script lang="ts">
2
2
import type { Item } from '$lib/types';
3
3
-
import { textAlignClasses, verticalAlignClasses } from '.';
3
3
+
import type { Editor } from '@tiptap/core';
4
4
+
import { textAlignClasses, textSizeClasses, verticalAlignClasses } from '.';
4
5
import type { ContentComponentProps } from '../types';
5
6
import MarkdownTextEditor from '../utils/MarkdownTextEditor.svelte';
7
7
+
import { cn } from '@foxui/core';
6
8
7
9
let { item = $bindable<Item>() }: ContentComponentProps = $props();
10
10
+
11
11
+
let editor: Editor | null = $state(null);
12
12
+
13
13
+
$inspect(textSizeClasses[item.cardData.textSize as number]);
8
14
</script>
9
15
16
16
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
17
17
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
10
18
<div
11
11
-
class={[
12
12
-
'prose dark:prose-invert prose-sm prose-a:no-underline prose-a:text-accent-600 dark:prose-a:text-accent-400 accent:prose-a:text-accent-950 accent:prose-a:underline accent:prose-p:text-base-900 hover:bg-base-500/20 prose-p:first:mt-0 prose-p:last:mb-0 h-full overflow-y-scroll rounded-md p-2 inline-flex w-full max-w-none',
19
19
+
class={cn(
20
20
+
'prose dark:prose-invert prose-neutral prose-sm prose-a:no-underline prose-a:text-accent-600 dark:prose-a:text-accent-400 accent:prose-a:text-accent-950 accent:prose-a:underline accent:prose-p:text-base-900 hover:bg-base-700/5 accent:hover:bg-accent-300/20 prose-p:first:mt-0 prose-p:last:mb-0 inline-flex h-full w-full text-lg max-w-none overflow-y-scroll rounded-md p-2 transition-colors duration-150 cursor-text',
13
21
textAlignClasses[item.cardData.textAlign as string],
14
14
-
verticalAlignClasses[item.cardData.verticalAlign as string]
15
15
-
]}
22
22
+
verticalAlignClasses[item.cardData.verticalAlign as string],
23
23
+
textSizeClasses[(item.cardData.textSize ?? 0) as number]
24
24
+
)}
25
25
+
onclick={() => {
26
26
+
editor?.commands.focus('end');
27
27
+
}}
16
28
>
17
17
-
<MarkdownTextEditor bind:item />
29
29
+
<MarkdownTextEditor bind:item bind:editor />
18
30
</div>
+4
-3
src/lib/cards/TextCard/TextCard.svelte
···
1
1
<script lang="ts">
2
2
import { marked } from 'marked';
3
3
import type { ContentComponentProps } from '../types';
4
4
-
import { textAlignClasses, verticalAlignClasses } from '.';
4
4
+
import { textAlignClasses, textSizeClasses, verticalAlignClasses } from '.';
5
5
6
6
let { item }: ContentComponentProps = $props();
7
7
···
12
12
13
13
<div
14
14
class={[
15
15
-
'prose dark:prose-invert prose-sm prose-a:no-underline prose-a:text-accent-600 dark:prose-a:text-accent-400 accent:prose-a:text-accent-950 accent:prose-a:underline accent:prose-p:text-base-900 prose-p:first:mt-0 prose-p:last:mb-0 inline-flex h-full w-full overflow-y-scroll rounded-md p-3 max-w-none',
15
15
+
'prose dark:prose-invert prose-neutral prose-sm prose-a:no-underline prose-a:text-accent-600 dark:prose-a:text-accent-400 accent:prose-a:text-accent-950 accent:prose-a:underline accent:prose-p:text-base-900 prose-p:first:mt-0 prose-p:last:mb-0 prose-headings:first:mt-0 prose-headings:last:mb-0 inline-flex h-full min-h-full w-full max-w-none overflow-y-scroll rounded-md p-3 text-lg',
16
16
textAlignClasses?.[item.cardData.textAlign as string],
17
17
-
verticalAlignClasses[item.cardData.verticalAlign as string]
17
17
+
verticalAlignClasses[item.cardData.verticalAlign as string],
18
18
+
textSizeClasses[(item.cardData.textSize ?? 0) as number]
18
19
]}
19
20
>
20
21
<span>{@html marked.parse(item.cardData.text ?? '', { renderer })}</span>
+50
-1
src/lib/cards/TextCard/TextCardSettings.svelte
···
1
1
<script lang="ts">
2
2
import type { Item } from '$lib/types';
3
3
import type { ContentComponentProps } from '../types';
4
4
-
import { ToggleGroup, ToggleGroupItem } from '@foxui/core';
4
4
+
import { ToggleGroup, ToggleGroupItem, Button } from '@foxui/core';
5
5
6
6
let { item = $bindable<Item>() }: ContentComponentProps = $props();
7
7
···
119
119
></ToggleGroupItem
120
120
>
121
121
</ToggleGroup>
122
122
+
123
123
+
<div>
124
124
+
<Button
125
125
+
variant="ghost"
126
126
+
onclick={() => {
127
127
+
item.cardData.textSize = Math.max((item.cardData.textSize ?? 0) - 1, 0);
128
128
+
}}
129
129
+
disabled={(item.cardData.textSize ?? 0) < 1}
130
130
+
>
131
131
+
<svg
132
132
+
xmlns="http://www.w3.org/2000/svg"
133
133
+
width="24"
134
134
+
height="24"
135
135
+
viewBox="0 0 24 24"
136
136
+
fill="none"
137
137
+
stroke="currentColor"
138
138
+
stroke-width="2"
139
139
+
stroke-linecap="round"
140
140
+
stroke-linejoin="round"
141
141
+
class="lucide lucide-aarrow-down-icon lucide-a-arrow-down"
142
142
+
><path d="m14 12 4 4 4-4" /><path d="M18 16V7" /><path
143
143
+
d="m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16"
144
144
+
/><path d="M3.304 13h6.392" /></svg
145
145
+
>
146
146
+
</Button>
147
147
+
<Button
148
148
+
variant="ghost"
149
149
+
onclick={() => {
150
150
+
item.cardData.textSize = Math.min((item.cardData.textSize ?? 0) + 1, 5);
151
151
+
}}
152
152
+
disabled={(item.cardData.textSize ?? 0) > 4}
153
153
+
>
154
154
+
<svg
155
155
+
xmlns="http://www.w3.org/2000/svg"
156
156
+
width="24"
157
157
+
height="24"
158
158
+
viewBox="0 0 24 24"
159
159
+
fill="none"
160
160
+
stroke="currentColor"
161
161
+
stroke-width="2"
162
162
+
stroke-linecap="round"
163
163
+
stroke-linejoin="round"
164
164
+
class="lucide lucide-aarrow-up-icon lucide-a-arrow-up"
165
165
+
><path d="m14 11 4-4 4 4" /><path d="M18 16V7" /><path
166
166
+
d="m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16"
167
167
+
/><path d="M3.304 13h6.392" /></svg
168
168
+
>
169
169
+
</Button>
170
170
+
</div>
122
171
</div>
+10
-1
src/lib/cards/TextCard/index.ts
···
24
24
};
25
25
26
26
export const verticalAlignClasses: Record<string, string> = {
27
27
-
top: 'items-start',
27
27
+
top: 'items-stretch',
28
28
center: 'items-center-safe',
29
29
bottom: 'items-end-safe'
30
30
};
31
31
32
32
+
export const textSizeClasses = [
33
33
+
'text-lg',
34
34
+
'text-xl',
35
35
+
'text-2xl',
36
36
+
'text-3xl',
37
37
+
'text-4xl',
38
38
+
'text-5xl'
39
39
+
];
40
40
+
+2
src/lib/cards/types.ts
···
60
60
maxH?: number;
61
61
62
62
canResize?: boolean;
63
63
+
64
64
+
onUrlHandler?: (url: string, item: Item) => Item | null;
63
65
};
+17
-6
src/lib/cards/utils/MarkdownTextEditor.svelte
···
10
10
import TurndownService from 'turndown';
11
11
import { RichTextLink } from './extensions/RichTextLink';
12
12
import type { Item } from '$lib/types';
13
13
+
import { textAlignClasses, verticalAlignClasses } from '../TextCard';
13
14
14
15
let element: HTMLElement | undefined = $state();
15
15
-
let editor: Editor | null = $state(null);
16
16
17
17
let loaded = $state(false);
18
18
19
19
let {
20
20
+
editor = $bindable(),
20
21
item = $bindable(),
21
22
placeholder = '',
22
23
defaultContent = ''
23
24
}: {
25
25
+
editor: Editor | null;
24
26
item: Item;
25
27
placeholder?: string;
26
28
defaultContent?: string;
···
50
52
51
53
// parse to json
52
54
json = generateJSON(html, [
53
53
-
StarterKit.configure(),
55
55
+
StarterKit.configure({
56
56
+
heading: false,
57
57
+
bulletList: false,
58
58
+
codeBlock: false
59
59
+
}),
54
60
Image.configure(),
55
61
RichTextLink.configure({
56
62
openOnClick: false
···
61
67
}
62
68
63
69
let extensions: Extensions = [
64
64
-
StarterKit.configure(),
70
70
+
StarterKit.configure({
71
71
+
heading: false,
72
72
+
bulletList: false,
73
73
+
codeBlock: false,
74
74
+
dropcursor: false
75
75
+
}),
65
76
Image.configure(),
66
77
Link.configure({
67
78
openOnClick: false
···
92
103
93
104
editorProps: {
94
105
attributes: {
95
95
-
class: 'outline-none'
106
106
+
class: 'outline-none w-full'
96
107
},
97
97
-
handleDOMEvents: { drop: () => true }
108
108
+
handleDOMEvents: { drop: () => false }
98
109
}
99
110
});
100
111
···
108
119
});
109
120
</script>
110
121
111
111
-
<div bind:this={element}></div>
122
122
+
<div class="w-full" bind:this={element}></div>
112
123
113
124
<style>
114
125
:global(.tiptap p.is-editor-empty:first-child::before) {
+66
src/lib/components/ImageDropper.svelte
···
1
1
+
<script lang="ts">
2
2
+
import { Portal } from 'bits-ui';
3
3
+
4
4
+
let isDragOver = $state(false);
5
5
+
6
6
+
let {
7
7
+
processImageFile
8
8
+
}: {
9
9
+
processImageFile: (file: File) => Promise<void>;
10
10
+
} = $props();
11
11
+
12
12
+
function handleDragOver(event: DragEvent) {
13
13
+
event.preventDefault();
14
14
+
event.stopPropagation();
15
15
+
16
16
+
const dt = event.dataTransfer;
17
17
+
if (!dt) return;
18
18
+
19
19
+
let imageCount = 0;
20
20
+
if (dt.items) {
21
21
+
for (let i = 0; i < dt.items.length; i++) {
22
22
+
const item = dt.items[i];
23
23
+
if (item && item.kind === 'file' && item.type.startsWith('image/')) {
24
24
+
imageCount++;
25
25
+
}
26
26
+
}
27
27
+
} else if (dt.files) {
28
28
+
for (let i = 0; i < dt.files.length; i++) {
29
29
+
const file = dt.files[i];
30
30
+
if (file?.type.startsWith('image/')) {
31
31
+
imageCount++;
32
32
+
}
33
33
+
}
34
34
+
}
35
35
+
36
36
+
isDragOver = imageCount > 0;
37
37
+
}
38
38
+
function handleDragLeave(event: DragEvent) {
39
39
+
event.preventDefault();
40
40
+
event.stopPropagation();
41
41
+
isDragOver = false;
42
42
+
}
43
43
+
async function handleDrop(event: DragEvent) {
44
44
+
event.preventDefault();
45
45
+
event.stopPropagation();
46
46
+
isDragOver = false;
47
47
+
if (!event.dataTransfer?.files?.length) return;
48
48
+
for (const file of event.dataTransfer.files) {
49
49
+
if (file?.type.startsWith('image/')) {
50
50
+
await processImageFile(file);
51
51
+
}
52
52
+
}
53
53
+
}
54
54
+
</script>
55
55
+
56
56
+
<svelte:window ondragover={handleDragOver} ondragleave={handleDragLeave} ondrop={handleDrop} />
57
57
+
58
58
+
{#if isDragOver}
59
59
+
<Portal>
60
60
+
<div
61
61
+
class="bg-base-100/80 dark:bg-base-900/80 text-primary dark:text-base-100 pointer-events-none absolute inset-0 z-[1000] flex items-center justify-center text-4xl font-bold backdrop-blur-md"
62
62
+
>
63
63
+
Drop file to add it to your message
64
64
+
</div>
65
65
+
</Portal>
66
66
+
{/if}