feat(nav): make navbar static (#13673)
* feat(nav): make navbar static make the navbar in react layout and the static layout stick to the top of the screen * feat(challenges): Make classic view flex Classic view now uses flex to control it's height. This was necessary to control view and allow navbar to be static on other pages. This breaks mobile view and other non-classic challenge views * feat(app): Add logic to make screen expand on tablet * fix(app): let routes define their content structure * fix(less): use American spelling of gray * fix(classic-preview): make preview smaller to prevent scroll * feat(classic-frame): Make frame border less distinct * fix(challenges): scope test suite less to challenges * feat(challenges): make generic ChallengeTitle component
This commit is contained in:
committed by
Quincy Larson
parent
c125c38546
commit
f4443e16dd
@ -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%;
|
||||
}
|
||||
|
117
client/less/flexgrid.less
Normal file
117
client/less/flexgrid.less
Normal file
@ -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);
|
||||
}
|
@ -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
|
||||
//
|
||||
|
@ -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"; }
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
];
|
||||
|
@ -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);
|
||||
}
|
@ -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 (
|
||||
<div>
|
||||
<div className={ `${ns}-container` }>
|
||||
<Nav { ...navProps }/>
|
||||
<Row>
|
||||
<div className={ `${ns}-content` }>
|
||||
{ this.props.children }
|
||||
</Row>
|
||||
</div>
|
||||
<Toasts />
|
||||
</div>
|
||||
);
|
||||
|
14
common/app/app.less
Normal file
14
common/app/app.less
Normal file
@ -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;
|
||||
}
|
@ -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 (
|
||||
<Navbar
|
||||
className='nav-height'
|
||||
fixedTop={ true }
|
||||
staticTop={ true }
|
||||
>
|
||||
<Navbar.Header>
|
||||
<Navbar.Toggle children={ toggleButtonChild } />
|
||||
|
2
common/app/index.less
Normal file
2
common/app/index.less
Normal file
@ -0,0 +1,2 @@
|
||||
&{ @import "./app.less"; }
|
||||
&{ @import "./routes/index.less"; }
|
1
common/app/ns.json
Normal file
1
common/app/ns.json
Normal file
@ -0,0 +1 @@
|
||||
"app"
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -22,10 +22,6 @@ export default createTypes([
|
||||
'hardGoTo',
|
||||
'delayedRedirect',
|
||||
|
||||
'initWindowHeight',
|
||||
'updateWindowHeight',
|
||||
'updateNavHeight',
|
||||
|
||||
// data handling
|
||||
'updateChallengesData',
|
||||
'updateHikesData',
|
||||
|
@ -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 {
|
||||
<Modal
|
||||
show={ isOpen }
|
||||
>
|
||||
<Modal.Header className='challenge-list-header'>
|
||||
<Modal.Header className={ `${ns}-list-header` }>
|
||||
Did you find a bug?
|
||||
<span
|
||||
className='close closing-x'
|
||||
@ -85,4 +87,4 @@ export class BugModal extends PureComponent {
|
||||
BugModal.displayName = 'BugModal';
|
||||
BugModal.propTypes = propTypes;
|
||||
|
||||
export default connect(mapStateToProps, actions)(BugModal);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BugModal);
|
30
common/app/routes/challenges/Challenge-Title.jsx
Normal file
30
common/app/routes/challenges/Challenge-Title.jsx
Normal file
@ -0,0 +1,30 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
import ns from './ns.json';
|
||||
|
||||
const propTypes = {
|
||||
children: PropTypes.string,
|
||||
isCompleted: PropTypes.bool
|
||||
};
|
||||
|
||||
export default function ChallengeTitle({ children, isCompleted }) {
|
||||
let icon = null;
|
||||
if (isCompleted) {
|
||||
icon = (
|
||||
<i
|
||||
className='ion-checkmark-circled text-primary'
|
||||
title='Completed'
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<h4 className={ `text-center ${ns}-title` }>
|
||||
{ children || 'Happy Coding!' }
|
||||
{ icon }
|
||||
<hr />
|
||||
</h4>
|
||||
);
|
||||
}
|
||||
|
||||
ChallengeTitle.displayName = 'ChallengeTitle';
|
||||
ChallengeTitle.propTypes = propTypes;
|
@ -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) {
|
@ -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 (
|
||||
<div className='challenge-log'>
|
||||
<div className={ `${ns}-log` }>
|
||||
<NoSSR onSSR={ <CodeMirrorSkeleton content={ output } /> }>
|
||||
<Codemirror
|
||||
options={ defaultOptions }
|
@ -5,32 +5,32 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import PureComponent from 'react-pure-render/component';
|
||||
|
||||
import Classic from './classic/Classic.jsx';
|
||||
import Step from './step/Step.jsx';
|
||||
import Project from './project/Project.jsx';
|
||||
import Video from './video/Video.jsx';
|
||||
import BackEnd from './backend/Back-End.jsx';
|
||||
import Classic from './views/classic';
|
||||
import Step from './views/step';
|
||||
import Project from './views/project';
|
||||
import Video from './views/video';
|
||||
import BackEnd from './views/backend';
|
||||
|
||||
import {
|
||||
fetchChallenge,
|
||||
fetchChallenges,
|
||||
replaceChallenge,
|
||||
resetUi
|
||||
} from '../redux/actions';
|
||||
import { challengeSelector } from '../redux/selectors';
|
||||
import { updateTitle } from '../../../redux/actions';
|
||||
import { makeToast } from '../../../toasts/redux/actions';
|
||||
} from './redux/actions';
|
||||
import { challengeSelector } from './redux/selectors';
|
||||
import { updateTitle } from '../../redux/actions';
|
||||
import { makeToast } from '../../toasts/redux/actions';
|
||||
|
||||
const views = {
|
||||
step: Step,
|
||||
backend: BackEnd,
|
||||
classic: Classic,
|
||||
project: Project,
|
||||
simple: Project,
|
||||
video: Video,
|
||||
backend: BackEnd
|
||||
step: Step,
|
||||
video: Video
|
||||
};
|
||||
|
||||
const bindableActions = {
|
||||
const mapDispatchToProps = {
|
||||
fetchChallenge,
|
||||
fetchChallenges,
|
||||
makeToast,
|
||||
@ -78,21 +78,21 @@ const link = 'http://forum.freecodecamp.com/t/' +
|
||||
'-to-any-language/19111';
|
||||
|
||||
const propTypes = {
|
||||
areChallengesLoaded: PropTypes.bool,
|
||||
fetchChallenges: PropTypes.func.isRequired,
|
||||
isStep: PropTypes.bool,
|
||||
isTranslated: PropTypes.bool,
|
||||
lang: PropTypes.string.isRequired,
|
||||
makeToast: PropTypes.func.isRequired,
|
||||
params: PropTypes.object.isRequired,
|
||||
replaceChallenge: PropTypes.func.isRequired,
|
||||
resetUi: PropTypes.func.isRequired,
|
||||
title: PropTypes.string,
|
||||
updateTitle: PropTypes.func.isRequired,
|
||||
viewType: PropTypes.string
|
||||
areChallengesLoaded: PropTypes.bool,
|
||||
fetchChallenges: PropTypes.func.isRequired,
|
||||
isStep: PropTypes.bool,
|
||||
isTranslated: PropTypes.bool,
|
||||
lang: PropTypes.string.isRequired,
|
||||
makeToast: PropTypes.func.isRequired,
|
||||
params: PropTypes.object.isRequired,
|
||||
replaceChallenge: PropTypes.func.isRequired,
|
||||
resetUi: PropTypes.func.isRequired,
|
||||
title: PropTypes.string,
|
||||
updateTitle: PropTypes.func.isRequired,
|
||||
viewType: PropTypes.string
|
||||
};
|
||||
|
||||
export class Challenges extends PureComponent {
|
||||
export class Show extends PureComponent {
|
||||
|
||||
componentWillMount() {
|
||||
const { lang, isTranslated, makeToast } = this.props;
|
||||
@ -137,25 +137,17 @@ export class Challenges extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
renderView(viewType) {
|
||||
render() {
|
||||
const { viewType } = this.props;
|
||||
const View = views[viewType] || Classic;
|
||||
return <View />;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { viewType } = this.props;
|
||||
return (
|
||||
<div>
|
||||
{ this.renderView(viewType) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
@ -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,
|
@ -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 (
|
||||
<div
|
||||
className='challenge-test-suite'
|
||||
className={ `${ns}-test-suite` }
|
||||
style={{ marginTop: '10px' }}
|
||||
>
|
||||
{ this.renderTests(tests) }
|
81
common/app/routes/challenges/challenges.less
Normal file
81
common/app/routes/challenges/challenges.less
Normal file
@ -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"; }
|
@ -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 (
|
||||
<div
|
||||
className='challenges-preview'
|
||||
>
|
||||
<div className='hidden-xs hidden-md'>
|
||||
<img
|
||||
className='iphone-position iframe-scroll'
|
||||
src='https://s3.amazonaws.com/freecodecamp/iphone6-frame.png'
|
||||
/>
|
||||
</div>
|
||||
<iframe
|
||||
className='iphone iframe-scroll'
|
||||
id={ mainId }
|
||||
/>
|
||||
<div className='spacer' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Preview.displayName = 'Preview';
|
@ -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
|
||||
};
|
||||
}
|
||||
|
1
common/app/routes/challenges/ns.json
Normal file
1
common/app/routes/challenges/ns.json
Normal file
@ -0,0 +1 @@
|
||||
"challenges"
|
@ -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 (
|
||||
<div>
|
||||
<Row>
|
||||
<Col
|
||||
md={ 6 }
|
||||
mdOffset={ 3 }
|
||||
xs={ 12 }
|
||||
>
|
||||
<Row className='challenge-instructions'>
|
||||
<h3>{ title }</h3>
|
||||
<ChallengeTitle>{ title }</ChallengeTitle>
|
||||
{ this.renderDescription(description) }
|
||||
</Row>
|
||||
<Row>
|
||||
@ -158,7 +159,7 @@ export class BackEnd extends PureComponent {
|
||||
<TestSuite tests={ tests } />
|
||||
</Row>
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
1
common/app/routes/challenges/views/backend/index.js
Normal file
1
common/app/routes/challenges/views/backend/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './Back-End.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 (
|
||||
<Modal
|
||||
animation={ false }
|
||||
dialogClassName='challenge-success-modal'
|
||||
dialogClassName={ `${ns}-success-modal` }
|
||||
keyboard={ true }
|
||||
onHide={ close }
|
||||
onKeyDown={ this.handleKeyDown }
|
||||
show={ open }
|
||||
>
|
||||
<Modal.Header
|
||||
className='challenge-list-header'
|
||||
className={ `${ns}-list-header` }
|
||||
closeButton={ true }
|
||||
>
|
||||
<Modal.Title>{ successMessage }</Modal.Title>
|
@ -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 (
|
||||
<div>
|
||||
<Row>
|
||||
<Col
|
||||
lg={ showPreview ? 3 : 4 }
|
||||
md={ showPreview ? 3 : 4 }
|
||||
@ -142,7 +142,7 @@ export class Challenge extends PureComponent {
|
||||
submitChallenge={ submitChallenge }
|
||||
successMessage={ successMessage }
|
||||
/>
|
||||
</div>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
@ -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 (
|
||||
<div
|
||||
className='challenges-editor'
|
||||
style={ style }
|
||||
>
|
||||
<div className={ `${ns}-editor` }>
|
||||
<NoSSR onSSR={ <CodeMirrorSkeleton content={ content } /> }>
|
||||
<Codemirror
|
||||
onChange={ this.handleChange }
|
||||
@ -151,6 +140,3 @@ export class Editor extends PureComponent {
|
||||
Editor.defaultProps = defaultProps;
|
||||
Editor.displayName = 'Editor';
|
||||
Editor.propTypes = propTypes;
|
||||
|
||||
|
||||
export default connect(mapStateToProps)(Editor);
|
20
common/app/routes/challenges/views/classic/Preview.jsx
Normal file
20
common/app/routes/challenges/views/classic/Preview.jsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import ns from './ns.json';
|
||||
|
||||
const mainId = 'fcc-main-frame';
|
||||
|
||||
export default class Preview extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div className={ `${ns}-preview` }>
|
||||
<iframe
|
||||
className={ `${ns}-preview-frame` }
|
||||
id={ mainId }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Preview.displayName = 'Preview';
|
@ -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 (
|
||||
<div
|
||||
className='challenges-instructions-panel'
|
||||
className={ `${ns}-instructions-panel` }
|
||||
ref='panel'
|
||||
style={ style }
|
||||
>
|
||||
<div>
|
||||
<h4 className='text-center challenge-instructions-title'>
|
||||
{ title || 'Happy Coding!' }
|
||||
</h4>
|
||||
<hr />
|
||||
<ChallengeTitle>
|
||||
{ title }
|
||||
</ChallengeTitle>
|
||||
<Row>
|
||||
<Col
|
||||
className='challenge-instructions'
|
||||
className={ `${ns}-instructions` }
|
||||
xs={ 12 }
|
||||
>
|
||||
{ this.renderDescription(description) }
|
109
common/app/routes/challenges/views/classic/classic.less
Normal file
109
common/app/routes/challenges/views/classic/classic.less
Normal file
@ -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%;
|
||||
}
|
1
common/app/routes/challenges/views/classic/index.js
Normal file
1
common/app/routes/challenges/views/classic/index.js
Normal file
@ -0,0 +1 @@
|
||||
export default from './Classic.jsx';
|
1
common/app/routes/challenges/views/classic/ns.json
Normal file
1
common/app/routes/challenges/views/classic/ns.json
Normal file
@ -0,0 +1 @@
|
||||
"classic"
|
2
common/app/routes/challenges/views/index.less
Normal file
2
common/app/routes/challenges/views/index.less
Normal file
@ -0,0 +1,2 @@
|
||||
&{ @import "./classic/classic.less"; }
|
||||
&{ @import "./step/step.less"; }
|
@ -79,10 +79,7 @@ export class Header extends PureComponent {
|
||||
'Hide all challenges';
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className='text-center map-fixed-header'
|
||||
style={{ top: '50px' }}
|
||||
>
|
||||
<div className='text-center'>
|
||||
<p>Challenges required for certifications are marked with a *</p>
|
||||
<Row className='map-buttons'>
|
||||
<Button
|
@ -2,25 +2,18 @@ import React, { PropTypes } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { contain } from 'redux-epic';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import PureComponent from 'react-pure-render/component';
|
||||
import { Col } from 'react-bootstrap';
|
||||
import { Col, Row } from 'react-bootstrap';
|
||||
|
||||
import MapHeader from './Header.jsx';
|
||||
import SuperBlock from './Super-Block.jsx';
|
||||
import { fetchChallenges } from '../../redux/actions';
|
||||
import { updateTitle } from '../../../../redux/actions';
|
||||
|
||||
const bindableActions = { fetchChallenges, updateTitle };
|
||||
const mapStateToProps = createSelector(
|
||||
state => state.app.windowHeight,
|
||||
state => state.app.navHeight,
|
||||
state => state.challengesApp.superBlocks,
|
||||
(windowHeight, navHeight, superBlocks) => ({
|
||||
superBlocks,
|
||||
height: windowHeight - navHeight - 150
|
||||
})
|
||||
);
|
||||
const mapStateToProps = state => ({
|
||||
superBlocks: state.challengesApp.superBlocks
|
||||
});
|
||||
const mapDispatchToProps = { fetchChallenges, updateTitle };
|
||||
const fetchOptions = {
|
||||
fetchAction: 'fetchChallenges',
|
||||
isPrimed({ superBlocks }) {
|
||||
@ -29,7 +22,6 @@ const fetchOptions = {
|
||||
};
|
||||
const propTypes = {
|
||||
fetchChallenges: PropTypes.func.isRequired,
|
||||
height: PropTypes.number,
|
||||
params: PropTypes.object,
|
||||
superBlocks: PropTypes.array,
|
||||
updateTitle: PropTypes.func.isRequired
|
||||
@ -61,21 +53,16 @@ export class ShowMap extends PureComponent {
|
||||
|
||||
render() {
|
||||
const { superBlocks } = this.props;
|
||||
let height = 'auto';
|
||||
if (!this.props.params) {
|
||||
height = this.props.height + 'px';
|
||||
}
|
||||
return (
|
||||
<Col xs={ 12 }>
|
||||
<MapHeader />
|
||||
<div
|
||||
className='map-accordion center-block'
|
||||
style={{ height: height }}
|
||||
>
|
||||
{ this.renderSuperBlocks(superBlocks) }
|
||||
<div className='spacer' />
|
||||
</div>
|
||||
</Col>
|
||||
<Row>
|
||||
<Col xs={ 12 }>
|
||||
<MapHeader />
|
||||
<div className='map-accordion center-block'>
|
||||
{ this.renderSuperBlocks(superBlocks) }
|
||||
<div className='spacer' />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -84,6 +71,6 @@ ShowMap.displayName = 'Map';
|
||||
ShowMap.propTypes = propTypes;
|
||||
|
||||
export default compose(
|
||||
connect(mapStateToProps, bindableActions),
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
contain(fetchOptions)
|
||||
)(ShowMap);
|
1
common/app/routes/challenges/views/map/index.js
Normal file
1
common/app/routes/challenges/views/map/index.js
Normal file
@ -0,0 +1 @@
|
||||
export default from './Map.jsx';
|
@ -6,7 +6,7 @@ import {
|
||||
FormControl
|
||||
} from 'react-bootstrap';
|
||||
|
||||
import SolutionInput from '../Solution-Input.jsx';
|
||||
import SolutionInput from '../../Solution-Input.jsx';
|
||||
import {
|
||||
isValidURL,
|
||||
makeRequired,
|
@ -3,11 +3,11 @@ import { createSelector } from 'reselect';
|
||||
import { connect } from 'react-redux';
|
||||
import Youtube from 'react-youtube';
|
||||
import PureComponent from 'react-pure-render/component';
|
||||
import { Col } from 'react-bootstrap';
|
||||
import { Col, Row } from 'react-bootstrap';
|
||||
|
||||
import SidePanel from './Side-Panel.jsx';
|
||||
import ToolPanel from './Tool-Panel.jsx';
|
||||
import BugModal from '../Bug-Modal.jsx';
|
||||
import BugModal from '../../Bug-Modal.jsx';
|
||||
|
||||
import { challengeSelector } from '../../redux/selectors';
|
||||
|
||||
@ -47,7 +47,7 @@ export class Project extends PureComponent {
|
||||
description
|
||||
} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<Row>
|
||||
<Col md={ 4 }>
|
||||
<SidePanel
|
||||
description={ description }
|
||||
@ -59,19 +59,16 @@ export class Project extends PureComponent {
|
||||
md={ 8 }
|
||||
xs={ 12 }
|
||||
>
|
||||
<div className='embed-responsive embed-responsive-16by9'>
|
||||
<Youtube
|
||||
className='embed-responsive-item'
|
||||
id={ id }
|
||||
videoId={ videoId }
|
||||
/>
|
||||
</div>
|
||||
<Youtube
|
||||
id={ id }
|
||||
videoId={ videoId }
|
||||
/>
|
||||
<br />
|
||||
<ToolPanel />
|
||||
<br />
|
||||
<BugModal />
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
@ -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 (
|
||||
<i
|
||||
className='ion-checkmark-circled text-primary'
|
||||
title='Completed'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderDescription(title = '', description = []) {
|
||||
return description.map((line, index) => (
|
||||
<li
|
||||
@ -36,11 +23,9 @@ export default class SidePanel extends PureComponent {
|
||||
const { title, description, isCompleted } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<h4 className='text-center challenge-instructions-title'>
|
||||
<ChallengeTitle isCompleted={ isCompleted }>
|
||||
{ title }
|
||||
{ this.renderIcon(isCompleted) }
|
||||
</h4>
|
||||
<hr />
|
||||
</ChallengeTitle>
|
||||
<ul>
|
||||
{ this.renderDescription(title, description) }
|
||||
</ul>
|
1
common/app/routes/challenges/views/project/index.js
Normal file
1
common/app/routes/challenges/views/project/index.js
Normal file
@ -0,0 +1 @@
|
||||
export default from './Project.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 (
|
||||
<div
|
||||
className=''
|
||||
key={ imgUrl }
|
||||
>
|
||||
<div key={ imgUrl }>
|
||||
<a
|
||||
href={ imgUrl }
|
||||
onClick={ this.handleLightBoxOpen }
|
||||
@ -206,17 +204,17 @@ export class StepChallenge extends PureComponent {
|
||||
xs={ 12 }
|
||||
>
|
||||
<p
|
||||
className='challenge-step-description'
|
||||
className={ `${ns}-description` }
|
||||
dangerouslySetInnerHTML={{ __html: info }}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className='spacer' />
|
||||
<div className='challenge-button-block'>
|
||||
<div className={ `${ns}-button-block` }>
|
||||
{ this.renderActionButton(action, completeAction) }
|
||||
{ this.renderBackButton(currentIndex, stepBackward) }
|
||||
<Col
|
||||
className='challenge-step-counter large-p text-center'
|
||||
className={ `${ns}-counter large-p text-center` }
|
||||
sm={ 4 }
|
||||
xs={ 12 }
|
||||
>
|
||||
@ -260,23 +258,25 @@ export class StepChallenge extends PureComponent {
|
||||
closeLightBoxImage
|
||||
} = this.props;
|
||||
return (
|
||||
<Col
|
||||
md={ 8 }
|
||||
mdOffset={ 2 }
|
||||
>
|
||||
{ this.renderStep(this.props) }
|
||||
<div className='hidden'>
|
||||
{ this.renderImages(steps) }
|
||||
</div>
|
||||
<LightBox
|
||||
backdropClosesModal={ true }
|
||||
images={ [ { src: step[0] } ] }
|
||||
isOpen={ isLightBoxOpen }
|
||||
onClose={ closeLightBoxImage }
|
||||
showImageCount={ false }
|
||||
/>
|
||||
<div className='spacer' />
|
||||
</Col>
|
||||
<Row>
|
||||
<Col
|
||||
md={ 8 }
|
||||
mdOffset={ 2 }
|
||||
>
|
||||
{ this.renderStep(this.props) }
|
||||
<div className='hidden'>
|
||||
{ this.renderImages(steps) }
|
||||
</div>
|
||||
<LightBox
|
||||
backdropClosesModal={ true }
|
||||
images={ [ { src: step[0] } ] }
|
||||
isOpen={ isLightBoxOpen }
|
||||
onClose={ closeLightBoxImage }
|
||||
showImageCount={ false }
|
||||
/>
|
||||
<div className='spacer' />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
1
common/app/routes/challenges/views/step/index.js
Normal file
1
common/app/routes/challenges/views/step/index.js
Normal file
@ -0,0 +1 @@
|
||||
export default from './Step.jsx';
|
1
common/app/routes/challenges/views/step/ns.json
Normal file
1
common/app/routes/challenges/views/step/ns.json
Normal file
@ -0,0 +1 @@
|
||||
"step"
|
54
common/app/routes/challenges/views/step/step.less
Normal file
54
common/app/routes/challenges/views/step/step.less
Normal file
@ -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);
|
||||
}
|
@ -70,15 +70,12 @@ export class Lecture extends React.Component {
|
||||
return (
|
||||
<Col xs={ 12 }>
|
||||
<Row>
|
||||
<div className='embed-responsive embed-responsive-16by9'>
|
||||
<Youtube
|
||||
className='embed-responsive-item'
|
||||
id={ id }
|
||||
onError={ this.handleError }
|
||||
opts={ embedOpts }
|
||||
videoId={ videoId }
|
||||
/>
|
||||
</div>
|
||||
<Youtube
|
||||
id={ id }
|
||||
onError={ this.handleError }
|
||||
opts={ embedOpts }
|
||||
videoId={ videoId }
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col
|
@ -1,6 +1,6 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Col } from 'react-bootstrap';
|
||||
import { Col, Row } from 'react-bootstrap';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import Lecture from './Lecture.jsx';
|
||||
@ -55,19 +55,21 @@ export class Video extends React.Component {
|
||||
shouldShowQuestions
|
||||
} = this.props;
|
||||
return (
|
||||
<Col xs={ 12 }>
|
||||
<header className='text-center'>
|
||||
<h4>{ title }</h4>
|
||||
</header>
|
||||
<hr />
|
||||
<div className='spacer' />
|
||||
<section
|
||||
className={ 'text-center' }
|
||||
title={ title }
|
||||
>
|
||||
{ this.renderBody(shouldShowQuestions) }
|
||||
</section>
|
||||
</Col>
|
||||
<Row>
|
||||
<Col xs={ 12 }>
|
||||
<header className='text-center'>
|
||||
<h4>{ title }</h4>
|
||||
</header>
|
||||
<hr />
|
||||
<div className='spacer' />
|
||||
<section
|
||||
className={ 'text-center' }
|
||||
title={ title }
|
||||
>
|
||||
{ this.renderBody(shouldShowQuestions) }
|
||||
</section>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
1
common/app/routes/challenges/views/video/index.js
Normal file
1
common/app/routes/challenges/views/video/index.js
Normal file
@ -0,0 +1 @@
|
||||
export default from './Video.jsx';
|
1
common/app/routes/index.less
Normal file
1
common/app/routes/index.less
Normal file
@ -0,0 +1 @@
|
||||
&{ @import "./challenges/challenges.less"; }
|
1
common/index.less
Normal file
1
common/index.less
Normal file
@ -0,0 +1 @@
|
||||
&{ @import "./app/index.less"; }
|
12
gulpfile.js
12
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' }) :
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user