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
allow changing cards
Florian
3 weeks ago
10068f47
604ac7af
+140
-40
4 changed files
expand all
collapse all
unified
split
src
lib
cards
BaseCard
BaseEditingCard.svelte
BigSocialCard
index.ts
LinkCard
index.ts
types.ts
+103
-38
src/lib/cards/BaseCard/BaseEditingCard.svelte
···
3
import type { HTMLAttributes } from 'svelte/elements';
4
import BaseCard from './BaseCard.svelte';
5
import type { Item } from '$lib/types';
6
-
import { Button, Popover } from '@foxui/core';
7
import { ColorSelect } from '@foxui/colors';
8
-
import { CardDefinitionsByType, getColor } from '..';
9
import { COLUMNS } from '$lib';
10
import { getCanEdit, getIsMobile } from '$lib/website/context';
11
···
151
}
152
153
let settingsPopoverOpen = $state(false);
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
154
</script>
155
156
<BaseCard
···
166
{#snippet controls()}
167
<!-- class="bg-base-100 border-base-200 dark:bg-base-800 dark:border-base-700 absolute -top-3 -left-3 hidden cursor-pointer items-center justify-center rounded-full border p-2 shadow-lg group-focus-within:inline-flex group-hover:inline-flex" -->
168
{#if canEdit()}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
169
<Button
170
size="icon"
171
variant="rose"
···
200
<div
201
class="bg-base-100 border-base-200 dark:bg-base-800 dark:border-base-700 z-[100] inline-flex items-center gap-0.5 rounded-2xl border p-1 px-2 shadow-lg"
202
>
203
-
<Popover bind:open={colorPopoverOpen}>
204
-
{#snippet child({ props })}
205
-
<button
206
-
{...props}
207
-
class={[
208
-
'm-2 size-4 cursor-pointer rounded-full',
209
-
!item.color || item.color === 'base' || item.color === 'transparent'
210
-
? 'text-base-800 dark:text-base-200'
211
-
: 'text-accent-500'
212
-
]}
213
-
>
214
-
<svg
215
-
xmlns="http://www.w3.org/2000/svg"
216
-
viewBox="0 0 24 24"
217
-
fill="currentColor"
218
-
class="size-4"
219
>
220
-
<path
221
-
fill-rule="evenodd"
222
-
d="M20.599 1.5c-.376 0-.743.111-1.055.32l-5.08 3.385a18.747 18.747 0 0 0-3.471 2.987 10.04 10.04 0 0 1 4.815 4.815 18.748 18.748 0 0 0 2.987-3.472l3.386-5.079A1.902 1.902 0 0 0 20.599 1.5Zm-8.3 14.025a18.76 18.76 0 0 0 1.896-1.207 8.026 8.026 0 0 0-4.513-4.513A18.75 18.75 0 0 0 8.475 11.7l-.278.5a5.26 5.26 0 0 1 3.601 3.602l.502-.278ZM6.75 13.5A3.75 3.75 0 0 0 3 17.25a1.5 1.5 0 0 1-1.601 1.497.75.75 0 0 0-.7 1.123 5.25 5.25 0 0 0 9.8-2.62 3.75 3.75 0 0 0-3.75-3.75Z"
223
-
clip-rule="evenodd"
224
-
/>
225
-
</svg>
226
-
</button>
227
-
{/snippet}
228
-
<ColorSelect
229
-
selected={selectedColor}
230
-
colors={colorsChoices}
231
-
onselected={(color, previous) => {
232
-
if (typeof previous === 'string' || typeof color === 'string') {
233
-
return;
234
-
}
0
0
0
0
0
0
235
236
-
item.color = color.label;
237
-
}}
238
-
class="w-64"
239
-
/>
240
-
</Popover>
0
241
242
{#if canSetSize(2, 2)}
243
<button
···
3
import type { HTMLAttributes } from 'svelte/elements';
4
import BaseCard from './BaseCard.svelte';
5
import type { Item } from '$lib/types';
6
+
import { Button, Label, Popover } from '@foxui/core';
7
import { ColorSelect } from '@foxui/colors';
8
+
import { AllCardDefinitions, CardDefinitionsByType, getColor } from '..';
9
import { COLUMNS } from '$lib';
10
import { getCanEdit, getIsMobile } from '$lib/website/context';
11
···
151
}
152
153
let settingsPopoverOpen = $state(false);
154
+
let changePopoverOpen = $state(false);
155
+
156
+
const changeOptions = $derived(
157
+
AllCardDefinitions.filter((def) => def.type !== item.cardType && def.canChange?.(item))
158
+
);
159
+
160
+
function applyChange(def: (typeof AllCardDefinitions)[number]) {
161
+
const updated = def.change ? def.change(item) : item;
162
+
if (updated !== item) {
163
+
item = updated;
164
+
}
165
+
item.cardType = def.type;
166
+
changePopoverOpen = false;
167
+
}
168
+
169
+
function getChangeLabel(def: (typeof AllCardDefinitions)[number]) {
170
+
return def.name;
171
+
}
172
</script>
173
174
<BaseCard
···
184
{#snippet controls()}
185
<!-- class="bg-base-100 border-base-200 dark:bg-base-800 dark:border-base-700 absolute -top-3 -left-3 hidden cursor-pointer items-center justify-center rounded-full border p-2 shadow-lg group-focus-within:inline-flex group-hover:inline-flex" -->
186
{#if canEdit()}
187
+
{#if changeOptions.length > 0}
188
+
<div
189
+
class={[
190
+
'absolute -top-3 -right-3 hidden group-focus-within:inline-flex group-hover:inline-flex',
191
+
changePopoverOpen ? 'inline-flex' : ''
192
+
]}
193
+
>
194
+
<Popover bind:open={changePopoverOpen} class="bg-base-50 dark:bg-base-900">
195
+
{#snippet child({ props })}
196
+
<Button size="icon" variant="secondary" {...props}>
197
+
<svg
198
+
xmlns="http://www.w3.org/2000/svg"
199
+
fill="none"
200
+
viewBox="0 0 24 24"
201
+
stroke-width="1.5"
202
+
stroke="currentColor"
203
+
class="size-6"
204
+
>
205
+
<path
206
+
stroke-linecap="round"
207
+
stroke-linejoin="round"
208
+
d="M7.5 21 3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5"
209
+
/>
210
+
</svg>
211
+
212
+
<span class="sr-only">Change card type</span>
213
+
</Button>
214
+
{/snippet}
215
+
216
+
<div class="flex min-w-36 flex-col gap-1">
217
+
<Label class="mb-2">Change card to</Label>
218
+
{#each changeOptions as changeDef}
219
+
<Button
220
+
class="justify-start"
221
+
variant="ghost"
222
+
onclick={() => applyChange(changeDef)}
223
+
>
224
+
{getChangeLabel(changeDef)}
225
+
</Button>
226
+
{/each}
227
+
</div>
228
+
</Popover>
229
+
</div>
230
+
{/if}
231
+
232
<Button
233
size="icon"
234
variant="rose"
···
263
<div
264
class="bg-base-100 border-base-200 dark:bg-base-800 dark:border-base-700 z-[100] inline-flex items-center gap-0.5 rounded-2xl border p-1 px-2 shadow-lg"
265
>
266
+
{#if cardDef.allowSetColor !== false}
267
+
<Popover bind:open={colorPopoverOpen}>
268
+
{#snippet child({ props })}
269
+
<button
270
+
{...props}
271
+
class={[
272
+
'm-2 size-4 cursor-pointer rounded-full',
273
+
!item.color || item.color === 'base' || item.color === 'transparent'
274
+
? 'text-base-800 dark:text-base-200'
275
+
: 'text-accent-500'
276
+
]}
0
0
0
0
0
277
>
278
+
<svg
279
+
xmlns="http://www.w3.org/2000/svg"
280
+
viewBox="0 0 24 24"
281
+
fill="currentColor"
282
+
class="size-4"
283
+
>
284
+
<path
285
+
fill-rule="evenodd"
286
+
d="M20.599 1.5c-.376 0-.743.111-1.055.32l-5.08 3.385a18.747 18.747 0 0 0-3.471 2.987 10.04 10.04 0 0 1 4.815 4.815 18.748 18.748 0 0 0 2.987-3.472l3.386-5.079A1.902 1.902 0 0 0 20.599 1.5Zm-8.3 14.025a18.76 18.76 0 0 0 1.896-1.207 8.026 8.026 0 0 0-4.513-4.513A18.75 18.75 0 0 0 8.475 11.7l-.278.5a5.26 5.26 0 0 1 3.601 3.602l.502-.278ZM6.75 13.5A3.75 3.75 0 0 0 3 17.25a1.5 1.5 0 0 1-1.601 1.497.75.75 0 0 0-.7 1.123 5.25 5.25 0 0 0 9.8-2.62 3.75 3.75 0 0 0-3.75-3.75Z"
287
+
clip-rule="evenodd"
288
+
/>
289
+
</svg>
290
+
</button>
291
+
{/snippet}
292
+
<ColorSelect
293
+
selected={selectedColor}
294
+
colors={colorsChoices}
295
+
onselected={(color, previous) => {
296
+
if (typeof previous === 'string' || typeof color === 'string') {
297
+
return;
298
+
}
299
300
+
item.color = color.label;
301
+
}}
302
+
class="w-64"
303
+
/>
304
+
</Popover>
305
+
{/if}
306
307
{#if canSetSize(2, 2)}
308
<button
+17
src/lib/cards/BigSocialCard/index.ts
···
19
card.mobileW = 4;
20
card.mobileH = 4;
21
},
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
22
allowSetColor: false,
23
defaultColor: 'transparent',
24
minW: 2,
···
19
card.mobileW = 4;
20
card.mobileH = 4;
21
},
22
+
canChange: (item) => {
23
+
const href = item.cardData?.href;
24
+
if (!href) return false;
25
+
return Boolean(detectPlatform(href));
26
+
},
27
+
change: (item) => {
28
+
const href = item.cardData?.href;
29
+
const platform = href ? detectPlatform(href) : null;
30
+
if (!href || !platform) return item;
31
+
item.cardData = {
32
+
href,
33
+
platform,
34
+
color: platformsData[platform].hex
35
+
};
36
+
return item;
37
+
},
38
+
name: 'Social Icon',
39
allowSetColor: false,
40
defaultColor: 'transparent',
41
minW: 2,
+14
-1
src/lib/cards/LinkCard/index.ts
···
0
1
import type { CardDefinition } from '../types';
2
import EditingLinkCard from './EditingLinkCard.svelte';
3
import LinkCard from './LinkCard.svelte';
···
11
card.cardType = 'link';
12
},
13
settingsComponent: LinkCardSettings,
0
0
0
0
0
0
0
0
0
0
0
0
0
14
onUrlHandler: (url, item) => {
15
item.cardData.href = url;
16
item.cardData.domain = new URL(url).hostname;
17
-
item.cardData.hasFetched = false;
18
return item;
19
},
20
urlHandlerPriority: 0
···
1
+
import { validateLink } from '$lib/helper';
2
import type { CardDefinition } from '../types';
3
import EditingLinkCard from './EditingLinkCard.svelte';
4
import LinkCard from './LinkCard.svelte';
···
12
card.cardType = 'link';
13
},
14
settingsComponent: LinkCardSettings,
15
+
16
+
name: 'Link Card',
17
+
canChange: (item) => Boolean(validateLink(item.cardData?.href)),
18
+
change: (item) => {
19
+
const href = validateLink(item.cardData?.href);
20
+
if (!href) return item;
21
+
22
+
item.cardData = {
23
+
href,
24
+
hasFetched: false
25
+
};
26
+
return item;
27
+
},
28
onUrlHandler: (url, item) => {
29
item.cardData.href = url;
30
item.cardData.domain = new URL(url).hostname;
0
31
return item;
32
},
33
urlHandlerPriority: 0
+6
-1
src/lib/cards/types.ts
···
62
63
onUrlHandler?: (url: string, item: Item) => Item | null;
64
urlHandlerPriority?: number;
65
-
};
0
0
0
0
0
···
62
63
onUrlHandler?: (url: string, item: Item) => Item | null;
64
urlHandlerPriority?: number;
65
+
66
+
canChange?: (item: Item) => boolean;
67
+
change?: (item: Item) => Item;
68
+
69
+
name?: string;
70
+
};