refactor: Reformat code and update z-index class

Reformat md-table.tsx to use double quotes and consistent code style.
Change z-index class in PrintPreview from `z-[60]` to `z-60`.
This commit is contained in:
hyper 2026-07-09 16:03:41 +08:00
parent a20063c624
commit c071cad50a
2 changed files with 202 additions and 175 deletions

View File

@ -115,7 +115,7 @@ export function PrintPreview(props: PrintPreviewProps) {
<PltPreview pltCode={pltCode()} onClose={handleClosePltPreview} /> <PltPreview pltCode={pltCode()} onClose={handleClosePltPreview} />
} }
> >
<div class="fixed inset-0 bg-black/50 z-[60] overflow-auto"> <div class="fixed inset-0 bg-black/50 z-60 overflow-auto">
<div class="min-h-screen py-20 px-4"> <div class="min-h-screen py-20 px-4">
<PrintPreviewHeader <PrintPreviewHeader
store={store} store={store}

View File

@ -1,9 +1,19 @@
import { customElement, noShadowDOM } from 'solid-element'; import { customElement, noShadowDOM } from "solid-element";
import { createSignal, For, Show, createEffect, createMemo, createResource } from 'solid-js'; import {
import { marked } from '../markdown'; createSignal,
import { loadCSV, CSV, processVariables, isCSV } from './utils/csv-loader'; For,
import { resolvePath } from './utils/path'; Show,
import { areAllLabelsNumeric, weightedRandomIndex } from './utils/weighted-random'; createEffect,
createMemo,
createResource,
} from "solid-js";
import { marked } from "../markdown";
import { loadCSV, CSV, processVariables, isCSV } from "./utils/csv-loader";
import { resolvePath } from "./utils/path";
import {
areAllLabelsNumeric,
weightedRandomIndex,
} from "./utils/weighted-random";
export interface TableProps { export interface TableProps {
roll?: boolean; roll?: boolean;
@ -16,191 +26,208 @@ interface TableRow {
[key: string]: string; [key: string]: string;
} }
customElement('md-table', { roll: false, remix: false }, (props, { element }) => { customElement(
noShadowDOM(); "md-table",
const [rows, setRows] = createSignal<CSV<TableRow>>([] as unknown as CSV<TableRow>); { roll: false, remix: false },
const [activeTab, setActiveTab] = createSignal(0); (props, { element }) => {
const [activeGroup, setActiveGroup] = createSignal<string | null>(null); noShadowDOM();
const [bodyHtml, setBodyHtml] = createSignal(''); const [rows, setRows] = createSignal<CSV<TableRow>>(
let tabsContainer: HTMLDivElement | undefined; [] as unknown as CSV<TableRow>,
);
const [activeTab, setActiveTab] = createSignal(0);
const [activeGroup, setActiveGroup] = createSignal<string | null>(null);
const [bodyHtml, setBodyHtml] = createSignal("");
let tabsContainer: HTMLDivElement | undefined;
// 从 element 的 textContent 获取 CSV 路径或 inline CSV 数据 // 从 element 的 textContent 获取 CSV 路径或 inline CSV 数据
const rawContent = element?.textContent?.trim() || ''; const rawContent = element?.textContent?.trim() || "";
// 隐藏原始文本内容 // 隐藏原始文本内容
if (element) { if (element) {
element.textContent = ''; element.textContent = "";
}
// 从父节点 article 的 data-src 获取当前 markdown 文件完整路径
const articleEl = element?.closest('article[data-src]');
const articlePath = articleEl?.getAttribute('data-src') || '';
// 如果是 inline CSV直接使用否则解析相对路径
const contentOrPath = isCSV(rawContent) ? rawContent : resolvePath(articlePath, rawContent);
// 使用 createResource 加载 CSV自动响应路径变化并避免重复加载
const [csvData] = createResource(() => contentOrPath, loadCSV);
// 当数据加载完成后更新 rows
createEffect(() => {
const data = csvData();
if (data) {
// 将加载的数据赋值给 rowsCSV 类型已经包含 sourcePath 等属性
setRows(data as unknown as CSV<TableRow>);
} }
});
// 检测是否有 group 列 // 从父节点 article 的 data-src 获取当前 markdown 文件完整路径
const hasGroup = createMemo(() => { const articleEl = element?.closest("article[data-src]");
const allRows = rows(); const articlePath = articleEl?.getAttribute("data-src") || "";
return allRows.length > 0 && 'group' in allRows[0];
});
// 获取所有分组 // 如果是 inline CSV直接使用否则解析相对路径
const groups = createMemo(() => { const contentOrPath = isCSV(rawContent)
if (!hasGroup()) return []; ? rawContent
const allRows = rows(); : resolvePath(articlePath, rawContent);
const groupSet = new Set<string>();
for (const row of allRows) { // 使用 createResource 加载 CSV自动响应路径变化并避免重复加载
if (row.group) { const [csvData] = createResource(() => contentOrPath, loadCSV);
groupSet.add(row.group);
// 当数据加载完成后更新 rows
createEffect(() => {
const data = csvData();
if (data) {
// 将加载的数据赋值给 rowsCSV 类型已经包含 sourcePath 等属性
setRows(data as unknown as CSV<TableRow>);
} }
} });
return Array.from(groupSet).sort();
});
// 根据当前选中的分组过滤行 // 检测是否有 group 列
const filteredRows = createMemo(() => { const hasGroup = createMemo(() => {
const allRows = rows(); const allRows = rows();
const group = activeGroup(); return allRows.length > 0 && "group" in allRows[0];
if (!group) return allRows; });
return allRows.filter(row => row.group === group);
});
// 处理 body 内容中的 {{prop}} 语法并解析 markdown // 获取所有分组
const processBody = (body: string, currentRow: TableRow): string => { const groups = createMemo(() => {
// 使用 marked 解析 markdown if (!hasGroup()) return [];
return marked.parse(processVariables(body, currentRow, rows(), filteredRows(), props.remix)) as string; const allRows = rows();
}; const groupSet = new Set<string>();
for (const row of allRows) {
if (row.group) {
groupSet.add(row.group);
}
}
return Array.from(groupSet).sort();
});
// 更新 body 内容 // 根据当前选中的分组过滤行
const updateBodyContent = () => { const filteredRows = createMemo(() => {
const filtered = filteredRows(); const allRows = rows();
if (!csvData.loading && filtered.length > 0) { const group = activeGroup();
const index = Math.min(activeTab(), filtered.length - 1); if (!group) return allRows;
const currentRow = filtered[index]; return allRows.filter((row) => row.group === group);
setBodyHtml(processBody(currentRow.body, currentRow)); });
}
};
// 监听 activeTab 和 activeGroup 变化并更新内容 // 处理 body 内容中的 {{prop}} 语法并解析 markdown
createEffect(() => { const processBody = (body: string, currentRow: TableRow): string => {
activeTab(); // 使用 marked 解析 markdown
activeGroup(); return marked.parse(
updateBodyContent(); processVariables(body, currentRow, rows(), filteredRows(), props.remix),
}); ) as string;
};
// 切换分组时重置 tab 索引 // 更新 body 内容
const handleGroupChange = (group: string | null) => { const updateBodyContent = () => {
setActiveGroup(group); const filtered = filteredRows();
setActiveTab(0); if (!csvData.loading && filtered.length > 0) {
}; const index = Math.min(activeTab(), filtered.length - 1);
const currentRow = filtered[index];
setBodyHtml(processBody(currentRow.body, currentRow));
}
};
// 随机切换 tab // 监听 activeTab 和 activeGroup 变化并更新内容
const handleRoll = () => { createEffect(() => {
const filtered = filteredRows(); activeTab();
if (filtered.length === 0) return; activeGroup();
updateBodyContent();
});
// 检查所有 label 是否都是整数/整数范围格式 // 切换分组时重置 tab 索引
const labels = filtered.map(row => row.label); const handleGroupChange = (group: string | null) => {
if (areAllLabelsNumeric(labels)) { setActiveGroup(group);
// 使用加权随机 setActiveTab(0);
const randomIndex = weightedRandomIndex(labels); };
if (randomIndex !== -1) {
// 随机切换 tab
const handleRoll = () => {
const filtered = filteredRows();
if (filtered.length === 0) return;
// 检查所有 label 是否都是整数/整数范围格式
const labels = filtered.map((row) => row.label);
if (areAllLabelsNumeric(labels)) {
// 使用加权随机
const randomIndex = weightedRandomIndex(labels);
if (randomIndex !== -1) {
setActiveTab(randomIndex);
}
} else {
// 使用简单随机
const randomIndex = Math.floor(Math.random() * filtered.length);
setActiveTab(randomIndex); setActiveTab(randomIndex);
} }
} else {
// 使用简单随机
const randomIndex = Math.floor(Math.random() * filtered.length);
setActiveTab(randomIndex);
}
// 滚动到可视区域 // 滚动到可视区域
setTimeout(() => { setTimeout(() => {
const activeButton = tabsContainer?.querySelector('.border-blue-600'); const activeButton = tabsContainer?.querySelector(".border-blue-600");
activeButton?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); activeButton?.scrollIntoView({
}, 50); behavior: "smooth",
}; block: "nearest",
inline: "nearest",
});
}, 50);
};
return ( return (
<div class="ttrpg-table"> <div class="ttrpg-table">
<div class="flex items-center gap-2 border-b border-gray-200"> <div class="flex items-center gap-2 border-b border-gray-200">
<div class="flex flex-col overflow-x-auto"> <div class="flex flex-col overflow-x-auto">
{/* 分组 tabs */} {/* 分组 tabs */}
<Show when={hasGroup()}> <Show when={hasGroup()}>
<div class="flex gap-2 border-b border-gray-100 pb-2"> <div class="flex gap-2 border-b border-gray-100 pb-2">
<button <button
onClick={() => handleGroupChange(null)} onClick={() => handleGroupChange(null)}
class={`font-medium transition-colors ${ class={`font-medium transition-colors ${
activeGroup() === null activeGroup() === null
? 'text-blue-600 border-b-2 border-blue-600' ? "text-blue-600 border-b-2 border-blue-600"
: 'text-gray-500 hover:text-gray-700' : "text-gray-500 hover:text-gray-700"
}`} }`}
> >
</button> </button>
<For each={groups()}> <For each={groups()}>
{(group) => ( {(group) => (
<button <button
onClick={() => handleGroupChange(group)} onClick={() => handleGroupChange(group)}
class={`font-medium transition-colors ${ class={`font-medium transition-colors ${
activeGroup() === group activeGroup() === group
? 'text-blue-600 border-b-2 border-blue-600' ? "text-blue-600 border-b-2 border-blue-600"
: 'text-gray-500 hover:text-gray-700' : "text-gray-500 hover:text-gray-700"
}`} }`}
> >
{group} {group}
</button> </button>
)} )}
</For> </For>
</div> </div>
</Show>
{/* 内容 tabs */}
<div class="flex items-center gap-2">
<Show when={props.roll}>
<button
onClick={handleRoll}
class="text-gray-500 hover:text-gray-700 flex-shrink-0 cursor-pointer"
title="随机切换"
>
🎲
</button>
</Show> </Show>
<div ref={tabsContainer} class="flex gap-1 overflow-x-auto flex-1 min-w-0"> {/* 内容 tabs */}
<For each={filteredRows()}> <div class="flex items-center gap-2">
{(row, index) => ( <Show when={props.roll}>
<button <button
onClick={() => setActiveTab(index())} onClick={handleRoll}
class={`font-medium transition-colors flex-shrink-0 min-w-[1.6em] cursor-pointer ${ class="text-gray-500 hover:text-gray-700 shrink-0 cursor-pointer"
activeTab() === index() title="随机切换"
? 'text-blue-600 border-b-2 border-blue-600' >
: 'text-gray-500 hover:text-gray-700' 🎲
}`} </button>
> </Show>
{row.label} <div
</button> ref={tabsContainer}
)} class="flex gap-1 overflow-x-auto flex-1 min-w-0"
</For> >
<For each={filteredRows()}>
{(row, index) => (
<button
onClick={() => setActiveTab(index())}
class={`font-medium transition-colors shrink-0 min-w-[1.6em] cursor-pointer ${
activeTab() === index()
? "text-blue-600 border-b-2 border-blue-600"
: "text-gray-500 hover:text-gray-700"
}`}
>
{row.label}
</button>
)}
</For>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="p-4 prose max-w-none">
<Show when={!csvData.loading && filteredRows().length > 0}>
<div innerHTML={bodyHtml()} />
</Show>
</div>
</div> </div>
<div class="p-4 prose max-w-none"> );
<Show when={!csvData.loading && filteredRows().length > 0}> },
<div innerHTML={bodyHtml()} /> );
</Show>
</div>
</div>
);
});