diff --git a/src/components/ExportDialog.test.tsx b/src/components/ExportDialog.test.tsx
new file mode 100644
index 0000000..6d47eb9
--- /dev/null
+++ b/src/components/ExportDialog.test.tsx
@@ -0,0 +1,48 @@
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { getArmy, getRuleSet } from "../data/gameData";
+import { createList } from "../domain/lists";
+import { buildShareUrl } from "../domain/shareCode";
+import ExportDialog from "./ExportDialog";
+
+vi.mock("../domain/shareCode", () => ({
+ buildShareUrl: vi.fn(),
+}));
+
+const writeText = vi.fn();
+
+function renderDialog() {
+ const ruleSet = getRuleSet("warmaster-custom")!;
+ const army = getArmy(ruleSet.id, "dwarfs")!;
+ const list = createList(ruleSet.id, ruleSet.version, "dwarfs", "Throng of [Dorin]", 2000);
+ render();
+}
+
+describe("ExportDialog share links", () => {
+ beforeEach(() => {
+ vi.mocked(buildShareUrl).mockResolvedValue("https://warmuster.example/#shared-list");
+ writeText.mockReset().mockResolvedValue(undefined);
+ Object.defineProperty(navigator, "clipboard", {
+ configurable: true,
+ value: { writeText },
+ });
+ });
+
+ it("copies the full sharable URL", async () => {
+ renderDialog();
+ fireEvent.click(screen.getByRole("button", { name: "Copy sharable link" }));
+ await waitFor(() =>
+ expect(writeText).toHaveBeenCalledWith("https://warmuster.example/#shared-list"),
+ );
+ });
+
+ it("copies a short Discord Markdown link labeled with the list name", async () => {
+ renderDialog();
+ fireEvent.click(screen.getByRole("button", { name: "Copy Discord link" }));
+ await waitFor(() =>
+ expect(writeText).toHaveBeenCalledWith(
+ "[Warmuster - Throng of \\[Dorin\\]](https://warmuster.example/#shared-list)",
+ ),
+ );
+ });
+});
diff --git a/src/components/ExportDialog.tsx b/src/components/ExportDialog.tsx
index fe6893f..e17a742 100644
--- a/src/components/ExportDialog.tsx
+++ b/src/components/ExportDialog.tsx
@@ -13,16 +13,18 @@ interface ExportDialogProps {
export default function ExportDialog({ list, army, onClose, onPrint }: ExportDialogProps) {
const [copied, setCopied] = useState(false);
- const [shareCopied, setShareCopied] = useState(false);
+ const [copiedLink, setCopiedLink] = useState<"sharable" | "discord" | null>(null);
const text = buildTextExport(list, army);
- const copyShareLink = async () => {
+ const copyShareLink = async (format: "sharable" | "discord") => {
try {
const url = await buildShareUrl(list);
- await navigator.clipboard.writeText(url);
- setShareCopied(true);
- setTimeout(() => setShareCopied(false), 1500);
+ const listName = list.name.replace(/\s+/g, " ").trim();
+ const label = `Warmuster - ${listName}`.replace(/([\\\[\]])/g, "\\$1");
+ await navigator.clipboard.writeText(format === "discord" ? `[${label}](${url})` : url);
+ setCopiedLink(format);
+ setTimeout(() => setCopiedLink(null), 1500);
} catch {
// Clipboard unavailable.
}
@@ -62,8 +64,21 @@ export default function ExportDialog({ list, army, onClose, onPrint }: ExportDia
-