Skip to content

feat: Implement Cheat Sheet Editor with LaTeX support#15

Open
Davictory2003 wants to merge 1 commit intomainfrom
listing
Open

feat: Implement Cheat Sheet Editor with LaTeX support#15
Davictory2003 wants to merge 1 commit intomainfrom
listing

Conversation

@Davictory2003
Copy link
Contributor

Supports downloading as PDF

Copilot AI review requested due to automatic review settings February 25, 2026 00:45
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 new in-browser cheat sheet editor in the frontend with Markdown + LaTeX rendering and an option to download the preview as a PDF.

Changes:

  • Added CreateCheatSheet editor component with live preview (React Markdown + KaTeX) and PDF export (html2canvas + jsPDF).
  • Updated the app shell to persist the current cheat sheet in localStorage and render the new editor UI.
  • Added editor styling and new frontend dependencies; adjusted root centering CSS.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
frontend/src/index.css Removes centered layout by commenting out text-align: center.
frontend/src/components/CreateCheatSheet.jsx New editor component with Markdown+LaTeX preview and PDF download logic.
frontend/src/App.jsx Replaces backend health check UI with cheat sheet editor + localStorage persistence.
frontend/src/App.css Adds layout and component styles for the editor and preview.
frontend/package.json Adds dependencies for Markdown/LaTeX rendering and PDF generation.
frontend/package-lock.json Locks the newly added dependency tree.
README.md Removes backend .env setup snippet from local dev instructions.
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.

Comment on lines 26 to 30
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 25, 2026

Choose a reason for hiding this comment

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

The README no longer mentions how to configure runtime environment variables for the backend. Since backend/cheat_sheet/settings.py loads backend/.env and supports DJANGO_DEBUG, DJANGO_SECRET_KEY, and DJANGO_ALLOWED_HOSTS, it would be helpful to keep (or replace) a brief example of these variables for local setup.

Copilot uses AI. Check for mistakes.
margin: 0 auto;
padding: 2rem;
text-align: center;
/* text-align: center; */
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Leaving text-align: center as a commented-out rule in index.css adds noise and makes it unclear what the intended default layout is. Prefer removing the line entirely (or moving it behind a more specific selector) if centering is no longer needed.

Suggested change
/* text-align: center; */

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

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

