| |
| export const DEFAULT_CONTENT_URL_PARAM = 'content'; |
|
|
| |
| export const DEFAULT_DEMO_URL_PARAM = 'demo'; |
|
|
| export function readContentUrlParam(paramName: string = DEFAULT_CONTENT_URL_PARAM): string | null { |
| try { |
| const u = new URL(window.location.href); |
| const v = u.searchParams.get(paramName); |
| return v != null && v.length > 0 ? v : null; |
| } catch { |
| return null; |
| } |
| } |
|
|
| export function replaceContentUrlParam( |
| value: string | null, |
| paramName: string = DEFAULT_CONTENT_URL_PARAM, |
| logLabel?: string |
| ): void { |
| replaceUrlParam(value, paramName, logLabel ?? 'contentUrl'); |
| } |
|
|
| export function readDemoUrlParam(paramName: string = DEFAULT_DEMO_URL_PARAM): string | null { |
| try { |
| const u = new URL(window.location.href); |
| const v = u.searchParams.get(paramName); |
| return v != null && v.length > 0 ? v : null; |
| } catch { |
| return null; |
| } |
| } |
|
|
| function replaceUrlParam(value: string | null, paramName: string, logLabel: string): void { |
| try { |
| const u = new URL(window.location.href); |
| if (value) { |
| u.searchParams.set(paramName, value); |
| } else { |
| u.searchParams.delete(paramName); |
| } |
| window.history.replaceState(null, '', u.toString()); |
| } catch (e: unknown) { |
| console.warn(`[${logLabel}] URL sync failed:`, e); |
| } |
| } |
|
|
| export function replaceDemoUrlParam( |
| value: string | null, |
| paramName: string = DEFAULT_DEMO_URL_PARAM, |
| logLabel?: string |
| ): void { |
| replaceUrlParam(value, paramName, logLabel ?? 'contentUrl:demo'); |
| } |
|
|
| export type RunContentUrlHydrateOptions<T> = { |
| readRaw: () => string | null; |
| fetchEntry: (raw: string) => Promise<T | undefined>; |
| |
| isValid?: (entry: T) => boolean; |
| |
| apply: (entry: T, rawContentKey: string) => void | Promise<void>; |
| onMissing: () => void | Promise<void>; |
| onApplyError?: (error: unknown) => void | Promise<void>; |
| }; |
|
|
| export async function runContentUrlHydrate<T>(options: RunContentUrlHydrateOptions<T>): Promise<void> { |
| const raw = options.readRaw(); |
| if (!raw) return; |
| const entry = await options.fetchEntry(raw); |
| const ok = |
| entry != null && (options.isValid ? options.isValid(entry) : true); |
| if (!ok) { |
| await Promise.resolve(options.onMissing()); |
| return; |
| } |
| try { |
| await options.apply(entry, raw); |
| } catch (e: unknown) { |
| if (options.onApplyError) { |
| await Promise.resolve(options.onApplyError(e)); |
| } else { |
| throw e; |
| } |
| } |
| } |
|
|