File size: 2,864 Bytes
494c9e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/** 三页 Cached history 共用的 `?content=` 参数名 */
export const DEFAULT_CONTENT_URL_PARAM = 'content';

/** LLM Causal Flow(gen_attribute)打包 demo:`?demo=` 为文件名(不含 .json) */
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>;
    /** 缺省:仅判断 entry 非 undefined/null */
    isValid?: (entry: T) => boolean;
    /** 第二参为 URL 中的原始 `content` 值(IndexedDB 条目的短哈希键) */
    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;
        }
    }
}