tangle_indexer


git clone https://radroots.dev/git/tangle_indexer.git
Log | Files | Refs | Submodules | LICENSE

commit c7a6c31f526419c9c4e97ced0836701926ee981b
parent 5caf0ab3f2ecae377661a26d7f6ff4f27809b4bf
Author: triesap <tyson@radroots.org>
Date:   Wed, 10 Jun 2026 20:33:30 +0000

chore: sync working tree

- record local source cleanup state
- preserve current checked-out project files
- update generated or scaffolded surfaces as staged
- keep nested repository remote syncable

Diffstat:
Mapp/src/app.css | 4+---
Mapp/src/app.d.ts | 6+++++-
Aapp/src/lib/components/dropdown-nested.svelte | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapp/src/lib/components/nav-search.svelte | 38++++++++++++++++++++++++++++++++++++++
Aapp/src/lib/components/nav-simple.svelte | 45+++++++++++++++++++++++++++++++++++++++++++++
Aapp/src/lib/components/nav.svelte | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapp/src/lib/stores/lib.ts | 31+++++++++++++++++++++++++++++++
Aapp/src/lib/stores/nostr-session.svelte.ts | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapp/src/lib/utils/routes/gen.tmp.ts | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapp/src/routes/(market)/(profile)/+layout.svelte | 13+++++++++++++
Aapp/static/fonts/brown-regular/Brown-Bold.woff | 0
Aapp/static/fonts/brown-regular/Brown-Bold.woff2 | 0
Aapp/static/fonts/brown-regular/Brown-Regular.woff | 0
Aapp/static/fonts/brown-regular/Brown-Regular.woff2 | 0
Aapp/static/fonts/brown-regular/styles.css | 18++++++++++++++++++
Aapp/static/fonts/serif/RadrootsSerif-bold.woff | 0
Aapp/static/fonts/serif/RadrootsSerif-regular-italic.woff | 0
Aapp/static/fonts/serif/RadrootsSerif-regular.woff | 0
Aapp/static/fonts/serif/RadrootsSerifDisplay-bold.woff | 0
Aapp/static/fonts/serif/RadrootsSerifDisplay-medium.woff | 0
Aapp/static/fonts/serif/RadrootsSerifDisplay-regular.woff | 0
Aapp/static/fonts/serif/styles.css | 49+++++++++++++++++++++++++++++++++++++++++++++++++
22 files changed, 571 insertions(+), 4 deletions(-)

