Add GitHub-style Markdown editor to PublishModal
This commit is contained in:
parent
659789b0dc
commit
758d5c0207
157
src/components/ChangelogEditor.tsx
Normal file
157
src/components/ChangelogEditor.tsx
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { theme } from "antd";
|
||||||
|
import { commands, type ICommand } from "@uiw/react-md-editor";
|
||||||
|
import "@uiw/react-md-editor/markdown-editor.css";
|
||||||
|
import "@uiw/react-markdown-preview/markdown.css";
|
||||||
|
|
||||||
|
// @uiw/react-md-editor accesses browser APIs on import — skip SSR
|
||||||
|
const MDEditor = dynamic(() => import("@uiw/react-md-editor"), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Types
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type EditorMode = "edit" | "preview";
|
||||||
|
|
||||||
|
interface ChangelogEditorProps {
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// GitHub-style Code / Preview tabs
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function ModeTab({
|
||||||
|
label,
|
||||||
|
active,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
active: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
}) {
|
||||||
|
const { token } = theme.useToken();
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: "inline-block",
|
||||||
|
padding: `2px ${token.paddingSM}px`,
|
||||||
|
fontSize: token.fontSizeSM,
|
||||||
|
fontWeight: active ? 600 : 400,
|
||||||
|
color: active ? token.colorPrimary : token.colorTextSecondary,
|
||||||
|
borderBottom: active
|
||||||
|
? `2px solid ${token.colorPrimary}`
|
||||||
|
: "2px solid transparent",
|
||||||
|
cursor: "pointer",
|
||||||
|
userSelect: "none",
|
||||||
|
}}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTabCommand(
|
||||||
|
key: string,
|
||||||
|
label: string,
|
||||||
|
active: boolean,
|
||||||
|
onSwitch: () => void,
|
||||||
|
): ICommand {
|
||||||
|
return {
|
||||||
|
name: key,
|
||||||
|
keyCommand: key,
|
||||||
|
icon: <ModeTab label={label} active={active} onClick={onSwitch} />,
|
||||||
|
buttonProps: null,
|
||||||
|
execute: (state, api, dispatch) => {
|
||||||
|
dispatch?.({ preview: key === "preview" ? "preview" : "edit" });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatting commands, like GitHub's toolbar — hidden in Preview mode
|
||||||
|
const FORMAT_COMMANDS: ICommand[] = [
|
||||||
|
commands.bold,
|
||||||
|
commands.italic,
|
||||||
|
commands.strikethrough,
|
||||||
|
commands.divider,
|
||||||
|
commands.link,
|
||||||
|
commands.code,
|
||||||
|
commands.codeBlock,
|
||||||
|
commands.image,
|
||||||
|
commands.divider,
|
||||||
|
commands.unorderedListCommand,
|
||||||
|
commands.orderedListCommand,
|
||||||
|
commands.checkedListCommand,
|
||||||
|
commands.quote,
|
||||||
|
commands.table,
|
||||||
|
];
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Component
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export function ChangelogEditor({ value, onChange }: ChangelogEditorProps) {
|
||||||
|
const { token } = theme.useToken();
|
||||||
|
const [mode, setMode] = useState<EditorMode>("edit");
|
||||||
|
|
||||||
|
const toolbarCommands: ICommand[] =
|
||||||
|
mode === "edit"
|
||||||
|
? [
|
||||||
|
buildTabCommand("code", "Code", true, () => setMode("edit")),
|
||||||
|
buildTabCommand("preview", "Preview", false, () => setMode("preview")),
|
||||||
|
commands.divider,
|
||||||
|
...FORMAT_COMMANDS,
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
buildTabCommand("code", "Code", false, () => setMode("edit")),
|
||||||
|
buildTabCommand("preview", "Preview", true, () => setMode("preview")),
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-color-mode="light"
|
||||||
|
style={{
|
||||||
|
// Map the editor's GitHub-flavored CSS variables onto Ant Design tokens
|
||||||
|
// so the preview/toolbar visually merge into the surrounding UI.
|
||||||
|
["--md-editor-background-color" as string]: token.colorBgContainer,
|
||||||
|
["--md-editor-box-shadow-color" as string]: token.colorBorder,
|
||||||
|
["--md-editor-font-family" as string]: token.fontFamily,
|
||||||
|
["--color-fg-default" as string]: token.colorText,
|
||||||
|
["--color-fg-muted" as string]: token.colorTextSecondary,
|
||||||
|
["--color-canvas-default" as string]: token.colorBgContainer,
|
||||||
|
["--color-canvas-subtle" as string]: token.colorFillQuaternary,
|
||||||
|
["--color-border-default" as string]: token.colorBorder,
|
||||||
|
["--color-border-muted" as string]: token.colorBorderSecondary,
|
||||||
|
["--color-neutral-muted" as string]: token.colorFillSecondary,
|
||||||
|
["--color-accent-fg" as string]: token.colorPrimary,
|
||||||
|
["--color-accent-emphasis" as string]: token.colorPrimaryActive,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MDEditor
|
||||||
|
value={value}
|
||||||
|
onChange={(val) => onChange(val ?? "")}
|
||||||
|
preview={mode}
|
||||||
|
height={520}
|
||||||
|
minHeight={400}
|
||||||
|
maxHeight={900}
|
||||||
|
visibleDragbar={false}
|
||||||
|
textareaProps={{
|
||||||
|
placeholder: "输入 Markdown 格式的更新日志…",
|
||||||
|
}}
|
||||||
|
commands={toolbarCommands}
|
||||||
|
extraCommands={[]}
|
||||||
|
style={{
|
||||||
|
borderRadius: token.borderRadius,
|
||||||
|
fontFamily: token.fontFamily,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -25,6 +25,7 @@ import { Template } from "@/lib/utils/template";
|
|||||||
import type { McVersionEntry } from "@/lib/utils/mcVersion";
|
import type { McVersionEntry } from "@/lib/utils/mcVersion";
|
||||||
import { computeVersionRanges } from "@/lib/utils/mcVersion";
|
import { computeVersionRanges } from "@/lib/utils/mcVersion";
|
||||||
import { getModrinthMcVersions, getCurseForgeMcVersions } from "@/services/meta";
|
import { getModrinthMcVersions, getCurseForgeMcVersions } from "@/services/meta";
|
||||||
|
import { ChangelogEditor } from "./ChangelogEditor";
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
@ -384,21 +385,7 @@ export function PublishModal({
|
|||||||
<Text strong style={{ display: "block", marginBottom: token.marginSM }}>
|
<Text strong style={{ display: "block", marginBottom: token.marginSM }}>
|
||||||
Changelog (Markdown)
|
Changelog (Markdown)
|
||||||
</Text>
|
</Text>
|
||||||
{/* TODO: Replace with @uiw/react-md-editor */}
|
<ChangelogEditor value={changelog} onChange={setChangelog} />
|
||||||
<textarea
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
minHeight: 160,
|
|
||||||
border: `1px solid ${token.colorBorder}`,
|
|
||||||
borderRadius: token.borderRadius,
|
|
||||||
padding: token.paddingSM,
|
|
||||||
fontFamily: token.fontFamilyCode ?? "monospace",
|
|
||||||
fontSize: token.fontSizeSM,
|
|
||||||
}}
|
|
||||||
placeholder="输入 Markdown 格式的更新日志…"
|
|
||||||
value={changelog}
|
|
||||||
onChange={(e) => setChangelog(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Version type */}
|
{/* Version type */}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user