Merge branch 'fix/merge-78e86f5' of https://github.com/mpontus/freeCodeCamp into fix/toolpanelConflict
This commit is contained in:
@ -81,10 +81,6 @@
|
|||||||
border-bottom: 1px solid @modal-header-border-color;
|
border-bottom: 1px solid @modal-header-border-color;
|
||||||
min-height: (@modal-title-padding + @modal-title-line-height);
|
min-height: (@modal-title-padding + @modal-title-line-height);
|
||||||
}
|
}
|
||||||
// Close icon
|
|
||||||
.modal-header .close {
|
|
||||||
margin-top: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Title text within header
|
// Title text within header
|
||||||
.modal-title {
|
.modal-title {
|
||||||
|
@ -500,7 +500,7 @@ form.update-email .btn{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.challenge-list-header {
|
.challenges-list-header {
|
||||||
background-color: @brand-primary;
|
background-color: @brand-primary;
|
||||||
color: @gray-lighter;
|
color: @gray-lighter;
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
@ -825,7 +825,7 @@ code {
|
|||||||
color: @night-text-color;
|
color: @night-text-color;
|
||||||
.btn-group,
|
.btn-group,
|
||||||
.text-success,
|
.text-success,
|
||||||
.challenge-list-header,
|
.challenges-list-header,
|
||||||
.fcc-footer {
|
.fcc-footer {
|
||||||
background-color: @night-body-bg;
|
background-color: @night-body-bg;
|
||||||
}
|
}
|
||||||
|
94
common/app/routes/Challenges/Help-Modal.jsx
Normal file
94
common/app/routes/Challenges/Help-Modal.jsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Button, Modal } from 'react-bootstrap';
|
||||||
|
|
||||||
|
import ns from './ns.json';
|
||||||
|
import {
|
||||||
|
createQuestion,
|
||||||
|
openHelpChatRoom,
|
||||||
|
closeHelpModal,
|
||||||
|
helpModalSelector
|
||||||
|
} from './redux';
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({ isOpen: helpModalSelector(state) });
|
||||||
|
const mapDispatchToProps = { createQuestion, openHelpChatRoom, closeHelpModal };
|
||||||
|
const methodologyUrl = 'https://forum.freecodecamp.org/t/the-read-search-ask-methodology-for-getting-unstuck/137307'; // eslint-disable-line max-len
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
closeHelpModal: PropTypes.func,
|
||||||
|
createQuestion: PropTypes.func,
|
||||||
|
isOpen: PropTypes.bool,
|
||||||
|
openHelpChatRoom: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
export class HelpModal extends PureComponent {
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
closeHelpModal,
|
||||||
|
openHelpChatRoom,
|
||||||
|
createQuestion
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
show={ isOpen }
|
||||||
|
>
|
||||||
|
<Modal.Header className={ `${ns}-list-header` }>
|
||||||
|
Ask for help?
|
||||||
|
<span
|
||||||
|
className='close closing-x'
|
||||||
|
onClick={ closeHelpModal }
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</span>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body className='text-center'>
|
||||||
|
<h3>
|
||||||
|
If you've already tried the Read-Search-Ask method,
|
||||||
|
then you can ask for help on the freeCodeCamp forum.
|
||||||
|
</h3>
|
||||||
|
<Button
|
||||||
|
block={ true }
|
||||||
|
bsSize='lg'
|
||||||
|
bsStyle='primary'
|
||||||
|
href={ methodologyUrl }
|
||||||
|
onClick={ closeHelpModal }
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
Learn about the Read-Search-Ask Methodology
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
block={ true }
|
||||||
|
bsSize='lg'
|
||||||
|
bsStyle='primary'
|
||||||
|
onClick={ createQuestion }
|
||||||
|
>
|
||||||
|
Create a help post on the forum
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
block={ true }
|
||||||
|
bsSize='lg'
|
||||||
|
bsStyle='primary'
|
||||||
|
onClick={ openHelpChatRoom }
|
||||||
|
>
|
||||||
|
Ask for help in the Gitter Chatroom
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
block={ true }
|
||||||
|
bsSize='lg'
|
||||||
|
bsStyle='primary'
|
||||||
|
onClick={ closeHelpModal }
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HelpModal.displayName = 'HelpModal';
|
||||||
|
HelpModal.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(HelpModal);
|
@ -7,6 +7,7 @@ import { connect } from 'react-redux';
|
|||||||
import ns from './ns.json';
|
import ns from './ns.json';
|
||||||
|
|
||||||
import BugModal from './Bug-Modal.jsx';
|
import BugModal from './Bug-Modal.jsx';
|
||||||
|
import HelpModal from './Help-Modal.jsx';
|
||||||
import ToolPanel from './Tool-Panel.jsx';
|
import ToolPanel from './Tool-Panel.jsx';
|
||||||
import ChallengeTitle from './Challenge-Title.jsx';
|
import ChallengeTitle from './Challenge-Title.jsx';
|
||||||
import ChallengeDescription from './Challenge-Description.jsx';
|
import ChallengeDescription from './Challenge-Description.jsx';
|
||||||
@ -14,6 +15,7 @@ import TestSuite from './Test-Suite.jsx';
|
|||||||
import Output from './Output.jsx';
|
import Output from './Output.jsx';
|
||||||
import {
|
import {
|
||||||
openBugModal,
|
openBugModal,
|
||||||
|
openHelpModal,
|
||||||
updateHint,
|
updateHint,
|
||||||
executeChallenge,
|
executeChallenge,
|
||||||
unlockUntrustedCode,
|
unlockUntrustedCode,
|
||||||
@ -22,8 +24,7 @@ import {
|
|||||||
testsSelector,
|
testsSelector,
|
||||||
outputSelector,
|
outputSelector,
|
||||||
hintIndexSelector,
|
hintIndexSelector,
|
||||||
codeLockedSelector,
|
codeLockedSelector
|
||||||
chatRoomSelector
|
|
||||||
} from './redux';
|
} from './redux';
|
||||||
|
|
||||||
import { descriptionRegex } from './utils';
|
import { descriptionRegex } from './utils';
|
||||||
@ -35,6 +36,7 @@ const mapDispatchToProps = {
|
|||||||
executeChallenge,
|
executeChallenge,
|
||||||
updateHint,
|
updateHint,
|
||||||
openBugModal,
|
openBugModal,
|
||||||
|
openHelpModal,
|
||||||
unlockUntrustedCode
|
unlockUntrustedCode
|
||||||
};
|
};
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
@ -44,7 +46,6 @@ const mapStateToProps = createSelector(
|
|||||||
outputSelector,
|
outputSelector,
|
||||||
hintIndexSelector,
|
hintIndexSelector,
|
||||||
codeLockedSelector,
|
codeLockedSelector,
|
||||||
chatRoomSelector,
|
|
||||||
(
|
(
|
||||||
{ description },
|
{ description },
|
||||||
{ title },
|
{ title },
|
||||||
@ -52,24 +53,22 @@ const mapStateToProps = createSelector(
|
|||||||
output,
|
output,
|
||||||
hintIndex,
|
hintIndex,
|
||||||
isCodeLocked,
|
isCodeLocked,
|
||||||
helpChatRoom
|
|
||||||
) => ({
|
) => ({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
tests,
|
tests,
|
||||||
output,
|
output,
|
||||||
isCodeLocked,
|
isCodeLocked
|
||||||
helpChatRoom
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
description: PropTypes.arrayOf(PropTypes.string),
|
description: PropTypes.arrayOf(PropTypes.string),
|
||||||
executeChallenge: PropTypes.func,
|
executeChallenge: PropTypes.func,
|
||||||
helpChatRoom: PropTypes.string,
|
|
||||||
hint: PropTypes.string,
|
hint: PropTypes.string,
|
||||||
isCodeLocked: PropTypes.bool,
|
isCodeLocked: PropTypes.bool,
|
||||||
makeToast: PropTypes.func,
|
makeToast: PropTypes.func,
|
||||||
openBugModal: PropTypes.func,
|
openBugModal: PropTypes.func,
|
||||||
|
openHelpModal: PropTypes.func,
|
||||||
output: PropTypes.string,
|
output: PropTypes.string,
|
||||||
tests: PropTypes.arrayOf(PropTypes.object),
|
tests: PropTypes.arrayOf(PropTypes.object),
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
@ -125,8 +124,8 @@ export class SidePanel extends PureComponent {
|
|||||||
executeChallenge,
|
executeChallenge,
|
||||||
updateHint,
|
updateHint,
|
||||||
makeToast,
|
makeToast,
|
||||||
helpChatRoom,
|
|
||||||
openBugModal,
|
openBugModal,
|
||||||
|
openHelpModal,
|
||||||
isCodeLocked,
|
isCodeLocked,
|
||||||
unlockUntrustedCode
|
unlockUntrustedCode
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@ -147,15 +146,16 @@ export class SidePanel extends PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
<ToolPanel
|
<ToolPanel
|
||||||
executeChallenge={ executeChallenge }
|
executeChallenge={ executeChallenge }
|
||||||
helpChatRoom={ helpChatRoom }
|
|
||||||
hint={ hint }
|
hint={ hint }
|
||||||
isCodeLocked={ isCodeLocked }
|
isCodeLocked={ isCodeLocked }
|
||||||
makeToast={ makeToast }
|
makeToast={ makeToast }
|
||||||
openBugModal={ openBugModal }
|
openBugModal={ openBugModal }
|
||||||
|
openHelpModal={ openHelpModal }
|
||||||
unlockUntrustedCode={ unlockUntrustedCode }
|
unlockUntrustedCode={ unlockUntrustedCode }
|
||||||
updateHint={ updateHint }
|
updateHint={ updateHint }
|
||||||
/>
|
/>
|
||||||
<BugModal />
|
<BugModal />
|
||||||
|
<HelpModal />
|
||||||
<Output
|
<Output
|
||||||
defaultOutput={
|
defaultOutput={
|
||||||
`/**
|
`/**
|
||||||
|
@ -2,6 +2,8 @@ import React, { PureComponent } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, ButtonGroup, Tooltip, OverlayTrigger } from 'react-bootstrap';
|
import { Button, ButtonGroup, Tooltip, OverlayTrigger } from 'react-bootstrap';
|
||||||
|
|
||||||
|
import ns from './ns.json';
|
||||||
|
|
||||||
const unlockWarning = (
|
const unlockWarning = (
|
||||||
<Tooltip id='tooltip'>
|
<Tooltip id='tooltip'>
|
||||||
<h4>
|
<h4>
|
||||||
@ -12,11 +14,11 @@ const unlockWarning = (
|
|||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
executeChallenge: PropTypes.func.isRequired,
|
executeChallenge: PropTypes.func.isRequired,
|
||||||
helpChatRoom: PropTypes.string,
|
|
||||||
hint: PropTypes.string,
|
hint: PropTypes.string,
|
||||||
isCodeLocked: PropTypes.bool,
|
isCodeLocked: PropTypes.bool,
|
||||||
makeToast: PropTypes.func.isRequired,
|
makeToast: PropTypes.func.isRequired,
|
||||||
openBugModal: PropTypes.func.isRequired,
|
openBugModal: PropTypes.func.isRequired,
|
||||||
|
openHelpModal: PropTypes.func.isRequired,
|
||||||
unlockUntrustedCode: PropTypes.func.isRequired,
|
unlockUntrustedCode: PropTypes.func.isRequired,
|
||||||
updateHint: PropTypes.func.isRequired
|
updateHint: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
@ -93,10 +95,10 @@ export default class ToolPanel extends PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
executeChallenge,
|
executeChallenge,
|
||||||
helpChatRoom,
|
|
||||||
hint,
|
hint,
|
||||||
isCodeLocked,
|
isCodeLocked,
|
||||||
openBugModal,
|
openBugModal,
|
||||||
|
openHelpModal,
|
||||||
unlockUntrustedCode
|
unlockUntrustedCode
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
@ -111,13 +113,12 @@ export default class ToolPanel extends PureComponent {
|
|||||||
}
|
}
|
||||||
<div className='button-spacer' />
|
<div className='button-spacer' />
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
className='input-group'
|
className={`input-group ${ns}-tool-panel-btn-grp`}
|
||||||
justified={ true }
|
justified={ true }
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
bsSize='large'
|
bsSize='large'
|
||||||
bsStyle='primary'
|
bsStyle='primary'
|
||||||
componentClass='label'
|
|
||||||
onClick={ this.makeReset }
|
onClick={ this.makeReset }
|
||||||
>
|
>
|
||||||
Reset
|
Reset
|
||||||
@ -125,16 +126,13 @@ export default class ToolPanel extends PureComponent {
|
|||||||
<Button
|
<Button
|
||||||
bsSize='large'
|
bsSize='large'
|
||||||
bsStyle='primary'
|
bsStyle='primary'
|
||||||
componentClass='a'
|
onClick={ openHelpModal }
|
||||||
href={ `https://gitter.im/freecodecamp/${helpChatRoom}` }
|
|
||||||
target='_blank'
|
|
||||||
>
|
>
|
||||||
Help
|
Help
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
bsSize='large'
|
bsSize='large'
|
||||||
bsStyle='primary'
|
bsStyle='primary'
|
||||||
componentClass='label'
|
|
||||||
onClick={ openBugModal }
|
onClick={ openBugModal }
|
||||||
>
|
>
|
||||||
Bug
|
Bug
|
||||||
|
@ -216,4 +216,12 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.@{ns}-tool-panel-btn-grp {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&{ @import "./views/index.less"; }
|
&{ @import "./views/index.less"; }
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
import { ofType } from 'redux-epic';
|
|
||||||
import {
|
|
||||||
types,
|
|
||||||
closeBugModal
|
|
||||||
} from '../redux';
|
|
||||||
|
|
||||||
import { filesSelector } from '../../../files';
|
|
||||||
import { currentChallengeSelector } from '../../../redux';
|
|
||||||
|
|
||||||
function filesToMarkdown(files = {}) {
|
|
||||||
const moreThenOneFile = Object.keys(files).length > 1;
|
|
||||||
return Object.keys(files).reduce((fileString, key) => {
|
|
||||||
const file = files[key];
|
|
||||||
if (!file) {
|
|
||||||
return fileString;
|
|
||||||
}
|
|
||||||
const fileName = moreThenOneFile ? `\\ file: ${file.contents}` : '';
|
|
||||||
const fileType = file.ext;
|
|
||||||
return fileString +
|
|
||||||
'\`\`\`' +
|
|
||||||
fileType +
|
|
||||||
'\n' +
|
|
||||||
fileName +
|
|
||||||
'\n' +
|
|
||||||
file.contents +
|
|
||||||
'\n' +
|
|
||||||
'\`\`\`\n\n';
|
|
||||||
}, '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function bugEpic(actions, { getState }, { window }) {
|
|
||||||
return actions::ofType(types.openIssueSearch, types.createIssue)
|
|
||||||
.map(({ type }) => {
|
|
||||||
const state = getState();
|
|
||||||
const files = filesSelector(state);
|
|
||||||
const challengeName = currentChallengeSelector(state);
|
|
||||||
const {
|
|
||||||
navigator: { userAgent },
|
|
||||||
location: { href }
|
|
||||||
} = window;
|
|
||||||
let titleText = challengeName;
|
|
||||||
if (type === types.openIssueSearch) {
|
|
||||||
window.open(
|
|
||||||
'https://forum.freecodecamp.org/search?q=' +
|
|
||||||
window.encodeURIComponent(titleText)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
titleText = 'Need assistance in ' + challengeName;
|
|
||||||
let textMessage = [
|
|
||||||
'#### Challenge Name\n',
|
|
||||||
'[',
|
|
||||||
challengeName,
|
|
||||||
'](',
|
|
||||||
href,
|
|
||||||
') has an issue.\n',
|
|
||||||
'#### Issue Description\n',
|
|
||||||
'<!-- Describe below when the issue happens and how to ',
|
|
||||||
'reproduce it -->\n\n\n',
|
|
||||||
'#### Browser Information\n',
|
|
||||||
'<!-- Describe your workspace in which you are having issues-->\n',
|
|
||||||
'User Agent is: <code>',
|
|
||||||
userAgent,
|
|
||||||
'</code>.\n\n',
|
|
||||||
'#### Screenshot\n',
|
|
||||||
'<!-- Add a screenshot of your issue -->\n\n\n',
|
|
||||||
'#### Your Code'
|
|
||||||
].join('');
|
|
||||||
const body = filesToMarkdown(files);
|
|
||||||
if (body.length > 10) {
|
|
||||||
textMessage = textMessage + body;
|
|
||||||
}
|
|
||||||
window.open(
|
|
||||||
'https://forum.freecodecamp.org/new-topic?category=General&title=' +
|
|
||||||
window.encodeURIComponent(titleText) + '&body=' +
|
|
||||||
window.encodeURIComponent(textMessage),
|
|
||||||
'_blank'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return closeBugModal();
|
|
||||||
});
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ import {
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import noop from 'lodash/noop';
|
import noop from 'lodash/noop';
|
||||||
|
|
||||||
import bugEpic from './bug-epic';
|
import modalEpic from './modal-epic';
|
||||||
import completionEpic from './completion-epic.js';
|
import completionEpic from './completion-epic.js';
|
||||||
import challengeEpic from './challenge-epic.js';
|
import challengeEpic from './challenge-epic.js';
|
||||||
import executeChallengeEpic from './execute-challenge-epic.js';
|
import executeChallengeEpic from './execute-challenge-epic.js';
|
||||||
@ -44,7 +44,7 @@ const challengeToFilesMetaCreator =
|
|||||||
_.flow(challengeToFiles, createFilesMetaCreator);
|
_.flow(challengeToFiles, createFilesMetaCreator);
|
||||||
|
|
||||||
export const epics = [
|
export const epics = [
|
||||||
bugEpic,
|
modalEpic,
|
||||||
challengeEpic,
|
challengeEpic,
|
||||||
codeStorageEpic,
|
codeStorageEpic,
|
||||||
completionEpic,
|
completionEpic,
|
||||||
@ -83,6 +83,12 @@ export const types = createTypes([
|
|||||||
'openIssueSearch',
|
'openIssueSearch',
|
||||||
'createIssue',
|
'createIssue',
|
||||||
|
|
||||||
|
// help
|
||||||
|
'openHelpModal',
|
||||||
|
'closeHelpModal',
|
||||||
|
'createQuestion',
|
||||||
|
'openHelpChatRoom',
|
||||||
|
|
||||||
// panes
|
// panes
|
||||||
'toggleClassicEditor',
|
'toggleClassicEditor',
|
||||||
'toggleMain',
|
'toggleMain',
|
||||||
@ -157,6 +163,12 @@ export const closeBugModal = createAction(types.closeBugModal);
|
|||||||
export const openIssueSearch = createAction(types.openIssueSearch);
|
export const openIssueSearch = createAction(types.openIssueSearch);
|
||||||
export const createIssue = createAction(types.createIssue);
|
export const createIssue = createAction(types.createIssue);
|
||||||
|
|
||||||
|
// help
|
||||||
|
export const openHelpModal = createAction(types.openHelpModal);
|
||||||
|
export const closeHelpModal = createAction(types.closeHelpModal);
|
||||||
|
export const createQuestion = createAction(types.createQuestion);
|
||||||
|
export const openHelpChatRoom = createAction(types.openHelpChatRoom);
|
||||||
|
|
||||||
// code storage
|
// code storage
|
||||||
export const storedCodeFound = createAction(
|
export const storedCodeFound = createAction(
|
||||||
types.storedCodeFound,
|
types.storedCodeFound,
|
||||||
@ -174,6 +186,7 @@ const initialUiState = {
|
|||||||
output: null,
|
output: null,
|
||||||
isChallengeModalOpen: false,
|
isChallengeModalOpen: false,
|
||||||
isBugOpen: false,
|
isBugOpen: false,
|
||||||
|
isHelpOpen: false,
|
||||||
successMessage: 'Happy Coding!'
|
successMessage: 'Happy Coding!'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -206,6 +219,7 @@ export const challengeModalSelector =
|
|||||||
state => getNS(state).isChallengeModalOpen;
|
state => getNS(state).isChallengeModalOpen;
|
||||||
|
|
||||||
export const bugModalSelector = state => getNS(state).isBugOpen;
|
export const bugModalSelector = state => getNS(state).isBugOpen;
|
||||||
|
export const helpModalSelector = state => getNS(state).isHelpOpen;
|
||||||
|
|
||||||
export const challengeRequiredSelector = state =>
|
export const challengeRequiredSelector = state =>
|
||||||
challengeSelector(state).required || [];
|
challengeSelector(state).required || [];
|
||||||
@ -318,9 +332,10 @@ export default combineReducers(
|
|||||||
...state,
|
...state,
|
||||||
output: (state.output || '') + output
|
output: (state.output || '') + output
|
||||||
}),
|
}),
|
||||||
|
|
||||||
[types.openBugModal]: state => ({ ...state, isBugOpen: true }),
|
[types.openBugModal]: state => ({ ...state, isBugOpen: true }),
|
||||||
[types.closeBugModal]: state => ({ ...state, isBugOpen: false })
|
[types.closeBugModal]: state => ({ ...state, isBugOpen: false }),
|
||||||
|
[types.openHelpModal]: state => ({ ...state, isHelpOpen: true }),
|
||||||
|
[types.closeHelpModal]: state => ({ ...state, isHelpOpen: false })
|
||||||
}),
|
}),
|
||||||
initialState,
|
initialState,
|
||||||
ns
|
ns
|
||||||
|
146
common/app/routes/Challenges/redux/modal-epic.js
Normal file
146
common/app/routes/Challenges/redux/modal-epic.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { combineEpics, ofType } from 'redux-epic';
|
||||||
|
import {
|
||||||
|
types,
|
||||||
|
chatRoomSelector,
|
||||||
|
closeBugModal,
|
||||||
|
closeHelpModal
|
||||||
|
} from '../redux';
|
||||||
|
|
||||||
|
import { filesSelector } from '../../../files';
|
||||||
|
import { currentChallengeSelector } from '../../../redux';
|
||||||
|
|
||||||
|
function filesToMarkdown(files = {}) {
|
||||||
|
const moreThenOneFile = Object.keys(files).length > 1;
|
||||||
|
return Object.keys(files).reduce((fileString, key) => {
|
||||||
|
const file = files[key];
|
||||||
|
if (!file) {
|
||||||
|
return fileString;
|
||||||
|
}
|
||||||
|
const fileName = moreThenOneFile ? `\\ file: ${file.contents}` : '';
|
||||||
|
const fileType = file.ext;
|
||||||
|
return fileString +
|
||||||
|
'\`\`\`' +
|
||||||
|
fileType +
|
||||||
|
'\n' +
|
||||||
|
fileName +
|
||||||
|
'\n' +
|
||||||
|
file.contents +
|
||||||
|
'\n' +
|
||||||
|
'\`\`\`\n\n';
|
||||||
|
}, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openIssueSearchEpic(actions, { getState }, { window }) {
|
||||||
|
return actions::ofType(types.openIssueSearch).map(() => {
|
||||||
|
const state = getState();
|
||||||
|
const challengeName = currentChallengeSelector(state);
|
||||||
|
|
||||||
|
window.open(
|
||||||
|
'https://forum.freecodecamp.org/search?q=' +
|
||||||
|
window.encodeURIComponent(challengeName)
|
||||||
|
);
|
||||||
|
|
||||||
|
return closeBugModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createIssueEpic(actions, { getState }, { window }) {
|
||||||
|
return actions::ofType(types.createIssue).map(() => {
|
||||||
|
const state = getState();
|
||||||
|
const files = filesSelector(state);
|
||||||
|
const challengeName = currentChallengeSelector(state);
|
||||||
|
const {
|
||||||
|
navigator: { userAgent },
|
||||||
|
location: { href }
|
||||||
|
} = window;
|
||||||
|
const titleText = 'Need assistance in ' + challengeName;
|
||||||
|
let textMessage = [
|
||||||
|
'#### Challenge Name\n',
|
||||||
|
'[',
|
||||||
|
challengeName,
|
||||||
|
'](',
|
||||||
|
href,
|
||||||
|
') has an issue.\n',
|
||||||
|
'#### Issue Description\n',
|
||||||
|
'<!-- Describe below when the issue happens and how to ',
|
||||||
|
'reproduce it -->\n\n\n',
|
||||||
|
'#### Browser Information\n',
|
||||||
|
'<!-- Describe your workspace in which you are having issues-->\n',
|
||||||
|
'User Agent is: <code>',
|
||||||
|
userAgent,
|
||||||
|
'</code>.\n\n',
|
||||||
|
'#### Screenshot\n',
|
||||||
|
'<!-- Add a screenshot of your issue -->\n\n\n',
|
||||||
|
'#### Your Code'
|
||||||
|
].join('');
|
||||||
|
|
||||||
|
const body = filesToMarkdown(files);
|
||||||
|
if (body.length > 10) {
|
||||||
|
textMessage += body;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.open(
|
||||||
|
'https://forum.freecodecamp.org/new-topic'
|
||||||
|
+ '?category=General'
|
||||||
|
+ '&title=' + window.encodeURIComponent(titleText)
|
||||||
|
+ '&body=' + window.encodeURIComponent(textMessage),
|
||||||
|
'_blank'
|
||||||
|
);
|
||||||
|
|
||||||
|
return closeBugModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openHelpChatRoomEpic(actions, { getState }, { window }) {
|
||||||
|
return actions::ofType(types.openHelpChatRoom).map(() => {
|
||||||
|
const state = getState();
|
||||||
|
const helpChatRoom = chatRoomSelector(state);
|
||||||
|
|
||||||
|
window.open(
|
||||||
|
'https://gitter.im/freecodecamp/' +
|
||||||
|
window.encodeURIComponent(helpChatRoom)
|
||||||
|
);
|
||||||
|
|
||||||
|
return closeHelpModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createQuestionEpic(actions, { getState }, { window }) {
|
||||||
|
return actions::ofType(types.createQuestion).map(() => {
|
||||||
|
const state = getState();
|
||||||
|
const files = filesSelector(state);
|
||||||
|
const challengeName = currentChallengeSelector(state);
|
||||||
|
const {
|
||||||
|
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: <code>',
|
||||||
|
userAgent,
|
||||||
|
'</code>.\n\n',
|
||||||
|
'**Link to the challenge:**\n',
|
||||||
|
href
|
||||||
|
].join('');
|
||||||
|
|
||||||
|
window.open(
|
||||||
|
'https://forum.freecodecamp.org/new-topic'
|
||||||
|
+ '?category=help'
|
||||||
|
+ '&title=' + window.encodeURIComponent(challengeName)
|
||||||
|
+ '&body=' + window.encodeURIComponent(textMessage),
|
||||||
|
'_blank'
|
||||||
|
);
|
||||||
|
|
||||||
|
return closeHelpModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default combineEpics(
|
||||||
|
openIssueSearchEpic,
|
||||||
|
createIssueEpic,
|
||||||
|
openHelpChatRoomEpic,
|
||||||
|
createQuestionEpic
|
||||||
|
);
|
Reference in New Issue
Block a user