diff --git a/app/src/app.css b/app/src/app.css @@ -1,7 +1,6 @@ @import "tailwindcss"; @import "../theme.css"; - @import "../static/fonts/brown-regular/styles.css"; @import "../static/fonts/serif/styles.css"; @@ -21,4 +20,4 @@ --color-morning_snow: #f8f4ee; --color-cloak_grey: #616161; --color-black_panther: #424242; -} -\ No newline at end of file +} diff --git a/app/src/app.d.ts b/app/src/app.d.ts @@ -1,5 +1,9 @@ declare global { - namespace App { } + namespace App { + interface SettingsNip78 { + dev_mode: boolean; + } + } } export { }; diff --git a/app/src/lib/components/dropdown-nested.svelte b/app/src/lib/components/dropdown-nested.svelte @@ -0,0 +1,65 @@ +<script lang="ts"> + import { fmt_cl } from "@radroots/apps-lib"; + import { onDestroy, onMount, type Snippet } from "svelte"; + + let { + basis, + primary, + dropdown, + }: { + basis?: { + classes_primary?: string; + classes_dropdown?: string; + }; + primary: Snippet; + dropdown: Snippet; + } = $props(); + + let el_container: HTMLDetailsElement | null = $state(null); + let el_dropdown: HTMLElement | null = $state(null); + + let shift_x = $state(0); + + $effect(() => { + const rect_primary = el_container?.getBoundingClientRect(); + const rect_dropdown = el_dropdown?.getBoundingClientRect(); + if (rect_primary && rect_dropdown) + shift_x = rect_dropdown.width - rect_primary.width; + }); + + const handle_click = (event: MouseEvent) => { + if (!el_container || !el_dropdown) return; + if ( + !el_container.contains(event.target as Node) || + el_dropdown.contains(event.target as Node) + ) + el_container.open = false; + }; + + onMount(() => { + document.addEventListener("click", handle_click); + }); + + onDestroy(() => { + document.removeEventListener("click", handle_click); + }); +</script> + +<details bind:this={el_container} class="dropdown dropdown-bottom"> + <summary class={`${fmt_cl(basis?.classes_primary)} flex`}> + {@render primary()} + </summary> + <ul + bind:this={el_dropdown} + class={`dropdown-content flex flex-col pt-1`} + style={`transform: translateX(-${shift_x}px);`} + > + <ul + class={`z-50 ${fmt_cl( + basis?.classes_dropdown || `menu min-w-52 p-2 bg-white shadow`, + )} flex flex-col w-full justify-center items-center`} + > + {@render dropdown()} + </ul> + </ul> +</details> diff --git a/app/src/lib/components/nav-search.svelte b/app/src/lib/components/nav-search.svelte @@ -0,0 +1,38 @@ +<script lang="ts"> + import { locale, ls } from "$lib/utils/i18n"; + import { Glyph, Input } from "@radroots/apps-lib"; + + let val_search = $state(``); + + const handle_search = async (): Promise<void> => { + alert(`todo handle search`); + }; +</script> + +<div class={`flex flex-row h-full w-full justify-start items-center`}> + <Input + bind:val={val_search} + basis={{ + classes: `h-[22px] pl-2 bg-gray-200 text-[13px] font-rsfd placeholder:lowercase`, + placeholder: `${$ls(`common.search`)} ${$locale}`, + callback_keydown: async ({ is_submit }) => { + if (is_submit) await handle_search(); + }, + }} + /> + <button + class={`flex flex-row h-[22px] flex-shrink-0 px-2 justify-center items-center bg-lime-500 hover:bg-lime-400`} + onclick={async () => { + locale.set($locale === `en` ? `es` : `en`); + }} + > + <Glyph + basis={{ + classes: `text-white`, + size: `xs`, + weight: `bold`, + key: `magnifying-glass`, + }} + /> + </button> +</div> diff --git a/app/src/lib/components/nav-simple.svelte b/app/src/lib/components/nav-simple.svelte @@ -0,0 +1,45 @@ +<script lang="ts"> + import { goto } from "$app/navigation"; + import { page } from "$app/state"; + import { home_menu_visible } from "$lib/stores/lib"; + import { locale, ls } from "$lib/utils/i18n"; + import { root_symbol } from "@radroots/utils"; + import type { Snippet } from "svelte"; + + let { children }: { children: Snippet } = $props(); +</script> + +<div + class={`flex flex-row h-[45px] desktop:h-[4.25rem] w-full px-4 desktop:px-12 justify-between items-center bg-white/80 border-b-2 border-b-gray-200`} +> + <div class={`flex flex-row gap-4 justify-start items-center`}> + <button + class={`flex flex-row justify-center items-center`} + onclick={async () => { + if (page.url.pathname === `/`) home_menu_visible.toggle(); + else await goto(`/`); + }} + > + <p + class={`font-sans font-[700] text-cloak_grey text-3xl tracking-tightest`} + > + {root_symbol} + </p> + </button> + <button + class={`flex flex-row justify-center items-center`} + onclick={async () => { + if (page.url.pathname === `/`) + locale.set($locale === `en` ? `es` : `en`); + else await goto(`/`); + }} + > + <p + class={`font-br font-[700] text-cloak_grey text-lg desktop:text-2xl max-desktop:-translate-y-[1px]`} + > + {`${$ls(`web.nav.label`, { default: "rad roots" })}`} + </p> + </button> + </div> + {@render children()} +</div> diff --git a/app/src/lib/components/nav.svelte b/app/src/lib/components/nav.svelte @@ -0,0 +1,118 @@ +<script lang="ts"> + import DropdownNested from "$lib/components/dropdown-nested.svelte"; + import { nostr_session } from "$lib/stores/lib"; + import { locale, ls } from "$lib/utils/i18n"; + import { localise_auth_route } from "$lib/utils/routes/gen.tmp"; + import { type CallbackPromise } from "@radroots/utils"; + import NavSearch from "./nav-search.svelte"; + import NavSimple from "./nav-simple.svelte"; + + const logout = async (): Promise<void> => { + //nostr_session.set(null); + // location.reload(); + }; +</script> + +<NavSimple> + <div class={`flex flex-col justify-center items-center`}> + <div + class={`relative hidden desktop:flex flex-col h-[27px] w-[180px] justify-center items-center -translate-y-1`} + > + <NavSearch /> + {#if $nostr_session?.user} + <div + class={`absolute -bottom-[1rem] flex flex-row w-full justify-start items-center`} + > + <DropdownNested> + {#snippet primary()} + <p + class={`font-sans font-[400] text-xs text-layer-0-glyph line-clamp-1 overflow-hidden break-all hover:opacity-80`} + > + {#if $nostr_session.profile?.nip05} + {`logged in as ${$nostr_session.profile.nip05}`} + {:else} + {`logged in as ${$nostr_session.user?.npub}`} + {/if} + </p> + {/snippet} + {#snippet dropdown()} + <div + class={`flex flex-col w-full py-2 justify-center items-center`} + > + <div + class={`flex flex-row w-full justify-center items-center`} + > + <p + class={`font-br font-[400] text-xs text-layer-0-glyph text-center`} + > + {`You are logged in via Nostr extension`} + </p> + </div> + </div> + {#snippet anchor(href: string, label: string)} + <a + {href} + class={`flex flex-row w-full h-6 justify-center items-center hover:bg-gray-100`} + > + <p + class={`font-rsfd font-[400] text-xs text-layer-0-glyph capitalize`} + > + {label} + </p> + </a> + {/snippet} + {@render anchor( + `/profile`, //@todo + `${$ls(`web.common.view_profile`)}`, + )} + {@render anchor( + `/`, //@todo + `${$ls(`web.common.settings`)}`, + )} + {/snippet} + </DropdownNested> + </div> + {:else} + <div + class={`absolute -bottom-[1rem] flex flex-row w-full justify-start items-center`} + > + <a + href={`${localise_auth_route($locale, `login`)}`} + class={`flex flex-row pl-1 justify-center items-center hover:opacity-40`} + > + <p + class={`font-br font-[400] text-[11px] text-black_panther/80 `} + > + {`${$ls(`web.nav.login.label`)}!!`} + </p> + </a> + </div> + {/if} + </div> + <div class={`desktop:hidden flex flex-row justify-start items-center`}> + {#snippet button(label: string, callback: CallbackPromise)} + <button + class={`flex flex-row w-full h-6 px-4 justify-center items-center bg-lime-500 hover:bg-lime-400 rounded-sm`} + onclick={async () => { + await callback(); + }} + > + <p + class={`font-br font-[700] text-xs text-white -translate-y-[1px]`} + > + {label} + </p> + </button> + {/snippet} + {#if $nostr_session?.user} + {@render button(`${$ls(`common.profile`)}`, async () => { + // + })} + {:else} + {@render button(`${$ls(`common.log_in`)}`, async () => { + await logout(); + })} + {/if} + </div> + </div> +</NavSimple> diff --git a/app/src/lib/stores/lib.ts b/app/src/lib/stores/lib.ts @@ -0,0 +1,30 @@ +import { writable } from "svelte/store"; +import { NostrSession } from "./nostr-session.svelte"; + +export const home_menu_visible = (() => { + const { subscribe, set, update } = writable<boolean>(false); + return { + subscribe, + set, + toggle: () => update(value => !value), + }; +})(); + +export const nostr_session = (() => { + const { subscribe, set, update: _update } = writable<NostrSession | null>(null); + + return { + subscribe, + get: (): NostrSession | null => { + let value: NostrSession | null; + const unsubscribe = subscribe(v => (value = v)); + unsubscribe(); + return value!; + }, + set: (npub: string | null): NostrSession | null => { + const profile = npub ? new NostrSession(npub) : null; + set(profile); + return profile; + }, + }; +})(); +\ No newline at end of file diff --git a/app/src/lib/stores/nostr-session.svelte.ts b/app/src/lib/stores/nostr-session.svelte.ts @@ -0,0 +1,66 @@ +import { browser } from "$app/environment"; +import { NDKKind, NDKNip07Signer, NDKUser, type NDKUserProfile } from "@nostr-dev-kit/ndk"; +import { get_store, ndk } from "@radroots/apps-lib"; + +export class NostrSession { + user: NDKUser | null = $state(null); + profile: NDKUserProfile | null = $state(null); + follows: string[] = $state([]); + settings: App.SettingsNip78 | null = $state(null); + + constructor(npub: string) { + const ndk_store = get_store(ndk); + this.user = ndk_store.getUser({ npub }) as unknown as NDKUser; //@todo + if (this.user) { + this.fetch_profile(); + this.fetch_follows(); + this.fetch_settings(); + } + } + + async fetch_profile(): Promise<NDKUserProfile | null> { + if (this.user) { + const profile = await this.user.fetchProfile({}); + if (profile) this.profile = profile; + return profile + } + return Promise.resolve(null); + } + + async fetch_follows(): Promise<string[]> { + if (this.user) { + const follows_set = await this.user.followSet(); + const follows = Array.from(follows_set).map((public_key) => public_key); + if (follows.length) this.follows = follows; + return follows; + } + return Promise.resolve([]); + } + + async fetch_settings(): Promise<App.SettingsNip78> { + if (!this.user || !this.user.ndk) throw new Error(`[error] No nostr session user.`); + if (!browser) return Promise.resolve({ dev_mode: false }); + const ndk = this.user.ndk; + const events_app_data = await ndk.fetchEvents({ + kinds: [NDKKind.AppSpecificData], + authors: [this.user.pubkey], + "#d": [`radroots/settings/v1`], + }); + const list_events_app_data = Array.from(events_app_data); + + let settings: App.SettingsNip78 = { dev_mode: false }; + if (list_events_app_data.length === 1) { + const event_app_data = list_events_app_data[0]; + let signer: NDKNip07Signer; + if (!ndk.signer) { + signer = new NDKNip07Signer(); + ndk.signer = signer; + } + await event_app_data.decrypt(this.user); + settings = JSON.parse(event_app_data.content); + } else if (list_events_app_data.length > 1) { + console.error(`[todo] Multiple app data settings events`, list_events_app_data) + } + return settings; + } +} +\ No newline at end of file diff --git a/app/src/lib/utils/routes/gen.tmp.ts b/app/src/lib/utils/routes/gen.tmp.ts @@ -0,0 +1,120 @@ +// this file was created with @radroots gen-localised-routes + +import { get_locales, type Locales } from "@radroots/locales"; + +export const locale_routes: Record<string, string> = { + "/login": "/login", + "/login/confirm": "/login/confirm", + "/signup": "/signup", + "/signup/confirm": "/signup/confirm", + "/acceso": "/login", + "/acceso/confirmar": "/login/confirm", + "/régistrarse": "/signup", + "/régistrarse/confirmar": "/signup/confirm", + "/about": "/about", + "/blog": "/blog", + "/contact": "/contact", + "/faq": "/faq", + "/acerca": "/about", + "/contacto": "/contact", + "/profile": "/profile", + "/perfil": "/profile", + // (simple) routes + "/map": "/map", + "/mapa": "/map", +}; + +export type LocalisedRoutesSimpleEntries = { + "map": string; +}; + +export const locales_routes_simple_map: Record<Locales, LocalisedRoutesSimpleEntries> = { + en: { + map: "/map" + }, + es: { + map: "/mapa" + }, +}; + +export const localise_simple_route = (locale: string, key: keyof LocalisedRoutesSimpleEntries): string => { + const loc = get_locales(locale); + return locales_routes_simple_map[loc][key]; +}; + +export const set_locales_routes_map = new Set(["map", "mapa"]); + +export type LocalisedRoutesAuthEntries = { + "login": string; + "login_confirm": string; + "signup": string; + "signup_confirm": string; +}; + +export const locales_routes_auth_map: Record<Locales, LocalisedRoutesAuthEntries> = { + en: { + login: "/login", + login_confirm: "/login/confirm", + signup: "/signup", + signup_confirm: "/signup/confirm", + }, + es: { + login: "/acceso", + login_confirm: "/acceso/confirmar", + signup: "/régistrarse", + signup_confirm: "/régistrarse/confirmar", + }, +}; +export const set_locales_routes_auth = new Set(["/login", "/login/confirm", "/signup", "/signup/confirm", "acceso", "acceso/confirmar", "régistrarse", "régistrarse/confirmar"]); + +export const localise_auth_route = (locale: string, key: keyof LocalisedRoutesAuthEntries): string => { + const loc = get_locales(locale); + return locales_routes_auth_map[loc][key]; +}; + +export type LocalisedRoutesInfoEntries = { + "about": string; + "blog": string; + "contact": string; + "faq": string; +}; + +export const locales_routes_info_map: Record<Locales, LocalisedRoutesInfoEntries> = { + en: { + about: "/about", + blog: "/blog", + contact: "/contact", + faq: "/faq", + }, + es: { + about: "/acerca", + blog: "/blog", + contact: "/contacto", + faq: "/faq", + }, +}; +export const set_locales_routes_info = new Set(["/about", "/blog", "/contact", "/faq", "acerca", "blog", "contacto", "faq"]); + +export const localise_info_route = (locale: string, key: keyof LocalisedRoutesInfoEntries): string => { + const loc = get_locales(locale); + return locales_routes_info_map[loc][key]; +}; + +export type LocalisedRoutesProtectedEntries = { + "profile": string; +}; + +export const locales_routes_protected_map: Record<Locales, LocalisedRoutesProtectedEntries> = { + en: { + profile: "/profile", + }, + es: { + profile: "/perfil", + }, +}; +export const set_locales_routes_protected = new Set(["/profile", "perfil"]); + +export const localise_protected_route = (locale: string, key: keyof LocalisedRoutesProtectedEntries): string => { + const loc = get_locales(locale); + return locales_routes_protected_map[loc][key]; +}; +\ No newline at end of file diff --git a/app/src/routes/(market)/(profile)/+layout.svelte b/app/src/routes/(market)/(profile)/+layout.svelte @@ -0,0 +1,13 @@ +<script lang="ts"> + import Nav from "$lib/components/nav.svelte"; + import type { LayoutProps } from "./$types"; + + let { children }: LayoutProps = $props(); +</script> + +<div + class={`relative flex flex-col min-h-[100vh] w-full justify-start items-center overflow-hidden bg-morning_snow cursor-default`} +> + <Nav /> + {@render children()} +</div> diff --git a/app/static/fonts/brown-regular/Brown-Bold.woff b/app/static/fonts/brown-regular/Brown-Bold.woff Binary files differ. diff --git a/app/static/fonts/brown-regular/Brown-Bold.woff2 b/app/static/fonts/brown-regular/Brown-Bold.woff2 Binary files differ. diff --git a/app/static/fonts/brown-regular/Brown-Regular.woff b/app/static/fonts/brown-regular/Brown-Regular.woff Binary files differ. diff --git a/app/static/fonts/brown-regular/Brown-Regular.woff2 b/app/static/fonts/brown-regular/Brown-Regular.woff2 Binary files differ. diff --git a/app/static/fonts/brown-regular/styles.css b/app/static/fonts/brown-regular/styles.css @@ -0,0 +1,18 @@ +@font-face { + font-family: 'Brown'; + src: url('/fonts/brown-regular/Brown-Bold.woff2') format('woff2'), + url('/fonts/brown-regular/Brown-Bold.woff') format('woff'); + font-weight: bold; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Brown'; + src: url('/fonts/brown-regular/Brown-Regular.woff2') format('woff2'), + url('/fonts/brown-regular/Brown-Regular.woff') format('woff'); + font-weight: normal; + font-style: normal; + font-display: swap; +} + diff --git a/app/static/fonts/serif/RadrootsSerif-bold.woff b/app/static/fonts/serif/RadrootsSerif-bold.woff Binary files differ. diff --git a/app/static/fonts/serif/RadrootsSerif-regular-italic.woff b/app/static/fonts/serif/RadrootsSerif-regular-italic.woff Binary files differ. diff --git a/app/static/fonts/serif/RadrootsSerif-regular.woff b/app/static/fonts/serif/RadrootsSerif-regular.woff Binary files differ. diff --git a/app/static/fonts/serif/RadrootsSerifDisplay-bold.woff b/app/static/fonts/serif/RadrootsSerifDisplay-bold.woff Binary files differ. diff --git a/app/static/fonts/serif/RadrootsSerifDisplay-medium.woff b/app/static/fonts/serif/RadrootsSerifDisplay-medium.woff Binary files differ. diff --git a/app/static/fonts/serif/RadrootsSerifDisplay-regular.woff b/app/static/fonts/serif/RadrootsSerifDisplay-regular.woff Binary files differ. diff --git a/app/static/fonts/serif/styles.css b/app/static/fonts/serif/styles.css @@ -0,0 +1,49 @@ +@font-face { + font-family: 'RadrootsSerif'; + src: url('/fonts/serif/RadrootsSerif-regular.woff') format('woff'); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'RadrootsSerif'; + src: url('/fonts/serif/RadrootsSerif-regular-italic.woff') format('woff'); + font-weight: normal; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: 'RadrootsSerif'; + src: url('/fonts/serif/RadrootsSerif-bold.woff') format('woff'); + font-weight: bold; + font-style: normal; + font-display: swap; +} + + +@font-face { + font-family: 'RadrootsSerifDisplay'; + src: url('/fonts/serif/RadrootsSerifDisplay-regular.woff') format('woff'); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'RadrootsSerifDisplay'; + src: url('/fonts/serif/RadrootsSerifDisplay-medium.woff') format('woff'); + font-weight: 600; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'RadrootsSerifDisplay'; + src: url('/fonts/serif/RadrootsSerifDisplay-bold.woff') format('woff'); + font-weight: 700; + font-style: normal; + font-display: swap; +} +