feat(ui): view challenge source link (#66305)

Co-authored-by: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com>
Co-authored-by: Ahmad Abdolsaheb <ahmad.abdolsaheb@gmail.com>
This commit is contained in:
Anna
2026-05-02 11:28:18 -04:00
committed by GitHub
parent 791fdec328
commit 482de9f187
12 changed files with 102 additions and 4 deletions
+1
View File
@@ -2,3 +2,4 @@ export const MAX_MOBILE_WIDTH = 767;
export const EX_SMALL_VIEWPORT_HEIGHT = 300; export const EX_SMALL_VIEWPORT_HEIGHT = 300;
export const TOOL_PANEL_HEIGHT = 37; export const TOOL_PANEL_HEIGHT = 37;
export const SEARCH_EXPOSED_WIDTH = 980; export const SEARCH_EXPOSED_WIDTH = 980;
export const GITHUB_LOCATION = 'https://github.com/freeCodeCamp';
@@ -125,6 +125,7 @@
"sign-in-with-google": "Sign in with Google", "sign-in-with-google": "Sign in with Google",
"go-to-dcc-today": "Go to Today's Challenge", "go-to-dcc-today": "Go to Today's Challenge",
"go-to-dcc-archive": "Go to Daily Coding Challenge Archive", "go-to-dcc-archive": "Go to Daily Coding Challenge Archive",
"challenge-source": "View Challenge Source",
"outline": "Outline" "outline": "Outline"
}, },
"daily-coding-challenges": { "daily-coding-challenges": {
@@ -1421,7 +1422,8 @@
"too-long-three": "Please copy/paste all the editor code showing in the challenge from where you just linked.", "too-long-three": "Please copy/paste all the editor code showing in the challenge from where you just linked.",
"add-code-one": "Replace these two sentences with your copied code.", "add-code-one": "Replace these two sentences with your copied code.",
"add-code-two": "Please leave the ``` line above and the ``` line below,", "add-code-two": "Please leave the ``` line above and the ``` line below,",
"add-code-three": "because they allow your code to properly format in the post." "add-code-three": "because they allow your code to properly format in the post.",
"git-info": "Github Link: {{gitLink}}"
}, },
"user-token": { "user-token": {
"title": "User Token", "title": "User Token",
@@ -0,0 +1,39 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
describe('generateGitHubLink', () => {
afterEach(() => vi.resetModules());
it('should return a link to a challenge for an english block', async () => {
vi.doMock('../../config/env.json', () => ({
default: {
curriculumLocale: 'english'
}
}));
const { generateGithubLink } = await import('./create-github-link');
const link = generateGithubLink(
'5d5a813321b9e3db6c106a46',
'learn-basic-javascript-by-building-a-role-playing-game'
);
expect(link).toBe(
'https://github.com/freeCodeCamp/freeCodeCamp/blob/main/curriculum/challenges/english/blocks/learn-basic-javascript-by-building-a-role-playing-game/5d5a813321b9e3db6c106a46.md'
);
});
it('should return a link for a challenge in the Spanish curriculum', async () => {
vi.doMock('../../config/env.json', () => ({
default: {
curriculumLocale: 'espanol'
}
}));
const { generateGithubLink } = await import('./create-github-link');
const link = generateGithubLink(
'5d5a813321b9e3db6c106a46',
'learn-basic-javascript-by-building-a-role-playing-game'
);
expect(link).toBe(
'https://github.com/freeCodeCamp/i18n-curriculum/blob/main/curriculum/challenges/espanol/blocks/learn-basic-javascript-by-building-a-role-playing-game/5d5a813321b9e3db6c106a46.md'
);
});
});
@@ -0,0 +1,22 @@
import envData from '../../config/env.json';
import { GITHUB_LOCATION } from '../../config/misc';
const { curriculumLocale } = envData;
export const generateGithubLink = (challengeId: string, block: string) => {
const repository =
curriculumLocale === 'english' ? '/freeCodeCamp' : '/i18n-curriculum';
const gitURL = new URL(GITHUB_LOCATION);
gitURL.pathname =
gitURL.pathname +
[
repository,
'blob/main/curriculum/challenges',
curriculumLocale,
'blocks',
block,
`${challengeId}.md`
].join('/');
return gitURL.toString();
};
@@ -203,6 +203,7 @@ function ShowClassic({
title, title,
description, description,
instructions, instructions,
id,
hooks, hooks,
tests, tests,
challengeType, challengeType,
@@ -421,6 +422,8 @@ function ShowClassic({
description={description} description={description}
instructions={instructions} instructions={instructions}
superBlock={superBlock} superBlock={superBlock}
challengeId={id}
block={block}
/> />
} }
challengeTitle={ challengeTitle={
@@ -2,17 +2,22 @@ import React, { useEffect } from 'react';
import { initializeMathJax, isMathJaxAllowed } from '../../../utils/math-jax'; import { initializeMathJax, isMathJaxAllowed } from '../../../utils/math-jax';
import PrismFormatted from './prism-formatted'; import PrismFormatted from './prism-formatted';
import './challenge-description.css'; import './challenge-description.css';
import { generateGithubLink } from '../../../components/create-github-link';
type Props = { type Props = {
description?: string; description?: string;
instructions?: string; instructions?: string;
superBlock?: string; superBlock?: string;
challengeId: string;
block: string;
}; };
const ChallengeDescription = ({ const ChallengeDescription = ({
description, description,
instructions, instructions,
superBlock superBlock,
challengeId,
block
}: Props) => { }: Props) => {
useEffect(() => { useEffect(() => {
if (superBlock && isMathJaxAllowed(superBlock)) { if (superBlock && isMathJaxAllowed(superBlock)) {
@@ -20,10 +25,12 @@ const ChallengeDescription = ({
} }
}, [superBlock]); }, [superBlock]);
const githubLink = generateGithubLink(challengeId, block);
return ( return (
<div <div
className={'challenge-instructions mathjax-support'} className={'challenge-instructions mathjax-support'}
data-playwright-test-label='challenge-description' data-playwright-test-label='challenge-description'
data-github-link={githubLink}
> >
{description && <PrismFormatted text={description} />} {description && <PrismFormatted text={description} />}
{instructions && description && <hr />} {instructions && description && <hr />}
@@ -120,6 +120,7 @@ const ShowGeneric = ({
explanation, explanation,
challengeType, challengeType,
helpCategory, helpCategory,
id,
instructions, instructions,
questions, questions,
tests, tests,
@@ -297,6 +298,8 @@ const ShowGeneric = ({
<ChallengeDescription <ChallengeDescription
description={description} description={description}
superBlock={superBlock} superBlock={superBlock}
block={block}
challengeId={id}
/> />
<Spacer size='m' /> <Spacer size='m' />
</Col> </Col>
@@ -336,6 +339,8 @@ const ShowGeneric = ({
<ChallengeDescription <ChallengeDescription
instructions={instructions} instructions={instructions}
superBlock={superBlock} superBlock={superBlock}
block={block}
challengeId={id}
/> />
<Spacer size='m' /> <Spacer size='m' />
</> </>
@@ -139,6 +139,7 @@ function MsTrophy(props: MsTrophyProps) {
description, description,
instructions, instructions,
superBlock, superBlock,
id,
block, block,
translationPending translationPending
} }
@@ -175,6 +176,8 @@ function MsTrophy(props: MsTrophyProps) {
superBlock={superBlock} superBlock={superBlock}
description={description} description={description}
instructions={instructions} instructions={instructions}
block={block}
challengeId={id}
/> />
<LinkMsUser /> <LinkMsUser />
<hr /> <hr />
@@ -144,6 +144,7 @@ const ShowBackEnd = (props: BackEndProps) => {
challengeType, challengeType,
forumTopicId, forumTopicId,
title, title,
id,
description, description,
instructions, instructions,
translationPending, translationPending,
@@ -183,6 +184,8 @@ const ShowBackEnd = (props: BackEndProps) => {
superBlock={superBlock} superBlock={superBlock}
description={description} description={description}
instructions={instructions} instructions={instructions}
block={block}
challengeId={id}
/> />
<Spacer size='m' /> <Spacer size='m' />
<SolutionForm <SolutionForm
@@ -120,6 +120,7 @@ const ShowFrontEndProject = (props: ProjectProps) => {
challengeType, challengeType,
forumTopicId, forumTopicId,
title, title,
id,
description, description,
instructions, instructions,
superBlock, superBlock,
@@ -157,6 +158,8 @@ const ShowFrontEndProject = (props: ProjectProps) => {
superBlock={superBlock} superBlock={superBlock}
description={description} description={description}
instructions={instructions} instructions={instructions}
block={block}
challengeId={id}
/> />
<Spacer size='m' /> <Spacer size='m' />
<SolutionForm <SolutionForm
@@ -98,6 +98,7 @@ const ShowQuiz = ({
description, description,
challengeType, challengeType,
helpCategory, helpCategory,
id,
superBlock, superBlock,
block, block,
tests, tests,
@@ -356,6 +357,8 @@ const ShowQuiz = ({
<ChallengeDescription <ChallengeDescription
description={description} description={description}
superBlock={superBlock} superBlock={superBlock}
block={block}
challengeId={id}
/> />
<Spacer size='l' /> <Spacer size='l' />
<ObserveKeys> <ObserveKeys>
@@ -12,6 +12,7 @@ import {
challengeMetaSelector, challengeMetaSelector,
projectFormValuesSelector projectFormValuesSelector
} from './selectors'; } from './selectors';
import { generateGithubLink } from '../../../components/create-github-link';
const { forumLocation } = envData; const { forumLocation } = envData;
@@ -131,7 +132,8 @@ function createQuestionEpic(action$, state$, { window }) {
superBlock, superBlock,
block, block,
helpCategory, helpCategory,
challengeType challengeType,
id
} = challengeMetaSelector(state); } = challengeMetaSelector(state);
challengeFiles = insertEditableRegions(challengeFiles); challengeFiles = insertEditableRegions(challengeFiles);
@@ -156,13 +158,18 @@ function createQuestionEpic(action$, state$, { window }) {
projectFormValuesSelector(state) projectFormValuesSelector(state)
); );
const gitLink = generateGithubLink(id, block);
const gitInfo = i18next.t('forum-help.git-info', {
gitLink
});
const browserInfoHeading = i18next.t('forum-help.browser-info'); const browserInfoHeading = i18next.t('forum-help.browser-info');
const userAgentHeading = i18next.t('forum-help.user-agent', { const userAgentHeading = i18next.t('forum-help.user-agent', {
userAgent userAgent
}); });
const challengeHeading = i18next.t('forum-help.challenge'); const challengeHeading = i18next.t('forum-help.challenge');
const blockTitle = i18next.t(`intro:${superBlock}.blocks.${block}.title`); const blockTitle = i18next.t(`intro:${superBlock}.blocks.${block}.title`);
const endingText = `### ${browserInfoHeading}\n\n${userAgentHeading}\n\n### ${challengeHeading}\n${blockTitle} - ${challengeTitle}\n${challengeUrl}`; const endingText = `### ${browserInfoHeading}\n\n${userAgentHeading}\n\n### ${challengeHeading}\n${blockTitle} - ${challengeTitle}\n${challengeUrl}\n${gitInfo}`;
const camperCodeHeading = nonCodeChallenges.includes(challengeType) const camperCodeHeading = nonCodeChallenges.includes(challengeType)
? '' ? ''