From 1a1321d2413fd4eb9758f131097a0493ca80c6bb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 11 May 2026 13:48:11 +0000
Subject: [PATCH] feat: fetch and display CF problem statements in
ProblemWorkspace
- Install cheerio for server-side HTML parsing
- Add cfStatementParser.js with parseCFProblemStatement() helper
- Add GET /api/cf/problem/:contestId/:index/statement route to server.js
- Add cfStatementParser.test.js with 11 unit tests (mocked HTML fixtures)
- Add src/services/cfStatement.js frontend fetch service
- Update ProblemWorkspace.jsx with statement panel, loading/error states
- Update ProblemWorkspace.css with statement panel styles
- Update eslint.config.js for node globals on new server files
- Update vite.config.js with /api proxy for dev server
Agent-Logs-Url: https://github.com/enlorik/HDD/sessions/ba819bfd-a6b0-4a90-8ea5-965d62ff0a58
Co-authored-by: enlorik <99548776+enlorik@users.noreply.github.com>
---
cfStatementParser.js | 115 +++++++++++
cfStatementParser.test.js | 214 ++++++++++++++++++++
eslint.config.js | 2 +-
package-lock.json | 304 ++++++++++++++++++++++++++++
package.json | 1 +
server.js | 61 ++++++
src/components/ProblemWorkspace.css | 113 ++++++++++-
src/components/ProblemWorkspace.jsx | 185 ++++++++++++++---
src/services/cfStatement.js | 28 +++
vite.config.js | 5 +
10 files changed, 996 insertions(+), 32 deletions(-)
create mode 100644 cfStatementParser.js
create mode 100644 cfStatementParser.test.js
create mode 100644 src/services/cfStatement.js
diff --git a/cfStatementParser.js b/cfStatementParser.js
new file mode 100644
index 0000000..dd7b838
--- /dev/null
+++ b/cfStatementParser.js
@@ -0,0 +1,115 @@
+/**
+ * Server-side helper: parse a Codeforces problem page HTML string into
+ * structured, plain-text fields safe to return to the React frontend.
+ *
+ * Returns null when the page does not contain a `.problem-statement` element
+ * (e.g. the problem/contest does not exist or the URL was wrong).
+ */
+
+import * as cheerio from 'cheerio';
+
+/**
+ * Replace block-level children and
tags with newlines, then return the
+ * trimmed text content of an element. This preserves paragraph breaks without
+ * leaking any HTML tags to the caller.
+ *
+ * @param {import('cheerio').CheerioAPI} $ - cheerio root
+ * @param {import('cheerio').Cheerio} el - element to extract text from
+ * @returns {string}
+ */
+function blockText($, el) {
+ const clone = $(el).clone();
+ clone.find('br').replaceWith('\n');
+ clone.find('p').each((_, p) => {
+ $(p).prepend('\n').append('\n');
+ });
+ return clone
+ .text()
+ .replace(/\r\n|\r/g, '\n')
+ .replace(/\n{3,}/g, '\n\n')
+ .trim();
+}
+
+/**
+ * Parse a raw Codeforces problem page HTML string.
+ *
+ * @param {string} html - full HTML of the Codeforces problemset/problem page
+ * @returns {{ title: string, timeLimit: string, memoryLimit: string,
+ * statement: string, inputSpecification: string,
+ * outputSpecification: string,
+ * samples: Array<{ input: string, output: string }> } | null}
+ */
+export function parseCFProblemStatement(html) {
+ const $ = cheerio.load(html);
+
+ const stmtEl = $('.problem-statement').first();
+ if (!stmtEl.length) return null;
+
+ const header = stmtEl.find('.header').first();
+
+ // ---- title ----------------------------------------------------------------
+ const title = header.find('.title').first().text().trim();
+
+ // ---- limits ---------------------------------------------------------------
+ // The limit divs look like:
+ //
You are given two integers $a$ and $b$.
+Print their sum.
+ +The first line contains two integers $a$ and $b$ ($1 <= a, b <= 10^9$).
+Print a single integer — the sum of $a$ and $b$.
+3 4+
7+
1000000000 1000000000+
2000000000+
In the first example the answer is 7.
+First paragraph.
+Second paragraph.
+Third paragraph.
+ +First line is $n$.
+Second line contains $n$ integers.
+Print the answer.
+3 +1 2 3+
6+