Add diff viewer line marking and submitting fixes by line
This commit is contained in:
parent
b4b7d989cb
commit
6596dc6d9b
15 changed files with 1610 additions and 82 deletions
154
.astro/content.d.ts
vendored
Normal file
154
.astro/content.d.ts
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
declare module 'astro:content' {
|
||||
export interface RenderResult {
|
||||
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
|
||||
headings: import('astro').MarkdownHeading[];
|
||||
remarkPluginFrontmatter: Record<string, any>;
|
||||
}
|
||||
interface Render {
|
||||
'.md': Promise<RenderResult>;
|
||||
}
|
||||
|
||||
export interface RenderedContent {
|
||||
html: string;
|
||||
metadata?: {
|
||||
imagePaths: Array<string>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
|
||||
|
||||
export type CollectionKey = keyof DataEntryMap;
|
||||
export type CollectionEntry<C extends CollectionKey> = Flatten<DataEntryMap[C]>;
|
||||
|
||||
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
|
||||
|
||||
export type ReferenceDataEntry<
|
||||
C extends CollectionKey,
|
||||
E extends keyof DataEntryMap[C] = string,
|
||||
> = {
|
||||
collection: C;
|
||||
id: E;
|
||||
};
|
||||
|
||||
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
|
||||
collection: C;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export function getCollection<C extends keyof DataEntryMap, E extends CollectionEntry<C>>(
|
||||
collection: C,
|
||||
filter?: (entry: CollectionEntry<C>) => entry is E,
|
||||
): Promise<E[]>;
|
||||
export function getCollection<C extends keyof DataEntryMap>(
|
||||
collection: C,
|
||||
filter?: (entry: CollectionEntry<C>) => unknown,
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
|
||||
export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
|
||||
collection: C,
|
||||
filter?: LiveLoaderCollectionFilterType<C>,
|
||||
): Promise<
|
||||
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
|
||||
>;
|
||||
|
||||
export function getEntry<
|
||||
C extends keyof DataEntryMap,
|
||||
E extends keyof DataEntryMap[C] | (string & {}),
|
||||
>(
|
||||
entry: ReferenceDataEntry<C, E>,
|
||||
): E extends keyof DataEntryMap[C]
|
||||
? Promise<DataEntryMap[C][E]>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getEntry<
|
||||
C extends keyof DataEntryMap,
|
||||
E extends keyof DataEntryMap[C] | (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
id: E,
|
||||
): E extends keyof DataEntryMap[C]
|
||||
? string extends keyof DataEntryMap[C]
|
||||
? Promise<DataEntryMap[C][E]> | undefined
|
||||
: Promise<DataEntryMap[C][E]>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
|
||||
collection: C,
|
||||
filter: string | LiveLoaderEntryFilterType<C>,
|
||||
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
|
||||
|
||||
/** Resolve an array of entry references from the same collection */
|
||||
export function getEntries<C extends keyof DataEntryMap>(
|
||||
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
|
||||
export function render<C extends keyof DataEntryMap>(
|
||||
entry: DataEntryMap[C][string],
|
||||
): Promise<RenderResult>;
|
||||
|
||||
export function reference<
|
||||
C extends
|
||||
| keyof DataEntryMap
|
||||
// Allow generic `string` to avoid excessive type errors in the config
|
||||
// if `dev` is not running to update as you edit.
|
||||
// Invalid collection names will be caught at build time.
|
||||
| (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
): import('astro/zod').ZodPipe<
|
||||
import('astro/zod').ZodString,
|
||||
import('astro/zod').ZodTransform<
|
||||
C extends keyof DataEntryMap
|
||||
? {
|
||||
collection: C;
|
||||
id: string;
|
||||
}
|
||||
: never,
|
||||
string
|
||||
>
|
||||
>;
|
||||
|
||||
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
|
||||
type InferEntrySchema<C extends keyof DataEntryMap> = import('astro/zod').infer<
|
||||
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
|
||||
>;
|
||||
type ExtractLoaderConfig<T> = T extends { loader: infer L } ? L : never;
|
||||
type InferLoaderSchema<
|
||||
C extends keyof DataEntryMap,
|
||||
L = ExtractLoaderConfig<ContentConfig['collections'][C]>,
|
||||
> = L extends { schema: import('astro/zod').ZodSchema }
|
||||
? import('astro/zod').infer<L['schema']>
|
||||
: any;
|
||||
|
||||
type DataEntryMap = {
|
||||
|
||||
};
|
||||
|
||||
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
|
||||
infer TData,
|
||||
infer TEntryFilter,
|
||||
infer TCollectionFilter,
|
||||
infer TError
|
||||
>
|
||||
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
|
||||
: { data: never; entryFilter: never; collectionFilter: never; error: never };
|
||||
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
|
||||
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
|
||||
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
|
||||
|
||||
type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
|
||||
LiveContentConfig['collections'][C]['schema'] extends undefined
|
||||
? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
|
||||
: import('astro/zod').infer<
|
||||
Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
|
||||
>;
|
||||
type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
|
||||
ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
|
||||
type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
|
||||
ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
|
||||
type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
|
||||
LiveContentConfig['collections'][C]['loader']
|
||||
>;
|
||||
|
||||
export type ContentConfig = never;
|
||||
export type LiveContentConfig = never;
|
||||
}
|
||||
1
.astro/types.d.ts
vendored
1
.astro/types.d.ts
vendored
|
|
@ -1 +1,2 @@
|
|||
/// <reference types="astro/client" />
|
||||
/// <reference path="content.d.ts" />
|
||||
47
AGENTS.md
Normal file
47
AGENTS.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# PR Dojo — Agent Guide
|
||||
|
||||
## Project
|
||||
**Code Review Hunter** ("PR Dojo") — an Astro site where users practice finding bugs in rejected PR code, earn XP, and submit fixes. Currently a static Astro frontend; backend (Quarkus or Node.js+SQLite) is planned per `full_plan.md`.
|
||||
|
||||
## Developer Commands
|
||||
```
|
||||
bun install # install deps
|
||||
bun dev # start dev server at localhost:4321
|
||||
bun build # static build → dist/
|
||||
bun preview # preview the build locally
|
||||
bun astro ... # run Astro CLI (add, check, etc.)
|
||||
```
|
||||
**Use `bun`, not npm or npx.**
|
||||
|
||||
## Tech Stack
|
||||
- **Astro 6** with **Tailwind CSS v4** via `@tailwindcss/vite` plugin (not the PostCSS pipeline)
|
||||
- **TypeScript** strict mode via `astro/tsconfigs/strict`
|
||||
- **Node >= 22.12.0** (enforced in `package.json` engines)
|
||||
- No test framework, linter, or formatter configured yet
|
||||
|
||||
## Architecture
|
||||
```
|
||||
src/
|
||||
data/challenges.json # static challenge data (id, code, hints, expectedLines, expectedPatch)
|
||||
pages/
|
||||
index.astro # challenge listing + hero
|
||||
profile.astro # user profile / XP dashboard
|
||||
challenges/[slug].astro # single challenge view with DiffViewer
|
||||
components/
|
||||
DiffViewer.astro # side-by-side code diff UI
|
||||
Welcome.astro # legacy starter component
|
||||
layouts/Layout.astro # root layout wrapping header/nav/footer
|
||||
styles/global.css # global styles
|
||||
```
|
||||
|
||||
## Challenge Data Schema
|
||||
Each entry in `src/data/challenges.json`:
|
||||
- `id`, `title`, `repository`, `baseSha`, `difficulty` (1-5), `xpValue`, `bugType`
|
||||
- `file`, `code` (buggy source), `hints` (array of "Line N: description")
|
||||
- `expectedLines` (array of line numbers), `expectedPatch` (unified diff string)
|
||||
|
||||
## Key Constraints
|
||||
- Tailwind v4 uses the Vite plugin, not PostCSS. Do not add `tailwind.config.js` or `@layer` directives — they won't work.
|
||||
- Astro SSR renders everything client-side by default. Add `client:*` directives only when interactivity is needed.
|
||||
- `full_plan.md` documents the planned backend architecture and MVP roadmap. It is a reference, not a spec to implement blindly.
|
||||
- No CI, no pre-commit hooks, no test runner. If you add tests, document the command in this file.
|
||||
1
dist/_astro/Layout.D3ovbguN.css
vendored
Normal file
1
dist/_astro/Layout.D3ovbguN.css
vendored
Normal file
File diff suppressed because one or more lines are too long
212
dist/challenges/1/index.html
vendored
Normal file
212
dist/challenges/1/index.html
vendored
Normal file
File diff suppressed because one or more lines are too long
211
dist/challenges/2/index.html
vendored
Normal file
211
dist/challenges/2/index.html
vendored
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css">
|
||||
<style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px}
|
||||
</style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #2</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>Null Pointer Dereference in User Service</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★☆☆☆</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>expressjs/express</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>def5678</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Null Pointer</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★☆☆☆</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+75 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-f5n3kgt" class="diff-viewer-container" data-dv-id="dv-f5n3kgt" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function getUserProfile(userId) { </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> const user = database.findUser(userId); </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> return { </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
|
||||
• No null check before accessing user properties </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> name: user.name, </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> email: user.email, </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> profile: user.profile.displayName </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
|
||||
• Potential null pointer on user.profile </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> }; </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> <div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]" data-astro-cid-2ex44dsa> <h3 class="text-sm font-semibold text-[#c9d1d9] mb-2" data-astro-cid-2ex44dsa>Hints:</h3> <ul class="space-y-1" data-astro-cid-2ex44dsa> <li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa>•</span> <span data-astro-cid-2ex44dsa>Line 3: No null check before accessing user properties</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa>•</span> <span data-astro-cid-2ex44dsa>Line 6: Potential null pointer on user.profile</span> </li> </ul> </div> </div> </div> <script client:load>
|
||||
(() => {
|
||||
const el = document.querySelector('[data-dv-id]');
|
||||
if (!el) return;
|
||||
|
||||
const buggedLines = new Set();
|
||||
|
||||
function updateBuggedState() {
|
||||
const rows = el.querySelectorAll('tr[data-line]');
|
||||
rows.forEach(row => {
|
||||
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
||||
row.classList.toggle('bugged', buggedLines.has(lineNum));
|
||||
});
|
||||
}
|
||||
|
||||
el.addEventListener('click', function(e) {
|
||||
const target = e.target;
|
||||
if (!(target instanceof HTMLElement)) return;
|
||||
const row = target.closest('tr[data-line]');
|
||||
if (!row) return;
|
||||
|
||||
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
||||
if (buggedLines.has(lineNum)) {
|
||||
buggedLines.delete(lineNum);
|
||||
} else {
|
||||
buggedLines.add(lineNum);
|
||||
}
|
||||
|
||||
updateBuggedState();
|
||||
|
||||
el.dispatchEvent(new CustomEvent('bugged-line-toggle', {
|
||||
bubbles: true,
|
||||
detail: { line: lineNum, bugged: buggedLines.has(lineNum), allBugged: [...buggedLines] }
|
||||
}));
|
||||
});
|
||||
})();
|
||||
</script> <!-- Fix Submission --> <div id="fix-panel" class="mt-6 bg-[#161b22] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-7fgtuneg> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center justify-between" data-astro-cid-7fgtuneg> <h2 class="text-lg font-semibold text-[#c9d1d9]" data-astro-cid-7fgtuneg>Submit Your Fix</h2> <div id="bugged-count" class="text-sm text-[#8b949e]" data-astro-cid-7fgtuneg>
|
||||
Lines marked: <span id="bugged-count-num" class="text-[#f85149] font-semibold" data-astro-cid-7fgtuneg>0</span> </div> </div> <!-- Marked lines summary --> <div id="marked-lines-summary" class="px-4 py-3 border-b border-[#30363d] hidden" data-astro-cid-7fgtuneg> <p class="text-sm text-[#8b949e] mb-2" data-astro-cid-7fgtuneg>Marked lines:</p> <div id="marked-lines-tags" class="flex flex-wrap gap-2" data-astro-cid-7fgtuneg></div> </div> <!-- Inline fix editor --> <div class="p-4" data-astro-cid-7fgtuneg> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
|
||||
Line-by-line fix:
|
||||
</label> <p class="text-xs text-[#8b949e] mb-3" data-astro-cid-7fgtuneg>Click a marked line number below to edit its fix.</p> <div id="fix-editor-container" class="space-y-2" data-astro-cid-7fgtuneg> <p id="fix-editor-placeholder" class="text-sm text-[#484f58] italic" data-astro-cid-7fgtuneg>Click on lines in the diff above to mark them, then add your fixes here.</p> </div> </div> <!-- Unified diff textarea --> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
|
||||
Or provide as unified diff:
|
||||
</label> <textarea id="diff-textarea" placeholder="- old line
|
||||
+ new line" rows="6" class="w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono" data-astro-cid-7fgtuneg></textarea> </div> <div class="flex gap-3" data-astro-cid-7fgtuneg> <button id="submit-fix-btn" class="flex-1 bg-[#1f6feb] hover:bg-[#388bfd] disabled:bg-[#21262d] disabled:text-[#484f58] disabled:cursor-not-allowed text-white font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
|
||||
Submit Fix
|
||||
</button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
|
||||
Clear All
|
||||
</button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> </body></html> <script client:load>
|
||||
(() => {
|
||||
const buggedLines = new Set();
|
||||
const fixEditorContainer = document.getElementById('fix-editor-container');
|
||||
const markedLinesTags = document.getElementById('marked-lines-tags');
|
||||
const markedLinesSummary = document.getElementById('marked-lines-summary');
|
||||
const buggedCountNum = document.getElementById('bugged-count-num');
|
||||
const submitBtn = document.getElementById('submit-fix-btn');
|
||||
const clearBtn = document.getElementById('clear-all-btn');
|
||||
const diffTextarea = document.getElementById('diff-textarea');
|
||||
const submissionMessage = document.getElementById('submission-message');
|
||||
const fixEditorPlaceholder = document.getElementById('fix-editor-placeholder');
|
||||
|
||||
function updateUI() {
|
||||
const arr = [...buggedLines].sort((a, b) => a - b);
|
||||
buggedCountNum.textContent = arr.length.toString();
|
||||
|
||||
if (markedLinesTags) {
|
||||
markedLinesTags.innerHTML = '';
|
||||
arr.forEach(lineNum => {
|
||||
const tag = document.createElement('span');
|
||||
tag.className = 'inline-flex items-center gap-1 bg-[#f8514920] text-[#f85149] text-xs px-2 py-0.5 rounded cursor-pointer hover:bg-[#f8514930] transition-colors';
|
||||
tag.textContent = `Line ${lineNum}`;
|
||||
tag.addEventListener('click', () => {
|
||||
showLineFixEditor(lineNum);
|
||||
});
|
||||
markedLinesTags.appendChild(tag);
|
||||
});
|
||||
}
|
||||
|
||||
if (markedLinesSummary) {
|
||||
markedLinesSummary.classList.toggle('hidden', arr.length === 0);
|
||||
}
|
||||
|
||||
if (fixEditorPlaceholder) {
|
||||
fixEditorPlaceholder.classList.toggle('hidden', arr.length > 0);
|
||||
}
|
||||
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = arr.length === 0 && !diffTextarea?.value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
function showLineFixEditor(lineNum) {
|
||||
if (!fixEditorContainer) return;
|
||||
|
||||
const existing = document.getElementById(`fix-editor-${lineNum}`);
|
||||
if (existing) {
|
||||
existing.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
existing.querySelector('textarea')?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const editorDiv = document.createElement('div');
|
||||
editorDiv.id = `fix-editor-${lineNum}`;
|
||||
editorDiv.className = 'bg-[#0d1117] border border-[#30363d] rounded-md p-3';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'flex items-center justify-between mb-2';
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'text-sm font-medium text-[#f85149]';
|
||||
label.textContent = `Line ${lineNum} — Your fix:`;
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'text-xs text-[#484f58] hover:text-[#f85149] transition-colors';
|
||||
removeBtn.textContent = '✕';
|
||||
removeBtn.addEventListener('click', () => {
|
||||
buggedLines.delete(lineNum);
|
||||
editorDiv.remove();
|
||||
updateUI();
|
||||
});
|
||||
|
||||
header.appendChild(label);
|
||||
header.appendChild(removeBtn);
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.rows = 3;
|
||||
textarea.className = 'w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono';
|
||||
textarea.placeholder = 'Enter the corrected line...';
|
||||
textarea.addEventListener('input', updateUI);
|
||||
|
||||
editorDiv.appendChild(header);
|
||||
editorDiv.appendChild(textarea);
|
||||
|
||||
fixEditorContainer.appendChild(editorDiv);
|
||||
}
|
||||
|
||||
// Listen for bugged-line-toggle events from DiffViewer
|
||||
document.addEventListener('bugged-line-toggle', (e) => {
|
||||
const detail = e.detail;
|
||||
if (detail.bugged) {
|
||||
buggedLines.add(detail.line);
|
||||
showLineFixEditor(detail.line);
|
||||
} else {
|
||||
buggedLines.delete(detail.line);
|
||||
const editor = document.getElementById(`fix-editor-${detail.line}`);
|
||||
if (editor) editor.remove();
|
||||
}
|
||||
updateUI();
|
||||
});
|
||||
|
||||
// Diff textarea input
|
||||
if (diffTextarea) {
|
||||
diffTextarea.addEventListener('input', updateUI);
|
||||
}
|
||||
|
||||
// Submit
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', () => {
|
||||
const lineFixes = {};
|
||||
buggedLines.forEach(lineNum => {
|
||||
const editor = document.getElementById(`fix-editor-${lineNum}`);
|
||||
if (editor) {
|
||||
const ta = editor.querySelector('textarea');
|
||||
if (ta?.value.trim()) {
|
||||
lineFixes[lineNum] = ta.value.trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const unifiedDiff = diffTextarea?.value.trim() || '';
|
||||
|
||||
if (Object.keys(lineFixes).length === 0 && !unifiedDiff) {
|
||||
if (submissionMessage) {
|
||||
submissionMessage.textContent = 'Please mark some lines or provide a diff.';
|
||||
submissionMessage.className = 'mt-3 text-sm text-[#f85149]';
|
||||
submissionMessage.classList.remove('hidden');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Show success message
|
||||
if (submissionMessage) {
|
||||
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
|
||||
submissionMessage.className = 'mt-3 text-sm text-[3ac840]';
|
||||
submissionMessage.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Clear all
|
||||
if (clearBtn) {
|
||||
clearBtn.addEventListener('click', () => {
|
||||
buggedLines.clear();
|
||||
const editors = fixEditorContainer?.querySelectorAll('[id^="fix-editor-"]');
|
||||
editors?.forEach(e => e.remove());
|
||||
if (diffTextarea) diffTextarea.value = '';
|
||||
if (submissionMessage) submissionMessage.classList.add('hidden');
|
||||
updateUI();
|
||||
|
||||
// Reset bugged state in DiffViewer
|
||||
document.querySelectorAll('tr.bugged').forEach(row => {
|
||||
row.classList.remove('bugged');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateUI();
|
||||
})();
|
||||
</script>
|
||||
212
dist/challenges/3/index.html
vendored
Normal file
212
dist/challenges/3/index.html
vendored
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css">
|
||||
<style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px}
|
||||
</style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #3</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>Race Condition in Counter Service</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★★★☆</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>rails/rails</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>ghi9012</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Concurrency</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★★★☆</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+150 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-mubau9j" class="diff-viewer-container" data-dv-id="dv-mubau9j" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> class Counter { </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
|
||||
• Line 12: Same issue with decrement operation </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> constructor() { </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> this.value = 0; </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> increment() { </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> this.value = this.value + 1; </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
|
||||
• Race condition when increment called concurrently </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return this.value; </span> </td> </tr><tr data-line="9" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 9 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 9 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="10" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 10 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 10 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> </span> </td> </tr><tr data-line="11" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 11 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 11 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> decrement() { </span> </td> </tr><tr data-line="12" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 12 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 12 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> this.value = this.value - 1; </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
|
||||
• Same issue with decrement operation </span> </td> </tr><tr data-line="13" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 13 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 13 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return this.value; </span> </td> </tr><tr data-line="14" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 14 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 14 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="15" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 15 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 15 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> <div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]" data-astro-cid-2ex44dsa> <h3 class="text-sm font-semibold text-[#c9d1d9] mb-2" data-astro-cid-2ex44dsa>Hints:</h3> <ul class="space-y-1" data-astro-cid-2ex44dsa> <li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa>•</span> <span data-astro-cid-2ex44dsa>Line 7: Race condition when increment called concurrently</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa>•</span> <span data-astro-cid-2ex44dsa>Line 12: Same issue with decrement operation</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa>•</span> <span data-astro-cid-2ex44dsa>No synchronization mechanism for thread safety</span> </li> </ul> </div> </div> </div> <script client:load>
|
||||
(() => {
|
||||
const el = document.querySelector('[data-dv-id]');
|
||||
if (!el) return;
|
||||
|
||||
const buggedLines = new Set();
|
||||
|
||||
function updateBuggedState() {
|
||||
const rows = el.querySelectorAll('tr[data-line]');
|
||||
rows.forEach(row => {
|
||||
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
||||
row.classList.toggle('bugged', buggedLines.has(lineNum));
|
||||
});
|
||||
}
|
||||
|
||||
el.addEventListener('click', function(e) {
|
||||
const target = e.target;
|
||||
if (!(target instanceof HTMLElement)) return;
|
||||
const row = target.closest('tr[data-line]');
|
||||
if (!row) return;
|
||||
|
||||
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
||||
if (buggedLines.has(lineNum)) {
|
||||
buggedLines.delete(lineNum);
|
||||
} else {
|
||||
buggedLines.add(lineNum);
|
||||
}
|
||||
|
||||
updateBuggedState();
|
||||
|
||||
el.dispatchEvent(new CustomEvent('bugged-line-toggle', {
|
||||
bubbles: true,
|
||||
detail: { line: lineNum, bugged: buggedLines.has(lineNum), allBugged: [...buggedLines] }
|
||||
}));
|
||||
});
|
||||
})();
|
||||
</script> <!-- Fix Submission --> <div id="fix-panel" class="mt-6 bg-[#161b22] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-7fgtuneg> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center justify-between" data-astro-cid-7fgtuneg> <h2 class="text-lg font-semibold text-[#c9d1d9]" data-astro-cid-7fgtuneg>Submit Your Fix</h2> <div id="bugged-count" class="text-sm text-[#8b949e]" data-astro-cid-7fgtuneg>
|
||||
Lines marked: <span id="bugged-count-num" class="text-[#f85149] font-semibold" data-astro-cid-7fgtuneg>0</span> </div> </div> <!-- Marked lines summary --> <div id="marked-lines-summary" class="px-4 py-3 border-b border-[#30363d] hidden" data-astro-cid-7fgtuneg> <p class="text-sm text-[#8b949e] mb-2" data-astro-cid-7fgtuneg>Marked lines:</p> <div id="marked-lines-tags" class="flex flex-wrap gap-2" data-astro-cid-7fgtuneg></div> </div> <!-- Inline fix editor --> <div class="p-4" data-astro-cid-7fgtuneg> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
|
||||
Line-by-line fix:
|
||||
</label> <p class="text-xs text-[#8b949e] mb-3" data-astro-cid-7fgtuneg>Click a marked line number below to edit its fix.</p> <div id="fix-editor-container" class="space-y-2" data-astro-cid-7fgtuneg> <p id="fix-editor-placeholder" class="text-sm text-[#484f58] italic" data-astro-cid-7fgtuneg>Click on lines in the diff above to mark them, then add your fixes here.</p> </div> </div> <!-- Unified diff textarea --> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
|
||||
Or provide as unified diff:
|
||||
</label> <textarea id="diff-textarea" placeholder="- old line
|
||||
+ new line" rows="6" class="w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono" data-astro-cid-7fgtuneg></textarea> </div> <div class="flex gap-3" data-astro-cid-7fgtuneg> <button id="submit-fix-btn" class="flex-1 bg-[#1f6feb] hover:bg-[#388bfd] disabled:bg-[#21262d] disabled:text-[#484f58] disabled:cursor-not-allowed text-white font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
|
||||
Submit Fix
|
||||
</button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
|
||||
Clear All
|
||||
</button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> </body></html> <script client:load>
|
||||
(() => {
|
||||
const buggedLines = new Set();
|
||||
const fixEditorContainer = document.getElementById('fix-editor-container');
|
||||
const markedLinesTags = document.getElementById('marked-lines-tags');
|
||||
const markedLinesSummary = document.getElementById('marked-lines-summary');
|
||||
const buggedCountNum = document.getElementById('bugged-count-num');
|
||||
const submitBtn = document.getElementById('submit-fix-btn');
|
||||
const clearBtn = document.getElementById('clear-all-btn');
|
||||
const diffTextarea = document.getElementById('diff-textarea');
|
||||
const submissionMessage = document.getElementById('submission-message');
|
||||
const fixEditorPlaceholder = document.getElementById('fix-editor-placeholder');
|
||||
|
||||
function updateUI() {
|
||||
const arr = [...buggedLines].sort((a, b) => a - b);
|
||||
buggedCountNum.textContent = arr.length.toString();
|
||||
|
||||
if (markedLinesTags) {
|
||||
markedLinesTags.innerHTML = '';
|
||||
arr.forEach(lineNum => {
|
||||
const tag = document.createElement('span');
|
||||
tag.className = 'inline-flex items-center gap-1 bg-[#f8514920] text-[#f85149] text-xs px-2 py-0.5 rounded cursor-pointer hover:bg-[#f8514930] transition-colors';
|
||||
tag.textContent = `Line ${lineNum}`;
|
||||
tag.addEventListener('click', () => {
|
||||
showLineFixEditor(lineNum);
|
||||
});
|
||||
markedLinesTags.appendChild(tag);
|
||||
});
|
||||
}
|
||||
|
||||
if (markedLinesSummary) {
|
||||
markedLinesSummary.classList.toggle('hidden', arr.length === 0);
|
||||
}
|
||||
|
||||
if (fixEditorPlaceholder) {
|
||||
fixEditorPlaceholder.classList.toggle('hidden', arr.length > 0);
|
||||
}
|
||||
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = arr.length === 0 && !diffTextarea?.value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
function showLineFixEditor(lineNum) {
|
||||
if (!fixEditorContainer) return;
|
||||
|
||||
const existing = document.getElementById(`fix-editor-${lineNum}`);
|
||||
if (existing) {
|
||||
existing.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
existing.querySelector('textarea')?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const editorDiv = document.createElement('div');
|
||||
editorDiv.id = `fix-editor-${lineNum}`;
|
||||
editorDiv.className = 'bg-[#0d1117] border border-[#30363d] rounded-md p-3';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'flex items-center justify-between mb-2';
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'text-sm font-medium text-[#f85149]';
|
||||
label.textContent = `Line ${lineNum} — Your fix:`;
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'text-xs text-[#484f58] hover:text-[#f85149] transition-colors';
|
||||
removeBtn.textContent = '✕';
|
||||
removeBtn.addEventListener('click', () => {
|
||||
buggedLines.delete(lineNum);
|
||||
editorDiv.remove();
|
||||
updateUI();
|
||||
});
|
||||
|
||||
header.appendChild(label);
|
||||
header.appendChild(removeBtn);
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.rows = 3;
|
||||
textarea.className = 'w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono';
|
||||
textarea.placeholder = 'Enter the corrected line...';
|
||||
textarea.addEventListener('input', updateUI);
|
||||
|
||||
editorDiv.appendChild(header);
|
||||
editorDiv.appendChild(textarea);
|
||||
|
||||
fixEditorContainer.appendChild(editorDiv);
|
||||
}
|
||||
|
||||
// Listen for bugged-line-toggle events from DiffViewer
|
||||
document.addEventListener('bugged-line-toggle', (e) => {
|
||||
const detail = e.detail;
|
||||
if (detail.bugged) {
|
||||
buggedLines.add(detail.line);
|
||||
showLineFixEditor(detail.line);
|
||||
} else {
|
||||
buggedLines.delete(detail.line);
|
||||
const editor = document.getElementById(`fix-editor-${detail.line}`);
|
||||
if (editor) editor.remove();
|
||||
}
|
||||
updateUI();
|
||||
});
|
||||
|
||||
// Diff textarea input
|
||||
if (diffTextarea) {
|
||||
diffTextarea.addEventListener('input', updateUI);
|
||||
}
|
||||
|
||||
// Submit
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', () => {
|
||||
const lineFixes = {};
|
||||
buggedLines.forEach(lineNum => {
|
||||
const editor = document.getElementById(`fix-editor-${lineNum}`);
|
||||
if (editor) {
|
||||
const ta = editor.querySelector('textarea');
|
||||
if (ta?.value.trim()) {
|
||||
lineFixes[lineNum] = ta.value.trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const unifiedDiff = diffTextarea?.value.trim() || '';
|
||||
|
||||
if (Object.keys(lineFixes).length === 0 && !unifiedDiff) {
|
||||
if (submissionMessage) {
|
||||
submissionMessage.textContent = 'Please mark some lines or provide a diff.';
|
||||
submissionMessage.className = 'mt-3 text-sm text-[#f85149]';
|
||||
submissionMessage.classList.remove('hidden');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Show success message
|
||||
if (submissionMessage) {
|
||||
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
|
||||
submissionMessage.className = 'mt-3 text-sm text-[3ac840]';
|
||||
submissionMessage.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Clear all
|
||||
if (clearBtn) {
|
||||
clearBtn.addEventListener('click', () => {
|
||||
buggedLines.clear();
|
||||
const editors = fixEditorContainer?.querySelectorAll('[id^="fix-editor-"]');
|
||||
editors?.forEach(e => e.remove());
|
||||
if (diffTextarea) diffTextarea.value = '';
|
||||
if (submissionMessage) submissionMessage.classList.add('hidden');
|
||||
updateUI();
|
||||
|
||||
// Reset bugged state in DiffViewer
|
||||
document.querySelectorAll('tr.bugged').forEach(row => {
|
||||
row.classList.remove('bugged');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateUI();
|
||||
})();
|
||||
</script>
|
||||
211
dist/challenges/4/index.html
vendored
Normal file
211
dist/challenges/4/index.html
vendored
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css">
|
||||
<style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px}
|
||||
</style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #4</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>SQL Injection Vulnerability</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★★★★</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>django/django</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>jkl3456</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Security</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★★★★</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+200 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-ikbmd7r" class="diff-viewer-container" data-dv-id="dv-ikbmd7r" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function findUserByUsername(username) { </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> const query = `SELECT * FROM users WHERE username = '${username}'`; </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
|
||||
• Direct string interpolation allows SQL injection </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return database.query(query); </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function deleteUser(userId) { </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> const query = `DELETE FROM users WHERE id = ${userId}`; </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
|
||||
• Second SQL injection vulnerability in delete operation </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return database.query(query); </span> </td> </tr><tr data-line="9" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 9 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 9 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> <div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]" data-astro-cid-2ex44dsa> <h3 class="text-sm font-semibold text-[#c9d1d9] mb-2" data-astro-cid-2ex44dsa>Hints:</h3> <ul class="space-y-1" data-astro-cid-2ex44dsa> <li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa>•</span> <span data-astro-cid-2ex44dsa>Line 2: Direct string interpolation allows SQL injection</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa>•</span> <span data-astro-cid-2ex44dsa>Line 7: Second SQL injection vulnerability in delete operation</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa>•</span> <span data-astro-cid-2ex44dsa>Should use parameterized queries instead</span> </li> </ul> </div> </div> </div> <script client:load>
|
||||
(() => {
|
||||
const el = document.querySelector('[data-dv-id]');
|
||||
if (!el) return;
|
||||
|
||||
const buggedLines = new Set();
|
||||
|
||||
function updateBuggedState() {
|
||||
const rows = el.querySelectorAll('tr[data-line]');
|
||||
rows.forEach(row => {
|
||||
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
||||
row.classList.toggle('bugged', buggedLines.has(lineNum));
|
||||
});
|
||||
}
|
||||
|
||||
el.addEventListener('click', function(e) {
|
||||
const target = e.target;
|
||||
if (!(target instanceof HTMLElement)) return;
|
||||
const row = target.closest('tr[data-line]');
|
||||
if (!row) return;
|
||||
|
||||
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
||||
if (buggedLines.has(lineNum)) {
|
||||
buggedLines.delete(lineNum);
|
||||
} else {
|
||||
buggedLines.add(lineNum);
|
||||
}
|
||||
|
||||
updateBuggedState();
|
||||
|
||||
el.dispatchEvent(new CustomEvent('bugged-line-toggle', {
|
||||
bubbles: true,
|
||||
detail: { line: lineNum, bugged: buggedLines.has(lineNum), allBugged: [...buggedLines] }
|
||||
}));
|
||||
});
|
||||
})();
|
||||
</script> <!-- Fix Submission --> <div id="fix-panel" class="mt-6 bg-[#161b22] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-7fgtuneg> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center justify-between" data-astro-cid-7fgtuneg> <h2 class="text-lg font-semibold text-[#c9d1d9]" data-astro-cid-7fgtuneg>Submit Your Fix</h2> <div id="bugged-count" class="text-sm text-[#8b949e]" data-astro-cid-7fgtuneg>
|
||||
Lines marked: <span id="bugged-count-num" class="text-[#f85149] font-semibold" data-astro-cid-7fgtuneg>0</span> </div> </div> <!-- Marked lines summary --> <div id="marked-lines-summary" class="px-4 py-3 border-b border-[#30363d] hidden" data-astro-cid-7fgtuneg> <p class="text-sm text-[#8b949e] mb-2" data-astro-cid-7fgtuneg>Marked lines:</p> <div id="marked-lines-tags" class="flex flex-wrap gap-2" data-astro-cid-7fgtuneg></div> </div> <!-- Inline fix editor --> <div class="p-4" data-astro-cid-7fgtuneg> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
|
||||
Line-by-line fix:
|
||||
</label> <p class="text-xs text-[#8b949e] mb-3" data-astro-cid-7fgtuneg>Click a marked line number below to edit its fix.</p> <div id="fix-editor-container" class="space-y-2" data-astro-cid-7fgtuneg> <p id="fix-editor-placeholder" class="text-sm text-[#484f58] italic" data-astro-cid-7fgtuneg>Click on lines in the diff above to mark them, then add your fixes here.</p> </div> </div> <!-- Unified diff textarea --> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
|
||||
Or provide as unified diff:
|
||||
</label> <textarea id="diff-textarea" placeholder="- old line
|
||||
+ new line" rows="6" class="w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono" data-astro-cid-7fgtuneg></textarea> </div> <div class="flex gap-3" data-astro-cid-7fgtuneg> <button id="submit-fix-btn" class="flex-1 bg-[#1f6feb] hover:bg-[#388bfd] disabled:bg-[#21262d] disabled:text-[#484f58] disabled:cursor-not-allowed text-white font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
|
||||
Submit Fix
|
||||
</button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
|
||||
Clear All
|
||||
</button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> </body></html> <script client:load>
|
||||
(() => {
|
||||
const buggedLines = new Set();
|
||||
const fixEditorContainer = document.getElementById('fix-editor-container');
|
||||
const markedLinesTags = document.getElementById('marked-lines-tags');
|
||||
const markedLinesSummary = document.getElementById('marked-lines-summary');
|
||||
const buggedCountNum = document.getElementById('bugged-count-num');
|
||||
const submitBtn = document.getElementById('submit-fix-btn');
|
||||
const clearBtn = document.getElementById('clear-all-btn');
|
||||
const diffTextarea = document.getElementById('diff-textarea');
|
||||
const submissionMessage = document.getElementById('submission-message');
|
||||
const fixEditorPlaceholder = document.getElementById('fix-editor-placeholder');
|
||||
|
||||
function updateUI() {
|
||||
const arr = [...buggedLines].sort((a, b) => a - b);
|
||||
buggedCountNum.textContent = arr.length.toString();
|
||||
|
||||
if (markedLinesTags) {
|
||||
markedLinesTags.innerHTML = '';
|
||||
arr.forEach(lineNum => {
|
||||
const tag = document.createElement('span');
|
||||
tag.className = 'inline-flex items-center gap-1 bg-[#f8514920] text-[#f85149] text-xs px-2 py-0.5 rounded cursor-pointer hover:bg-[#f8514930] transition-colors';
|
||||
tag.textContent = `Line ${lineNum}`;
|
||||
tag.addEventListener('click', () => {
|
||||
showLineFixEditor(lineNum);
|
||||
});
|
||||
markedLinesTags.appendChild(tag);
|
||||
});
|
||||
}
|
||||
|
||||
if (markedLinesSummary) {
|
||||
markedLinesSummary.classList.toggle('hidden', arr.length === 0);
|
||||
}
|
||||
|
||||
if (fixEditorPlaceholder) {
|
||||
fixEditorPlaceholder.classList.toggle('hidden', arr.length > 0);
|
||||
}
|
||||
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = arr.length === 0 && !diffTextarea?.value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
function showLineFixEditor(lineNum) {
|
||||
if (!fixEditorContainer) return;
|
||||
|
||||
const existing = document.getElementById(`fix-editor-${lineNum}`);
|
||||
if (existing) {
|
||||
existing.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
existing.querySelector('textarea')?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const editorDiv = document.createElement('div');
|
||||
editorDiv.id = `fix-editor-${lineNum}`;
|
||||
editorDiv.className = 'bg-[#0d1117] border border-[#30363d] rounded-md p-3';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'flex items-center justify-between mb-2';
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'text-sm font-medium text-[#f85149]';
|
||||
label.textContent = `Line ${lineNum} — Your fix:`;
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'text-xs text-[#484f58] hover:text-[#f85149] transition-colors';
|
||||
removeBtn.textContent = '✕';
|
||||
removeBtn.addEventListener('click', () => {
|
||||
buggedLines.delete(lineNum);
|
||||
editorDiv.remove();
|
||||
updateUI();
|
||||
});
|
||||
|
||||
header.appendChild(label);
|
||||
header.appendChild(removeBtn);
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.rows = 3;
|
||||
textarea.className = 'w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono';
|
||||
textarea.placeholder = 'Enter the corrected line...';
|
||||
textarea.addEventListener('input', updateUI);
|
||||
|
||||
editorDiv.appendChild(header);
|
||||
editorDiv.appendChild(textarea);
|
||||
|
||||
fixEditorContainer.appendChild(editorDiv);
|
||||
}
|
||||
|
||||
// Listen for bugged-line-toggle events from DiffViewer
|
||||
document.addEventListener('bugged-line-toggle', (e) => {
|
||||
const detail = e.detail;
|
||||
if (detail.bugged) {
|
||||
buggedLines.add(detail.line);
|
||||
showLineFixEditor(detail.line);
|
||||
} else {
|
||||
buggedLines.delete(detail.line);
|
||||
const editor = document.getElementById(`fix-editor-${detail.line}`);
|
||||
if (editor) editor.remove();
|
||||
}
|
||||
updateUI();
|
||||
});
|
||||
|
||||
// Diff textarea input
|
||||
if (diffTextarea) {
|
||||
diffTextarea.addEventListener('input', updateUI);
|
||||
}
|
||||
|
||||
// Submit
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', () => {
|
||||
const lineFixes = {};
|
||||
buggedLines.forEach(lineNum => {
|
||||
const editor = document.getElementById(`fix-editor-${lineNum}`);
|
||||
if (editor) {
|
||||
const ta = editor.querySelector('textarea');
|
||||
if (ta?.value.trim()) {
|
||||
lineFixes[lineNum] = ta.value.trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const unifiedDiff = diffTextarea?.value.trim() || '';
|
||||
|
||||
if (Object.keys(lineFixes).length === 0 && !unifiedDiff) {
|
||||
if (submissionMessage) {
|
||||
submissionMessage.textContent = 'Please mark some lines or provide a diff.';
|
||||
submissionMessage.className = 'mt-3 text-sm text-[#f85149]';
|
||||
submissionMessage.classList.remove('hidden');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Show success message
|
||||
if (submissionMessage) {
|
||||
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
|
||||
submissionMessage.className = 'mt-3 text-sm text-[3ac840]';
|
||||
submissionMessage.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Clear all
|
||||
if (clearBtn) {
|
||||
clearBtn.addEventListener('click', () => {
|
||||
buggedLines.clear();
|
||||
const editors = fixEditorContainer?.querySelectorAll('[id^="fix-editor-"]');
|
||||
editors?.forEach(e => e.remove());
|
||||
if (diffTextarea) diffTextarea.value = '';
|
||||
if (submissionMessage) submissionMessage.classList.add('hidden');
|
||||
updateUI();
|
||||
|
||||
// Reset bugged state in DiffViewer
|
||||
document.querySelectorAll('tr.bugged').forEach(row => {
|
||||
row.classList.remove('bugged');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateUI();
|
||||
})();
|
||||
</script>
|
||||
211
dist/challenges/5/index.html
vendored
Normal file
211
dist/challenges/5/index.html
vendored
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css">
|
||||
<style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px}
|
||||
</style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #5</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>Infinite Loop in Data Processing</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★★☆☆</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>angular/angular</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>mno7890</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Logic Error</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★★☆☆</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+100 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-qgi8fcj" class="diff-viewer-container" data-dv-id="dv-qgi8fcj" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function processItems(items) { </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> let index = 0; </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> while (index < items.length) { </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
|
||||
• Line 3-6: Infinite loop - index never increments </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> console.log(items[index]); </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> // Missing: index++ </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
|
||||
• Missing increment statement causes endless iteration </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return 'done'; </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> <div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]" data-astro-cid-2ex44dsa> <h3 class="text-sm font-semibold text-[#c9d1d9] mb-2" data-astro-cid-2ex44dsa>Hints:</h3> <ul class="space-y-1" data-astro-cid-2ex44dsa> <li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa>•</span> <span data-astro-cid-2ex44dsa>Line 3-6: Infinite loop - index never increments</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa>•</span> <span data-astro-cid-2ex44dsa>Line 5: Missing increment statement causes endless iteration</span> </li> </ul> </div> </div> </div> <script client:load>
|
||||
(() => {
|
||||
const el = document.querySelector('[data-dv-id]');
|
||||
if (!el) return;
|
||||
|
||||
const buggedLines = new Set();
|
||||
|
||||
function updateBuggedState() {
|
||||
const rows = el.querySelectorAll('tr[data-line]');
|
||||
rows.forEach(row => {
|
||||
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
||||
row.classList.toggle('bugged', buggedLines.has(lineNum));
|
||||
});
|
||||
}
|
||||
|
||||
el.addEventListener('click', function(e) {
|
||||
const target = e.target;
|
||||
if (!(target instanceof HTMLElement)) return;
|
||||
const row = target.closest('tr[data-line]');
|
||||
if (!row) return;
|
||||
|
||||
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
||||
if (buggedLines.has(lineNum)) {
|
||||
buggedLines.delete(lineNum);
|
||||
} else {
|
||||
buggedLines.add(lineNum);
|
||||
}
|
||||
|
||||
updateBuggedState();
|
||||
|
||||
el.dispatchEvent(new CustomEvent('bugged-line-toggle', {
|
||||
bubbles: true,
|
||||
detail: { line: lineNum, bugged: buggedLines.has(lineNum), allBugged: [...buggedLines] }
|
||||
}));
|
||||
});
|
||||
})();
|
||||
</script> <!-- Fix Submission --> <div id="fix-panel" class="mt-6 bg-[#161b22] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-7fgtuneg> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center justify-between" data-astro-cid-7fgtuneg> <h2 class="text-lg font-semibold text-[#c9d1d9]" data-astro-cid-7fgtuneg>Submit Your Fix</h2> <div id="bugged-count" class="text-sm text-[#8b949e]" data-astro-cid-7fgtuneg>
|
||||
Lines marked: <span id="bugged-count-num" class="text-[#f85149] font-semibold" data-astro-cid-7fgtuneg>0</span> </div> </div> <!-- Marked lines summary --> <div id="marked-lines-summary" class="px-4 py-3 border-b border-[#30363d] hidden" data-astro-cid-7fgtuneg> <p class="text-sm text-[#8b949e] mb-2" data-astro-cid-7fgtuneg>Marked lines:</p> <div id="marked-lines-tags" class="flex flex-wrap gap-2" data-astro-cid-7fgtuneg></div> </div> <!-- Inline fix editor --> <div class="p-4" data-astro-cid-7fgtuneg> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
|
||||
Line-by-line fix:
|
||||
</label> <p class="text-xs text-[#8b949e] mb-3" data-astro-cid-7fgtuneg>Click a marked line number below to edit its fix.</p> <div id="fix-editor-container" class="space-y-2" data-astro-cid-7fgtuneg> <p id="fix-editor-placeholder" class="text-sm text-[#484f58] italic" data-astro-cid-7fgtuneg>Click on lines in the diff above to mark them, then add your fixes here.</p> </div> </div> <!-- Unified diff textarea --> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
|
||||
Or provide as unified diff:
|
||||
</label> <textarea id="diff-textarea" placeholder="- old line
|
||||
+ new line" rows="6" class="w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono" data-astro-cid-7fgtuneg></textarea> </div> <div class="flex gap-3" data-astro-cid-7fgtuneg> <button id="submit-fix-btn" class="flex-1 bg-[#1f6feb] hover:bg-[#388bfd] disabled:bg-[#21262d] disabled:text-[#484f58] disabled:cursor-not-allowed text-white font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
|
||||
Submit Fix
|
||||
</button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
|
||||
Clear All
|
||||
</button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> </body></html> <script client:load>
|
||||
(() => {
|
||||
const buggedLines = new Set();
|
||||
const fixEditorContainer = document.getElementById('fix-editor-container');
|
||||
const markedLinesTags = document.getElementById('marked-lines-tags');
|
||||
const markedLinesSummary = document.getElementById('marked-lines-summary');
|
||||
const buggedCountNum = document.getElementById('bugged-count-num');
|
||||
const submitBtn = document.getElementById('submit-fix-btn');
|
||||
const clearBtn = document.getElementById('clear-all-btn');
|
||||
const diffTextarea = document.getElementById('diff-textarea');
|
||||
const submissionMessage = document.getElementById('submission-message');
|
||||
const fixEditorPlaceholder = document.getElementById('fix-editor-placeholder');
|
||||
|
||||
function updateUI() {
|
||||
const arr = [...buggedLines].sort((a, b) => a - b);
|
||||
buggedCountNum.textContent = arr.length.toString();
|
||||
|
||||
if (markedLinesTags) {
|
||||
markedLinesTags.innerHTML = '';
|
||||
arr.forEach(lineNum => {
|
||||
const tag = document.createElement('span');
|
||||
tag.className = 'inline-flex items-center gap-1 bg-[#f8514920] text-[#f85149] text-xs px-2 py-0.5 rounded cursor-pointer hover:bg-[#f8514930] transition-colors';
|
||||
tag.textContent = `Line ${lineNum}`;
|
||||
tag.addEventListener('click', () => {
|
||||
showLineFixEditor(lineNum);
|
||||
});
|
||||
markedLinesTags.appendChild(tag);
|
||||
});
|
||||
}
|
||||
|
||||
if (markedLinesSummary) {
|
||||
markedLinesSummary.classList.toggle('hidden', arr.length === 0);
|
||||
}
|
||||
|
||||
if (fixEditorPlaceholder) {
|
||||
fixEditorPlaceholder.classList.toggle('hidden', arr.length > 0);
|
||||
}
|
||||
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = arr.length === 0 && !diffTextarea?.value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
function showLineFixEditor(lineNum) {
|
||||
if (!fixEditorContainer) return;
|
||||
|
||||
const existing = document.getElementById(`fix-editor-${lineNum}`);
|
||||
if (existing) {
|
||||
existing.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
existing.querySelector('textarea')?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const editorDiv = document.createElement('div');
|
||||
editorDiv.id = `fix-editor-${lineNum}`;
|
||||
editorDiv.className = 'bg-[#0d1117] border border-[#30363d] rounded-md p-3';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'flex items-center justify-between mb-2';
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'text-sm font-medium text-[#f85149]';
|
||||
label.textContent = `Line ${lineNum} — Your fix:`;
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'text-xs text-[#484f58] hover:text-[#f85149] transition-colors';
|
||||
removeBtn.textContent = '✕';
|
||||
removeBtn.addEventListener('click', () => {
|
||||
buggedLines.delete(lineNum);
|
||||
editorDiv.remove();
|
||||
updateUI();
|
||||
});
|
||||
|
||||
header.appendChild(label);
|
||||
header.appendChild(removeBtn);
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.rows = 3;
|
||||
textarea.className = 'w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono';
|
||||
textarea.placeholder = 'Enter the corrected line...';
|
||||
textarea.addEventListener('input', updateUI);
|
||||
|
||||
editorDiv.appendChild(header);
|
||||
editorDiv.appendChild(textarea);
|
||||
|
||||
fixEditorContainer.appendChild(editorDiv);
|
||||
}
|
||||
|
||||
// Listen for bugged-line-toggle events from DiffViewer
|
||||
document.addEventListener('bugged-line-toggle', (e) => {
|
||||
const detail = e.detail;
|
||||
if (detail.bugged) {
|
||||
buggedLines.add(detail.line);
|
||||
showLineFixEditor(detail.line);
|
||||
} else {
|
||||
buggedLines.delete(detail.line);
|
||||
const editor = document.getElementById(`fix-editor-${detail.line}`);
|
||||
if (editor) editor.remove();
|
||||
}
|
||||
updateUI();
|
||||
});
|
||||
|
||||
// Diff textarea input
|
||||
if (diffTextarea) {
|
||||
diffTextarea.addEventListener('input', updateUI);
|
||||
}
|
||||
|
||||
// Submit
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', () => {
|
||||
const lineFixes = {};
|
||||
buggedLines.forEach(lineNum => {
|
||||
const editor = document.getElementById(`fix-editor-${lineNum}`);
|
||||
if (editor) {
|
||||
const ta = editor.querySelector('textarea');
|
||||
if (ta?.value.trim()) {
|
||||
lineFixes[lineNum] = ta.value.trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const unifiedDiff = diffTextarea?.value.trim() || '';
|
||||
|
||||
if (Object.keys(lineFixes).length === 0 && !unifiedDiff) {
|
||||
if (submissionMessage) {
|
||||
submissionMessage.textContent = 'Please mark some lines or provide a diff.';
|
||||
submissionMessage.className = 'mt-3 text-sm text-[#f85149]';
|
||||
submissionMessage.classList.remove('hidden');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Show success message
|
||||
if (submissionMessage) {
|
||||
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
|
||||
submissionMessage.className = 'mt-3 text-sm text-[3ac840]';
|
||||
submissionMessage.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Clear all
|
||||
if (clearBtn) {
|
||||
clearBtn.addEventListener('click', () => {
|
||||
buggedLines.clear();
|
||||
const editors = fixEditorContainer?.querySelectorAll('[id^="fix-editor-"]');
|
||||
editors?.forEach(e => e.remove());
|
||||
if (diffTextarea) diffTextarea.value = '';
|
||||
if (submissionMessage) submissionMessage.classList.add('hidden');
|
||||
updateUI();
|
||||
|
||||
// Reset bugged state in DiffViewer
|
||||
document.querySelectorAll('tr.bugged').forEach(row => {
|
||||
row.classList.remove('bugged');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateUI();
|
||||
})();
|
||||
</script>
|
||||
BIN
dist/favicon.ico
vendored
Normal file
BIN
dist/favicon.ico
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 655 B |
9
dist/favicon.svg
vendored
Normal file
9
dist/favicon.svg
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 749 B |
21
dist/index.html
vendored
Normal file
21
dist/index.html
vendored
Normal file
File diff suppressed because one or more lines are too long
3
dist/profile/index.html
vendored
Normal file
3
dist/profile/index.html
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css"></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen"> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]"> <div class="max-w-6xl mx-auto px-4 py-3"> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0">← Back to Challenges</a> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6"> <!-- Profile Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6"> <div class="flex items-center gap-6"> <div class="w-20 h-20 bg-[#21262d] rounded-full flex items-center justify-center"> <svg class="w-12 h-12 text-[#8b949e]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path> </svg> </div> <div> <h1 class="text-2xl font-semibold text-[#c9d1d9]">Anonymous User</h1> <p class="text-[#8b949e] text-sm mt-1">Code Review Practitioner</p> </div> </div> </div> <!-- Stats Grid --> <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6"> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="flex items-center gap-3 mb-2"> <svg class="w-6 h-6 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20"> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z"></path> </svg> <span class="text-[#8b949e] text-sm">Total XP</span> </div> <div class="text-3xl font-bold text-[#58a6ff]">0</div> </div> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="flex items-center gap-3 mb-2"> <svg class="w-6 h-6 text-[#a5d6ff]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path> </svg> <span class="text-[#8b949e] text-sm">Challenges Solved</span> </div> <div class="text-3xl font-bold text-[#c9d1d9]">0<span class="text-[#8b949e] text-xl">/5</span></div> </div> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="flex items-center gap-3 mb-2"> <svg class="w-6 h-6 text-[#79c0ff]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path> </svg> <span class="text-[#8b949e] text-sm">Current Streak</span> </div> <div class="text-3xl font-bold text-[#79c0ff]">0<span class="text-[#8b949e] text-xl"> days</span></div> </div> </div> <!-- Rank Progress --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6"> <div class="flex items-center justify-between mb-3"> <span class="text-[#c9d1d9] font-semibold">Current Rank: Novice</span> <span class="text-[#8b949e] text-sm">0 / 500 XP</span> </div> <div class="h-3 bg-[#21262d] rounded-full overflow-hidden"> <div class="h-full bg-[#58a6ff] w-0"></div> </div> <div class="mt-4 flex items-center justify-between"> <span class="text-[#c9d1d9] font-semibold">Next: Apprentice</span> <span class="text-[#8b949e] text-sm">500 / 1500 XP</span> </div> <div class="h-3 bg-[#21262d] rounded-full overflow-hidden"> <div class="h-full bg-[#a5d6ff] w-0"></div> </div> </div> <!-- Recent Activity --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6"> <h2 class="text-lg font-semibold text-[#c9d1d9] mb-4">Recent Activity</h2> <div class="text-center text-[#8b949e] py-8"> <svg class="w-12 h-12 mx-auto mb-3 text-[#30363d]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path> </svg> <p>No challenges completed yet</p> <a href="/challenges/1" class="text-[#58a6ff] mt-2 inline-block no-underline">
|
||||
Start your first challenge →
|
||||
</a> </div> </div> </main> </div> </body></html>
|
||||
|
|
@ -5,18 +5,15 @@ interface Props {
|
|||
}
|
||||
|
||||
const { code, hints } = Astro.props;
|
||||
const id = `dv-${Math.random().toString(36).substring(2, 9)}`;
|
||||
|
||||
const lines = code.split('\n');
|
||||
---
|
||||
|
||||
<div id={id} class="diff-viewer-container" data-dv-id={id}>
|
||||
<div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden">
|
||||
<div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center gap-2">
|
||||
<div class="flex gap-1.5">
|
||||
<div class="w-3 h-3 rounded-full bg-[#ff5f57]"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-[#febc2e]"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-[#28c840]"></div>
|
||||
</div>
|
||||
<span class="ml-2 text-sm text-[#8b949e]">{Astro.url.pathname.split('/').pop() || 'file.js'}</span>
|
||||
<div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]">
|
||||
<span class="text-sm text-[#8b949e]">{Astro.url.pathname.split('/').pop() || 'file.js'}</span>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
|
|
@ -29,11 +26,12 @@ const lines = code.split('\n');
|
|||
|
||||
return (
|
||||
<tr
|
||||
data-line={lineNum}
|
||||
class="group hover:bg-[#161b22] cursor-pointer transition-colors"
|
||||
title={`Mark line ${lineNum} as buggy`}
|
||||
>
|
||||
<td class="w-16 text-right pr-4 select-none">
|
||||
<span class={`text-[#8b949e] ${hasHint ? 'text-[#ff7b72] font-semibold' : ''}`}>
|
||||
<span class={`text-[#8b949e] line-number ${hasHint ? 'text-[#ff7b72] font-semibold' : ''}`}>
|
||||
{lineNum}
|
||||
</span>
|
||||
</td>
|
||||
|
|
@ -42,7 +40,7 @@ const lines = code.split('\n');
|
|||
{line || ' '}
|
||||
</span>
|
||||
{hasHint && (
|
||||
<span class="ml-2 text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
• {hintText?.replace(`Line ${lineNum}: `, '')}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -68,6 +66,7 @@ const lines = code.split('\n');
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
tr {
|
||||
|
|
@ -79,4 +78,51 @@ const lines = code.split('\n');
|
|||
td:first-child {
|
||||
border-right: 1px solid #30363d;
|
||||
}
|
||||
tr.bugged td:first-child {
|
||||
border-left: 3px solid #f85149;
|
||||
}
|
||||
tr.bugged .line-number {
|
||||
color: #f85149;
|
||||
}
|
||||
tr.bugged {
|
||||
background-color: #f8514918;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script client:load>
|
||||
(() => {
|
||||
const el = document.querySelector('[data-dv-id]');
|
||||
if (!el) return;
|
||||
|
||||
const buggedLines = new Set();
|
||||
|
||||
function updateBuggedState() {
|
||||
const rows = el.querySelectorAll('tr[data-line]');
|
||||
rows.forEach(row => {
|
||||
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
||||
row.classList.toggle('bugged', buggedLines.has(lineNum));
|
||||
});
|
||||
}
|
||||
|
||||
el.addEventListener('click', function(e) {
|
||||
const target = e.target;
|
||||
if (!(target instanceof HTMLElement)) return;
|
||||
const row = target.closest('tr[data-line]');
|
||||
if (!row) return;
|
||||
|
||||
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
||||
if (buggedLines.has(lineNum)) {
|
||||
buggedLines.delete(lineNum);
|
||||
} else {
|
||||
buggedLines.add(lineNum);
|
||||
}
|
||||
|
||||
updateBuggedState();
|
||||
|
||||
el.dispatchEvent(new CustomEvent('bugged-line-toggle', {
|
||||
bubbles: true,
|
||||
detail: { line: lineNum, bugged: buggedLines.has(lineNum), allBugged: [...buggedLines] }
|
||||
}));
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -75,41 +75,230 @@ const stars = '★'.repeat(challenge.difficulty) + '☆'.repeat(5 - challenge.di
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Code Viewer -->
|
||||
<div class="lg:col-span-2">
|
||||
<DiffViewer code={challenge.code} hints={challenge.hints} />
|
||||
|
||||
<!-- Fix Submission -->
|
||||
<div id="fix-panel" class="mt-6 bg-[#161b22] border border-[#30363d] rounded-md overflow-hidden">
|
||||
<div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold text-[#c9d1d9]">Submit Your Fix</h2>
|
||||
<div id="bugged-count" class="text-sm text-[#8b949e]">
|
||||
Lines marked: <span id="bugged-count-num" class="text-[#f85149] font-semibold">0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Controls Panel -->
|
||||
<div class="lg:col-span-1">
|
||||
<div class="bg-[#161b22] border border-[#30363d] rounded-md p-5 sticky top-6">
|
||||
<h2 class="text-lg font-semibold text-[#c9d1d9] mb-4">Analysis Tools</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="text-sm text-[#8b949e] mb-4">
|
||||
<p class="mb-2">Click on the left side of lines to mark them as buggy (like breakpoints).</p>
|
||||
<p>Hover over marked lines to see hints.</p>
|
||||
<!-- Marked lines summary -->
|
||||
<div id="marked-lines-summary" class="px-4 py-3 border-b border-[#30363d] hidden">
|
||||
<p class="text-sm text-[#8b949e] mb-2">Marked lines:</p>
|
||||
<div id="marked-lines-tags" class="flex flex-wrap gap-2"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- Inline fix editor -->
|
||||
<div class="p-4">
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-[#c9d1d9] mb-2">
|
||||
Your Fix (unified diff):
|
||||
Line-by-line fix:
|
||||
</label>
|
||||
<p class="text-xs text-[#8b949e] mb-3">Click a marked line number below to edit its fix.</p>
|
||||
<div id="fix-editor-container" class="space-y-2">
|
||||
<p id="fix-editor-placeholder" class="text-sm text-[#484f58] italic">Click on lines in the diff above to mark them, then add your fixes here.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Unified diff textarea -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-[#c9d1d9] mb-2">
|
||||
Or provide as unified diff:
|
||||
</label>
|
||||
<textarea
|
||||
id="diff-textarea"
|
||||
placeholder="- old line + new line"
|
||||
rows={8}
|
||||
rows={6}
|
||||
class="w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<button class="w-full bg-[#1f6feb] hover:bg-[#388bfd] text-white font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0">
|
||||
<div class="flex gap-3">
|
||||
<button id="submit-fix-btn" class="flex-1 bg-[#1f6feb] hover:bg-[#388bfd] disabled:bg-[#21262d] disabled:text-[#484f58] disabled:cursor-not-allowed text-white font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0">
|
||||
Submit Fix
|
||||
</button>
|
||||
<button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0">
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="submission-message" class="mt-3 text-sm hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<script client:load>
|
||||
(() => {
|
||||
const buggedLines = new Set();
|
||||
const fixEditorContainer = document.getElementById('fix-editor-container');
|
||||
const markedLinesTags = document.getElementById('marked-lines-tags');
|
||||
const markedLinesSummary = document.getElementById('marked-lines-summary');
|
||||
const buggedCountNum = document.getElementById('bugged-count-num');
|
||||
const submitBtn = document.getElementById('submit-fix-btn');
|
||||
const clearBtn = document.getElementById('clear-all-btn');
|
||||
const diffTextarea = document.getElementById('diff-textarea');
|
||||
const submissionMessage = document.getElementById('submission-message');
|
||||
const fixEditorPlaceholder = document.getElementById('fix-editor-placeholder');
|
||||
|
||||
function updateUI() {
|
||||
const arr = [...buggedLines].sort((a, b) => a - b);
|
||||
buggedCountNum.textContent = arr.length.toString();
|
||||
|
||||
if (markedLinesTags) {
|
||||
markedLinesTags.innerHTML = '';
|
||||
arr.forEach(lineNum => {
|
||||
const tag = document.createElement('span');
|
||||
tag.className = 'inline-flex items-center gap-1 bg-[#f8514920] text-[#f85149] text-xs px-2 py-0.5 rounded cursor-pointer hover:bg-[#f8514930] transition-colors';
|
||||
tag.textContent = `Line ${lineNum}`;
|
||||
tag.addEventListener('click', () => {
|
||||
showLineFixEditor(lineNum);
|
||||
});
|
||||
markedLinesTags.appendChild(tag);
|
||||
});
|
||||
}
|
||||
|
||||
if (markedLinesSummary) {
|
||||
markedLinesSummary.classList.toggle('hidden', arr.length === 0);
|
||||
}
|
||||
|
||||
if (fixEditorPlaceholder) {
|
||||
fixEditorPlaceholder.classList.toggle('hidden', arr.length > 0);
|
||||
}
|
||||
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = arr.length === 0 && !diffTextarea?.value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
function showLineFixEditor(lineNum) {
|
||||
if (!fixEditorContainer) return;
|
||||
|
||||
const existing = document.getElementById(`fix-editor-${lineNum}`);
|
||||
if (existing) {
|
||||
existing.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
existing.querySelector('textarea')?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const editorDiv = document.createElement('div');
|
||||
editorDiv.id = `fix-editor-${lineNum}`;
|
||||
editorDiv.className = 'bg-[#0d1117] border border-[#30363d] rounded-md p-3';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'flex items-center justify-between mb-2';
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'text-sm font-medium text-[#f85149]';
|
||||
label.textContent = `Line ${lineNum} — Your fix:`;
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'text-xs text-[#484f58] hover:text-[#f85149] transition-colors';
|
||||
removeBtn.textContent = '✕';
|
||||
removeBtn.addEventListener('click', () => {
|
||||
buggedLines.delete(lineNum);
|
||||
editorDiv.remove();
|
||||
updateUI();
|
||||
});
|
||||
|
||||
header.appendChild(label);
|
||||
header.appendChild(removeBtn);
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.rows = 3;
|
||||
textarea.className = 'w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono';
|
||||
textarea.placeholder = 'Enter the corrected line...';
|
||||
textarea.addEventListener('input', updateUI);
|
||||
|
||||
editorDiv.appendChild(header);
|
||||
editorDiv.appendChild(textarea);
|
||||
|
||||
fixEditorContainer.appendChild(editorDiv);
|
||||
}
|
||||
|
||||
// Listen for bugged-line-toggle events from DiffViewer
|
||||
document.addEventListener('bugged-line-toggle', (e) => {
|
||||
const detail = e.detail;
|
||||
if (detail.bugged) {
|
||||
buggedLines.add(detail.line);
|
||||
showLineFixEditor(detail.line);
|
||||
} else {
|
||||
buggedLines.delete(detail.line);
|
||||
const editor = document.getElementById(`fix-editor-${detail.line}`);
|
||||
if (editor) editor.remove();
|
||||
}
|
||||
updateUI();
|
||||
});
|
||||
|
||||
// Diff textarea input
|
||||
if (diffTextarea) {
|
||||
diffTextarea.addEventListener('input', updateUI);
|
||||
}
|
||||
|
||||
// Submit
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', () => {
|
||||
const lineFixes = {};
|
||||
buggedLines.forEach(lineNum => {
|
||||
const editor = document.getElementById(`fix-editor-${lineNum}`);
|
||||
if (editor) {
|
||||
const ta = editor.querySelector('textarea');
|
||||
if (ta?.value.trim()) {
|
||||
lineFixes[lineNum] = ta.value.trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const unifiedDiff = diffTextarea?.value.trim() || '';
|
||||
|
||||
if (Object.keys(lineFixes).length === 0 && !unifiedDiff) {
|
||||
if (submissionMessage) {
|
||||
submissionMessage.textContent = 'Please mark some lines or provide a diff.';
|
||||
submissionMessage.className = 'mt-3 text-sm text-[#f85149]';
|
||||
submissionMessage.classList.remove('hidden');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Show success message
|
||||
if (submissionMessage) {
|
||||
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
|
||||
submissionMessage.className = 'mt-3 text-sm text-[3ac840]';
|
||||
submissionMessage.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Clear all
|
||||
if (clearBtn) {
|
||||
clearBtn.addEventListener('click', () => {
|
||||
buggedLines.clear();
|
||||
const editors = fixEditorContainer?.querySelectorAll('[id^="fix-editor-"]');
|
||||
editors?.forEach(e => e.remove());
|
||||
if (diffTextarea) diffTextarea.value = '';
|
||||
if (submissionMessage) submissionMessage.classList.add('hidden');
|
||||
updateUI();
|
||||
|
||||
// Reset bugged state in DiffViewer
|
||||
document.querySelectorAll('tr.bugged').forEach(row => {
|
||||
row.classList.remove('bugged');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateUI();
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#fix-editor-container textarea {
|
||||
resize: vertical;
|
||||
min-height: 48px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue