feat(prompts+i18n): add prompt management and improve prompt editor i18n (#193)
* feat(prompts): add prompt management across Tauri service and React UI
- backend: add commands/prompt.rs, services/prompt.rs, register in commands/mod.rs and lib.rs, refine app_config.rs
- frontend: add PromptPanel, PromptFormModal, PromptListItem, MarkdownEditor, usePromptActions, integrate in App.tsx
- api: add src/lib/api/prompts.ts
- i18n: update src/i18n/locales/{en,zh}.json
- build: update package.json and pnpm-lock.yaml
* feat(i18n): improve i18n for prompts and Markdown editor
- update src/i18n/locales/{en,zh}.json keys and strings
- apply i18n in PromptFormModal, PromptPanel, and MarkdownEditor
- align prompt text with src-tauri/src/services/prompt.rs
* feat(prompts): add enable/disable toggle and simplify panel UI
- Add PromptToggle component and integrate in prompt list items
- Implement toggleEnabled with optimistic update; enable via API, disable via upsert with enabled=false;
reload after success
- Simplify PromptPanel: remove file import and current-file preview to keep CRUD flow focused
- Tweak header controls style (use mcp variant) and minor copy: rename “Prompt Management” to “Prompts”
- i18n: add disableSuccess/disableFailed messages
- Backend (Tauri): prevent duplicate backups when importing original prompt content
* style: unify code formatting with trailing commas
* feat(prompts): add Gemini filename support to PromptFormModal
Update filename mapping to use Record<AppId, string> pattern, supporting
GEMINI.md alongside CLAUDE.md and AGENTS.md.
* fix(prompts): sync enabled prompt to file when updating
When updating a prompt that is currently enabled, automatically sync
the updated content to the corresponding live file (CLAUDE.md/AGENTS.md/GEMINI.md).
This ensures the active prompt file always reflects the latest content
when editing enabled prompts.
This commit is contained in:
159
src/components/MarkdownEditor.tsx
Normal file
159
src/components/MarkdownEditor.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import React, { useRef, useEffect } from "react";
|
||||
import { EditorView, basicSetup } from "codemirror";
|
||||
import { markdown } from "@codemirror/lang-markdown";
|
||||
import { oneDark } from "@codemirror/theme-one-dark";
|
||||
import { EditorState } from "@codemirror/state";
|
||||
import { placeholder as placeholderExt } from "@codemirror/view";
|
||||
|
||||
interface MarkdownEditorProps {
|
||||
value: string;
|
||||
onChange?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
darkMode?: boolean;
|
||||
readOnly?: boolean;
|
||||
className?: string;
|
||||
minHeight?: string;
|
||||
maxHeight?: string;
|
||||
}
|
||||
|
||||
const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
placeholder: placeholderText = "",
|
||||
darkMode = false,
|
||||
readOnly = false,
|
||||
className = "",
|
||||
minHeight = "300px",
|
||||
maxHeight,
|
||||
}) => {
|
||||
const editorRef = useRef<HTMLDivElement>(null);
|
||||
const viewRef = useRef<EditorView | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editorRef.current) return;
|
||||
|
||||
// 定义基础主题
|
||||
const baseTheme = EditorView.baseTheme({
|
||||
"&": {
|
||||
height: "100%",
|
||||
minHeight,
|
||||
maxHeight: maxHeight || "none",
|
||||
},
|
||||
".cm-scroller": {
|
||||
overflow: "auto",
|
||||
fontFamily:
|
||||
"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
||||
fontSize: "14px",
|
||||
},
|
||||
"&light .cm-content, &dark .cm-content": {
|
||||
padding: "12px 0",
|
||||
},
|
||||
"&light .cm-editor, &dark .cm-editor": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
"&.cm-focused": {
|
||||
outline: "none",
|
||||
},
|
||||
});
|
||||
|
||||
const extensions = [
|
||||
basicSetup,
|
||||
markdown(),
|
||||
baseTheme,
|
||||
EditorView.lineWrapping,
|
||||
EditorState.readOnly.of(readOnly),
|
||||
];
|
||||
|
||||
if (!readOnly) {
|
||||
extensions.push(
|
||||
placeholderExt(placeholderText),
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged && onChange) {
|
||||
onChange(update.state.doc.toString());
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
// 只读模式下隐藏光标和高亮行
|
||||
extensions.push(
|
||||
EditorView.theme({
|
||||
".cm-cursor, .cm-dropCursor": { border: "none" },
|
||||
".cm-activeLine": { backgroundColor: "transparent !important" },
|
||||
".cm-activeLineGutter": { backgroundColor: "transparent !important" },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// 如果启用深色模式,添加深色主题
|
||||
if (darkMode) {
|
||||
extensions.push(oneDark);
|
||||
} else {
|
||||
// 浅色模式下的简单样式调整,使其更融入 UI
|
||||
extensions.push(
|
||||
EditorView.theme(
|
||||
{
|
||||
"&": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
".cm-content": {
|
||||
color: "#374151", // text-gray-700
|
||||
},
|
||||
".cm-gutters": {
|
||||
backgroundColor: "#f9fafb", // bg-gray-50
|
||||
color: "#9ca3af", // text-gray-400
|
||||
borderRight: "1px solid #e5e7eb", // border-gray-200
|
||||
},
|
||||
".cm-activeLineGutter": {
|
||||
backgroundColor: "#e5e7eb",
|
||||
},
|
||||
},
|
||||
{ dark: false },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 创建初始状态
|
||||
const state = EditorState.create({
|
||||
doc: value,
|
||||
extensions,
|
||||
});
|
||||
|
||||
// 创建编辑器视图
|
||||
const view = new EditorView({
|
||||
state,
|
||||
parent: editorRef.current,
|
||||
});
|
||||
|
||||
viewRef.current = view;
|
||||
|
||||
return () => {
|
||||
view.destroy();
|
||||
viewRef.current = null;
|
||||
};
|
||||
}, [darkMode, readOnly, minHeight, maxHeight, placeholderText]); // 添加 placeholderText 依赖以支持国际化切换
|
||||
|
||||
// 当 value 从外部改变时更新编辑器内容
|
||||
useEffect(() => {
|
||||
if (viewRef.current && viewRef.current.state.doc.toString() !== value) {
|
||||
const transaction = viewRef.current.state.update({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: viewRef.current.state.doc.length,
|
||||
insert: value,
|
||||
},
|
||||
});
|
||||
viewRef.current.dispatch(transaction);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={editorRef}
|
||||
className={`border rounded-md overflow-hidden ${
|
||||
darkMode ? "border-gray-800" : "border-gray-200"
|
||||
} ${className}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkdownEditor;
|
||||
Reference in New Issue
Block a user