diff --git a/Foxnouns.Frontend/src/lib/components/editor/LinksEditor.svelte b/Foxnouns.Frontend/src/lib/components/editor/LinksEditor.svelte index f908e0e..4047880 100644 --- a/Foxnouns.Frontend/src/lib/components/editor/LinksEditor.svelte +++ b/Foxnouns.Frontend/src/lib/components/editor/LinksEditor.svelte @@ -2,14 +2,16 @@ import type { RawApiError } from "$api/error"; import IconButton from "$components/IconButton.svelte"; import { t } from "$lib/i18n"; + import ephemeralState from "$lib/state.svelte"; import FormStatusMarker from "./FormStatusMarker.svelte"; type Props = { + stateKey: string; currentLinks: string[]; save(links: string[]): Promise; form: { ok: boolean; error: RawApiError | null } | null; }; - let { currentLinks, save, form }: Props = $props(); + let { stateKey, currentLinks, save, form }: Props = $props(); let links = $state(currentLinks); let newEntry = $state(""); @@ -37,6 +39,12 @@ links = [...links, newEntry]; newEntry = ""; }; + + ephemeralState( + stateKey, + () => links, + (data) => (links = data), + );

diff --git a/Foxnouns.Frontend/src/lib/components/editor/ProfileFlagsEditor.svelte b/Foxnouns.Frontend/src/lib/components/editor/ProfileFlagsEditor.svelte index 5bd62fd..304ae88 100644 --- a/Foxnouns.Frontend/src/lib/components/editor/ProfileFlagsEditor.svelte +++ b/Foxnouns.Frontend/src/lib/components/editor/ProfileFlagsEditor.svelte @@ -4,16 +4,18 @@ import FlagSearch from "$components/editor/FlagSearch.svelte"; import IconButton from "$components/IconButton.svelte"; import { t } from "$lib/i18n"; + import ephemeralState from "$lib/state.svelte"; import FlagButton from "./FlagButton.svelte"; import FormStatusMarker from "./FormStatusMarker.svelte"; type Props = { + stateKey: string; profileFlags: PrideFlag[]; allFlags: PrideFlag[]; save(flags: string[]): Promise; form: { ok: boolean; error: RawApiError | null } | null; }; - let { profileFlags, allFlags, save, form }: Props = $props(); + let { stateKey, profileFlags, allFlags, save, form }: Props = $props(); let flags = $state(profileFlags); @@ -40,6 +42,12 @@ }; const saveChanges = () => save(flags.map((f) => f.id)); + + ephemeralState( + stateKey, + () => flags, + (data) => (flags = data), + );
diff --git a/Foxnouns.Frontend/src/lib/state.svelte.ts b/Foxnouns.Frontend/src/lib/state.svelte.ts new file mode 100644 index 0000000..8358bf3 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/state.svelte.ts @@ -0,0 +1,37 @@ +import { onMount, onDestroy } from "svelte"; +import { browser } from "$app/environment"; +import log from "./log"; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import type { Snapshot } from "@sveltejs/kit"; + +/** + * Store ephemeral state in sessionStorage to persist between navigations. + * Similar to {@link Snapshot}, but doesn't attach it to a history entry. + * @param key Unique key to use for this state. + * @param capture Function that returns the state to store. + * @param restore Function that takes the state that was stored previously and assigns it back to component variables. + */ +export default function ephemeralState( + key: string, + capture: () => T, + restore: (data: T) => void, +): void { + if (!browser) return; + + onMount(() => { + if (!("sessionStorage" in window)) return; + const rawData = sessionStorage.getItem("ephemeral-" + key); + if (!rawData) return; + + log.debug("Restoring data %s from session storage", key); + const data = JSON.parse(rawData) as T; + restore(data); + }); + + onDestroy(() => { + if (!("sessionStorage" in window)) return; + + log.debug("Saving data %s to session storage", key); + sessionStorage.setItem("ephemeral-" + key, JSON.stringify(capture())); + }); +} diff --git a/Foxnouns.Frontend/src/routes/settings/members/[id]/+page.svelte b/Foxnouns.Frontend/src/routes/settings/members/[id]/+page.svelte index 64be86e..0e34638 100644 --- a/Foxnouns.Frontend/src/routes/settings/members/[id]/+page.svelte +++ b/Foxnouns.Frontend/src/routes/settings/members/[id]/+page.svelte @@ -15,6 +15,7 @@ import BioEditor from "$components/editor/BioEditor.svelte"; import { PUBLIC_BASE_URL } from "$env/static/public"; import { enhance } from "$app/forms"; + import ephemeralState from "$lib/state.svelte"; type Props = { data: PageData; form: ActionData }; let { data, form }: Props = $props(); @@ -61,6 +62,12 @@ // Bio is stored in a $state() so we have a markdown preview let bio = $state(data.member.bio || ""); + + ephemeralState( + "member-" + data.member.id + "-bio", + () => bio, + (data) => (bio = data), + ); {#if error} diff --git a/Foxnouns.Frontend/src/routes/settings/members/[id]/fields/+page.svelte b/Foxnouns.Frontend/src/routes/settings/members/[id]/fields/+page.svelte index 470f4f9..7e86851 100644 --- a/Foxnouns.Frontend/src/routes/settings/members/[id]/fields/+page.svelte +++ b/Foxnouns.Frontend/src/routes/settings/members/[id]/fields/+page.svelte @@ -6,6 +6,7 @@ import FieldsEditor from "$components/editor/FieldsEditor.svelte"; import NoscriptWarning from "$components/editor/NoscriptWarning.svelte"; import log from "$lib/log"; + import ephemeralState from "$lib/state.svelte"; import type { PageData } from "./$types"; type Props = { data: PageData }; @@ -29,6 +30,12 @@ if (e instanceof ApiError) ok.error = e.obj; } }; + + ephemeralState( + "member-" + data.member.id + "-fields", + () => fields, + (data) => (fields = data), + ); diff --git a/Foxnouns.Frontend/src/routes/settings/members/[id]/flags-links/+page.svelte b/Foxnouns.Frontend/src/routes/settings/members/[id]/flags-links/+page.svelte index b6aaadb..e9e1c2d 100644 --- a/Foxnouns.Frontend/src/routes/settings/members/[id]/flags-links/+page.svelte +++ b/Foxnouns.Frontend/src/routes/settings/members/[id]/flags-links/+page.svelte @@ -41,10 +41,16 @@ - + diff --git a/Foxnouns.Frontend/src/routes/settings/members/[id]/names-pronouns/+page.svelte b/Foxnouns.Frontend/src/routes/settings/members/[id]/names-pronouns/+page.svelte index 918381d..9aa19bb 100644 --- a/Foxnouns.Frontend/src/routes/settings/members/[id]/names-pronouns/+page.svelte +++ b/Foxnouns.Frontend/src/routes/settings/members/[id]/names-pronouns/+page.svelte @@ -10,6 +10,7 @@ import PronounsEditor from "$components/editor/PronounsEditor.svelte"; import { t } from "$lib/i18n"; import log from "$lib/log"; + import ephemeralState from "$lib/state.svelte"; import type { PageData } from "./$types"; type Props = { data: PageData }; @@ -22,6 +23,15 @@ let allPreferences = $derived(mergePreferences(data.user.custom_preferences)); + ephemeralState( + "member-" + data.member.id + "-names-pronouns", + () => ({ names, pronouns }), + (data) => { + names = data.names; + pronouns = data.pronouns; + }, + ); + const update = async () => { try { const resp = await apiRequest("PATCH", `/users/@me/members/${data.member.id}`, { diff --git a/Foxnouns.Frontend/src/routes/settings/profile/bio/+page.svelte b/Foxnouns.Frontend/src/routes/settings/profile/bio/+page.svelte index 19e04fb..91e452b 100644 --- a/Foxnouns.Frontend/src/routes/settings/profile/bio/+page.svelte +++ b/Foxnouns.Frontend/src/routes/settings/profile/bio/+page.svelte @@ -3,11 +3,18 @@ import type { ActionData, PageData } from "./$types"; import BioEditor from "$components/editor/BioEditor.svelte"; import { t } from "$lib/i18n"; + import ephemeralState from "$lib/state.svelte"; type Props = { data: PageData; form: ActionData }; let { data, form }: Props = $props(); let bio = $state(data.user.bio || ""); + + ephemeralState( + "user-bio", + () => bio, + (data) => (bio = data), + );

