feat: copy note template IDs

This commit is contained in:
tianma-if committed 2026-08-15 22:34:49 +08:00
1 parent d06b6531aa
commit a558062d37
4 files changed
+57

No files matched your search

+36
View File
@@ -2,6 +2,7 @@ import { useMemo, useState, type MouseEvent, type ReactNode } from "react";
import {
Check,
ChevronLeft,
Copy,
Eye,
LayoutList,
Pencil,
@@ -20,6 +21,7 @@ import { Input } from "@/components/ui/input";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { WORKSPACE_PAGE_TITLE_CLASSNAME } from "@/lib/workspace-ui";
import { copyTextToClipboard } from "@/lib/clipboard";
import type { MemoTemplate as SavedMemoTemplate } from "@edgeever/shared";
const TemplateIconAction = ({
@@ -86,6 +88,16 @@ export const TemplatesPane = ({
} | null>(null);
const [deleteConfirmTemplate, setDeleteConfirmTemplate] = useState<SavedMemoTemplate | null>(null);
const [templateIdCopyNotice, setTemplateIdCopyNotice] = useState<{
id: string;
status: "copied" | "error";
} | null>(null);
const copyTemplateId = async (template: SavedMemoTemplate) => {
const copied = await copyTextToClipboard(template.id);
setTemplateIdCopyNotice({ id: template.id, status: copied ? "copied" : "error" });
window.setTimeout(() => setTemplateIdCopyNotice(null), copied ? 2200 : 3000);
};
const startEditing = (template: SavedMemoTemplate) => {
setCreatingTemplate(false);
@@ -354,6 +366,16 @@ export const TemplatesPane = ({
<div className="mt-4 border-t border-slate-100 pt-3 flex items-center justify-between gap-2">
<div className="flex items-center gap-1 text-slate-400">
<TemplateIconAction
className="p-1 hover:text-slate-700 transition rounded-md hover:bg-slate-100"
label={t("templates.copyId")}
onClick={(e) => {
e.stopPropagation();
void copyTemplateId(template);
}}
>
<Copy className="h-3.5 w-3.5" />
</TemplateIconAction>
<TemplateIconAction
className="p-1 hover:text-slate-700 transition rounded-md hover:bg-slate-100"
disabled={isCreating}
@@ -519,6 +541,20 @@ export const TemplatesPane = ({
</DialogContent>
</Dialog>
)}
{templateIdCopyNotice && (
<div
className={`fixed bottom-5 left-1/2 z-[120] max-w-[calc(100vw-2rem)] -translate-x-1/2 truncate rounded-md px-3 py-2 text-sm font-medium text-white shadow-lg ${
templateIdCopyNotice.status === "copied" ? "bg-emerald-700" : "bg-rose-600"
}`}
role={templateIdCopyNotice.status === "copied" ? "status" : "alert"}
>
{t(
templateIdCopyNotice.status === "copied" ? "templates.idCopied" : "templates.idCopyFailed",
{ id: templateIdCopyNotice.id },
)}
</div>
)}
</div>
</TooltipProvider>
);
+3
View File
@@ -1272,6 +1272,9 @@ export const enUS = {
emptyMyTemplates: "No templates yet",
emptyMyTemplatesHint: "Click \"New Template\" above or save any note as a template directly in the editor.",
delete: "Delete template",
copyId: "Copy template ID",
idCopied: "Template ID copied: {{id}}",
idCopyFailed: "Could not copy the template ID.",
deleteConfirmTitle: "Delete template \"{{name}}\"",
deleteConfirmDescription: "Are you sure you want to delete this template? This operation cannot be undone.",
saveAsTemplate: "Save as template",
+3
View File
@@ -1272,6 +1272,9 @@ export const zhCN = {
emptyMyTemplates: "暂无模板",
emptyMyTemplatesHint: "你可以点击上方「新建模板」,或在笔记编辑器中将常用笔记另存为模板。",
delete: "删除模板",
copyId: "复制模板 ID",
idCopied: "模板 ID 已复制:{{id}}",
idCopyFailed: "复制模板 ID 失败。",
deleteConfirmTitle: "删除模板「{{name}}」",
deleteConfirmDescription: "确定要删除该模板吗?此操作不可恢复。",
saveAsTemplate: "另存为模板",
+15
View File
@@ -0,0 +1,15 @@
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
const templatesPaneSource = readFileSync(
new URL("../apps/web/src/components/TemplatesPane.tsx", import.meta.url),
"utf8",
);
describe("copy note template ID", () => {
test("copies the raw persisted template ID from every template card", () => {
expect(templatesPaneSource).toContain("copyTextToClipboard(template.id)");
expect(templatesPaneSource).toContain('label={t("templates.copyId")}');
expect(templatesPaneSource).toContain('"templates.idCopied"');
});
});