feat(ui): replace unicode icons with solid-icons
Swap emoji and unicode glyphs for Tabler components so icons inherit currentColor and font-size and render consistently across platforms. - add src/components/doc-icons.ts to resolve doc-entry frontmatter icon keys to components; frontmatter now stores semantic keys - replace the align-*.png assets with TbOutlineAlign* icons - render RevealManager action SVGs as components instead of injected innerHTML strings - carry completion icons via an optional CompletionItem.icon field
This commit is contained in:
+8
-3
@@ -7,6 +7,11 @@ import {
|
||||
createSignal,
|
||||
} from "solid-js";
|
||||
import { useLocation } from "@solidjs/router";
|
||||
import {
|
||||
TbOutlineMenu2,
|
||||
TbOutlineClipboardList,
|
||||
TbOutlineHelpCircle,
|
||||
} from "solid-icons/tb";
|
||||
import { useJournalStream } from "./components/stores/journalStream";
|
||||
|
||||
// 导入组件以注册自定义元素
|
||||
@@ -99,7 +104,7 @@ const App: Component = () => {
|
||||
class="md:hidden text-gray-600 hover:text-gray-900 p-1 rounded hover:bg-gray-100"
|
||||
title="目录"
|
||||
>
|
||||
☰
|
||||
<TbOutlineMenu2 class="inline-block align-[-0.125em]" />
|
||||
</button>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Tabletop Toolkit</h1>
|
||||
<div class="flex-1" />
|
||||
@@ -122,14 +127,14 @@ const App: Component = () => {
|
||||
"bg-red-500": stream.connectionStatus === "error",
|
||||
}}
|
||||
/>
|
||||
📋
|
||||
<TbOutlineClipboardList size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsDocOpen(true)}
|
||||
class="w-8 h-8 rounded-full border border-gray-300 text-gray-500 hover:text-gray-700 hover:border-gray-400 flex items-center justify-center text-sm font-medium"
|
||||
title="指令文档"
|
||||
>
|
||||
?
|
||||
<TbOutlineHelpCircle size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -6,8 +6,17 @@ import {
|
||||
onCleanup,
|
||||
onMount,
|
||||
} from "solid-js";
|
||||
import { Dynamic } from "solid-js/web";
|
||||
import {
|
||||
TbOutlineBook2,
|
||||
TbOutlineCheck,
|
||||
TbOutlineChevronDown,
|
||||
TbOutlineClipboardList,
|
||||
TbOutlineX,
|
||||
} from "solid-icons/tb";
|
||||
import { docEntries, type DocEntry } from "./doc-data";
|
||||
import { journalDocEntries, type JournalDocEntry } from "./journal-doc-data";
|
||||
import { docIcon } from "./doc-icons";
|
||||
import { parseMarkdown } from "../markdown";
|
||||
|
||||
type DocSet = "directives" | "journal";
|
||||
@@ -77,7 +86,7 @@ const DocDialog: Component<DocDialogProps> = (props) => {
|
||||
class="text-lg font-bold text-gray-900 hover:text-blue-600 flex items-center gap-1"
|
||||
>
|
||||
{docSetLabel()}
|
||||
<span class="text-gray-400 text-sm">▾</span>
|
||||
<TbOutlineChevronDown class="text-gray-400 text-sm" />
|
||||
</button>
|
||||
<Show when={dropdownOpen()}>
|
||||
<div class="absolute top-full left-0 mt-1 w-52 bg-white border border-gray-200 rounded shadow-lg z-60 py-1">
|
||||
@@ -89,10 +98,10 @@ const DocDialog: Component<DocDialogProps> = (props) => {
|
||||
: "text-gray-700"
|
||||
}`}
|
||||
>
|
||||
<span>📖</span>
|
||||
<TbOutlineBook2 />
|
||||
<span>指令组件文档</span>
|
||||
<Show when={docSet() === "directives"}>
|
||||
<span class="ml-auto text-blue-500">✓</span>
|
||||
<TbOutlineCheck class="ml-auto text-blue-500" />
|
||||
</Show>
|
||||
</button>
|
||||
<button
|
||||
@@ -103,10 +112,10 @@ const DocDialog: Component<DocDialogProps> = (props) => {
|
||||
: "text-gray-700"
|
||||
}`}
|
||||
>
|
||||
<span>📋</span>
|
||||
<TbOutlineClipboardList />
|
||||
<span>Journal 服务器文档</span>
|
||||
<Show when={docSet() === "journal"}>
|
||||
<span class="ml-auto text-blue-500">✓</span>
|
||||
<TbOutlineCheck class="ml-auto text-blue-500" />
|
||||
</Show>
|
||||
</button>
|
||||
</div>
|
||||
@@ -117,7 +126,7 @@ const DocDialog: Component<DocDialogProps> = (props) => {
|
||||
class="text-gray-400 hover:text-gray-600 text-xl leading-none p-1"
|
||||
title="关闭"
|
||||
>
|
||||
✕
|
||||
<TbOutlineX />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -133,7 +142,10 @@ const DocDialog: Component<DocDialogProps> = (props) => {
|
||||
: "text-gray-700 hover:bg-gray-100"
|
||||
}`}
|
||||
>
|
||||
<span class="mr-2">{entry.icon}</span>
|
||||
<Dynamic
|
||||
component={docIcon(entry.icon)}
|
||||
class="mr-2 inline-block align-[-0.125em]"
|
||||
/>
|
||||
<Show
|
||||
when={docSet() === "directives"}
|
||||
fallback={<span class="text-xs">{entry.title}</span>}
|
||||
@@ -164,7 +176,11 @@ const DocContent: Component<{ entry: AnyEntry }> = (props) => {
|
||||
return (
|
||||
<div class="max-w-none">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">
|
||||
{e.icon} {e.title}
|
||||
<Dynamic
|
||||
component={docIcon(e.icon)}
|
||||
class="inline-block align-[-0.125em]"
|
||||
/>{" "}
|
||||
{e.title}
|
||||
</h2>
|
||||
|
||||
<article
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { Component, createMemo, createSignal, Show } from "solid-js";
|
||||
import { useLocation } from "@solidjs/router";
|
||||
import {
|
||||
TbOutlineFolderOpen,
|
||||
TbOutlineFolder,
|
||||
TbOutlineFile,
|
||||
TbOutlineChevronRight,
|
||||
} from "solid-icons/tb";
|
||||
import { type FileNode, type TocNode } from "../data-loader";
|
||||
import { useNavigateWithParams } from "./useNavigateWithParams";
|
||||
import { useScrollContainer } from "../App";
|
||||
@@ -61,10 +67,14 @@ export const FileTreeNode: Component<{
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Show when={isDir}>
|
||||
<span class="mr-1 text-gray-400">{isExpanded() ? "📂" : "📁"}</span>
|
||||
{isExpanded() ? (
|
||||
<TbOutlineFolderOpen class="mr-1 text-gray-400" />
|
||||
) : (
|
||||
<TbOutlineFolder class="mr-1 text-gray-400" />
|
||||
)}
|
||||
</Show>
|
||||
<Show when={!isDir}>
|
||||
<span class="mr-1 text-gray-400">📄</span>
|
||||
<TbOutlineFile class="mr-1 text-gray-400" />
|
||||
</Show>
|
||||
<span class="text-sm truncate">{props.node.name}</span>
|
||||
</a>
|
||||
@@ -151,7 +161,7 @@ export const HeadingNode: Component<{
|
||||
<span
|
||||
class={`${hasChildren ? "" : "invisible"} ${isExpanded() ? "rotate-90" : ""} mt-1 inline-block transition-transform `}
|
||||
>
|
||||
▶
|
||||
<TbOutlineChevronRight class="inline-block align-[-0.125em]" />
|
||||
</span>
|
||||
</span>
|
||||
<a
|
||||
|
||||
@@ -15,8 +15,10 @@ import {
|
||||
onCleanup,
|
||||
Show,
|
||||
} from "solid-js";
|
||||
import { Portal } from "solid-js/web";
|
||||
import { Portal, Dynamic } from "solid-js/web";
|
||||
import { useLocation } from "@solidjs/router";
|
||||
import type { IconTypes } from "solid-icons";
|
||||
import { TbOutlineLink, TbOutlineSparkles } from "solid-icons/tb";
|
||||
import { useArticleDom } from "./Article";
|
||||
import { journalStreamState } from "./stores/journalStream";
|
||||
import { useJournalCompletions } from "./journal/completions";
|
||||
@@ -27,25 +29,6 @@ import {
|
||||
normalizePath,
|
||||
} from "./stores/reveal";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SVG icons
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LINK_SVG =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" ' +
|
||||
'viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" ' +
|
||||
'stroke-linecap="round" stroke-linejoin="round">' +
|
||||
'<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>' +
|
||||
'<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>' +
|
||||
"</svg>";
|
||||
|
||||
const SPARK_SVG =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" ' +
|
||||
'viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" ' +
|
||||
'stroke-linecap="round" stroke-linejoin="round">' +
|
||||
'<path d="M12 2l1.5 6.5L18 7l-4.5 4.5L16 16l-4-2.5L8 16l1.5-4.5L5 7l4.5-.5z"/>' +
|
||||
"</svg>";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Floating button state
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -54,7 +37,7 @@ interface FloatingButton {
|
||||
rect: DOMRect;
|
||||
action: () => void;
|
||||
title: string;
|
||||
svg: string;
|
||||
icon: IconTypes;
|
||||
color: string;
|
||||
}
|
||||
|
||||
@@ -130,7 +113,7 @@ export const RevealManager: Component = () => {
|
||||
text: combinedSlug,
|
||||
}),
|
||||
title: "Roll spark table",
|
||||
svg: SPARK_SVG,
|
||||
icon: TbOutlineSparkles,
|
||||
color: "text-purple-400 hover:text-purple-600 hover:bg-purple-50",
|
||||
});
|
||||
return;
|
||||
@@ -150,7 +133,7 @@ export const RevealManager: Component = () => {
|
||||
text: `${normalized}#${text}`,
|
||||
}),
|
||||
title: "Send /link to stream",
|
||||
svg: LINK_SVG,
|
||||
icon: TbOutlineLink,
|
||||
color: "text-blue-500 hover:bg-blue-50",
|
||||
});
|
||||
}
|
||||
@@ -183,14 +166,15 @@ export const RevealManager: Component = () => {
|
||||
left: `${btn().rect.left - 20}px`,
|
||||
}}
|
||||
title={btn().title}
|
||||
innerHTML={btn().svg}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
btn().action();
|
||||
}}
|
||||
onMouseEnter={() => clearTimeout(hideTimer)}
|
||||
onMouseLeave={hide}
|
||||
/>
|
||||
>
|
||||
<Dynamic component={btn().icon} size={12} />
|
||||
</button>
|
||||
</Portal>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, createMemo, createSignal, onMount, Show } from "solid-js";
|
||||
import { generateToc, type FileNode, type TocNode } from "../data-loader";
|
||||
import { useLocation } from "@solidjs/router";
|
||||
import { TbOutlineFolderOpen, TbOutlineX } from "solid-icons/tb";
|
||||
import { FileTreeNode, HeadingNode } from "./FileTree";
|
||||
import { isPathRevealed } from "./stores/reveal";
|
||||
|
||||
@@ -57,7 +58,7 @@ const SidebarContent: Component<SidebarContentProps> = (props) => {
|
||||
class="text-gray-400 hover:text-gray-600 p-1 rounded hover:bg-gray-100"
|
||||
title="Content Source"
|
||||
>
|
||||
📂
|
||||
<TbOutlineFolderOpen class="inline-block align-[-0.125em]" />
|
||||
</button>
|
||||
<Show when={!props.isDesktop}>
|
||||
<button
|
||||
@@ -65,7 +66,7 @@ const SidebarContent: Component<SidebarContentProps> = (props) => {
|
||||
class="text-gray-500 hover:text-gray-700"
|
||||
title="关闭"
|
||||
>
|
||||
✕
|
||||
<TbOutlineX class="inline-block align-[-0.125em]" />
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Icon registry for built-in doc entries.
|
||||
*
|
||||
* Doc-entry frontmatter `icon:` values are semantic keys (e.g. `dice`), not
|
||||
* glyphs — YAML can't reference a component. This maps each key to a Tabler
|
||||
* icon component; unknown keys fall back to a generic file icon.
|
||||
*/
|
||||
|
||||
import type { IconTypes } from "solid-icons";
|
||||
import {
|
||||
TbOutlineCards,
|
||||
TbOutlineDice5,
|
||||
TbOutlineFile,
|
||||
TbOutlineFileText,
|
||||
TbOutlineGitBranch,
|
||||
TbOutlineHash,
|
||||
TbOutlineLink,
|
||||
TbOutlineMapPin,
|
||||
TbOutlinePhoto,
|
||||
TbOutlineSparkles,
|
||||
TbOutlineSquare,
|
||||
TbOutlineTable,
|
||||
TbOutlineTheater,
|
||||
TbOutlineTypography,
|
||||
} from "solid-icons/tb";
|
||||
|
||||
const DOC_ICONS: Record<string, IconTypes> = {
|
||||
dice: TbOutlineDice5,
|
||||
table: TbOutlineTable,
|
||||
link: TbOutlineLink,
|
||||
pins: TbOutlineMapPin,
|
||||
font: TbOutlineTypography,
|
||||
background: TbOutlinePhoto,
|
||||
border: TbOutlineSquare,
|
||||
embed: TbOutlineFileText,
|
||||
deck: TbOutlineCards,
|
||||
yarn: TbOutlineGitBranch,
|
||||
gm: TbOutlineTheater,
|
||||
player: TbOutlineDice5,
|
||||
roll: TbOutlineDice5,
|
||||
set: TbOutlineHash,
|
||||
spark: TbOutlineSparkles,
|
||||
};
|
||||
|
||||
/** Resolve a frontmatter icon key to a Tabler icon component. */
|
||||
export function docIcon(key: string): IconTypes {
|
||||
return DOC_ICONS[key] ?? TbOutlineFile;
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { Component, For, Show, createEffect, onMount, onCleanup } from "solid-js";
|
||||
import { Dynamic } from "solid-js/web";
|
||||
import type { CompletionItem } from "./command-parser";
|
||||
import type { CompletionsState } from "./completions";
|
||||
|
||||
@@ -102,6 +103,11 @@ export const CompletionsDropdown: Component<CompletionsDropdownProps> = (
|
||||
{item.kind === "command" ? "cmd" : "val"}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={item.icon}>
|
||||
{(icon) => (
|
||||
<Dynamic component={icon()} size={14} class="shrink-0" />
|
||||
)}
|
||||
</Show>
|
||||
<span class="font-mono truncate flex-1">{item.label}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* or creating a session is a separate step that reuses that connection.
|
||||
*/
|
||||
import { Component, createSignal, For, onMount, Show } from "solid-js";
|
||||
import { TbOutlineTrash } from "solid-icons/tb";
|
||||
import {
|
||||
connectBroker,
|
||||
createSession,
|
||||
@@ -167,7 +168,7 @@ export const ConnectDialog: Component = () => {
|
||||
class="shrink-0 px-2 py-1.5 text-[10px] text-gray-300 hover:text-red-500 disabled:opacity-50"
|
||||
title="删除会话"
|
||||
>
|
||||
🗑
|
||||
<TbOutlineTrash />
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* CreateSessionDialog — modal for naming a new session
|
||||
*/
|
||||
import { Component, createSignal, onMount } from "solid-js";
|
||||
import { TbOutlineX } from "solid-icons/tb";
|
||||
import { createSession, joinSession } from "../stores/journalStream";
|
||||
|
||||
interface CreateSessionDialogProps {
|
||||
@@ -48,7 +49,7 @@ export const CreateSessionDialog: Component<CreateSessionDialogProps> = (
|
||||
onClick={props.onClose}
|
||||
class="text-gray-400 hover:text-gray-600 text-sm"
|
||||
>
|
||||
✕
|
||||
<TbOutlineX />
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">输入新会话的名称。</p>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* log), so the warning spells out that it cannot be undone.
|
||||
*/
|
||||
import { Component, createSignal, Show } from "solid-js";
|
||||
import { TbOutlineX } from "solid-icons/tb";
|
||||
import { deleteSession } from "../stores/journalStream";
|
||||
|
||||
interface DeleteSessionDialogProps {
|
||||
@@ -49,7 +50,7 @@ export const DeleteSessionDialog: Component<DeleteSessionDialogProps> = (
|
||||
onClick={props.onClose}
|
||||
class="text-gray-400 hover:text-gray-600 text-sm"
|
||||
>
|
||||
✕
|
||||
<TbOutlineX />
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-600">
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* the journal input area. Toggled by clicking the truncated error text.
|
||||
*/
|
||||
import { Component, Show } from "solid-js";
|
||||
import { TbOutlineX } from "solid-icons/tb";
|
||||
|
||||
interface ErrorPopupProps {
|
||||
message: string | null;
|
||||
@@ -21,7 +22,7 @@ export const ErrorPopup: Component<ErrorPopupProps> = (props) => {
|
||||
class="text-red-400 hover:text-red-600 text-xs leading-none ml-2 shrink-0"
|
||||
title="关闭"
|
||||
>
|
||||
✕
|
||||
<TbOutlineX />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* InviteDialog — input player name, copy invite link
|
||||
*/
|
||||
import { Component, createSignal } from "solid-js";
|
||||
import { TbOutlineX } from "solid-icons/tb";
|
||||
import { useJournalStream } from "../stores/journalStream";
|
||||
|
||||
interface InviteDialogProps {
|
||||
@@ -51,7 +52,7 @@ export const InviteDialog: Component<InviteDialogProps> = (props) => {
|
||||
onClick={props.onClose}
|
||||
class="text-gray-400 hover:text-gray-600 text-sm"
|
||||
>
|
||||
✕
|
||||
<TbOutlineX />
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
* JournalHeader — status dot, player name, session dropdown, leave & close
|
||||
*/
|
||||
import { Component, Show } from "solid-js";
|
||||
import {
|
||||
TbOutlineArrowLeft,
|
||||
TbOutlineLayoutSidebarRightCollapse,
|
||||
} from "solid-icons/tb";
|
||||
import { useJournalStream, leaveSession } from "../stores/journalStream";
|
||||
import { SessionDropdown } from "./SessionDropdown";
|
||||
|
||||
@@ -70,7 +74,7 @@ export const JournalHeader: Component<JournalHeaderProps> = (props) => {
|
||||
class="text-xs text-gray-400 hover:text-blue-500 px-1"
|
||||
title="返回会话列表"
|
||||
>
|
||||
←
|
||||
<TbOutlineArrowLeft />
|
||||
</button>
|
||||
</Show>
|
||||
<button
|
||||
@@ -78,7 +82,7 @@ export const JournalHeader: Component<JournalHeaderProps> = (props) => {
|
||||
class="text-gray-400 hover:text-gray-600 text-sm px-1"
|
||||
title="隐藏面板"
|
||||
>
|
||||
▸
|
||||
<TbOutlineLayoutSidebarRightCollapse />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
* SessionDropdown — session switcher for the journal header (GM only)
|
||||
*/
|
||||
import { Component, createSignal, For, Show } from "solid-js";
|
||||
import {
|
||||
TbOutlineCheck,
|
||||
TbOutlineChevronDown,
|
||||
TbOutlineTrash,
|
||||
} from "solid-icons/tb";
|
||||
import {
|
||||
sessions,
|
||||
joinSession,
|
||||
@@ -46,7 +51,7 @@ export const SessionDropdown: Component<SessionDropdownProps> = (props) => {
|
||||
class="text-[10px] text-gray-400 hover:text-gray-600 truncate leading-tight cursor-pointer text-left"
|
||||
>
|
||||
{stream.sessionName || stream.sessionId}{" "}
|
||||
<span class="text-gray-300">▾</span>
|
||||
<TbOutlineChevronDown class="text-gray-300 inline-block align-[-0.125em]" />
|
||||
</button>
|
||||
<Show when={dropdownOpen()}>
|
||||
<div
|
||||
@@ -71,7 +76,7 @@ export const SessionDropdown: Component<SessionDropdownProps> = (props) => {
|
||||
>
|
||||
<span class="truncate flex-1">{meta.name || id}</span>
|
||||
<Show when={id === stream.sessionId}>
|
||||
<span class="text-blue-500 shrink-0">✓</span>
|
||||
<TbOutlineCheck class="text-blue-500 shrink-0" />
|
||||
</Show>
|
||||
</button>
|
||||
<button
|
||||
@@ -83,7 +88,7 @@ export const SessionDropdown: Component<SessionDropdownProps> = (props) => {
|
||||
class="shrink-0 px-2 py-1 text-[10px] text-gray-300 hover:text-red-500"
|
||||
title="删除会话"
|
||||
>
|
||||
🗑
|
||||
<TbOutlineTrash />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { Component, Show, createMemo } from "solid-js";
|
||||
import { TbOutlineArrowBackUp } from "solid-icons/tb";
|
||||
import type { StreamMessage as StreamMessageType } from "./registry";
|
||||
import { getMessageType } from "./registry";
|
||||
import { revertLatest, useJournalStream } from "../stores/journalStream";
|
||||
@@ -47,7 +48,7 @@ export const StreamMessageCard: Component<{
|
||||
class="text-xs text-gray-300 hover:text-red-500 ml-auto px-0.5 leading-none"
|
||||
title="撤回此消息"
|
||||
>
|
||||
×
|
||||
<TbOutlineArrowBackUp />
|
||||
</button>
|
||||
</Show>
|
||||
<span
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import type { CompletionItem } from "./command-parser";
|
||||
import type { JournalCompletions } from "./completions";
|
||||
import { TbOutlineDice5, TbOutlineSparkles } from "solid-icons/tb";
|
||||
|
||||
export interface CompletionsContext {
|
||||
data: JournalCompletions;
|
||||
@@ -63,7 +64,8 @@ export function buildCompletions(
|
||||
|
||||
for (const d of diceMatches) {
|
||||
items.push({
|
||||
label: `🎲 ${d.notation}`,
|
||||
label: d.notation,
|
||||
icon: TbOutlineDice5,
|
||||
kind: "value",
|
||||
insertText: "/roll " + d.notation,
|
||||
});
|
||||
@@ -71,7 +73,8 @@ export function buildCompletions(
|
||||
|
||||
for (const s of sparkMatches) {
|
||||
items.push({
|
||||
label: `✨ ${s.slug} (${s.notation})`,
|
||||
label: `${s.slug} (${s.notation})`,
|
||||
icon: TbOutlineSparkles,
|
||||
kind: "value",
|
||||
insertText: `/roll ${s.slug}`,
|
||||
});
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
* module and keep JournalInput small.
|
||||
*/
|
||||
|
||||
import type { IconTypes } from "solid-icons";
|
||||
|
||||
export interface CompletionItem {
|
||||
label: string;
|
||||
kind: "command" | "value" | "no-results";
|
||||
insertText: string;
|
||||
/** Optional icon rendered before the label in the completions dropdown. */
|
||||
icon?: IconTypes;
|
||||
}
|
||||
|
||||
export interface ParsedInput {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { TbOutlineCircleCheck, TbOutlineHandStop } from "solid-icons/tb";
|
||||
import { registerMessageType } from "../registry";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -25,7 +26,7 @@ registerMessageType<IntentPayload>({
|
||||
render: (p) => (
|
||||
<div class="space-y-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-lg">🙋</span>
|
||||
<TbOutlineHandStop class="text-lg" />
|
||||
<span class="text-sm font-medium text-yellow-700">意图</span>
|
||||
</div>
|
||||
<p class="text-sm">{p.description}</p>
|
||||
@@ -58,7 +59,7 @@ registerMessageType<ResolutionPayload>({
|
||||
render: (p) => (
|
||||
<div class="space-y-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-lg">✅</span>
|
||||
<TbOutlineCircleCheck class="text-lg" />
|
||||
<span class="text-sm font-medium text-green-700">裁决</span>
|
||||
<span class="text-xs text-gray-400">re: {p.intentId}</span>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import { Component } from "solid-js";
|
||||
import { TbOutlineFileText } from "solid-icons/tb";
|
||||
import { useNavigateWithParams } from "../../useNavigateWithParams";
|
||||
import { z } from "zod";
|
||||
import { registerMessageType } from "../registry";
|
||||
@@ -69,7 +70,7 @@ const RevealLink: Component<LinkPayload> = (p) => {
|
||||
|
||||
return (
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-lg">📄</span>
|
||||
<TbOutlineFileText class="text-lg" />
|
||||
<div>
|
||||
<a
|
||||
href={target()}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { TbOutlineArrowRight, TbOutlineDice5 } from "solid-icons/tb";
|
||||
import { registerMessageType } from "../registry";
|
||||
import { rollFormula } from "../../dice";
|
||||
|
||||
@@ -55,9 +56,10 @@ registerMessageType<RollPayload>({
|
||||
return (
|
||||
<div class="space-y-0.5">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-lg">🎲</span>
|
||||
<TbOutlineDice5 class="text-lg" />
|
||||
<span class="font-mono text-sm">
|
||||
{p.notation} →{" "}
|
||||
{p.notation}{" "}
|
||||
<TbOutlineArrowRight class="inline-block align-[-0.125em]" />{" "}
|
||||
<span class="font-bold text-base">{result.total}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { TbOutlineHash } from "solid-icons/tb";
|
||||
import { registerMessageType } from "../registry";
|
||||
import { journalSetState } from "../../stores/journalStream";
|
||||
import { produce } from "solid-js/store";
|
||||
@@ -35,7 +36,7 @@ registerMessageType<VarPayload>({
|
||||
render: (p) => {
|
||||
return (
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-lg">🔢</span>
|
||||
<TbOutlineHash class="text-lg" />
|
||||
<span class="font-mono text-sm">
|
||||
{p.key} = <span class="font-bold">{p.value}</span>
|
||||
</span>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { For, Show, createEffect, createSignal, on } from "solid-js";
|
||||
import { TbOutlineChevronLeft, TbOutlineChevronRight } from "solid-icons/tb";
|
||||
import type { DeckStore } from "./hooks/deckStore";
|
||||
|
||||
export interface CardListProps {
|
||||
@@ -67,7 +68,7 @@ export function CardList(props: CardListProps) {
|
||||
disabled={page() === 0}
|
||||
class="px-2 py-1 rounded text-sm text-gray-600 hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer"
|
||||
>
|
||||
‹
|
||||
<TbOutlineChevronLeft />
|
||||
</button>
|
||||
<span class="text-xs text-gray-500 tabular-nums">
|
||||
{page() + 1} / {totalPages()}
|
||||
@@ -77,7 +78,7 @@ export function CardList(props: CardListProps) {
|
||||
disabled={page() >= totalPages() - 1}
|
||||
class="px-2 py-1 rounded text-sm text-gray-600 hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer"
|
||||
>
|
||||
›
|
||||
<TbOutlineChevronRight />
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { Show, createSignal, onMount } from "solid-js";
|
||||
import { Portal } from "solid-js/web";
|
||||
import {
|
||||
TbOutlineCheck,
|
||||
TbOutlineCopy,
|
||||
TbOutlineDownload,
|
||||
TbOutlinePencil,
|
||||
} from "solid-icons/tb";
|
||||
import type { DeckStore } from "./hooks/deckStore";
|
||||
|
||||
export interface DeckHeaderProps {
|
||||
@@ -43,7 +49,15 @@ export function DeckHeader(props: DeckHeaderProps) {
|
||||
: "bg-gray-100 text-gray-600 hover:bg-gray-200"
|
||||
}`}
|
||||
>
|
||||
{store.state.isEditing ? "✓ 编辑中" : "✏️ 编辑"}
|
||||
{store.state.isEditing ? (
|
||||
<>
|
||||
<TbOutlineCheck class="inline-block align-[-0.125em]" /> 编辑中
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TbOutlinePencil class="inline-block align-[-0.125em]" /> 编辑
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* 导出 PDF 按钮 */}
|
||||
@@ -51,7 +65,7 @@ export function DeckHeader(props: DeckHeaderProps) {
|
||||
onClick={() => store.actions.exportDeck()}
|
||||
class="px-2 py-1 rounded text-xs font-medium transition-colors cursor-pointer bg-green-100 text-green-600 hover:bg-green-200"
|
||||
>
|
||||
📥 导出 PDF
|
||||
<TbOutlineDownload class="inline-block align-[-0.125em]" /> 导出 PDF
|
||||
</button>
|
||||
|
||||
{/* 复制代码按钮 */}
|
||||
@@ -59,7 +73,7 @@ export function DeckHeader(props: DeckHeaderProps) {
|
||||
onClick={handleCopy}
|
||||
class="px-2 py-1 rounded text-xs font-medium transition-colors cursor-pointer bg-purple-100 text-purple-600 hover:bg-purple-200"
|
||||
>
|
||||
📋 复制代码
|
||||
<TbOutlineCopy class="inline-block align-[-0.125em]" /> 复制代码
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createSignal, For, Show, createMemo } from "solid-js";
|
||||
import { Portal } from "solid-js/web";
|
||||
import { TbOutlineDownload } from "solid-icons/tb";
|
||||
import { parsePlt, extractCutPaths } from "../../plotcutter/parser";
|
||||
import { generateTravelPaths, travelPathsToSvg } from "../../plotcutter/layout";
|
||||
import type { CardPath } from "../../plotcutter/types";
|
||||
@@ -115,7 +116,7 @@ export function PltPreview(props: PltPreviewProps) {
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1.5 rounded text-sm font-medium cursor-pointer flex items-center gap-1"
|
||||
disabled={parsedData().cardPaths.length === 0}
|
||||
>
|
||||
📥 下载 PLT
|
||||
<TbOutlineDownload /> 下载 PLT
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Show } from 'solid-js';
|
||||
import { TbOutlineCircleX, TbOutlineX } from 'solid-icons/tb';
|
||||
import type { DeckStore } from './hooks/deckStore';
|
||||
|
||||
export interface PrintPreviewFooterProps {
|
||||
@@ -26,14 +27,14 @@ export function PrintPreviewFooter(props: PrintPreviewFooterProps) {
|
||||
<Show when={store.state.exportError}>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-2 text-red-600">
|
||||
<span>❌</span>
|
||||
<TbOutlineCircleX />
|
||||
<span class="text-sm font-medium">{store.state.exportError}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => store.actions.clearExportError()}
|
||||
class="text-gray-500 hover:text-gray-700 cursor-pointer"
|
||||
>
|
||||
✕
|
||||
<TbOutlineX />
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { createSignal, For, onCleanup, onMount, Show } from "solid-js";
|
||||
import {
|
||||
TbOutlineDownload,
|
||||
TbOutlinePlus,
|
||||
TbOutlineRuler,
|
||||
} from "solid-icons/tb";
|
||||
import type { DeckStore } from "./hooks/deckStore";
|
||||
import { getCompatibleDecks } from "./hooks/deck-registry";
|
||||
|
||||
@@ -77,7 +82,7 @@ export function PrintPreviewHeader(props: PrintPreviewHeaderProps) {
|
||||
: "添加其他牌组到打印"
|
||||
}
|
||||
>
|
||||
<span>➕</span>
|
||||
<TbOutlinePlus />
|
||||
<span>添加牌组</span>
|
||||
<Show when={props.selectedDeckIds.length > 0}>
|
||||
<span class="bg-purple-600 text-white text-xs rounded-full w-5 h-5 inline-flex items-center justify-center">
|
||||
@@ -232,14 +237,14 @@ export function PrintPreviewHeader(props: PrintPreviewHeaderProps) {
|
||||
onClick={props.onOpenPltPreview}
|
||||
class="bg-green-600 hover:bg-green-700 text-white px-4 py-1.5 rounded text-sm font-medium cursor-pointer flex items-center gap-2"
|
||||
>
|
||||
<span>📐</span>
|
||||
<TbOutlineRuler />
|
||||
<span>PLT 预览</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={props.onExport}
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-1.5 rounded text-sm font-medium cursor-pointer flex items-center gap-2"
|
||||
>
|
||||
<span>📥</span>
|
||||
<TbOutlineDownload />
|
||||
<span>导出 PDF</span>
|
||||
</button>
|
||||
<button
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
import { For, createSignal, onCleanup, onMount } from "solid-js";
|
||||
import type { JSX } from "solid-js";
|
||||
import { Dynamic } from "solid-js/web";
|
||||
import { createSortable, useDragDropContext } from "@thisbeyond/solid-dnd";
|
||||
import type { IconTypes } from "solid-icons";
|
||||
import {
|
||||
TbOutlineAlignCenter,
|
||||
TbOutlineAlignLeft,
|
||||
TbOutlineAlignRight,
|
||||
TbOutlineArrowDown,
|
||||
TbOutlineArrowDownLeft,
|
||||
TbOutlineArrowDownRight,
|
||||
TbOutlineArrowLeft,
|
||||
TbOutlineArrowRight,
|
||||
TbOutlineArrowUp,
|
||||
TbOutlineArrowUpLeft,
|
||||
TbOutlineArrowUpRight,
|
||||
TbOutlineEye,
|
||||
TbOutlineEyeOff,
|
||||
TbOutlineGripVertical,
|
||||
TbOutlineTrash,
|
||||
} from "solid-icons/tb";
|
||||
import type { DeckStore } from "../hooks/deckStore";
|
||||
import type { Align, LayerConfig } from "../types";
|
||||
import alignLeftIcon from "./icons/align-left.png";
|
||||
import alignCenterIcon from "./icons/align-center.png";
|
||||
import alignRightIcon from "./icons/align-right.png";
|
||||
|
||||
export interface LayerRowProps {
|
||||
store: DeckStore;
|
||||
@@ -22,65 +39,67 @@ export interface LayerRowProps {
|
||||
onRemove: () => void;
|
||||
}
|
||||
|
||||
const ORIENTATIONS = [
|
||||
{ value: "n" as const, label: "↑ 北" },
|
||||
{ value: "e" as const, label: "→ 东" },
|
||||
{ value: "s" as const, label: "↓ 南" },
|
||||
{ value: "w" as const, label: "← 西" },
|
||||
const ORIENTATIONS: {
|
||||
value: "n" | "e" | "s" | "w";
|
||||
label: string;
|
||||
icon: IconTypes;
|
||||
}[] = [
|
||||
{ value: "n", label: "北", icon: TbOutlineArrowUp },
|
||||
{ value: "e", label: "东", icon: TbOutlineArrowRight },
|
||||
{ value: "s", label: "南", icon: TbOutlineArrowDown },
|
||||
{ value: "w", label: "西", icon: TbOutlineArrowLeft },
|
||||
];
|
||||
|
||||
const ALIGNS: { value: Align | ""; icon: string }[] = [
|
||||
{ value: "", icon: alignCenterIcon },
|
||||
{ value: "l", icon: alignLeftIcon },
|
||||
{ value: "c", icon: alignCenterIcon },
|
||||
{ value: "r", icon: alignRightIcon },
|
||||
{ value: "tl", icon: "↖" },
|
||||
{ value: "tc", icon: "↑" },
|
||||
{ value: "tr", icon: "↗" },
|
||||
{ value: "bl", icon: "↙" },
|
||||
{ value: "bc", icon: "↓" },
|
||||
{ value: "br", icon: "↘" },
|
||||
const ALIGNS: { value: Align | ""; icon: IconTypes }[] = [
|
||||
{ value: "", icon: TbOutlineAlignCenter },
|
||||
{ value: "l", icon: TbOutlineAlignLeft },
|
||||
{ value: "c", icon: TbOutlineAlignCenter },
|
||||
{ value: "r", icon: TbOutlineAlignRight },
|
||||
{ value: "tl", icon: TbOutlineArrowUpLeft },
|
||||
{ value: "tc", icon: TbOutlineArrowUp },
|
||||
{ value: "tr", icon: TbOutlineArrowUpRight },
|
||||
{ value: "bl", icon: TbOutlineArrowDownLeft },
|
||||
{ value: "bc", icon: TbOutlineArrowDown },
|
||||
{ value: "br", icon: TbOutlineArrowDownRight },
|
||||
];
|
||||
|
||||
const FONT_PRESETS = [3, 5, 8, 12] as const;
|
||||
|
||||
function orientChar(v: string) {
|
||||
function orientIcon(v: string): IconTypes {
|
||||
switch (v) {
|
||||
case "n":
|
||||
return "↑";
|
||||
return TbOutlineArrowUp;
|
||||
case "e":
|
||||
return "→";
|
||||
return TbOutlineArrowRight;
|
||||
case "s":
|
||||
return "↓";
|
||||
return TbOutlineArrowDown;
|
||||
case "w":
|
||||
return "←";
|
||||
return TbOutlineArrowLeft;
|
||||
default:
|
||||
return "↑";
|
||||
return TbOutlineArrowUp;
|
||||
}
|
||||
}
|
||||
|
||||
function alignIcon(v: string): import("solid-js").JSX.Element {
|
||||
function alignIcon(v: string): IconTypes {
|
||||
switch (v) {
|
||||
case "tl":
|
||||
return "↖";
|
||||
return TbOutlineArrowUpLeft;
|
||||
case "tc":
|
||||
return "↑";
|
||||
return TbOutlineArrowUp;
|
||||
case "tr":
|
||||
return "↗";
|
||||
return TbOutlineArrowUpRight;
|
||||
case "bl":
|
||||
return "↙";
|
||||
return TbOutlineArrowDownLeft;
|
||||
case "bc":
|
||||
return "↓";
|
||||
return TbOutlineArrowDown;
|
||||
case "br":
|
||||
return "↘";
|
||||
return TbOutlineArrowDownRight;
|
||||
case "l":
|
||||
return <img src={alignLeftIcon} alt="align" class="w-5 h-5 not-prose" />;
|
||||
return TbOutlineAlignLeft;
|
||||
case "r":
|
||||
return <img src={alignRightIcon} alt="align" class="w-5 h-5 not-prose" />;
|
||||
return TbOutlineAlignRight;
|
||||
default:
|
||||
return (
|
||||
<img src={alignCenterIcon} alt="align" class="w-5 h-5 not-prose" />
|
||||
);
|
||||
return TbOutlineAlignCenter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +144,7 @@ export function LayerRow(props: LayerRowProps) {
|
||||
class="cursor-grab text-gray-400 hover:text-gray-600 text-sm px-0.5 select-none shrink-0"
|
||||
title="拖动排序"
|
||||
>
|
||||
⠿
|
||||
<TbOutlineGripVertical class="inline-block align-[-0.125em]" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -133,7 +152,11 @@ export function LayerRow(props: LayerRowProps) {
|
||||
class="shrink-0 text-sm cursor-pointer select-none"
|
||||
title={props.layer.visible ? "隐藏" : "显示"}
|
||||
>
|
||||
{props.layer.visible ? "👁" : "👁🗨"}
|
||||
{props.layer.visible ? (
|
||||
<TbOutlineEye class="inline-block align-[-0.125em]" />
|
||||
) : (
|
||||
<TbOutlineEyeOff class="inline-block align-[-0.125em]" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
<span
|
||||
@@ -144,7 +167,7 @@ export function LayerRow(props: LayerRowProps) {
|
||||
</span>
|
||||
|
||||
<DropdownButton
|
||||
icon={orientChar(props.layer.orientation || "n")}
|
||||
icon={orientIcon(props.layer.orientation || "n")}
|
||||
visible={props.layer.visible}
|
||||
open={props.openDropdown === `orient-${props.index}`}
|
||||
onToggle={() =>
|
||||
@@ -162,6 +185,10 @@ export function LayerRow(props: LayerRowProps) {
|
||||
onClick={() => props.onUpdateOrientation(o.value)}
|
||||
class="block w-full text-left px-3 py-1.5 text-sm hover:bg-gray-100 cursor-pointer whitespace-nowrap"
|
||||
>
|
||||
<Dynamic
|
||||
component={o.icon}
|
||||
class="inline-block align-[-0.125em]"
|
||||
/>{" "}
|
||||
{o.label}
|
||||
</button>
|
||||
)}
|
||||
@@ -187,13 +214,11 @@ export function LayerRow(props: LayerRowProps) {
|
||||
onClick={() => props.onUpdateAlign(o.value || undefined)}
|
||||
class="flex items-center gap-2 w-full px-3 py-1.5 text-sm hover:bg-gray-100 cursor-pointer whitespace-nowrap"
|
||||
>
|
||||
{o.icon.endsWith(".png") ? (
|
||||
<img src={o.icon} alt="" class="w-4 h-4 not-prose max-w-none" />
|
||||
) : (
|
||||
<span class="w-4 h-4 flex items-center justify-center not-prose">
|
||||
{o.icon}
|
||||
</span>
|
||||
)}
|
||||
<Dynamic
|
||||
component={o.icon}
|
||||
size={16}
|
||||
class="not-prose max-w-none"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
@@ -260,19 +285,19 @@ export function LayerRow(props: LayerRowProps) {
|
||||
class={`shrink-0 w-5 h-5 text-xs rounded cursor-pointer flex items-center justify-center text-red-400 hover:text-red-600 hover:bg-red-50 transition-opacity ${props.hovered ? "opacity-100" : "opacity-0"}`}
|
||||
title="删除图层"
|
||||
>
|
||||
✕
|
||||
<TbOutlineTrash size={14} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownButton(props: {
|
||||
icon: import("solid-js").JSX.Element;
|
||||
icon: IconTypes;
|
||||
visible: boolean;
|
||||
open: boolean;
|
||||
onToggle: () => void;
|
||||
title: string;
|
||||
children: import("solid-js").JSX.Element;
|
||||
children: JSX.Element;
|
||||
}) {
|
||||
return (
|
||||
<div class="relative">
|
||||
@@ -284,7 +309,7 @@ function DropdownButton(props: {
|
||||
class={`w-7 h-7 rounded cursor-pointer flex items-center justify-center bg-gray-200 hover:bg-gray-300 ${!props.visible ? "invisible pointer-events-none" : ""}`}
|
||||
title={props.title}
|
||||
>
|
||||
{props.icon}
|
||||
<Dynamic component={props.icon} size={16} />
|
||||
</button>
|
||||
{props.open && (
|
||||
<div
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1002 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1006 B |
Binary file not shown.
|
Before Width: | Height: | Size: 996 B |
@@ -1,5 +1,6 @@
|
||||
import { customElement, noShadowDOM } from "solid-element";
|
||||
import { createSignal, onMount } from "solid-js";
|
||||
import { TbOutlineDice5 } from "solid-icons/tb";
|
||||
import { rollFormula } from "./dice";
|
||||
|
||||
export interface DiceProps {
|
||||
@@ -86,7 +87,7 @@ customElement("md-dice", { key: "" }, (props, { element }) => {
|
||||
class="text-blue-600 hover:text-blue-800 cursor-pointer"
|
||||
title={rollDetail() || "掷骰子"}
|
||||
>
|
||||
🎲
|
||||
<TbOutlineDice5 class="inline-block align-[-0.125em]" />
|
||||
</span>
|
||||
<a
|
||||
onClick={handleRoll}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { customElement, noShadowDOM } from "solid-element";
|
||||
import {Show, For, createResource, createMemo, createSignal} from "solid-js";
|
||||
import { TbOutlineCopy } from "solid-icons/tb";
|
||||
import { resolvePath } from "./utils/path";
|
||||
import { createPinsStore } from "./stores/pinsStore";
|
||||
|
||||
@@ -108,7 +109,7 @@ customElement("md-pins", { pins: "", fixed: false, labelStart: "A" }, (props, {
|
||||
class="bg-gray-800 hover:bg-gray-700 text-white px-3 py-1.5 rounded shadow-lg text-sm flex items-center gap-1 transition-colors"
|
||||
title="复制所有 pin 坐标"
|
||||
>
|
||||
📋 复制
|
||||
<TbOutlineCopy /> 复制
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
createMemo,
|
||||
createResource,
|
||||
} from "solid-js";
|
||||
import { TbOutlineDice5 } from "solid-icons/tb";
|
||||
import { parseMarkdown } from "../markdown";
|
||||
import { parseCSVString, CSV, processVariables } from "./utils/csv-loader";
|
||||
import { resolveContentRef } from "./utils/resolve-content";
|
||||
@@ -210,7 +211,7 @@ customElement(
|
||||
class="text-gray-500 hover:text-gray-700 shrink-0 cursor-pointer"
|
||||
title="随机切换"
|
||||
>
|
||||
🎲
|
||||
<TbOutlineDice5 class="inline-block align-[-0.125em]" />
|
||||
</button>
|
||||
</Show>
|
||||
<div
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { customElement, noShadowDOM } from 'solid-element';
|
||||
import { For, Show, createEffect } from 'solid-js';
|
||||
import {
|
||||
TbOutlineRotate,
|
||||
TbOutlinePlayerTrackNext,
|
||||
TbOutlineArrowRight,
|
||||
} from 'solid-icons/tb';
|
||||
import type {TextResult, RuntimeResult, OptionsResult} from 'yarn-spinner-loader';
|
||||
import { createYarnStore } from './stores/yarnStore';
|
||||
|
||||
@@ -82,7 +87,7 @@ customElement<{start: string}>('md-yarn-spinner', {start: 'start'}, (props, { el
|
||||
rounded-bl-md transition-colors cursor-pointer"
|
||||
title="重新开始"
|
||||
>
|
||||
🔄
|
||||
<TbOutlineRotate class="inline-block align-[-0.125em]" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => advance()}
|
||||
@@ -90,7 +95,7 @@ customElement<{start: string}>('md-yarn-spinner', {start: 'start'}, (props, { el
|
||||
transition-colors cursor-pointer"
|
||||
title="继续"
|
||||
>
|
||||
⏩
|
||||
<TbOutlinePlayerTrackNext class="inline-block align-[-0.125em]" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -104,7 +109,7 @@ customElement<{start: string}>('md-yarn-spinner', {start: 'start'}, (props, { el
|
||||
class="option-button text-left px-4 py-2 bg-blue-50 hover:bg-blue-100
|
||||
rounded border border-blue-200 transition-colors cursor-pointer"
|
||||
>
|
||||
→ {option.text}
|
||||
<TbOutlineArrowRight class="inline-block align-[-0.125em]" /> {option.text}
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
---
|
||||
tag: journal-gm
|
||||
icon: 🎭
|
||||
icon: gm
|
||||
title: 主持人指南
|
||||
---
|
||||
|
||||
作为 GM 创建会话、管理玩家、使用命令与玩家互动。
|
||||
|
||||
点击顶部导航栏的 📋 按钮打开 Journal 面板。
|
||||
点击顶部导航栏的剪贴板按钮打开 Journal 面板。
|
||||
|
||||
## 快速开始
|
||||
|
||||
1. 点击顶部导航栏的 📋 按钮打开 Journal 面板
|
||||
1. 点击顶部导航栏的剪贴板按钮打开 Journal 面板
|
||||
2. 输入你的名字,选择"主持人"角色,点击"加入"
|
||||
3. 连接成功后,你可以创建新会话或选择已有会话
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: journal-link
|
||||
icon: 🔗
|
||||
icon: link
|
||||
title: 链接命令
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: journal-player
|
||||
icon: 🎲
|
||||
icon: player
|
||||
title: 玩家指南
|
||||
---
|
||||
|
||||
@@ -19,7 +19,7 @@ GM 会发送一个包含 `?session=xxx&player=你的名字&autojoin=1` 的链接
|
||||
|
||||
### 手动加入
|
||||
|
||||
1. 点击顶部导航栏的 📋 按钮打开 Journal 面板
|
||||
1. 点击顶部导航栏的剪贴板按钮打开 Journal 面板
|
||||
2. 输入你的名字
|
||||
3. 选择"玩家"角色
|
||||
4. 点击"加入"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: journal-roll
|
||||
icon: 🎲
|
||||
icon: roll
|
||||
title: 掷骰与火花表命令
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: journal-set
|
||||
icon: 🔢
|
||||
icon: set
|
||||
title: 变量系统
|
||||
description: 在文档中声明变量和标签,通过 /set 命令设置变量值,自动计算派生变量和标签效果。
|
||||
syntax: '```yaml role=declare'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: journal-spark
|
||||
icon: 🎰
|
||||
icon: spark
|
||||
title: 火花表
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: md-bg
|
||||
icon: 🖼️
|
||||
icon: background
|
||||
title: 背景组件
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: md-border
|
||||
icon: 🖼️
|
||||
icon: border
|
||||
title: 边框组件
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: md-deck
|
||||
icon: 🃏
|
||||
icon: deck
|
||||
title: 卡牌组件
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: md-dice
|
||||
icon: 🎲
|
||||
icon: dice
|
||||
title: 骰子组件
|
||||
---
|
||||
|
||||
@@ -23,7 +23,7 @@ title: 骰子组件
|
||||
:md-dice[2d20k1+5]{key="advantage"}
|
||||
```
|
||||
|
||||
点击 🎲 图标执行投掷,显示结果;点击文字重置为原始公式。
|
||||
点击骰子图标执行投掷,显示结果;点击文字重置为原始公式。
|
||||
|
||||
## 属性
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: md-embed
|
||||
icon: 📄
|
||||
icon: embed
|
||||
title: 嵌入组件
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: md-font
|
||||
icon: 🔤
|
||||
icon: font
|
||||
title: 字体组件
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: md-link
|
||||
icon: 🔗
|
||||
icon: link
|
||||
title: 链接组件
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: md-pins
|
||||
icon: 📍
|
||||
icon: pins
|
||||
title: 标记组件
|
||||
---
|
||||
|
||||
@@ -27,7 +27,7 @@ title: 标记组件
|
||||
|
||||
## 复制坐标
|
||||
|
||||
非 `fixed` 模式下,图片右上角会显示 📋 复制按钮,点击可将所有标记的坐标复制到剪贴板,方便粘贴回文档的 `pins` 属性中。
|
||||
非 `fixed` 模式下,图片右上角会显示复制按钮,点击可将所有标记的坐标复制到剪贴板,方便粘贴回文档的 `pins` 属性中。
|
||||
|
||||
## 属性
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: md-table
|
||||
icon: 📊
|
||||
icon: table
|
||||
title: 表格组件
|
||||
---
|
||||
|
||||
@@ -38,7 +38,7 @@ B,另一段描述,第二章]{}
|
||||
|
||||
## 随机抽取
|
||||
|
||||
当 `roll=true` 时,表格顶部显示 🎲 随机按钮:
|
||||
当 `roll=true` 时,表格顶部显示骰子随机按钮:
|
||||
- 普通随机:从所有行中均匀随机选取
|
||||
- **加权随机**:当 `label` 列全部为整数或整数范围格式(如 `1-3`)时,以 label 值为权重进行加权随机抽取
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
tag: md-yarn-spinner
|
||||
icon: 🧶
|
||||
icon: yarn
|
||||
title: 叙事线组件
|
||||
---
|
||||
|
||||
@@ -19,8 +19,8 @@ Yarn Spinner 是用于游戏对话系统的格式,支持选项、条件分支
|
||||
|
||||
- **对话历史**:上半部分显示已进行的对话,说话者以蓝色粗体显示,命令以灰色斜体显示
|
||||
- **当前选项**:下半部分显示可选的对话选项,点击选项推进剧情
|
||||
- **⏩ 继续**:点击右上角继续按钮推进到下一段对话
|
||||
- **🔄 重新开始**:点击右上角重启按钮从头开始对话
|
||||
- **继续**:点击右上角继续按钮推进到下一段对话
|
||||
- **重新开始**:点击右上角重启按钮从头开始对话
|
||||
|
||||
## 属性
|
||||
|
||||
|
||||
Reference in New Issue
Block a user