{$t("edit-profile.bio-tab")}

diff --git a/Foxnouns.Frontend/src/routes/settings/profile/fields/+page.svelte b/Foxnouns.Frontend/src/routes/settings/profile/fields/+page.svelte index 44b0cf7..3f47f74 100644 --- a/Foxnouns.Frontend/src/routes/settings/profile/fields/+page.svelte +++ b/Foxnouns.Frontend/src/routes/settings/profile/fields/+page.svelte @@ -6,6 +6,7 @@ import FieldsEditor from "$components/editor/FieldsEditor.svelte"; import NoscriptWarning from "$components/editor/NoscriptWarning.svelte"; import log from "$lib/log"; + import ephemeralState from "$lib/state.svelte"; import type { PageData } from "./$types"; type Props = { data: PageData }; @@ -29,6 +30,12 @@ if (e instanceof ApiError) ok.error = e.obj; } }; + + ephemeralState( + "user-fields", + () => fields, + (data) => (fields = data), + ); diff --git a/Foxnouns.Frontend/src/routes/settings/profile/flags-links/+page.svelte b/Foxnouns.Frontend/src/routes/settings/profile/flags-links/+page.svelte index 4b2b165..b56d0c2 100644 --- a/Foxnouns.Frontend/src/routes/settings/profile/flags-links/+page.svelte +++ b/Foxnouns.Frontend/src/routes/settings/profile/flags-links/+page.svelte @@ -41,10 +41,16 @@ - + diff --git a/Foxnouns.Frontend/src/routes/settings/profile/names-pronouns/+page.svelte b/Foxnouns.Frontend/src/routes/settings/profile/names-pronouns/+page.svelte index b787096..2703748 100644 --- a/Foxnouns.Frontend/src/routes/settings/profile/names-pronouns/+page.svelte +++ b/Foxnouns.Frontend/src/routes/settings/profile/names-pronouns/+page.svelte @@ -9,6 +9,7 @@ import PronounsEditor from "$components/editor/PronounsEditor.svelte"; import { t } from "$lib/i18n"; import log from "$lib/log"; + import ephemeralState from "$lib/state.svelte"; import type { PageData } from "./$types"; type Props = { data: PageData }; @@ -19,6 +20,15 @@ let ok: { ok: boolean; error: RawApiError | null } | null = $state(null); let allPreferences = $derived(mergePreferences(data.user.custom_preferences)); + ephemeralState( + "user-names-pronouns", + () => ({ names, pronouns }), + (data) => { + names = data.names; + pronouns = data.pronouns; + }, + ); + const update = async () => { try { const resp = await apiRequest("PATCH", "/users/@me", {