Fix(accounts): show challenge info on user profile
This changes the behavior of the user profile page to pull the current challenge info from our challenge map and overwrite the user challenge. This should also make name changes point to the correct challenge regardless of the info saved to the user profile
This commit is contained in:
@ -12,21 +12,10 @@ import {
|
|||||||
delayedRedirect,
|
delayedRedirect,
|
||||||
createErrorObservable
|
createErrorObservable
|
||||||
} from '../../../redux/actions';
|
} from '../../../redux/actions';
|
||||||
|
import createNameIdMap from '../../../../utils/create-name-id-map';
|
||||||
|
|
||||||
const { fetchChallenge, fetchChallenges, replaceChallenge } = types;
|
const { fetchChallenge, fetchChallenges, replaceChallenge } = types;
|
||||||
|
|
||||||
function createNameIdMap(entities) {
|
|
||||||
const { challenge } = entities;
|
|
||||||
return {
|
|
||||||
...entities,
|
|
||||||
challengeIdToName: Object.keys(challenge)
|
|
||||||
.reduce((map, challengeName) => {
|
|
||||||
map[challenge[challengeName].id] = challenge[challengeName].dashedName;
|
|
||||||
return map;
|
|
||||||
}, {})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function fetchChallengesSaga(action$, getState, { services }) {
|
export default function fetchChallengesSaga(action$, getState, { services }) {
|
||||||
return action$
|
return action$
|
||||||
.filter(({ type }) => (
|
.filter(({ type }) => (
|
||||||
|
12
common/utils/create-name-id-map.js
Normal file
12
common/utils/create-name-id-map.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// createNameIdMap(entities: Object) => Object
|
||||||
|
export default function createNameIdMap(entities) {
|
||||||
|
const { challenge } = entities;
|
||||||
|
return {
|
||||||
|
...entities,
|
||||||
|
challengeIdToName: Object.keys(challenge)
|
||||||
|
.reduce((map, challengeName) => {
|
||||||
|
map[challenge[challengeName].id] = challenge[challengeName].dashedName;
|
||||||
|
return map;
|
||||||
|
}, {})
|
||||||
|
};
|
||||||
|
}
|
@ -3,15 +3,12 @@ import moment from 'moment-timezone';
|
|||||||
import { Observable } from 'rx';
|
import { Observable } from 'rx';
|
||||||
import debugFactory from 'debug';
|
import debugFactory from 'debug';
|
||||||
|
|
||||||
import supportedLanguages from '../../common/utils/supported-languages';
|
|
||||||
import {
|
import {
|
||||||
frontEndChallengeId,
|
frontEndChallengeId,
|
||||||
dataVisChallengeId,
|
dataVisChallengeId,
|
||||||
backEndChallengeId
|
backEndChallengeId
|
||||||
} from '../utils/constantStrings.json';
|
} from '../utils/constantStrings.json';
|
||||||
|
|
||||||
import certTypes from '../utils/certTypes.json';
|
import certTypes from '../utils/certTypes.json';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ifNoUser401,
|
ifNoUser401,
|
||||||
ifNoUserRedirectTo
|
ifNoUserRedirectTo
|
||||||
@ -22,6 +19,9 @@ import {
|
|||||||
calcCurrentStreak,
|
calcCurrentStreak,
|
||||||
calcLongestStreak
|
calcLongestStreak
|
||||||
} from '../utils/user-stats';
|
} from '../utils/user-stats';
|
||||||
|
import supportedLanguages from '../../common/utils/supported-languages';
|
||||||
|
import createNameIdMap from '../../common/utils/create-name-id-map';
|
||||||
|
import { cachedMap } from '../utils/map';
|
||||||
|
|
||||||
const debug = debugFactory('fcc:boot:user');
|
const debug = debugFactory('fcc:boot:user');
|
||||||
const sendNonUserToMap = ifNoUserRedirectTo('/map');
|
const sendNonUserToMap = ifNoUserRedirectTo('/map');
|
||||||
@ -85,25 +85,35 @@ function getChallengeGroup(challenge) {
|
|||||||
return 'challenges';
|
return 'challenges';
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildDisplayChallenges(challengeMap: Object, tz: String) => Observable[{
|
// buildDisplayChallenges(
|
||||||
|
// entities: { challenge: Object, challengeIdToName: Object },
|
||||||
|
// challengeMap: Object,
|
||||||
|
// tz: String
|
||||||
|
// ) => Observable[{
|
||||||
// algorithms: Array,
|
// algorithms: Array,
|
||||||
// projects: Array,
|
// projects: Array,
|
||||||
// challenges: Array
|
// challenges: Array
|
||||||
// }]
|
// }]
|
||||||
function buildDisplayChallenges(challengeMap = {}, timezone) {
|
function buildDisplayChallenges(
|
||||||
return Observable.from(Object.keys(challengeMap))
|
{ challenge: challengeMap = {}, challengeIdToName },
|
||||||
.map(challengeId => challengeMap[challengeId])
|
userChallengeMap = {},
|
||||||
.map(challenge => {
|
timezone
|
||||||
let finalChallenge = { ...challenge };
|
) {
|
||||||
if (challenge.completedDate) {
|
return Observable.from(Object.keys(userChallengeMap))
|
||||||
|
.map(challengeId => userChallengeMap[challengeId])
|
||||||
|
.map(userChallenge => {
|
||||||
|
const challengeId = userChallenge.id;
|
||||||
|
const challenge = challengeMap[ challengeIdToName[challengeId] ];
|
||||||
|
let finalChallenge = { ...userChallenge, ...challenge };
|
||||||
|
if (userChallenge.completedDate) {
|
||||||
finalChallenge.completedDate = moment
|
finalChallenge.completedDate = moment
|
||||||
.tz(challenge.completedDate, timezone)
|
.tz(userChallenge.completedDate, timezone)
|
||||||
.format(dateFormat);
|
.format(dateFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (challenge.lastUpdated) {
|
if (userChallenge.lastUpdated) {
|
||||||
finalChallenge.lastUpdated = moment
|
finalChallenge.lastUpdated = moment
|
||||||
.tz(challenge.lastUpdated, timezone)
|
.tz(userChallenge.lastUpdated, timezone)
|
||||||
.format(dateFormat);
|
.format(dateFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +138,8 @@ module.exports = function(app) {
|
|||||||
const router = app.loopback.Router();
|
const router = app.loopback.Router();
|
||||||
const api = app.loopback.Router();
|
const api = app.loopback.Router();
|
||||||
const User = app.models.User;
|
const User = app.models.User;
|
||||||
|
const Block = app.models.Block;
|
||||||
|
const map$ = cachedMap(Block);
|
||||||
function findUserByUsername$(username, fields) {
|
function findUserByUsername$(username, fields) {
|
||||||
return observeQuery(
|
return observeQuery(
|
||||||
User,
|
User,
|
||||||
@ -187,7 +199,7 @@ module.exports = function(app) {
|
|||||||
(req, res) => res.redirect(req.url.replace('full-stack', 'back-end'))
|
(req, res) => res.redirect(req.url.replace('full-stack', 'back-end'))
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get('/:username', returnUser);
|
router.get('/:username', showUserProfile);
|
||||||
|
|
||||||
app.use('/:lang', router);
|
app.use('/:lang', router);
|
||||||
app.use(api);
|
app.use(api);
|
||||||
@ -248,7 +260,7 @@ module.exports = function(app) {
|
|||||||
return res.redirect('/' + username);
|
return res.redirect('/' + username);
|
||||||
}
|
}
|
||||||
|
|
||||||
function returnUser(req, res, next) {
|
function showUserProfile(req, res, next) {
|
||||||
const username = req.params.username.toLowerCase();
|
const username = req.params.username.toLowerCase();
|
||||||
const { user } = req;
|
const { user } = req;
|
||||||
|
|
||||||
@ -313,7 +325,12 @@ module.exports = function(app) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildDisplayChallenges(userPortfolio.challengeMap, timezone)
|
return map$.map(({ entities }) => createNameIdMap(entities))
|
||||||
|
.flatMap(entities => buildDisplayChallenges(
|
||||||
|
entities,
|
||||||
|
userPortfolio.challengeMap,
|
||||||
|
timezone
|
||||||
|
))
|
||||||
.map(displayChallenges => ({
|
.map(displayChallenges => ({
|
||||||
...userPortfolio,
|
...userPortfolio,
|
||||||
...displayChallenges,
|
...displayChallenges,
|
||||||
|
@ -1,134 +1,12 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import { Observable } from 'rx';
|
import { Observable } from 'rx';
|
||||||
import { Schema, valuesOf, arrayOf, normalize } from 'normalizr';
|
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { nameify, unDasherize } from '../utils';
|
import { unDasherize } from '../utils';
|
||||||
import supportedLanguages from '../../common/utils/supported-languages';
|
import { mapChallengeToLang, cachedMap, getMapForLang } from '../utils/map';
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV !== 'production';
|
const isDev = process.env.NODE_ENV !== 'production';
|
||||||
const isBeta = !!process.env.BETA;
|
const isBeta = !!process.env.BETA;
|
||||||
const challengesRegex = /^(bonfire|waypoint|zipline|basejump|checkpoint)/i;
|
const challengesRegex = /^(bonfire|waypoint|zipline|basejump|checkpoint)/i;
|
||||||
const log = debug('fcc:services:challenges');
|
const log = debug('fcc:services:map');
|
||||||
const challenge = new Schema('challenge', { idAttribute: 'dashedName' });
|
|
||||||
const block = new Schema('block', { idAttribute: 'dashedName' });
|
|
||||||
const superBlock = new Schema('superBlock', { idAttribute: 'dashedName' });
|
|
||||||
|
|
||||||
block.define({
|
|
||||||
challenges: arrayOf(challenge)
|
|
||||||
});
|
|
||||||
|
|
||||||
superBlock.define({
|
|
||||||
blocks: arrayOf(block)
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapSchema = valuesOf(superBlock);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* interface ChallengeMap {
|
|
||||||
* result: [superBlockDashedName: String]
|
|
||||||
* entities: {
|
|
||||||
* superBlock: {
|
|
||||||
* [superBlockDashedName: String]: {
|
|
||||||
* blocks: [blockDashedName: String]
|
|
||||||
* }
|
|
||||||
* },
|
|
||||||
* block: {
|
|
||||||
* [blockDashedName: String]: {
|
|
||||||
* challenges: [challengeDashedName: String]
|
|
||||||
* }
|
|
||||||
* },
|
|
||||||
* challenge: {
|
|
||||||
* [challengeDashedName: String]: Challenge
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
function cachedMap(Block) {
|
|
||||||
const query = {
|
|
||||||
include: 'challenges',
|
|
||||||
order: ['superOrder ASC', 'order ASC']
|
|
||||||
};
|
|
||||||
return Block.find$(query)
|
|
||||||
.flatMap(blocks => Observable.from(blocks.map(block => block.toJSON())))
|
|
||||||
.reduce((map, block) => {
|
|
||||||
if (map[block.superBlock]) {
|
|
||||||
map[block.superBlock].blocks.push(block);
|
|
||||||
} else {
|
|
||||||
map[block.superBlock] = {
|
|
||||||
title: _.startCase(block.superBlock),
|
|
||||||
order: block.superOrder,
|
|
||||||
name: nameify(_.startCase(block.superBlock)),
|
|
||||||
dashedName: block.superBlock,
|
|
||||||
blocks: [block],
|
|
||||||
message: block.superBlockMessage
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}, {})
|
|
||||||
.map(map => normalize(map, mapSchema))
|
|
||||||
.map(map => {
|
|
||||||
// make sure challenges are in the right order
|
|
||||||
map.entities.block = Object.keys(map.entities.block)
|
|
||||||
// turn map into array
|
|
||||||
.map(key => map.entities.block[key])
|
|
||||||
// perform re-order
|
|
||||||
.map(block => {
|
|
||||||
block.challenges = block.challenges.reduce((accu, dashedName) => {
|
|
||||||
const index = map.entities.challenge[dashedName].suborder;
|
|
||||||
accu[index - 1] = dashedName;
|
|
||||||
return accu;
|
|
||||||
}, []);
|
|
||||||
return block;
|
|
||||||
})
|
|
||||||
// turn back into map
|
|
||||||
.reduce((blockMap, block) => {
|
|
||||||
blockMap[block.dashedName] = block;
|
|
||||||
return blockMap;
|
|
||||||
}, {});
|
|
||||||
return map;
|
|
||||||
})
|
|
||||||
.map(map => {
|
|
||||||
// re-order superBlocks result
|
|
||||||
const result = Object.keys(map.result).reduce((result, supName) => {
|
|
||||||
const index = map.entities.superBlock[supName].order;
|
|
||||||
result[index] = supName;
|
|
||||||
return result;
|
|
||||||
}, []);
|
|
||||||
return {
|
|
||||||
...map,
|
|
||||||
result
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.shareReplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapChallengeToLang({ translations = {}, ...challenge }, lang) {
|
|
||||||
if (!supportedLanguages[lang]) {
|
|
||||||
lang = 'en';
|
|
||||||
}
|
|
||||||
const translation = translations[lang] || {};
|
|
||||||
if (lang !== 'en') {
|
|
||||||
challenge = {
|
|
||||||
...challenge,
|
|
||||||
...translation
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMapForLang(lang) {
|
|
||||||
return ({ entities: { challenge: challengeMap, ...entities }, result }) => {
|
|
||||||
entities.challenge = Object.keys(challengeMap)
|
|
||||||
.reduce((translatedChallengeMap, key) => {
|
|
||||||
translatedChallengeMap[key] = mapChallengeToLang(
|
|
||||||
challengeMap[key],
|
|
||||||
lang
|
|
||||||
);
|
|
||||||
return translatedChallengeMap;
|
|
||||||
}, {});
|
|
||||||
return { result, entities };
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldNotFilterComingSoon({ isComingSoon, isBeta: challengeIsBeta }) {
|
function shouldNotFilterComingSoon({ isComingSoon, isBeta: challengeIsBeta }) {
|
||||||
return isDev ||
|
return isDev ||
|
||||||
|
136
server/utils/map.js
Normal file
136
server/utils/map.js
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import { Observable } from 'rx';
|
||||||
|
import { Schema, valuesOf, arrayOf, normalize } from 'normalizr';
|
||||||
|
|
||||||
|
import { nameify } from '../utils';
|
||||||
|
import supportedLanguages from '../../common/utils/supported-languages';
|
||||||
|
|
||||||
|
const challenge = new Schema('challenge', { idAttribute: 'dashedName' });
|
||||||
|
const block = new Schema('block', { idAttribute: 'dashedName' });
|
||||||
|
const superBlock = new Schema('superBlock', { idAttribute: 'dashedName' });
|
||||||
|
|
||||||
|
block.define({
|
||||||
|
challenges: arrayOf(challenge)
|
||||||
|
});
|
||||||
|
|
||||||
|
superBlock.define({
|
||||||
|
blocks: arrayOf(block)
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapSchema = valuesOf(superBlock);
|
||||||
|
let mapObservableCache;
|
||||||
|
/*
|
||||||
|
* interface ChallengeMap {
|
||||||
|
* result: [superBlockDashedName: String]
|
||||||
|
* entities: {
|
||||||
|
* superBlock: {
|
||||||
|
* [superBlockDashedName: String]: {
|
||||||
|
* blocks: [blockDashedName: String]
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* block: {
|
||||||
|
* [blockDashedName: String]: {
|
||||||
|
* challenges: [challengeDashedName: String]
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* challenge: {
|
||||||
|
* [challengeDashedName: String]: Challenge
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export function cachedMap(Block) {
|
||||||
|
if (mapObservableCache) {
|
||||||
|
return mapObservableCache;
|
||||||
|
}
|
||||||
|
const query = {
|
||||||
|
include: 'challenges',
|
||||||
|
order: ['superOrder ASC', 'order ASC']
|
||||||
|
};
|
||||||
|
const map$ = Block.find$(query)
|
||||||
|
.flatMap(blocks => Observable.from(blocks.map(block => block.toJSON())))
|
||||||
|
.reduce((map, block) => {
|
||||||
|
if (map[block.superBlock]) {
|
||||||
|
map[block.superBlock].blocks.push(block);
|
||||||
|
} else {
|
||||||
|
map[block.superBlock] = {
|
||||||
|
title: _.startCase(block.superBlock),
|
||||||
|
order: block.superOrder,
|
||||||
|
name: nameify(_.startCase(block.superBlock)),
|
||||||
|
dashedName: block.superBlock,
|
||||||
|
blocks: [block],
|
||||||
|
message: block.superBlockMessage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}, {})
|
||||||
|
.map(map => normalize(map, mapSchema))
|
||||||
|
.map(map => {
|
||||||
|
// make sure challenges are in the right order
|
||||||
|
map.entities.block = Object.keys(map.entities.block)
|
||||||
|
// turn map into array
|
||||||
|
.map(key => map.entities.block[key])
|
||||||
|
// perform re-order
|
||||||
|
.map(block => {
|
||||||
|
block.challenges = block.challenges.reduce((accu, dashedName) => {
|
||||||
|
const index = map.entities.challenge[dashedName].suborder;
|
||||||
|
accu[index - 1] = dashedName;
|
||||||
|
return accu;
|
||||||
|
}, []);
|
||||||
|
return block;
|
||||||
|
})
|
||||||
|
// turn back into map
|
||||||
|
.reduce((blockMap, block) => {
|
||||||
|
blockMap[block.dashedName] = block;
|
||||||
|
return blockMap;
|
||||||
|
}, {});
|
||||||
|
return map;
|
||||||
|
})
|
||||||
|
.map(map => {
|
||||||
|
// re-order superBlocks result
|
||||||
|
const result = Object.keys(map.result).reduce((result, supName) => {
|
||||||
|
const index = map.entities.superBlock[supName].order;
|
||||||
|
result[index] = supName;
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
|
return {
|
||||||
|
...map,
|
||||||
|
result
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.shareReplay();
|
||||||
|
mapObservableCache = map$;
|
||||||
|
return map$;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapChallengeToLang(
|
||||||
|
{ translations = {}, ...challenge },
|
||||||
|
lang
|
||||||
|
) {
|
||||||
|
if (!supportedLanguages[lang]) {
|
||||||
|
lang = 'en';
|
||||||
|
}
|
||||||
|
const translation = translations[lang] || {};
|
||||||
|
if (lang !== 'en') {
|
||||||
|
challenge = {
|
||||||
|
...challenge,
|
||||||
|
...translation
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return challenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMapForLang(lang) {
|
||||||
|
return ({ entities: { challenge: challengeMap, ...entities }, result }) => {
|
||||||
|
entities.challenge = Object.keys(challengeMap)
|
||||||
|
.reduce((translatedChallengeMap, key) => {
|
||||||
|
translatedChallengeMap[key] = mapChallengeToLang(
|
||||||
|
challengeMap[key],
|
||||||
|
lang
|
||||||
|
);
|
||||||
|
return translatedChallengeMap;
|
||||||
|
}, {});
|
||||||
|
return { result, entities };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -64,20 +64,6 @@ block content
|
|||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
var cal = new CalHeatMap();
|
var cal = new CalHeatMap();
|
||||||
var calendar = !{JSON.stringify(calender)};
|
var calendar = !{JSON.stringify(calender)};
|
||||||
/*
|
|
||||||
var estUTCOffset = -5;
|
|
||||||
// moment returns the utc offset in minutes
|
|
||||||
var userUTCOffset = moment().utcOffset() / 60;
|
|
||||||
var secondsToOffset =
|
|
||||||
(estUTCOffset - userUTCOffset) * 3600;
|
|
||||||
var offsetCalendar = {};
|
|
||||||
for (var prop in calendar) {
|
|
||||||
if (calendar.hasOwnProperty(prop)) {
|
|
||||||
var offsetProp = prop + secondsToOffset;
|
|
||||||
offsetCalendar[offsetProp] = calendar[prop];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
cal.init({
|
cal.init({
|
||||||
itemSelector: "#cal-heatmap",
|
itemSelector: "#cal-heatmap",
|
||||||
domain: "month",
|
domain: "month",
|
||||||
@ -118,13 +104,13 @@ block content
|
|||||||
for challenge in projects
|
for challenge in projects
|
||||||
tr
|
tr
|
||||||
td.col-xs-5.hidden-xs
|
td.col-xs-5.hidden-xs
|
||||||
a(href='/challenges/' + removeOldTerms(challenge.name), target='_blank')= removeOldTerms(challenge.name)
|
a(href='/challenges/#{challenge.block}/#{challenge.dashedName}', target='_blank')= challenge.name
|
||||||
td.col-xs-2.hidden-xs= challenge.completedDate ? challenge.completedDate : 'Not Available'
|
td.col-xs-2.hidden-xs= challenge.completedDate ? challenge.completedDate : 'Not Available'
|
||||||
td.col-xs-2.hidden-xs= challenge.lastUpdated ? challenge.lastUpdated : ''
|
td.col-xs-2.hidden-xs= challenge.lastUpdated ? challenge.lastUpdated : ''
|
||||||
td.col-xs-2.hidden-xs
|
td.col-xs-2.hidden-xs
|
||||||
a(href=challenge.solution, target='_blank') View project
|
a(href=challenge.solution, target='_blank') View project
|
||||||
td.col-xs-12.visible-xs
|
td.col-xs-12.visible-xs
|
||||||
a(href=challenge.solution, target='_blank')= removeOldTerms(challenge.name)
|
a(href=challenge.solution, target='_blank')= challenge.name
|
||||||
if (algorithms.length > 0)
|
if (algorithms.length > 0)
|
||||||
.col-sm-12
|
.col-sm-12
|
||||||
table.table.table-striped
|
table.table.table-striped
|
||||||
@ -136,19 +122,19 @@ block content
|
|||||||
th.col-xs-2.hidden-xs Solution
|
th.col-xs-2.hidden-xs Solution
|
||||||
for challenge in algorithms
|
for challenge in algorithms
|
||||||
tr
|
tr
|
||||||
td.col-xs-5.hidden-xs= removeOldTerms(challenge.name)
|
td.col-xs-5.hidden-xs= challenge.name
|
||||||
td.col-xs-2.hidden-xs= challenge.completedDate ? challenge.completedDate : 'Not Available'
|
td.col-xs-2.hidden-xs= challenge.completedDate ? challenge.completedDate : 'Not Available'
|
||||||
td.col-xs-2.hidden-xs= challenge.lastUpdated ? challenge.lastUpdated : ''
|
td.col-xs-2.hidden-xs= challenge.lastUpdated ? challenge.lastUpdated : ''
|
||||||
td.col-xs-2.hidden-xs
|
td.col-xs-2.hidden-xs
|
||||||
if (challenge.solution)
|
if (challenge.solution)
|
||||||
a(href='/challenges/' + removeOldTerms(challenge.name) + '?solution=' + encodeURIComponent(encodeFcc(challenge.solution)), target='_blank') View solution
|
a(href='/challenges/#{challenge.block}/#{challenge.dashedName}?solution=#{encodeURIComponent(encodeFcc(challenge.solution))}', target='_blank') View solution
|
||||||
else
|
else
|
||||||
a(href='/challenges/' + removeOldTerms(challenge.name)) View this challenge
|
a(href='/challenges/#{challenge.block}/#{challenge.dashedName}') View this challenge
|
||||||
td.col-xs-12.visible-xs
|
td.col-xs-12.visible-xs
|
||||||
if (challenge.solution)
|
if (challenge.solution)
|
||||||
a(href='/challenges/' + removeOldTerms(challenge.name) + '?solution=' + encodeURIComponent(encodeFcc(challenge.solution)), target='_blank')= removeOldTerms(challenge.name)
|
a(href='/challenges/#{challenge.block}/#{challenge.dashedName}?solution=#{encodeURIComponent(encodeFcc(challenge.solution))}', target='_blank')= challenge.name
|
||||||
else
|
else
|
||||||
a(href='/challenges/' + removeOldTerms(challenge.name))= removeOldTerms(challenge.name)
|
a(href='/challenges/#{challenge.block}/#{challenge.dashedName}')= challenge.name
|
||||||
if (challenges.length > 0)
|
if (challenges.length > 0)
|
||||||
.col-sm-12
|
.col-sm-12
|
||||||
table.table.table-striped
|
table.table.table-striped
|
||||||
@ -160,20 +146,20 @@ block content
|
|||||||
th.col-xs-2.hidden-xs Solution
|
th.col-xs-2.hidden-xs Solution
|
||||||
for challenge in challenges
|
for challenge in challenges
|
||||||
tr
|
tr
|
||||||
td.col-xs-5.hidden-xs= removeOldTerms(challenge.name)
|
td.col-xs-5.hidden-xs= challenge.name
|
||||||
td.col-xs-2.hidden-xs= challenge.completedDate ? challenge.completedDate : 'Not Available'
|
td.col-xs-2.hidden-xs= challenge.completedDate ? challenge.completedDate : 'Not Available'
|
||||||
td.col-xs-2.hidden-xs= challenge.lastUpdated ? challenge.lastUpdated : ''
|
td.col-xs-2.hidden-xs= challenge.lastUpdated ? challenge.lastUpdated : ''
|
||||||
td.col-xs-2.hidden-xs
|
td.col-xs-2.hidden-xs
|
||||||
if (challenge.solution && challenge.name)
|
if (challenge.solution && challenge.name)
|
||||||
a(href='/challenges/' + removeOldTerms(challenge.name) + '?solution=' + encodeURIComponent(encodeFcc(challenge.solution)), target='_blank') View solution
|
a(href='/challenges/#{challenge.block}/#{challenge.dashedName}?solution=#{encodeURIComponent(encodeFcc(challenge.solution))}', target='_blank') View solution
|
||||||
else if (challenge.name)
|
else if (challenge.name)
|
||||||
a(href='/challenges/' + removeOldTerms(challenge.name)) View this challenge
|
a(href='/challenges/#{challenge.block}/#{challenge.dashedName}') View this challenge
|
||||||
else
|
else
|
||||||
span N/A
|
span N/A
|
||||||
td.col-xs-12.visible-xs
|
td.col-xs-12.visible-xs
|
||||||
if (challenge.solution && challenge.name)
|
if (challenge.solution && challenge.name)
|
||||||
a(href='/challenges/' + removeOldTerms(challenge.name) + '?solution=' + encodeURIComponent(encodeFcc(challenge.solution)), target='_blank')= removeOldTerms(challenge.name)
|
a(href='/challenges/#{challenge.block}/#{challenge.dashedName}?solution=#{encodeURIComponent(encodeFcc(challenge.solution))}', target='_blank')= challenge.name
|
||||||
else if (challenge.name)
|
else if (challenge.name)
|
||||||
a(href='/challenges/' + removeOldTerms(challenge.name))= removeOldTerms(challenge.name)
|
a(href='/challenges/#{challenge.block}/#{challenge.dashedName}')= challenge.name
|
||||||
else
|
else
|
||||||
span N/A
|
span N/A
|
||||||
|
Reference in New Issue
Block a user