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:
mrugesh
2019-08-28 21:55:56 +05:30
committed by GitHub
parent 428ab97650
commit a53a4ac303
7 changed files with 108 additions and 52 deletions

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'))
);

View File

@ -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) {

View 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`
);
});
});
});