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
3
import type { HTMLAttributes } from 'svelte/elements';
4
4
import BaseCard from './BaseCard.svelte';
5
5
import type { Item } from '$lib/types';
6
6
-
import { Button, Popover } from '@foxui/core';
6
6
+
import { Button, Label, Popover } from '@foxui/core';
7
7
import { ColorSelect } from '@foxui/colors';
8
8
-
import { CardDefinitionsByType, getColor } from '..';
8
8
+
import { AllCardDefinitions, CardDefinitionsByType, getColor } from '..';
9
9
import { COLUMNS } from '$lib';
10
10
import { getCanEdit, getIsMobile } from '$lib/website/context';
11
11
···
151
151
}
152
152
153
153
let settingsPopoverOpen = $state(false);
154
154
+
let changePopoverOpen = $state(false);
155
155
+
156
156
+
const changeOptions = $derived(
157
157
+
AllCardDefinitions.filter((def) => def.type !== item.cardType && def.canChange?.(item))
158
158
+
);
159
159
+
160
160
+
function applyChange(def: (typeof AllCardDefinitions)[number]) {
161
161
+
const updated = def.change ? def.change(item) : item;
162
162
+
if (updated !== item) {
163
163
+
item = updated;
164
164
+
}
165
165
+
item.cardType = def.type;
166
166
+
changePopoverOpen = false;
167
167
+
}
168
168
+
169
169
+
function getChangeLabel(def: (typeof AllCardDefinitions)[number]) {
170
170
+
return def.name;
171
171
+
}
154
172
</script>
155
173
156
174
<BaseCard
···
166
184
{#snippet controls()}
167
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" -->
168
186
{#if canEdit()}
187
187
+
{#if changeOptions.length > 0}
188
188
+
<div
189
189
+
class={[
190
190
+
'absolute -top-3 -right-3 hidden group-focus-within:inline-flex group-hover:inline-flex',
191
191
+
changePopoverOpen ? 'inline-flex' : ''
192
192
+
]}
193
193
+
>
194
194
+
<Popover bind:open={changePopoverOpen} class="bg-base-50 dark:bg-base-900">
195
195
+
{#snippet child({ props })}
196
196
+
<Button size="icon" variant="secondary" {...props}>
197
197
+
<svg
198
198
+
xmlns="http://www.w3.org/2000/svg"
199
199
+
fill="none"
200
200
+
viewBox="0 0 24 24"
201
201
+
stroke-width="1.5"
202
202
+
stroke="currentColor"
203
203
+
class="size-6"
204
204
+
>
205
205
+
<path
206
206
+
stroke-linecap="round"
207
207
+
stroke-linejoin="round"
208
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
209
+
/>
210
210
+
</svg>
211
211
+
212
212
+
<span class="sr-only">Change card type</span>
213
213
+
</Button>
214
214
+
{/snippet}
215
215
+
216
216
+
<div class="flex min-w-36 flex-col gap-1">
217
217
+
<Label class="mb-2">Change card to</Label>
218
218
+
{#each changeOptions as changeDef}
219
219
+
<Button
220
220
+
class="justify-start"
221
221
+
variant="ghost"
222
222
+
onclick={() => applyChange(changeDef)}
223
223
+
>
224
224
+
{getChangeLabel(changeDef)}
225
225
+
</Button>
226
226
+
{/each}
227
227
+
</div>
228
228
+
</Popover>
229
229
+
</div>
230
230
+
{/if}
231
231
+
169
232
<Button
170
233
size="icon"
171
234
variant="rose"
···
200
263
<div
201
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"
202
265
>
203
203
-
<Popover bind:open={colorPopoverOpen}>
204
204
-
{#snippet child({ props })}
205
205
-
<button
206
206
-
{...props}
207
207
-
class={[
208
208
-
'm-2 size-4 cursor-pointer rounded-full',
209
209
-
!item.color || item.color === 'base' || item.color === 'transparent'
210
210
-
? 'text-base-800 dark:text-base-200'
211
211
-
: 'text-accent-500'
212
212
-
]}
213
213
-
>
214
214
-
<svg
215
215
-
xmlns="http://www.w3.org/2000/svg"
216
216
-
viewBox="0 0 24 24"
217
217
-
fill="currentColor"
218
218
-
class="size-4"
266
266
+
{#if cardDef.allowSetColor !== false}
267
267
+
<Popover bind:open={colorPopoverOpen}>
268
268
+
{#snippet child({ props })}
269
269
+
<button
270
270
+
{...props}
271
271
+
class={[
272
272
+
'm-2 size-4 cursor-pointer rounded-full',
273
273
+
!item.color || item.color === 'base' || item.color === 'transparent'
274
274
+
? 'text-base-800 dark:text-base-200'
275
275
+
: 'text-accent-500'
276
276
+
]}
219
277
>
220
220
-
<path
221
221
-
fill-rule="evenodd"
222
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
223
-
clip-rule="evenodd"
224
224
-
/>
225
225
-
</svg>
226
226
-
</button>
227
227
-
{/snippet}
228
228
-
<ColorSelect
229
229
-
selected={selectedColor}
230
230
-
colors={colorsChoices}
231
231
-
onselected={(color, previous) => {
232
232
-
if (typeof previous === 'string' || typeof color === 'string') {
233
233
-
return;
234
234
-
}
278
278
+
<svg
279
279
+
xmlns="http://www.w3.org/2000/svg"
280
280
+
viewBox="0 0 24 24"
281
281
+
fill="currentColor"
282
282
+
class="size-4"
283
283
+
>
284
284
+
<path
285
285
+
fill-rule="evenodd"
286
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
287
+
clip-rule="evenodd"
288
288
+
/>
289
289
+
</svg>
290
290
+
</button>
291
291
+
{/snippet}
292
292
+
<ColorSelect
293
293
+
selected={selectedColor}
294
294
+
colors={colorsChoices}
295
295
+
onselected={(color, previous) => {
296
296
+
if (typeof previous === 'string' || typeof color === 'string') {
297
297
+
return;
298
298
+
}
235
299
236
236
-
item.color = color.label;
237
237
-
}}
238
238
-
class="w-64"
239
239
-
/>
240
240
-
</Popover>
300
300
+
item.color = color.label;
301
301
+
}}
302
302
+
class="w-64"
303
303
+
/>
304
304
+
</Popover>
305
305
+
{/if}
241
306
242
307
{#if canSetSize(2, 2)}
243
308
<button
+17
src/lib/cards/BigSocialCard/index.ts
···
19
19
card.mobileW = 4;
20
20
card.mobileH = 4;
21
21
},
22
22
+
canChange: (item) => {
23
23
+
const href = item.cardData?.href;
24
24
+
if (!href) return false;
25
25
+
return Boolean(detectPlatform(href));
26
26
+
},
27
27
+
change: (item) => {
28
28
+
const href = item.cardData?.href;
29
29
+
const platform = href ? detectPlatform(href) : null;
30
30
+
if (!href || !platform) return item;
31
31
+
item.cardData = {
32
32
+
href,
33
33
+
platform,
34
34
+
color: platformsData[platform].hex
35
35
+
};
36
36
+
return item;
37
37
+
},
38
38
+
name: 'Social Icon',
22
39
allowSetColor: false,
23
40
defaultColor: 'transparent',
24
41
minW: 2,
+14
-1
src/lib/cards/LinkCard/index.ts
···
1
1
+
import { validateLink } from '$lib/helper';
1
2
import type { CardDefinition } from '../types';
2
3
import EditingLinkCard from './EditingLinkCard.svelte';
3
4
import LinkCard from './LinkCard.svelte';
···
11
12
card.cardType = 'link';
12
13
},
13
14
settingsComponent: LinkCardSettings,
15
15
+
16
16
+
name: 'Link Card',
17
17
+
canChange: (item) => Boolean(validateLink(item.cardData?.href)),
18
18
+
change: (item) => {
19
19
+
const href = validateLink(item.cardData?.href);
20
20
+
if (!href) return item;
21
21
+
22
22
+
item.cardData = {
23
23
+
href,
24
24
+
hasFetched: false
25
25
+
};
26
26
+
return item;
27
27
+
},
14
28
onUrlHandler: (url, item) => {
15
29
item.cardData.href = url;
16
30
item.cardData.domain = new URL(url).hostname;
17
17
-
item.cardData.hasFetched = false;
18
31
return item;
19
32
},
20
33
urlHandlerPriority: 0
+6
-1
src/lib/cards/types.ts
···
62
62
63
63
onUrlHandler?: (url: string, item: Item) => Item | null;
64
64
urlHandlerPriority?: number;
65
65
-
};
65
65
+
66
66
+
canChange?: (item: Item) => boolean;
67
67
+
change?: (item: Item) => Item;
68
68
+
69
69
+
name?: string;
70
70
+
};