diff --git a/src/components/journal/ConnectDialog.tsx b/src/components/journal/ConnectDialog.tsx
index 30612ec..2565ecb 100644
--- a/src/components/journal/ConnectDialog.tsx
+++ b/src/components/journal/ConnectDialog.tsx
@@ -16,6 +16,7 @@ import {
setMyRole,
useJournalStream,
} from "../stores/journalStream";
+import { DeleteSessionDialog } from "./DeleteSessionDialog";
export const ConnectDialog: Component = () => {
const stream = useJournalStream();
@@ -28,6 +29,10 @@ export const ConnectDialog: Component = () => {
const [busy, setBusy] = createSignal(false);
const [error, setError] = createSignal(null);
const [autoJoined, setAutoJoined] = createSignal(false);
+ const [sessionToDelete, setSessionToDelete] = createSignal<{
+ id: string;
+ name: string;
+ } | null>(null);
const ensureBroker = async () => {
if (stream.connected) return;
@@ -145,13 +150,27 @@ export const ConnectDialog: Component = () => {
{([id, meta]) => (
-
+
+
+
+
+
+
)}
@@ -187,6 +206,17 @@ export const ConnectDialog: Component = () => {
{error()}
+
+
+ {(target) => (
+ setSessionToDelete(null)}
+ />
+ )}
+
);
diff --git a/src/components/journal/DeleteSessionDialog.tsx b/src/components/journal/DeleteSessionDialog.tsx
new file mode 100644
index 0000000..3afc080
--- /dev/null
+++ b/src/components/journal/DeleteSessionDialog.tsx
@@ -0,0 +1,85 @@
+/**
+ * DeleteSessionDialog — destructive confirmation before removing a session.
+ *
+ * Deletion removes the session directory on the server (meta + full message
+ * log), so the warning spells out that it cannot be undone.
+ */
+import { Component, createSignal, Show } from "solid-js";
+import { deleteSession } from "../stores/journalStream";
+
+interface DeleteSessionDialogProps {
+ sessionId: string;
+ sessionName: string;
+ /** Whether this is the session the user is currently in. */
+ active: boolean;
+ onClose: () => void;
+}
+
+export const DeleteSessionDialog: Component = (
+ props,
+) => {
+ const [deleting, setDeleting] = createSignal(false);
+ const [error, setError] = createSignal(null);
+
+ const handleDelete = async () => {
+ setDeleting(true);
+ setError(null);
+ try {
+ await deleteSession(props.sessionId);
+ props.onClose();
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "删除失败");
+ } finally {
+ setDeleting(false);
+ }
+ };
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
删除会话
+
+
+
+ 确定要删除会话{" "}
+ {props.sessionName}
+ ({props.sessionId})吗?
+
+
+ 此操作不可恢复:该会话的全部消息记录、玩家名单和派生状态都会从服务器删除。
+ {props.active && " 你当前正在该会话中,删除后将返回会话列表。"}
+
+
+ {error()}
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/journal/JournalHeader.tsx b/src/components/journal/JournalHeader.tsx
index 0dd7330..7ec593f 100644
--- a/src/components/journal/JournalHeader.tsx
+++ b/src/components/journal/JournalHeader.tsx
@@ -8,6 +8,7 @@ import { SessionDropdown } from "./SessionDropdown";
interface JournalHeaderProps {
onClose: () => void;
onCreateSession: () => void;
+ onDeleteSession: (id: string, name: string) => void;
}
export const JournalHeader: Component = (props) => {
@@ -54,7 +55,10 @@ export const JournalHeader: Component = (props) => {
{/* Clickable subtitle — session dropdown (GM only) */}
-
+
diff --git a/src/components/journal/JournalPanel.tsx b/src/components/journal/JournalPanel.tsx
index 21d2434..66975f1 100644
--- a/src/components/journal/JournalPanel.tsx
+++ b/src/components/journal/JournalPanel.tsx
@@ -15,6 +15,7 @@ import { JournalHeader } from "./JournalHeader";
import { ConnectDialog } from "./ConnectDialog";
import { InviteDialog } from "./InviteDialog";
import { CreateSessionDialog } from "./CreateSessionDialog";
+import { DeleteSessionDialog } from "./DeleteSessionDialog";
import { VariableView } from "./VariableView";
export interface JournalPanelProps {
@@ -26,6 +27,10 @@ export const JournalPanel: Component = (props) => {
const stream = useJournalStream();
const [showInvite, setShowInvite] = createSignal(false);
const [showCreateSession, setShowCreateSession] = createSignal(false);
+ const [sessionToDelete, setSessionToDelete] = createSignal<{
+ id: string;
+ name: string;
+ } | null>(null);
const [viewMode, setViewMode] = createSignal<"stream" | "variables">("stream");
// Player list (exclude gm and observer)
@@ -56,6 +61,7 @@ export const JournalPanel: Component = (props) => {
setShowCreateSession(true)}
+ onDeleteSession={(id, name) => setSessionToDelete({ id, name })}
/>
{/* Player list tabs */}
@@ -126,6 +132,18 @@ export const JournalPanel: Component = (props) => {
setShowCreateSession(false)} />
+
+ {/* Delete session modal */}
+
+ {(target) => (
+ setSessionToDelete(null)}
+ />
+ )}
+
>
diff --git a/src/components/journal/SessionDropdown.tsx b/src/components/journal/SessionDropdown.tsx
index 045e37a..d97ead4 100644
--- a/src/components/journal/SessionDropdown.tsx
+++ b/src/components/journal/SessionDropdown.tsx
@@ -10,6 +10,7 @@ import {
interface SessionDropdownProps {
onCreate: () => void;
+ onDelete: (id: string, name: string) => void;
}
export const SessionDropdown: Component = (props) => {
@@ -54,22 +55,37 @@ export const SessionDropdown: Component = (props) => {
>
{([id, meta]) => (
-
diff --git a/src/components/journal/index.ts b/src/components/journal/index.ts
index d233345..3d3fc11 100644
--- a/src/components/journal/index.ts
+++ b/src/components/journal/index.ts
@@ -45,6 +45,7 @@ export { ConnectDialog } from "./ConnectDialog";
export { InviteDialog } from "./InviteDialog";
export { SessionDropdown } from "./SessionDropdown";
export { CreateSessionDialog } from "./CreateSessionDialog";
+export { DeleteSessionDialog } from "./DeleteSessionDialog";
export { StreamView } from "./StreamView";
export { StreamMessageCard } from "./StreamMessage";
export { ComposePanel } from "./ComposePanel";