translate comment of frontend/components

This commit is contained in:
david_bai
2025-06-21 23:35:27 +08:00
parent bb06ef95a7
commit c47895b938
13 changed files with 126 additions and 127 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ const ClipboardApp = () => {
const [retrieveRoomIdInput, setRetrieveRoomIdInput] = useState("");
const [activeTab, setActiveTab] = useState<"send" | "retrieve">("send");
const retrieveJoinRoomBtnRef = useRef<HTMLButtonElement>(null); //接收方--加入房间按钮ref
const retrieveJoinRoomBtnRef = useRef<HTMLButtonElement>(null); // Ref for the receiver's "Join Room" button
const { messages, isLoadingMessages } = usePageSetup({
setRetrieveRoomId: setRetrieveRoomIdInput,
@@ -11,21 +11,20 @@ import { getDictionary } from "@/lib/dictionary";
import { useLocale } from "@/hooks/useLocale";
import type { Messages } from "@/types/messages";
function formatFolderDis(
template: string,
num: number,
size: string
) {
return template.replace('{num}', num.toString()).replace('{size}', size);
function formatFolderDis(template: string, num: number, size: string) {
return template.replace("{num}", num.toString()).replace("{size}", size);
}
function formatFolderTips(
template: string,
name: string,
num: number,
template: string,
name: string,
num: number,
size: string
) {
return template.replace('{name}', name).replace('{num}', num.toString()).replace('{size}', size);
return template
.replace("{name}", name)
.replace("{num}", num.toString())
.replace("{size}", size);
}
interface FileListDisplayProps {
@@ -37,15 +36,15 @@ interface FileListDisplayProps {
};
};
onDownload?: (item: FileMeta) => void;
onRequest?: (item: FileMeta) => void; //请求文件
onRequest?: (item: FileMeta) => void; // Request file
onDelete?: (item: FileMeta) => void;
onLocationPick?: () => Promise<boolean>;
saveType?: { [fileId: string]: boolean }; //文件是存储在磁盘还是内存
saveType?: { [fileId: string]: boolean }; // File stored on disk or in memory
largeFileThreshold?: number;
}
// 添加类型判断辅助函数
// Add type guard helper function
function isCustomFile(file: FileMeta | CustomFile): file is CustomFile {
return "lastModified" in file; // 使用 File 对象特有的属性来判断
return "lastModified" in file; // Use a property specific to File objects to check
}
function isFileMetaArray(
files: FileMeta[] | CustomFile[]
@@ -68,21 +67,21 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
const [showFinished, setShowFinished] = useState<{
[fileId: string]: boolean;
}>({});
// 添加 ref 来存储上一次的 showFinished 状态
// Add a ref to store the previous showFinished state
const prevShowFinishedRef = useRef<{ [fileId: string]: boolean }>({});
const [pickedLocation, setPickedLocation] = useState<boolean>(false); //是否已经选取过保存目录
const [needPickLocation, setNeedPickLocation] = useState<boolean>(false); //是否需要选择保存目录--大文件或文件夹或用户主动选择
const [pickedLocation, setPickedLocation] = useState<boolean>(false); // Whether a save directory has been selected
const [needPickLocation, setNeedPickLocation] = useState<boolean>(false); // Whether a save directory needs to be selected -- for large files, folders, or user choice
const [folders, setFolders] = useState<FileMeta[]>([]); //将文件中属于 文件夹的 提取出来
const [singleFiles, setSingleFiles] = useState<FileMeta[]>([]); //保留单文件,不属于文件夹
// 添加当前显示的接收端追踪
const [folders, setFolders] = useState<FileMeta[]>([]); // Extract folder items from files
const [singleFiles, setSingleFiles] = useState<FileMeta[]>([]); // Keep single files, not part of a folder
// Add tracking for currently displayed receiver
const [activeTransfers, setActiveTransfers] = useState<{
[fileId: string]: string;
}>({});
//对是否有文件传输状态进行跟踪
// Track if any file transfer is in progress
const [isAnyFileTransferring, setIsAnyFileTransferring] = useState(false);
// 添加下载次数的状态
// Add state for download counts
const [downloadCounts, setDownloadCounts] = useState<{
[fileId: string]: number;
}>({});
@@ -91,7 +90,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
.then((dict) => setMessages(dict))
.catch((error) => console.error("Failed to load messages:", error));
}, [locale]);
//监控文件传输状态
// Monitor file transfer status
useEffect(() => {
const hasActiveTransfer = Object.values(fileProgresses).some(
(fileProgress) =>
@@ -103,12 +102,12 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
}, [fileProgresses]);
useEffect(() => {
//分离出单文件和文件夹
// Separate single files and folders
const tempSingleFiles: FileMeta[] = [];
let folders_: { [folderName: string]: FileMeta } = {};
let needPick = false;
// 如果是 CustomFile[] 类型,先转换为 FileMeta[]
// If it's a CustomFile[] type, convert it to FileMeta[] first
const processedFiles: FileMeta[] = isFileMetaArray(files)
? files
: files.map((file) => ({
@@ -123,10 +122,10 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
for (let file of processedFiles) {
if (file.folderName !== "") {
folders_[file.folderName] = folders_[file.folderName] || {
//如果对象不存在,则初始化
// If the object doesn't exist, initialize it
name: file.folderName,
size: 0,
fullName: file.folderName, // 文件夹的 fullName 就是 folderName
fullName: file.folderName, // The fullName of a folder is its folderName
fileType: "folder",
fileId: file.folderName,
folderName: file.folderName,
@@ -135,7 +134,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
};
folders_[file.folderName].fileCount =
(folders_[file.folderName].fileCount ?? 0) + 1; //如果 fileCount undefined,使用默认值 0
(folders_[file.folderName].fileCount ?? 0) + 1; // If fileCount is undefined, use default value 0
folders_[file.folderName].size += file.size;
folders_[file.folderName].fileNamesDis = folders_[file.folderName]
.fileNamesDis
@@ -151,7 +150,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
// console.log('Single files before setState:', tempSingleFiles);
// console.log('Folders before setState:', Object.values(folders_));
// 使用函数式更新确保状态正确更新
// Use functional updates to ensure the state is updated correctly
setSingleFiles((prev) => {
// console.log('Previous single files:', prev);
// console.log('New single files:', tempSingleFiles);
@@ -162,32 +161,32 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
// console.log('New folders:', Object.values(folders_));
return [...Object.values(folders_)];
});
setNeedPickLocation(needPick); //设置是否需要选择保存目录
setNeedPickLocation(needPick); // Set whether a save directory needs to be selected
}, [files, largeFileThreshold]);
useEffect(() => {
//如果一个文件同时被多个接收端请求,会先显示第一个,等结束后再显示第二个
// If a file is requested by multiple receivers simultaneously, the first one will be displayed, then the second after the first finishes.
let fileIds = [...singleFiles, ...folders].map((file) => file.fileId);
fileIds.forEach((fileId) => {
const fileProgress = fileProgresses[fileId];
if (!fileProgress) return;
// 获取当前文件的所有传输进度
// Get all transfer progresses for the current file
const transfers = Object.entries(fileProgress);
// 如果没有活跃传输,选择第一个开始的传输
// If there are no active transfers, select the first one that started
let newPeerId = "";
if (!activeTransfers[fileId] && transfers.length > 0) {
newPeerId = transfers[0][0];
setActiveTransfers((prev) => ({
...prev,
[fileId]: newPeerId, // 设置第一个 peerId
[fileId]: newPeerId, // Set the first peerId
}));
}
// set是异步操作 直接使用 newPeerId 而不是从 activeTransfers 中读取
// set is an async operation, use newPeerId directly instead of reading from activeTransfers
const activePeerId = newPeerId || activeTransfers[fileId];
// 检查当前活跃传输是否完成
// Check if the current active transfer is complete
if (activePeerId && fileProgress[activePeerId]?.progress >= 1) {
// 当前传输完成,等待2秒后切换到下一个未完成的传输
// Current transfer is complete, wait 2 seconds before switching to the next incomplete transfer
if (!showFinished[fileId]) {
setShowFinished((prev) => ({ ...prev, [fileId]: true }));
@@ -198,8 +197,8 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
return updated;
});
delete fileProgress[activePeerId]; //需要删掉这个peer的进度,否则下次相同文件被请求进度显示不正常
// 找到下一个未完成的传输
delete fileProgress[activePeerId]; //need to delete the progress of this peer, otherwise the progress will be displayed abnormally when the same file is requested next time.
// Find the next outstanding transfer
const nextTransfer = transfers.find(
([pid, prog]) =>
pid !== activePeerId && prog.progress > 0 && prog.progress < 1
@@ -228,7 +227,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
]);
useEffect(() => {
//监控 Finished 从false/null跳变为true这个事件 来触发下载
//Monitor the Finished event from false/null to true to trigger the download
let files_ = [...singleFiles, ...folders];
files_.forEach((item: FileMeta) => {
@@ -236,32 +235,32 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
const prevShowFinished = prevShowFinishedRef.current[item.fileId];
const isSaveToDisk = saveType ? saveType[item.fileId] : false;
// console.log(`last:${prevShowFinished} --> cur:${currentShowFinished}`);
// 检测 false -> true 的跳变
// Detecting false -> true transitions
if (!prevShowFinished && currentShowFinished) {
if (!isSaveToDisk && onDownload) {
onDownload(item);
}
// 增加下载次数
// Increase download count
setDownloadCounts((prevCounts) => ({
...prevCounts,
[item.fileId]: (prevCounts[item.fileId] || 0) + 1,
}));
}
// 更新上一次的状态
// Update the last status
prevShowFinishedRef.current[item.fileId] = currentShowFinished;
});
}, [showFinished, singleFiles, folders, saveType, onDownload]);
//每一项文件 对应的动作--进度、下载、删除
//Actions corresponding to each file - progress, download, delete
const renderItemActions = (item: FileMeta) => {
const fileProgress = fileProgresses[item.fileId];
const activePeerId = activeTransfers[item.fileId];
const progress = activePeerId ? fileProgress?.[activePeerId] : null;
const showCompletion = showFinished[item.fileId];
const isSaveToDisk = saveType ? saveType[item.fileId] : false;
// 获取下载次数
// Get download count
const downloadCount = downloadCounts[item.fileId] || 0;
if (messages === null) {
@@ -269,7 +268,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
}
return (
<div className="flex items-center">
{progress && progress.progress < 1 ? ( //显示进度或已完成
{progress && progress.progress < 1 ? ( //Show progress or completed
<TransferProgress
message={
mode === "sender"
@@ -285,7 +284,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
) : null}
{mode === "receiver" &&
onRequest &&
onDownload && ( //请求 && 下载
onDownload && ( //Request && Download
<FileTransferButton
onRequest={() => onRequest(item)}
isCurrentFileTransferring={
@@ -297,7 +296,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
isSavedToDisk={saveType ? saveType[item.fileId] : false}
/>
)}
{/* 展示下载次数 */}
{/* display download Num*/}
{mode === "sender" && (
<span className="mr-2 text-sm">
{messages.text.FileListDisplay.downloadNum_dis}: {downloadCount}
@@ -321,7 +320,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
</div>
);
};
//每一项文件 对应的展示--meta信息
//Display of each file --meta information
const renderItem = (item: FileMeta, isFolder: boolean) => {
const filenameDisplayLen = 30;
const formatSize = formatFileSize(item.size);
@@ -362,7 +361,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
<>
{(singleFiles.length > 0 || folders.length > 0) && (
<>
{/* 自动弹窗组件,当有大文件和文件夹时 只提醒一次 */}
{/* Automatic pop-up component, only remind once when there are large files and folders */}
{mode === "receiver" && (
<div className="mb-2">
<AutoPopupDialog
@@ -373,7 +372,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
}
condition={() => needPickLocation}
/>
{/* 常态化提醒选择保存目录 */}
{/* Regular reminder to select the save directory */}
<div className="flex items-center">
<p className="text-red-500 mb-2">
{messages.text.FileListDisplay.chooseSavePath_tips}
@@ -395,7 +394,6 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
</div>
)}
<div className="mb-2">
{/* 确保文件列表确实被渲染 */}
<div className="files-list">
{singleFiles.map((file) => (
<div key={`single-${file.name}`}>{renderItem(file, false)}</div>
@@ -12,7 +12,7 @@ interface FileTransferButtonProps {
isOtherFileTransferring: boolean;
isSavedToDisk: boolean;
}
//针对下载中不同状态的按键进行管理
// Manage buttons for different download statuses
const FileTransferButton = ({
onRequest,
isCurrentFileTransferring,
@@ -21,7 +21,7 @@ const FileTransferButton = ({
}: FileTransferButtonProps) => {
const locale = useLocale();
const [messages, setMessages] = useState<Messages | null>(null);
// 按钮状态判断
// Button status judgment
const isDisabled = isCurrentFileTransferring || isSavedToDisk || isOtherFileTransferring;
useEffect(() => {
@@ -29,7 +29,7 @@ const FileTransferButton = ({
.then(dict => setMessages(dict))
.catch(error => console.error('Failed to load messages:', error));
}, [locale]);
// 根据不同状态显示不同的提示信息
// Display different tooltips based on status
const getTooltipContent = () => {
if (isSavedToDisk) return messages!.text.FileTransferButton.SavedToDisk_tips;
if (isCurrentFileTransferring) return messages!.text.FileTransferButton.CurrentFileTransferring_tips;
@@ -37,7 +37,7 @@ const FileTransferButton = ({
return messages!.text.FileTransferButton.download_tips;
};
// 根据状态设置不同的按钮样式和类名
// Set different button styles and class names based on status
const getButtonStyles = () => {
if (isSavedToDisk) {
return {
@@ -71,7 +71,7 @@ const FileTransferButton = ({
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger asChild>
<span className="inline-block"> {/* 包装器确保禁用状态下tooltip仍然工作 */}
<span className="inline-block">
<Button
onClick={onRequest}
variant={buttonStyles.variant}
@@ -9,7 +9,7 @@ import {
DialogTitle,
DialogDescription,
} from '@/components/ui/dialog';
// 在文件顶部添加这个声明来扩展已有的类型,避免IDE报错
// Add this declaration at the top of the file to extend existing types and avoid IDE errors
declare module "@/components/ui/input" {
interface InputProps {
webkitdirectory?: string | boolean;
@@ -20,7 +20,7 @@ declare module "@/components/ui/input" {
import { getDictionary } from '@/lib/dictionary';
import { useLocale } from '@/hooks/useLocale';
import type { Messages } from '@/types/messages';
import { en } from '@/constants/messages/en'; // 导入英文字典作为默认值
import { en } from '@/constants/messages/en'; // Import English dictionary as default
const traverseFileTree = async (item: FileSystemEntry, path = ''): Promise<CustomFile[]> => {
return new Promise((resolve) => {
@@ -49,7 +49,7 @@ const traverseFileTree = async (item: FileSystemEntry, path = ''): Promise<Custo
// console.log('subResults',subResults)
const files: CustomFile[] = subResults.flat();
// console.log('files',files)
resolve(files); // 移除了条件判断,直接返回处理好的文件
resolve(files); // Removed conditional judgment, directly return processed files
}
});
};
@@ -76,17 +76,17 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
onFilePicked
}) => {
const locale = useLocale();
const [messages, setMessages] = useState<Messages>(en); // 使用英文字典作为初始值
const [messages, setMessages] = useState<Messages>(en); // Use English dictionary as initial value
const dropZoneRef = useRef<HTMLDivElement>(null);//拖拽文件至附件--支持
const dropZoneRef = useRef<HTMLDivElement>(null);// Drag and drop files to attachments -- support
const folderInputRef = useRef<HTMLInputElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
// 文件选择器--消息提示
// File selector -- message prompt
const [fileText, setFileText] = useState<string>(en.text.fileUploadHandler.NoFileChosen_tips);
const [isModalOpen, setIsModalOpen] = useState(false);
useEffect(() => {
if (locale !== 'en') { // 如果不是英文,才需要加载其他语言包
if (locale !== 'en') { // Only load other language packs if not English
getDictionary(locale)
.then(dict => {setMessages(dict);setFileText(dict.text.fileUploadHandler.NoFileChosen_tips);})
.catch(error => console.error('Failed to load messages:', error));
@@ -100,7 +100,6 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
const fileNum = newFiles.length;
const folderNum = newFiles.filter(file => file.folderName).length;
// 使用时
const choose_dis = formatFileChosen(
messages!.text.fileUploadHandler.fileChosen_tips_template, fileNum, folderNum
);
@@ -112,7 +111,7 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
fileInputRef.current.value = '';
}
}, [messages,onFilePicked]);
//拖拽上传文件夹 响应处理
// Drag and drop folder upload response processing
const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
@@ -132,15 +131,15 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
});
}
}, [handleFileChange]);
/* 定义一个处理拖动文件悬停事件的回调函数 handleDragOver。
handleDragOver 中,阻止默认行为和事件传播,以确保自定义处理。
没有依赖项数组,这意味着 handleDragOver 函数只会在组件第一次渲染时创建一次,并且不会在之后的渲染中重新创建
/* Define a callback function handleDragOver to handle the drag-over event.
In handleDragOver, prevent default behavior and event propagation to ensure custom handling.
There is no dependency array, which means the handleDragOver function will only be created once when the component first renders, and will not be re-created in subsequent renders.
*/
const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
}, []);
//点击上传文件 处理
// Click to upload file processing
const handleFileInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
const files = Array.from(e.target.files);
@@ -150,10 +149,10 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
files2.push(customFile);
}
handleFileChange(files2);
setIsModalOpen(false);//关闭对话框
setIsModalOpen(false);// Close the dialog
}
}, [handleFileChange]);
//点击上传文件夹 响应处理
// Click to upload folder response processing
const handleFolderInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
const files_ = Array.from(e.target.files);
@@ -167,20 +166,20 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
});
handleFileChange(files);
setIsModalOpen(false);//关闭对话框
setIsModalOpen(false);// Close the dialog
}
}, [handleFileChange]);
// 处理拖放区域的点击
// Handle drag and drop area click
const handleZoneClick = () => {
setIsModalOpen(true);
};
// 处理选择文件
// Handle file selection
const handleSelectFile = () => {
fileInputRef.current?.click();
};
// 处理选择文件夹
// Handle folder selection
const handleSelectFolder = () => {
folderInputRef.current?.click();
};
@@ -9,7 +9,7 @@ import {
import FileListDisplay from "@/components/ClipboardApp/FileListDisplay";
import type { Messages } from "@/types/messages";
import type { FileMeta } from "@/types/webrtc";
import type { ProgressState } from "@/hooks/useWebRTCConnection"; // 假设此类型已导出
import type { ProgressState } from "@/hooks/useWebRTCConnection"; // Assuming this type is exported
import type WebRTC_Recipient from "@/lib/webrtc_Recipient";
interface RetrieveTabPanelProps {
@@ -37,8 +37,8 @@ interface SendTabPanelProps {
removeFileToSend: (meta: FileMeta) => void;
richTextToPlainText: (html: string) => string;
sendProgress: ProgressState;
// shareRoomId: string; // 这个是从 useRoomManager 来的,代表已验证的ID
processRoomIdInput: (roomId: string) => void; // useRoomManager 传入
// shareRoomId: string; // This comes from useRoomManager and represents the validated ID
processRoomIdInput: (roomId: string) => void; // Passed from useRoomManager
joinRoom: (isSender: boolean, roomId: string) => void;
generateShareLinkAndBroadcast: () => void;
sender: WebRTC_Initiator | null;
@@ -67,12 +67,12 @@ export function SendTabPanel({
currentValidatedShareRoomId,
}: // initShareRoomId,
SendTabPanelProps) {
// 本地状态,用于输入框的即时响应
// Local state for immediate response in the input field
const [inputFieldValue, setInputFieldValue] = useState<string>(
currentValidatedShareRoomId
);
// 当来自父组件的 validatedShareRoomId 改变时(例如,初始获取后),同步本地输入框的值
// When the validatedShareRoomId from the parent component changes (e.g., after initial fetch), synchronize the local input field's value
useEffect(() => {
setInputFieldValue(currentValidatedShareRoomId);
}, [currentValidatedShareRoomId]);
@@ -80,8 +80,8 @@ SendTabPanelProps) {
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
setInputFieldValue(newValue); // 立即更新输入框显示
processRoomIdInput(newValue); // 调用hook中的处理函数,它会进行防抖验证
setInputFieldValue(newValue); // Immediately update the input field display
processRoomIdInput(newValue); // Call the handler function from the hook, which will perform debounced validation
},
[processRoomIdInput]
);
@@ -129,18 +129,14 @@ SendTabPanelProps) {
</span>
<Input
aria-label="Share Room ID"
value={inputFieldValue} // 绑定到本地状态
value={inputFieldValue} // Bind to local state
onChange={handleInputChange}
onPaste={handlePaste}
className="flex-grow min-w-0"
// placeholder={
// // messages.text.ClipboardApp.html.roomIdInput_placeholder ||
// "输入房间号或粘贴"
// }
/>
<Button
className="w-full sm:w-auto"
onClick={() => joinRoom(true, inputFieldValue.trim())} // 使用当前输入框的值尝试加入
onClick={() => joinRoom(true, inputFieldValue.trim())} // Attempt to join using the current input field value
disabled={!sender || sender.isInRoom || !inputFieldValue.trim()}
>
{messages.text.ClipboardApp.html.joinRoom_dis}
@@ -156,7 +152,7 @@ SendTabPanelProps) {
!sender.isInRoom ||
(sendFiles.length === 0 && shareContent.trim() === "") ||
!currentValidatedShareRoomId.trim()
} // 确保有已验证的房间ID才可分享
} // Ensure there is a validated room ID before allowing sharing
>
{messages.text.ClipboardApp.html.startSending_dis}
</AnimatedButton>
@@ -98,9 +98,9 @@ const ShareCard: React.FC<ShareCardProps> = ({ RoomID, shareLink }) => {
<div className="bg-blue-100 p-4 rounded-md">
<p className="text-blue-700 mb-4">{messages.text.RetrieveMethod.P}</p>
{/* 使用 flex-col 替代 list,更好控制移动端布局 */}
{/* Use flex-col instead of list for better control on mobile layout */}
<div className="flex flex-col space-y-4">
{/* RoomID 部分 */}
{/* RoomID section */}
<div className="flex flex-col space-y-2">
<div className="flex flex-wrap items-center gap-2">
<span>{messages.text.RetrieveMethod.RoomId_tips + RoomID}</span>
@@ -111,7 +111,7 @@ const ShareCard: React.FC<ShareCardProps> = ({ RoomID, shareLink }) => {
</div>
</div>
{/* URL 部分 */}
{/* URL section */}
<div className="flex flex-col space-y-2">
<div className="break-all">
{messages.text.RetrieveMethod.url_tips + shareLink}
@@ -124,7 +124,7 @@ const ShareCard: React.FC<ShareCardProps> = ({ RoomID, shareLink }) => {
</div>
</div>
{/* QR Code 部分 */}
{/* QR Code section */}
<div className="flex flex-col space-y-2">
<div>{messages.text.RetrieveMethod.scanQR_tips}</div>
<div className="flex flex-wrap gap-2">
@@ -157,7 +157,7 @@ const ShareCard: React.FC<ShareCardProps> = ({ RoomID, shareLink }) => {
</div>
</div>
{/* QR Code 显示区域 */}
{/* QR Code display area */}
<div className="mt-4 flex justify-center">
<div className="inline-block border-2 p-4 bg-white rounded-lg">
<div ref={qrRef}>
@@ -5,7 +5,7 @@ interface TransferProgressProps {
message: string;
progress: Progress;
}
//'Sending' : 'Receiving'
// Display 'Sending' or 'Receiving' message
const TransferProgress: React.FC<TransferProgressProps> = ({ message, progress }) => {
const speed = isNaN(progress.speed) ? 0 : progress.speed;
+1 -2
View File
@@ -9,7 +9,6 @@ interface ArticleListItemProps {
export function ArticleListItem({ post }: ArticleListItemProps) {
return (
<article className="bg-white rounded-xl shadow-lg hover:shadow-xl transition-shadow overflow-hidden">
{/* 增大封面图高度 */}
<div className="relative h-80 w-full">
<Image
src={post.frontmatter.cover}
@@ -21,7 +20,7 @@ export function ArticleListItem({ post }: ArticleListItemProps) {
/>
</div>
<div className="p-8"> {/* 增大内边距 */}
<div className="p-8">
<div className="flex items-center gap-4 text-sm text-gray-500 mb-4">
<time className="font-medium">
{new Date(post.frontmatter.date).toLocaleDateString()}
+2 -2
View File
@@ -1,7 +1,7 @@
import Image from 'next/image'
import { ComponentProps, DetailedHTMLProps, HTMLAttributes } from 'react'
import dynamic from 'next/dynamic'
// 动态导入 Mermaid 组件
// Dynamically import the Mermaid component
const Mermaid = dynamic(() => import('@/components/blog/Mermaid'), { ssr: false });
export type MDXComponents = {
@@ -126,6 +126,6 @@ export const mdxComponents: MDXComponents = {
{children}
</li>
),
mermaid: Mermaid, // 使用定义的 Mermaid 组件
mermaid: Mermaid, // Use the defined Mermaid component
}
+2 -2
View File
@@ -1,9 +1,9 @@
'use client' // 标记为客户端组件
'use client' // Mark as client component
import mermaid from 'mermaid'
import { useEffect, useRef } from 'react'
// 初始化 Mermaid.js
// Initialize Mermaid.js
mermaid.initialize({ startOnLoad: false })
const Mermaid: React.FC<{ children: string }> = ({ children }) => {
+17 -17
View File
@@ -16,30 +16,30 @@ export const TableOfContents: React.FC<TableOfContentsProps> = ({ content }) =>
const [activeId, setActiveId] = useState<string>('');
const [toc, setToc] = useState<TocItem[]>([]);
// 生成合法的 ID,保留中文字符
// Generate a valid ID, preserving Chinese characters
const generateValidId = (text: string): string => {
return encodeURIComponent(text
.trim() // 移除首尾空格
.replace(/\s+/g, '-') // 将空格替换为连字符
.replace(/\-\-+/g, '-') // 将多个连字符替换为单个
.replace(/^-+/, '') // 移除开头的连字符
.replace(/-+$/, '') // 移除结尾的连字符
.trim() // Remove leading/trailing spaces
.replace(/\s+/g, '-') // Replace spaces with hyphens
.replace(/\-\-+/g, '-') // Replace multiple hyphens with a single one
.replace(/^-+/, '') // Remove leading hyphens
.replace(/-+$/, '') // Remove trailing hyphens
);
};
useEffect(() => {
// 解析内容生成目录
// Parse content to generate table of contents
const headingRegex = /^(#{1,3})\s+(.+)$/gm;
const items: TocItem[] = [];
let match;
const usedIds = new Set<string>(); // 用于跟踪已使用的ID
const usedIds = new Set<string>(); // Used to track used IDs
while ((match = headingRegex.exec(content)) !== null) {
const level = match[1].length;
const text = match[2].trim();
let id = generateValidId(text);
// 如果ID已存在,添加数字后缀
// If ID already exists, add a numeric suffix
let counter = 1;
let uniqueId = id;
while (usedIds.has(uniqueId)) {
@@ -66,30 +66,30 @@ export const TableOfContents: React.FC<TableOfContentsProps> = ({ content }) =>
{ rootMargin: '-80px 0px -40% 0px' }
);
// 确保所有标题都已经渲染
// Ensure all headings are rendered
const setupObserver = () => {
const headers = document.querySelectorAll('h1[id], h2[id], h3[id]');
headers.forEach((header) => observer.observe(header));
};
// 确保 DOM 已更新
// Ensure DOM is updated
if (toc.length > 0) {
// 给 DOM 一点时间来更新
// Give the DOM some time to update
setTimeout(setupObserver, 100);
}
return () => observer.disconnect();
}, [toc]); // 依赖于 toc 而不是 content
}, [toc]); // Depends on toc instead of content
const scrollToHeader = (id: string) => {
// 不需要解码 ID,因为它已经是正确的格式
// No need to decode the ID, as it is already in the correct format
const element = document.getElementById(id);
if (element) {
// 获取元素位置
// Get element position
const rect = element.getBoundingClientRect();
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
// 计算目标位置(考虑固定导航栏的高度,假设是 80px
// Calculate target position (considering the fixed navigation bar height, assuming 80px)
const offsetTop = rect.top + scrollTop - 80;
window.scrollTo({
@@ -97,7 +97,7 @@ export const TableOfContents: React.FC<TableOfContentsProps> = ({ content }) =>
behavior: 'smooth'
});
// 设置当前活动项
// Set current active item
setActiveId(id);
}
};
+14 -7
View File
@@ -1,21 +1,28 @@
import Image from 'next/image'
import type { Messages } from '@/types/messages';
import Image from "next/image";
import type { Messages } from "@/types/messages";
interface PageContentProps {
messages: Messages;
}
export default function SystemDiagram({ messages }: PageContentProps) {
return (
<section className="py-16 bg-background">
<div className="container mx-auto px-4">
<h2 className="text-3xl font-bold mb-12 text-center">{messages.text.SystemDiagram.h2}</h2>
<Image src="/SystemDiagram.png" alt="SecureShare system diagram: Peer-to-peer file and clipboard sharing" width={1226} height={745} className="mx-auto mb-6" />
<h2 className="text-3xl font-bold mb-12 text-center">
{messages.text.SystemDiagram.h2}
</h2>
<Image
src="/SystemDiagram.png"
alt="SecureShare system diagram: Peer-to-peer file and clipboard sharing"
width={1226}
height={745}
className="mx-auto mb-6"
/>
<p className="mt-8 text-center max-w-2xl mx-auto">
{messages.text.SystemDiagram.h2_P}
</p>
</div>
</section>
)
}
);
}