refactor: replace stat system with variable system
Replaces the specialized `stat` system with a more general-purpose
variable system. This includes:
- Renaming `ReactiveStatManager` to `ReactiveVariableManager`
- Changing template syntax from `${key}` to `{{$key}}`
- Replacing `StatsView` with `VariableView` to show declared, plain,
and tag-typed variables
- Updating command `/stat` to `/set`
- Refactoring the reactivity engine to support variable declarations
and tag modifiers via `csv role=declare` blocks
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
* Journal completions — client-side loader for /__COMPLETIONS.json
|
||||
*
|
||||
* In CLI mode, fetches the pre-computed index. In dev/browser mode, falls
|
||||
* back to scanning the in-memory file index for dice expressions and headings.
|
||||
* back to scanning the in-memory file index for dice expressions, headings,
|
||||
* and declare blocks.
|
||||
*
|
||||
* The fetch runs eagerly on module import. Call `useJournalCompletions()`
|
||||
* from any Solid component to reactively read the state.
|
||||
@@ -15,13 +16,6 @@ import {
|
||||
getPathsByExtension,
|
||||
getIndexedData,
|
||||
} from "../../data-loader/file-index";
|
||||
import {
|
||||
parseStatYaml,
|
||||
parseStatCsv,
|
||||
parseTemplateCsv,
|
||||
parseStatModifiers,
|
||||
} from "../../cli/completions/stat-parser";
|
||||
import type { StatDef, StatTemplate } from "../../cli/completions/stat-parser";
|
||||
import {
|
||||
FENCED_BLOCK_RE,
|
||||
parseBlockAttrs,
|
||||
@@ -29,8 +23,11 @@ import {
|
||||
import {
|
||||
scanDirectives,
|
||||
} from "../../cli/completions/directive-scanner";
|
||||
import { parseDeclareCsv } from "./declare-parser";
|
||||
import type { VarDeclaration, TagModifier } from "./declare-parser";
|
||||
import { initReactivity } from "./var-reactivity";
|
||||
|
||||
export type { StatDef, StatTemplate };
|
||||
export type { VarDeclaration, TagModifier };
|
||||
|
||||
// ------------------- Types (mirrors CLI) -------------------
|
||||
|
||||
@@ -60,8 +57,8 @@ export interface JournalCompletions {
|
||||
dice: DiceCompletion[];
|
||||
links: LinkCompletion[];
|
||||
sparkTables: SparkTableCompletion[];
|
||||
stats: StatDef[];
|
||||
statTemplates: StatTemplate[];
|
||||
declarations: VarDeclaration[];
|
||||
tagModifiers: TagModifier[];
|
||||
}
|
||||
|
||||
export type CompletionsState =
|
||||
@@ -87,9 +84,11 @@ async function tryServer(): Promise<JournalCompletions | null> {
|
||||
dice: Array.isArray(data.dice) ? data.dice : [],
|
||||
links: Array.isArray(data.links) ? data.links : [],
|
||||
sparkTables: Array.isArray(data.sparkTables) ? data.sparkTables : [],
|
||||
stats: Array.isArray(data.stats) ? data.stats : [],
|
||||
statTemplates: Array.isArray(data.statTemplates)
|
||||
? data.statTemplates
|
||||
declarations: Array.isArray(data.declarations)
|
||||
? data.declarations
|
||||
: [],
|
||||
tagModifiers: Array.isArray(data.tagModifiers)
|
||||
? data.tagModifiers
|
||||
: [],
|
||||
};
|
||||
} catch {
|
||||
@@ -104,8 +103,8 @@ async function scanClientSide(): Promise<JournalCompletions> {
|
||||
const dice: DiceCompletion[] = [];
|
||||
const links: LinkCompletion[] = [];
|
||||
const sparkTables: SparkTableCompletion[] = [];
|
||||
const stats: StatDef[] = [];
|
||||
const statTemplates: StatTemplate[] = [];
|
||||
const declarations: VarDeclaration[] = [];
|
||||
const tagModifiers: TagModifier[] = [];
|
||||
|
||||
// Build a temporary index for resolving CSV paths
|
||||
const tempIndex: Record<string, string> = {};
|
||||
@@ -135,7 +134,7 @@ async function scanClientSide(): Promise<JournalCompletions> {
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Unified block scanning (stats) ----
|
||||
// ---- Unified block scanning (declare) ----
|
||||
FENCED_BLOCK_RE.lastIndex = 0;
|
||||
let blockMatch: RegExpExecArray | null;
|
||||
while ((blockMatch = FENCED_BLOCK_RE.exec(content)) !== null) {
|
||||
@@ -143,26 +142,15 @@ async function scanClientSide(): Promise<JournalCompletions> {
|
||||
const attrs = parseBlockAttrs(infoString);
|
||||
attrs.lang = attrs.lang || lang;
|
||||
|
||||
if (attrs.role === "stat") {
|
||||
if (attrs.lang === "yaml" || attrs.lang === "yml") {
|
||||
stats.push(...parseStatYaml(body, filePath));
|
||||
} else if (attrs.lang === "csv") {
|
||||
stats.push(...parseStatCsv(body, filePath));
|
||||
if (attrs.role === "declare") {
|
||||
try {
|
||||
const result = parseDeclareCsv(body, filePath);
|
||||
declarations.push(...result.variables);
|
||||
tagModifiers.push(...result.tagModifiers);
|
||||
} catch (e) {
|
||||
console.warn(`[completions] ${filePath}: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.role === "stat-template") {
|
||||
const name = attrs.id || `_tpl_${filePath}_${stats.length}`;
|
||||
statTemplates.push(parseTemplateCsv(body, filePath, name));
|
||||
}
|
||||
|
||||
if (attrs.role === "stat-modifiers") {
|
||||
const id = attrs.id || `_mod_${filePath}_${stats.length}`;
|
||||
const result = parseStatModifiers(body, filePath, id, "player", attrs.extra["label"]);
|
||||
stats.push(result.statDef);
|
||||
stats.push(...result.modifierDefs);
|
||||
statTemplates.push(result.template);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Directive scanning (dice + spark tables) ----
|
||||
@@ -172,7 +160,7 @@ async function scanClientSide(): Promise<JournalCompletions> {
|
||||
sparkTables.push(...directiveResult.sparkTables);
|
||||
}
|
||||
|
||||
return { dice, links, sparkTables, stats, statTemplates };
|
||||
return { dice, links, sparkTables, declarations, tagModifiers };
|
||||
}
|
||||
|
||||
// ------------------- Init (runs eagerly at import time) -------------------
|
||||
@@ -183,6 +171,7 @@ const _initPromise: Promise<void> = (async () => {
|
||||
const serverData = await tryServer();
|
||||
if (serverData) {
|
||||
setCompletionsState({ status: "loaded", data: serverData });
|
||||
try { initReactivity({ declarations: serverData.declarations, tagModifiers: serverData.tagModifiers }); } catch (e) { console.warn("[completions] reactivity init error:", e); }
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -191,6 +180,7 @@ const _initPromise: Promise<void> = (async () => {
|
||||
const data = await scanClientSide();
|
||||
if (data.dice.length > 0 || data.links.length > 0) {
|
||||
setCompletionsState({ status: "loaded", data });
|
||||
try { initReactivity({ declarations: data.declarations, tagModifiers: data.tagModifiers }); } catch (e) { console.warn("[completions] reactivity init error:", e); }
|
||||
} else {
|
||||
setCompletionsState({ status: "empty" });
|
||||
}
|
||||
@@ -216,8 +206,6 @@ export function ensureCompletions(): Promise<void> {
|
||||
let _invalidated = false;
|
||||
export function invalidateCompletions(): void {
|
||||
_invalidated = true;
|
||||
// On next import (page reload), the module will re-init.
|
||||
// For a runtime invalidation, you could call init again.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,8 +226,8 @@ export function useJournalCompletions(): {
|
||||
dice: [],
|
||||
links: [],
|
||||
sparkTables: [],
|
||||
stats: [],
|
||||
statTemplates: [],
|
||||
declarations: [],
|
||||
tagModifiers: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user