Skip to content

Commit cc61b02

Browse files
Merge pull request #703 from freeCodeCamp/main
Create a new pull request by comparing changes across two branches
2 parents e4010f5 + 411c820 commit cc61b02

File tree

72 files changed

+2470
-176
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+2470
-176
lines changed

client/i18n/locales/english/intro.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -585,10 +585,7 @@
585585
"In these projects, you'll need to fetch data and parse a dataset, then use D3 to create different data visualizations. Finish them all to earn your Data Visualization certification."
586586
]
587587
},
588-
"d3-dashboard": {
589-
"title": "D3 Dashboard",
590-
"intro": ["", ""]
591-
}
588+
"d3-dashboard": { "title": "D3 Dashboard", "intro": ["", ""] }
592589
}
593590
},
594591
"relational-database": {
@@ -891,7 +888,6 @@
891888
"In this project, you'll discover how to implement an interface in Python while building a simple equation solver program."
892889
]
893890
},
894-
895891
"learn-special-methods-by-building-a-vector-space": {
896892
"title": "Learn Special Methods by Building a Vector Space",
897893
"intro": [
@@ -1713,6 +1709,12 @@
17131709
"front-end-development-certification-exam": {
17141710
"title": "Front End Development Certification Exam",
17151711
"intro": ["", ""]
1712+
},
1713+
"workshop-blog-page": {
1714+
"title": "Build a Cat Blog Page",
1715+
"intro": [
1716+
"In this workshop, you will learn how to build an HTML only blog page using semantic elements including the main, nav, article and footer elements."
1717+
]
17161718
}
17171719
}
17181720
},

