diff --git a/web/src/app/(dashboard)/lexicons/[id]/lexicon-detail.tsx b/web/src/app/(dashboard)/lexicons/[id]/lexicon-detail.tsx --- a/web/src/app/(dashboard)/lexicons/[id]/lexicon-detail.tsx +++ b/web/src/app/(dashboard)/lexicons/[id]/lexicon-detail.tsx @@ -12,16 +12,28 @@ getLexicon, uploadLexicon, } from "@/lib/api"; import type { LexiconDetail } from "@/types/lexicons"; -import { procedureScript, queryScript } from "@/lib/lua-templates"; +import { + indexHookScript, + procedureScript, + queryScript, +} from "@/lib/lua-templates"; import { useLuaCompletions } from "@/hooks/use-lua-completions"; import { SiteHeader } from "@/components/site-header"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; export default function LexiconDetailPage() { const pathname = usePathname(); - const id = decodeURIComponent(pathname.split("/").filter(Boolean).pop() ?? ""); + const id = decodeURIComponent( + pathname.split("/").filter(Boolean).pop() ?? "", + ); const { getToken } = useAuth(); const router = useRouter(); const [lexicon, setLexicon] = useState(null); @@ -34,6 +46,9 @@ const [jsonText, setJsonText] = useState(""); const [luaText, setLuaText] = useState(""); const [originalJson, setOriginalJson] = useState(""); const [originalLua, setOriginalLua] = useState(""); + const [hookText, setHookText] = useState(""); + const [originalHook, setOriginalHook] = useState(""); + const [showHookEditor, setShowHookEditor] = useState(false); const { luaCompletions, collections } = useLuaCompletions(jsonText); const load = useCallback(() => { @@ -60,6 +75,9 @@ } else { setLuaText(lex.script ?? ""); setOriginalLua(lex.script ?? ""); } + setHookText(lex.on_index_script ?? ""); + setOriginalHook(lex.on_index_script ?? ""); + setShowHookEditor(!!lex.on_index_script); }) .catch((e) => setError(e instanceof Error ? e.message : String(e))); }, [getToken, id]); @@ -68,7 +86,10 @@ useEffect(() => { load(); }, [load]); - const isDirty = jsonText !== originalJson || luaText !== originalLua; + const isDirty = + jsonText !== originalJson || + luaText !== originalLua || + hookText !== originalHook; async function handleSave() { if (!lexicon) return; @@ -80,6 +101,7 @@ await uploadLexicon(getToken, { lexicon_json: lexiconJson, backfill: lexicon.backfill, script: luaText || undefined, + on_index_script: hookText || undefined, }); load(); } catch (e: unknown) { @@ -132,6 +154,8 @@ const showLua = lexicon.has_script || lexicon.lexicon_type === "query" || lexicon.lexicon_type === "procedure"; + const isRecord = lexicon.lexicon_type === "record"; + const showHook = isRecord && (showHookEditor || !!lexicon.on_index_script); return (
@@ -203,8 +227,10 @@ className="flex-1 min-h-0 px-4 md:px-6" jsonValue={jsonText} onJsonChange={isNetwork ? undefined : setJsonText} jsonReadOnly={isNetwork} - luaValue={showLua ? luaText : undefined} - onLuaChange={showLua ? setLuaText : undefined} + luaValue={showHook ? hookText : showLua ? luaText : undefined} + onLuaChange={ + showHook ? setHookText : showLua ? setLuaText : undefined + } luaCompletions={showLua ? luaCompletions : undefined} collections={showLua ? collections : undefined} /> @@ -219,9 +245,45 @@ > {deleting ? "Deleting..." : "Delete Lexicon"} - +
+ {isRecord && !showHook && ( + + + + + + + { + "An Index Hook is a Lua script that runs automatically whenever a record in this collection is created, updated, or deleted on the network." + } + + + + )} + {isRecord && showHook && ( + + )} + + +
diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -110,6 +110,7 @@ backfill?: boolean target_collection?: string action?: string script?: string + on_index_script?: string } ) { return apiFetch<{ id: string; revision: number }>("/admin/lexicons", getToken, { diff --git a/web/src/lib/lua-templates.ts b/web/src/lib/lua-templates.ts --- a/web/src/lib/lua-templates.ts +++ b/web/src/lib/lua-templates.ts @@ -29,6 +29,19 @@ end ` } +export function indexHookScript(): string { + return `function handle() +\tif action == "delete" then +\t\t-- record was deleted +\t\tlog("deleted " .. uri) +\telse +\t\t-- record was created or updated +\t\tlog(action .. " " .. uri) +\tend +end +` +} + export function queryScript(collection: string): string { const target = collection || "COLLECTION" return `collection = "${target}" diff --git a/web/src/types/lexicons.ts b/web/src/types/lexicons.ts --- a/web/src/types/lexicons.ts +++ b/web/src/types/lexicons.ts @@ -6,6 +6,7 @@ backfill: boolean action: string | null target_collection: string | null has_script: boolean + has_on_index_script: boolean source: string authority_did: string | null last_fetched_at: string | null @@ -16,4 +17,5 @@ export interface LexiconDetail extends LexiconSummary { lexicon_json: Record script: string | null + on_index_script: string | null }