diff --git a/.opencode/opencode.json b/.opencode/opencode.json new file mode 100644 index 0000000..30e8a0b --- /dev/null +++ b/.opencode/opencode.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://opencode.ai/config.json", + "plugin": [ + "oh-my-openagent" + ] +} \ No newline at end of file diff --git a/backend/api/latex_utils.py b/backend/api/latex_utils.py index 80614f5..2a48307 100644 --- a/backend/api/latex_utils.py +++ b/backend/api/latex_utils.py @@ -33,12 +33,12 @@ "12pt": "\\normalsize", } -# Spacing presets: (section_before, section_after, subsection_before, subsection_after, formula_spacing) +# Spacing presets: (section_before, section_after, subsection_before, subsection_after, formula_gap, baselineskip) SPACING_MAP = { - "tiny": ("0pt", "0pt", "0pt", "0pt", "0pt"), - "small": ("2pt", "1pt", "1pt", "0.5pt", "2pt"), - "medium": ("8pt", "4pt", "4pt", "2pt", "8pt"), - "large": ("16pt", "8pt", "8pt", "4pt", "16pt"), + "tiny": ("0pt", "0pt", "0pt", "0pt", "0pt", "4pt"), + "small": ("2pt", "1pt", "1pt", "0.5pt", "2pt", "6pt"), + "medium": ("8pt", "4pt", "4pt", "2pt", "8pt", "10pt"), + "large": ("16pt", "8pt", "8pt", "4pt", "16pt", "14pt"), } @@ -47,7 +47,7 @@ def build_dynamic_header(columns=2, font_size="10pt", margins="0.25in", spacing= Build a dynamic LaTeX header based on user-selected options. """ size_command = FONT_SIZE_MAP.get(font_size, "\\footnotesize") - sec_before, sec_after, subsec_before, subsec_after, _ = SPACING_MAP.get(spacing, SPACING_MAP["large"]) + sec_before, sec_after, subsec_before, subsec_after, _, baseline_skip = SPACING_MAP.get(spacing, SPACING_MAP["large"]) # The standard `article` class only supports 10pt/11pt/12pt. # Use `extarticle` (from the extsizes package) for 8pt and 9pt. @@ -70,6 +70,7 @@ def build_dynamic_header(columns=2, font_size="10pt", margins="0.25in", spacing= "\\titleformat{\\subsection}{\\normalfont\\scriptsize\\bfseries}{}{0pt}{}", f"\\titlespacing*{{\\section}}{{0pt}}{{{sec_before}}}{{{sec_after}}}", f"\\titlespacing*{{\\subsection}}{{0pt}}{{{subsec_before}}}{{{subsec_after}}}", + f"\\setlength{{\\baselineskip}}{{{baseline_skip}}}", "", "\\begin{document}", size_command, @@ -103,7 +104,7 @@ def build_latex_for_formulas(selected_formulas, columns=2, font_size="10pt", mar """ header = build_dynamic_header(columns, font_size, margins, spacing) footer = build_dynamic_footer(columns) - _, _, _, _, formula_gap = SPACING_MAP.get(spacing, SPACING_MAP["large"]) + _, _, _, _, formula_gap, _ = SPACING_MAP.get(spacing, SPACING_MAP["large"]) if not selected_formulas: return header + footer @@ -124,10 +125,10 @@ def build_latex_for_formulas(selected_formulas, columns=2, font_size="10pt", mar body_lines.append(r"\end{flushleft}") in_flushleft = False if current_class is not None: - body_lines.append("") + body_lines.append("%") # Suppress paragraph break between sections escaped_class = class_name.replace("&", "\\&") body_lines.append("\\section*{" + escaped_class + "}") - body_lines.append("") + body_lines.append("%") # Suppress paragraph break after section current_class = class_name current_category = None @@ -139,7 +140,7 @@ def build_latex_for_formulas(selected_formulas, columns=2, font_size="10pt", mar if not is_special: escaped_category = category.replace("&", "\\&") body_lines.append("\\subsection*{" + escaped_category + "}") - body_lines.append("") + body_lines.append("%") body_lines.append(r"\begin{flushleft}") in_flushleft = True current_category = category @@ -149,9 +150,10 @@ def build_latex_for_formulas(selected_formulas, columns=2, font_size="10pt", mar else: # Escape special LaTeX characters in the formula name escaped_name = name.replace("\\", "\\textbackslash ").replace("&", "\\&").replace("%", "\\%").replace("#", "\\#").replace("_", "\\_").replace("^", "\\textasciicircum ").replace("{", "\\{").replace("}", "\\}") - body_lines.append("\\textbf{" + escaped_name + "}") - body_lines.append("\\[ \\adjustbox{max width=\\linewidth}{\\displaystyle " + latex + "} \\]") - body_lines.append(f"\\\\[{formula_gap}]") + body_lines.append(r"\textbf{" + escaped_name + "}") + body_lines.append(r"\[" + r" \adjustbox{max width=\linewidth}{$" + latex + r"$} " + r"\]") + if formula_gap != "0pt": + body_lines.append(r"\vspace{" + formula_gap + "}") if in_flushleft: body_lines.append(r"\end{flushleft}") diff --git a/backend/api/tests.py b/backend/api/tests.py index 84cf7db..ac3f6f0 100644 --- a/backend/api/tests.py +++ b/backend/api/tests.py @@ -242,13 +242,13 @@ def test_filter_problems_by_sheet(self, api_client, sample_problem, sample_sheet class TestGenerateSheetEndpoint: def test_generate_sheet_no_formulas(self, api_client): resp = api_client.post("/api/generate-sheet/", {"formulas": []}, format="json") - assert resp.status_code == 400 - assert "error" in resp.json() + assert resp.status_code == 200 + assert "tex_code" in resp.json() def test_generate_sheet_missing_formulas_key(self, api_client): resp = api_client.post("/api/generate-sheet/", {}, format="json") - assert resp.status_code == 400 - assert "error" in resp.json() + assert resp.status_code == 200 + assert "tex_code" in resp.json() def test_generate_sheet_valid_formula(self, api_client): resp = api_client.post( diff --git a/backend/api/views.py b/backend/api/views.py index 8181847..23bd3be 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -22,7 +22,7 @@ def validate_layout_params(columns, font_size, margins, spacing): try: - columns = max(1, min(3, int(columns))) + columns = max(1, min(4, int(columns))) except (TypeError, ValueError): columns = 2 @@ -72,11 +72,12 @@ def generate_sheet(request): margins = request.data.get("margins", "0.25in") spacing = request.data.get("spacing", "large") - if not selected: - return Response({"error": "No formulas selected"}, status=400) - columns, font_size, margins, spacing = validate_layout_params(columns, font_size, margins, spacing) + if not selected: + tex_code = build_latex_for_formulas([], columns, font_size, margins, spacing) + return Response({"tex_code": tex_code}) + formula_data = get_formula_data() selected_formulas = [] diff --git a/frontend/src/components/CreateCheatSheet.jsx b/frontend/src/components/CreateCheatSheet.jsx index f110837..771eab6 100644 --- a/frontend/src/components/CreateCheatSheet.jsx +++ b/frontend/src/components/CreateCheatSheet.jsx @@ -449,6 +449,7 @@ const LayoutOptions = ({ columns, setColumns, fontSize, setFontSize, spacing, se +
@@ -601,7 +602,10 @@ const CreateCheatSheet = ({ onSave, initialData }) => { onRemoveClass={removeClassFromOrder} onRemoveFormula={removeSingleFormula} /> +
+ {/* Box 2: Editor and preview */} +
{ margins={margins} setMargins={setMargins} /> -
- - {/* Box 2: Editor and preview */} -
+
1) { + const afterMulticols = newParts[1]; + const columnMatch = afterMulticols.match(/^\{(\d+)\}/); + + if (columnMatch) { + const columnCount = columnMatch[1]; + const afterColumn = afterMulticols.slice(columnMatch[0].length); + + const beforeEnd = afterColumn.split('\\end{multicols}')[0]; + const afterEnd = afterColumn.split('\\end{multicols}').slice(1).join('\\end{multicols}'); + + contentToCompile = newParts[0] + '\\begin{multicols}{' + columnCount + '}' + beforeEnd + formulaContent + '\\end{multicols}' + afterEnd; + } else { + contentToCompile = newLatex; + } + } else { + contentToCompile = newLatex; + } + } else { + contentToCompile = newLatex; + } + } + } catch (e) { + console.log('Regenerate failed, using existing content:', e); + } + try { const response = await fetch('/api/compile/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ content }), + body: JSON.stringify({ content: contentToCompile }), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); @@ -163,7 +221,7 @@ export function useLatex(initialData) { setIsCompiling(false); isCompilingRef.current = false; } - }, [content]); + }, [content, columns, fontSize, spacing, margins]); const handlePreview = useCallback(async (latexContent = null, regenerateOptions = null) => { if (isCompilingRef.current) return;