client/src/components/profile/components/time-line.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,13 @@ function TimelineInner({
198198
</Table>
199199
)}
200200
{id && (
201-
<Modal onClose={closeSolution} open={solutionOpen}>
201+
<Modal onClose={closeSolution} open={solutionOpen} size='large'>
202202
<Modal.Header showCloseButton={true} closeButtonClassNames='close'>
203203
{`${username}'s Solution to ${
204204
idToNameMap.get(id)?.challengeTitle ?? ''
205205
}`}
206206
</Modal.Header>
207-
<Modal.Body>
207+
<Modal.Body alignment='left'>
208208
<SolutionViewer
209209
challengeFiles={challengeData.challengeFiles}
210210
solution={challengeData.solution ?? ''}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: Introduction to the Build a Cat Blog Page
3+
block: workshop-blog-page
4+
superBlock: front-end-development
5+
---
6+
7+
## Introduction to the Build a Cat Blog Page
8+
9+
This is the second workshop for the new frontend certification.

client/src/templates/Challenges/classic/editor.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ textarea.inputarea {
5555
max-width: unset !important;
5656
}
5757

58+
.editor-upper-jaw code {
59+
white-space: pre-wrap;
60+
}
61+
5862
.action-row-container,
5963
.description-container {
6064
background-color: var(--secondary-background);
@@ -141,6 +145,7 @@ textarea.inputarea {
141145
from {
142146
background-color: var(--highlight-background);
143147
}
148+
144149
to {
145150
background-color: var(--secondary-background);
146151
}
@@ -211,10 +216,12 @@ textarea.inputarea {
211216
0% {
212217
opacity: 0;
213218
}
219+
214220
/* keep test feedback contents initially hidden for 200ms*/
215221
40% {
216222
opacity: 0;
217223
}
224+
218225
100% {
219226
opacity: 1;
220227
}

client/src/templates/Challenges/classic/mobile-layout.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import i18next from 'i18next';
2-
import React, { Component } from 'react';
2+
import React, { Component, type ReactNode } from 'react';
33
import { faWindowRestore } from '@fortawesome/free-solid-svg-icons';
44
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
55
import { createSelector } from 'reselect';
@@ -17,14 +17,12 @@ import {
1717
showPreviewPaneSelector
1818
} from '../redux/selectors';
1919
import { TOOL_PANEL_HEIGHT } from '../../../../config/misc';
20-
import ToolPanel from '../components/tool-panel';
2120
import PreviewPortal from '../components/preview-portal';
2221
import Notes from '../components/notes';
2322
import EditorTabs from './editor-tabs';
2423

2524
interface MobileLayoutProps {
2625
editor: JSX.Element | null;
27-
guideUrl: string;
2826
hasEditableBoundaries: boolean;
2927
hasPreview: boolean;
3028
instructions: JSX.Element;
@@ -34,13 +32,13 @@ interface MobileLayoutProps {
3432
windowTitle: string;
3533
showPreviewPortal: boolean;
3634
showPreviewPane: boolean;
35+
toolPanel: ReactNode;
3736
removePortalWindow: () => void;
3837
setShowPreviewPortal: (arg: boolean) => void;
3938
setShowPreviewPane: (arg: boolean) => void;
4039
portalWindow: null | Window;
4140
updateUsingKeyboardInTablist: (arg0: boolean) => void;
4241
testOutput: JSX.Element;
43-
videoUrl: string;
4442
usesMultifileEditor: boolean;
4543
}
4644

@@ -160,13 +158,12 @@ class MobileLayout extends Component<MobileLayoutProps, MobileLayoutState> {
160158
onPreviewResize,
161159
showPreviewPane,
162160
showPreviewPortal,
161+
toolPanel,
163162
removePortalWindow,
164163
setShowPreviewPane,
165164
setShowPreviewPortal,
166165
portalWindow,
167166
windowTitle,
168-
guideUrl,
169-
videoUrl,
170167
usesMultifileEditor
171168
} = this.props;
172169

@@ -309,13 +306,7 @@ class MobileLayout extends Component<MobileLayoutProps, MobileLayoutState> {
309306
)}
310307
</TabsContent>
311308
)}
312-
{!hasEditableBoundaries && (
313-
<ToolPanel
314-
guideUrl={guideUrl}
315-
isMobile={true}
316-
videoUrl={videoUrl}
317-
/>
318-
)}
309+
{!hasEditableBoundaries && toolPanel}
319310
{hasPreview && this.state.currentTab !== 'preview' && (
320311
<div className='portal-button-wrap'>
321312
<button

client/src/templates/Challenges/classic/show.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import { savedChallengesSelector } from '../../../redux/selectors';
6060
import { getGuideUrl } from '../utils';
6161
import { preloadPage } from '../../../../utils/gatsby/page-loading';
6262
import envData from '../../../../config/env.json';
63+
import ToolPanel from '../components/tool-panel';
6364
import { XtermTerminal } from './xterm';
6465
import MultifileEditor from './multifile-editor';
6566
import DesktopLayout from './desktop-layout';
@@ -227,6 +228,7 @@ function ShowClassic({
227228
const isMobile = useMediaQuery({
228229
query: `(max-width: ${MAX_MOBILE_WIDTH}px)`
229230
});
231+
const guideUrl = getGuideUrl({ forumTopicId, title });
230232

231233
const blockNameTitle = `${t(
232234
`intro:${superBlock}.blocks.${block}.title`
@@ -369,9 +371,9 @@ function ShowClassic({
369371
};
370372

371373
const renderInstructionsPanel = ({
372-
showToolPanel
374+
toolPanel
373375
}: {
374-
showToolPanel: boolean;
376+
toolPanel: React.ReactNode;
375377
}) => {
376378
return (
377379
<SidePanel
@@ -392,11 +394,9 @@ function ShowClassic({
392394
{title}
393395
</ChallengeTitle>
394396
}
395-
guideUrl={getGuideUrl({ forumTopicId, title })}
396397
instructionsPanelRef={instructionsPanelRef}
397-
showToolPanel={showToolPanel}
398+
toolPanel={toolPanel}
398399
superBlock={superBlock}
399-
videoUrl={videoUrl}
400400
/>
401401
);
402402
};
@@ -445,11 +445,10 @@ function ShowClassic({
445445
isMobileLayout: true,
446446
isUsingKeyboardInTablist: usingKeyboardInTablist
447447
})}
448-
guideUrl={getGuideUrl({ forumTopicId, title })}
449448
hasEditableBoundaries={hasEditableBoundaries}
450449
hasPreview={showPreview}
451450
instructions={renderInstructionsPanel({
452-
showToolPanel: false
451+
toolPanel: null
453452
})}
454453
notes={notes}
455454
onPreviewResize={onPreviewResize}
@@ -465,9 +464,11 @@ function ShowClassic({
465464
testOutput={
466465
<Output defaultOutput={defaultOutput} output={output} />
467466
}
467+
toolPanel={
468+
<ToolPanel guideUrl={guideUrl} isMobile videoUrl={videoUrl} />
469+
}
468470
updateUsingKeyboardInTablist={updateUsingKeyboardInTablist}
469471
usesMultifileEditor={usesMultifileEditor}
470-
videoUrl={videoUrl}
471472
/>
472473
)}
473474
{!isMobile && (
@@ -481,7 +482,7 @@ function ShowClassic({
481482
hasEditableBoundaries={hasEditableBoundaries}
482483
hasPreview={showPreview}
483484
instructions={renderInstructionsPanel({
484-
showToolPanel: true
485+
toolPanel: <ToolPanel guideUrl={guideUrl} videoUrl={videoUrl} />
485486
})}
486487
isFirstStep={isFirstStep}
487488
layoutState={layout}

client/src/templates/Challenges/components/side-panel.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, ReactElement } from 'react';
1+
import React, { useEffect, ReactElement, ReactNode } from 'react';
22
import { connect } from 'react-redux';
33
import { createSelector } from 'reselect';
44
import { Test } from '../../../redux/prop-types';
@@ -7,7 +7,6 @@ import { SuperBlocks } from '../../../../../shared/config/curriculum';
77
import { initializeMathJax } from '../../../utils/math-jax';
88
import { challengeTestsSelector } from '../redux/selectors';
99
import TestSuite from './test-suite';
10-
import ToolPanel from './tool-panel';
1110

1211
import './side-panel.css';
1312

@@ -21,24 +20,20 @@ interface SidePanelProps {
2120
block: string;
2221
challengeDescription: ReactElement;
2322
challengeTitle: ReactElement;
24-
guideUrl: string;
2523
instructionsPanelRef: React.RefObject<HTMLDivElement>;
26-
showToolPanel: boolean;
24+
toolPanel: ReactNode;
2725
superBlock: SuperBlocks;
2826
tests: Test[];
29-
videoUrl: string;
3027
}
3128

3229
export function SidePanel({
3330
block,
3431
challengeDescription,
3532
challengeTitle,
36-
guideUrl,
3733
instructionsPanelRef,
38-
showToolPanel = false,
34+
toolPanel,
3935
superBlock,
40-
tests,
41-
videoUrl
36+
tests
4237
}: SidePanelProps): JSX.Element {
4338
useEffect(() => {
4439
const mathJaxChallenge =
@@ -56,7 +51,7 @@ export function SidePanel({
5651
>
5752
{challengeTitle}
5853
{challengeDescription}
59-
{showToolPanel && <ToolPanel guideUrl={guideUrl} videoUrl={videoUrl} />}
54+
{toolPanel}
6055
<TestSuite tests={tests} />
6156
</div>
6257
);

client/src/templates/Challenges/rechallenge/transformers.js

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,11 @@ export const embedFilesInHtml = async function (challengeFiles) {
213213
script.removeAttribute('src');
214214
script.setAttribute('data-src', 'script.js');
215215
}
216-
return {
217-
contents: documentElement.innerHTML
218-
};
216+
return documentElement.innerHTML;
219217
};
220218

221219
if (indexHtml) {
222-
const { contents } = await transformWithFrame(
220+
const contents = await parseAndTransform(
223221
embedStylesAndScript,
224222
indexHtml.contents
225223
);
@@ -243,32 +241,11 @@ function challengeFilesToObject(challengeFiles) {
243241
return { indexHtml, indexJsx, stylesCss, scriptJs };
244242
}
245243

246-
const transformWithFrame = async function (transform, contents) {
247-
// we use iframe here since file.contents is destined to be be inserted into
248-
// the root of an iframe.
249-
const frame = document.createElement('iframe');
250-
frame.style = 'display: none';
251-
let out = { contents };
252-
try {
253-
// the frame needs to be inserted into the document to create the html
254-
// element
255-
document.body.appendChild(frame);
256-
// replace the root element with user code
257-
frame.contentDocument.documentElement.innerHTML = contents;
258-
// grab the contents now, in case the transformation fails
259-
out = { contents: frame.contentDocument.documentElement.innerHTML };
260-
// it's important to pass around the documentElement and NOT the frame
261-
// itself. It appears that the frame's documentElement can get replaced by a
262-
// blank documentElement without the contents. This seems only to happen on
263-
// Firefox.
264-
out = await transform(
265-
frame.contentDocument.documentElement,
266-
frame.contentDocument
267-
);
268-
} finally {
269-
document.body.removeChild(frame);
270-
}
271-
return out;
244+
const parseAndTransform = async function (transform, contents) {
245+
const parser = new DOMParser();
246+
const newDoc = parser.parseFromString(contents, 'text/html');
247+
248+
return await transform(newDoc.documentElement, newDoc);
272249
};
273250

274251
const transformHtml = async function (file) {
@@ -277,10 +254,10 @@ const transformHtml = async function (file) {
277254
transformSASS(documentElement),
278255
transformScript(documentElement)
279256
]);
280-
return { contents: documentElement.innerHTML };
257+
return documentElement.innerHTML;
281258
};
282259

283-
const { contents } = await transformWithFrame(transform, file.contents);
260+
const contents = await parseAndTransform(transform, file.contents);
284261
return transformContents(() => contents, file);
285262
};
286263

0 commit comments

Comments
 (0)