.editor-container uses display: flex !important and flex-direction: row !important. If there isn’t a specific specificity/override problem, the !important flags will make future styling harder. Prefer removing !important and adjusting selector specificity instead.

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

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +50
try {
// Create canvas from the preview element
// Temporarily remove styles that might mess up the PDF
const originalStyle = previewRef.current.style.cssText;
// Force clean background and no border for capture
previewRef.current.style.border = 'none';
previewRef.current.style.boxShadow = 'none';
previewRef.current.style.background = '#ffffff';

const canvas = await html2canvas(previewRef.current, {
scale: 2,
useCORS: true,
backgroundColor: '#ffffff',
logging: false,
x: 0,
y: 0,
width: previewRef.current.offsetWidth,
height: previewRef.current.offsetHeight
});

// Restore original styles
previewRef.current.style.cssText = originalStyle;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

handleDownloadPDF mutates previewRef.current.style but restores it only after html2canvas succeeds; if html2canvas throws, the preview will remain borderless/white for the rest of the session. Move the style restoration into a finally block (or restore inside catch as well) so it always runs.

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +18
const [title, setTitle] = useState(initialData ? initialData.title : '');
const [content, setContent] = useState(initialData ? initialData.content : '');
const previewRef = useRef(null);

useEffect(() => {
if (initialData) {
if (initialData.title) setTitle(initialData.title);
if (initialData.content) setContent(initialData.content);
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The useEffect only updates state when initialData.title/content are truthy, so switching to an empty string (or a saved sheet missing fields) won’t update the editor and can leave title/content as undefined, which then gets passed to controlled inputs. Prefer setting setTitle(initialData?.title ?? '') and setContent(initialData?.content ?? '') unconditionally when initialData changes (and similarly use nullish coalescing in the initial useState).

Suggested change
const [title, setTitle] = useState(initialData ? initialData.title : '');
const [content, setContent] = useState(initialData ? initialData.content : '');
const previewRef = useRef(null);
useEffect(() => {
if (initialData) {
if (initialData.title) setTitle(initialData.title);
if (initialData.content) setContent(initialData.content);
}
const [title, setTitle] = useState(initialData?.title ?? '');
const [content, setContent] = useState(initialData?.content ?? '');
const previewRef = useRef(null);
useEffect(() => {
setTitle(initialData?.title ?? '');
setContent(initialData?.content ?? '');

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,133 @@
import React, { useState, useEffect, useRef } from 'react';
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

import React, ... imports the default React binding but it isn’t used anywhere in this file (new JSX transform doesn’t require it). Remove the unused default import to avoid lint/build warnings.

Suggested change
import React, { useState, useEffect, useRef } from 'react';
import { useState, useEffect, useRef } from 'react';

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +64
const imgProps = pdf.getImageProperties(imgData);
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;

// Add image to PDF - if height is greater than page, it will cut off.
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

This implementation renders the entire preview to a single image and adds it to one A4 page; when the rendered height exceeds the page it will be truncated (as noted in the comment). If “Download as PDF” is a supported feature, consider splitting the canvas across multiple pages (or scaling to fit) so longer cheat sheets export correctly.

Suggested change
const imgProps = pdf.getImageProperties(imgData);
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
// Add image to PDF - if height is greater than page, it will cut off.
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfPageHeight = pdf.internal.pageSize.getHeight();
// Scale the canvas image to fit the PDF width
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const scale = pdfWidth / canvasWidth;
const scaledCanvasHeight = canvasHeight * scale;
if (scaledCanvasHeight <= pdfPageHeight) {
// Content fits on a single page; keep existing behavior
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, scaledCanvasHeight);
} else {
// Content exceeds a single page; split canvas into multiple pages
const pageHeightInCanvasPx = pdfPageHeight / scale;
let renderedHeight = 0;
while (renderedHeight < canvasHeight) {
// Create a temporary canvas for the current page slice
const pageCanvas = document.createElement('canvas');
pageCanvas.width = canvasWidth;
pageCanvas.height = Math.min(pageHeightInCanvasPx, canvasHeight - renderedHeight);
const pageCtx = pageCanvas.getContext('2d');
// Draw the current slice from the full canvas into the page canvas
pageCtx.drawImage(
canvas,
0,
renderedHeight,
canvasWidth,
pageCanvas.height,
0,
0,
canvasWidth,
pageCanvas.height
);
const pageData = pageCanvas.toDataURL('image/png');
const pageHeightScaled = pageCanvas.height * scale;
if (renderedHeight > 0) {
pdf.addPage();
}
pdf.addImage(pageData, 'PNG', 0, 0, pdfWidth, pageHeightScaled);
renderedHeight += pageHeightInCanvasPx;
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +6
import CreateCheatSheet from './components/CreateCheatSheet';

function App() {
const [status, setStatus] = useState(null)
const [cheatSheet, setCheatSheet] = useState({ title: '', content: '' });
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

New code introduces semicolons, which is inconsistent with the existing frontend style (e.g., frontend/src/main.jsx:1-10 omits semicolons). Consider removing the semicolons here (and in CreateCheatSheet.jsx) to keep formatting consistent across the codebase.

Copilot uses AI. Check for mistakes.

// Add image to PDF - if height is greater than page, it will cut off.
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
pdf.save(`${title || 'cheat-sheet'}.pdf`);
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The saved filename uses the raw title string; titles containing characters like /, \, : etc. can produce invalid or confusing filenames on some platforms. Consider sanitizing title to a safe filename (and trimming whitespace) before passing it to pdf.save.

Copilot uses AI. Check for mistakes.
<CreateCheatSheet
initialData={cheatSheet}
onSave={handleSave}
onCancel={() => {}}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

onCancel is passed to CreateCheatSheet as an empty callback, but the child component doesn’t use onCancel at all. Removing this prop (or implementing a real cancel flow) would simplify the API surface and avoid dead code paths.

Suggested change
onCancel={() => {}}

Copilot uses AI. Check for mistakes.
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.

3 participants