web_lib

Common web application libraries
git clone https://radroots.dev/git/web_lib.git
Log | Files | Refs | LICENSE

commit 4ca507394f753cdb2622c35ce55992690040eba0
parent cc5143c11cfdc8525c45f214fe78f29857b728d2
Author: triesap <triesap@radroots.dev>
Date:   Mon,  3 Nov 2025 07:26:05 +0000

apps-lib: add svelte ui primitives for layout/icon/input, integrate icu i18n and typed fetch, implement nostr auth with streamlined idb, tighten theme state with reset and lazy init, extend types and exports, drop path aliases and an unused dependency, and include an env template

Diffstat:
Aapps-lib/.env.example | 4++++
Mapps-lib/package.json | 1-
Aapps-lib/src/lib/components/flex.svelte | 3+++
Aapps-lib/src/lib/components/glyph.svelte | 20++++++++++++++++++++
Aapps-lib/src/lib/components/input.svelte | 28++++++++++++++++++++++++++++
Mapps-lib/src/lib/index.ts | 7+++++++
Mapps-lib/src/lib/stores/theme.ts | 8++++----
Aapps-lib/src/lib/types/components.ts | 28++++++++++++++++++++++++++++
Mapps-lib/src/lib/types/lib.ts | 9+++++++--
Aapps-lib/src/lib/utils/fetch/lib.ts | 8++++++++
Aapps-lib/src/lib/utils/i18n.ts | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapps-lib/src/lib/utils/idb/lib.ts | 3+--
Mapps-lib/src/lib/utils/lib.ts | 7+++++--
Aapps-lib/src/lib/utils/nostr/lib.ts | 43+++++++++++++++++++++++++++++++++++++++++++
Mapps-lib/svelte.config.js | 3---
Mapps-lib/vite.config.ts | 5-----
16 files changed, 210 insertions(+), 19 deletions(-)

