Skip to content

feat: Implement basic cheat sheet functionality with LaTeX support#13

Closed
Davictory2003 wants to merge 1 commit intomainfrom
frontend
Closed

feat: Implement basic cheat sheet functionality with LaTeX support#13
Davictory2003 wants to merge 1 commit intomainfrom
frontend

Conversation

@Davictory2003
Copy link
Contributor

Added ability to save cheatsheet as pdf, as well as a working markdown and LaTeX editor. Still need to implement prebuilt formulas

Copilot AI review requested due to automatic review settings February 24, 2026 21:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements a basic frontend-only cheat sheet workflow (create/list/view/edit) with Markdown + LaTeX rendering and client-side PDF export, persisting data in localStorage.

Changes:

  • Added Create/List/View components with Markdown + KaTeX rendering.
  • Added PDF download via html2canvas + jsPDF.
  • Added client-side persistence and updated styling + frontend dependencies.

Reviewed changes

Copilot reviewed 7 out of 9 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
package-lock.json Adds a root lockfile (currently without a root package.json).
frontend/src/components/CreateCheatSheet.jsx Markdown + LaTeX editor with live preview.
frontend/src/components/CheatSheetView.jsx Renders a sheet and supports PDF download.
frontend/src/components/CheatSheetList.jsx Lists saved sheets and supports selection/deletion.
frontend/src/App.jsx Adds view routing/state, localStorage persistence, and wires components.
frontend/src/App.css Adds layout and component styling.
frontend/package.json Adds dependencies for KaTeX/Markdown and PDF generation.
frontend/package-lock.json Locks newly added frontend dependencies.
README.md Updates setup instructions (removes .env guidance).
Files not reviewed (1)
  • frontend/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

<h1>Cheat Sheet</h1>
<p>Backend status: {status ?? 'loading...'}</p>
<header className="app-header">
<h1 onClick={() => setView('list')} style={{cursor: 'pointer'}}>Cheat Sheet Generator</h1>
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This h1 is clickable but not keyboard accessible and has no semantic role. For accessibility, use a / for navigation, or add role="button", tabIndex={0}, and key handlers (Enter/Space) so keyboard and assistive tech users can activate it.

Suggested change
<h1 onClick={() => setView('list')} style={{cursor: 'pointer'}}>Cheat Sheet Generator</h1>
<h1
onClick={() => setView('list')}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
event.preventDefault();
setView('list');
}
}}
role="button"
tabIndex={0}
style={{cursor: 'pointer'}}
>
Cheat Sheet Generator
</h1>

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +43
// Add subsequent pages if needed
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pdfHeight;
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PDF pagination loop uses while (heightLeft >= 0), which will add an extra blank/duplicate page when the content height fits exactly into an integer number of pages (heightLeft becomes 0 after subtracting pdfHeight). Use a strict > 0 check or adjust the loop to only add pages when there is remaining content.

Copilot uses AI. Check for mistakes.
heightLeft -= pdfHeight;
}

pdf.save(`${sheet.title.replace(/\s+/g, '_')}.pdf`);
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The download filename assumes sheet.title is a valid string and only replaces whitespace. If title is empty/undefined this will throw, and characters like /\\:*?"<>| can create invalid filenames on some platforms. Consider a safe fallback (e.g., "cheatsheet") and sanitizing disallowed filename characters.

