diff --git a/src/components/md-commander/index.tsx b/src/components/md-commander/index.tsx index 96fa1a4..9a70789 100644 --- a/src/components/md-commander/index.tsx +++ b/src/components/md-commander/index.tsx @@ -14,7 +14,7 @@ customElement( { placeholder: "", class: "", height: "", commandTemplates: "" }, (props, { element }) => { noShadowDOM(); - const { articlePath, rawSrc } = loadElementSrc(element as any); + const { articlePath, rawSrc } = loadElementSrc(element); // 初始化命令 const commands = initializeCommands(props.commands); diff --git a/src/components/md-yarn-spinner.tsx b/src/components/md-yarn-spinner.tsx index 4f262ac..acf8298 100644 --- a/src/components/md-yarn-spinner.tsx +++ b/src/components/md-yarn-spinner.tsx @@ -1,19 +1,9 @@ import { customElement, noShadowDOM } from 'solid-element'; -import { createSignal, createMemo, createResource, createEffect, For, Show } from 'solid-js'; +import {createSignal, createResource, For, Show, createEffect} from 'solid-js'; import { parseYarn, compile, YarnRunner } from '../yarn-spinner'; import type { RunnerOptions } from '../yarn-spinner/runtime/runner'; -import type { RuntimeResult, TextResult, OptionsResult, CommandResult } from '../yarn-spinner/runtime/results'; - -export interface YarnSpinnerProps extends RunnerOptions { - startNode?: string; - multipleFiles?: boolean; -} - -interface DialogueEntry { - id: number; - result: RuntimeResult; - selectedOptionIndex?: number; -} +import type { RuntimeResult, TextResult, OptionsResult } from '../yarn-spinner/runtime/results'; +import {loadElementSrc, resolvePath} from "./utils/path"; // 缓存已加载的 yarn 文件内容 const yarnCache = new Map(); @@ -35,105 +25,52 @@ async function loadYarnFiles(paths: string[]): Promise { return contents.join('\n'); } -customElement('md-yarn-spinner', { - startNode: undefined as string | undefined, - multipleFiles: false +customElement('md-yarn-spinner', { + startAt: "start", }, (props, { element }) => { noShadowDOM(); - const [dialogueHistory, setDialogueHistory] = createSignal([]); + const [dialogueHistory, setDialogueHistory] = createSignal([]); const [currentOptions, setCurrentOptions] = createSignal(null); const [isEnded, setIsEnded] = createSignal(false); - const [entryIdCounter, setEntryIdCounter] = createSignal(0); const [runnerInstance, setRunnerInstance] = createSignal(null); // 获取文件路径 - const rawSrc = element?.textContent?.trim() || ''; - const articleEl = element?.closest('article[data-src]'); - const articlePath = articleEl?.getAttribute('data-src') || ''; - - // 隐藏原始文本内容 - if (element) { - element.textContent = ''; - } - - // 解析路径列表(支持多文件) - const yarnPaths = createMemo(() => { - const src = rawSrc; - if (!src) return []; - if (props.multipleFiles) { - return src.split('\n').map((s: string) => s.trim()).filter(Boolean); - } - return [src]; - }); - - // 解析相对路径 - const resolvedPaths = createMemo(() => { - return yarnPaths().map((path: string) => { - if (path.startsWith('/')) return path; - if (path.startsWith('http://') || path.startsWith('https://')) return path; - - const baseParts = articlePath.split('/').filter(Boolean); - if (baseParts.length > 0 && !articlePath.endsWith('/')) { - baseParts.pop(); - } - - const relativeParts = path.split('/'); - for (const part of relativeParts) { - if (part === '.' || part === '') continue; - else if (part === '..') baseParts.pop(); - else baseParts.push(part); - } - - return '/' + baseParts.join('/'); - }); - }); + const {articlePath, rawSrc} = loadElementSrc(element); + const yarnPaths = (rawSrc || '').split(',') + .map((s: string) => resolvePath(articlePath, s.trim())) + .filter(Boolean); // 加载 yarn 内容 - const [yarnContent] = createResource( - () => resolvedPaths(), - async (paths) => { - if (paths.length === 0) return ''; - if (paths.length === 1) { - return await loadYarnFile(paths[0]); - } - return await loadYarnFiles(paths); - } - ); + const [yarnContent] = createResource(() => yarnPaths, loadYarnFiles); // 创建 runner - const createRunner = (content: string, startNode?: string) => { + const createRunner = () => { + const content = yarnContent(); if (!content) return null; try { const ast = parseYarn(content); const program = compile(ast); - const runnerOptions: RunnerOptions = { - startAt: startNode || 'Start', - variables: (props as YarnSpinnerProps).variables, - functions: (props as YarnSpinnerProps).functions, - handleCommand: (props as YarnSpinnerProps).handleCommand, - onStoryEnd: (props as YarnSpinnerProps).onStoryEnd, - }; - - return new YarnRunner(program, runnerOptions); + return new YarnRunner(program, props); } catch (error) { console.error('Failed to initialize YarnRunner:', error); return null; } }; - // 初始化 runner + function advance(index?: number){ + const runner = runnerInstance(); + if(!runner) return; + runner.advance(index); + processRunnerOutput(runner); + } + createEffect(() => { - const content = yarnContent(); - if (content && !runnerInstance()) { - const startNode = (props as YarnSpinnerProps).startNode; - const runner = createRunner(content, startNode); - if (runner) { - setRunnerInstance(runner); - } - } + if(!yarnContent()) return; + setRunnerInstance(createRunner()); + processRunnerOutput(runnerInstance()!); }); // 处理 runner 输出 @@ -141,66 +78,13 @@ customElement('md-yarn-spinner', { const result = runner.currentResult; if (!result) return; - const newEntry: DialogueEntry = { - id: entryIdCounter(), - result, - }; - - setDialogueHistory(prev => [...prev, newEntry]); - setEntryIdCounter(prev => prev + 1); - - if (result.type === 'options') { - setCurrentOptions(result as OptionsResult); - } else if (result.type === 'text' && (result as TextResult).isDialogueEnd) { - // 检查是否是对话结束 - const lastOptions = runner.history.slice().reverse().find(r => r.type === 'options') as OptionsResult | undefined; - if (lastOptions && runner.currentResult?.type !== 'options') { - // 之前有选项但没有选择,等待用户输入 - setCurrentOptions(lastOptions); - return; - } - } - - // 检查故事是否结束 - if ((result as TextResult).isDialogueEnd && result.type !== 'options') { - setIsEnded(true); + if(result.type === 'options'){ + setCurrentOptions(result); + }else{ setCurrentOptions(null); } - }; - - // 监听 runner 变化 - createEffect(() => { - const runner = runnerInstance(); - if (runner && runner.currentResult) { - processRunnerOutput(runner); - } - }); - - // 选择选项 - const selectOption = (index: number) => { - const runner = runnerInstance(); - if (!runner || !currentOptions()) return; - // 更新历史记录,标记已选择的选项 - setDialogueHistory(prev => { - const lastOptionsIndex = prev.slice().reverse().findIndex(e => e.result.type === 'options'); - if (lastOptionsIndex === -1) return prev; - - const actualIndex = prev.length - 1 - lastOptionsIndex; - return prev.map((entry, i) => - i === actualIndex - ? { ...entry, selectedOptionIndex: index } - : entry - ); - }); - - setCurrentOptions(null); - runner.advance(index); - - // 处理选择后的输出 - setTimeout(() => { - processRunnerOutput(runner); - }, 0); + setDialogueHistory([...runner.history]); }; // 重新开始 @@ -208,21 +92,12 @@ customElement('md-yarn-spinner', { setDialogueHistory([]); setCurrentOptions(null); setIsEnded(false); - setEntryIdCounter(0); - - // 重新创建 runner - const content = yarnContent(); - if (!content) return; - - const runner = createRunner(content, (props as YarnSpinnerProps).startNode); - if (runner) { - setRunnerInstance(runner); - } + setRunnerInstance(createRunner()); + processRunnerOutput(runnerInstance()!); }; // 渲染对话历史项 - const renderEntry = (entry: DialogueEntry) => { - const result = entry.result; + const renderEntry = (result: RuntimeResult) => { if (result.type === 'text') { const textResult = result as TextResult; @@ -244,39 +119,11 @@ customElement('md-yarn-spinner', { ); } - if (result.type === 'options') { - const optionsResult = result as OptionsResult; - return ( -
- -
- → {optionsResult.options[entry.selectedOptionIndex!].text} -
-
- -
- - {(option, index) => ( - - )} - -
-
-
- ); - } - return null; }; return ( -
+
{/* 对话历史 */}
@@ -286,7 +133,7 @@ customElement('md-yarn-spinner', {
加载对话中...
- {entry => renderEntry(entry)} + {renderEntry}
@@ -297,7 +144,7 @@ customElement('md-yarn-spinner', { {(option, index) => ( +
); diff --git a/src/components/utils/path.ts b/src/components/utils/path.ts index 1a887a7..6cc739d 100644 --- a/src/components/utils/path.ts +++ b/src/components/utils/path.ts @@ -6,6 +6,7 @@ * @returns 解析后的路径 */ export function resolvePath(base: string, relative: string): string { + if(!relative) return relative; if (relative.startsWith('/')) { return relative; } @@ -39,7 +40,8 @@ export function resolvePath(base: string, relative: string): string { } -export function loadElementSrc(element?: { textContent: string, closest: (arg0: string) => HTMLElement }){ +// export function loadElementSrc(element?: { textContent: string, closest: (arg0: string) => HTMLElement }){ +export function loadElementSrc(element?: any | HTMLElement){ const rawSrc = element?.textContent; if(element) element.textContent = "";