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 TOOL_PANEL_HEIGHT = 37;
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",
"go-to-dcc-today": "Go to Today's Challenge",
"go-to-dcc-archive": "Go to Daily Coding Challenge Archive",
"challenge-source": "View Challenge Source",
"outline": "Outline"
},
"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.",
"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-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": {
"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,
description,
instructions,
id,
hooks,
tests,
challengeType,
@@ -421,6 +422,8 @@ function ShowClassic({
description={description}
instructions={instructions}
superBlock={superBlock}
challengeId={id}
block={block}
/>
}
challengeTitle={
@@ -2,17 +2,22 @@ import React, { useEffect } from 'react';
import { initializeMathJax, isMathJaxAllowed } from '../../../utils/math-jax';
import PrismFormatted from './prism-formatted';
import './challenge-description.css';
import { generateGithubLink } from '../../../components/create-github-link';
type Props = {
description?: string;
instructions?: string;
superBlock?: string;
challengeId: string;
block: string;
};
const ChallengeDescription = ({
description,
instructions,
superBlock
superBlock,
challengeId,
block
}: Props) => {
useEffect(() => {
if (superBlock && isMathJaxAllowed(superBlock)) {
@@ -20,10 +25,12 @@ const ChallengeDescription = ({
}
}, [superBlock]);
const githubLink = generateGithubLink(challengeId, block);
return (
<div
className={'challenge-instructions mathjax-support'}
data-playwright-test-label='challenge-description'
data-github-link={githubLink}
>
{description && <PrismFormatted text={description} />}
{instructions && description && <hr />}
@@ -120,6 +120,7 @@ const ShowGeneric = ({
explanation,
challengeType,
helpCategory,
id,
instructions,
questions,
tests,
@@ -297,6 +298,8 @@ const ShowGeneric = ({
<ChallengeDescription
description={description}
superBlock={superBlock}
block={block}
challengeId={id}
/>
<Spacer size='m' />
</Col>
@@ -336,6 +339,8 @@ const ShowGeneric = ({
<ChallengeDescription
instructions={instructions}
superBlock={superBlock}
block={block}
challengeId={id}
/>
<Spacer size='m' />
</>
@@ -139,6 +139,7 @@ function MsTrophy(props: MsTrophyProps) {
description,
instructions,
superBlock,
id,
block,
translationPending
}
@@ -175,6 +176,8 @@ function MsTrophy(props: MsTrophyProps) {
superBlock={superBlock}
description={description}
instructions={instructions}
block={block}
challengeId={id}
/>
<LinkMsUser />
<hr />
@@ -144,6 +144,7 @@ const ShowBackEnd = (props: BackEndProps) => {
challengeType,
forumTopicId,
title,
id,
description,
instructions,
translationPending,
@@ -183,6 +184,8 @@ const ShowBackEnd = (props: BackEndProps) => {
superBlock={superBlock}
description={description}
instructions={instructions}
block={block}
challengeId={id}
/>
<Spacer size='m' />
<SolutionForm
@@ -120,6 +120,7 @@ const ShowFrontEndProject = (props: ProjectProps) => {
challengeType,
forumTopicId,
title,
id,
description,
instructions,
superBlock,
@@ -157,6 +158,8 @@ const ShowFrontEndProject = (props: ProjectProps) => {
superBlock={superBlock}
description={description}
instructions={instructions}
block={block}
challengeId={id}
/>
<Spacer size='m' />
<SolutionForm
@@ -98,6 +98,7 @@ const ShowQuiz = ({
description,
challengeType,
helpCategory,
id,
superBlock,
block,
tests,
@@ -356,6 +357,8 @@ const ShowQuiz = ({
<ChallengeDescription
description={description}
superBlock={superBlock}
block={block}
challengeId={id}
/>
<Spacer size='l' />
<ObserveKeys>
@@ -12,6 +12,7 @@ import {
challengeMetaSelector,
projectFormValuesSelector
} from './selectors';
import { generateGithubLink } from '../../../components/create-github-link';
const { forumLocation } = envData;
@@ -131,7 +132,8 @@ function createQuestionEpic(action$, state$, { window }) {
superBlock,
block,
helpCategory,
challengeType
challengeType,
id
} = challengeMetaSelector(state);
challengeFiles = insertEditableRegions(challengeFiles);
@@ -156,13 +158,18 @@ function createQuestionEpic(action$, state$, { window }) {
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 userAgentHeading = i18next.t('forum-help.user-agent', {
userAgent
});
const challengeHeading = i18next.t('forum-help.challenge');
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)
? ''