diff --git a/client/less/challenge.less b/client/less/challenge.less
index e5c79dc0c9..e69de29bb2 100644
--- a/client/less/challenge.less
+++ b/client/less/challenge.less
@@ -1,237 +0,0 @@
-.challenges-instructions-panel {
- clear: both;
- overflow-x: hidden;
- overflow-y: auto;
- padding-left: 5px;
- padding-right: 5px;
- margin-bottom: 10px;
-}
-
-.challenge-step-description {
- font-size: 1.5em;
-}
-.challenge-step-counter {
- font-size: 20px;
- line-height: 44px;
-}
-
-.challenge-step-forward-leave {
- transition: opacity .4s ease-in, transform .3s ease-in-out;
- opacity: 1;
- transform: translate(0, 0);
-}
-
-.challenge-step-forward-leave-active {
- opacity: 0;
- transform: translate(-100%, 0);
-}
-
-.challenge-step-forward-enter {
- transition: opacity .4s ease-in, transform .3s ease-in-out;
- opacity: 0;
- transform: translate(100%, 0);
-}
-
-.challenge-step-forward-enter-active {
- opacity: 1;
- transform: translate(0, 0);
-}
-
-.challenge-step-backward-leave {
- transition: opacity .4s ease-in, transform .3s ease-in-out;
- opacity: 1;
- transform: translate(0, 0);
-}
-
-.challenge-step-backward-leave-active {
- opacity: 0;
- transform: translate(100%, 0);
-}
-
-.challenge-step-backward-enter {
- transition: opacity .4s ease-in, transform .3s ease-in-out;
- opacity: 0;
- transform: translate(-100%, 0);
-}
-
-.challenge-step-backward-enter-active {
- opacity: 1;
- transform: translate(0, 0);
-}
-
-.challenge-instructions-title {
- margin-top: 0;
- i {
- margin-left: 5px;
- line-height: 20px;
- }
-}
-
-.challenge-instructions {
- margin-bottom: 5px;
- h4 {
- margin-bottom: 0;
- }
- blockquote {
- font-size: 90%;
- font-family: @font-family-monospace;
- color: @code-color;
- background-color: #fffbe5;
- border-radius: @border-radius-base;
- border: 1px solid @pre-border-color;
- white-space: pre;
- padding: 5px 10px;
- margin-bottom: 10px;
- margin-top: -5px;
- overflow: auto;
- }
- dfn {
- font-family: @font-family-monospace;
- color: @code-color;
- background-color: @code-bg;
- border-radius: @border-radius-base;
- padding: 1px 5px;
- }
- & a, #MDN-links a {
- color: #31708f;
- }
- & a::after, #MDN-links a::after {
- font-size: 70%;
- font-family: FontAwesome;
- content: " \f08e";
- }
- ol {
- font-size: 16px;
- }
-}
-
-.challenge-test-suite {
- margin-top: 10px;
- & .row {
- margin: 0!important;
- }
-}
-
-.test-output {
- font-size: 15px;
- font-family: "Ubuntu Mono";
- margin-top: 8px;
- line-height:20px;
- word-wrap: break-word;
-}
-
-.grayed-out-test-output {
- color: @gray-light;
-}
-
-.big-icon {
- font-size: 30px;
-}
-
-.error-icon {
- color: @brand-danger;
- top: 50%;
-}
-
-.success-icon {
- color: @brand-primary;
-}
-
-.refresh-icon {
- color: @icon-grey;
-}
-
-.night {
- .challenge-instructions blockquote {
- background-color: #242424;
- border-color: #515151;
- color: #ABABAB
- }
- .challenge-instructions dfn {
- background-color: #242424;
- color: #02a902;
- }
- .CodeMirror {
- background-color:#242424;
- color:#ABABAB;
- &-gutters {
- background-color:#242424;
- color:#ABABAB;
- }
- .cm-bracket, .cm-tag {
- color:#5CAFD6;
- }
- .cm-property, .cm-string {
- color:#B5753A;
- }
- .cm-keyword, .cm-attribute {
- color:#9BBBDC;
- }
- }
- .refresh-icon {
- color: @icon-light-grey;
- }
-}
-
-.challenges-editor {
- height: auto;
- width: 100%;
- overflow-y: auto;
-}
-
-.challenges-preview {
- clear: both;
- overflow: hidden;
-}
-
-@media only screen and (min-width: 1031px) {
- .iframe-scroll {
- z-index: 1;
- }
-}
-@media only screen and (max-width: 1030px) {
- .iframe-scroll {
- height: auto;
- overflow: auto;
- }
- .iphone-position {
- display: none;
- }
-}
-
-iframe.iphone {
- border: none;
- @media(min-width: 1031px) {
- width: 280px;
- height: 497px;
- position: absolute;
- top: 75px;
- right: 35px;
- overflow-y: scroll;
- }
- @media(max-width: 1030px) {
- width: 100%;
- border-radius: 5px;
- overflow-y: visible;
- height: 500px;
- }
- @media (min-width: 1200px) and (max-width: 1250px){
- right: 22px;
- }
-}
-
-// To adjust right margin, negative values bring the image closer to the edge of the screen
-.iphone-position {
- position: absolute;
- top: -45px;
- z-index: -1;
- right: -195px;
- @media (min-width: 1200px) and (max-width: 1250px){
- right: -207px;
- }
-}
-
-// YouTube embed
-.embed-responsive-item {
- max-width: 100%;
-}
diff --git a/client/less/flexgrid.less b/client/less/flexgrid.less
new file mode 100644
index 0000000000..de154c4482
--- /dev/null
+++ b/client/less/flexgrid.less
@@ -0,0 +1,117 @@
+.justify-mixin(start) { justify-content: flex-start }
+.justify-mixin(end) { justify-content: flex-end }
+.justify-mixin(center) { justify-content: center }
+.justify-mixin(around) { justify-content: space-around }
+.justify-mixin(between) { justify-content: between }
+.justify-mixin(@_) {}
+
+.items-mixin(top) { align-items: flex-start; }
+.items-mixin(bottom) { align-items: flex-end; }
+.items-mixin(center) { align-items: center; }
+.items-mixin(stretch) { align-items: stretch; }
+.items-mixin(baseline) { align-items: baseline; }
+.items-mixin(@_) {}
+
+.item-mixin(top) { align-self: flex-start; }
+.item-mixin(bottom) { align-self: flex-end; }
+.item-mixin(center) { align-self: center; }
+.item-mixin(stretch) { align-self: stretch; }
+.item-mixin(baseline) { align-self: baseline; }
+.item-mixin(@_) {}
+
+.content-mixin(top) { align-content: flex-start; }
+.content-mixin(bottom) { align-content: flex-end; }
+.content-mixin(center) { align-content: center; }
+.content-mixin(stretch) { align-content: stretch; }
+.content-mixin(baseline) { align-content: baseline; }
+.content-mixin(@_) {}
+
+.grid(@direction: row; @items: none; @justify: none; @content: none) {
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: @direction;
+
+ .justify-mixin(@justify);
+ .items-mixin(@item);
+ .content-mixin(@content);
+}
+
+.row(@items: none; @justify: none; @content: none) {
+ .grid(@direction: row, @items, @justify, @content);
+}
+
+.column(@items: none; @justify: none; @content: none) {
+ .grid(@direction: column; @items; @justify; @content);
+}
+
+.margin-mixin(@g) when not (@g = 0) {
+ margin: @g / 2;
+}
+
+.cell(@i: 1; @item; @g: @grid-gutter-width; @cols: @grid-columns) {
+ flex-basis: %('calc(100% * %s - %s)', @i / @cols, @g);
+ min-width: 0; // FF adjustment for responsive images
+ .item-mixin(@item);
+ .margin-mixin(@g);
+}
+
+.cell-offset(@i: 1; @g: @grid-gutter-width; @cols: @grid-columns) {
+ margin-left: e%('calc(100% * %s + %s)', @i / @cols, @g / 2) !important;
+}
+
+.padding-mixin(@pad) when (@pad) {
+ padding-left: @pad;
+ padding-right: @pad;
+}
+
+// center an element
+.center(@value: 50%; @padding: 0) {
+ margin-left: auto;
+ margin-right: auto;
+ max-width: @value;
+ width: 100%;
+
+ .padding-mixin(@padding);
+}
+
+.between(@min; @max; @rules) {
+ // BS logic to do string building with conditions
+ .condition-wrapper(@new) {
+ .redefine-condition() {
+ @condition: @new;
+ }
+ }
+ .init-condition() {
+ .condition-wrapper('only screen');
+ }
+ .init-condition();
+ .add-min-media(@min) when (iskeyword(@min)) {
+ .redefine-condition();
+ .condition-wrapper(%('%s and (min-width: @{screen-%s})', @condition, @min));
+ }
+
+ .add-max-media(@max) when (iskeyword(@max)) {
+ .redefine-condition();
+ .condition-wrapper(%('%s and (max-width: @{screen-%s})', @condition, @max));
+ }
+ .add-min-media(@min);
+ .add-max-media(@max);
+
+ .add-query() {
+ .redefine-condition();
+ @query: e(@condition);
+ @media @query {
+ @rules();
+ }
+ }
+
+ .add-query();
+}
+
+.below(@max, @rules) {
+ .between(@empty; @max; @rules);
+}
+
+.above(@min, @rules) {
+ .between(@min; @empty; @rules);
+}
diff --git a/client/less/lib/bootstrap/variables.less b/client/less/lib/bootstrap/variables.less
index 5539c048e6..4498534de0 100755
--- a/client/less/lib/bootstrap/variables.less
+++ b/client/less/lib/bootstrap/variables.less
@@ -1,6 +1,7 @@
//
// Variables
// --------------------------------------------------
+@empty: ~'';
//== Colors
@@ -20,8 +21,8 @@
@brand-warning: #f0ad4e;
@brand-danger: #d9534f;
-@icon-grey: #575757;
-@icon-light-grey: #888888;
+@icon-gray: #575757;
+@icon-light-gray: #888888;
//== Scaffolding
//
//## Settings for some of the most global styles.
@@ -305,6 +306,10 @@
//** Deprecated `@screen-lg-desktop` as of v3.0.1
@screen-lg-desktop: @screen-lg-min;
+// Large screen / wide desktop
+@screen-xl: 1400px;
+@screen-lg-min: @screen-lg;
+
// So media queries don't overlap when required, provide a maximum
@screen-xs-max: (@screen-sm-min - 1);
@screen-sm-max: (@screen-md-min - 1);
@@ -345,6 +350,8 @@
//** For `@screen-lg-min` and up.
@container-lg: @container-large-desktop;
+@container-xl: (1340px + @grid-gutter-width);
+
//== Navbar
//
diff --git a/client/less/main.less b/client/less/main.less
index d70dc76365..683ccae15e 100644
--- a/client/less/main.less
+++ b/client/less/main.less
@@ -1,8 +1,9 @@
-@import "lib/bootstrap/bootstrap";
-@import "lib/bootstrap-social/bootstrap-social";
-@import "lib/ionicons/ionicons";
-@import "lib/animate";
-@import "lib/bootstrap/variables";
+@import "./lib/bootstrap/bootstrap";
+@import "./lib/bootstrap-social/bootstrap-social";
+@import "./lib/ionicons/ionicons";
+@import "./lib/animate";
+@import "./lib/bootstrap/variables";
+@import "./flexgrid.less";
html,body,div,span,a,li,td,th {
font-family: 'Lato', sans-serif;
@@ -34,13 +35,6 @@ pre.wrappable {
word-wrap: break-word; /* IE 5+ */
}
-html {
- position: relative;
- min-height: 100%;
- // hack to prevent horizontal overflow problem on showHTML view
- overflow-x: hidden;
-}
-
//input[type=checkbox] {
// /* Double-sized Checkboxes */
// -ms-transform: scale(2); /* IE */
@@ -54,28 +48,14 @@ html {
border-color: @brand-primary;
}
-body.full-screen-body-background {
+.full-screen-body-background {
background-color: @body-bg;
}
-body.top-and-bottom-margins {
- padding-top: 80px;
- margin-bottom: 60px;
-}
-
-body.no-top-and-bottom-margins {
+.no-top-and-bottom-margins {
margin: 75px 20px 0px 20px;
}
-body.react-layout {
- margin-top: 75px;
- margin-bottom: 15px;
- width: auto;
- padding-left: 15px;
- padding-right: 15px;
- min-height: 650px;
-}
-
h1, h2 {
font-weight: 400;
}
@@ -165,9 +145,12 @@ h1, h2, h3, h4, h5, h6, p, li {
}
}
+// defined in bootstrap
+@navbar-total-height: @navbar-height + @navbar-margin-bottom;
.nav-height {
- height: 50px;
border: none;
+ height: @navbar-height;
+ width: 100%;
}
.landing-icon {
@@ -1241,11 +1224,14 @@ and (max-width : 400px) {
}
}
-
-@import "code-mirror.less";
-@import "challenge.less";
-@import "toastr.less";
-@import "map.less";
-@import "sk-wave.less";
-@import "classic-modal.less";
-@import "skeleton-shimmer.less";
+// surrounding downstream import with &{}
+// creates locally scoped imports
+// and prevents vaiables from overwriting each other
+&{ @import "./code-mirror.less"; }
+&{ @import "./challenge.less"; }
+&{ @import "./toastr.less"; }
+&{ @import "./map.less"; }
+&{ @import "./sk-wave.less"; }
+&{ @import "./classic-modal.less"; }
+&{ @import "./skeleton-shimmer.less"; }
+&{ @import "../../common/index.less"; }
diff --git a/client/less/map.less b/client/less/map.less
index a4b07d3b68..58bf701ae0 100644
--- a/client/less/map.less
+++ b/client/less/map.less
@@ -54,27 +54,10 @@
width: 100%;
}
-.map-fixed-header {
- position: fixed;
- background: white;
- width: 100%;
- padding-top: 5px;
- z-index: 1;
- left: 0;
- top: 0;
-}
-
-.drawer .map-fixed-header {
- padding-top: 30px;
- position: static;
- margin-bottom: -100px;
-}
-
.map-accordion {
- width: 100%;
- margin-top: 100px;
max-width: 700px;
overflow-y: auto;
+ width: 100%;
.map-accordion-panel-title {
padding-bottom: 0px;
diff --git a/client/sagas/index.js b/client/sagas/index.js
index 8fe5789425..004e8f75f0 100644
--- a/client/sagas/index.js
+++ b/client/sagas/index.js
@@ -7,7 +7,6 @@ import hardGoToSaga from './hard-go-to-saga.js';
import mouseTrapSaga from './mouse-trap-saga.js';
import nightModeSaga from './night-mode-saga.js';
import titleSaga from './title-saga.js';
-import windowSaga from './window-saga.js';
export default [
analyticsSaga,
@@ -18,6 +17,5 @@ export default [
hardGoToSaga,
mouseTrapSaga,
nightModeSaga,
- titleSaga,
- windowSaga
+ titleSaga
];
diff --git a/client/sagas/window-saga.js b/client/sagas/window-saga.js
deleted file mode 100644
index 0e3302186d..0000000000
--- a/client/sagas/window-saga.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { Observable } from 'rx';
-import types from '../../common/app/redux/types';
-import { updateWindowHeight } from '../../common/app/redux/actions';
-
-const { initWindowHeight } = types;
-function getWindowSize(document, window) {
- const body = document.getElementsByTagName('body')[0];
- return window.innerHeight ||
- document.docElement.clientHeight ||
- body.clientHeight ||
- 0;
-}
-
-function listenForResize(document, window) {
- return Observable.fromEvent(window, 'resize')
- .debounce(250)
- .startWith({})
- .map(() => getWindowSize(document, window));
-}
-
-export default function windowSaga(
- action$,
- getState,
- { isDev, document, window }
-) {
- return action$
- .filter(({ type }) => type === initWindowHeight)
- .flatMap(() => {
- if (isDev) {
- return listenForResize(document, window);
- }
- return Observable.just(getWindowSize(document, window));
- })
- .map(updateWindowHeight);
-}
diff --git a/common/app/App.jsx b/common/app/App.jsx
index 72e4f7790c..bb488ad925 100644
--- a/common/app/App.jsx
+++ b/common/app/App.jsx
@@ -1,12 +1,11 @@
import React, { PropTypes } from 'react';
-import { Button, Row } from 'react-bootstrap';
+import { Button } from 'react-bootstrap';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
+import ns from './ns.json';
import {
fetchUser,
- initWindowHeight,
- updateNavHeight,
updateAppLang,
trackEvent,
loadCurrentChallenge,
@@ -21,15 +20,13 @@ import Toasts from './toasts/Toasts.jsx';
import { userSelector } from './redux/selectors';
const mapDispatchToProps = {
- initWindowHeight,
- updateNavHeight,
+ closeDropdown,
fetchUser,
- submitChallenge,
- updateAppLang,
- trackEvent,
loadCurrentChallenge,
openDropdown,
- closeDropdown
+ submitChallenge,
+ trackEvent,
+ updateAppLang
};
const mapStateToProps = createSelector(
@@ -58,7 +55,6 @@ const propTypes = {
children: PropTypes.node,
closeDropdown: PropTypes.func.isRequired,
fetchUser: PropTypes.func,
- initWindowHeight: PropTypes.func,
isNavDropdownOpen: PropTypes.bool,
isSignedIn: PropTypes.bool,
loadCurrentChallenge: PropTypes.func.isRequired,
@@ -71,7 +67,6 @@ const propTypes = {
toast: PropTypes.object,
trackEvent: PropTypes.func.isRequired,
updateAppLang: PropTypes.func.isRequired,
- updateNavHeight: PropTypes.func,
username: PropTypes.string
};
@@ -84,7 +79,6 @@ export class FreeCodeCamp extends React.Component {
}
componentDidMount() {
- this.props.initWindowHeight();
if (!this.props.isSignedIn) {
this.props.fetchUser();
}
@@ -110,7 +104,6 @@ export class FreeCodeCamp extends React.Component {
username,
points,
picture,
- updateNavHeight,
trackEvent,
loadCurrentChallenge,
openDropdown,
@@ -118,23 +111,22 @@ export class FreeCodeCamp extends React.Component {
isNavDropdownOpen
} = this.props;
const navProps = {
- username,
- points,
- picture,
- updateNavHeight,
- trackEvent,
+ closeDropdown,
+ isNavDropdownOpen,
loadCurrentChallenge,
openDropdown,
- closeDropdown,
- isNavDropdownOpen
+ picture,
+ points,
+ trackEvent,
+ username
};
return (
-
+
-
+
{ this.props.children }
-
+
);
diff --git a/common/app/app.less b/common/app/app.less
new file mode 100644
index 0000000000..3705a8db5f
--- /dev/null
+++ b/common/app/app.less
@@ -0,0 +1,14 @@
+// should match ./ns.json value and filename
+@ns: app;
+
+.@{ns}-container {
+ .column();
+ width: 100vw;
+}
+
+.@{ns}-content {
+ .center(@value: @container-xl, @padding: @grid-gutter-width);
+ // makes the inital content height 0px
+ // then lets it grow to fit the rest of the space
+ flex: 1 0 0px;
+}
diff --git a/common/app/components/Nav/Nav.jsx b/common/app/components/Nav/Nav.jsx
index 4c6fa7c9af..033c4af227 100644
--- a/common/app/components/Nav/Nav.jsx
+++ b/common/app/components/Nav/Nav.jsx
@@ -1,5 +1,4 @@
import React, { PropTypes } from 'react';
-import ReactDOM from 'react-dom';
import { LinkContainer } from 'react-router-bootstrap';
import {
Col,
@@ -41,7 +40,6 @@ const propTypes = {
showLoading: PropTypes.bool,
signedIn: PropTypes.bool,
trackEvent: PropTypes.func.isRequired,
- updateNavHeight: PropTypes.func,
username: PropTypes.string
};
@@ -55,11 +53,6 @@ export default class FCCNav extends React.Component {
});
}
- componentDidMount() {
- const navBar = ReactDOM.findDOMNode(this);
- this.props.updateNavHeight(navBar.clientHeight);
- }
-
handleMapClickOnMap(e) {
e.preventDefault();
this.props.trackEvent({
@@ -172,7 +165,7 @@ export default class FCCNav extends React.Component {
return (
diff --git a/common/app/index.less b/common/app/index.less
new file mode 100644
index 0000000000..69bf5eb725
--- /dev/null
+++ b/common/app/index.less
@@ -0,0 +1,2 @@
+&{ @import "./app.less"; }
+&{ @import "./routes/index.less"; }
diff --git a/common/app/ns.json b/common/app/ns.json
new file mode 100644
index 0000000000..74999efc4e
--- /dev/null
+++ b/common/app/ns.json
@@ -0,0 +1 @@
+"app"
diff --git a/common/app/redux/actions.js b/common/app/redux/actions.js
index a4602bc899..6768f58627 100644
--- a/common/app/redux/actions.js
+++ b/common/app/redux/actions.js
@@ -113,11 +113,6 @@ export const delayedRedirect = createAction(types.delayedRedirect);
// hardGoTo(path: String) => Action
export const hardGoTo = createAction(types.hardGoTo);
-export const initWindowHeight = createAction(types.initWindowHeight);
-export const updateWindowHeight = createAction(types.updateWindowHeight);
-export const updateNavHeight = createAction(types.updateNavHeight);
-
-
// data
export const updateChallengesData = createAction(types.updateChallengesData);
export const updateHikesData = createAction(types.updateHikesData);
diff --git a/common/app/redux/reducer.js b/common/app/redux/reducer.js
index b0e14cb50a..754345ac76 100644
--- a/common/app/redux/reducer.js
+++ b/common/app/redux/reducer.js
@@ -7,8 +7,6 @@ const initialState = {
user: '',
lang: '',
csrfToken: '',
- windowHeight: 0,
- navHeight: 0,
theme: 'default'
};
@@ -41,14 +39,6 @@ export default handleActions(
...state,
points
}),
- [types.updateWindowHeight]: (state, { payload: windowHeight }) => ({
- ...state,
- windowHeight
- }),
- [types.updateNavHeight]: (state, { payload: navHeight }) => ({
- ...state,
- navHeight
- }),
[types.delayedRedirect]: (state, { payload }) => ({
...state,
delayedRedirect: payload
diff --git a/common/app/redux/types.js b/common/app/redux/types.js
index 1eb0a70635..766b695c85 100644
--- a/common/app/redux/types.js
+++ b/common/app/redux/types.js
@@ -22,10 +22,6 @@ export default createTypes([
'hardGoTo',
'delayedRedirect',
- 'initWindowHeight',
- 'updateWindowHeight',
- 'updateNavHeight',
-
// data handling
'updateChallengesData',
'updateHikesData',
diff --git a/common/app/routes/challenges/components/Bug-Modal.jsx b/common/app/routes/challenges/Bug-Modal.jsx
similarity index 87%
rename from common/app/routes/challenges/components/Bug-Modal.jsx
rename to common/app/routes/challenges/Bug-Modal.jsx
index f60f2b5019..b22f7d0809 100644
--- a/common/app/routes/challenges/components/Bug-Modal.jsx
+++ b/common/app/routes/challenges/Bug-Modal.jsx
@@ -2,10 +2,13 @@ import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { Button, Modal } from 'react-bootstrap';
import PureComponent from 'react-pure-render/component';
-import { createIssue, openIssueSearch, closeBugModal } from '../redux/actions';
+
+import ns from './ns.json';
+
+import { createIssue, openIssueSearch, closeBugModal } from './redux/actions';
const mapStateToProps = state => ({ isOpen: state.challengesApp.isBugOpen });
-const actions = { createIssue, openIssueSearch, closeBugModal };
+const mapDispatchToProps = { createIssue, openIssueSearch, closeBugModal };
const bugLink = 'http://forum.freecodecamp.com/t/how-to-report-a-bug/19543';
const propTypes = {
@@ -16,7 +19,6 @@ const propTypes = {
};
export class BugModal extends PureComponent {
-
render() {
const {
isOpen,
@@ -28,7 +30,7 @@ export class BugModal extends PureComponent {
-
+
Did you find a bug?
+ );
+ }
+ return (
+
+ { children || 'Happy Coding!' }
+ { icon }
+
+
+ );
+}
+
+ChallengeTitle.displayName = 'ChallengeTitle';
+ChallengeTitle.propTypes = propTypes;
diff --git a/common/app/routes/challenges/components/CodeMirrorSkeleton.jsx b/common/app/routes/challenges/Code-Mirror-Skeleton.jsx
similarity index 99%
rename from common/app/routes/challenges/components/CodeMirrorSkeleton.jsx
rename to common/app/routes/challenges/Code-Mirror-Skeleton.jsx
index a79d220b36..a9fc788d79 100644
--- a/common/app/routes/challenges/components/CodeMirrorSkeleton.jsx
+++ b/common/app/routes/challenges/Code-Mirror-Skeleton.jsx
@@ -5,6 +5,7 @@ import { Grid, Col, Row } from 'react-bootstrap';
const propTypes = {
content: PropTypes.string
};
+
export default class CodeMirrorSkeleton extends PureComponent {
renderLine(line, i) {
diff --git a/common/app/routes/challenges/components/Output.jsx b/common/app/routes/challenges/Output.jsx
similarity index 81%
rename from common/app/routes/challenges/components/Output.jsx
rename to common/app/routes/challenges/Output.jsx
index 476c4324d4..eb27d3c860 100644
--- a/common/app/routes/challenges/components/Output.jsx
+++ b/common/app/routes/challenges/Output.jsx
@@ -2,14 +2,15 @@ import React, { PureComponent, PropTypes } from 'react';
import NoSSR from 'react-no-ssr';
import Codemirror from 'react-codemirror';
-import CodeMirrorSkeleton from './CodeMirrorSkeleton.jsx';
+import ns from './ns.json';
+import CodeMirrorSkeleton from './Code-Mirror-Skeleton.jsx';
const defaultOptions = {
lineNumbers: false,
+ lineWrapping: true,
mode: 'javascript',
- theme: 'monokai',
readOnly: 'nocursor',
- lineWrapping: true
+ theme: 'monokai'
};
const propTypes = {
@@ -21,7 +22,7 @@ export default class Output extends PureComponent {
render() {
const { output, defaultOutput } = this.props;
return (
-
+
}>
;
}
-
- render() {
- const { viewType } = this.props;
- return (
-
- { this.renderView(viewType) }
-
- );
- }
}
-Challenges.displayName = 'Challenges';
-Challenges.propTypes = propTypes;
+Show.displayName = 'Show(ChallengeView)';
+Show.propTypes = propTypes;
export default compose(
- connect(mapStateToProps, bindableActions),
+ connect(mapStateToProps, mapDispatchToProps),
contain(fetchOptions)
-)(Challenges);
+)(Show);
diff --git a/common/app/routes/challenges/components/Solution-Input.jsx b/common/app/routes/challenges/Solution-Input.jsx
similarity index 91%
rename from common/app/routes/challenges/components/Solution-Input.jsx
rename to common/app/routes/challenges/Solution-Input.jsx
index afbf2cdc23..bb242c254f 100644
--- a/common/app/routes/challenges/components/Solution-Input.jsx
+++ b/common/app/routes/challenges/Solution-Input.jsx
@@ -1,6 +1,6 @@
import React, { PropTypes } from 'react';
import { HelpBlock, FormGroup, FormControl } from 'react-bootstrap';
-import { getValidationState, DOMOnlyProps } from '../../../utils/form';
+import { getValidationState, DOMOnlyProps } from '../../utils/form';
const propTypes = {
placeholder: PropTypes.string,
diff --git a/common/app/routes/challenges/components/Test-Suite.jsx b/common/app/routes/challenges/Test-Suite.jsx
similarity index 95%
rename from common/app/routes/challenges/components/Test-Suite.jsx
rename to common/app/routes/challenges/Test-Suite.jsx
index 4d8d7e3269..d11837d911 100644
--- a/common/app/routes/challenges/components/Test-Suite.jsx
+++ b/common/app/routes/challenges/Test-Suite.jsx
@@ -2,6 +2,8 @@ import React, { PropTypes, PureComponent } from 'react';
import classnames from 'classnames';
import { Col, Row } from 'react-bootstrap';
+import ns from './ns.json';
+
const propTypes = {
tests: PropTypes.arrayOf(PropTypes.object)
};
@@ -41,7 +43,7 @@ export default class TestSuite extends PureComponent {
const { tests } = this.props;
return (
{ this.renderTests(tests) }
diff --git a/common/app/routes/challenges/challenges.less b/common/app/routes/challenges/challenges.less
new file mode 100644
index 0000000000..efda7469a8
--- /dev/null
+++ b/common/app/routes/challenges/challenges.less
@@ -0,0 +1,81 @@
+// should be the same as the filename and ./ns.json
+@ns: challenges;
+
+
+.@{ns}-title {
+ margin-top: 0;
+ i {
+ margin-left: 5px;
+ line-height: 20px;
+ }
+}
+
+.@{ns}-grayed-out-test-output {
+ color: @gray-light;
+}
+
+.@{ns}-test-suite {
+ margin-top: 10px;
+ & .row {
+ margin: 0!important;
+ }
+
+ .big-icon {
+ font-size: 30px;
+ }
+
+ .error-icon {
+ color: @brand-danger;
+ top: 50%;
+ }
+
+ .success-icon {
+ color: @brand-primary;
+ }
+
+ .refresh-icon {
+ color: @icon-gray;
+ }
+}
+
+.night {
+ .@{ns}-instructions blockquote {
+ background-color: #242424;
+ border-color: #515151;
+ color: #ABABAB
+ }
+ .@{ns}-instructions dfn {
+ background-color: #242424;
+ color: #02a902;
+ }
+ .@{ns}-editor .CodeMirror {
+ background-color:#242424;
+ color:#ABABAB;
+ &-gutters {
+ background-color:#242424;
+ color:#ABABAB;
+ }
+ .cm-bracket, .cm-tag {
+ color:#5CAFD6;
+ }
+ .cm-property, .cm-string {
+ color:#B5753A;
+ }
+ .cm-keyword, .cm-attribute {
+ color:#9BBBDC;
+ }
+ }
+ .refresh-icon {
+ color: @icon-light-gray;
+ }
+}
+
+.@{ns}-test-output {
+ font-size: 15px;
+ font-family: "Ubuntu Mono";
+ margin-top: 8px;
+ line-height:20px;
+ word-wrap: break-word;
+}
+
+&{ @import "./views/index.less"; }
diff --git a/common/app/routes/challenges/components/classic/Preview.jsx b/common/app/routes/challenges/components/classic/Preview.jsx
deleted file mode 100644
index ce0fdb1e13..0000000000
--- a/common/app/routes/challenges/components/classic/Preview.jsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from 'react';
-import PureComponent from 'react-pure-render/component';
-
-const mainId = 'fcc-main-frame';
-
-export default class Preview extends PureComponent {
- render() {
- return (
-
-
-

-
-
-
-
- );
- }
-}
-
-Preview.displayName = 'Preview';
diff --git a/common/app/routes/challenges/index.js b/common/app/routes/challenges/index.js
index 38ddc2ddf3..c422581b24 100644
--- a/common/app/routes/challenges/index.js
+++ b/common/app/routes/challenges/index.js
@@ -1,5 +1,5 @@
-import Show from './components/Show.jsx';
-import ShowMap from './components/map/Map.jsx';
+import Show from './Show.jsx';
+import _Map from './views/map';
export function challengesRoute() {
return {
@@ -24,6 +24,6 @@ export function modernChallengesRoute() {
export function mapRoute() {
return {
path: 'map',
- component: ShowMap
+ component: _Map
};
}
diff --git a/common/app/routes/challenges/ns.json b/common/app/routes/challenges/ns.json
new file mode 100644
index 0000000000..fbbd505dfe
--- /dev/null
+++ b/common/app/routes/challenges/ns.json
@@ -0,0 +1 @@
+"challenges"
diff --git a/common/app/routes/challenges/components/backend/Back-End.jsx b/common/app/routes/challenges/views/backend/Back-End.jsx
similarity index 92%
rename from common/app/routes/challenges/components/backend/Back-End.jsx
rename to common/app/routes/challenges/views/backend/Back-End.jsx
index f3e433a58f..f15ed99ba4 100644
--- a/common/app/routes/challenges/components/backend/Back-End.jsx
+++ b/common/app/routes/challenges/views/backend/Back-End.jsx
@@ -7,16 +7,17 @@ import {
Row
} from 'react-bootstrap';
-import SolutionInput from '../Solution-Input.jsx';
-import TestSuite from '../Test-Suite.jsx';
-import Output from '../Output.jsx';
+import ChallengeTitle from '../../Challenge-Title.jsx';
+import SolutionInput from '../../Solution-Input.jsx';
+import TestSuite from '../../Test-Suite.jsx';
+import Output from '../../Output.jsx';
import { submitChallenge, executeChallenge } from '../../redux/actions.js';
import { challengeSelector } from '../../redux/selectors.js';
import { descriptionRegex } from '../../utils.js';
import {
+ createFormValidator,
isValidURL,
- makeRequired,
- createFormValidator
+ makeRequired
} from '../../../../utils/form.js';
// provided by redux form
@@ -114,14 +115,14 @@ export class BackEnd extends PureComponent {
'Submit and go to my next challenge' :
"I've completed this challenge";
return (
-
+
- { title }
+ { title }
{ this.renderDescription(description) }
@@ -158,7 +159,7 @@ export class BackEnd extends PureComponent {
-
+
);
}
}
diff --git a/common/app/routes/challenges/views/backend/index.js b/common/app/routes/challenges/views/backend/index.js
new file mode 100644
index 0000000000..9da7aec1c3
--- /dev/null
+++ b/common/app/routes/challenges/views/backend/index.js
@@ -0,0 +1 @@
+export { default } from './Back-End.jsx';
diff --git a/common/app/routes/challenges/components/Classic-Modal.jsx b/common/app/routes/challenges/views/classic/Classic-Modal.jsx
similarity index 90%
rename from common/app/routes/challenges/components/Classic-Modal.jsx
rename to common/app/routes/challenges/views/classic/Classic-Modal.jsx
index 85ff2c39b8..945d4161c5 100644
--- a/common/app/routes/challenges/components/Classic-Modal.jsx
+++ b/common/app/routes/challenges/views/classic/Classic-Modal.jsx
@@ -3,6 +3,8 @@ import { Button, Modal } from 'react-bootstrap';
import PureComponent from 'react-pure-render/component';
import FontAwesome from 'react-fontawesome';
+import ns from './ns.json';
+
const propTypes = {
close: PropTypes.func,
open: PropTypes.bool.isRequired,
@@ -22,10 +24,10 @@ export default class ClassicModal extends PureComponent {
e.keyCode === 13 &&
(e.ctrlKey || e.meta) &&
open
- ) {
- e.preventDefault();
- submitChallenge();
- }
+ ) {
+ e.preventDefault();
+ submitChallenge();
+ }
}
render() {
@@ -38,14 +40,14 @@ export default class ClassicModal extends PureComponent {
return (
{ successMessage }
diff --git a/common/app/routes/challenges/components/classic/Classic.jsx b/common/app/routes/challenges/views/classic/Classic.jsx
similarity index 95%
rename from common/app/routes/challenges/components/classic/Classic.jsx
rename to common/app/routes/challenges/views/classic/Classic.jsx
index aaaae44a22..cab8cfb1f9 100644
--- a/common/app/routes/challenges/components/classic/Classic.jsx
+++ b/common/app/routes/challenges/views/classic/Classic.jsx
@@ -1,14 +1,14 @@
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
-import { Col } from 'react-bootstrap';
+import { Row, Col } from 'react-bootstrap';
import { createSelector } from 'reselect';
import PureComponent from 'react-pure-render/component';
import Editor from './Editor.jsx';
import SidePanel from './Side-Panel.jsx';
import Preview from './Preview.jsx';
-import BugModal from '../Bug-Modal.jsx';
-import ClassicModal from '../Classic-Modal.jsx';
+import BugModal from '../../Bug-Modal.jsx';
+import ClassicModal from './Classic-Modal.jsx';
import { challengeSelector } from '../../redux/selectors';
import {
executeChallenge,
@@ -116,7 +116,7 @@ export class Challenge extends PureComponent {
} = this.props;
return (
-
+
-
+
);
}
}
diff --git a/common/app/routes/challenges/components/classic/Editor.jsx b/common/app/routes/challenges/views/classic/Editor.jsx
similarity index 83%
rename from common/app/routes/challenges/components/classic/Editor.jsx
rename to common/app/routes/challenges/views/classic/Editor.jsx
index f35eb37cde..3424d5a547 100644
--- a/common/app/routes/challenges/components/classic/Editor.jsx
+++ b/common/app/routes/challenges/views/classic/Editor.jsx
@@ -1,21 +1,14 @@
import { Subject } from 'rx';
import React, { PropTypes } from 'react';
import { createSelector } from 'reselect';
-import { connect } from 'react-redux';
import Codemirror from 'react-codemirror';
import NoSSR from 'react-no-ssr';
import PureComponent from 'react-pure-render/component';
-
import MouseTrap from 'mousetrap';
-import CodeMirrorSkeleton from '../CodeMirrorSkeleton.jsx';
-
-const mapStateToProps = createSelector(
- state => state.app.windowHeight,
- state => state.app.navHeight,
- (windowHeight, navHeight) => ({ height: windowHeight - navHeight - 50 })
-);
+import ns from './ns.json';
+import CodeMirrorSkeleton from '../../Code-Mirror-Skeleton.jsx';
const editorDebounceTimeout = 750;
@@ -40,12 +33,11 @@ const defaultProps = {
const propTypes = {
content: PropTypes.string,
executeChallenge: PropTypes.func,
- height: PropTypes.number,
mode: PropTypes.string,
updateFile: PropTypes.func
};
-export class Editor extends PureComponent {
+export default class Editor extends PureComponent {
constructor(...args) {
super(...args);
this._editorContent$ = new Subject();
@@ -125,16 +117,13 @@ export class Editor extends PureComponent {
}
render() {
- const { executeChallenge, content, height, mode } = this.props;
- const style = {};
- if (height) {
- style.height = height + 'px';
- }
+ const {
+ content,
+ executeChallenge,
+ mode
+ } = this.props;
return (
-
+
}>
+
+
+ );
+ }
+}
+
+Preview.displayName = 'Preview';
diff --git a/common/app/routes/challenges/components/classic/Side-Panel.jsx b/common/app/routes/challenges/views/classic/Side-Panel.jsx
similarity index 85%
rename from common/app/routes/challenges/components/classic/Side-Panel.jsx
rename to common/app/routes/challenges/views/classic/Side-Panel.jsx
index 4f78b9a0aa..3d928a85b2 100644
--- a/common/app/routes/challenges/components/classic/Side-Panel.jsx
+++ b/common/app/routes/challenges/views/classic/Side-Panel.jsx
@@ -5,8 +5,11 @@ import { connect } from 'react-redux';
import PureComponent from 'react-pure-render/component';
import { Col, Row } from 'react-bootstrap';
-import TestSuite from '../Test-Suite.jsx';
-import Output from '../Output.jsx';
+import ns from './ns.json';
+
+import ChallengeTitle from '../../Challenge-Title.jsx';
+import TestSuite from '../../Test-Suite.jsx';
+import Output from '../../Output.jsx';
import ToolPanel from './Tool-Panel.jsx';
import { challengeSelector } from '../../redux/selectors';
import {
@@ -27,8 +30,6 @@ const mapDispatchToProps = {
};
const mapStateToProps = createSelector(
challengeSelector,
- state => state.app.windowHeight,
- state => state.app.navHeight,
state => state.challengesApp.tests,
state => state.challengesApp.output,
state => state.challengesApp.hintIndex,
@@ -42,8 +43,6 @@ const mapStateToProps = createSelector(
} = {},
title
},
- windowHeight,
- navHeight,
tests,
output,
hintIndex,
@@ -52,7 +51,6 @@ const mapStateToProps = createSelector(
) => ({
title,
description,
- height: windowHeight - navHeight - 20,
tests,
output,
hint: hints[hintIndex],
@@ -63,7 +61,6 @@ const mapStateToProps = createSelector(
const propTypes = {
description: PropTypes.arrayOf(PropTypes.string),
executeChallenge: PropTypes.func,
- height: PropTypes.number,
helpChatRoom: PropTypes.string,
hint: PropTypes.string,
isCodeLocked: PropTypes.bool,
@@ -108,7 +105,6 @@ export class SidePanel extends PureComponent {
const {
title,
description,
- height,
tests = [],
output,
hint,
@@ -120,24 +116,18 @@ export class SidePanel extends PureComponent {
isCodeLocked,
unlockUntrustedCode
} = this.props;
- const style = {};
- if (height) {
- style.height = height + 'px';
- }
return (
-
- { title || 'Happy Coding!' }
-
-
+
+ { title }
+
{ this.renderDescription(description) }
diff --git a/common/app/routes/challenges/components/classic/Tool-Panel.jsx b/common/app/routes/challenges/views/classic/Tool-Panel.jsx
similarity index 100%
rename from common/app/routes/challenges/components/classic/Tool-Panel.jsx
rename to common/app/routes/challenges/views/classic/Tool-Panel.jsx
diff --git a/common/app/routes/challenges/views/classic/classic.less b/common/app/routes/challenges/views/classic/classic.less
new file mode 100644
index 0000000000..e8dde73d7a
--- /dev/null
+++ b/common/app/routes/challenges/views/classic/classic.less
@@ -0,0 +1,109 @@
+// should match filename and ./ns.json
+@ns: classic;
+
+// make the height no larger than (window - navbar)
+.max-element-height(up-to) {
+ max-height: e(%('calc(100vh - %s)', @navbar-total-height));
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+
+.max-element-height(always) {
+ height: e(%('calc(100vh - %s)', @navbar-total-height));
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+
+.@{ns}-instructions-panel {
+ .max-element-height(always);
+ padding-bottom: 10px;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+.@{ns}-instructions {
+ margin-bottom: 5px;
+ h4 {
+ margin-bottom: 0;
+ }
+ blockquote {
+ font-size: 90%;
+ font-family: @font-family-monospace;
+ color: @code-color;
+ background-color: #fffbe5;
+ border-radius: @border-radius-base;
+ border: 1px solid @pre-border-color;
+ white-space: pre;
+ padding: 5px 10px;
+ margin-bottom: 10px;
+ margin-top: -5px;
+ overflow: auto;
+ }
+ dfn {
+ font-family: @font-family-monospace;
+ color: @code-color;
+ background-color: @code-bg;
+ border-radius: @border-radius-base;
+ padding: 1px 5px;
+ }
+ & a, #MDN-links a {
+ color: #31708f;
+ }
+ & a::after, #MDN-links a::after {
+ font-size: 70%;
+ font-family: FontAwesome;
+ content: " \f08e";
+ }
+ ol {
+ font-size: 16px;
+ }
+}
+
+
+.night {
+ .@{ns}-instructions blockquote {
+ background-color: #242424;
+ border-color: #515151;
+ color: #ABABAB
+ }
+ .@{ns}-instructions dfn {
+ background-color: #242424;
+ color: #02a902;
+ }
+ .@{ns}-editor .CodeMirror {
+ background-color:#242424;
+ color:#ABABAB;
+ &-gutters {
+ background-color:#242424;
+ color:#ABABAB;
+ }
+ .cm-bracket, .cm-tag {
+ color:#5CAFD6;
+ }
+ .cm-property, .cm-string {
+ color:#B5753A;
+ }
+ .cm-keyword, .cm-attribute {
+ color:#9BBBDC;
+ }
+ }
+}
+
+.@{ns}-editor {
+ .max-element-height(always);
+ width: 100%;
+}
+
+.@{ns}-preview {
+ .max-element-height(always);
+ width: 100%;
+}
+
+.@{ns}-preview-frame {
+ border: 1px solid gray;
+ border-radius: 5px;
+ color: @gray-lighter;
+ height: 99%;
+ overflow: hidden;
+ width: 100%;
+}
diff --git a/common/app/routes/challenges/views/classic/index.js b/common/app/routes/challenges/views/classic/index.js
new file mode 100644
index 0000000000..479ed82803
--- /dev/null
+++ b/common/app/routes/challenges/views/classic/index.js
@@ -0,0 +1 @@
+export default from './Classic.jsx';
diff --git a/common/app/routes/challenges/views/classic/ns.json b/common/app/routes/challenges/views/classic/ns.json
new file mode 100644
index 0000000000..1229003f77
--- /dev/null
+++ b/common/app/routes/challenges/views/classic/ns.json
@@ -0,0 +1 @@
+"classic"
diff --git a/common/app/routes/challenges/views/index.less b/common/app/routes/challenges/views/index.less
new file mode 100644
index 0000000000..839c5aa630
--- /dev/null
+++ b/common/app/routes/challenges/views/index.less
@@ -0,0 +1,2 @@
+&{ @import "./classic/classic.less"; }
+&{ @import "./step/step.less"; }
diff --git a/common/app/routes/challenges/components/map/Block.jsx b/common/app/routes/challenges/views/map/Block.jsx
similarity index 100%
rename from common/app/routes/challenges/components/map/Block.jsx
rename to common/app/routes/challenges/views/map/Block.jsx
diff --git a/common/app/routes/challenges/components/map/Challenge.jsx b/common/app/routes/challenges/views/map/Challenge.jsx
similarity index 100%
rename from common/app/routes/challenges/components/map/Challenge.jsx
rename to common/app/routes/challenges/views/map/Challenge.jsx
diff --git a/common/app/routes/challenges/components/map/Header.jsx b/common/app/routes/challenges/views/map/Header.jsx
similarity index 96%
rename from common/app/routes/challenges/components/map/Header.jsx
rename to common/app/routes/challenges/views/map/Header.jsx
index 1a2af91e78..712189bdda 100644
--- a/common/app/routes/challenges/components/map/Header.jsx
+++ b/common/app/routes/challenges/views/map/Header.jsx
@@ -79,10 +79,7 @@ export class Header extends PureComponent {
'Hide all challenges';
return (
-
+
Challenges required for certifications are marked with a *
);
}
}
diff --git a/common/app/routes/challenges/components/project/Side-Panel.jsx b/common/app/routes/challenges/views/project/Side-Panel.jsx
similarity index 64%
rename from common/app/routes/challenges/components/project/Side-Panel.jsx
rename to common/app/routes/challenges/views/project/Side-Panel.jsx
index 014231b60c..2b797f2ec5 100644
--- a/common/app/routes/challenges/components/project/Side-Panel.jsx
+++ b/common/app/routes/challenges/views/project/Side-Panel.jsx
@@ -1,6 +1,5 @@
-import React, { PropTypes } from 'react';
-
-import PureComponent from 'react-pure-render/component';
+import React, { PropTypes, PureComponent } from 'react';
+import ChallengeTitle from '../../Challenge-Title.jsx';
const propTypes = {
description: PropTypes.arrayOf(PropTypes.string),
@@ -10,18 +9,6 @@ const propTypes = {
};
export default class SidePanel extends PureComponent {
- renderIcon(isCompleted) {
- if (!isCompleted) {
- return null;
- }
- return (
-
- );
- }
-
renderDescription(title = '', description = []) {
return description.map((line, index) => (
-
+
{ title }
- { this.renderIcon(isCompleted) }
-
-
+
{ this.renderDescription(title, description) }
diff --git a/common/app/routes/challenges/components/project/Tool-Panel.jsx b/common/app/routes/challenges/views/project/Tool-Panel.jsx
similarity index 100%
rename from common/app/routes/challenges/components/project/Tool-Panel.jsx
rename to common/app/routes/challenges/views/project/Tool-Panel.jsx
diff --git a/common/app/routes/challenges/views/project/index.js b/common/app/routes/challenges/views/project/index.js
new file mode 100644
index 0000000000..6784471995
--- /dev/null
+++ b/common/app/routes/challenges/views/project/index.js
@@ -0,0 +1 @@
+export default from './Project.jsx';
diff --git a/common/app/routes/challenges/components/step/Step.jsx b/common/app/routes/challenges/views/step/Step.jsx
similarity index 89%
rename from common/app/routes/challenges/components/step/Step.jsx
rename to common/app/routes/challenges/views/step/Step.jsx
index 3a56559692..a507bf800e 100644
--- a/common/app/routes/challenges/components/step/Step.jsx
+++ b/common/app/routes/challenges/views/step/Step.jsx
@@ -5,6 +5,7 @@ import { createSelector } from 'reselect';
import PureComponent from 'react-pure-render/component';
import LightBox from 'react-images';
+import ns from './ns.json';
import {
closeLightBoxImage,
completeAction,
@@ -180,10 +181,7 @@ export class StepChallenge extends PureComponent {
}
const [imgUrl, imgAlt, info, action] = step;
return (
-
+
-
+
{ this.renderActionButton(action, completeAction) }
{ this.renderBackButton(currentIndex, stepBackward) }
@@ -260,23 +258,25 @@ export class StepChallenge extends PureComponent {
closeLightBoxImage
} = this.props;
return (
-
- { this.renderStep(this.props) }
-
- { this.renderImages(steps) }
-
-
-
-
+
+
+ { this.renderStep(this.props) }
+
+ { this.renderImages(steps) }
+
+
+
+
+
);
}
}
diff --git a/common/app/routes/challenges/views/step/index.js b/common/app/routes/challenges/views/step/index.js
new file mode 100644
index 0000000000..5fc4e2aca9
--- /dev/null
+++ b/common/app/routes/challenges/views/step/index.js
@@ -0,0 +1 @@
+export default from './Step.jsx';
diff --git a/common/app/routes/challenges/views/step/ns.json b/common/app/routes/challenges/views/step/ns.json
new file mode 100644
index 0000000000..8cade00f13
--- /dev/null
+++ b/common/app/routes/challenges/views/step/ns.json
@@ -0,0 +1 @@
+"step"
diff --git a/common/app/routes/challenges/views/step/step.less b/common/app/routes/challenges/views/step/step.less
new file mode 100644
index 0000000000..afcb872b08
--- /dev/null
+++ b/common/app/routes/challenges/views/step/step.less
@@ -0,0 +1,54 @@
+// should match ./ns.json value and filename
+@ns: step;
+
+.@{ns}-description {
+ font-size: 1.5em;
+}
+.@{ns}-counter {
+ font-size: 20px;
+ line-height: 44px;
+}
+
+.@{ns}-forward-leave {
+ transition: opacity .4s ease-in, transform .3s ease-in-out;
+ opacity: 1;
+ transform: translate(0, 0);
+}
+
+.@{ns}-forward-leave-active {
+ opacity: 0;
+ transform: translate(-100%, 0);
+}
+
+.@{ns}-forward-enter {
+ transition: opacity .4s ease-in, transform .3s ease-in-out;
+ opacity: 0;
+ transform: translate(100%, 0);
+}
+
+.@{ns}-forward-enter-active {
+ opacity: 1;
+ transform: translate(0, 0);
+}
+
+.@{ns}-backward-leave {
+ transition: opacity .4s ease-in, transform .3s ease-in-out;
+ opacity: 1;
+ transform: translate(0, 0);
+}
+
+.@{ns}-backward-leave-active {
+ opacity: 0;
+ transform: translate(100%, 0);
+}
+
+.@{ns}-backward-enter {
+ transition: opacity .4s ease-in, transform .3s ease-in-out;
+ opacity: 0;
+ transform: translate(-100%, 0);
+}
+
+.@{ns}-backward-enter-active {
+ opacity: 1;
+ transform: translate(0, 0);
+}
diff --git a/common/app/routes/challenges/components/video/Lecture.jsx b/common/app/routes/challenges/views/video/Lecture.jsx
similarity index 88%
rename from common/app/routes/challenges/components/video/Lecture.jsx
rename to common/app/routes/challenges/views/video/Lecture.jsx
index fbe9284326..388f4d9b5c 100644
--- a/common/app/routes/challenges/components/video/Lecture.jsx
+++ b/common/app/routes/challenges/views/video/Lecture.jsx
@@ -70,15 +70,12 @@ export class Lecture extends React.Component {
return (
-
-
-
+
-
-
-
-
- { this.renderBody(shouldShowQuestions) }
-
-
+
+
+
+
+
+
+ { this.renderBody(shouldShowQuestions) }
+
+
+
);
}
}
diff --git a/common/app/routes/challenges/views/video/index.js b/common/app/routes/challenges/views/video/index.js
new file mode 100644
index 0000000000..a46de403e7
--- /dev/null
+++ b/common/app/routes/challenges/views/video/index.js
@@ -0,0 +1 @@
+export default from './Video.jsx';
diff --git a/common/app/routes/index.less b/common/app/routes/index.less
new file mode 100644
index 0000000000..5fb84826d4
--- /dev/null
+++ b/common/app/routes/index.less
@@ -0,0 +1 @@
+&{ @import "./challenges/challenges.less"; }
diff --git a/common/index.less b/common/index.less
new file mode 100644
index 0000000000..2688647f8f
--- /dev/null
+++ b/common/index.less
@@ -0,0 +1 @@
+&{ @import "./app/index.less"; }
diff --git a/gulpfile.js b/gulpfile.js
index e6b6d8135a..b080e887e2 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -50,7 +50,6 @@ var Rx = require('rx'),
Rx.config.longStackSupport = true;
var sync = browserSync.create('fcc-sync-server');
-var reload = sync.reload.bind(sync);
// user definable
var __DEV__ = !yargs.argv.p;
@@ -111,7 +110,6 @@ var paths = {
require.resolve('cal-heatmap'),
require.resolve('moment').replace('.js', '.min.js'),
require.resolve('moment-timezone').replace('index.js', 'builds/moment-timezone-with-data.min.js'),
-
require.resolve('mousetrap').replace('.js', '.min.js'),
require.resolve('lightbox2').replace('.js', '.min.js'),
require.resolve('rx').replace('index.js', 'dist/rx.all.min.js')
@@ -124,7 +122,10 @@ var paths = {
],
less: './client/less/main.less',
- lessFiles: './client/less/**/*.less',
+ lessFiles: [
+ './client/**/*.less',
+ './common/**/*.less'
+ ],
manifest: 'server/manifests/',
@@ -304,7 +305,10 @@ gulp.task('less', function() {
.pipe(__DEV__ ? sourcemaps.init() : gutil.noop())
// compile
.pipe(less({
- paths: [ path.join(__dirname, 'less', 'includes') ]
+ paths: [
+ path.join(__dirname, 'client', 'less'),
+ path.join(__dirname, 'common')
+ ]
}))
.pipe(__DEV__ ?
sourcemaps.write({ sourceRoot: '/less' }) :
diff --git a/server/views/layout-react.jade b/server/views/layout-react.jade
index 200cec021a..60cae513ac 100644
--- a/server/views/layout-react.jade
+++ b/server/views/layout-react.jade
@@ -6,7 +6,7 @@ html(lang='en')
else
title freeCodeCamp
include partials/react-stylesheets
- body.container.react-layout(style='overflow: hidden')
+ body
#fcc!= markup
script!= state
script.
diff --git a/server/views/layout.jade b/server/views/layout.jade
index ca093688e5..1d01679e3f 100644
--- a/server/views/layout.jade
+++ b/server/views/layout.jade
@@ -3,7 +3,7 @@ html(lang='en')
head
include partials/meta
include partials/stylesheets
- body.top-and-bottom-margins(class=theme !== 'default' ? theme : '')
+ body.main-container(class=theme !== 'default' ? theme : '')
include partials/scripts
include partials/navbar
include partials/flash
diff --git a/server/views/partials/navbar.jade b/server/views/partials/navbar.jade
index 4ade136d5b..da01c78835 100644
--- a/server/views/partials/navbar.jade
+++ b/server/views/partials/navbar.jade
@@ -1,4 +1,4 @@
-nav.navbar.navbar-default.navbar-fixed-top.nav-height
+nav.navbar.navbar-default.navbar-static-top.nav-height
.navbar-header
button.hamburger.navbar-toggle(type='button', data-toggle='collapse', data-target='.navbar-collapse')
.col-xs-12