0.2.1 Release (#32)

* feat(security): implement CSRF protection

* chore: clean up CSRF implementation

  - Remove unused generateCsrfToken export from security.ts
  - Remove redundant /csrf-token path check (GET already exempt)
  - Restore defineConfig wrapper in vitest.config.ts for type safety

* add K8S note in README, fix broken e2e

* feat/upload-bar (#30)

* feat/upload-bar: add a upload bar when user upload file, indicate the upload process

* feat/save-loading-status: add save status when click back button from editor

* fix: address PR review issues in upload and save features

- Replace deprecated substr() with substring() in UploadContext
- Fix broken error handling that checked stale task status
- Fix missing useEffect dependency in UploadStatus
- Fix CSS class conflict in progress bar styling
- Add error recovery for save state in Editor (reset on failure)
- Use .finally() instead of .then() to ensure refresh on upload failure
- Fix inconsistent indentation in UploadContext

* fix e2e tests

---------

Co-authored-by: Zimeng Xiong <zxzimeng@gmail.com>

* chore: pre-release v0.2.1-dev

* Update backend/src/security.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix filename/math random UUID generation

---------

Co-authored-by: AdrianAcala <adrianacala017@gmail.com>
Co-authored-by: adamant368 <60790941+Yiheng-Liu@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Zimeng Xiong
2026-01-14 11:25:27 -08:00
committed by GitHub
parent e75b727a5a
commit 0476315322
37 changed files with 2074 additions and 685 deletions
+13 -31
View File
@@ -9,7 +9,7 @@ import type { DrawingSummary, Collection } from '../types';
import { useDebounce } from '../hooks/useDebounce';
import clsx from 'clsx';
import { ConfirmModal } from '../components/ConfirmModal';
import { importDrawings } from '../utils/importUtils';
import { useUpload } from '../context/UploadContext';
type Point = { x: number; y: number };
@@ -78,7 +78,6 @@ export const Dashboard: React.FC = () => {
const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false);
const [showImportError, setShowImportError] = useState<{ isOpen: boolean; message: string }>({ isOpen: false, message: '' });
const [showImportSuccess, setShowImportSuccess] = useState(false);
const [isDragSelecting, setIsDragSelecting] = useState(false);
const [dragStart, setDragStart] = useState<Point | null>(null);
@@ -99,6 +98,8 @@ export const Dashboard: React.FC = () => {
const [isLoading, setIsLoading] = useState(false);
const { uploadFiles } = useUpload();
const refreshData = useCallback(async () => {
setIsLoading(true);
try {
@@ -303,17 +304,12 @@ export const Dashboard: React.FC = () => {
const fileArray = Array.from(files);
const targetCollectionId = selectedCollectionId === undefined ? null : selectedCollectionId;
const result = await importDrawings(fileArray, targetCollectionId, refreshData);
if (result.failed > 0) {
setShowImportError({
isOpen: true,
message: `Import complete with errors.\nSuccess: ${result.success}\nFailed: ${result.failed}\nErrors:\n${result.errors.join('\n')}`
});
} else {
setShowImportSuccess(true);
}
// Use the global upload context
uploadFiles(fileArray, targetCollectionId).finally(() => {
// Refresh after all uploads complete (success or failure)
refreshData();
});
};
const handleRenameDrawing = async (id: string, name: string) => {
@@ -525,8 +521,7 @@ export const Dashboard: React.FC = () => {
// Handle Files
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
const files = Array.from(e.dataTransfer.files);
setIsLoading(true);
const libFiles = files.filter(f => f.name.endsWith('.excalidrawlib'));
if (libFiles.length > 0) {
setShowImportError({
@@ -537,13 +532,11 @@ export const Dashboard: React.FC = () => {
const drawingFiles = files.filter(f => !f.name.endsWith('.excalidrawlib'));
if (drawingFiles.length > 0) {
const result = await importDrawings(drawingFiles, targetCollectionId, refreshData);
if (result.failed > 0) {
alert(`Import complete with errors.\nSuccess: ${result.success}\nFailed: ${result.failed}\nErrors:\n${result.errors.join('\n')}`);
}
uploadFiles(drawingFiles, targetCollectionId).finally(() => {
refreshData();
});
}
setIsLoading(false);
return;
}
@@ -938,17 +931,6 @@ export const Dashboard: React.FC = () => {
onCancel={() => setShowImportError({ isOpen: false, message: '' })}
/>
<ConfirmModal
isOpen={showImportSuccess}
title="Import Successful"
message="Drawings imported successfully."
confirmText="OK"
showCancel={false}
isDangerous={false}
variant="success"
onConfirm={() => setShowImportSuccess(false)}
onCancel={() => setShowImportSuccess(false)}
/>
</Layout>
);
};