fix: caching and reactivity
This commit is contained in:
parent
9bb48e0388
commit
588ae49f5f
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, createSignal, onMount } from "solid-js";
|
import { Component, createMemo, createSignal } from "solid-js";
|
||||||
import { useLocation } from "@solidjs/router";
|
import { useLocation } from "@solidjs/router";
|
||||||
|
|
||||||
// 导入组件以注册自定义元素
|
// 导入组件以注册自定义元素
|
||||||
|
|
@ -7,10 +7,9 @@ import { Article, Sidebar } from "./components";
|
||||||
|
|
||||||
const App: Component = () => {
|
const App: Component = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [currentPath, setCurrentPath] = createSignal("");
|
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = createSignal(false);
|
const [isSidebarOpen, setIsSidebarOpen] = createSignal(false);
|
||||||
|
|
||||||
onMount(() => {
|
const currentPath = createMemo(() => {
|
||||||
// 根据路由加载对应的 markdown 文件
|
// 根据路由加载对应的 markdown 文件
|
||||||
let path = decodeURIComponent(location.pathname);
|
let path = decodeURIComponent(location.pathname);
|
||||||
|
|
||||||
|
|
@ -18,7 +17,7 @@ const App: Component = () => {
|
||||||
if (path.endsWith("/")) path += "index";
|
if (path.endsWith("/")) path += "index";
|
||||||
if (!path.endsWith(".md")) path += ".md";
|
if (!path.endsWith(".md")) path += ".md";
|
||||||
|
|
||||||
setCurrentPath(path);
|
return path;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, createSignal, onMount, onCleanup, Show } from 'solid-js';
|
import { Component, createSignal, createEffect, onCleanup, Show, createResource } from 'solid-js';
|
||||||
import { parseMarkdown } from '../markdown';
|
import { parseMarkdown } from '../markdown';
|
||||||
import { fetchData, extractSection } from '../data-loader';
|
import { fetchData, extractSection } from '../data-loader';
|
||||||
|
|
||||||
|
|
@ -9,51 +9,45 @@ export interface ArticleProps {
|
||||||
onError?: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchArticleContent(params: { src: string; section?: string }): Promise<string> {
|
||||||
|
const text = await fetchData(params.src);
|
||||||
|
// 如果指定了 section,提取对应内容
|
||||||
|
return params.section ? extractSection(text, params.section) : text;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Article 组件
|
* Article 组件
|
||||||
* 用于将特定 src 位置的 md 文件显示为 markdown 文章
|
* 用于将特定 src 位置的 md 文件显示为 markdown 文章
|
||||||
*/
|
*/
|
||||||
export const Article: Component<ArticleProps> = (props) => {
|
export const Article: Component<ArticleProps> = (props) => {
|
||||||
const [content, setContent] = createSignal('');
|
const [content, { refetch }] = createResource(
|
||||||
const [loading, setLoading] = createSignal(true);
|
() => ({ src: props.src, section: props.section }),
|
||||||
const [error, setError] = createSignal<Error | null>(null);
|
fetchArticleContent
|
||||||
|
);
|
||||||
|
|
||||||
let articleRef: HTMLArticleElement | undefined;
|
let articleRef: HTMLArticleElement | undefined;
|
||||||
|
|
||||||
onMount(async () => {
|
createEffect(() => {
|
||||||
setLoading(true);
|
const data = content();
|
||||||
try {
|
if (data) {
|
||||||
const text = await fetchData(props.src);
|
|
||||||
// 如果指定了 section,提取对应内容
|
|
||||||
const finalContent = props.section
|
|
||||||
? extractSection(text, props.section)
|
|
||||||
: text;
|
|
||||||
setContent(finalContent);
|
|
||||||
setLoading(false);
|
|
||||||
props.onLoaded?.();
|
props.onLoaded?.();
|
||||||
} catch (err) {
|
|
||||||
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
||||||
setError(errorObj);
|
|
||||||
setLoading(false);
|
|
||||||
props.onError?.(errorObj);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
// 清理时清空内容,触发内部组件的销毁
|
// 清理时清空内容,触发内部组件的销毁
|
||||||
setContent('');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article ref={articleRef} class="prose" data-src={props.src}>
|
<article ref={articleRef} class="prose" data-src={props.src}>
|
||||||
<Show when={loading()}>
|
<Show when={content.loading}>
|
||||||
<div class="text-gray-500">加载中...</div>
|
<div class="text-gray-500">加载中...</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={error()}>
|
<Show when={content.error}>
|
||||||
<div class="text-red-500">加载失败:{error()?.message}</div>
|
<div class="text-red-500">加载失败:{content.error?.message}</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={!loading() && !error()}>
|
<Show when={!content.loading && !content.error && content()}>
|
||||||
<div innerHTML={parseMarkdown(content())} />
|
<div innerHTML={parseMarkdown(content()!)} />
|
||||||
</Show>
|
</Show>
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { customElement, noShadowDOM } from 'solid-element';
|
import { customElement, noShadowDOM } from 'solid-element';
|
||||||
import { createSignal, For, Show, createEffect, createMemo } from 'solid-js';
|
import { createSignal, For, Show, createEffect, createMemo, createResource } from 'solid-js';
|
||||||
import { parse } from 'csv-parse/browser/esm/sync';
|
import { parse } from 'csv-parse/browser/esm/sync';
|
||||||
import { marked } from '../markdown';
|
import { marked } from '../markdown';
|
||||||
|
|
||||||
|
|
@ -10,12 +10,14 @@ interface TableRow {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 全局缓存已加载的 CSV 内容
|
||||||
|
const csvCache = new Map<string, TableRow[]>();
|
||||||
|
|
||||||
customElement('md-table', { roll: false, remix: false }, (props, { element }) => {
|
customElement('md-table', { roll: false, remix: false }, (props, { element }) => {
|
||||||
noShadowDOM();
|
noShadowDOM();
|
||||||
const [rows, setRows] = createSignal<TableRow[]>([]);
|
const [rows, setRows] = createSignal<TableRow[]>([]);
|
||||||
const [activeTab, setActiveTab] = createSignal(0);
|
const [activeTab, setActiveTab] = createSignal(0);
|
||||||
const [activeGroup, setActiveGroup] = createSignal<string | null>(null);
|
const [activeGroup, setActiveGroup] = createSignal<string | null>(null);
|
||||||
const [loaded, setLoaded] = createSignal(false);
|
|
||||||
const [bodyHtml, setBodyHtml] = createSignal('');
|
const [bodyHtml, setBodyHtml] = createSignal('');
|
||||||
|
|
||||||
// 从 element 的 textContent 获取 CSV 路径
|
// 从 element 的 textContent 获取 CSV 路径
|
||||||
|
|
@ -41,34 +43,36 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) =>
|
||||||
|
|
||||||
const resolvedSrc = resolvePath(articlePath, src);
|
const resolvedSrc = resolvePath(articlePath, src);
|
||||||
|
|
||||||
// 解析 CSV 内容
|
// 加载 CSV 文件的函数
|
||||||
const parseCSV = (content: string) => {
|
const loadCSV = async (path: string): Promise<TableRow[]> => {
|
||||||
|
// 先从缓存获取
|
||||||
|
if (csvCache.has(path)) {
|
||||||
|
return csvCache.get(path)!;
|
||||||
|
}
|
||||||
|
const response = await fetch(path);
|
||||||
|
const content = await response.text();
|
||||||
const records = parse(content, {
|
const records = parse(content, {
|
||||||
columns: true,
|
columns: true,
|
||||||
comment: '#',
|
comment: '#',
|
||||||
trim: true,
|
trim: true,
|
||||||
skipEmptyLines: true
|
skipEmptyLines: true
|
||||||
});
|
});
|
||||||
setRows(records as TableRow[]);
|
const result = records as TableRow[];
|
||||||
setLoaded(true);
|
// 缓存结果
|
||||||
|
csvCache.set(path, result);
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载 CSV 文件
|
// 使用 createResource 加载 CSV,自动响应路径变化并避免重复加载
|
||||||
const loadCSV = async () => {
|
const [csvData] = createResource(() => resolvedSrc, loadCSV);
|
||||||
try {
|
|
||||||
const response = await fetch(resolvedSrc);
|
|
||||||
const content = await response.text();
|
|
||||||
parseCSV(content);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load CSV:', error);
|
|
||||||
setLoaded(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化加载
|
// 当数据加载完成后更新 rows
|
||||||
if (!loaded()) {
|
createEffect(() => {
|
||||||
loadCSV();
|
const data = csvData();
|
||||||
|
if (data) {
|
||||||
|
setRows(data);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 检测是否有 group 列
|
// 检测是否有 group 列
|
||||||
const hasGroup = createMemo(() => {
|
const hasGroup = createMemo(() => {
|
||||||
|
|
@ -119,7 +123,7 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) =>
|
||||||
// 更新 body 内容
|
// 更新 body 内容
|
||||||
const updateBodyContent = () => {
|
const updateBodyContent = () => {
|
||||||
const filtered = filteredRows();
|
const filtered = filteredRows();
|
||||||
if (loaded() && filtered.length > 0) {
|
if (!csvData.loading && filtered.length > 0) {
|
||||||
const index = Math.min(activeTab(), filtered.length - 1);
|
const index = Math.min(activeTab(), filtered.length - 1);
|
||||||
const currentRow = filtered[index];
|
const currentRow = filtered[index];
|
||||||
setBodyHtml(processBody(currentRow.body, currentRow));
|
setBodyHtml(processBody(currentRow.body, currentRow));
|
||||||
|
|
@ -207,7 +211,7 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) =>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-1 prose max-w-none">
|
<div class="p-1 prose max-w-none">
|
||||||
<Show when={loaded() && filteredRows().length > 0}>
|
<Show when={!csvData.loading && filteredRows().length > 0}>
|
||||||
<div innerHTML={bodyHtml()} />
|
<div innerHTML={bodyHtml()} />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue