Fix/open app (#658)

* fix: registry-js not copied

* Update wiki

* Update getWorkspaceMenuTemplate.ts

* fix: tiddlers\新条目.tid become "tiddlers/\346\226\260\346\235\241\347\233\256.tid" in git log

* fix: git can't show and discard newly added or deleted files

* refactor: duplicate code

* lint

* fix: type
This commit is contained in:
lin onetwo 2025-11-24 03:42:17 +08:00 committed by GitHub
parent 45e3f76da1
commit a674cd269f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 386 additions and 79 deletions

View file

@ -13,6 +13,7 @@ import Typography from '@mui/material/Typography';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { getFileStatusStyles, type GitFileStatus } from './fileStatusStyles';
import type { GitLogEntry } from './types';
const Panel = styled(Box)`
@ -53,6 +54,17 @@ const ActionsWrapper = styled(Box)`
gap: 12px;
`;
const FileStatusBadge = styled(Box)<{ $status?: GitFileStatus }>`
display: inline-block;
font-size: 0.6rem;
padding: 1px 4px;
margin-right: 4px;
border-radius: 2px;
font-weight: 600;
text-transform: uppercase;
${({ $status, theme }) => getFileStatusStyles($status, theme)}
`;
interface ICommitDetailsPanelProps {
commit: GitLogEntry | null;
isLatestCommit?: boolean;
@ -241,13 +253,18 @@ export function CommitDetailsPanel(
{fileChanges.map((file, index) => (
<ListItem key={index} disablePadding>
<ListItemButton
selected={file === selectedFile}
selected={file.path === selectedFile}
onClick={() => {
onFileSelect?.(file === selectedFile ? null : file);
onFileSelect?.(file.path === selectedFile ? null : file.path);
}}
>
<ListItemText
primary={file}
primary={
<>
<FileStatusBadge $status={file.status}>{file.status.charAt(0)}</FileStatusBadge>
{file.path}
</>
}
slotProps={{
primary: {
variant: 'body2',

View file

@ -136,9 +136,10 @@ interface IFileDiffPanelProps {
commitHash: string;
filePath: string | null;
onDiscardSuccess?: () => void;
showSnackbar?: (message: string, severity: 'success' | 'error' | 'info') => void;
}
export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess }: IFileDiffPanelProps): React.JSX.Element {
export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess, showSnackbar: showSnackbarFromParent }: IFileDiffPanelProps): React.JSX.Element {
const { t } = useTranslation();
const [diff, setDiff] = useState<string>('');
const [fileContent, setFileContent] = useState<string>('');
@ -153,6 +154,9 @@ export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess }: IFileD
const [isLoadingFullDiff, setIsLoadingFullDiff] = useState(false);
const [isLoadingFullContent, setIsLoadingFullContent] = useState(false);
// Use parent's showSnackbar if provided, otherwise create local one
const showSnackbar = showSnackbarFromParent ?? (() => {});
const getWorkspace = async () => {
const meta = window.meta();
const workspaceID = (meta as { workspaceID?: string }).workspaceID;
@ -192,11 +196,13 @@ export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess }: IFileD
try {
await window.service.git.discardFileChanges(workspace.wikiFolderLocation, filePath);
showSnackbar(t('GitLog.DiscardSuccess'), 'success');
// Clear selection and trigger refresh
onDiscardSuccess?.();
} catch (error) {
console.error('Failed to discard changes:', error);
// TODO: Show error message
const errorMessage = error instanceof Error ? error.message : String(error);
showSnackbar(t('GitLog.DiscardFailed') + ': ' + errorMessage, 'error');
}
};
@ -207,10 +213,13 @@ export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess }: IFileD
try {
await window.service.git.addToGitignore(workspace.wikiFolderLocation, filePath);
// TODO: Show success message
showSnackbar(t('GitLog.IgnoreSuccess'), 'success');
// Trigger refresh after adding to .gitignore
onDiscardSuccess?.();
} catch (error) {
console.error('Failed to add to .gitignore:', error);
// TODO: Show error message
const errorMessage = error instanceof Error ? error.message : String(error);
showSnackbar(t('GitLog.IgnoreFailed') + ': ' + errorMessage, 'error');
}
};
@ -221,10 +230,13 @@ export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess }: IFileD
try {
await window.service.git.addToGitignore(workspace.wikiFolderLocation, `*.${fileExtension}`);
// TODO: Show success message
showSnackbar(t('GitLog.IgnoreSuccess'), 'success');
// Trigger refresh after adding to .gitignore
onDiscardSuccess?.();
} catch (error) {
console.error('Failed to add extension to .gitignore:', error);
// TODO: Show error message
const errorMessage = error instanceof Error ? error.message : String(error);
showSnackbar(t('GitLog.IgnoreFailed') + ': ' + errorMessage, 'error');
}
};
@ -235,13 +247,13 @@ export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess }: IFileD
const fullPath = `${workspace.wikiFolderLocation}/${filePath}`;
await navigator.clipboard.writeText(fullPath);
// TODO: Show success message
showSnackbar(t('GitLog.CopySuccess'), 'success');
};
const handleCopyRelativePath = async () => {
if (!filePath) return;
await navigator.clipboard.writeText(filePath);
// TODO: Show success message
showSnackbar(t('GitLog.CopySuccess'), 'success');
};
const handleShowInExplorer = async () => {

View file

@ -0,0 +1,61 @@
import type { Theme } from '@mui/material/styles';
import type { GitFileStatus } from '../../services/git/interface';
// Re-export for convenience
export type { GitFileStatus };
/**
* Get styled CSS for file status badge/chip based on the status and theme
*/
export function getFileStatusStyles(status: GitFileStatus | undefined, theme: Theme): string {
const isDark = theme.palette.mode === 'dark';
switch (status) {
case 'added':
case 'untracked':
return isDark
? `
background-color: rgba(46, 160, 67, 0.3);
color: #7ee787;
`
: `
background-color: rgba(46, 160, 67, 0.2);
color: #116329;
`;
case 'deleted':
return isDark
? `
background-color: rgba(248, 81, 73, 0.3);
color: #ffa198;
`
: `
background-color: rgba(248, 81, 73, 0.2);
color: #82071e;
`;
case 'modified':
return isDark
? `
background-color: rgba(187, 128, 9, 0.3);
color: #f0b83f;
`
: `
background-color: rgba(187, 128, 9, 0.2);
color: #7d4e00;
`;
case 'renamed':
return isDark
? `
background-color: rgba(56, 139, 253, 0.3);
color: #79c0ff;
`
: `
background-color: rgba(56, 139, 253, 0.2);
color: #0969da;
`;
default:
return `
background-color: ${theme.palette.action.hover};
color: ${theme.palette.text.secondary};
`;
}
}

View file

@ -1,7 +1,9 @@
import { Helmet } from '@dr.pogodin/react-helmet';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import Container from '@mui/material/Container';
import Snackbar from '@mui/material/Snackbar';
import { styled } from '@mui/material/styles';
import Tab from '@mui/material/Tab';
import Table from '@mui/material/Table';
@ -21,6 +23,7 @@ import { useTranslation } from 'react-i18next';
import { CommitDetailsPanel } from './CommitDetailsPanel';
import { CustomGitTooltip } from './CustomGitTooltip';
import { FileDiffPanel } from './FileDiffPanel';
import { getFileStatusStyles, type GitFileStatus } from './fileStatusStyles';
import type { GitLogEntry } from './types';
import { useCommitDetails } from './useCommitDetails';
import { useGitLogData } from './useGitLogData';
@ -108,18 +111,18 @@ const LoadingContainer = styled(Box)`
height: 100%;
`;
const FileChip = styled(Box)`
const FileChip = styled(Box)<{ $status?: GitFileStatus }>`
display: inline-block;
font-size: 0.7rem;
font-family: monospace;
padding: 2px 4px;
margin: 2px;
background-color: ${({ theme }) => theme.palette.action.hover};
border-radius: 3px;
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
${({ $status, theme }) => getFileStatusStyles($status, theme)}
`;
interface ICommitTableRowProps {
@ -158,10 +161,10 @@ function CommitTableRow({ commit, selected, commitDate, onSelect }: ICommitTable
<TableCell>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{displayFiles.map((file, index) => {
const fileName = file.split('/').pop() || file;
const fileName = file.path.split('/').pop() || file.path;
return (
<Tooltip key={index} title={file} placement='top'>
<FileChip>{fileName}</FileChip>
<Tooltip key={index} title={`${file.path} (${file.status})`} placement='top'>
<FileChip $status={file.status}>{fileName}</FileChip>
</Tooltip>
);
})}
@ -195,6 +198,19 @@ export default function GitHistory(): React.JSX.Element {
const [selectedFile, setSelectedFile] = useState<string | null>(null);
const [viewMode, setViewMode] = useState<'current' | 'all'>('current');
const [shouldSelectFirst, setShouldSelectFirst] = useState(false);
const [snackbar, setSnackbar] = useState<{ open: boolean; message: string; severity: 'success' | 'error' | 'info' }>({
open: false,
message: '',
severity: 'info',
});
const showSnackbar = (message: string, severity: 'success' | 'error' | 'info' = 'info') => {
setSnackbar({ open: true, message, severity });
};
const handleCloseSnackbar = () => {
setSnackbar(previous => ({ ...previous, open: false }));
};
// Create a tooltip wrapper that passes the translation function
// The props coming from react-git-log don't include 't', so we add it
@ -387,9 +403,21 @@ export default function GitHistory(): React.JSX.Element {
// Trigger git log refresh after discard
setShouldSelectFirst(true);
}}
showSnackbar={showSnackbar}
/>
</DiffPanelWrapper>
</ContentWrapper>
<Snackbar
open={snackbar.open}
autoHideDuration={4000}
onClose={handleCloseSnackbar}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert onClose={handleCloseSnackbar} severity={snackbar.severity} sx={{ width: '100%' }}>
{snackbar.message}
</Alert>
</Snackbar>
</Root>
);
}

View file

@ -1,3 +1,5 @@
import type { GitFileStatus, IFileWithStatus } from '../../services/git/interface';
/**
* Represents the author or committer of a commit.
*/
@ -12,6 +14,9 @@ export interface CommitAuthor {
name: string;
}
// Re-export for convenience
export type { GitFileStatus, IFileWithStatus };
/**
* Represents a single entry in the git log.
*/
@ -45,7 +50,7 @@ export interface GitLogEntry {
*/
parents: string[];
/**
* Array of file paths changed in this commit.
* Array of files with status changed in this commit.
*/
files?: string[];
files?: IFileWithStatus[];
}