diff --git a/apps-lib/.env.example b/apps-lib/.env.example @@ -0,0 +1,4 @@ +VITE_PUBLIC_IDB_NAME= +VITE_PUBLIC_NDK_CACHE_NAME= +VITE_PUBLIC_NDK_CLIENT_NAME= +VITE_PUBLIC_RADROOTS_MARKET_RELAY_URL= diff --git a/apps-lib/package.json b/apps-lib/package.json @@ -51,7 +51,6 @@ "@radroots/locales": "*", "@radroots/utils": "*", "@radroots/utils-nostr": "*", - "@radroots/radroots-common-bindings": "*", "@nostr-dev-kit/ndk": "2.14.33", "@nostr-dev-kit/ndk-cache-dexie": "2.6.34", "@nostr-dev-kit/ndk-svelte": "2.4.38", diff --git a/apps-lib/src/lib/components/flex.svelte b/apps-lib/src/lib/components/flex.svelte @@ -0,0 +1,3 @@ +<div class={`flex flex-fill text-transparent bg-transparent`}> + <p class={`font-sans text-[8px]`}>{`~`}</p> +</div> diff --git a/apps-lib/src/lib/components/glyph.svelte b/apps-lib/src/lib/components/glyph.svelte @@ -0,0 +1,20 @@ +<script lang="ts"> + import type { IGlyphI } from "$lib/types/components"; + import { fmt_cl } from "$lib/utils/lib"; + + const styles = { + xs: `text-[16px]`, + sm: `text-[20px]`, + md: `text-[26px]`, + lg: `text-[32px]`, + xl: `text-[48px]`, + "2xl": "text-[64px]", + }; + + let { basis }: { basis: IGlyphI } = $props(); +</script> + +<i + class={`ph${!basis?.weight || basis?.weight === `regular` ? `` : `-${basis?.weight}`} ph-${basis.key} ${fmt_cl(basis.classes)} ${basis.size ? styles[basis.size] : ""}`} +> +</i> diff --git a/apps-lib/src/lib/components/input.svelte b/apps-lib/src/lib/components/input.svelte @@ -0,0 +1,28 @@ +<script lang="ts"> + import type { ILibInput } from "$lib/types/components"; + + let { val = $bindable(``), basis }: { val?: string; basis: ILibInput } = + $props(); +</script> + +<input + bind:value={val} + type={basis.type || "text"} + inputmode={basis.type === "numeric" ? "numeric" : undefined} + pattern={basis.pattern || basis.type === "numeric" ? "[0-9]*" : undefined} + disabled={basis.disabled} + class={basis.classes} + placeholder={basis.placeholder || ``} + onkeydown={async (ev) => { + if (basis?.callback_keydown) + await basis?.callback_keydown({ + key: ev.key, + is_submit: ev.key === `Enter`, + el: ev.currentTarget, + }); + }} + oninput={async (ev) => { + if (basis?.callback_input) + await basis?.callback_input(ev.currentTarget); + }} +/> diff --git a/apps-lib/src/lib/index.ts b/apps-lib/src/lib/index.ts @@ -1,6 +1,13 @@ +export { default as Flex } from "./components/flex.svelte" +export { default as Glyph } from "./components/glyph.svelte" +export { default as Input } from "./components/input.svelte" export * from "./stores/ndk.js" export * from "./stores/theme.js" +export * from "./types/components.js" export * from "./types/lib.js" export * from "./types/ndk.js" +export * from "./utils/fetch/lib.js" +export * from "./utils/i18n.js" export * from "./utils/idb/lib.js" export * from "./utils/lib.js" +export * from "./utils/nostr/lib.js" diff --git a/apps-lib/src/lib/stores/theme.ts b/apps-lib/src/lib/stores/theme.ts @@ -3,11 +3,12 @@ import { get_store } from "$lib/utils/lib"; import { type CallbackPromiseGeneric } from "@radroots/utils"; import { writable } from "svelte/store"; -export const theme_mode = writable<ThemeMode>(`light`); -export const theme_key = writable<string>(`default`); +export const theme_mode = writable<ThemeMode>(); +export const theme_key = writable<string>(); +export const theme_reset = writable<boolean>(false); export const theme_toggle = async (callback: CallbackPromiseGeneric<ThemeMode>): Promise<void> => { const mode = get_store(theme_mode) === `light` ? `dark` : `light`; theme_mode.set(mode); await callback(mode); -}; -\ No newline at end of file +}; diff --git a/apps-lib/src/lib/types/components.ts b/apps-lib/src/lib/types/components.ts @@ -0,0 +1,27 @@ +import type { CallbackPromiseArgs } from "$lib"; +import type { HTMLInputTypeAttribute } from "svelte/elements"; + +export type IconWeight = `regular` | `bold` | `fill`; + +export type IGlyphI = { + classes?: string; + key: string; + weight?: IconWeight; + size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl"; +}; + +export type ILibInputKeydown = { + key: string; + is_submit: boolean; + el: HTMLInputElement; +}; + +export type ILibInput = { + classes?: string; + type?: HTMLInputTypeAttribute; + pattern?: string; + placeholder?: string; + disabled?: boolean; + callback_keydown?: CallbackPromiseArgs<ILibInputKeydown>; + callback_input?: CallbackPromiseArgs<HTMLInputElement>; +}; +\ No newline at end of file diff --git a/apps-lib/src/lib/types/lib.ts b/apps-lib/src/lib/types/lib.ts @@ -1 +1,7 @@ -export type ThemeMode = 'light' | 'dark'; -\ No newline at end of file +import type { Writable } from "svelte/store"; + +export type ThemeMode = 'light' | 'dark'; +export type StoreWritable<S> = S extends Writable<infer T> ? T : never; + +export type CallbackPromise = () => Promise<void> +export type CallbackPromiseArgs<T> = (args: T) => Promise<void> diff --git a/apps-lib/src/lib/utils/fetch/lib.ts b/apps-lib/src/lib/utils/fetch/lib.ts @@ -0,0 +1,7 @@ +export type HttpFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>; + +export async function fetch_json<T>(fetch_fn: HttpFetch, url: string): Promise<T> { + const res = await fetch_fn(url); + if (!res.ok) throw new Error(url); + return res.json() as Promise<T>; +} +\ No newline at end of file diff --git a/apps-lib/src/lib/utils/i18n.ts b/apps-lib/src/lib/utils/i18n.ts @@ -0,0 +1,51 @@ +import { type Loader } from '@sveltekit-i18n/base'; +import type { Config as ConfigIcu, Parser as ParserIcu } from "@sveltekit-i18n/parser-icu"; +import parser_icu from "@sveltekit-i18n/parser-icu"; +import i18n, { type Config } from 'sveltekit-i18n'; + +type LanguageConfig = { + default?: string; + value?: string; +}; + +const lib_config: Config<LanguageConfig> = { + initLocale: `en`, + fallbackLocale: `en`, + translations: {}, + loaders: [], +}; + +const lib_i18n = new i18n(lib_config); +export type I18nTranslateFunction = typeof lib_i18n.t; +export type I18nTranslateLocale = typeof lib_i18n.locale; + +export const i18n_conf = <T extends string>(opts: { + default_locale: T; + translations: Record<T, any>; + loaders: Loader.LoaderModule[] +}) => { + const { default_locale: initLocale, translations, loaders } = opts; + const config: Config<LanguageConfig> = { + initLocale, + fallbackLocale: initLocale, + translations, + loaders, + }; + return new i18n(config); +}; + +export const i18n_conf_icu = <T extends string>(opts: { + default_locale: T; + translations: Record<T, any>; + loaders: Loader.LoaderModule[] +}): i18n<ParserIcu.Params<LanguageConfig>> => { + const { default_locale: initLocale, translations, loaders } = opts; + const config: ConfigIcu<LanguageConfig> = { + initLocale, + fallbackLocale: initLocale, + translations, + parser: parser_icu(), + loaders, + }; + return new i18n(config); +}; +\ No newline at end of file diff --git a/apps-lib/src/lib/utils/idb/lib.ts b/apps-lib/src/lib/utils/idb/lib.ts @@ -1,10 +1,9 @@ import { browser } from '$app/environment'; -import { fmt_id } from '../lib'; +import { fmt_id } from '$lib'; const IDB_NAME = import.meta.env.VITE_PUBLIC_IDB_NAME; if (!IDB_NAME) throw new Error('VITE_PUBLIC_IDB_NAME is required'); - let _kv: Keyva | null = null; export function get_idb(): Keyva { if (!browser) throw new Error('IndexedDB not available on server'); diff --git a/apps-lib/src/lib/utils/lib.ts b/apps-lib/src/lib/utils/lib.ts @@ -4,6 +4,8 @@ import { get } from "svelte/store"; export const get_store = get; +export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); + export const trim_slashes = (path: string): string => path.replace(/^\/+|\/+$/g, ''); @@ -38,4 +40,5 @@ export const get_system_theme = (): ThemeMode => { export const theme_set = (theme_key: string, color_mode: ThemeMode): void => { document.documentElement.setAttribute("data-theme", `${theme_key}_${color_mode}`); -}; -\ No newline at end of file +}; +export const fmt_cl = (classes?: string): string => `${classes || ``}`; +\ No newline at end of file diff --git a/apps-lib/src/lib/utils/nostr/lib.ts b/apps-lib/src/lib/utils/nostr/lib.ts @@ -0,0 +1,43 @@ +import { get_store } from "$lib"; +import { ndk, ndk_user } from "$lib/stores/ndk"; +import { NDKNip07Signer, NDKPrivateKeySigner, NDKUser } from "@nostr-dev-kit/ndk"; + +export type NostrLoginOptionNip05 = { + nip07: true; +}; + +export type NostrLoginOptionNostrKey = { + nostr_key: Uint8Array | string; +}; + +export type NostrLoginOptions = + | NostrLoginOptionNip05 + | NostrLoginOptionNostrKey + + +export const nostr_logout = async (): Promise<void> => { + const $ndk = get_store(ndk); + $ndk.activeUser = undefined; + document.cookie = "radroots_npub="; + console.log(`logged out (nostr)`) + +}; + +export const nostr_login = async (opts: NostrLoginOptions): Promise<void> => { + const $ndk = get_store(ndk); + let user: NDKUser | null = null; + if (`nip07` in opts) { + const signer = new NDKNip07Signer(); + $ndk.signer = signer; + user = await $ndk.signer.user(); + user.ndk = $ndk; + } else if (`nostr_key` in opts) { + const signer = new NDKPrivateKeySigner(opts.nostr_key); + $ndk.signer = signer; + user = await $ndk.signer.user(); + user.ndk = $ndk; + } + if (!user) return; + ndk_user.set(user); + $ndk.activeUser = user; +}; diff --git a/apps-lib/svelte.config.js b/apps-lib/svelte.config.js @@ -6,9 +6,6 @@ const config = { preprocess: vitePreprocess(), kit: { adapter: adapter(), - alias: { - $root: './src/lib/index.js', - } }, }; diff --git a/apps-lib/vite.config.ts b/apps-lib/vite.config.ts @@ -2,10 +2,5 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ - resolve: { - alias: { - '$root': '/src/lib/index.js', - } - }, plugins: [sveltekit()] });