Suggested change
pdf.save(`${sheet.title.replace(/\s+/g, '_')}.pdf`);
const rawTitle = (sheet && typeof sheet.title === 'string') ? sheet.title : '';
const baseTitle = rawTitle.trim() || 'cheatsheet';
const sanitizedBaseTitle = baseTitle
.replace(/[\/\\:*?"<>|]/g, '') // remove characters invalid in many filesystems
.replace(/\s+/g, '_') || 'cheatsheet';
pdf.save(`${sanitizedBaseTitle}.pdf`);

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +18
display: flex !important;
flex-direction: row !important;
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using !important for basic layout rules makes overrides/debugging harder and usually indicates a specificity issue elsewhere. Prefer removing !important and adjusting selector specificity or component structure so flex layout applies without forcing it.

Suggested change
display: flex !important;
flex-direction: row !important;
display: flex;
flex-direction: row;

Copilot uses AI. Check for mistakes.
Comment on lines 24 to 30
```bash
cd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# create a .env file in the backend/ directory with the required settings, for example:
# DJANGO_SECRET_KEY=your-dev-secret-key
# DJANGO_DEBUG=True
# DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
python manage.py migrate
python manage.py runserver
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend settings load configuration from backend/.env and require DJANGO_SECRET_KEY in production (they only fall back to a dev key when DJANGO_DEBUG=True). Removing the README instructions for creating a .env file makes initial setup and deployment requirements less clear; consider re-adding a brief note about optional dev defaults vs required prod env vars.

Copilot uses AI. Check for mistakes.

useEffect(() => {
if (sheets.length > 0) {
localStorage.setItem('cheatSheets', JSON.stringify(sheets));
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This effect only writes to localStorage when sheets.length > 0. If sheets becomes an empty array via any path other than the explicit delete handlers (e.g., parse failure recovery, future features), localStorage will retain stale data. Consider writing on every change and removing the key (or setting "[]") when sheets is empty.

Suggested change
localStorage.setItem('cheatSheets', JSON.stringify(sheets));
localStorage.setItem('cheatSheets', JSON.stringify(sheets));
} else {
localStorage.removeItem('cheatSheets');

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +17
<span className="sheet-title" onClick={() => onSelect(sheet)}>
{sheet.title}
</span>
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sheet title is rendered in a with onClick, which is not keyboard accessible and lacks button semantics. Consider using a (styled as a link) or adding role="button", tabIndex={0}, and onKeyDown handlers so users can select a sheet via keyboard.

Suggested change
<span className="sheet-title" onClick={() => onSelect(sheet)}>
{sheet.title}
</span>
<button
type="button"
className="sheet-title"
onClick={() => onSelect(sheet)}
>
{sheet.title}
</button>

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +5
import CheatSheetList from './components/CheatSheetList';
import CreateCheatSheet from './components/CreateCheatSheet';
import CheatSheetView from './components/CheatSheetView';
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file (and other existing frontend entry files like frontend/src/main.jsx) omits semicolons, but the newly added import lines include them. To keep code style consistent and reduce formatting churn, consider removing semicolons (or run the repo's formatter if one is intended to enforce semicolons).

Suggested change
import CheatSheetList from './components/CheatSheetList';
import CreateCheatSheet from './components/CreateCheatSheet';
import CheatSheetView from './components/CheatSheetView';
import CheatSheetList from './components/CheatSheetList'
import CreateCheatSheet from './components/CreateCheatSheet'
import CheatSheetView from './components/CheatSheetView'

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +18
setSheets(JSON.parse(savedSheets));
} catch (e) {
console.error("Failed to parse sheets", e);
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If JSON.parse fails, the invalid "cheatSheets" value is left in localStorage, so the app will hit this error on every load. Consider removing the key (or resetting it) in the catch block after logging, and optionally validate that the parsed value is an array before calling setSheets.

Suggested change
setSheets(JSON.parse(savedSheets));
} catch (e) {
console.error("Failed to parse sheets", e);
const parsedSheets = JSON.parse(savedSheets);
if (Array.isArray(parsedSheets)) {
setSheets(parsedSheets);
} else {
console.error("Invalid sheets data in localStorage, expected an array. Resetting.");
localStorage.removeItem('cheatSheets');
setSheets([]);
}
} catch (e) {
console.error("Failed to parse sheets", e);
localStorage.removeItem('cheatSheets');

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +67
const handleDelete = (id) => {
if (window.confirm('Are you sure you want to delete this cheat sheet?')) {
const updatedSheets = sheets.filter(s => s.id !== id);
setSheets(updatedSheets);
// If we delete all, clear storage or keep empty array
if (updatedSheets.length === 0) {
localStorage.removeItem('cheatSheets');
}
if (activeSheet && activeSheet.id === id) {
setActiveSheet(null);
setView('list');
}
}
};

const handleSelect = (sheet) => {
setActiveSheet(sheet);
setView('view');
};
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleDelete and handleSelect are defined but the list view passes inline onDelete/onSelect callbacks instead, duplicating logic and making future changes error-prone (e.g., activeSheet cleanup is handled in handleDelete but not in the inline handler). Prefer reusing handleDelete/handleSelect when wiring props to keep behavior consistent.

Copilot uses AI. Check for mistakes.
@Davictory2003 Davictory2003 deleted the frontend branch February 24, 2026 21:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants