fix(client): forum help and hints (#36625)
* fix(client): add warning on exceeding forum post length limit Co-authored-by: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com> * fix: corrected guide url for backend challenges and projects * fix: moved getGuideUrl to utils * fix: added forumTopicId to ChallengeNode PropTypes * fix: conolidated helpers and add base url to env.json * fix: made use template literals * tests: created unit tests for getGuideUrl * fix: add characters which will be encoded Co-Authored-By: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * fix: use encoded version of title Co-Authored-By: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * fix: encode title Co-Authored-By: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@ -20,6 +20,7 @@ export const MarkdownRemark = PropTypes.shape({
|
||||
|
||||
export const ChallengeNode = PropTypes.shape({
|
||||
block: PropTypes.string,
|
||||
challengeOrder: PropTypes.number,
|
||||
challengeType: PropTypes.number,
|
||||
dashedName: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
@ -31,9 +32,9 @@ export const ChallengeNode = PropTypes.shape({
|
||||
slug: PropTypes.string,
|
||||
blockName: PropTypes.string
|
||||
}),
|
||||
forumTopicId: PropTypes.number,
|
||||
guideUrl: PropTypes.string,
|
||||
head: PropTypes.arrayOf(PropTypes.string),
|
||||
challengeOrder: PropTypes.number,
|
||||
instructions: PropTypes.string,
|
||||
isBeta: PropTypes.bool,
|
||||
isComingSoon: PropTypes.bool,
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
updateProjectFormValues,
|
||||
backendNS
|
||||
} from '../redux';
|
||||
import { createGuideUrl } from '../utils';
|
||||
import { getGuideUrl } from '../utils';
|
||||
|
||||
import LearnLayout from '../../../components/layouts/Learn';
|
||||
import ChallengeTitle from '../components/Challenge-Title';
|
||||
@ -44,6 +44,7 @@ const propTypes = {
|
||||
}),
|
||||
description: PropTypes.string,
|
||||
executeChallenge: PropTypes.func.isRequired,
|
||||
forumTopicId: PropTypes.number,
|
||||
id: PropTypes.string,
|
||||
initConsole: PropTypes.func.isRequired,
|
||||
initTests: PropTypes.func.isRequired,
|
||||
@ -155,8 +156,9 @@ export class BackEnd extends Component {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
fields: { blockName, slug },
|
||||
fields: { blockName },
|
||||
challengeType,
|
||||
forumTopicId,
|
||||
title,
|
||||
description,
|
||||
instructions
|
||||
@ -210,7 +212,9 @@ export class BackEnd extends Component {
|
||||
updateProjectForm={updateProjectFormValues}
|
||||
/>
|
||||
)}
|
||||
<ProjectToolPanel guideUrl={createGuideUrl(slug)} />
|
||||
<ProjectToolPanel
|
||||
guideUrl={getGuideUrl({ forumTopicId, title })}
|
||||
/>
|
||||
<br />
|
||||
<Output
|
||||
defaultOutput={`/**
|
||||
@ -247,6 +251,7 @@ export default connect(
|
||||
export const query = graphql`
|
||||
query BackendChallenge($slug: String!) {
|
||||
challengeNode(fields: { slug: { eq: $slug } }) {
|
||||
forumTopicId
|
||||
title
|
||||
description
|
||||
instructions
|
||||
|
@ -20,7 +20,7 @@ import ResetModal from '../components/ResetModal';
|
||||
import MobileLayout from './MobileLayout';
|
||||
import DesktopLayout from './DesktopLayout';
|
||||
|
||||
import { createGuideUrl } from '../utils';
|
||||
import { getGuideUrl } from '../utils';
|
||||
import { challengeTypes } from '../../../../utils/challengeTypes';
|
||||
import { ChallengeNode } from '../../../redux/propTypes';
|
||||
import { dasherize } from '../../../../utils';
|
||||
@ -166,13 +166,6 @@ class ShowClassic extends Component {
|
||||
return `${blockName}: ${title}`;
|
||||
}
|
||||
|
||||
getGuideUrl() {
|
||||
const { forumTopicId, title } = this.getChallenge();
|
||||
return forumTopicId
|
||||
? 'https://www.freecodecamp.org/forum/t/' + forumTopicId
|
||||
: createGuideUrl(title);
|
||||
}
|
||||
|
||||
getVideoUrl = () => this.getChallenge().videoUrl;
|
||||
|
||||
getChallengeFile() {
|
||||
@ -200,11 +193,13 @@ class ShowClassic extends Component {
|
||||
nextChallengePath,
|
||||
prevChallengePath
|
||||
} = this.props.pageContext.challengeMeta;
|
||||
|
||||
const { forumTopicId, title } = this.getChallenge();
|
||||
return (
|
||||
<SidePanel
|
||||
className='full-height'
|
||||
description={description}
|
||||
guideUrl={this.getGuideUrl()}
|
||||
guideUrl={getGuideUrl({ forumTopicId, title })}
|
||||
instructions={instructions}
|
||||
introPath={introPath}
|
||||
nextChallengePath={nextChallengePath}
|
||||
@ -247,6 +242,7 @@ class ShowClassic extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { forumTopicId, title } = this.getChallenge();
|
||||
return (
|
||||
<LearnLayout>
|
||||
<Helmet
|
||||
@ -255,7 +251,7 @@ class ShowClassic extends Component {
|
||||
<Media maxWidth={MAX_MOBILE_WIDTH}>
|
||||
<MobileLayout
|
||||
editor={this.renderEditor()}
|
||||
guideUrl={this.getGuideUrl()}
|
||||
guideUrl={getGuideUrl({ forumTopicId, title })}
|
||||
hasPreview={this.hasPreview()}
|
||||
instructions={this.renderInstructionsPanel({
|
||||
showToolPanel: false
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
updateProjectFormValues
|
||||
} from '../redux';
|
||||
import { frontEndProject } from '../../../../utils/challengeTypes';
|
||||
import { createGuideUrl } from '../utils';
|
||||
import { getGuideUrl } from '../utils';
|
||||
|
||||
import LearnLayout from '../../../components/layouts/Learn';
|
||||
import Spacer from '../../../components/helpers/Spacer';
|
||||
@ -93,7 +93,8 @@ export class Project extends Component {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challengeType,
|
||||
fields: { blockName, slug },
|
||||
fields: { blockName },
|
||||
forumTopicId,
|
||||
title,
|
||||
description,
|
||||
guideUrl
|
||||
@ -127,7 +128,7 @@ export class Project extends Component {
|
||||
onSubmit={openCompletionModal}
|
||||
updateProjectForm={updateProjectFormValues}
|
||||
/>
|
||||
<ToolPanel guideUrl={createGuideUrl(slug)} />
|
||||
<ToolPanel guideUrl={getGuideUrl({ forumTopicId, title })} />
|
||||
<Spacer />
|
||||
</div>
|
||||
<CompletionModal />
|
||||
@ -148,6 +149,7 @@ export default connect(
|
||||
export const query = graphql`
|
||||
query ProjectChallenge($slug: String!) {
|
||||
challengeNode(fields: { slug: { eq: $slug } }) {
|
||||
forumTopicId
|
||||
title
|
||||
description
|
||||
challengeType
|
||||
|
@ -1,3 +1,4 @@
|
||||
import dedent from 'dedent';
|
||||
import { ofType } from 'redux-observable';
|
||||
import {
|
||||
types,
|
||||
@ -7,6 +8,7 @@ import {
|
||||
} from '../redux';
|
||||
import { tap, mapTo } from 'rxjs/operators';
|
||||
import { helpCategory } from '../../../../utils/challengeTypes';
|
||||
import { forumLocation } from '../../../../../config/env.json';
|
||||
|
||||
function filesToMarkdown(files = {}) {
|
||||
const moreThenOneFile = Object.keys(files).length > 1;
|
||||
@ -17,17 +19,7 @@ function filesToMarkdown(files = {}) {
|
||||
}
|
||||
const fileName = moreThenOneFile ? `\\ file: ${file.contents}` : '';
|
||||
const fileType = file.ext;
|
||||
return (
|
||||
fileString +
|
||||
'```' +
|
||||
fileType +
|
||||
'\n' +
|
||||
fileName +
|
||||
'\n' +
|
||||
file.contents +
|
||||
'\n' +
|
||||
'```\n\n'
|
||||
);
|
||||
return `${fileString}\`\`\`${fileType}\n${fileName}\n${file.contents}\n\`\`\`\n\n`;
|
||||
}, '\n');
|
||||
}
|
||||
|
||||
@ -44,28 +36,59 @@ function createQuestionEpic(action$, state$, { window }) {
|
||||
navigator: { userAgent },
|
||||
location: { href }
|
||||
} = window;
|
||||
const textMessage = [
|
||||
"**Tell us what's happening:**\n\n\n\n",
|
||||
'**Your code so far**\n',
|
||||
filesToMarkdown(files),
|
||||
'**Your browser information:**\n\n',
|
||||
'User Agent is: `',
|
||||
userAgent,
|
||||
'`.\n\n',
|
||||
'**Challenge:**\n',
|
||||
challengeTitle,
|
||||
'\n**Link to the challenge:**\n',
|
||||
href
|
||||
].join('');
|
||||
window.open(
|
||||
'https://forum.freecodecamp.org/new-topic' +
|
||||
'?category=' +
|
||||
window.encodeURIComponent(helpCategory[challengeType] || 'Help') +
|
||||
'&title=' +
|
||||
'&body=' +
|
||||
window.encodeURIComponent(textMessage),
|
||||
'_blank'
|
||||
|
||||
const endingText = dedent(
|
||||
`**Your browser information:**
|
||||
|
||||
User Agent is: <code>${userAgent}</code>.
|
||||
|
||||
**Challenge:** ${challengeTitle}
|
||||
|
||||
**Link to the challenge:**
|
||||
${href}`
|
||||
);
|
||||
|
||||
let textMessage = dedent(
|
||||
`**Tell us what's happening:**\n\n\n\n**Your code so far**
|
||||
${filesToMarkdown(files)}\n${endingText}`
|
||||
);
|
||||
|
||||
const altTextMessage = dedent(
|
||||
`**Tell us what's happening:**
|
||||
|
||||
|
||||
|
||||
**Your code so far**
|
||||
|
||||
WARNING
|
||||
|
||||
The challenge seed code and/or your solution exceeded the maximum length we can port over from the challenge.
|
||||
|
||||
You will need to take an additional step here so the code you wrote presents in an easy to read format.
|
||||
|
||||
Please copy/paste all the editor code showing in the challenge from where you just linked.
|
||||
|
||||
\`\`\`
|
||||
|
||||
Replace these two sentences with your copied code.
|
||||
Please leave the \`\`\` line above and the \`\`\` line below,
|
||||
because they allow your code to properly format in the post.
|
||||
|
||||
\`\`\`\n${endingText}`
|
||||
);
|
||||
|
||||
const category = window.encodeURIComponent(
|
||||
helpCategory[challengeType] || 'Help'
|
||||
);
|
||||
|
||||
const studentCode = window.encodeURIComponent(textMessage);
|
||||
const altStudentCode = window.encodeURIComponent(altTextMessage);
|
||||
|
||||
const baseURI = `${forumLocation}/new-topic?category=${category}&title=&body=`;
|
||||
const defaultURI = `${baseURI}${studentCode}`;
|
||||
const altURI = `${baseURI}${altStudentCode}`;
|
||||
|
||||
window.open(defaultURI.length < 8000 ? defaultURI : altURI, '_blank');
|
||||
}),
|
||||
mapTo(closeModal('help'))
|
||||
);
|
||||
|
@ -1,7 +1,10 @@
|
||||
const guideBase = 'https://www.freecodecamp.org/forum/search?q=';
|
||||
import { forumLocation } from '../../../../../config/env.json';
|
||||
|
||||
export function createGuideUrl(title = '') {
|
||||
return guideBase + title + '%20in%3Atitle%20order%3Aviews';
|
||||
export function getGuideUrl({ forumTopicId, title = '' }) {
|
||||
title = encodeURIComponent(title);
|
||||
return forumTopicId
|
||||
? `${forumLocation}/t/${forumTopicId}`
|
||||
: `${forumLocation}/search?q=${title}%20in%3Atitle%20order%3Aviews`;
|
||||
}
|
||||
|
||||
export function isGoodXHRStatus(status) {
|
||||
|
26
client/src/templates/Challenges/utils/index.test.js
Normal file
26
client/src/templates/Challenges/utils/index.test.js
Normal file
@ -0,0 +1,26 @@
|
||||
/* global expect */
|
||||
|
||||
import { forumLocation } from '../../../../../config/env.json';
|
||||
|
||||
const { getGuideUrl } = require('./');
|
||||
|
||||
describe('index', () => {
|
||||
describe('getGuideUrl', () => {
|
||||
it('should use forum topic url when forumTopicId is supplied', () => {
|
||||
const value = getGuideUrl({
|
||||
forumTopicId: 12345,
|
||||
title: 'a sample title'
|
||||
});
|
||||
expect(value).toEqual(`${forumLocation}/t/12345`);
|
||||
});
|
||||
|
||||
it('should use search endpoint when no forumTopicId is supplied', () => {
|
||||
const value = getGuideUrl({
|
||||
title: '& a sample title?'
|
||||
});
|
||||
expect(value).toEqual(
|
||||
`${forumLocation}/search?q=%26%20a%20sample%20title%3F%20in%3Atitle%20order%3Aviews`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user