refactor: improve spark table lookup and error reporting
Replace the heuristic-based slug parsing with a more robust combined-slug lookup to correctly handle page names containing hyphens. Added `scanSparkTables` to provide better error messages when a table is not found.
This commit is contained in:
parent
9a312f76ad
commit
e801ac9b5f
|
|
@ -16,7 +16,11 @@ import { z } from "zod";
|
|||
import { For } from "solid-js";
|
||||
import { registerMessageType } from "../registry";
|
||||
import { rollFormula } from "../../md-commander/hooks";
|
||||
import { findSparkTable, rollSparkTable } from "../../utils/spark-table";
|
||||
import {
|
||||
findSparkTableByCombinedSlug,
|
||||
rollSparkTable,
|
||||
scanSparkTables,
|
||||
} from "../../utils/spark-table";
|
||||
import { getIndexedData } from "../../../data-loader/file-index";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -72,28 +76,28 @@ export async function resolveSparkPayload(raw: {
|
|||
key: string;
|
||||
filePath: string;
|
||||
}): Promise<SparkPayload> {
|
||||
// key is "pageName-columnSlug". The page name is the last segment of
|
||||
// filePath (e.g. "01-dry-dock" from "/ayi-games/.../01-dry-dock").
|
||||
// Strip it from the key to recover the column slug.
|
||||
const pathParts = raw.filePath.replace(/^\//, "").split("/");
|
||||
const pageName = pathParts[pathParts.length - 1] || raw.filePath;
|
||||
const columnSlug =
|
||||
raw.key.length > pageName.length + 1 && raw.key.startsWith(pageName + "-")
|
||||
? raw.key.slice(pageName.length + 1)
|
||||
: raw.key;
|
||||
|
||||
const filePath = `/${raw.filePath.replace(/^\//, "")}`;
|
||||
const mdPath = `/${raw.filePath.replace(/^\//, "")}.md`;
|
||||
const pageName =
|
||||
raw.filePath.replace(/^\//, "").split("/").filter(Boolean).pop() ||
|
||||
raw.filePath;
|
||||
|
||||
let content: string;
|
||||
try {
|
||||
content = await getIndexedData(filePath);
|
||||
content = await getIndexedData(mdPath);
|
||||
} catch {
|
||||
throw new Error(`Failed to load file: "${filePath}"`);
|
||||
throw new Error(`Failed to load file: "${mdPath}"`);
|
||||
}
|
||||
|
||||
const meta = findSparkTable(content, columnSlug);
|
||||
const meta = findSparkTableByCombinedSlug(content, raw.key, pageName);
|
||||
if (!meta) {
|
||||
throw new Error(`Spark table "${columnSlug}" not found in "${filePath}"`);
|
||||
const available = scanSparkTables(content);
|
||||
const names =
|
||||
available.length > 0
|
||||
? available.map((t) => `${pageName}-${t.slug}`).join(", ")
|
||||
: "(none)";
|
||||
throw new Error(
|
||||
`Spark table "${raw.key}" not found in "${mdPath}". Available: ${names}`,
|
||||
);
|
||||
}
|
||||
|
||||
const sparkResult = rollSparkTable(meta);
|
||||
|
|
|
|||
|
|
@ -148,14 +148,40 @@ export function findSparkTable(
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a spark table by its combined slug (`pageName-columnSlug`).
|
||||
* Iterates every spark table in the content, constructs the combined slug,
|
||||
* and returns the first match. Avoids the need to guess where the page
|
||||
* name ends and the column slug begins (page names may contain `-`).
|
||||
*/
|
||||
export function findSparkTableByCombinedSlug(
|
||||
markdown: string,
|
||||
combinedSlug: string,
|
||||
pageName: string,
|
||||
): SparkTableMeta | null {
|
||||
const tables = parseMarkdownTables(markdown);
|
||||
for (const table of tables) {
|
||||
if (!isSparkTable(table)) continue;
|
||||
const colSlug = sparkTableSlug(table);
|
||||
const candidate = `${pageName}-${colSlug}`;
|
||||
if (candidate === combinedSlug) {
|
||||
return {
|
||||
notation: table.headers[0],
|
||||
slug: colSlug,
|
||||
dataHeaders: table.headers.slice(1),
|
||||
rows: table.rows,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Scan all spark tables in a markdown file and return their metadata */
|
||||
export function scanSparkTables(
|
||||
markdown: string,
|
||||
): Omit<SparkTableMeta, "rows">[] {
|
||||
const tables = parseMarkdownTables(markdown);
|
||||
return tables
|
||||
.filter(isSparkTable)
|
||||
.map((table) => ({
|
||||
return tables.filter(isSparkTable).map((table) => ({
|
||||
notation: table.headers[0],
|
||||
slug: sparkTableSlug(table),
|
||||
dataHeaders: table.headers.slice(1),
|
||||
|
|
@ -170,9 +196,7 @@ export function scanSparkTables(
|
|||
* Roll a spark table: for each data column, roll the dice formula and look
|
||||
* up the corresponding row value.
|
||||
*/
|
||||
export function rollSparkTable(
|
||||
meta: SparkTableMeta,
|
||||
): SparkTableResult {
|
||||
export function rollSparkTable(meta: SparkTableMeta): SparkTableResult {
|
||||
const slugger = new Slugger();
|
||||
const columns: SparkTableColumn[] = [];
|
||||
|
||||
|
|
@ -183,11 +207,17 @@ export function rollSparkTable(
|
|||
const roll = rollFormula(meta.notation);
|
||||
const rolledValue = roll.result.total;
|
||||
|
||||
// Find the row matching the rolled value (1-based dice result)
|
||||
// Find the row matching the rolled value — compare as integers,
|
||||
// falling back to string comparison.
|
||||
let value = `(no row for ${rolledValue})`;
|
||||
for (const row of meta.rows) {
|
||||
const diceCell = row[0] ?? "";
|
||||
if (String(rolledValue) === diceCell.trim()) {
|
||||
const cellNum = parseInt(diceCell.trim(), 10);
|
||||
const match =
|
||||
!isNaN(cellNum) && rolledValue === cellNum
|
||||
? true
|
||||
: String(rolledValue) === diceCell.trim();
|
||||
if (match) {
|
||||
value = row[colIdx + 1] ?? "";
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue