diff --git a/src/lib/helper.ts b/src/lib/helper.ts index 935db87..8680f43 100644 --- a/src/lib/helper.ts +++ b/src/lib/helper.ts @@ -455,20 +455,21 @@ export async function savePage( ) { const promises = []; // find all cards that have been updated (where items differ from originalItems) - for (const item of currentItems) { + for (let item of currentItems) { const originalItem = data.cards.find((i) => cardsEqual(i, item)); if (!originalItem) { - let parsedItem = JSON.parse(JSON.stringify(item)); - console.log('updated or new item', parsedItem); - parsedItem.updatedAt = new Date().toISOString(); + console.log('updated or new item', item); + item.updatedAt = new Date().toISOString(); // run optional upload function for this card type - const cardDef = CardDefinitionsByType[parsedItem.cardType]; + const cardDef = CardDefinitionsByType[item.cardType]; if (cardDef?.upload) { - parsedItem = await cardDef?.upload(parsedItem); + item = await cardDef?.upload(item); } + const parsedItem = JSON.parse(JSON.stringify(item)); + parsedItem.page = data.page; parsedItem.version = 2; @@ -536,15 +537,6 @@ export async function savePage( } await Promise.all(promises); - - fetch('/' + data.handle + '/api/refresh').then(() => { - console.log('data refreshed!'); - }); - console.log('refreshing data'); - - toast('Saved', { - description: 'Your website has been saved!' - }); } export function createEmptyCard(page: string) { diff --git a/src/lib/website/EditBar.svelte b/src/lib/website/EditBar.svelte index f4f02e4..e07681b 100644 --- a/src/lib/website/EditBar.svelte +++ b/src/lib/website/EditBar.svelte @@ -2,7 +2,7 @@ import { dev } from '$app/environment'; import { user } from '$lib/atproto'; import type { WebsiteData } from '$lib/types'; - import { Button, Input, Modal, Navbar, Popover, Toggle } from '@foxui/core'; + import { Button, Input, Navbar, Popover, Toggle, toast } from '@foxui/core'; let { data, @@ -12,6 +12,7 @@ showingMobileView = $bindable(), isSaving = $bindable(), + hasUnsavedChanges, save, @@ -26,6 +27,7 @@ showingMobileView: boolean; isSaving: boolean; + hasUnsavedChanges: boolean; save: () => Promise; @@ -38,7 +40,18 @@ let imageInputRef: HTMLInputElement | undefined = $state(); let videoInputRef: HTMLInputElement | undefined = $state(); - let shareModalOpen = $state(false); + function getShareUrl() { + const base = typeof window !== 'undefined' ? window.location.origin : ''; + const pagePath = + data.page && data.page !== 'blento.self' ? `/${data.page.replace('blento.', '')}` : ''; + return `${base}/${data.handle}${pagePath}`; + } + + async function copyShareLink() { + const url = getShareUrl(); + await navigator.clipboard.writeText(url); + toast.success('Link copied to clipboard!'); + } - - {#if dev || (user.isLoggedIn && user.profile?.did === data.did)} - + {#if hasUnsavedChanges} + + {:else} + + {/if} {/if} diff --git a/src/lib/website/EditableWebsite.svelte b/src/lib/website/EditableWebsite.svelte index f681c80..dcba277 100644 --- a/src/lib/website/EditableWebsite.svelte +++ b/src/lib/website/EditableWebsite.svelte @@ -32,7 +32,9 @@ import { compressImage } from '../helper'; import Account from './Account.svelte'; import EditBar from './EditBar.svelte'; + import SaveModal from './SaveModal.svelte'; import { user } from '$lib/atproto'; + import { launchConfetti } from '@foxui/visual'; let { data @@ -48,6 +50,29 @@ // svelte-ignore state_referenced_locally let publication = $state(JSON.stringify(data.publication)); + // Track saved state for comparison + // svelte-ignore state_referenced_locally + let savedItems = $state(JSON.stringify(data.cards)); + // svelte-ignore state_referenced_locally + let savedPublication = $state(JSON.stringify(data.publication)); + + let hasUnsavedChanges = $derived( + JSON.stringify(items) !== savedItems || JSON.stringify(data.publication) !== savedPublication + ); + + // Warn user before closing tab if there are unsaved changes + $effect(() => { + function handleBeforeUnload(e: BeforeUnloadEvent) { + if (hasUnsavedChanges) { + e.preventDefault(); + return ''; + } + } + + window.addEventListener('beforeunload', handleBeforeUnload); + return () => window.removeEventListener('beforeunload', handleBeforeUnload); + }); + let container: HTMLDivElement | undefined = $state(); let activeDragElement: { @@ -128,11 +153,15 @@ } let isSaving = $state(false); + let showSaveModal = $state(false); + let saveSuccess = $state(false); let newItem: { modal?: Component; item?: Item } = $state({}); async function save() { isSaving = true; + saveSuccess = false; + showSaveModal = true; try { // Upload profile icon if changed @@ -143,8 +172,20 @@ await savePage(data, items, publication); publication = JSON.stringify(data.publication); + + // Update saved state + savedItems = JSON.stringify(items); + savedPublication = JSON.stringify(data.publication); + + saveSuccess = true; + + launchConfetti(); + + // Refresh cached data + await fetch('/' + data.handle + '/api/refresh'); } catch (error) { console.log(error); + showSaveModal = false; toast.error('Error saving page!'); } finally { isSaving = false; @@ -320,14 +361,13 @@ const isGif = file.type === 'image/gif'; // Don't compress GIFs to preserve animation - const processedFile = isGif ? file : await compressImage(file); - const objectUrl = URL.createObjectURL(processedFile); + const objectUrl = URL.createObjectURL(file); let item = createEmptyCard(data.page); item.cardType = isGif ? 'gif' : 'image'; item.cardData = { - image: { blob: processedFile, objectUrl } + image: { blob: file, objectUrl } }; // If grid position is provided @@ -559,6 +599,13 @@ /> {/if} + +
+ import { Button, Modal, toast } from '@foxui/core'; + + let { + open = $bindable(), + success, + handle, + page + }: { + open: boolean; + success: boolean; + handle: string; + page: string; + } = $props(); + + function getShareUrl() { + const base = typeof window !== 'undefined' ? window.location.origin : ''; + const pagePath = page && page !== 'blento.self' ? `/${page.replace('blento.', '')}` : ''; + return `${base}/${handle}${pagePath}`; + } + + async function copyShareLink() { + const url = getShareUrl(); + await navigator.clipboard.writeText(url); + toast.success('Link copied to clipboard!'); + } + + + +
+ {#if !success} +
+ + + + +

Saving...

+
+ {:else} +
+ + + + +

Website Saved

+
+
+ + +
+ {/if} +
+