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
|
// Variables
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
@empty: ~'';
|
||||||
|
|
||||||
|
|
||||||
//== Colors
|
//== Colors
|
||||||
@ -20,8 +21,8 @@
|
|||||||
@brand-warning: #f0ad4e;
|
@brand-warning: #f0ad4e;
|
||||||
@brand-danger: #d9534f;
|
@brand-danger: #d9534f;
|
||||||
|
|
||||||
@icon-grey: #575757;
|
@icon-gray: #575757;
|
||||||
@icon-light-grey: #888888;
|
@icon-light-gray: #888888;
|
||||||
//== Scaffolding
|
//== Scaffolding
|
||||||
//
|
//
|
||||||
//## Settings for some of the most global styles.
|
//## Settings for some of the most global styles.
|
||||||
@ -305,6 +306,10 @@
|
|||||||
//** Deprecated `@screen-lg-desktop` as of v3.0.1
|
//** Deprecated `@screen-lg-desktop` as of v3.0.1
|
||||||
@screen-lg-desktop: @screen-lg-min;
|
@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
|
// So media queries don't overlap when required, provide a maximum
|
||||||
@screen-xs-max: (@screen-sm-min - 1);
|
@screen-xs-max: (@screen-sm-min - 1);
|
||||||
@screen-sm-max: (@screen-md-min - 1);
|
@screen-sm-max: (@screen-md-min - 1);
|
||||||
@ -345,6 +350,8 @@
|
|||||||
//** For `@screen-lg-min` and up.
|
//** For `@screen-lg-min` and up.
|
||||||
@container-lg: @container-large-desktop;
|
@container-lg: @container-large-desktop;
|
||||||
|
|
||||||
|
@container-xl: (1340px + @grid-gutter-width);
|
||||||
|
|
||||||
|
|
||||||
//== Navbar
|
//== Navbar
|
||||||
//
|
//
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
@import "lib/bootstrap/bootstrap";
|
@import "./lib/bootstrap/bootstrap";
|
||||||
@import "lib/bootstrap-social/bootstrap-social";
|
@import "./lib/bootstrap-social/bootstrap-social";
|
||||||
@import "lib/ionicons/ionicons";
|
@import "./lib/ionicons/ionicons";
|
||||||
@import "lib/animate";
|
@import "./lib/animate";
|
||||||
@import "lib/bootstrap/variables";
|
@import "./lib/bootstrap/variables";
|
||||||
|
@import "./flexgrid.less";
|
||||||
|
|
||||||
html,body,div,span,a,li,td,th {
|
html,body,div,span,a,li,td,th {
|
||||||
font-family: 'Lato', sans-serif;
|
font-family: 'Lato', sans-serif;
|
||||||
@ -34,13 +35,6 @@ pre.wrappable {
|
|||||||
word-wrap: break-word; /* IE 5+ */
|
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] {
|
//input[type=checkbox] {
|
||||||
// /* Double-sized Checkboxes */
|
// /* Double-sized Checkboxes */
|
||||||
// -ms-transform: scale(2); /* IE */
|
// -ms-transform: scale(2); /* IE */
|
||||||
@ -54,28 +48,14 @@ html {
|
|||||||
border-color: @brand-primary;
|
border-color: @brand-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.full-screen-body-background {
|
.full-screen-body-background {
|
||||||
background-color: @body-bg;
|
background-color: @body-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.top-and-bottom-margins {
|
.no-top-and-bottom-margins {
|
||||||
padding-top: 80px;
|
|
||||||
margin-bottom: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.no-top-and-bottom-margins {
|
|
||||||
margin: 75px 20px 0px 20px;
|
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 {
|
h1, h2 {
|
||||||
font-weight: 400;
|
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 {
|
.nav-height {
|
||||||
height: 50px;
|
|
||||||
border: none;
|
border: none;
|
||||||
|
height: @navbar-height;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.landing-icon {
|
.landing-icon {
|
||||||
@ -1241,11 +1224,14 @@ and (max-width : 400px) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// surrounding downstream import with &{}
|
||||||
@import "code-mirror.less";
|
// creates locally scoped imports
|
||||||
@import "challenge.less";
|
// and prevents vaiables from overwriting each other
|
||||||
@import "toastr.less";
|
&{ @import "./code-mirror.less"; }
|
||||||
@import "map.less";
|
&{ @import "./challenge.less"; }
|
||||||
@import "sk-wave.less";
|
&{ @import "./toastr.less"; }
|
||||||
@import "classic-modal.less";
|
&{ @import "./map.less"; }
|
||||||
@import "skeleton-shimmer.less";
|
&{ @import "./sk-wave.less"; }
|
||||||
|
&{ @import "./classic-modal.less"; }
|
||||||
|
&{ @import "./skeleton-shimmer.less"; }
|
||||||
|
&{ @import "../../common/index.less"; }
|
||||||
|
@ -54,27 +54,10 @@
|
|||||||
width: 100%;
|
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 {
|
.map-accordion {
|
||||||
width: 100%;
|
|
||||||
margin-top: 100px;
|
|
||||||
max-width: 700px;
|
max-width: 700px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.map-accordion-panel-title {
|
.map-accordion-panel-title {
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
|
@ -7,7 +7,6 @@ import hardGoToSaga from './hard-go-to-saga.js';
|
|||||||
import mouseTrapSaga from './mouse-trap-saga.js';
|
import mouseTrapSaga from './mouse-trap-saga.js';
|
||||||
import nightModeSaga from './night-mode-saga.js';
|
import nightModeSaga from './night-mode-saga.js';
|
||||||
import titleSaga from './title-saga.js';
|
import titleSaga from './title-saga.js';
|
||||||
import windowSaga from './window-saga.js';
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
analyticsSaga,
|
analyticsSaga,
|
||||||
@ -18,6 +17,5 @@ export default [
|
|||||||
hardGoToSaga,
|
hardGoToSaga,
|
||||||
mouseTrapSaga,
|
mouseTrapSaga,
|
||||||
nightModeSaga,
|
nightModeSaga,
|
||||||
titleSaga,
|
titleSaga
|
||||||
windowSaga
|
|
||||||
];
|
];
|
||||||
|
@ -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 React, { PropTypes } from 'react';
|
||||||
import { Button, Row } from 'react-bootstrap';
|
import { Button } from 'react-bootstrap';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
import ns from './ns.json';
|
||||||
import {
|
import {
|
||||||
fetchUser,
|
fetchUser,
|
||||||
initWindowHeight,
|
|
||||||
updateNavHeight,
|
|
||||||
updateAppLang,
|
updateAppLang,
|
||||||
trackEvent,
|
trackEvent,
|
||||||
loadCurrentChallenge,
|
loadCurrentChallenge,
|
||||||
@ -21,15 +20,13 @@ import Toasts from './toasts/Toasts.jsx';
|
|||||||
import { userSelector } from './redux/selectors';
|
import { userSelector } from './redux/selectors';
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
initWindowHeight,
|
closeDropdown,
|
||||||
updateNavHeight,
|
|
||||||
fetchUser,
|
fetchUser,
|
||||||
submitChallenge,
|
|
||||||
updateAppLang,
|
|
||||||
trackEvent,
|
|
||||||
loadCurrentChallenge,
|
loadCurrentChallenge,
|
||||||
openDropdown,
|
openDropdown,
|
||||||
closeDropdown
|
submitChallenge,
|
||||||
|
trackEvent,
|
||||||
|
updateAppLang
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
@ -58,7 +55,6 @@ const propTypes = {
|
|||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
closeDropdown: PropTypes.func.isRequired,
|
closeDropdown: PropTypes.func.isRequired,
|
||||||
fetchUser: PropTypes.func,
|
fetchUser: PropTypes.func,
|
||||||
initWindowHeight: PropTypes.func,
|
|
||||||
isNavDropdownOpen: PropTypes.bool,
|
isNavDropdownOpen: PropTypes.bool,
|
||||||
isSignedIn: PropTypes.bool,
|
isSignedIn: PropTypes.bool,
|
||||||
loadCurrentChallenge: PropTypes.func.isRequired,
|
loadCurrentChallenge: PropTypes.func.isRequired,
|
||||||
@ -71,7 +67,6 @@ const propTypes = {
|
|||||||
toast: PropTypes.object,
|
toast: PropTypes.object,
|
||||||
trackEvent: PropTypes.func.isRequired,
|
trackEvent: PropTypes.func.isRequired,
|
||||||
updateAppLang: PropTypes.func.isRequired,
|
updateAppLang: PropTypes.func.isRequired,
|
||||||
updateNavHeight: PropTypes.func,
|
|
||||||
username: PropTypes.string
|
username: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -84,7 +79,6 @@ export class FreeCodeCamp extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.initWindowHeight();
|
|
||||||
if (!this.props.isSignedIn) {
|
if (!this.props.isSignedIn) {
|
||||||
this.props.fetchUser();
|
this.props.fetchUser();
|
||||||
}
|
}
|
||||||
@ -110,7 +104,6 @@ export class FreeCodeCamp extends React.Component {
|
|||||||
username,
|
username,
|
||||||
points,
|
points,
|
||||||
picture,
|
picture,
|
||||||
updateNavHeight,
|
|
||||||
trackEvent,
|
trackEvent,
|
||||||
loadCurrentChallenge,
|
loadCurrentChallenge,
|
||||||
openDropdown,
|
openDropdown,
|
||||||
@ -118,23 +111,22 @@ export class FreeCodeCamp extends React.Component {
|
|||||||
isNavDropdownOpen
|
isNavDropdownOpen
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const navProps = {
|
const navProps = {
|
||||||
username,
|
closeDropdown,
|
||||||
points,
|
isNavDropdownOpen,
|
||||||
picture,
|
|
||||||
updateNavHeight,
|
|
||||||
trackEvent,
|
|
||||||
loadCurrentChallenge,
|
loadCurrentChallenge,
|
||||||
openDropdown,
|
openDropdown,
|
||||||
closeDropdown,
|
picture,
|
||||||
isNavDropdownOpen
|
points,
|
||||||
|
trackEvent,
|
||||||
|
username
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={ `${ns}-container` }>
|
||||||
<Nav { ...navProps }/>
|
<Nav { ...navProps }/>
|
||||||
<Row>
|
<div className={ `${ns}-content` }>
|
||||||
{ this.props.children }
|
{ this.props.children }
|
||||||
</Row>
|
</div>
|
||||||
<Toasts />
|
<Toasts />
|
||||||
</div>
|
</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 React, { PropTypes } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import { LinkContainer } from 'react-router-bootstrap';
|
import { LinkContainer } from 'react-router-bootstrap';
|
||||||
import {
|
import {
|
||||||
Col,
|
Col,
|
||||||
@ -41,7 +40,6 @@ const propTypes = {
|
|||||||
showLoading: PropTypes.bool,
|
showLoading: PropTypes.bool,
|
||||||
signedIn: PropTypes.bool,
|
signedIn: PropTypes.bool,
|
||||||
trackEvent: PropTypes.func.isRequired,
|
trackEvent: PropTypes.func.isRequired,
|
||||||
updateNavHeight: PropTypes.func,
|
|
||||||
username: PropTypes.string
|
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) {
|
handleMapClickOnMap(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.trackEvent({
|
this.props.trackEvent({
|
||||||
@ -172,7 +165,7 @@ export default class FCCNav extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Navbar
|
<Navbar
|
||||||
className='nav-height'
|
className='nav-height'
|
||||||
fixedTop={ true }
|
staticTop={ true }
|
||||||
>
|
>
|
||||||
<Navbar.Header>
|
<Navbar.Header>
|
||||||
<Navbar.Toggle children={ toggleButtonChild } />
|
<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
|
// hardGoTo(path: String) => Action
|
||||||
export const hardGoTo = createAction(types.hardGoTo);
|
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
|
// data
|
||||||
export const updateChallengesData = createAction(types.updateChallengesData);
|
export const updateChallengesData = createAction(types.updateChallengesData);
|
||||||
export const updateHikesData = createAction(types.updateHikesData);
|
export const updateHikesData = createAction(types.updateHikesData);
|
||||||
|
@ -7,8 +7,6 @@ const initialState = {
|
|||||||
user: '',
|
user: '',
|
||||||
lang: '',
|
lang: '',
|
||||||
csrfToken: '',
|
csrfToken: '',
|
||||||
windowHeight: 0,
|
|
||||||
navHeight: 0,
|
|
||||||
theme: 'default'
|
theme: 'default'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,14 +39,6 @@ export default handleActions(
|
|||||||
...state,
|
...state,
|
||||||
points
|
points
|
||||||
}),
|
}),
|
||||||
[types.updateWindowHeight]: (state, { payload: windowHeight }) => ({
|
|
||||||
...state,
|
|
||||||
windowHeight
|
|
||||||
}),
|
|
||||||
[types.updateNavHeight]: (state, { payload: navHeight }) => ({
|
|
||||||
...state,
|
|
||||||
navHeight
|
|
||||||
}),
|
|
||||||
[types.delayedRedirect]: (state, { payload }) => ({
|
[types.delayedRedirect]: (state, { payload }) => ({
|
||||||
...state,
|
...state,
|
||||||
delayedRedirect: payload
|
delayedRedirect: payload
|
||||||
|
@ -22,10 +22,6 @@ export default createTypes([
|
|||||||
'hardGoTo',
|
'hardGoTo',
|
||||||
'delayedRedirect',
|
'delayedRedirect',
|
||||||
|
|
||||||
'initWindowHeight',
|
|
||||||
'updateWindowHeight',
|
|
||||||
'updateNavHeight',
|
|
||||||
|
|
||||||
// data handling
|
// data handling
|
||||||
'updateChallengesData',
|
'updateChallengesData',
|
||||||
'updateHikesData',
|
'updateHikesData',
|
||||||
|
@ -2,10 +2,13 @@ import React, { PropTypes } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Button, Modal } from 'react-bootstrap';
|
import { Button, Modal } from 'react-bootstrap';
|
||||||
import PureComponent from 'react-pure-render/component';
|
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 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 bugLink = 'http://forum.freecodecamp.com/t/how-to-report-a-bug/19543';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
@ -16,7 +19,6 @@ const propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class BugModal extends PureComponent {
|
export class BugModal extends PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
isOpen,
|
isOpen,
|
||||||
@ -28,7 +30,7 @@ export class BugModal extends PureComponent {
|
|||||||
<Modal
|
<Modal
|
||||||
show={ isOpen }
|
show={ isOpen }
|
||||||
>
|
>
|
||||||
<Modal.Header className='challenge-list-header'>
|
<Modal.Header className={ `${ns}-list-header` }>
|
||||||
Did you find a bug?
|
Did you find a bug?
|
||||||
<span
|
<span
|
||||||
className='close closing-x'
|
className='close closing-x'
|
||||||
@ -85,4 +87,4 @@ export class BugModal extends PureComponent {
|
|||||||
BugModal.displayName = 'BugModal';
|
BugModal.displayName = 'BugModal';
|
||||||
BugModal.propTypes = propTypes;
|
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 = {
|
const propTypes = {
|
||||||
content: PropTypes.string
|
content: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class CodeMirrorSkeleton extends PureComponent {
|
export default class CodeMirrorSkeleton extends PureComponent {
|
||||||
|
|
||||||
renderLine(line, i) {
|
renderLine(line, i) {
|
@ -2,14 +2,15 @@ import React, { PureComponent, PropTypes } from 'react';
|
|||||||
import NoSSR from 'react-no-ssr';
|
import NoSSR from 'react-no-ssr';
|
||||||
import Codemirror from 'react-codemirror';
|
import Codemirror from 'react-codemirror';
|
||||||
|
|
||||||
import CodeMirrorSkeleton from './CodeMirrorSkeleton.jsx';
|
import ns from './ns.json';
|
||||||
|
import CodeMirrorSkeleton from './Code-Mirror-Skeleton.jsx';
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
lineNumbers: false,
|
lineNumbers: false,
|
||||||
|
lineWrapping: true,
|
||||||
mode: 'javascript',
|
mode: 'javascript',
|
||||||
theme: 'monokai',
|
|
||||||
readOnly: 'nocursor',
|
readOnly: 'nocursor',
|
||||||
lineWrapping: true
|
theme: 'monokai'
|
||||||
};
|
};
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
@ -21,7 +22,7 @@ export default class Output extends PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
const { output, defaultOutput } = this.props;
|
const { output, defaultOutput } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className='challenge-log'>
|
<div className={ `${ns}-log` }>
|
||||||
<NoSSR onSSR={ <CodeMirrorSkeleton content={ output } /> }>
|
<NoSSR onSSR={ <CodeMirrorSkeleton content={ output } /> }>
|
||||||
<Codemirror
|
<Codemirror
|
||||||
options={ defaultOptions }
|
options={ defaultOptions }
|
@ -5,32 +5,32 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import PureComponent from 'react-pure-render/component';
|
import PureComponent from 'react-pure-render/component';
|
||||||
|
|
||||||
import Classic from './classic/Classic.jsx';
|
import Classic from './views/classic';
|
||||||
import Step from './step/Step.jsx';
|
import Step from './views/step';
|
||||||
import Project from './project/Project.jsx';
|
import Project from './views/project';
|
||||||
import Video from './video/Video.jsx';
|
import Video from './views/video';
|
||||||
import BackEnd from './backend/Back-End.jsx';
|
import BackEnd from './views/backend';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fetchChallenge,
|
fetchChallenge,
|
||||||
fetchChallenges,
|
fetchChallenges,
|
||||||
replaceChallenge,
|
replaceChallenge,
|
||||||
resetUi
|
resetUi
|
||||||
} from '../redux/actions';
|
} from './redux/actions';
|
||||||
import { challengeSelector } from '../redux/selectors';
|
import { challengeSelector } from './redux/selectors';
|
||||||
import { updateTitle } from '../../../redux/actions';
|
import { updateTitle } from '../../redux/actions';
|
||||||
import { makeToast } from '../../../toasts/redux/actions';
|
import { makeToast } from '../../toasts/redux/actions';
|
||||||
|
|
||||||
const views = {
|
const views = {
|
||||||
step: Step,
|
backend: BackEnd,
|
||||||
classic: Classic,
|
classic: Classic,
|
||||||
project: Project,
|
project: Project,
|
||||||
simple: Project,
|
simple: Project,
|
||||||
video: Video,
|
step: Step,
|
||||||
backend: BackEnd
|
video: Video
|
||||||
};
|
};
|
||||||
|
|
||||||
const bindableActions = {
|
const mapDispatchToProps = {
|
||||||
fetchChallenge,
|
fetchChallenge,
|
||||||
fetchChallenges,
|
fetchChallenges,
|
||||||
makeToast,
|
makeToast,
|
||||||
@ -78,21 +78,21 @@ const link = 'http://forum.freecodecamp.com/t/' +
|
|||||||
'-to-any-language/19111';
|
'-to-any-language/19111';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
areChallengesLoaded: PropTypes.bool,
|
areChallengesLoaded: PropTypes.bool,
|
||||||
fetchChallenges: PropTypes.func.isRequired,
|
fetchChallenges: PropTypes.func.isRequired,
|
||||||
isStep: PropTypes.bool,
|
isStep: PropTypes.bool,
|
||||||
isTranslated: PropTypes.bool,
|
isTranslated: PropTypes.bool,
|
||||||
lang: PropTypes.string.isRequired,
|
lang: PropTypes.string.isRequired,
|
||||||
makeToast: PropTypes.func.isRequired,
|
makeToast: PropTypes.func.isRequired,
|
||||||
params: PropTypes.object.isRequired,
|
params: PropTypes.object.isRequired,
|
||||||
replaceChallenge: PropTypes.func.isRequired,
|
replaceChallenge: PropTypes.func.isRequired,
|
||||||
resetUi: PropTypes.func.isRequired,
|
resetUi: PropTypes.func.isRequired,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
updateTitle: PropTypes.func.isRequired,
|
updateTitle: PropTypes.func.isRequired,
|
||||||
viewType: PropTypes.string
|
viewType: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Challenges extends PureComponent {
|
export class Show extends PureComponent {
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
const { lang, isTranslated, makeToast } = this.props;
|
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;
|
const View = views[viewType] || Classic;
|
||||||
return <View />;
|
return <View />;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
|
||||||
const { viewType } = this.props;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{ this.renderView(viewType) }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Challenges.displayName = 'Challenges';
|
Show.displayName = 'Show(ChallengeView)';
|
||||||
Challenges.propTypes = propTypes;
|
Show.propTypes = propTypes;
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
connect(mapStateToProps, bindableActions),
|
connect(mapStateToProps, mapDispatchToProps),
|
||||||
contain(fetchOptions)
|
contain(fetchOptions)
|
||||||
)(Challenges);
|
)(Show);
|
@ -1,6 +1,6 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { HelpBlock, FormGroup, FormControl } from 'react-bootstrap';
|
import { HelpBlock, FormGroup, FormControl } from 'react-bootstrap';
|
||||||
import { getValidationState, DOMOnlyProps } from '../../../utils/form';
|
import { getValidationState, DOMOnlyProps } from '../../utils/form';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
@ -2,6 +2,8 @@ import React, { PropTypes, PureComponent } from 'react';
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Col, Row } from 'react-bootstrap';
|
import { Col, Row } from 'react-bootstrap';
|
||||||
|
|
||||||
|
import ns from './ns.json';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
tests: PropTypes.arrayOf(PropTypes.object)
|
tests: PropTypes.arrayOf(PropTypes.object)
|
||||||
};
|
};
|
||||||
@ -41,7 +43,7 @@ export default class TestSuite extends PureComponent {
|
|||||||
const { tests } = this.props;
|
const { tests } = this.props;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='challenge-test-suite'
|
className={ `${ns}-test-suite` }
|
||||||
style={{ marginTop: '10px' }}
|
style={{ marginTop: '10px' }}
|
||||||
>
|
>
|
||||||
{ this.renderTests(tests) }
|
{ 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 Show from './Show.jsx';
|
||||||
import ShowMap from './components/map/Map.jsx';
|
import _Map from './views/map';
|
||||||
|
|
||||||
export function challengesRoute() {
|
export function challengesRoute() {
|
||||||
return {
|
return {
|
||||||
@ -24,6 +24,6 @@ export function modernChallengesRoute() {
|
|||||||
export function mapRoute() {
|
export function mapRoute() {
|
||||||
return {
|
return {
|
||||||
path: 'map',
|
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
|
Row
|
||||||
} from 'react-bootstrap';
|
} from 'react-bootstrap';
|
||||||
|
|
||||||
import SolutionInput from '../Solution-Input.jsx';
|
import ChallengeTitle from '../../Challenge-Title.jsx';
|
||||||
import TestSuite from '../Test-Suite.jsx';
|
import SolutionInput from '../../Solution-Input.jsx';
|
||||||
import Output from '../Output.jsx';
|
import TestSuite from '../../Test-Suite.jsx';
|
||||||
|
import Output from '../../Output.jsx';
|
||||||
import { submitChallenge, executeChallenge } from '../../redux/actions.js';
|
import { submitChallenge, executeChallenge } from '../../redux/actions.js';
|
||||||
import { challengeSelector } from '../../redux/selectors.js';
|
import { challengeSelector } from '../../redux/selectors.js';
|
||||||
import { descriptionRegex } from '../../utils.js';
|
import { descriptionRegex } from '../../utils.js';
|
||||||
import {
|
import {
|
||||||
|
createFormValidator,
|
||||||
isValidURL,
|
isValidURL,
|
||||||
makeRequired,
|
makeRequired
|
||||||
createFormValidator
|
|
||||||
} from '../../../../utils/form.js';
|
} from '../../../../utils/form.js';
|
||||||
|
|
||||||
// provided by redux form
|
// provided by redux form
|
||||||
@ -114,14 +115,14 @@ export class BackEnd extends PureComponent {
|
|||||||
'Submit and go to my next challenge' :
|
'Submit and go to my next challenge' :
|
||||||
"I've completed this challenge";
|
"I've completed this challenge";
|
||||||
return (
|
return (
|
||||||
<div>
|
<Row>
|
||||||
<Col
|
<Col
|
||||||
md={ 6 }
|
md={ 6 }
|
||||||
mdOffset={ 3 }
|
mdOffset={ 3 }
|
||||||
xs={ 12 }
|
xs={ 12 }
|
||||||
>
|
>
|
||||||
<Row className='challenge-instructions'>
|
<Row className='challenge-instructions'>
|
||||||
<h3>{ title }</h3>
|
<ChallengeTitle>{ title }</ChallengeTitle>
|
||||||
{ this.renderDescription(description) }
|
{ this.renderDescription(description) }
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
@ -158,7 +159,7 @@ export class BackEnd extends PureComponent {
|
|||||||
<TestSuite tests={ tests } />
|
<TestSuite tests={ tests } />
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</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 PureComponent from 'react-pure-render/component';
|
||||||
import FontAwesome from 'react-fontawesome';
|
import FontAwesome from 'react-fontawesome';
|
||||||
|
|
||||||
|
import ns from './ns.json';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
close: PropTypes.func,
|
close: PropTypes.func,
|
||||||
open: PropTypes.bool.isRequired,
|
open: PropTypes.bool.isRequired,
|
||||||
@ -22,10 +24,10 @@ export default class ClassicModal extends PureComponent {
|
|||||||
e.keyCode === 13 &&
|
e.keyCode === 13 &&
|
||||||
(e.ctrlKey || e.meta) &&
|
(e.ctrlKey || e.meta) &&
|
||||||
open
|
open
|
||||||
) {
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
submitChallenge();
|
submitChallenge();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -38,14 +40,14 @@ export default class ClassicModal extends PureComponent {
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
animation={ false }
|
animation={ false }
|
||||||
dialogClassName='challenge-success-modal'
|
dialogClassName={ `${ns}-success-modal` }
|
||||||
keyboard={ true }
|
keyboard={ true }
|
||||||
onHide={ close }
|
onHide={ close }
|
||||||
onKeyDown={ this.handleKeyDown }
|
onKeyDown={ this.handleKeyDown }
|
||||||
show={ open }
|
show={ open }
|
||||||
>
|
>
|
||||||
<Modal.Header
|
<Modal.Header
|
||||||
className='challenge-list-header'
|
className={ `${ns}-list-header` }
|
||||||
closeButton={ true }
|
closeButton={ true }
|
||||||
>
|
>
|
||||||
<Modal.Title>{ successMessage }</Modal.Title>
|
<Modal.Title>{ successMessage }</Modal.Title>
|
@ -1,14 +1,14 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Col } from 'react-bootstrap';
|
import { Row, Col } from 'react-bootstrap';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import PureComponent from 'react-pure-render/component';
|
import PureComponent from 'react-pure-render/component';
|
||||||
|
|
||||||
import Editor from './Editor.jsx';
|
import Editor from './Editor.jsx';
|
||||||
import SidePanel from './Side-Panel.jsx';
|
import SidePanel from './Side-Panel.jsx';
|
||||||
import Preview from './Preview.jsx';
|
import Preview from './Preview.jsx';
|
||||||
import BugModal from '../Bug-Modal.jsx';
|
import BugModal from '../../Bug-Modal.jsx';
|
||||||
import ClassicModal from '../Classic-Modal.jsx';
|
import ClassicModal from './Classic-Modal.jsx';
|
||||||
import { challengeSelector } from '../../redux/selectors';
|
import { challengeSelector } from '../../redux/selectors';
|
||||||
import {
|
import {
|
||||||
executeChallenge,
|
executeChallenge,
|
||||||
@ -116,7 +116,7 @@ export class Challenge extends PureComponent {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Row>
|
||||||
<Col
|
<Col
|
||||||
lg={ showPreview ? 3 : 4 }
|
lg={ showPreview ? 3 : 4 }
|
||||||
md={ showPreview ? 3 : 4 }
|
md={ showPreview ? 3 : 4 }
|
||||||
@ -142,7 +142,7 @@ export class Challenge extends PureComponent {
|
|||||||
submitChallenge={ submitChallenge }
|
submitChallenge={ submitChallenge }
|
||||||
successMessage={ successMessage }
|
successMessage={ successMessage }
|
||||||
/>
|
/>
|
||||||
</div>
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,21 +1,14 @@
|
|||||||
import { Subject } from 'rx';
|
import { Subject } from 'rx';
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import Codemirror from 'react-codemirror';
|
import Codemirror from 'react-codemirror';
|
||||||
import NoSSR from 'react-no-ssr';
|
import NoSSR from 'react-no-ssr';
|
||||||
import PureComponent from 'react-pure-render/component';
|
import PureComponent from 'react-pure-render/component';
|
||||||
|
|
||||||
import MouseTrap from 'mousetrap';
|
import MouseTrap from 'mousetrap';
|
||||||
|
|
||||||
import CodeMirrorSkeleton from '../CodeMirrorSkeleton.jsx';
|
import ns from './ns.json';
|
||||||
|
import CodeMirrorSkeleton from '../../Code-Mirror-Skeleton.jsx';
|
||||||
const mapStateToProps = createSelector(
|
|
||||||
state => state.app.windowHeight,
|
|
||||||
state => state.app.navHeight,
|
|
||||||
(windowHeight, navHeight) => ({ height: windowHeight - navHeight - 50 })
|
|
||||||
);
|
|
||||||
|
|
||||||
const editorDebounceTimeout = 750;
|
const editorDebounceTimeout = 750;
|
||||||
|
|
||||||
@ -40,12 +33,11 @@ const defaultProps = {
|
|||||||
const propTypes = {
|
const propTypes = {
|
||||||
content: PropTypes.string,
|
content: PropTypes.string,
|
||||||
executeChallenge: PropTypes.func,
|
executeChallenge: PropTypes.func,
|
||||||
height: PropTypes.number,
|
|
||||||
mode: PropTypes.string,
|
mode: PropTypes.string,
|
||||||
updateFile: PropTypes.func
|
updateFile: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Editor extends PureComponent {
|
export default class Editor extends PureComponent {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
this._editorContent$ = new Subject();
|
this._editorContent$ = new Subject();
|
||||||
@ -125,16 +117,13 @@ export class Editor extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { executeChallenge, content, height, mode } = this.props;
|
const {
|
||||||
const style = {};
|
content,
|
||||||
if (height) {
|
executeChallenge,
|
||||||
style.height = height + 'px';
|
mode
|
||||||
}
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={ `${ns}-editor` }>
|
||||||
className='challenges-editor'
|
|
||||||
style={ style }
|
|
||||||
>
|
|
||||||
<NoSSR onSSR={ <CodeMirrorSkeleton content={ content } /> }>
|
<NoSSR onSSR={ <CodeMirrorSkeleton content={ content } /> }>
|
||||||
<Codemirror
|
<Codemirror
|
||||||
onChange={ this.handleChange }
|
onChange={ this.handleChange }
|
||||||
@ -151,6 +140,3 @@ export class Editor extends PureComponent {
|
|||||||
Editor.defaultProps = defaultProps;
|
Editor.defaultProps = defaultProps;
|
||||||
Editor.displayName = 'Editor';
|
Editor.displayName = 'Editor';
|
||||||
Editor.propTypes = propTypes;
|
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 PureComponent from 'react-pure-render/component';
|
||||||
import { Col, Row } from 'react-bootstrap';
|
import { Col, Row } from 'react-bootstrap';
|
||||||
|
|
||||||
import TestSuite from '../Test-Suite.jsx';
|
import ns from './ns.json';
|
||||||
import Output from '../Output.jsx';
|
|
||||||
|
import ChallengeTitle from '../../Challenge-Title.jsx';
|
||||||
|
import TestSuite from '../../Test-Suite.jsx';
|
||||||
|
import Output from '../../Output.jsx';
|
||||||
import ToolPanel from './Tool-Panel.jsx';
|
import ToolPanel from './Tool-Panel.jsx';
|
||||||
import { challengeSelector } from '../../redux/selectors';
|
import { challengeSelector } from '../../redux/selectors';
|
||||||
import {
|
import {
|
||||||
@ -27,8 +30,6 @@ const mapDispatchToProps = {
|
|||||||
};
|
};
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
challengeSelector,
|
challengeSelector,
|
||||||
state => state.app.windowHeight,
|
|
||||||
state => state.app.navHeight,
|
|
||||||
state => state.challengesApp.tests,
|
state => state.challengesApp.tests,
|
||||||
state => state.challengesApp.output,
|
state => state.challengesApp.output,
|
||||||
state => state.challengesApp.hintIndex,
|
state => state.challengesApp.hintIndex,
|
||||||
@ -42,8 +43,6 @@ const mapStateToProps = createSelector(
|
|||||||
} = {},
|
} = {},
|
||||||
title
|
title
|
||||||
},
|
},
|
||||||
windowHeight,
|
|
||||||
navHeight,
|
|
||||||
tests,
|
tests,
|
||||||
output,
|
output,
|
||||||
hintIndex,
|
hintIndex,
|
||||||
@ -52,7 +51,6 @@ const mapStateToProps = createSelector(
|
|||||||
) => ({
|
) => ({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
height: windowHeight - navHeight - 20,
|
|
||||||
tests,
|
tests,
|
||||||
output,
|
output,
|
||||||
hint: hints[hintIndex],
|
hint: hints[hintIndex],
|
||||||
@ -63,7 +61,6 @@ const mapStateToProps = createSelector(
|
|||||||
const propTypes = {
|
const propTypes = {
|
||||||
description: PropTypes.arrayOf(PropTypes.string),
|
description: PropTypes.arrayOf(PropTypes.string),
|
||||||
executeChallenge: PropTypes.func,
|
executeChallenge: PropTypes.func,
|
||||||
height: PropTypes.number,
|
|
||||||
helpChatRoom: PropTypes.string,
|
helpChatRoom: PropTypes.string,
|
||||||
hint: PropTypes.string,
|
hint: PropTypes.string,
|
||||||
isCodeLocked: PropTypes.bool,
|
isCodeLocked: PropTypes.bool,
|
||||||
@ -108,7 +105,6 @@ export class SidePanel extends PureComponent {
|
|||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
height,
|
|
||||||
tests = [],
|
tests = [],
|
||||||
output,
|
output,
|
||||||
hint,
|
hint,
|
||||||
@ -120,24 +116,18 @@ export class SidePanel extends PureComponent {
|
|||||||
isCodeLocked,
|
isCodeLocked,
|
||||||
unlockUntrustedCode
|
unlockUntrustedCode
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const style = {};
|
|
||||||
if (height) {
|
|
||||||
style.height = height + 'px';
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='challenges-instructions-panel'
|
className={ `${ns}-instructions-panel` }
|
||||||
ref='panel'
|
ref='panel'
|
||||||
style={ style }
|
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h4 className='text-center challenge-instructions-title'>
|
<ChallengeTitle>
|
||||||
{ title || 'Happy Coding!' }
|
{ title }
|
||||||
</h4>
|
</ChallengeTitle>
|
||||||
<hr />
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col
|
<Col
|
||||||
className='challenge-instructions'
|
className={ `${ns}-instructions` }
|
||||||
xs={ 12 }
|
xs={ 12 }
|
||||||
>
|
>
|
||||||
{ this.renderDescription(description) }
|
{ 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';
|
'Hide all challenges';
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div className='text-center'>
|
||||||
className='text-center map-fixed-header'
|
|
||||||
style={{ top: '50px' }}
|
|
||||||
>
|
|
||||||
<p>Challenges required for certifications are marked with a *</p>
|
<p>Challenges required for certifications are marked with a *</p>
|
||||||
<Row className='map-buttons'>
|
<Row className='map-buttons'>
|
||||||
<Button
|
<Button
|
@ -2,25 +2,18 @@ import React, { PropTypes } from 'react';
|
|||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
import { contain } from 'redux-epic';
|
import { contain } from 'redux-epic';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import PureComponent from 'react-pure-render/component';
|
import PureComponent from 'react-pure-render/component';
|
||||||
import { Col } from 'react-bootstrap';
|
import { Col, Row } from 'react-bootstrap';
|
||||||
|
|
||||||
import MapHeader from './Header.jsx';
|
import MapHeader from './Header.jsx';
|
||||||
import SuperBlock from './Super-Block.jsx';
|
import SuperBlock from './Super-Block.jsx';
|
||||||
import { fetchChallenges } from '../../redux/actions';
|
import { fetchChallenges } from '../../redux/actions';
|
||||||
import { updateTitle } from '../../../../redux/actions';
|
import { updateTitle } from '../../../../redux/actions';
|
||||||
|
|
||||||
const bindableActions = { fetchChallenges, updateTitle };
|
const mapStateToProps = state => ({
|
||||||
const mapStateToProps = createSelector(
|
superBlocks: state.challengesApp.superBlocks
|
||||||
state => state.app.windowHeight,
|
});
|
||||||
state => state.app.navHeight,
|
const mapDispatchToProps = { fetchChallenges, updateTitle };
|
||||||
state => state.challengesApp.superBlocks,
|
|
||||||
(windowHeight, navHeight, superBlocks) => ({
|
|
||||||
superBlocks,
|
|
||||||
height: windowHeight - navHeight - 150
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
fetchAction: 'fetchChallenges',
|
fetchAction: 'fetchChallenges',
|
||||||
isPrimed({ superBlocks }) {
|
isPrimed({ superBlocks }) {
|
||||||
@ -29,7 +22,6 @@ const fetchOptions = {
|
|||||||
};
|
};
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
fetchChallenges: PropTypes.func.isRequired,
|
fetchChallenges: PropTypes.func.isRequired,
|
||||||
height: PropTypes.number,
|
|
||||||
params: PropTypes.object,
|
params: PropTypes.object,
|
||||||
superBlocks: PropTypes.array,
|
superBlocks: PropTypes.array,
|
||||||
updateTitle: PropTypes.func.isRequired
|
updateTitle: PropTypes.func.isRequired
|
||||||
@ -61,21 +53,16 @@ export class ShowMap extends PureComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { superBlocks } = this.props;
|
const { superBlocks } = this.props;
|
||||||
let height = 'auto';
|
|
||||||
if (!this.props.params) {
|
|
||||||
height = this.props.height + 'px';
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Col xs={ 12 }>
|
<Row>
|
||||||
<MapHeader />
|
<Col xs={ 12 }>
|
||||||
<div
|
<MapHeader />
|
||||||
className='map-accordion center-block'
|
<div className='map-accordion center-block'>
|
||||||
style={{ height: height }}
|
{ this.renderSuperBlocks(superBlocks) }
|
||||||
>
|
<div className='spacer' />
|
||||||
{ this.renderSuperBlocks(superBlocks) }
|
</div>
|
||||||
<div className='spacer' />
|
</Col>
|
||||||
</div>
|
</Row>
|
||||||
</Col>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,6 +71,6 @@ ShowMap.displayName = 'Map';
|
|||||||
ShowMap.propTypes = propTypes;
|
ShowMap.propTypes = propTypes;
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
connect(mapStateToProps, bindableActions),
|
connect(mapStateToProps, mapDispatchToProps),
|
||||||
contain(fetchOptions)
|
contain(fetchOptions)
|
||||||
)(ShowMap);
|
)(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
|
FormControl
|
||||||
} from 'react-bootstrap';
|
} from 'react-bootstrap';
|
||||||
|
|
||||||
import SolutionInput from '../Solution-Input.jsx';
|
import SolutionInput from '../../Solution-Input.jsx';
|
||||||
import {
|
import {
|
||||||
isValidURL,
|
isValidURL,
|
||||||
makeRequired,
|
makeRequired,
|
@ -3,11 +3,11 @@ import { createSelector } from 'reselect';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Youtube from 'react-youtube';
|
import Youtube from 'react-youtube';
|
||||||
import PureComponent from 'react-pure-render/component';
|
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 SidePanel from './Side-Panel.jsx';
|
||||||
import ToolPanel from './Tool-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';
|
import { challengeSelector } from '../../redux/selectors';
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ export class Project extends PureComponent {
|
|||||||
description
|
description
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<Row>
|
||||||
<Col md={ 4 }>
|
<Col md={ 4 }>
|
||||||
<SidePanel
|
<SidePanel
|
||||||
description={ description }
|
description={ description }
|
||||||
@ -59,19 +59,16 @@ export class Project extends PureComponent {
|
|||||||
md={ 8 }
|
md={ 8 }
|
||||||
xs={ 12 }
|
xs={ 12 }
|
||||||
>
|
>
|
||||||
<div className='embed-responsive embed-responsive-16by9'>
|
<Youtube
|
||||||
<Youtube
|
id={ id }
|
||||||
className='embed-responsive-item'
|
videoId={ videoId }
|
||||||
id={ id }
|
/>
|
||||||
videoId={ videoId }
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<br />
|
<br />
|
||||||
<ToolPanel />
|
<ToolPanel />
|
||||||
<br />
|
<br />
|
||||||
<BugModal />
|
<BugModal />
|
||||||
</Col>
|
</Col>
|
||||||
</div>
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes, PureComponent } from 'react';
|
||||||
|
import ChallengeTitle from '../../Challenge-Title.jsx';
|
||||||
import PureComponent from 'react-pure-render/component';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
description: PropTypes.arrayOf(PropTypes.string),
|
description: PropTypes.arrayOf(PropTypes.string),
|
||||||
@ -10,18 +9,6 @@ const propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default class SidePanel extends PureComponent {
|
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 = []) {
|
renderDescription(title = '', description = []) {
|
||||||
return description.map((line, index) => (
|
return description.map((line, index) => (
|
||||||
<li
|
<li
|
||||||
@ -36,11 +23,9 @@ export default class SidePanel extends PureComponent {
|
|||||||
const { title, description, isCompleted } = this.props;
|
const { title, description, isCompleted } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h4 className='text-center challenge-instructions-title'>
|
<ChallengeTitle isCompleted={ isCompleted }>
|
||||||
{ title }
|
{ title }
|
||||||
{ this.renderIcon(isCompleted) }
|
</ChallengeTitle>
|
||||||
</h4>
|
|
||||||
<hr />
|
|
||||||
<ul>
|
<ul>
|
||||||
{ this.renderDescription(title, description) }
|
{ this.renderDescription(title, description) }
|
||||||
</ul>
|
</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 PureComponent from 'react-pure-render/component';
|
||||||
import LightBox from 'react-images';
|
import LightBox from 'react-images';
|
||||||
|
|
||||||
|
import ns from './ns.json';
|
||||||
import {
|
import {
|
||||||
closeLightBoxImage,
|
closeLightBoxImage,
|
||||||
completeAction,
|
completeAction,
|
||||||
@ -180,10 +181,7 @@ export class StepChallenge extends PureComponent {
|
|||||||
}
|
}
|
||||||
const [imgUrl, imgAlt, info, action] = step;
|
const [imgUrl, imgAlt, info, action] = step;
|
||||||
return (
|
return (
|
||||||
<div
|
<div key={ imgUrl }>
|
||||||
className=''
|
|
||||||
key={ imgUrl }
|
|
||||||
>
|
|
||||||
<a
|
<a
|
||||||
href={ imgUrl }
|
href={ imgUrl }
|
||||||
onClick={ this.handleLightBoxOpen }
|
onClick={ this.handleLightBoxOpen }
|
||||||
@ -206,17 +204,17 @@ export class StepChallenge extends PureComponent {
|
|||||||
xs={ 12 }
|
xs={ 12 }
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
className='challenge-step-description'
|
className={ `${ns}-description` }
|
||||||
dangerouslySetInnerHTML={{ __html: info }}
|
dangerouslySetInnerHTML={{ __html: info }}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<div className='spacer' />
|
<div className='spacer' />
|
||||||
<div className='challenge-button-block'>
|
<div className={ `${ns}-button-block` }>
|
||||||
{ this.renderActionButton(action, completeAction) }
|
{ this.renderActionButton(action, completeAction) }
|
||||||
{ this.renderBackButton(currentIndex, stepBackward) }
|
{ this.renderBackButton(currentIndex, stepBackward) }
|
||||||
<Col
|
<Col
|
||||||
className='challenge-step-counter large-p text-center'
|
className={ `${ns}-counter large-p text-center` }
|
||||||
sm={ 4 }
|
sm={ 4 }
|
||||||
xs={ 12 }
|
xs={ 12 }
|
||||||
>
|
>
|
||||||
@ -260,23 +258,25 @@ export class StepChallenge extends PureComponent {
|
|||||||
closeLightBoxImage
|
closeLightBoxImage
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<Col
|
<Row>
|
||||||
md={ 8 }
|
<Col
|
||||||
mdOffset={ 2 }
|
md={ 8 }
|
||||||
>
|
mdOffset={ 2 }
|
||||||
{ this.renderStep(this.props) }
|
>
|
||||||
<div className='hidden'>
|
{ this.renderStep(this.props) }
|
||||||
{ this.renderImages(steps) }
|
<div className='hidden'>
|
||||||
</div>
|
{ this.renderImages(steps) }
|
||||||
<LightBox
|
</div>
|
||||||
backdropClosesModal={ true }
|
<LightBox
|
||||||
images={ [ { src: step[0] } ] }
|
backdropClosesModal={ true }
|
||||||
isOpen={ isLightBoxOpen }
|
images={ [ { src: step[0] } ] }
|
||||||
onClose={ closeLightBoxImage }
|
isOpen={ isLightBoxOpen }
|
||||||
showImageCount={ false }
|
onClose={ closeLightBoxImage }
|
||||||
/>
|
showImageCount={ false }
|
||||||
<div className='spacer' />
|
/>
|
||||||
</Col>
|
<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 (
|
return (
|
||||||
<Col xs={ 12 }>
|
<Col xs={ 12 }>
|
||||||
<Row>
|
<Row>
|
||||||
<div className='embed-responsive embed-responsive-16by9'>
|
<Youtube
|
||||||
<Youtube
|
id={ id }
|
||||||
className='embed-responsive-item'
|
onError={ this.handleError }
|
||||||
id={ id }
|
opts={ embedOpts }
|
||||||
onError={ this.handleError }
|
videoId={ videoId }
|
||||||
opts={ embedOpts }
|
/>
|
||||||
videoId={ videoId }
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Col
|
<Col
|
@ -1,6 +1,6 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Col } from 'react-bootstrap';
|
import { Col, Row } from 'react-bootstrap';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import Lecture from './Lecture.jsx';
|
import Lecture from './Lecture.jsx';
|
||||||
@ -55,19 +55,21 @@ export class Video extends React.Component {
|
|||||||
shouldShowQuestions
|
shouldShowQuestions
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<Col xs={ 12 }>
|
<Row>
|
||||||
<header className='text-center'>
|
<Col xs={ 12 }>
|
||||||
<h4>{ title }</h4>
|
<header className='text-center'>
|
||||||
</header>
|
<h4>{ title }</h4>
|
||||||
<hr />
|
</header>
|
||||||
<div className='spacer' />
|
<hr />
|
||||||
<section
|
<div className='spacer' />
|
||||||
className={ 'text-center' }
|
<section
|
||||||
title={ title }
|
className={ 'text-center' }
|
||||||
>
|
title={ title }
|
||||||
{ this.renderBody(shouldShowQuestions) }
|
>
|
||||||
</section>
|
{ this.renderBody(shouldShowQuestions) }
|
||||||
</Col>
|
</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;
|
Rx.config.longStackSupport = true;
|
||||||
var sync = browserSync.create('fcc-sync-server');
|
var sync = browserSync.create('fcc-sync-server');
|
||||||
var reload = sync.reload.bind(sync);
|
|
||||||
|
|
||||||
// user definable
|
// user definable
|
||||||
var __DEV__ = !yargs.argv.p;
|
var __DEV__ = !yargs.argv.p;
|
||||||
@ -111,7 +110,6 @@ var paths = {
|
|||||||
require.resolve('cal-heatmap'),
|
require.resolve('cal-heatmap'),
|
||||||
require.resolve('moment').replace('.js', '.min.js'),
|
require.resolve('moment').replace('.js', '.min.js'),
|
||||||
require.resolve('moment-timezone').replace('index.js', 'builds/moment-timezone-with-data.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('mousetrap').replace('.js', '.min.js'),
|
||||||
require.resolve('lightbox2').replace('.js', '.min.js'),
|
require.resolve('lightbox2').replace('.js', '.min.js'),
|
||||||
require.resolve('rx').replace('index.js', 'dist/rx.all.min.js')
|
require.resolve('rx').replace('index.js', 'dist/rx.all.min.js')
|
||||||
@ -124,7 +122,10 @@ var paths = {
|
|||||||
],
|
],
|
||||||
|
|
||||||
less: './client/less/main.less',
|
less: './client/less/main.less',
|
||||||
lessFiles: './client/less/**/*.less',
|
lessFiles: [
|
||||||
|
'./client/**/*.less',
|
||||||
|
'./common/**/*.less'
|
||||||
|
],
|
||||||
|
|
||||||
manifest: 'server/manifests/',
|
manifest: 'server/manifests/',
|
||||||
|
|
||||||
@ -304,7 +305,10 @@ gulp.task('less', function() {
|
|||||||
.pipe(__DEV__ ? sourcemaps.init() : gutil.noop())
|
.pipe(__DEV__ ? sourcemaps.init() : gutil.noop())
|
||||||
// compile
|
// compile
|
||||||
.pipe(less({
|
.pipe(less({
|
||||||
paths: [ path.join(__dirname, 'less', 'includes') ]
|
paths: [
|
||||||
|
path.join(__dirname, 'client', 'less'),
|
||||||
|
path.join(__dirname, 'common')
|
||||||
|
]
|
||||||
}))
|
}))
|
||||||
.pipe(__DEV__ ?
|
.pipe(__DEV__ ?
|
||||||
sourcemaps.write({ sourceRoot: '/less' }) :
|
sourcemaps.write({ sourceRoot: '/less' }) :
|
||||||
|
@ -6,7 +6,7 @@ html(lang='en')
|
|||||||
else
|
else
|
||||||
title freeCodeCamp
|
title freeCodeCamp
|
||||||
include partials/react-stylesheets
|
include partials/react-stylesheets
|
||||||
body.container.react-layout(style='overflow: hidden')
|
body
|
||||||
#fcc!= markup
|
#fcc!= markup
|
||||||
script!= state
|
script!= state
|
||||||
script.
|
script.
|
||||||
|
@ -3,7 +3,7 @@ html(lang='en')
|
|||||||
head
|
head
|
||||||
include partials/meta
|
include partials/meta
|
||||||
include partials/stylesheets
|
include partials/stylesheets
|
||||||
body.top-and-bottom-margins(class=theme !== 'default' ? theme : '')
|
body.main-container(class=theme !== 'default' ? theme : '')
|
||||||
include partials/scripts
|
include partials/scripts
|
||||||
include partials/navbar
|
include partials/navbar
|
||||||
include partials/flash
|
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
|
.navbar-header
|
||||||
button.hamburger.navbar-toggle(type='button', data-toggle='collapse', data-target='.navbar-collapse')
|
button.hamburger.navbar-toggle(type='button', data-toggle='collapse', data-target='.navbar-collapse')
|
||||||
.col-xs-12
|
.col-xs-12
|
||||||
|
Reference in New Issue
Block a user