diff --git a/docs/Beta.md b/docs/Beta.md deleted file mode 100644 index 6dc5a68..0000000 --- a/docs/Beta.md +++ /dev/null @@ -1,42 +0,0 @@ -# Todo for beta version - -- site.standard - - move description to markdownDescription and set description as text only - -- allow editing on mobile - -- get automatic layout for mobile if only edited on desktop (and vice versa) - -- add cards in middle of current position (both mobile and desktop version) - -- show nsfw warnings - -- card with big call to action button "create your blento" - -- ask to fill with some default cards on page creation - -- when adding images try to add them in a size that best fits aspect ratio - -- onboarding? - -- switch sidebar to a quick list of available cards with search function - -- test - - selfhosting - -- guestbook card - -- onboarding? - -- switch sidebar to a quick list of available cards with search function - -- test - - selfhosting - -- guestbook card - -- analytics? - -- refresh recently updated blentos (move to top of list, update profiles every 24 hours) - -- server side oauth? diff --git a/package.json b/package.json index 70fde20..b7e00bf 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "svelte-sonner": "^1.0.7", "tailwind-merge": "^3.4.0", "tailwind-variants": "^3.2.2", + "tailwindcss-animate": "^1.0.7", "three": "^0.176.0", "turndown": "^7.2.2", "wrangler": "^4.60.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6434d0a..fefeeb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -146,6 +146,9 @@ importers: tailwind-variants: specifier: ^3.2.2 version: 3.2.2(tailwind-merge@3.4.0)(tailwindcss@4.1.18) + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@4.1.18) three: specifier: ^0.176.0 version: 0.176.0 @@ -2800,6 +2803,11 @@ packages: tailwind-merge: optional: true + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==, tarball: https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + tailwindcss@4.1.18: resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} @@ -5561,6 +5569,10 @@ snapshots: optionalDependencies: tailwind-merge: 3.4.0 + tailwindcss-animate@1.0.7(tailwindcss@4.1.18): + dependencies: + tailwindcss: 4.1.18 + tailwindcss@4.1.18: {} tapable@2.3.0: {} diff --git a/src/app.css b/src/app.css index 3914088..a8f1c18 100644 --- a/src/app.css +++ b/src/app.css @@ -3,6 +3,10 @@ @plugin '@tailwindcss/forms'; @plugin '@tailwindcss/typography'; + +@plugin "tailwindcss-animate"; + + @source '../node_modules/@foxui'; @custom-variant dark (&:where(.dark, .dark *):not(:where(.light, .light *))); diff --git a/src/lib/cards/ATProtoCollectionsCard/index.ts b/src/lib/cards/ATProtoCollectionsCard/index.ts index 95a06a8..3d50466 100644 --- a/src/lib/cards/ATProtoCollectionsCard/index.ts +++ b/src/lib/cards/ATProtoCollectionsCard/index.ts @@ -19,5 +19,9 @@ export const ATProtoCollectionsCardDefinition = { item.w = 4; item.mobileW = 8; }, - sidebarButtonText: 'Atmosphere Collections' + sidebarButtonText: 'Atmosphere Collections', + + name: 'ATProto Collections', + + groups: ['Social'] } as CardDefinition & { type: 'atprotocollections' }; diff --git a/src/lib/cards/BigSocialCard/index.ts b/src/lib/cards/BigSocialCard/index.ts index d0657bf..d46dbbc 100644 --- a/src/lib/cards/BigSocialCard/index.ts +++ b/src/lib/cards/BigSocialCard/index.ts @@ -51,7 +51,9 @@ export const BigSocialCardDefinition = { return item; }, urlHandlerPriority: 1, - canHaveLabel: true + canHaveLabel: true, + + groups: ['Social'] } as CardDefinition & { type: 'bigsocial' }; import { diff --git a/src/lib/cards/BlueskyMediaCard/index.ts b/src/lib/cards/BlueskyMediaCard/index.ts index b3411a2..722ab57 100644 --- a/src/lib/cards/BlueskyMediaCard/index.ts +++ b/src/lib/cards/BlueskyMediaCard/index.ts @@ -8,5 +8,9 @@ export const BlueskyMediaCardDefinition = { createNew: () => {}, creationModalComponent: CreateBlueskyMediaCardModal, sidebarButtonText: 'Bluesky Media', - canHaveLabel: true + canHaveLabel: true, + + groups: ['Media'], + + name: 'Video/Image from Bluesky' } as CardDefinition & { type: 'blueskyMedia' }; diff --git a/src/lib/cards/BlueskyPostCard/index.ts b/src/lib/cards/BlueskyPostCard/index.ts index aae6e61..5194efb 100644 --- a/src/lib/cards/BlueskyPostCard/index.ts +++ b/src/lib/cards/BlueskyPostCard/index.ts @@ -63,5 +63,7 @@ export const BlueskyPostCardDefinition = { return postsMap; }, minW: 4, - name: 'Bluesky Post' + name: 'Bluesky Post', + + groups: ['Social'] } as CardDefinition & { type: 'blueskyPost' }; diff --git a/src/lib/cards/ButtonCard/index.ts b/src/lib/cards/ButtonCard/index.ts index e5c0080..48317ae 100644 --- a/src/lib/cards/ButtonCard/index.ts +++ b/src/lib/cards/ButtonCard/index.ts @@ -27,5 +27,8 @@ export const ButtonCardDefinition: CardDefinition = { minW: 2, minH: 1, maxW: 8, - maxH: 4 + maxH: 4, + + groups: ['Utilities'], + name: 'Button' }; diff --git a/src/lib/cards/DrawCard/index.ts b/src/lib/cards/DrawCard/index.ts index 29d99dd..0b36c69 100644 --- a/src/lib/cards/DrawCard/index.ts +++ b/src/lib/cards/DrawCard/index.ts @@ -23,5 +23,7 @@ export const DrawCardDefinition = { strokeWidth: 1, locked: true }; - } + }, + + groups: ['Visual'] } as CardDefinition & { type: 'draw' }; diff --git a/src/lib/cards/EmbedCard/index.ts b/src/lib/cards/EmbedCard/index.ts index 4bc8c95..52cd098 100644 --- a/src/lib/cards/EmbedCard/index.ts +++ b/src/lib/cards/EmbedCard/index.ts @@ -19,5 +19,5 @@ export const EmbedCardDefinition = { // change: (item) => { // return item; // }, - name: 'Embed Card' + name: 'Embed' } as CardDefinition & { type: 'embed' }; diff --git a/src/lib/cards/EventCard/index.ts b/src/lib/cards/EventCard/index.ts index 5d03aca..01a219d 100644 --- a/src/lib/cards/EventCard/index.ts +++ b/src/lib/cards/EventCard/index.ts @@ -112,5 +112,7 @@ export const EventCardDefinition = { urlHandlerPriority: 5, - name: 'Event Card' + name: 'Event', + + groups: ['Social'] } as CardDefinition & { type: 'event' }; diff --git a/src/lib/cards/FluidTextCard/index.ts b/src/lib/cards/FluidTextCard/index.ts index 666e89f..a55d2f6 100644 --- a/src/lib/cards/FluidTextCard/index.ts +++ b/src/lib/cards/FluidTextCard/index.ts @@ -23,5 +23,8 @@ export const FluidTextCardDefinition = { sidebarButtonText: 'Fluid Text', defaultColor: 'transparent', allowSetColor: true, - minW: 2 + minW: 2, + + groups: ['Visual'], + name: 'Fluid Text' } as CardDefinition & { type: 'fluid-text' }; diff --git a/src/lib/cards/GIFCard/index.ts b/src/lib/cards/GIFCard/index.ts index 21e9cf1..56ffd26 100644 --- a/src/lib/cards/GIFCard/index.ts +++ b/src/lib/cards/GIFCard/index.ts @@ -45,5 +45,7 @@ export const GifCardDefinition = { return null; }, urlHandlerPriority: 5, - name: 'GIF' + name: 'GIF', + + groups: ['Media'] } as CardDefinition & { type: 'gif' }; diff --git a/src/lib/cards/GameCards/DinoGameCard/index.ts b/src/lib/cards/GameCards/DinoGameCard/index.ts index e037b5c..b2902b2 100644 --- a/src/lib/cards/GameCards/DinoGameCard/index.ts +++ b/src/lib/cards/GameCards/DinoGameCard/index.ts @@ -14,5 +14,8 @@ export const DinoGameCardDefinition = { card.mobileH = 6; card.cardData = {}; }, - canHaveLabel: true + canHaveLabel: true, + + groups: ['Games'], + name: 'Dino Game' } as CardDefinition & { type: 'dino-game' }; diff --git a/src/lib/cards/GameCards/TetrisCard/index.ts b/src/lib/cards/GameCards/TetrisCard/index.ts index 030f890..e11df77 100644 --- a/src/lib/cards/GameCards/TetrisCard/index.ts +++ b/src/lib/cards/GameCards/TetrisCard/index.ts @@ -19,5 +19,9 @@ export const TetrisCardDefinition = { card.cardData = {}; }, maxH: 10, - canHaveLabel: true + canHaveLabel: true, + + groups: ['Games'], + + name: 'Tetris' } as CardDefinition & { type: 'tetris' }; diff --git a/src/lib/cards/GitHubProfileCard/index.ts b/src/lib/cards/GitHubProfileCard/index.ts index af7ccc8..2c9d9d1 100644 --- a/src/lib/cards/GitHubProfileCard/index.ts +++ b/src/lib/cards/GitHubProfileCard/index.ts @@ -50,7 +50,9 @@ export const GithubProfileCardDefitition = { return item; }, - name: 'Github Profile' + name: 'Github Profile', + + groups: ['Social'] } as CardDefinition & { type: 'githubProfile' }; function getGitHubUsername(url: string | undefined): string | undefined { diff --git a/src/lib/cards/GuestbookCard/index.ts b/src/lib/cards/GuestbookCard/index.ts index 9e4fdfe..b3a7cd6 100644 --- a/src/lib/cards/GuestbookCard/index.ts +++ b/src/lib/cards/GuestbookCard/index.ts @@ -60,5 +60,6 @@ export const GuestbookCardDefinition = { return results; }, - name: 'Guestbook' + name: 'Guestbook', + groups: ['Social'] } as CardDefinition & { type: 'guestbook' }; diff --git a/src/lib/cards/ImageCard/index.ts b/src/lib/cards/ImageCard/index.ts index 4dcec56..9e14944 100644 --- a/src/lib/cards/ImageCard/index.ts +++ b/src/lib/cards/ImageCard/index.ts @@ -42,7 +42,24 @@ export const ImageCardDefinition = { }, urlHandlerPriority: 3, - name: 'Image Card', + name: 'Image', - canHaveLabel: true + canHaveLabel: true, + + groups: ['Core'], + + icon: ` + + ` } as CardDefinition & { type: 'image' }; diff --git a/src/lib/cards/LatestBlueskyPostCard/index.ts b/src/lib/cards/LatestBlueskyPostCard/index.ts index dc07aa4..f547949 100644 --- a/src/lib/cards/LatestBlueskyPostCard/index.ts +++ b/src/lib/cards/LatestBlueskyPostCard/index.ts @@ -18,5 +18,9 @@ export const LatestBlueskyPostCardDefinition = { return JSON.parse(JSON.stringify(authorFeed)); }, - minW: 4 + minW: 4, + + name: 'Latest Bluesky Post', + + groups: ['Social'] } as CardDefinition & { type: 'latestPost' }; diff --git a/src/lib/cards/LinkCard/CreateLinkCardModal.svelte b/src/lib/cards/LinkCard/CreateLinkCardModal.svelte new file mode 100644 index 0000000..1699229 --- /dev/null +++ b/src/lib/cards/LinkCard/CreateLinkCardModal.svelte @@ -0,0 +1,44 @@ + + + +
{ + if (!item.cardData.href.trim()) return; + + let link = validateLink(item.cardData.href); + if (!link) { + errorMessage = 'Invalid link'; + return; + } + + item.cardData.href = link; + item.cardData.domain = new URL(link).hostname; + item.cardData.hasFetched = false; + + oncreate?.(); + }} + class="flex flex-col gap-2" + > + Enter a link + + + {#if errorMessage} +

{errorMessage}

+ {/if} + +
+ + +
+
+
diff --git a/src/lib/cards/LinkCard/index.ts b/src/lib/cards/LinkCard/index.ts index bc65d59..1fa7ec3 100644 --- a/src/lib/cards/LinkCard/index.ts +++ b/src/lib/cards/LinkCard/index.ts @@ -1,5 +1,6 @@ import { checkAndUploadImage, validateLink } from '$lib/helper'; import type { CardDefinition } from '../types'; +import CreateLinkCardModal from './CreateLinkCardModal.svelte'; import EditingLinkCard from './EditingLinkCard.svelte'; import LinkCard from './LinkCard.svelte'; import LinkCardSettings from './LinkCardSettings.svelte'; @@ -13,7 +14,9 @@ export const LinkCardDefinition = { }, settingsComponent: LinkCardSettings, - name: 'Link Card', + creationModalComponent: CreateLinkCardModal, + + name: 'Link', canChange: (item) => Boolean(validateLink(item.cardData?.href)), change: (item) => { const href = validateLink(item.cardData?.href); @@ -36,5 +39,22 @@ export const LinkCardDefinition = { await checkAndUploadImage(item.cardData, 'favicon'); return item; }, - urlHandlerPriority: 0 + urlHandlerPriority: 0, + + groups: ['Core'], + + icon: ` + +` } as CardDefinition & { type: 'link' }; diff --git a/src/lib/cards/LivestreamCard/index.ts b/src/lib/cards/LivestreamCard/index.ts index 9fbd513..d20adfb 100644 --- a/src/lib/cards/LivestreamCard/index.ts +++ b/src/lib/cards/LivestreamCard/index.ts @@ -81,7 +81,8 @@ export const LivestreamCardDefitition = { urlHandlerPriority: 5, - name: 'stream.place Card' + name: 'Latest Livestream (stream.place)', + groups: ['Media'] } as CardDefinition & { type: 'latestLivestream' }; export const LivestreamEmbedCardDefitition = { diff --git a/src/lib/cards/MapCard/index.ts b/src/lib/cards/MapCard/index.ts index 87dd5bb..e0149a1 100644 --- a/src/lib/cards/MapCard/index.ts +++ b/src/lib/cards/MapCard/index.ts @@ -17,7 +17,16 @@ export const MapCardDefinition = { creationModalComponent: CreateMapCardModal, allowSetColor: false, canHaveLabel: true, - settingsComponent: MapCardSettings + settingsComponent: MapCardSettings, + + groups: ['Core'], + + name: 'Map', + + icon: ` + + +` } as CardDefinition & { type: 'mapLocation' }; export function getZoomLevel(type: string | undefined): number { diff --git a/src/lib/cards/PopfeedReviews/index.ts b/src/lib/cards/PopfeedReviews/index.ts index 6888e0f..ac58439 100644 --- a/src/lib/cards/PopfeedReviews/index.ts +++ b/src/lib/cards/PopfeedReviews/index.ts @@ -18,5 +18,8 @@ export const PopfeedReviewsCardDefinition = { }, minH: 3, sidebarButtonText: 'Popfeed Reviews', - canHaveLabel: true + canHaveLabel: true, + + groups: ['Media'], + name: 'Movie and TV Reviews' } as CardDefinition & { type: 'recentPopfeedReviews' }; diff --git a/src/lib/cards/SectionCard/index.ts b/src/lib/cards/SectionCard/index.ts index f8a2235..c3128df 100644 --- a/src/lib/cards/SectionCard/index.ts +++ b/src/lib/cards/SectionCard/index.ts @@ -26,7 +26,22 @@ export const SectionCardDefinition = { defaultColor: 'transparent', maxH: 1, canResize: false, - settingsComponent: SectionCardSettings + settingsComponent: SectionCardSettings, + + name: 'Heading', + groups: ['Core'], + + icon: `` } as CardDefinition & { type: 'section' }; export const textAlignClasses: Record = { diff --git a/src/lib/cards/SpotifyCard/index.ts b/src/lib/cards/SpotifyCard/index.ts index 9db2828..873347e 100644 --- a/src/lib/cards/SpotifyCard/index.ts +++ b/src/lib/cards/SpotifyCard/index.ts @@ -40,7 +40,9 @@ export const SpotifyCardDefinition = { name: 'Spotify Embed', canResize: true, minW: 4, - minH: 5 + minH: 5, + + groups: ['Media'] } as CardDefinition & { type: typeof cardType }; // Match Spotify album and playlist URLs diff --git a/src/lib/cards/StandardSiteDocumentListCard/index.ts b/src/lib/cards/StandardSiteDocumentListCard/index.ts index f0d7fc8..191230f 100644 --- a/src/lib/cards/StandardSiteDocumentListCard/index.ts +++ b/src/lib/cards/StandardSiteDocumentListCard/index.ts @@ -42,5 +42,9 @@ export const StandardSiteDocumentListCardDefinition = { return records; }, - sidebarButtonText: 'site.standard.document list' + sidebarButtonText: 'site.standard.document list', + + name: 'Blog Posts', + + groups: ['Content'] } as CardDefinition & { type: 'site.standard.document list' }; diff --git a/src/lib/cards/StatusphereCard/index.ts b/src/lib/cards/StatusphereCard/index.ts index 31aa6e1..2e80076 100644 --- a/src/lib/cards/StatusphereCard/index.ts +++ b/src/lib/cards/StatusphereCard/index.ts @@ -47,7 +47,10 @@ export const StatusphereCardDefinition = { item.cardData.label = item.cardData.title; } }, - canHaveLabel: true + canHaveLabel: true, + + name: 'Emoji', + groups: ['Media'] } as CardDefinition & { type: 'statusphere' }; export function emojiToNotoAnimatedWebp(emoji: string | undefined): string | undefined { diff --git a/src/lib/cards/TealFMPlaysCard/index.ts b/src/lib/cards/TealFMPlaysCard/index.ts index c18a511..e2d9d1f 100644 --- a/src/lib/cards/TealFMPlaysCard/index.ts +++ b/src/lib/cards/TealFMPlaysCard/index.ts @@ -22,5 +22,9 @@ export const TealFMPlaysCardDefinition = { }, minW: 4, sidebarButtonText: 'teal.fm Plays', - canHaveLabel: true + canHaveLabel: true, + + name: 'Teal.fm Plays', + + groups: ['Media'] } as CardDefinition & { type: 'recentTealFMPlays' }; diff --git a/src/lib/cards/TextCard/index.ts b/src/lib/cards/TextCard/index.ts index 4f8296a..ff7c4b9 100644 --- a/src/lib/cards/TextCard/index.ts +++ b/src/lib/cards/TextCard/index.ts @@ -14,7 +14,22 @@ export const TextCardDefinition = { }; }, - settingsComponent: TextCardSettings + settingsComponent: TextCardSettings, + + name: 'Text', + + groups: ['Core'], + + icon: `` } as CardDefinition & { type: 'text' }; export const textAlignClasses: Record = { diff --git a/src/lib/cards/TimerCard/index.ts b/src/lib/cards/TimerCard/index.ts index 7bbe0b2..86726a3 100644 --- a/src/lib/cards/TimerCard/index.ts +++ b/src/lib/cards/TimerCard/index.ts @@ -33,5 +33,6 @@ export const TimerCardDefinition = { allowSetColor: true, name: 'Timer Card', minW: 4, - canHaveLabel: true + canHaveLabel: true, + groups: ['Utilities'] } as CardDefinition & { type: 'timer' }; diff --git a/src/lib/cards/VCardCard/index.ts b/src/lib/cards/VCardCard/index.ts index 6632304..fc2b9ed 100644 --- a/src/lib/cards/VCardCard/index.ts +++ b/src/lib/cards/VCardCard/index.ts @@ -122,5 +122,6 @@ export const VCardCardDefinition = { sidebarButtonText: 'vCard', allowSetColor: true, - name: 'vCard Card' + name: 'vCard Card', + groups: ['Social'] } as CardDefinition & { type: 'vcard' }; diff --git a/src/lib/cards/YoutubeVideoCard/index.ts b/src/lib/cards/YoutubeVideoCard/index.ts index d8579cb..b2296c6 100644 --- a/src/lib/cards/YoutubeVideoCard/index.ts +++ b/src/lib/cards/YoutubeVideoCard/index.ts @@ -51,7 +51,16 @@ export const YoutubeCardDefinition = { return item; }, - name: 'Youtube Video' + name: 'Youtube Video', + + groups: ['Media'], + + icon: `` } as CardDefinition & { type: 'youtubeVideo' }; // Thanks to eleventy-plugin-youtube-embed diff --git a/src/lib/cards/types.ts b/src/lib/cards/types.ts index 3143eec..0620740 100644 --- a/src/lib/cards/types.ts +++ b/src/lib/cards/types.ts @@ -73,4 +73,10 @@ export type CardDefinition = { canHaveLabel?: boolean; migrate?: (item: Item) => void; + + groups?: string[]; + + keywords?: string[]; + + icon?: string; }; diff --git a/src/lib/components/card-command/CardCommand.svelte b/src/lib/components/card-command/CardCommand.svelte new file mode 100644 index 0000000..16fa762 --- /dev/null +++ b/src/lib/components/card-command/CardCommand.svelte @@ -0,0 +1,102 @@ + + + + + + + + + Command Menu + + This is the command menu. Use the arrow keys to navigate and press ⌘K to open the search + bar. + + + + + + + No results found. + + + {#each CardDefGroups as group, index} + {#if group && AllCardDefinitions.some((cardDef) => cardDef.groups?.includes(group))} + + + {group} + + + {#each AllCardDefinitions.filter( (cardDef) => cardDef.groups?.includes(group) ) as cardDef} + { + open = false; + onselect(cardDef); + }} + class="rounded-button data-selected:bg-accent-500/10 flex h-10 cursor-pointer items-center gap-2 rounded-xl px-3 py-2.5 text-sm outline-hidden select-none" + keywords={[group, cardDef.type, ...(cardDef.keywords || [])]} + > + {#if cardDef.icon} +
+ {@html cardDef.icon} +
+ {/if} + {cardDef.name} +
+ {/each} +
+
+ {#if index < CardDefGroups.length - 1} + + {/if} + {/if} + {/each} +
+
+
+
+
+
diff --git a/src/lib/website/EditBar.svelte b/src/lib/website/EditBar.svelte index e07681b..e650629 100644 --- a/src/lib/website/EditBar.svelte +++ b/src/lib/website/EditBar.svelte @@ -17,7 +17,9 @@ save, handleImageInputChange, - handleVideoInputChange + handleVideoInputChange, + + showCardCommand }: { data: WebsiteData; linkValue: string; @@ -33,6 +35,8 @@ handleImageInputChange: (evt: Event) => void; handleVideoInputChange: (evt: Event) => void; + + showCardCommand: () => void; } = $props(); let linkPopoverOpen = $state(false); @@ -59,6 +63,7 @@ accept="image/*" onchange={handleImageInputChange} class="hidden" + id="image-input" multiple bind:this={imageInputRef} /> @@ -220,7 +225,7 @@ {/if} -