diff --git a/src/components/journal/JournalInput.tsx b/src/components/journal/JournalInput.tsx index f945796..de9622f 100644 --- a/src/components/journal/JournalInput.tsx +++ b/src/components/journal/JournalInput.tsx @@ -20,12 +20,7 @@ import { Switch, Match, } from "solid-js"; -import { - sendMessage, - revertLatest, - canRevert, - useJournalStream, -} from "../stores/journalStream"; +import { sendMessage, useJournalStream } from "../stores/journalStream"; import { useJournalCompletions, ensureCompletions, @@ -123,10 +118,7 @@ export const JournalInput: Component = () => { textareaRef?.focus(); } - function handleRevert() { - revertLatest(); - } - + // ---- Completions ---- // ---- Scroll selected completion into view ---- createEffect(() => { @@ -313,8 +305,6 @@ export const JournalInput: Component = () => { onCleanup(() => document.removeEventListener("mousedown", handler)); }); - const showRevert = () => canRevert() && stream.myName === "gm"; - // Completions placeholder — shown in the dropdown area when no matches function renderCompletionsDropdown() { const comps = currentCompletions(); @@ -414,15 +404,6 @@ export const JournalInput: Component = () => {

{error()}

- - - + + {props.message.type}
diff --git a/src/components/journal/types/article.tsx b/src/components/journal/types/article.tsx index 953ab9a..dc8e79a 100644 --- a/src/components/journal/types/article.tsx +++ b/src/components/journal/types/article.tsx @@ -2,6 +2,8 @@ * Built-in message type: article.reveal */ +import { Component } from "solid-js"; +import { useNavigate } from "@solidjs/router"; import { z } from "zod"; import { registerMessageType } from "../registry"; import { journalSetState } from "../../stores/journalStream"; @@ -15,25 +17,69 @@ const schema = z.object({ export type ArticleRevealPayload = z.infer; +function fileNameFromPath(path: string): string { + const parts = path.split("/").filter(Boolean); + return parts[parts.length - 1] || path; +} + +/** Section slug → human-readable title (e.g. "attack-rules" → "Attack Rules") */ +function slugToTitle(slug: string): string { + return slug + .split("-") + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(" "); +} + +/** Clickable link that navigates without dropping URL params */ +const RevealLink: Component = (p) => { + const navigate = useNavigate(); + + const target = () => { + let t = p.path; + if (p.section) t += `#${p.section}`; + return t; + }; + + const displayLabel = () => { + if (p.label) return p.label; + const file = fileNameFromPath(p.path); + if (p.section) return `${file} § ${slugToTitle(p.section)}`; + return file; + }; + + const handleClick = (e: MouseEvent) => { + e.preventDefault(); + navigate(target()); + }; + + return ( +
+ 📄 +
+ + {displayLabel()} + + {p.section && ( + + § {slugToTitle(p.section)} + + )} +
+
+ ); +}; + registerMessageType({ type: "article.reveal", label: "Reveal Article", emitters: ["gm"], schema, defaultPayload: () => ({ path: "/" }), - render: (p) => ( -
- 📄 -
- - {p.label || p.path} - - {p.section && ( - § {p.section} - )} -
-
- ), + render: (p) => , reducer: (p) => { journalSetState( produce((s) => {