220
.eslintrc
Normal file
220
.eslintrc
Normal file
@ -0,0 +1,220 @@
|
||||
{
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"mocha": true,
|
||||
"node": true
|
||||
},
|
||||
"globals": {
|
||||
"window": true,
|
||||
"$": true,
|
||||
"ga": true,
|
||||
"jQuery": true
|
||||
},
|
||||
"rules": {
|
||||
"no-comma-dangle": 2,
|
||||
"no-cond-assign": 2,
|
||||
"no-console": 0,
|
||||
"no-constant-condition": 2,
|
||||
"no-control-regex": 2,
|
||||
"no-debugger": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-empty": 2,
|
||||
"no-empty-class": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-parens": 0,
|
||||
"no-extra-semi": 2,
|
||||
"no-func-assign": 2,
|
||||
"no-inner-declarations": 2,
|
||||
"no-invalid-regexp": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-reserved-keys": 0,
|
||||
"no-sparse-arrays": 2,
|
||||
"no-unreachable": 2,
|
||||
"use-isnan": 2,
|
||||
"valid-jsdoc": 2,
|
||||
"valid-typeof": 2,
|
||||
|
||||
"block-scoped-var": 2,
|
||||
"complexity": 0,
|
||||
"consistent-return": 2,
|
||||
"curly": 2,
|
||||
"default-case": 1,
|
||||
"dot-notation": 0,
|
||||
"eqeqeq": 1,
|
||||
"guard-for-in": 1,
|
||||
"no-alert": 1,
|
||||
"no-caller": 2,
|
||||
"no-div-regex": 2,
|
||||
"no-else-return": 0,
|
||||
"no-empty-label": 2,
|
||||
"no-eq-null": 1,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-fallthrough": 2,
|
||||
"no-floating-decimal": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-iterator": 2,
|
||||
"no-labels": 2,
|
||||
"no-lone-blocks": 2,
|
||||
"no-loop-func": 1,
|
||||
"no-multi-spaces": 1,
|
||||
"no-multi-str": 2,
|
||||
"no-native-reassign": 2,
|
||||
"no-new": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-octal": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-process-env": 0,
|
||||
"no-proto": 2,
|
||||
"no-redeclare": 1,
|
||||
"no-return-assign": 2,
|
||||
"no-script-url": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-unused-expressions": 2,
|
||||
"no-void": 1,
|
||||
"no-warning-comments": [
|
||||
1,
|
||||
{
|
||||
"terms": [
|
||||
"fixme"
|
||||
],
|
||||
"location": "start"
|
||||
}
|
||||
],
|
||||
"no-with": 2,
|
||||
"radix": 2,
|
||||
"vars-on-top": 0,
|
||||
"wrap-iife": [2, "any"],
|
||||
"yoda": 0,
|
||||
|
||||
"strict": 0,
|
||||
|
||||
"no-catch-shadow": 2,
|
||||
"no-delete-var": 2,
|
||||
"no-label-var": 2,
|
||||
"no-shadow": 0,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-undef": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-undefined": 1,
|
||||
"no-unused-vars": 2,
|
||||
"no-use-before-define": 0,
|
||||
|
||||
"handle-callback-err": 2,
|
||||
"no-mixed-requires": 0,
|
||||
"no-new-require": 2,
|
||||
"no-path-concat": 2,
|
||||
"no-process-exit": 2,
|
||||
"no-restricted-modules": 0,
|
||||
"no-sync": 0,
|
||||
|
||||
"brace-style": [
|
||||
2,
|
||||
"1tbs",
|
||||
{ "allowSingleLine": true }
|
||||
],
|
||||
"camelcase": 1,
|
||||
"comma-spacing": [
|
||||
2,
|
||||
{
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"comma-style": [
|
||||
2, "last"
|
||||
],
|
||||
"consistent-this": 0,
|
||||
"eol-last": 2,
|
||||
"func-names": 0,
|
||||
"func-style": 0,
|
||||
"key-spacing": [
|
||||
2,
|
||||
{
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}
|
||||
],
|
||||
"max-nested-callbacks": 0,
|
||||
"new-cap": 0,
|
||||
"new-parens": 2,
|
||||
"no-array-constructor": 2,
|
||||
"no-inline-comments": 1,
|
||||
"no-lonely-if": 1,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multiple-empty-lines": [
|
||||
1,
|
||||
{ "max": 2 }
|
||||
],
|
||||
"no-nested-ternary": 2,
|
||||
"no-new-object": 2,
|
||||
"no-space-before-semi": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-ternary": 0,
|
||||
"no-trailing-spaces": 1,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-wrap-func": 2,
|
||||
"one-var": 0,
|
||||
"operator-assignment": 0,
|
||||
"padded-blocks": 0,
|
||||
"quote-props": 0,
|
||||
"quotes": [
|
||||
2,
|
||||
"single",
|
||||
"avoid-escape"
|
||||
],
|
||||
"semi": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"sort-vars": 0,
|
||||
"space-after-keywords": [
|
||||
2,
|
||||
"always",
|
||||
{ "checkFunctionKeyword": false }
|
||||
],
|
||||
"space-after-function-names": "never",
|
||||
"space-before-blocks": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"space-in-brackets": 0,
|
||||
"space-in-parens": 0,
|
||||
"space-infix-ops": 2,
|
||||
"space-return-throw-case": 2,
|
||||
"space-unary-ops": [
|
||||
1,
|
||||
{
|
||||
"words": true,
|
||||
"nonwords": false
|
||||
}
|
||||
],
|
||||
"spaced-line-comment": [
|
||||
2,
|
||||
"always",
|
||||
{ "exceptions": ["-"] }
|
||||
],
|
||||
"wrap-regex": 1,
|
||||
|
||||
"max-depth": 0,
|
||||
"max-len": [
|
||||
1,
|
||||
80,
|
||||
2
|
||||
],
|
||||
"max-params": 0,
|
||||
"max-statements": 0,
|
||||
"no-bitwise": 1,
|
||||
"no-plusplus": 0
|
||||
}
|
||||
}
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -23,3 +23,5 @@ node_modules
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
bower_components
|
||||
.eslintignore
|
||||
.eslintrc
|
||||
|
92
app.js
92
app.js
@ -34,6 +34,11 @@ var express = require('express'),
|
||||
bonfireController = require('./controllers/bonfire'),
|
||||
coursewareController = require('./controllers/courseware'),
|
||||
|
||||
/**
|
||||
* Stories
|
||||
*/
|
||||
storyController = require('./controllers/story');
|
||||
|
||||
/**
|
||||
* User model
|
||||
*/
|
||||
@ -219,6 +224,7 @@ app.get('/chat', resourcesController.chat);
|
||||
app.get('/live-pair-programming', resourcesController.livePairProgramming);
|
||||
app.get('/install-screenhero', resourcesController.installScreenHero);
|
||||
app.get('/javascript-in-your-inbox', resourcesController.javaScriptInYourInbox);
|
||||
app.get('/guide-to-our-nonprofit-projects', resourcesController.guideToOurNonprofitProjects);
|
||||
app.get('/chromebook', resourcesController.chromebook);
|
||||
app.get('/deploy-a-website', resourcesController.deployAWebsite);
|
||||
app.get('/gmail-shortcuts', resourcesController.gmailShortcuts);
|
||||
@ -272,6 +278,92 @@ app.post(
|
||||
userController.updateProgress
|
||||
);
|
||||
|
||||
/**
|
||||
* Main routes.
|
||||
*/
|
||||
app.get(
|
||||
'/stories/hotStories',
|
||||
storyController.hotJSON
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/stories/recentStories',
|
||||
storyController.recentJSON
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/stories/',
|
||||
function(req, res) {
|
||||
res.redirect(302, '/stories/hot');
|
||||
}
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/stories/comments/:id',
|
||||
storyController.comments
|
||||
);
|
||||
|
||||
app.post(
|
||||
'/stories/comment/',
|
||||
storyController.commentSubmit
|
||||
);
|
||||
|
||||
app.post(
|
||||
'/stories/comment/:id/comment',
|
||||
storyController.commentOnCommentSubmit
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/stories/submit',
|
||||
storyController.submitNew
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/stories/submit/:newStory',
|
||||
storyController.preSubmit
|
||||
);
|
||||
|
||||
app.post(
|
||||
'/stories/preliminary',
|
||||
storyController.newStory
|
||||
);
|
||||
|
||||
app.post(
|
||||
'/stories/',
|
||||
storyController.storySubmission
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/stories/hot',
|
||||
storyController.hot
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/stories/recent',
|
||||
storyController.recent
|
||||
);
|
||||
|
||||
|
||||
app.get(
|
||||
'/stories/search',
|
||||
storyController.search
|
||||
);
|
||||
|
||||
app.post(
|
||||
'/stories/search',
|
||||
storyController.getStories
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/stories/:storyName',
|
||||
storyController.returnIndividualStory
|
||||
);
|
||||
|
||||
app.post(
|
||||
'/stories/upvote/',
|
||||
storyController.upvote
|
||||
);
|
||||
|
||||
/**
|
||||
* Challenge related routes
|
||||
*/
|
||||
|
@ -1,6 +1,8 @@
|
||||
var User = require('../models/User'),
|
||||
Challenge = require('./../models/Challenge'),
|
||||
Bonfire = require('./../models/Bonfire'),
|
||||
Story = require('./../models/Story'),
|
||||
Comment = require('./../models/Comment'),
|
||||
resources = require('./resources.json'),
|
||||
questions = resources.questions,
|
||||
steps = resources.steps,
|
||||
@ -8,9 +10,11 @@ var User = require('../models/User'),
|
||||
bonfires = require('../seed_data/bonfires.json'),
|
||||
coursewares = require('../seed_data/coursewares.json'),
|
||||
moment = require('moment'),
|
||||
Client = require('node-rest-client').Client,
|
||||
client = new Client(),
|
||||
debug = require('debug')('freecc:cntr:bonfires');
|
||||
https = require('https'),
|
||||
debug = require('debug')('freecc:cntr:resources'),
|
||||
cheerio = require('cheerio'),
|
||||
request = require('request'),
|
||||
R = require('ramda');
|
||||
|
||||
/**
|
||||
* GET /
|
||||
@ -24,33 +28,6 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
stats: function stats(req, res) {
|
||||
var date1 = new Date("10/15/2014");
|
||||
var date2 = new Date();
|
||||
var timeDiff = Math.abs(date2.getTime() - date1.getTime());
|
||||
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
|
||||
client.get('https://trello.com/1/boards/BA3xVpz9/cards?key=' + secrets.trello.key, function(trello, response) {
|
||||
var nonprofitProjects = (trello && trello.length) || 15;
|
||||
User.count({'points': {'$gt': 2}}, function(err, c3) { if (err) { debug('User err: ', err); next(err); }
|
||||
User.count({'points': {'$gt': 9}}, function(err, c10) { if (err) { debug('User err: ', err); next(err); }
|
||||
User.count({'points': {'$gt': 29}}, function(err, c30) { if (err) { debug('User err: ', err); next(err); }
|
||||
User.count({'points': {'$gt': 53}}, function(err, all) { if (err) { debug('User err: ', err); next(err); }
|
||||
res.render('resources/stats', {
|
||||
title: 'Free Code Camp Stats:',
|
||||
daysRunning: daysRunning,
|
||||
nonprofitProjects: nonprofitProjects,
|
||||
c3: c3,
|
||||
c10: c10,
|
||||
c30: c30,
|
||||
all: all
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
sitemap: function sitemap(req, res, next) {
|
||||
var appUrl = 'http://www.freecodecamp.com';
|
||||
var now = moment(new Date).format('YYYY-MM-DD');
|
||||
@ -109,6 +86,12 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
guideToOurNonprofitProjects: function guideToOurNonprofitProjects(req, res) {
|
||||
res.render('resources/guide-to-our-nonprofit-projects', {
|
||||
title: 'A guide to our Nonprofit Projects'
|
||||
});
|
||||
},
|
||||
|
||||
controlShortcuts: function controlShortcuts(req, res) {
|
||||
res.render('resources/control-shortcuts', {
|
||||
title: 'These Control Shortcuts will save you Hours'
|
||||
@ -146,22 +129,27 @@ module.exports = {
|
||||
},
|
||||
githubCalls: function(req, res) {
|
||||
var githubHeaders = {headers: {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36'}, port:80 };
|
||||
client.get('https://api.github.com/repos/freecodecamp/freecodecamp/pulls?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function(pulls, res3) {
|
||||
request('https://api.github.com/repos/freecodecamp/freecodecamp/pulls?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function(err, status1, pulls) {
|
||||
pulls = pulls ? Object.keys(JSON.parse(pulls)).length : "Can't connect to github";
|
||||
client.get('https://api.github.com/repos/freecodecamp/freecodecamp/issues?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function (issues, res4) {
|
||||
debug('pulls', pulls);
|
||||
request('https://api.github.com/repos/freecodecamp/freecodecamp/issues?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function (err, status2, issues) {
|
||||
debug('issues', issues);
|
||||
issues = ((pulls === parseInt(pulls)) && issues) ? Object.keys(JSON.parse(issues)).length - pulls : "Can't connect to GitHub";
|
||||
res.send({"issues": issues, "pulls" : pulls});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
|
||||
trelloCalls: function(req, res) {
|
||||
client.get('https://trello.com/1/boards/BA3xVpz9/cards?key=' + secrets.trello.key, function(trello, res2) {
|
||||
request('https://trello.com/1/boards/BA3xVpz9/cards?key=' + secrets.trello.key, function(err, status, trello) {
|
||||
trello = trello ? (JSON.parse(trello)).length : "Can't connect to to Trello";
|
||||
res.send({"trello": trello});
|
||||
});
|
||||
},
|
||||
bloggerCalls: function(req, res) {
|
||||
client.get('https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/posts?key=' + secrets.blogger.key, function (blog, res5) {
|
||||
request('https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/posts?key=' + secrets.blogger.key, function (err, status, blog) {
|
||||
var blog = blog.length > 100 ? JSON.parse(blog) : "";
|
||||
res.send({
|
||||
blog1Title: blog ? blog["items"][0]["title"] : "Can't connect to Blogger",
|
||||
@ -262,7 +250,7 @@ module.exports = {
|
||||
return {
|
||||
_id: elem._id,
|
||||
difficulty: elem.difficulty
|
||||
}
|
||||
};
|
||||
})
|
||||
.sort(function(a, b) {
|
||||
return a.difficulty - b.difficulty;
|
||||
@ -276,7 +264,7 @@ module.exports = {
|
||||
return {
|
||||
name: elem.name,
|
||||
difficulty: elem.difficulty
|
||||
}
|
||||
};
|
||||
})
|
||||
.sort(function(a, b) {
|
||||
return a.difficulty - b.difficulty;
|
||||
@ -287,9 +275,64 @@ module.exports = {
|
||||
},
|
||||
whichEnvironment: function() {
|
||||
return process.env.NODE_ENV;
|
||||
},
|
||||
getURLTitle: function(url, callback) {
|
||||
|
||||
(function () {
|
||||
var result = {title: ''};
|
||||
request(url, function (error, response, body) {
|
||||
if (!error && response.statusCode === 200) {
|
||||
var $ = cheerio.load(body);
|
||||
var title = $('title').text();
|
||||
result.title = title;
|
||||
debug('calling callback with', result);
|
||||
callback(null, result);
|
||||
} else {
|
||||
callback('failed');
|
||||
}
|
||||
});
|
||||
})();
|
||||
},
|
||||
updateUserStoryPictures: function(userId, picture) {
|
||||
|
||||
var counter = 0,
|
||||
foundStories,
|
||||
foundComments;
|
||||
|
||||
Story.find({'author.userId': userId}, function(err, stories) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
foundStories = stories;
|
||||
counter++;
|
||||
saveStoriesAndComments();
|
||||
});
|
||||
Comment.find({'author.userId': userId}, function(err, comments) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
foundComments = comments;
|
||||
counter++;
|
||||
saveStoriesAndComments();
|
||||
});
|
||||
|
||||
function saveStoriesAndComments() {
|
||||
if (counter !== 2) {
|
||||
return;
|
||||
}
|
||||
R.forEach(function(comment) {
|
||||
comment.author.picture = picture;
|
||||
comment.markModified('author');
|
||||
comment.save();
|
||||
}, foundComments);
|
||||
|
||||
R.forEach(function(story) {
|
||||
story.author.picture = picture;
|
||||
debug('This is a story', story);
|
||||
debug(story.author.picture);
|
||||
story.markModified('author');
|
||||
story.save();
|
||||
}, foundStories);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
347
controllers/story.js
Normal file
347
controllers/story.js
Normal file
@ -0,0 +1,347 @@
|
||||
var R = require('ramda'),
|
||||
debug = require('debug')('freecc:cntr:story'),
|
||||
Story = require('./../models/Story'),
|
||||
Comment = require('./../models/Comment'),
|
||||
User = require('./../models/User'),
|
||||
moment = require('../public/js/lib/moment/moment.js'),
|
||||
resources = require('./resources'),
|
||||
mongodb = require('mongodb'),
|
||||
MongoClient = mongodb.MongoClient,
|
||||
secrets = require('../config/secrets'),
|
||||
User = require('./../models/User');
|
||||
|
||||
function hotRank(timeValue, rank, headline) {
|
||||
/*
|
||||
* Hotness ranking algorithm: http://amix.dk/blog/post/19588
|
||||
* tMS = postedOnDate - foundationTime;
|
||||
* Ranking...
|
||||
* f(ts, 1, rank) = log(10)z + (ts)/45000;
|
||||
*/
|
||||
var hotness;
|
||||
var z = Math.log(rank) / Math.log(10);
|
||||
hotness = z + (timeValue / 45000000);
|
||||
return hotness;
|
||||
|
||||
}
|
||||
|
||||
exports.hotJSON = function(req, res, next) {
|
||||
var story = Story.find({}).sort({'timePosted': -1}).limit(1000);
|
||||
story.exec(function(err, stories) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var foundationDate = 1413298800000;
|
||||
|
||||
var sliceVal = stories.length >= 100 ? 100 : stories.length;
|
||||
var rankedStories = stories;
|
||||
return res.json(rankedStories.map(function(elem) {
|
||||
return elem;
|
||||
}).sort(function(a, b) {
|
||||
debug('a rank and b rank', hotRank(a.timePosted - foundationDate, a.rank, a.headline), hotRank(b.timePosted - foundationDate, b.rank, b.headline));
|
||||
return hotRank(b.timePosted - foundationDate, b.rank, b.headline) - hotRank(a.timePosted - foundationDate, a.rank, a.headline);
|
||||
}).slice(0, sliceVal));
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
exports.recentJSON = function(req, res, next) {
|
||||
var story = Story.find({}).sort({'timePosted': -1}).limit(100);
|
||||
story.exec(function(err, stories) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.json(stories);
|
||||
});
|
||||
};
|
||||
|
||||
exports.hot = function(req, res, next) {
|
||||
res.render('stories/index', {
|
||||
page: 'hot'
|
||||
});
|
||||
};
|
||||
|
||||
exports.submitNew = function(req,res, next) {
|
||||
res.render('stories/index', {
|
||||
page: 'submit'
|
||||
});
|
||||
};
|
||||
|
||||
exports.search = function(req, res, next) {
|
||||
res.render('stories/index', {
|
||||
page: 'search'
|
||||
});
|
||||
};
|
||||
|
||||
exports.recent = function(req, res, next) {
|
||||
res.render('stories/index', {
|
||||
page: 'recent'
|
||||
});
|
||||
};
|
||||
|
||||
exports.preSubmit = function(req, res, next) {
|
||||
|
||||
var data = req.params.newStory;
|
||||
|
||||
|
||||
data = data.replace(/url=/gi, '').replace(/&title=/gi, ',').split(',');
|
||||
var url = data[0];
|
||||
var title = data[1];
|
||||
res.render('stories/index', {
|
||||
page: 'storySubmission',
|
||||
storyURL: url,
|
||||
storyTitle: title
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
exports.returnIndividualStory = function(req, res, next) {
|
||||
var dashedName = req.params.storyName;
|
||||
|
||||
var storyName = dashedName.replace(/\-/g, ' ');
|
||||
|
||||
Story.find({'storyLink' : new RegExp(storyName, 'i')}, function(err, story) {
|
||||
if (err) {
|
||||
next(err);
|
||||
}
|
||||
|
||||
|
||||
if (story.length < 1) {
|
||||
req.flash('errors', {
|
||||
msg: "404: We couldn't find a story with that name. Please double check the name."
|
||||
});
|
||||
|
||||
return res.redirect('/stories/');
|
||||
}
|
||||
|
||||
story = story.pop();
|
||||
var dashedNameFull = story.storyLink.toLowerCase().replace(/\s/g, '-');
|
||||
if (dashedNameFull !== dashedName) {
|
||||
return res.redirect('../stories/' + dashedNameFull);
|
||||
}
|
||||
|
||||
res.render('stories/index', {
|
||||
title: story.headline,
|
||||
link: story.link,
|
||||
author: story.author,
|
||||
description: story.description,
|
||||
rank: story.upVotes.length,
|
||||
upVotes: story.upVotes,
|
||||
comments: story.comments,
|
||||
id: story._id,
|
||||
user: req.user,
|
||||
timeAgo: moment(story.timePosted).fromNow(),
|
||||
image: story.image,
|
||||
page: 'show'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.getStories = function(req, res, next) {
|
||||
MongoClient.connect(secrets.db, function(err, database) {
|
||||
var db = database;
|
||||
db.collection('stories').find({
|
||||
"$text": {
|
||||
"$search": req.body.data.searchValue
|
||||
}
|
||||
}, {
|
||||
headline: 1,
|
||||
timePosted: 1,
|
||||
link: 1,
|
||||
description: 1,
|
||||
rank: 1,
|
||||
upVotes: 1,
|
||||
author: 1,
|
||||
comments: 1,
|
||||
image: 1,
|
||||
storyLink: 1,
|
||||
textScore: {
|
||||
$meta: "textScore"
|
||||
}
|
||||
}, {
|
||||
sort: {
|
||||
textScore: {
|
||||
$meta: "textScore"
|
||||
}
|
||||
}
|
||||
}).toArray(function(err, items) {
|
||||
if (items !== null && items.length !== 0) {
|
||||
return res.json(items);
|
||||
}
|
||||
return res.status(404);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.upvote = function(req, res, next) {
|
||||
var data = req.body.data;
|
||||
Story.find({'_id': data.id}, function(err, story) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
story = story.pop();
|
||||
story.rank++;
|
||||
story.upVotes.push(
|
||||
{
|
||||
upVotedBy: data.upVoter._id,
|
||||
upVotedByUsername: data.upVoter.profile.username
|
||||
}
|
||||
);
|
||||
story.markModified('rank');
|
||||
story.save();
|
||||
return res.send(story);
|
||||
});
|
||||
};
|
||||
|
||||
exports.comments = function(req, res, next) {
|
||||
var data = req.params.id;
|
||||
Comment.find({'_id': data}, function(err, comment) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
comment = comment.pop();
|
||||
return res.send(comment);
|
||||
});
|
||||
};
|
||||
|
||||
exports.newStory = function(req, res, next) {
|
||||
var url = req.body.data.url;
|
||||
if (url.search(/^https?:\/\//g) === -1) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
debug('In pre submit with a url', url);
|
||||
|
||||
Story.find({'link': url}, function(err, story) {
|
||||
debug('Attempting to find a story');
|
||||
if (err) {
|
||||
debug('oops');
|
||||
return res.status(500);
|
||||
}
|
||||
if (story.length) {
|
||||
debug('Found a story already, here\'s the return from find', story);
|
||||
req.flash('errors', {
|
||||
msg: "Someone's already posted that link. Here's the discussion."
|
||||
});
|
||||
debug('Redirecting the user with', story[0].storyLink);
|
||||
return res.json({
|
||||
alreadyPosted: true,
|
||||
storyURL: story.pop().storyLink
|
||||
});
|
||||
}
|
||||
resources.getURLTitle(url, processResponse);
|
||||
});
|
||||
|
||||
function processResponse(err, storyTitle) {
|
||||
if (err) {
|
||||
res.json({
|
||||
alreadyPosted: false,
|
||||
storyURL: url,
|
||||
storyTitle: ''
|
||||
});
|
||||
} else {
|
||||
storyTitle = storyTitle ? storyTitle : '';
|
||||
res.json({
|
||||
alreadyPosted: false,
|
||||
storyURL: url,
|
||||
storyTitle: storyTitle.title
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.storySubmission = function(req, res, next) {
|
||||
var data = req.body.data;
|
||||
var storyLink = data.headline
|
||||
.replace(/\'/g, '')
|
||||
.replace(/\"/g, '')
|
||||
.replace(/,/g, '')
|
||||
.replace(/[^a-z0-9]/gi, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.toLowerCase();
|
||||
var link = data.link;
|
||||
if (link.search(/^https?:\/\//g) === -1) {
|
||||
link = 'http://' + link;
|
||||
}
|
||||
var story = new Story({
|
||||
headline: data.headline,
|
||||
timePosted: Date.now(),
|
||||
link: link,
|
||||
description: data.description,
|
||||
rank: 1,
|
||||
upVotes: data.upVotes,
|
||||
author: data.author,
|
||||
comments: [],
|
||||
image: data.image,
|
||||
storyLink: storyLink
|
||||
});
|
||||
|
||||
story.save(function(err, data) {
|
||||
if (err) {
|
||||
return res.status(500);
|
||||
}
|
||||
res.send(JSON.stringify({
|
||||
storyLink: story.storyLink.replace(/\s/g, '-').toLowerCase()
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
exports.commentSubmit = function(req, res, next) {
|
||||
debug('comment submit fired');
|
||||
var data = req.body.data;
|
||||
var comment = new Comment({
|
||||
associatedPost: data.associatedPost,
|
||||
body: data.body,
|
||||
rank: 0,
|
||||
upvotes: 0,
|
||||
author: data.author,
|
||||
comments: [],
|
||||
topLevel: true,
|
||||
commentOn: Date.now()
|
||||
});
|
||||
commentSave(comment, Story, res);
|
||||
};
|
||||
|
||||
exports.commentOnCommentSubmit = function(req, res, next) {
|
||||
debug('comment on comment submit');
|
||||
var idToFind = req.params.id;
|
||||
var data = req.body.data;
|
||||
var comment = new Comment({
|
||||
associatedPost: data.associatedPost,
|
||||
body: data.body,
|
||||
rank: 0,
|
||||
upvotes: 0,
|
||||
author: data.author,
|
||||
comments: [],
|
||||
topLevel: false,
|
||||
commentOn: Date.now()
|
||||
});
|
||||
commentSave(comment, Comment, res);
|
||||
};
|
||||
|
||||
function commentSave(comment, Context, res) {
|
||||
comment.save(function(err, data) {
|
||||
if (err) {
|
||||
return res.status(500);
|
||||
}
|
||||
try {
|
||||
Context.find({'_id': comment.associatedPost}, function (err, associatedStory) {
|
||||
if (err) {
|
||||
return res.status(500);
|
||||
}
|
||||
associatedStory = associatedStory.pop();
|
||||
if (associatedStory) {
|
||||
associatedStory.comments.push(data._id);
|
||||
associatedStory.save(function (err, data) {
|
||||
if (err) {
|
||||
res.status(500);
|
||||
}
|
||||
res.send(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// delete comment
|
||||
return res.status(500);
|
||||
}
|
||||
});
|
||||
}
|
@ -7,7 +7,8 @@ var _ = require('lodash'),
|
||||
secrets = require('../config/secrets'),
|
||||
moment = require('moment'),
|
||||
Challenge = require('./../models/Challenge'),
|
||||
debug = require('debug')('freecc:cntr:challenges');
|
||||
debug = require('debug')('freecc:cntr:challenges')
|
||||
resources = require('./resources');
|
||||
|
||||
//TODO(Berks): Refactor to use module.exports = {} pattern.
|
||||
|
||||
@ -314,7 +315,7 @@ exports.postUpdateProfile = function(req, res, next) {
|
||||
return next(err);
|
||||
}
|
||||
var user = req.user;
|
||||
if (existingUsername && existingUsername.profile.username != user.profile.username) {
|
||||
if (existingUsername && existingUsername.profile.username !== user.profile.username) {
|
||||
req.flash('errors', {
|
||||
msg: 'An account with that username already exists.'
|
||||
});
|
||||
@ -330,7 +331,7 @@ exports.postUpdateProfile = function(req, res, next) {
|
||||
user.profile.codepenProfile = req.body.codepenProfile.trim() || '';
|
||||
user.profile.twitterHandle = req.body.twitterHandle.trim() || '';
|
||||
user.profile.bio = req.body.bio.trim() || '';
|
||||
user.profile.picture = req.body.picture.trim() || '';
|
||||
user.profile.picture = req.body.picture.trim() || 'https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png';
|
||||
user.portfolio.website1Title = req.body.website1Title.trim() || '';
|
||||
user.portfolio.website1Link = req.body.website1Link.trim() || '';
|
||||
user.portfolio.website1Image = req.body.website1Image.trim() || '';
|
||||
@ -343,9 +344,12 @@ exports.postUpdateProfile = function(req, res, next) {
|
||||
|
||||
|
||||
user.save(function (err) {
|
||||
if (err) return next(err);
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
req.flash('success', {msg: 'Profile information updated.'});
|
||||
res.redirect('/account');
|
||||
resources.updateUserStoryPictures(user._id.toString(), user.profile.picture);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
39
models/Comment.js
Normal file
39
models/Comment.js
Normal file
@ -0,0 +1,39 @@
|
||||
var mongoose = require('mongoose');
|
||||
var secrets = require('../config/secrets');
|
||||
|
||||
var commentSchema = new mongoose.Schema({
|
||||
associatedPost: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
body: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
rank: {
|
||||
type: Number,
|
||||
default: -Infinity
|
||||
},
|
||||
upvotes: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
author: {},
|
||||
comments: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
commentOn: {
|
||||
type: Number,
|
||||
default: Date.now()
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('Comment', commentSchema);
|
||||
|
||||
/*
|
||||
author: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
},
|
||||
*/
|
58
models/Story.js
Normal file
58
models/Story.js
Normal file
@ -0,0 +1,58 @@
|
||||
var mongoose = require('mongoose');
|
||||
var secrets = require('../config/secrets');
|
||||
|
||||
var storySchema = new mongoose.Schema({
|
||||
headline: {
|
||||
type: String,
|
||||
unique: false
|
||||
},
|
||||
timePosted: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
link: {
|
||||
type: String,
|
||||
unique: false
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
unique: false
|
||||
},
|
||||
rank: {
|
||||
type: Number,
|
||||
default: -Infinity
|
||||
},
|
||||
upVotes: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
author: {},
|
||||
comments: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
storyLink: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
storySchema.pre('save', function(next) {
|
||||
console.log('pre save test');
|
||||
next();
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('Story', storySchema);
|
||||
|
||||
/*
|
||||
author: {
|
||||
userId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
}
|
||||
},
|
||||
*/
|
@ -42,11 +42,12 @@
|
||||
"lusca": "^1.0.2",
|
||||
"method-override": "^2.3.0",
|
||||
"moment": "^2.8.4",
|
||||
"mongodb": "^1.4.33",
|
||||
"mongoose": "^3.8.19",
|
||||
"mongoose-text-search": "0.0.2",
|
||||
"morgan": "^1.5.0",
|
||||
"newrelic": "^1.13.3",
|
||||
"node": "0.0.0",
|
||||
"node-rest-client": "^1.4.3",
|
||||
"nodemailer": "^1.3.0",
|
||||
"passport": "^0.2.1",
|
||||
"passport-facebook": "^1.0.3",
|
||||
@ -57,7 +58,7 @@
|
||||
"passport-oauth": "^1.0.0",
|
||||
"passport-twitter": "^1.0.2",
|
||||
"ramda": "^0.10.0",
|
||||
"request": "^2.49.0",
|
||||
"request": "^2.53.0",
|
||||
"sitemap": "^0.7.4",
|
||||
"uglify-js": "^2.4.15",
|
||||
"validator": "^3.22.1",
|
||||
|
@ -27,6 +27,8 @@ li, .wrappable {
|
||||
html {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
// hack to prevent horizontal overflow problem on showHTML view
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body.full-screen-body-background {
|
||||
@ -171,6 +173,18 @@ ul {
|
||||
.responsive-container { position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden; }
|
||||
.responsive-container iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
|
||||
|
||||
.positive-5 {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.positive-10 {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.positive-15 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.negative-45 {
|
||||
margin-top: -45px;
|
||||
margin-bottom: -45px;
|
||||
@ -180,10 +194,18 @@ ul {
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
.negative-28 {
|
||||
margin-top: -28px;
|
||||
}
|
||||
|
||||
.negative-35 {
|
||||
margin-top: -35px;
|
||||
}
|
||||
|
||||
.negative-30 {
|
||||
margin-top: -30px;
|
||||
}
|
||||
|
||||
.negative-5 {
|
||||
margin-top: -5px;
|
||||
}
|
||||
@ -192,6 +214,10 @@ ul {
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
.negative-20 {
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.landing-p {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
@ -383,6 +409,26 @@ ul {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.big-text-field {
|
||||
font-size: 30px;
|
||||
height: 57px;
|
||||
}
|
||||
|
||||
.btn-responsive {
|
||||
@media (max-width: 768px) {
|
||||
font-size: 15px;
|
||||
height: 26.5px;
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.field-responsive {
|
||||
@media (max-width: 768px) {
|
||||
font-size: 15px;
|
||||
height: 26.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.table {
|
||||
margin-left: -16px;
|
||||
}
|
||||
@ -719,7 +765,6 @@ iframe.iphone {
|
||||
-ms-transition: background .2s ease-in-out, border .2s ease-in-out;
|
||||
-o-transition: background .2s ease-in-out, border .2s ease-in-out;
|
||||
transition: background .2s ease-in-out, border .2s ease-in-out;
|
||||
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
@ -744,6 +789,56 @@ iframe.iphone {
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
|
||||
.story-list {
|
||||
padding-bottom: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.big-ion-up-arrow {
|
||||
font-size: 45px;
|
||||
margin-top: -10px;
|
||||
margin-bottom: -15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.story-up-votes {
|
||||
padding-top: 0px;
|
||||
margin-left: -5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.control-label .control-label-story-submission {
|
||||
telt-align: left;
|
||||
}
|
||||
|
||||
.img-story-post {
|
||||
max-width: 110px;
|
||||
max-height: 110px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
padding: 15px 0 15px 0;
|
||||
}
|
||||
|
||||
.img-news {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
//.media ~ .media .media-body-wrapper:nth-child(odd) {
|
||||
// background-color: #e5e5e5;
|
||||
//}
|
||||
|
||||
.media-news {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.comment-to-comment-formgroup {
|
||||
width: 50%;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
|
||||
//uncomment this to see the dimensions of all elements outlined in red
|
||||
//* {
|
||||
// border-color: red;
|
||||
|
@ -15,5 +15,5 @@
|
||||
|
||||
//= require lib/jquery-2.1.1.min
|
||||
//= require lib/bootstrap.min
|
||||
//= require lib/together/togetherjs
|
||||
//= require lib/moment/moment
|
||||
//= require main
|
||||
|
@ -1,786 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*jshint scripturl:true */
|
||||
(function () {
|
||||
|
||||
var defaultConfiguration = {
|
||||
// Disables clicks for a certain element.
|
||||
// (e.g., 'canvas' would not show clicks on canvas elements.)
|
||||
// Setting this to true will disable clicks globally.
|
||||
dontShowClicks: false,
|
||||
// Experimental feature to echo clicks to certain elements across clients:
|
||||
cloneClicks: false,
|
||||
// Enable Mozilla or Google analytics on the page when TogetherJS is activated:
|
||||
// FIXME: these don't seem to be working, and probably should be removed in favor
|
||||
// of the hub analytics
|
||||
enableAnalytics: false,
|
||||
// The code to enable (this is defaulting to a Mozilla code):
|
||||
analyticsCode: "UA-55446531-1",
|
||||
// The base URL of the hub (gets filled in below):
|
||||
hubBase: "https://fcctogether.herokuapp.com",
|
||||
// A function that will return the name of the user:
|
||||
getUserName: null,
|
||||
// A function that will return the color of the user:
|
||||
getUserColor: null,
|
||||
// A function that will return the avatar of the user:
|
||||
getUserAvatar: null,
|
||||
// The siteName is used in the walkthrough (defaults to document.title):
|
||||
siteName: null,
|
||||
// Whether to use the minimized version of the code (overriding the built setting)
|
||||
useMinimizedCode: undefined,
|
||||
// Any events to bind to
|
||||
on: {},
|
||||
// Hub events to bind to
|
||||
hub_on: {},
|
||||
// Enables the alt-T alt-T TogetherJS shortcut; however, this setting
|
||||
// must be enabled early as TogetherJSConfig_enableShortcut = true;
|
||||
enableShortcut: false,
|
||||
// The name of this tool as provided to users. The UI is updated to use this.
|
||||
// Because of how it is used in text it should be a proper noun, e.g.,
|
||||
// "MySite's Collaboration Tool"
|
||||
toolName: null,
|
||||
// Used to auto-start TogetherJS with a {prefix: pageName, max: participants}
|
||||
// Also with findRoom: "roomName" it will connect to the given room name
|
||||
findRoom: null,
|
||||
// If true, starts TogetherJS automatically (of course!)
|
||||
autoStart: false,
|
||||
// If true, then the "Join TogetherJS Session?" confirmation dialog
|
||||
// won't come up
|
||||
suppressJoinConfirmation: false,
|
||||
// If true, then the "Invite a friend" window won't automatically come up
|
||||
suppressInvite: false,
|
||||
// A room in which to find people to invite to this session,
|
||||
inviteFromRoom: null,
|
||||
// This is used to keep sessions from crossing over on the same
|
||||
// domain, if for some reason you want sessions that are limited
|
||||
// to only a portion of the domain:
|
||||
storagePrefix: "togetherjs",
|
||||
// When true, we treat the entire URL, including the hash, as the identifier
|
||||
// of the page; i.e., if you one person is on `http://example.com/#view1`
|
||||
// and another person is at `http://example.com/#view2` then these two people
|
||||
// are considered to be at completely different URLs
|
||||
includeHashInUrl: false,
|
||||
// When true, the WebRTC-based mic/chat will be disabled
|
||||
disableWebRTC: false,
|
||||
// When true, youTube videos will synchronize
|
||||
youtube: true,
|
||||
// Ignores the following console messages, disables all messages if set to true
|
||||
ignoreMessages: ["cursor-update", "keydown", "scroll-update"],
|
||||
// Ignores the following forms (will ignore all forms if set to true):
|
||||
ignoreForms: [":password"]
|
||||
};
|
||||
|
||||
var styleSheet = "/togetherjs/togetherjs.css";
|
||||
|
||||
var baseUrl = "https://togetherjs.com";
|
||||
if (baseUrl == "__" + "baseUrl__") {
|
||||
// Reset the variable if it doesn't get substituted
|
||||
baseUrl = "";
|
||||
}
|
||||
// True if this file should use minimized sub-resources:
|
||||
var min = "no" == "__" + "min__" ? false : "no" == "yes";
|
||||
|
||||
var baseUrlOverride = localStorage.getItem("togetherjs.baseUrlOverride");
|
||||
if (baseUrlOverride) {
|
||||
try {
|
||||
baseUrlOverride = JSON.parse(baseUrlOverride);
|
||||
} catch (e) {
|
||||
baseUrlOverride = null;
|
||||
}
|
||||
if ((! baseUrlOverride) || baseUrlOverride.expiresAt < Date.now()) {
|
||||
// Ignore because it has expired
|
||||
localStorage.removeItem("togetherjs.baseUrlOverride");
|
||||
} else {
|
||||
baseUrl = baseUrlOverride.baseUrl;
|
||||
var logger = console.warn || console.log;
|
||||
logger.call(console, "Using TogetherJS baseUrlOverride:", baseUrl);
|
||||
logger.call(console, "To undo run: localStorage.removeItem('togetherjs.baseUrlOverride')");
|
||||
}
|
||||
}
|
||||
|
||||
var configOverride = localStorage.getItem("togetherjs.configOverride");
|
||||
if (configOverride) {
|
||||
try {
|
||||
configOverride = JSON.parse(configOverride);
|
||||
} catch (e) {
|
||||
configOverride = null;
|
||||
}
|
||||
if ((! configOverride) || configOverride.expiresAt < Date.now()) {
|
||||
localStorage.removeItem("togetherjs.configOverride");
|
||||
} else {
|
||||
var shownAny = false;
|
||||
for (var attr in configOverride) {
|
||||
if (attr == "expiresAt" || ! configOverride.hasOwnProperty(attr)) {
|
||||
continue;
|
||||
}
|
||||
if (! shownAny) {
|
||||
console.warn("Using TogetherJS configOverride");
|
||||
console.warn("To undo run: localStorage.removeItem('togetherjs.configOverride')");
|
||||
}
|
||||
window["TogetherJSConfig_" + attr] = configOverride[attr];
|
||||
console.log("Config override:", attr, "=", configOverride[attr]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var version = "unknown";
|
||||
// FIXME: we could/should use a version from the checkout, at least
|
||||
// for production
|
||||
var cacheBust = "";
|
||||
if ((! cacheBust) || cacheBust == "") {
|
||||
cacheBust = Date.now() + "";
|
||||
} else {
|
||||
version = cacheBust;
|
||||
}
|
||||
|
||||
// Make sure we have all of the console.* methods:
|
||||
if (typeof console == "undefined") {
|
||||
console = {};
|
||||
}
|
||||
if (! console.log) {
|
||||
console.log = function () {};
|
||||
}
|
||||
["debug", "info", "warn", "error"].forEach(function (method) {
|
||||
if (! console[method]) {
|
||||
console[method] = console.log;
|
||||
}
|
||||
});
|
||||
|
||||
if (! baseUrl) {
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
for (var i=0; i<scripts.length; i++) {
|
||||
var src = scripts[i].src;
|
||||
if (src && src.search(/togetherjs.js(\?.*)?$/) !== -1) {
|
||||
baseUrl = src.replace(/\/*togetherjs.js(\?.*)?$/, "");
|
||||
console.warn("Detected baseUrl as", baseUrl);
|
||||
break;
|
||||
} else if (src && src.search(/togetherjs-min.js(\?.*)?$/) !== -1) {
|
||||
baseUrl = src.replace(/\/*togetherjs-min.js(\?.*)?$/, "");
|
||||
console.warn("Detected baseUrl as", baseUrl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! baseUrl) {
|
||||
console.warn("Could not determine TogetherJS's baseUrl (looked for a <script> with togetherjs.js and togetherjs-min.js)");
|
||||
}
|
||||
|
||||
function addStyle() {
|
||||
var existing = document.getElementById("togetherjs-stylesheet");
|
||||
if (! existing) {
|
||||
var link = document.createElement("link");
|
||||
link.id = "togetherjs-stylesheet";
|
||||
link.setAttribute("rel", "stylesheet");
|
||||
link.href = baseUrl + styleSheet + "?bust=" + cacheBust;
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
}
|
||||
|
||||
function addScript(url) {
|
||||
var script = document.createElement("script");
|
||||
script.src = baseUrl + url + "?bust=" + cacheBust;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
var TogetherJS = window.TogetherJS = function TogetherJS(event) {
|
||||
if (TogetherJS.running) {
|
||||
var session = TogetherJS.require("session");
|
||||
session.close();
|
||||
return;
|
||||
}
|
||||
TogetherJS.startup.button = null;
|
||||
try {
|
||||
if (event && typeof event == "object") {
|
||||
if (event.target && typeof event) {
|
||||
TogetherJS.startup.button = event.target;
|
||||
} else if (event.nodeType == 1) {
|
||||
TogetherJS.startup.button = event;
|
||||
} else if (event[0] && event[0].nodeType == 1) {
|
||||
// Probably a jQuery element
|
||||
TogetherJS.startup.button = event[0];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Error determining starting button:", e);
|
||||
}
|
||||
if (window.TowTruckConfig) {
|
||||
console.warn("TowTruckConfig is deprecated; please use TogetherJSConfig");
|
||||
if (window.TogetherJSConfig) {
|
||||
console.warn("Ignoring TowTruckConfig in favor of TogetherJSConfig");
|
||||
} else {
|
||||
window.TogetherJSConfig = TowTruckConfig;
|
||||
}
|
||||
}
|
||||
if (window.TogetherJSConfig && (! window.TogetherJSConfig.loaded)) {
|
||||
TogetherJS.config(window.TogetherJSConfig);
|
||||
window.TogetherJSConfig.loaded = true;
|
||||
}
|
||||
|
||||
// This handles loading configuration from global variables. This
|
||||
// includes TogetherJSConfig_on_*, which are attributes folded into
|
||||
// the "on" configuration value.
|
||||
var attr;
|
||||
var attrName;
|
||||
var globalOns = {};
|
||||
for (attr in window) {
|
||||
if (attr.indexOf("TogetherJSConfig_on_") === 0) {
|
||||
attrName = attr.substr(("TogetherJSConfig_on_").length);
|
||||
globalOns[attrName] = window[attr];
|
||||
} else if (attr.indexOf("TogetherJSConfig_") === 0) {
|
||||
attrName = attr.substr(("TogetherJSConfig_").length);
|
||||
TogetherJS.config(attrName, window[attr]);
|
||||
} else if (attr.indexOf("TowTruckConfig_on_") === 0) {
|
||||
attrName = attr.substr(("TowTruckConfig_on_").length);
|
||||
console.warn("TowTruckConfig_* is deprecated, please rename", attr, "to TogetherJSConfig_on_" + attrName);
|
||||
globalOns[attrName] = window[attr];
|
||||
} else if (attr.indexOf("TowTruckConfig_") === 0) {
|
||||
attrName = attr.substr(("TowTruckConfig_").length);
|
||||
console.warn("TowTruckConfig_* is deprecated, please rename", attr, "to TogetherJSConfig_" + attrName);
|
||||
TogetherJS.config(attrName, window[attr]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
// FIXME: copy existing config?
|
||||
// FIXME: do this directly in TogetherJS.config() ?
|
||||
// FIXME: close these configs?
|
||||
var ons = TogetherJS.config.get("on");
|
||||
for (attr in globalOns) {
|
||||
if (globalOns.hasOwnProperty(attr)) {
|
||||
// FIXME: should we avoid overwriting? Maybe use arrays?
|
||||
ons[attr] = globalOns[attr];
|
||||
}
|
||||
}
|
||||
TogetherJS.config("on", ons);
|
||||
for (attr in ons) {
|
||||
TogetherJS.on(attr, ons[attr]);
|
||||
}
|
||||
var hubOns = TogetherJS.config.get("hub_on");
|
||||
if (hubOns) {
|
||||
for (attr in hubOns) {
|
||||
if (hubOns.hasOwnProperty(attr)) {
|
||||
TogetherJS.hub.on(attr, hubOns[attr]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! TogetherJS.startup.reason) {
|
||||
// Then a call to TogetherJS() from a button must be started TogetherJS
|
||||
TogetherJS.startup.reason = "started";
|
||||
}
|
||||
|
||||
// FIXME: maybe I should just test for TogetherJS.require:
|
||||
if (TogetherJS._loaded) {
|
||||
var session = TogetherJS.require("session");
|
||||
addStyle();
|
||||
session.start();
|
||||
return;
|
||||
}
|
||||
// A sort of signal to session.js to tell it to actually
|
||||
// start itself (i.e., put up a UI and try to activate)
|
||||
TogetherJS.startup._launch = true;
|
||||
|
||||
addStyle();
|
||||
var minSetting = TogetherJS.config.get("useMinimizedCode");
|
||||
TogetherJS.config.close("useMinimizedCode");
|
||||
if (minSetting !== undefined) {
|
||||
min = !! minSetting;
|
||||
}
|
||||
var requireConfig = TogetherJS._extend(TogetherJS.requireConfig);
|
||||
var deps = ["session", "jquery"];
|
||||
function callback(session, jquery) {
|
||||
TogetherJS._loaded = true;
|
||||
if (! min) {
|
||||
TogetherJS.require = require.config({context: "togetherjs"});
|
||||
TogetherJS._requireObject = require;
|
||||
}
|
||||
}
|
||||
if (! min) {
|
||||
if (typeof require == "function") {
|
||||
if (! require.config) {
|
||||
console.warn("The global require (", require, ") is not requirejs; please use togetherjs-min.js");
|
||||
throw new Error("Conflict with window.require");
|
||||
}
|
||||
TogetherJS.require = require.config(requireConfig);
|
||||
}
|
||||
}
|
||||
if (typeof TogetherJS.require == "function") {
|
||||
// This is an already-configured version of require
|
||||
TogetherJS.require(deps, callback);
|
||||
} else {
|
||||
requireConfig.deps = deps;
|
||||
requireConfig.callback = callback;
|
||||
if (! min) {
|
||||
window.require = requireConfig;
|
||||
}
|
||||
}
|
||||
if (min) {
|
||||
addScript("/togetherjs/togetherjsPackage.js");
|
||||
} else {
|
||||
addScript("/togetherjs/libs/require.js");
|
||||
}
|
||||
};
|
||||
|
||||
TogetherJS.pageLoaded = Date.now();
|
||||
|
||||
TogetherJS._extend = function (base, extensions) {
|
||||
if (! extensions) {
|
||||
extensions = base;
|
||||
base = {};
|
||||
}
|
||||
for (var a in extensions) {
|
||||
if (extensions.hasOwnProperty(a)) {
|
||||
base[a] = extensions[a];
|
||||
}
|
||||
}
|
||||
return base;
|
||||
};
|
||||
|
||||
TogetherJS._startupInit = {
|
||||
// What element, if any, was used to start the session:
|
||||
button: null,
|
||||
// The startReason is the reason TogetherJS was started. One of:
|
||||
// null: not started
|
||||
// started: hit the start button (first page view)
|
||||
// joined: joined the session (first page view)
|
||||
reason: null,
|
||||
// Also, the session may have started on "this" page, or maybe is continued
|
||||
// from a past page. TogetherJS.continued indicates the difference (false the
|
||||
// first time TogetherJS is started or joined, true on later page loads).
|
||||
continued: false,
|
||||
// This is set to tell the session what shareId to use, if the boot
|
||||
// code knows (mostly because the URL indicates the id).
|
||||
_joinShareId: null,
|
||||
// This tells session to start up immediately (otherwise it would wait
|
||||
// for session.start() to be run)
|
||||
_launch: false
|
||||
};
|
||||
TogetherJS.startup = TogetherJS._extend(TogetherJS._startupInit);
|
||||
TogetherJS.running = false;
|
||||
|
||||
TogetherJS.requireConfig = {
|
||||
context: "togetherjs",
|
||||
baseUrl: baseUrl + "/togetherjs",
|
||||
urlArgs: "bust=" + cacheBust,
|
||||
paths: {
|
||||
jquery: "libs/jquery-1.8.3.min",
|
||||
walkabout: "libs/walkabout/walkabout",
|
||||
esprima: "libs/walkabout/lib/esprima",
|
||||
falafel: "libs/walkabout/lib/falafel",
|
||||
tinycolor: "libs/tinycolor",
|
||||
whrandom: "libs/whrandom/random"
|
||||
}
|
||||
};
|
||||
|
||||
TogetherJS._mixinEvents = function (proto) {
|
||||
proto.on = function on(name, callback) {
|
||||
if (typeof callback != "function") {
|
||||
console.warn("Bad callback for", this, ".once(", name, ", ", callback, ")");
|
||||
throw "Error: .once() called with non-callback";
|
||||
}
|
||||
if (name.search(" ") != -1) {
|
||||
var names = name.split(/ +/g);
|
||||
names.forEach(function (n) {
|
||||
this.on(n, callback);
|
||||
}, this);
|
||||
return;
|
||||
}
|
||||
if (this._knownEvents && this._knownEvents.indexOf(name) == -1) {
|
||||
var thisString = "" + this;
|
||||
if (thisString.length > 20) {
|
||||
thisString = thisString.substr(0, 20) + "...";
|
||||
}
|
||||
console.warn(thisString + ".on('" + name + "', ...): unknown event");
|
||||
if (console.trace) {
|
||||
console.trace();
|
||||
}
|
||||
}
|
||||
if (! this._listeners) {
|
||||
this._listeners = {};
|
||||
}
|
||||
if (! this._listeners[name]) {
|
||||
this._listeners[name] = [];
|
||||
}
|
||||
if (this._listeners[name].indexOf(callback) == -1) {
|
||||
this._listeners[name].push(callback);
|
||||
}
|
||||
};
|
||||
proto.once = function once(name, callback) {
|
||||
if (typeof callback != "function") {
|
||||
console.warn("Bad callback for", this, ".once(", name, ", ", callback, ")");
|
||||
throw "Error: .once() called with non-callback";
|
||||
}
|
||||
var attr = "onceCallback_" + name;
|
||||
// FIXME: maybe I should add the event name to the .once attribute:
|
||||
if (! callback[attr]) {
|
||||
callback[attr] = function onceCallback() {
|
||||
callback.apply(this, arguments);
|
||||
this.off(name, onceCallback);
|
||||
delete callback[attr];
|
||||
};
|
||||
}
|
||||
this.on(name, callback[attr]);
|
||||
};
|
||||
proto.off = proto.removeListener = function off(name, callback) {
|
||||
if (this._listenerOffs) {
|
||||
// Defer the .off() call until the .emit() is done.
|
||||
this._listenerOffs.push([name, callback]);
|
||||
return;
|
||||
}
|
||||
if (name.search(" ") != -1) {
|
||||
var names = name.split(/ +/g);
|
||||
names.forEach(function (n) {
|
||||
this.off(n, callback);
|
||||
}, this);
|
||||
return;
|
||||
}
|
||||
if ((! this._listeners) || ! this._listeners[name]) {
|
||||
return;
|
||||
}
|
||||
var l = this._listeners[name], _len = l.length;
|
||||
for (var i=0; i<_len; i++) {
|
||||
if (l[i] == callback) {
|
||||
l.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
proto.emit = function emit(name) {
|
||||
var offs = this._listenerOffs = [];
|
||||
if ((! this._listeners) || ! this._listeners[name]) {
|
||||
return;
|
||||
}
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
var l = this._listeners[name];
|
||||
l.forEach(function (callback) {
|
||||
|
||||
callback.apply(this, args);
|
||||
}, this);
|
||||
delete this._listenerOffs;
|
||||
if (offs.length) {
|
||||
offs.forEach(function (item) {
|
||||
this.off(item[0], item[1]);
|
||||
}, this);
|
||||
}
|
||||
|
||||
};
|
||||
return proto;
|
||||
};
|
||||
|
||||
/* This finalizes the unloading of TogetherJS, including unloading modules */
|
||||
TogetherJS._teardown = function () {
|
||||
var requireObject = TogetherJS._requireObject || window.require;
|
||||
// FIXME: this doesn't clear the context for min-case
|
||||
if (requireObject.s && requireObject.s.contexts) {
|
||||
delete requireObject.s.contexts.togetherjs;
|
||||
}
|
||||
TogetherJS._loaded = false;
|
||||
TogetherJS.startup = TogetherJS._extend(TogetherJS._startupInit);
|
||||
TogetherJS.running = false;
|
||||
};
|
||||
|
||||
TogetherJS._mixinEvents(TogetherJS);
|
||||
TogetherJS._knownEvents = ["ready", "close"];
|
||||
TogetherJS.toString = function () {
|
||||
return "TogetherJS";
|
||||
};
|
||||
|
||||
TogetherJS._configuration = {};
|
||||
TogetherJS._defaultConfiguration = defaultConfiguration;
|
||||
TogetherJS._configTrackers = {};
|
||||
TogetherJS._configClosed = {};
|
||||
|
||||
/* TogetherJS.config(configurationObject)
|
||||
or: TogetherJS.config(configName, value)
|
||||
|
||||
Adds configuration to TogetherJS. You may also set the global variable TogetherJSConfig
|
||||
and when TogetherJS is started that configuration will be loaded.
|
||||
|
||||
Unknown configuration values will lead to console error messages.
|
||||
*/
|
||||
TogetherJS.config = function (name, maybeValue) {
|
||||
var settings;
|
||||
if (arguments.length == 1) {
|
||||
if (typeof name != "object") {
|
||||
throw new Error('TogetherJS.config(value) must have an object value (not: ' + name + ')');
|
||||
}
|
||||
settings = name;
|
||||
} else {
|
||||
settings = {};
|
||||
settings[name] = maybeValue;
|
||||
}
|
||||
var i;
|
||||
var tracker;
|
||||
for (var attr in settings) {
|
||||
if (settings.hasOwnProperty(attr)) {
|
||||
if (TogetherJS._configClosed[attr] && TogetherJS.running) {
|
||||
throw new Error("The configuration " + attr + " is finalized and cannot be changed");
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var attr in settings) {
|
||||
if (! settings.hasOwnProperty(attr)) {
|
||||
continue;
|
||||
}
|
||||
if (attr == "loaded" || attr == "callToStart") {
|
||||
continue;
|
||||
}
|
||||
if (! TogetherJS._defaultConfiguration.hasOwnProperty(attr)) {
|
||||
console.warn("Unknown configuration value passed to TogetherJS.config():", attr);
|
||||
}
|
||||
var previous = TogetherJS._configuration[attr];
|
||||
var value = settings[attr];
|
||||
TogetherJS._configuration[attr] = value;
|
||||
var trackers = TogetherJS._configTrackers[name] || [];
|
||||
var failed = false;
|
||||
for (i=0; i<trackers.length; i++) {
|
||||
try {
|
||||
tracker = trackers[i];
|
||||
tracker(value, previous);
|
||||
} catch (e) {
|
||||
console.warn("Error setting configuration", name, "to", value,
|
||||
":", e, "; reverting to", previous);
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (failed) {
|
||||
TogetherJS._configuration[attr] = previous;
|
||||
for (i=0; i<trackers.length; i++) {
|
||||
try {
|
||||
tracker = trackers[i];
|
||||
tracker(value);
|
||||
} catch (e) {
|
||||
console.warn("Error REsetting configuration", name, "to", previous,
|
||||
":", e, "(ignoring)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TogetherJS.config.get = function (name) {
|
||||
var value = TogetherJS._configuration[name];
|
||||
if (value === undefined) {
|
||||
if (! TogetherJS._defaultConfiguration.hasOwnProperty(name)) {
|
||||
console.error("Tried to load unknown configuration value:", name);
|
||||
}
|
||||
value = TogetherJS._defaultConfiguration[name];
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
TogetherJS.config.track = function (name, callback) {
|
||||
if (! TogetherJS._defaultConfiguration.hasOwnProperty(name)) {
|
||||
throw new Error("Configuration is unknown: " + name);
|
||||
}
|
||||
callback(TogetherJS.config.get(name));
|
||||
if (! TogetherJS._configTrackers[name]) {
|
||||
TogetherJS._configTrackers[name] = [];
|
||||
}
|
||||
TogetherJS._configTrackers[name].push(callback);
|
||||
return callback;
|
||||
};
|
||||
|
||||
TogetherJS.config.close = function (name) {
|
||||
if (! TogetherJS._defaultConfiguration.hasOwnProperty(name)) {
|
||||
throw new Error("Configuration is unknown: " + name);
|
||||
}
|
||||
TogetherJS._configClosed[name] = true;
|
||||
};
|
||||
|
||||
TogetherJS.reinitialize = function () {
|
||||
if (TogetherJS.running && typeof TogetherJS.require == "function") {
|
||||
TogetherJS.require(["session"], function (session) {
|
||||
session.emit("reinitialize");
|
||||
});
|
||||
}
|
||||
// If it's not set, TogetherJS has not been loaded, and reinitialization is not needed
|
||||
};
|
||||
|
||||
TogetherJS.refreshUserData = function () {
|
||||
if (TogetherJS.running && typeof TogetherJS.require == "function") {
|
||||
TogetherJS.require(["session"], function (session) {
|
||||
session.emit("refresh-user-data");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// This should contain the output of "git describe --always --dirty"
|
||||
// FIXME: substitute this on the server (and update make-static-client)
|
||||
TogetherJS.version = version;
|
||||
TogetherJS.baseUrl = baseUrl;
|
||||
|
||||
TogetherJS.hub = TogetherJS._mixinEvents({});
|
||||
|
||||
TogetherJS._onmessage = function (msg) {
|
||||
var type = msg.type;
|
||||
if (type.search(/^app\./) === 0) {
|
||||
type = type.substr("app.".length);
|
||||
} else {
|
||||
type = "togetherjs." + type;
|
||||
}
|
||||
msg.type = type;
|
||||
TogetherJS.hub.emit(msg.type, msg);
|
||||
};
|
||||
|
||||
TogetherJS.send = function (msg) {
|
||||
if (! TogetherJS.require) {
|
||||
throw "You cannot use TogetherJS.send() when TogetherJS is not running";
|
||||
}
|
||||
var session = TogetherJS.require("session");
|
||||
session.appSend(msg);
|
||||
};
|
||||
|
||||
TogetherJS.shareUrl = function () {
|
||||
if (! TogetherJS.require) {
|
||||
return null;
|
||||
}
|
||||
var session = TogetherJS.require("session");
|
||||
return session.shareUrl();
|
||||
};
|
||||
|
||||
var listener = null;
|
||||
|
||||
TogetherJS.listenForShortcut = function () {
|
||||
console.warn("Listening for alt-T alt-T to start TogetherJS");
|
||||
TogetherJS.removeShortcut();
|
||||
listener = function listener(event) {
|
||||
if (event.which == 84 && event.altKey) {
|
||||
if (listener.pressed) {
|
||||
// Second hit
|
||||
TogetherJS();
|
||||
} else {
|
||||
listener.pressed = true;
|
||||
}
|
||||
} else {
|
||||
listener.pressed = false;
|
||||
}
|
||||
};
|
||||
TogetherJS.once("ready", TogetherJS.removeShortcut);
|
||||
document.addEventListener("keyup", listener, false);
|
||||
};
|
||||
|
||||
TogetherJS.removeShortcut = function () {
|
||||
if (listener) {
|
||||
document.addEventListener("keyup", listener, false);
|
||||
listener = null;
|
||||
}
|
||||
};
|
||||
|
||||
TogetherJS.config.track("enableShortcut", function (enable, previous) {
|
||||
if (enable) {
|
||||
TogetherJS.listenForShortcut();
|
||||
} else if (previous) {
|
||||
TogetherJS.removeShortcut();
|
||||
}
|
||||
});
|
||||
|
||||
TogetherJS.checkForUsersOnChannel = function (address, callback) {
|
||||
if (address.search(/^https?:/i) === 0) {
|
||||
address = address.replace(/^http/i, 'ws');
|
||||
}
|
||||
var socket = new WebSocket(address);
|
||||
var gotAnswer = false;
|
||||
socket.onmessage = function (event) {
|
||||
var msg = JSON.parse(event.data);
|
||||
if (msg.type != "init-connection") {
|
||||
console.warn("Got unexpected first message (should be init-connection):", msg);
|
||||
return;
|
||||
}
|
||||
if (gotAnswer) {
|
||||
console.warn("Somehow received two responses from channel; ignoring second");
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
gotAnswer = true;
|
||||
socket.close();
|
||||
callback(msg.peerCount);
|
||||
};
|
||||
socket.onclose = socket.onerror = function () {
|
||||
if (! gotAnswer) {
|
||||
console.warn("Socket was closed without receiving answer");
|
||||
gotAnswer = true;
|
||||
callback(undefined);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// It's nice to replace this early, before the load event fires, so we conflict
|
||||
// as little as possible with the app we are embedded in:
|
||||
var hash = location.hash.replace(/^#/, "");
|
||||
var m = /&?togetherjs=([^&]*)/.exec(hash);
|
||||
if (m) {
|
||||
TogetherJS.startup._joinShareId = m[1];
|
||||
TogetherJS.startup.reason = "joined";
|
||||
var newHash = hash.substr(0, m.index) + hash.substr(m.index + m[0].length);
|
||||
location.hash = newHash;
|
||||
}
|
||||
if (window._TogetherJSShareId) {
|
||||
// A weird hack for something the addon does, to force a shareId.
|
||||
// FIXME: probably should remove, it's a wonky feature.
|
||||
TogetherJS.startup._joinShareId = window._TogetherJSShareId;
|
||||
delete window._TogetherJSShareId;
|
||||
}
|
||||
|
||||
function conditionalActivate() {
|
||||
if (window.TogetherJSConfig_noAutoStart) {
|
||||
return;
|
||||
}
|
||||
// A page can define this function to defer TogetherJS from starting
|
||||
var callToStart = window.TogetherJSConfig_callToStart;
|
||||
if (! callToStart && window.TowTruckConfig_callToStart) {
|
||||
callToStart = window.TowTruckConfig_callToStart;
|
||||
console.warn("Please rename TowTruckConfig_callToStart to TogetherJSConfig_callToStart");
|
||||
}
|
||||
if (window.TogetherJSConfig && window.TogetherJSConfig.callToStart) {
|
||||
callToStart = window.TogetherJSConfig.callToStart;
|
||||
}
|
||||
if (callToStart) {
|
||||
// FIXME: need to document this:
|
||||
callToStart(onload);
|
||||
} else {
|
||||
onload();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: can we push this up before the load event?
|
||||
// Do we need to wait at all?
|
||||
function onload() {
|
||||
if (TogetherJS.startup._joinShareId) {
|
||||
TogetherJS();
|
||||
} else if (window._TogetherJSBookmarklet) {
|
||||
delete window._TogetherJSBookmarklet;
|
||||
TogetherJS();
|
||||
} else {
|
||||
// FIXME: this doesn't respect storagePrefix:
|
||||
var key = "togetherjs-session.status";
|
||||
var value = sessionStorage.getItem(key);
|
||||
if (value) {
|
||||
value = JSON.parse(value);
|
||||
if (value && value.running) {
|
||||
TogetherJS.startup.continued = true;
|
||||
TogetherJS.startup.reason = value.startupReason;
|
||||
TogetherJS();
|
||||
}
|
||||
} else if (window.TogetherJSConfig_autoStart ||
|
||||
(window.TogetherJSConfig && window.TogetherJSConfig.autoStart)) {
|
||||
TogetherJS.startup.reason = "joined";
|
||||
TogetherJS();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conditionalActivate();
|
||||
|
||||
// FIXME: wait until load event to double check if this gets set?
|
||||
if (window.TogetherJSConfig_enableShortcut) {
|
||||
TogetherJS.listenForShortcut();
|
||||
}
|
||||
|
||||
// For compatibility:
|
||||
window.TowTruck = TogetherJS;
|
||||
|
||||
})();
|
@ -55,7 +55,7 @@ $(document).ready(function() {
|
||||
if (res) {
|
||||
window.location.href = '/bonfires'
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,8 +107,6 @@ $(document).ready(function() {
|
||||
window.location = '/challenges/' + (parseInt(l[l.length - 1]) + 1);
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Bonfire instructions functions
|
||||
$('#more-info').on('click', function() {
|
||||
ga('send', 'event', 'Challenge', 'more-info', challengeName);
|
||||
@ -120,6 +118,106 @@ $(document).ready(function() {
|
||||
$('#brief-instructions').show();
|
||||
$('#long-instructions').hide();
|
||||
});
|
||||
|
||||
var upvoteHandler = function () {
|
||||
var _id = storyId;
|
||||
$('#upvote').unbind('click');
|
||||
var alreadyUpvoted = false;
|
||||
for (var i = 0; i < upVotes.length; i++) {
|
||||
if (upVotes[i].upVotedBy === user._id) {
|
||||
alreadyUpvoted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!alreadyUpvoted) {
|
||||
$.post('/stories/upvote',
|
||||
{
|
||||
data: {
|
||||
id: _id,
|
||||
upVoter: user
|
||||
}
|
||||
})
|
||||
.fail(function (xhr, textStatus, errorThrown) {
|
||||
$('#upvote').bind('click', upvoteHandler);
|
||||
})
|
||||
.done(function (data, textStatus, xhr) {
|
||||
$('#storyRank').text(data.rank);
|
||||
});
|
||||
} else {
|
||||
console.log('Can\'t upvote because you\'ve already upvoted');
|
||||
}
|
||||
};
|
||||
$('#upvote').on('click', upvoteHandler);
|
||||
|
||||
var storySubmitButtonHandler = function storySubmitButtonHandler() {
|
||||
|
||||
var link = $('#story-url').val();
|
||||
var headline = $('#story-title').val();
|
||||
var description = $('#description-box').val();
|
||||
console.log(link, headline, description);
|
||||
var userDataForUpvote = {
|
||||
upVotedBy: user._id,
|
||||
upVotedByUsername: user.profile.username
|
||||
};
|
||||
$('#story-submit').unbind('click');
|
||||
$.post('/stories/',
|
||||
{
|
||||
data: {
|
||||
link: link,
|
||||
headline: headline,
|
||||
timePosted: Date.now(),
|
||||
description: description,
|
||||
|
||||
rank: 1,
|
||||
upVotes: [userDataForUpvote],
|
||||
author: {
|
||||
picture: user.profile.picture,
|
||||
userId: user._id,
|
||||
username: user.profile.username
|
||||
},
|
||||
comments: [],
|
||||
image: ''
|
||||
}
|
||||
})
|
||||
.fail(function (xhr, textStatus, errorThrown) {
|
||||
$('#story-submit').bind('click', storySubmitButtonHandler);
|
||||
})
|
||||
.done(function (data, textStatus, xhr) {
|
||||
window.location = '/stories/' + JSON.parse(data).storyLink;
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$('#story-submit').on('click', storySubmitButtonHandler);
|
||||
|
||||
var commentSubmitButtonHandler = function commentSubmitButtonHandler() {
|
||||
$('comment-button').unbind('click');
|
||||
var data = $('#comment-box').val();
|
||||
console.log('comment clicked');
|
||||
|
||||
$('#comment-button').attr('disabled', 'disabled');
|
||||
$.post('/stories/comment/',
|
||||
{
|
||||
data: {
|
||||
associatedPost: storyId,
|
||||
body: data,
|
||||
author: {
|
||||
picture: user.profile.picture,
|
||||
userId: user._id,
|
||||
username: user.profile.username
|
||||
}
|
||||
}
|
||||
})
|
||||
.fail(function (xhr, textStatus, errorThrown) {
|
||||
$('#comment-button').attr('disabled', false);
|
||||
})
|
||||
.done(function (data, textStatus, xhr) {
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$('#comment-button').on('click', commentSubmitButtonHandler);
|
||||
});
|
||||
|
||||
var profileValidation = angular.module('profileValidation',['ui.bootstrap']);
|
||||
@ -155,6 +253,12 @@ profileValidation.controller('emailSignInController', ['$scope',
|
||||
}
|
||||
]);
|
||||
|
||||
profileValidation.controller('URLSubmitController', ['$scope',
|
||||
function($scope) {
|
||||
|
||||
}
|
||||
]);
|
||||
|
||||
profileValidation.controller('nonprofitFormController', ['$scope',
|
||||
function($scope) {
|
||||
|
||||
|
3
seed_data/comments.json
Normal file
3
seed_data/comments.json
Normal file
@ -0,0 +1,3 @@
|
||||
[
|
||||
|
||||
]
|
@ -1,17 +1,21 @@
|
||||
require('dotenv').load();
|
||||
var Challenge = require('../models/Challenge.js'),
|
||||
Bonfire = require('../models/Bonfire.js'),
|
||||
Comment = require('../models/Comment.js'),
|
||||
Story = require('../models/Story.js'),
|
||||
Courseware = require('../models/Courseware.js'),
|
||||
mongoose = require('mongoose'),
|
||||
secrets = require('../config/secrets'),
|
||||
challenges = require('./challenges.json'),
|
||||
coursewares = require('./coursewares.json'),
|
||||
bonfires = require('./bonfires.json');
|
||||
stories = require('./stories.json'),
|
||||
bonfires = require('./bonfires.json'),
|
||||
comments = require('./comments.json');
|
||||
|
||||
mongoose.connect(secrets.db);
|
||||
|
||||
var counter = 0;
|
||||
var offerings = 3;
|
||||
var offerings = 5;
|
||||
|
||||
var CompletionMonitor = function() {
|
||||
counter++;
|
||||
@ -74,3 +78,37 @@ Courseware.remove({}, function(err, data) {
|
||||
});
|
||||
console.log('coursewares');
|
||||
});
|
||||
|
||||
Story.remove({}, function(err, data) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
} else {
|
||||
console.log('Deleted ', data);
|
||||
}
|
||||
Story.create(stories, function(err, data) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
console.log('Saved ', data);
|
||||
}
|
||||
CompletionMonitor();
|
||||
});
|
||||
console.log('stories');
|
||||
});
|
||||
|
||||
Comment.remove({}, function(err, data) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
} else {
|
||||
console.log('Deleted ', data);
|
||||
}
|
||||
Comment.create(comments, function(err, data) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
console.log('Saved ', data);
|
||||
}
|
||||
CompletionMonitor();
|
||||
});
|
||||
console.log('stories');
|
||||
});
|
3082
seed_data/stories.json
Normal file
3082
seed_data/stories.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -317,9 +317,9 @@ block content
|
||||
a.close(href='#', data-dismiss='modal', aria-hidden='true') ×
|
||||
h3 Are you really leaving us?
|
||||
.modal-body
|
||||
p Pro Tip: If you tweet feedback to
|
||||
p Pro Tip: If you tweet feedback to 
|
||||
a(href="https://twitter.com/intent/tweet?text=Hey%20@freecodecamp") @FreeCodeCamp
|
||||
| , we'll act quickly on it!
|
||||
| , we'll act quickly on it!
|
||||
.modal-footer
|
||||
a.btn.btn-success.btn-block(href='#', data-dismiss='modal', aria-hidden='true')
|
||||
span.ion-happy
|
||||
|
@ -14,7 +14,7 @@ block content
|
||||
button.btn.btn-primary(type='submit')
|
||||
span.ion-android-hand
|
||||
| Login
|
||||
span
|
||||
span   
|
||||
a.btn.btn-info(href='/forgot') Forgot your password?
|
||||
br
|
||||
br
|
||||
|
@ -23,7 +23,7 @@ block content
|
||||
#testCreatePanel
|
||||
h1.text-center= name
|
||||
h2.text-center
|
||||
.bonfire-flames Difficulty:
|
||||
.bonfire-flames Difficulty: 
|
||||
if (difficulty == "0")
|
||||
i.ion-ios-flame-outline
|
||||
i.ion-ios-flame-outline
|
||||
@ -127,7 +127,7 @@ block content
|
||||
|
||||
- if (points && points > 2)
|
||||
a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(target="_blank")
|
||||
i.fa.fa-twitter
|
||||
i.fa.fa-twitter  
|
||||
= phrase
|
||||
- else
|
||||
a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress
|
||||
|
@ -32,7 +32,7 @@ block content
|
||||
a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-challenge-button(name='_csrf', value=_csrf, aria-hidden='true') Take me to my next challenge
|
||||
- if (points && points > 2)
|
||||
a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(target="_blank")
|
||||
i.fa.fa-twitter
|
||||
i.fa.fa-twitter  
|
||||
= phrase
|
||||
- else
|
||||
a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress
|
||||
|
@ -4,7 +4,7 @@ block content
|
||||
.jumbotron
|
||||
h1.animated.lightSpeedIn.text-center.hug-top We'll code software solutions for your nonprofit, for free!
|
||||
.animated.fadeIn.delay-2.landing-panel-body
|
||||
h3 Our students will help you build that donor tracking system, community message board, or whatever your organization needs,
|
||||
h3 Our students will help you build that donor tracking system, community message board, or whatever your organization needs, 
|
||||
strong at no cost.
|
||||
h3 In exchange, we ask only that you:
|
||||
h4
|
||||
|
@ -68,7 +68,7 @@ block content
|
||||
a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter)
|
||||
- if (points && points > 2)
|
||||
a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank")
|
||||
i.fa.fa-twitter
|
||||
i.fa.fa-twitter  
|
||||
= phrase
|
||||
- else
|
||||
a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress
|
@ -39,7 +39,7 @@ block content
|
||||
a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter)
|
||||
- if (points && points > 2)
|
||||
a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank")
|
||||
i.fa.fa-twitter
|
||||
i.fa.fa-twitter  
|
||||
= phrase
|
||||
- else
|
||||
a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress
|
||||
|
@ -74,15 +74,15 @@ block content
|
||||
h2 Why you should join our community right now:
|
||||
h3.col-xs-offset-0.col-sm-offset-1
|
||||
ul.text-left
|
||||
li.ion-code We're thousands of professionals, all learning to code together
|
||||
li.ion-code We're building projects for dozens of nonprofits
|
||||
li.ion-code Our community is 100% free and open source
|
||||
li.ion-code You'll learn Full Stack JavaScript and become a Software Engineer
|
||||
li.ion-code You'll work through our focused, interactive courses and tutorials
|
||||
li.ion-code You'll learn to code at your own pace, in your browser or on your phone
|
||||
li.ion-code You'll become qualified for thousands of jobs currently going unfilled
|
||||
li.ion-code You can get help in real time from our community chat rooms and forum
|
||||
li.ion-code We all share one common goal: to boost our careers with code
|
||||
li.ion-code   We're thousands of professionals, all learning to code together
|
||||
li.ion-code   We're building projects for dozens of nonprofits
|
||||
li.ion-code   Our community is 100% free and open source
|
||||
li.ion-code   You'll learn Full Stack JavaScript and become a Software Engineer
|
||||
li.ion-code   You'll work through our focused, interactive courses and tutorials
|
||||
li.ion-code   You'll learn to code at your own pace, in your browser or on your phone
|
||||
li.ion-code   You'll become qualified for thousands of jobs currently going unfilled
|
||||
li.ion-code   You can get help in real time from our community chat rooms and forum
|
||||
li.ion-code   We all share one common goal: to boost our careers with code
|
||||
.big-break
|
||||
a.btn.btn-cta.signup-btn(href="/login") Learn to code today (it's free)
|
||||
script.
|
||||
|
@ -3,5 +3,5 @@ h3
|
||||
for bonfire in bonfires
|
||||
li
|
||||
a(href="/bonfires/#{bonfire.bonfireNumber}", class="#{ (cc && cc[bonfire.bonfireNumber] > 0) ? 'strikethrough' : '' }") #{bonfire.name}
|
||||
| (Level #{bonfire.difficulty})
|
||||
|   (Level #{bonfire.difficulty})
|
||||
a.btn.btn-lg.btn-primary.btn-block(href="/done-with-first-100-hours", class="#{ ((cc && cc[53] === 0) || (!cc)) ? 'disabled' : '' }") I'm done with all the challenges!
|
@ -3,6 +3,6 @@ h3
|
||||
for challenge in challenges
|
||||
li
|
||||
a(href="/challenges/#{challenge.challengeNumber}", class="#{ (cc && cc[challenge.challengeNumber] > 0) ? 'strikethrough' : '' }") #{challenge.name}
|
||||
| (#{challenge.time} mins)
|
||||
|   (#{challenge.time} mins)
|
||||
|
||||
a.btn.btn-lg.btn-primary.btn-block(href="/done-with-first-100-hours", class="#{ ((points && points < 54) || (!points)) ? 'disabled' : '' }") I'm done with all the challenges!
|
@ -1,11 +1,11 @@
|
||||
.fcc-footer
|
||||
.col-xs-12.hidden-xs.hidden-sm
|
||||
a.ion-speakerphone(href='http://blog.freecodecamp.com', target='_blank') Blog
|
||||
a.ion-social-twitch-outline(href="http://www.twitch.tv/freecodecamp", target='_blank') Twitch
|
||||
a.ion-social-github(href="http://github.com/freecodecamp", target='_blank') Github
|
||||
a.ion-social-twitter(href="http://twitter.com/freecodecamp", target='_blank') Twitter
|
||||
a.ion-information-circled(href="/learn-to-code") About
|
||||
a.ion-locked(href="/privacy") Privacy
|
||||
a.ion-speakerphone(href='http://blog.freecodecamp.com', target='_blank')  Blog  
|
||||
a.ion-social-twitch-outline(href="http://www.twitch.tv/freecodecamp", target='_blank')  Twitch  
|
||||
a.ion-social-github(href="http://github.com/freecodecamp", target='_blank')  Github  
|
||||
a.ion-social-twitter(href="http://twitter.com/freecodecamp", target='_blank')  Twitter  
|
||||
a.ion-information-circled(href="/learn-to-code")  About  
|
||||
a.ion-locked(href="/privacy")  Privacy  
|
||||
.col-xs-12.visible-xs.visible-sm
|
||||
a.ion-speakerphone(href='http://blog.freecodecamp.com', target='_blank')
|
||||
span.sr-only Free Code Camp's Blog
|
||||
|
@ -15,18 +15,20 @@
|
||||
a(href='/challenges') Challenges
|
||||
li
|
||||
a(href='/chat') Chat
|
||||
li
|
||||
a(href='/stories/hot') News
|
||||
li
|
||||
a(href='/bonfires') Bonfires
|
||||
if !user
|
||||
li
|
||||
li      
|
||||
li
|
||||
a.btn.signup-btn.signup-btn-nav(href='/login') Sign in
|
||||
else
|
||||
li
|
||||
if (user.profile.username)
|
||||
a(href='/' + user.profile.username) [ #{user.points} ]
|
||||
a(href='/' + user.profile.username) [ #{user.points} ]
|
||||
else
|
||||
a(href='/account') [ #{user.points} ]
|
||||
a(href='/account') [ #{user.points} ]
|
||||
.hidden-xs
|
||||
if user.profile.picture
|
||||
if (user.profile.username)
|
||||
|
@ -2,18 +2,18 @@ extends ../layout-wide
|
||||
block content
|
||||
h3
|
||||
ol.col-md-offset-2
|
||||
li Create a GitHub Account
|
||||
li Create a GitHub Account 
|
||||
a(href="http://github.com/join", target='_blank') here
|
||||
| .
|
||||
li Download the chat room app on
|
||||
li Download the chat room app on  
|
||||
a(href="https://update.gitter.im/win/latest") Windows
|
||||
| ,
|
||||
| , 
|
||||
a(href="https://update.gitter.im/osx/latest") Mac
|
||||
| ,
|
||||
| , 
|
||||
a(href="http://appstore.com/gitter") iPhone
|
||||
| , or
|
||||
| ,  or  
|
||||
a(href="https://play.google.com/store/apps/details?id=im.gitter.gitter&hl=en_GB") Android
|
||||
| , or go
|
||||
|  , or go  
|
||||
a(href="http://chat.freecodecamp.com") here
|
||||
| .
|
||||
li Keep the chat room open while you code so that you can meet friends and ask for help.
|
||||
|
@ -11,15 +11,15 @@ block content
|
||||
ul
|
||||
li
|
||||
a(href='http://www.atom.io' target='_blank') http://www.atom.io
|
||||
| - free text editor
|
||||
|   - free text editor
|
||||
li
|
||||
a(href='http://www.startbootstrap.com' target='_blank') http://www.startbootstrap.com
|
||||
| - free responsive (Bootstrap) templates
|
||||
|   - free responsive (Bootstrap) templates
|
||||
li
|
||||
a(href='http://www.powr.io' target='_blank') http://www.powr.io
|
||||
| - great plugins
|
||||
|   - great plugins
|
||||
li
|
||||
a(href='http://www.bitballoon.com' target='_blank') http://www.bitballoon.com
|
||||
| - drag-and-drop deployment
|
||||
|   - drag-and-drop deployment
|
||||
a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free)
|
||||
br
|
||||
|
96
views/resources/guide-to-our-nonprofit-projects.jade
Normal file
96
views/resources/guide-to-our-nonprofit-projects.jade
Normal file
@ -0,0 +1,96 @@
|
||||
extends ../layout
|
||||
block content
|
||||
.jumbotron
|
||||
h1.hug-top.text-center A Guide to our Nonprofit Projects
|
||||
br
|
||||
p Building nonprofit projects is the main way that our campers learn full stack JavaScript and agile software development. Once you complete the Free Code Camp challenges and Bonfire challenges, you'll begin this process.
|
||||
p Once you've finished all the challenges, click the "I'm done with all the challenges" button, which will become enabled. We will prompt you for your email address, then give you further instructions on our  
|
||||
a(href="http://www.freecodecamp.com/nonprofit-project-instructions") Nonprofit Projects Instructions page
|
||||
| .
|
||||
p We will add you to our  
|
||||
a(href="https://trello.com/b/BA3xVpz9/nonprofit-projects") Nonprofit Project Trello board
|
||||
| .
|
||||
h2 Starting with the end in mind
|
||||
p Our goal at Free Code Camp is to help you land a job as a junior software developer (or, if you prefer, a 'pivot job' that leads your current career in a more technical direction).
|
||||
p You'll continue to work on nonprofit projects until you've built a sufficiently impressive portfolio and references to start your job search. Your portfolio will ultimately have three to five nonprofit projects. We estimate that the 900 hours of nonprofit projects you're going to complete, in addition to the 100 hours of challenges you've already completed, will be enough to qualify you for your first coding job. This will produce a much broader portfolio than a traditional coding bootcamp, which generally only has one or two capstone projects.
|
||||
h2 Choosing your first Nonprofit Project
|
||||
p We've categorized all the nonprofit projects by estimated time investment per camper: 100 hours, 200 hours, and 300 hours. These are only rough estimates.
|
||||
p Example: if you and the camper you're paired up with (your pair) each stated you could work 20 hours per week (on the  
|
||||
a(href="http://goo.gl/forms/f61dLt67t8") form you filled out
|
||||
| ). If the project is a 100 hour per camper project, you should be able to complete it in about 5 weeks.
|
||||
p Our team of nonprofit project camp counselors will match you and your pair based on:
|
||||
ol
|
||||
li Your estimated time commitment (10, 20 or 40 hours per week)
|
||||
li Your time zone
|
||||
li The nonprofit projects you've chosen
|
||||
li Prior coding experience (we'd like both campers to be able to contribute equally)
|
||||
p We won't take age or gender into account. This will provide you with valuable experience in meshing with diverse teams, which is a reality of the contemporary workplace.
|
||||
p You'll only work on one project at a time. Once you start a nonprofit project, we'll remove you from all other nonprofit project Trello cards. There's a good chance those projects will no longer be available when you finish your current project, anyway. Don't worry, though - we get new nonprofit project requests every day, so there will be plenty more projects for you to consider after you finish your current one.
|
||||
h2 Finalizing the Project
|
||||
p Before you can start working on the project, our team of Nonprofit Project Coordinators will go through the following process:
|
||||
ol
|
||||
li We'll wait until there are two campers who have chosen the same project and look like they're a good match for one another based on the factors mentioned above.
|
||||
li We'll call the stakeholder to confirm once again that he or she agrees with our  
|
||||
a(href="freecodecamp.com/nonprofits") terms  
|
||||
| and has signed our  
|
||||
a(href="http://goo.gl/forms/0YKkd9bpcR") Nonprofit Project Stakeholder Pledge
|
||||
| .
|
||||
li We'll set an initial meeting with representatives from Free Code Camp, the two campers, and the stakeholder.
|
||||
li If the stakeholder and both campers shows up promptly, and seem enthusiastic and professional, we'll start the project.
|
||||
p This lengthy process serves an important purpose: it reduces the likelihood that any of our campers or stakeholders will waste their precious time.
|
||||
h2 Nonprofit Stakeholders
|
||||
p Each nonprofit project was submitted by a nonprofit. A representative from this nonprofit has agreed to serve as a "stakeholder" - an authorative person who understands the organization and its needs for this particular project.
|
||||
p Stakeholders have a deep understanding of their organizations' needs. Campers will work with them to figure out the best solutions to these needs.
|
||||
p When you and your pair first speak with your nonprofit stakeholder, you'll:
|
||||
ul
|
||||
li talk at length to better understand their needs.
|
||||
li create a new Trello board and use it to prioritize what needs to be built.
|
||||
li and establish deadlines based on your weekly time commitment, and how long you think each task will take.
|
||||
p It's notoriously difficult to estimate how long building software projects will take, so feel free to ask camp counselors for help.
|
||||
p You'll continue to meet with your stakeholder regularly throughout this process.
|
||||
p Ultimately, the project will be considered complete once both the stakeholder's needs have been met, and you and your pair are happy with the project. Then you can add it to your portfolio!
|
||||
h2 Working with your Pair
|
||||
p You and your pair will pair program (code together on the same computer virtually) about half of the time, and work independently the other half of the time.
|
||||
p Here are our recommended ways of collaborating:
|
||||
ul
|
||||
li • Gitter has robust private messaging functionality. It's the main way our team communicates, and we recommend it over email.
|
||||
li • Trello is great for managing projects. Work with your stakeholder to create Trello cards, and update these cards regularly as you make progress on them.
|
||||
li • Screen Hero or Team Viewer - These are the ideal way to pair program. Tools like TMUX are good, but difficult to use. We discourage you from using screen sharing tools where only one person has control of the keyboard and mouse - that isn't real pair programming.
|
||||
li • Write clear and readable code, commit messages, branch names, and pull request messages.
|
||||
h2 Setting up your Development Environment
|
||||
p We've created a custom virtual machine image with Ubuntu Linux, Git, Team Viewer, the MEAN Stack and all its dependencies. You can run this virtual image on any computer with at least 2 gigabytes of RAM and 16 gigabytes of hard drive space.
|
||||
p The benefits of using this virtual machine are as follows:
|
||||
ul
|
||||
li • Everyone else on Free Code Camp is using this image, so we can all help you troubleshoot various problems that may arise.
|
||||
li • When you pair program, you and your pair will have the exact same environment, which means you will both feel comfortable on each other's machines.
|
||||
li • You can install the image on any computer without worrying about messing up the computer's original data or configuration.
|
||||
li • Even if you end up using Windows or Mac OSX for development later, your server will almost certainly run Linux, so it's worth getting used to Linux.
|
||||
li • Even experienced developers encounter hangups when setting up a development environment. This virtual machine image will remove this tedious process.
|
||||
p Install a bit torrent client, then  
|
||||
a(href="http://mgnet.me/ZOQk0rd") download our virtual machine image
|
||||
| .
|
||||
p Please note that even though Bit Torrent is often used to download content illegally, all the content on our image is open source and perfectly legal to redistribute.
|
||||
p Once you've downloaded the file,  
|
||||
a(href="https://www.virtualbox.org/wiki/Downloads") download VirtualBox  
|
||||
| and follow  
|
||||
a(href="http://techathlon.com/how-to-run-a-vmdk-file-in-oracle-virtualbox/") this tutorial  
|
||||
| to open the image in VirtualBox. You'll want to assign the virtual machine at least two gigabytes of ram.
|
||||
p Now you have your own Linux development environment. You can shut it down when you're not using it and it will save its state. Please continue to seed the file in bit torrent so that other campers can download it as well. Enjoy!
|
||||
h2 Hosting Apps
|
||||
p Unless your stakeholder has an existing modern host (AWS, Digital Ocean), you'll need to transition them over to a new platform. We believe Heroku is the best choice for a vast majority of web projects. It's free, easy to use, and has both browser and command line interfaces. It's owned by Salesforce and used by a ton of companies, so it's accountable and unlikely to go away.
|
||||
p If you need help convincing your stakeholder that Heroku is the ideal platform, we'll be happy to talk with them.
|
||||
h2 Maintaining Apps
|
||||
p Once you complete a nonprofit project, your obligation to its stakeholder is finished. You goal is to leave behind a well documented solution that can be easily maintained by a contract JavaScript developer (or even a less-technical "super user").
|
||||
p While you will no longer need to help with feature development, we encourage you to consider helping your stakeholder with occasional patches down the road. After all, this project will be an important piece of your portfolio, and you'll want it to remain in good shape for curious future employers.
|
||||
h2 Office Hours
|
||||
p Quincy Larson and/or Michael Johnson will be in the  
|
||||
a(href="https://gitter.im/FreeCodeCamp/NonprofitProjects") Gitter Nonprofit Project Chatroom  
|
||||
| every Monday and Thursday from 9 - 10 p.m. EST.
|
||||
p Our goal is to make the discussion as public as possible so all our campers can benefit from each camper’s questions.
|
||||
p If necessary, we can also hop on Screen Hero with you to help you with issues more specific to your project.
|
||||
h2 Pledging to finish the project
|
||||
p Your nonprofit stakeholder, your pair, and the volunteer camp counselor team are all counting on you to finish your nonprofit project. If you walk away from an unfinished nonprofit project, you'll become ineligible to ever be assigned another one.
|
||||
p To confirm that you understand the seriousness of this commitment, we require that all campers  
|
||||
a(href="http://goo.gl/forms/ZMn96z2QqY") sign this pledge  
|
||||
| before starting on their nonprofit projects.
|
||||
p There will likely be times of confusion or frustration. This is normal in software development. The most important thing is that you do not give up and instead persevere through these setbacks. As Steve Jobs famously said, "Real artists ship." And you are going to ship one successful nonprofit project after another until you feel ready to take the next step in your promising career.
|
@ -6,8 +6,8 @@ block content
|
||||
a(href="http://links.screenhero.com/e/c/eyJlbWFpbF9pZCI6Ik1qQTNNem9XQkNJQ1pBQUNjd0FYQVZrVEdnRkxNamtfX0JWZEdGVEpSZkVCWlRwbFpXRTBNamM0WVMxaE56SmlMVEV4WlRRdE9HUXpZUzFpWXpVNE1HRTJNalkxTldNNk1UUTJNVEEyQUE9PSIsInBvc2l0aW9uIjowLCJocmVmIjoiaHR0cDovL2RsLnNjcmVlbmhlcm8uY29tL3NtYXJ0ZG93bmxvYWQvZklYQU1UUUJBTEtQQkhQTC9TY3JlZW5oZXJvLnppcD9zb3VyY2U9d2ViIn0=") Download for Mac
|
||||
h2
|
||||
a(href="http://links.screenhero.com/e/c/eyJlbWFpbF9pZCI6Ik1qQTNNem9XQkNJQ1pBQUNjd0FYQVZrVEdnRkxNamtfX0JWZEdGVEpSZkVCWlRwbFpXRTBNamM0WVMxaE56SmlMVEV4WlRRdE9HUXpZUzFpWXpVNE1HRTJNalkxTldNNk1UUTJNVEEyQUE9PSIsInBvc2l0aW9uIjoxLCJocmVmIjoiaHR0cDovL2RsLnNjcmVlbmhlcm8uY29tL3NtYXJ0ZG93bmxvYWQvZklYQU1UUUJBTEtQQkhQTC9TY3JlZW5oZXJvLXNldHVwLmV4ZSJ9") Download for Windows
|
||||
p You can learn more about using Screen Hero by taking
|
||||
p You can learn more about using Screen Hero by taking  
|
||||
a(href="http://www.freecodecamp.com/challenges/34") Challenge 34.
|
||||
p Screen Hero was recently acquired by a collaboration tool called Slack. It's still available and free, but will go away in the indefinite future. Discuss alternatives on our
|
||||
p Screen Hero was recently acquired by a collaboration tool called Slack. It's still available and free, but will go away in the indefinite future. Discuss alternatives on our  
|
||||
a(href="http://forum.freecodecamp.com/t/replacing-screen-hero/992") Screen Hero replacement thread
|
||||
| .
|
@ -7,7 +7,7 @@ block content
|
||||
|
||||
h2#next-session
|
||||
|
||||
h2 Watch the live stream below or on our
|
||||
h2 Watch the live stream below or on our  
|
||||
a(href="http://twitch.tv/freecodecamp", target='_blank') Twitch.tv channel
|
||||
| .
|
||||
.row
|
||||
@ -25,23 +25,23 @@ block content
|
||||
.col-xs-12
|
||||
.embed-responsive.embed-responsive-16by9.big-break
|
||||
iframe.embed-responsive-item(src='//www.youtube.com/embed/_BErpDdmBOw')
|
||||
h3.wrappable link:
|
||||
h3.wrappable link:  
|
||||
a(href="http://www.youtube.com/watch/_BErpDdmBOw") http://www.youtube.com/watch/_BErpDdmBOw
|
||||
.embed-responsive.embed-responsive-16by9.big-break
|
||||
iframe.embed-responsive-item(src='//www.youtube.com/embed/Fn9HMn79KH0')
|
||||
h3.wrappable link:
|
||||
h3.wrappable link:  
|
||||
a(href="http://www.youtube.com/watch/Fn9HMn79KH0") http://www.youtube.com/watch/Fn9HMn79KH0
|
||||
.embed-responsive.embed-responsive-16by9.big-break
|
||||
iframe.embed-responsive-item(src='//www.youtube.com/embed/S7iRBZJwOAs')
|
||||
h3.wrappable link:
|
||||
h3.wrappable link:  
|
||||
a(href="http://www.youtube.com/watch/S7iRBZJwOAs") http://www.youtube.com/watch/S7iRBZJwOAs
|
||||
.embed-responsive.embed-responsive-16by9.big-break
|
||||
iframe.embed-responsive-item(src='//www.youtube.com/embed/BHNRg39ZblE')
|
||||
h3.wrappable link:
|
||||
h3.wrappable link:  
|
||||
a(href="http://www.youtube.com/watch/BHNRg39ZblE") http://www.youtube.com/watch/BHNRg39ZblE
|
||||
.embed-responsive.embed-responsive-16by9.big-break
|
||||
iframe.embed-responsive-item(src='//www.youtube.com/embed/YDfkHlDmehA')
|
||||
h3.wrappable link:
|
||||
h3.wrappable link:  
|
||||
a(href="http://www.youtube.com/watch/YDfkHlDmehA") http://www.youtube.com/watch/YDfkHlDmehA
|
||||
h3 Got 3 minutes? Learn to code with us!
|
||||
a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free)
|
||||
|
@ -8,16 +8,16 @@ block content
|
||||
h3 Please do the following immediately:
|
||||
h4
|
||||
ol
|
||||
li Complete this form:
|
||||
li Complete this form:  
|
||||
a(href="http://goo.gl/forms/f61dLt67t8" target="_blank") http://goo.gl/forms/f61dLt67t8
|
||||
| .
|
||||
li Read this document, which will answer many questions you may have about our nonprofit projects:
|
||||
a(href="http://forum.freecodecamp.com/t/an-introduction-to-our-nonprofit-projects/856" target="_blank") http://forum.freecodecamp.com/t/an-introduction-to-our-nonprofit-projects/856
|
||||
li Read this document, which will answer many questions you may have about our nonprofit projects:  
|
||||
a(href="/guide-to-our-nonprofit-projects" target="_blank") http://freecodecamp.com/guide-to-our-nonprofit-projects
|
||||
| .
|
||||
li We'll send you an invite to our Nonprofit Projects Trello board. Once we do, go there and add yourself to at least 3 nonprofit projects that interest you.
|
||||
li Finish any unfinished Bonfire challenges. These challenges serve as the Free Code Camp "exit test". You must complete these before you can start working on nonprofit projects. If you completed CoderByte or CodeWars challenges instead of Bonfire, email us and we'll take a look:
|
||||
li Finish any unfinished Bonfire challenges. These challenges serve as the Free Code Camp "exit test". You must complete these before you can start working on nonprofit projects. If you completed CoderByte or CodeWars challenges instead of Bonfire, email us and we'll take a look: 
|
||||
a(href="mailto:team@freecodecamp.com") team@freecodecamp.com
|
||||
| .
|
||||
h4 Please email us if you have further questions:
|
||||
h4 Please email us if you have further questions:  
|
||||
a(href="mailto:team@freecodecamp.com") team@freecodecamp.com
|
||||
| .
|
@ -26,7 +26,7 @@ block content
|
||||
<h3>Contacting Us</h3>
|
||||
<p>If you have questions about Free Code Camp, or to correct, update, or remove personally identifiable information, please email us at <a href="mailto:team@freecodecamp.com" target="_blank">team@freecodecamp.com</a>.</p>
|
||||
<h3>Links to Other Web sites</h3>
|
||||
<p>The Free Code Camp sites each contain links to other Web sites. Free Code Camp is not responsible for the privacy practices or content of these third-party Web sites. We urge all FreeCodeCamp.com visitors to follow safe Internet practices: Do not supply Personally Identifiable Information to these Web sites unless you have verified their security and privacy policies.</p>
|
||||
<p>Free Code Camp's sites each contain links to other Web sites. Free Code Camp is not responsible for the privacy practices or content of these third-party Web sites. We urge all FreeCodeCamp.com visitors to follow safe Internet practices: Do not supply Personally Identifiable Information to these Web sites unless you have verified their security and privacy policies.</p>
|
||||
<h3>Data Retention</h3>
|
||||
<p>We retain your information for as long as necessary to permit us to use it for the purposes that we have communicated to you and comply with applicable law or regulations.</p>
|
||||
<h3>Business Transfers</h3>
|
||||
|
124
views/stories/comments.jade
Normal file
124
views/stories/comments.jade
Normal file
@ -0,0 +1,124 @@
|
||||
.text-left
|
||||
div#comment-list
|
||||
|
||||
script(src="https://cdn.jsdelivr.net/ramda/0.10.0/ramda.min.js")
|
||||
script(src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.9.0/moment.min.js")
|
||||
script.
|
||||
var sentinel = 0;
|
||||
var renderComments = function renderComments(comments, containerSelector, level) {
|
||||
var commentDetails;
|
||||
var backgroundColorForCommentNestingLevel = level % 2 !== 0 ? 'odd' : 'even';
|
||||
R.forEach(function displayComments(comment) {
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/stories/comments/' + comment,
|
||||
error: function (xhr, textStatus, errorThrown) {
|
||||
console.log('got error');
|
||||
commentDetails = {
|
||||
error: true,
|
||||
body: 'There seems to be a problem fetching this comment.'
|
||||
}
|
||||
},
|
||||
success: function (data, textStatus, xhr) {
|
||||
commentDetails = data;
|
||||
console.log(commentDetails.commentOn);
|
||||
var div = document.createElement('div');
|
||||
$(div)
|
||||
.html(
|
||||
'<div class="media ' + backgroundColorForCommentNestingLevel + ' media-news">' +
|
||||
'<div class="media-left">' +
|
||||
"<a href='/" + commentDetails.author.username + "'>" +
|
||||
'<img class="media-object img-news" src="' + commentDetails.author.picture +'" alt="' + commentDetails.author.username + '">' +
|
||||
'</a>' +
|
||||
'</div>' +
|
||||
'<div class="media-body comment_' + commentDetails._id + '">' +
|
||||
'<div class="media-body-wrapper">' +
|
||||
'<p>' + commentDetails.body + '</p>' +
|
||||
'<h6>' +
|
||||
'<div class="clearfix comment-a-comment negative-15">' +
|
||||
"commented " + moment(commentDetails.commentOn).fromNow() + " by " +
|
||||
"<a href='/" + commentDetails.author.username + "'>@" + commentDetails.author.username + "</a> · " +
|
||||
"<a id='" + commentDetails._id + "'>Reply</a>" +
|
||||
'</div>' +
|
||||
'</h6>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)
|
||||
.addClass('comment-wrapper positive-10')
|
||||
.appendTo($(containerSelector));
|
||||
|
||||
sentinel += commentDetails.comments.length;
|
||||
|
||||
renderComments(commentDetails.comments, '.comment_' + commentDetails._id, ++level);
|
||||
|
||||
},
|
||||
complete: function () {
|
||||
sentinel--;
|
||||
if (!sentinel) {
|
||||
$('.comment-a-comment').on('click', 'a', function () {
|
||||
$(this).unbind('click');
|
||||
$('.comment-to-comment-formgroup').empty();
|
||||
var div = document.createElement('div');
|
||||
var commentId = $(this).attr('id');
|
||||
$(div).html(
|
||||
"<div class='formgroup comment-to-comment-formgroup'>" +
|
||||
"<textarea class='form-control' id='comment-to-comment-textarea' maxlength='140' autofocus></textarea>" +
|
||||
'<div class="clearfix">' +
|
||||
'<span class="pull-left" id="textarea_feedback"></span>' +
|
||||
"<button class='btn btn-small btn-primary pull-right' id='submit-comment-to-comment'>Submit</button>" +
|
||||
'</div>' +
|
||||
|
||||
"</div>"
|
||||
)
|
||||
.addClass('row')
|
||||
.appendTo($(this).closest('.media-body-wrapper'));
|
||||
var text_max = 140;
|
||||
$('#textarea_feedback').html(text_max + ' characters remaining');
|
||||
$('#comment-to-comment-textarea').keyup(function () {
|
||||
var text_length = $('#comment-to-comment-textarea').val().length;
|
||||
var text_remaining = text_max - text_length;
|
||||
$('#textarea_feedback').html(text_remaining + ' characters remaining');
|
||||
});
|
||||
var submitCommentToCommentHandler = function submitCommentToCommentHandler() {
|
||||
$('#submit-comment-to-comment').unbind('click');
|
||||
$.post('/stories/comment/' + commentId + '/comment',
|
||||
{
|
||||
data: {
|
||||
associatedPost: commentId,
|
||||
body: $('#comment-to-comment-textarea').val(),
|
||||
author: {
|
||||
picture: user.profile.picture,
|
||||
userId: user._id,
|
||||
username: user.profile.username
|
||||
}
|
||||
}
|
||||
})
|
||||
.fail(function (xhr, textStatus, errorThrown) {
|
||||
$('#submit-comment-to-comment').bind('click', submitCommentToCommentHandler);
|
||||
})
|
||||
.done(function (data, textStatus, xhr) {
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$('#submit-comment-to-comment').on('click', submitCommentToCommentHandler);
|
||||
});//
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}, comments);
|
||||
}
|
||||
sentinel += comments.length;
|
||||
|
||||
|
||||
renderComments(comments, $('#comment-list'), 0);
|
||||
|
||||
|
||||
|
||||
|
||||
|
52
views/stories/hot-stories.jade
Normal file
52
views/stories/hot-stories.jade
Normal file
@ -0,0 +1,52 @@
|
||||
.spacer
|
||||
.col-xs-12
|
||||
#story-list.story-list
|
||||
|
||||
script(src="https://cdn.jsdelivr.net/ramda/0.10.0/ramda.min.js")
|
||||
script.
|
||||
|
||||
var getLinkedName = function getLinkedName(name) {
|
||||
return name.trim().toLowerCase().replace(/\s/g, '-');
|
||||
}
|
||||
$.ajax({
|
||||
url: '/stories/hotStories',
|
||||
type: 'GET'
|
||||
})
|
||||
.success(
|
||||
function(data) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var div = document.createElement('div');
|
||||
var linkedName = getLinkedName(data[i].storyLink);
|
||||
var rank = data[i].rank;
|
||||
|
||||
$(div)
|
||||
.html(
|
||||
"<div class='col-xs-12 text-left'>" +
|
||||
"<div class='col-xs-1 col-sm-1 positive-5'>" +
|
||||
(i + 1) +
|
||||
"</div>" +
|
||||
"<div class='col-xs-2 col-sm-1'>" +
|
||||
"<img src='" + data[i].author.picture + "' class='img-news'/>" +
|
||||
"</div>" +
|
||||
"<div class='col-xs-9 col-sm-10'>" +
|
||||
"<div class='row'>" +
|
||||
"<div class='col-xs-12'>" +
|
||||
"<a href='/stories/" + linkedName + "'>"
|
||||
+ data[i].storyLink +
|
||||
"</a>" +
|
||||
"</div>" +
|
||||
"<div class='col-xs-12'>" +
|
||||
"<span>" +
|
||||
rank + " points, posted " +
|
||||
moment(data[i].timePosted).fromNow() +
|
||||
" by <a href='/" + data[i].author.username + "'>@" + data[i].author.username + "</a> " +
|
||||
"</span>" +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"</div>"
|
||||
);
|
||||
$(div).addClass('story-list')
|
||||
$(div).appendTo($('#story-list'));
|
||||
}
|
||||
});
|
30
views/stories/index.jade
Normal file
30
views/stories/index.jade
Normal file
@ -0,0 +1,30 @@
|
||||
extends ../layout
|
||||
block content
|
||||
script(src='/js/lib/moment/moment.js')
|
||||
script.
|
||||
var challengeName = 'Camper News';
|
||||
var user = !{JSON.stringify(user)};
|
||||
var page = !{JSON.stringify(page)};
|
||||
.panel.panel-info
|
||||
.panel-heading.text-center Camper News
|
||||
.panel-body
|
||||
include ./news-nav
|
||||
.spacer
|
||||
if (page === 'hot')
|
||||
include ./hot-stories
|
||||
if (page === 'recent')
|
||||
include ./new-stories
|
||||
if (page === 'submit')
|
||||
if (user)
|
||||
include ./preliminary-submit
|
||||
else
|
||||
.spacer
|
||||
.text-center
|
||||
a.btn.btn-cta.signup-btn.btn-primary(href="/login") Sign in to post your story (it's free)
|
||||
.spacer
|
||||
if (page === 'search')
|
||||
include ./search-stories
|
||||
if (page === 'storySubmission')
|
||||
include ./submit-story
|
||||
if (page === 'show')
|
||||
include ./show
|
52
views/stories/new-stories.jade
Normal file
52
views/stories/new-stories.jade
Normal file
@ -0,0 +1,52 @@
|
||||
.spacer
|
||||
.col-xs-12
|
||||
#story-list.story-list
|
||||
|
||||
script(src="https://cdn.jsdelivr.net/ramda/0.10.0/ramda.min.js")
|
||||
script.
|
||||
|
||||
var getLinkedName = function getLinkedName(name) {
|
||||
return name.trim().toLowerCase().replace(/\s/g, '-');
|
||||
}
|
||||
$.ajax({
|
||||
url: '/stories/recentStories',
|
||||
type: 'GET'
|
||||
})
|
||||
.success(
|
||||
function(data) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var div = document.createElement('div');
|
||||
var linkedName = getLinkedName(data[i].storyLink);
|
||||
var rank = data[i].rank;
|
||||
|
||||
$(div)
|
||||
.html(
|
||||
"<div class='col-xs-12 text-left'>" +
|
||||
"<div class='col-xs-1 col-sm-1 positive-5'>" +
|
||||
(i + 1) +
|
||||
"</div>" +
|
||||
"<div class='col-xs-2 col-sm-1'>" +
|
||||
"<img src='" + data[i].author.picture + "' class='img-news'/>" +
|
||||
"</div>" +
|
||||
"<div class='col-xs-9 col-sm-10'>" +
|
||||
"<div class='row'>" +
|
||||
"<div class='col-xs-12'>" +
|
||||
"<a href='/stories/" + linkedName + "'>"
|
||||
+ data[i].storyLink +
|
||||
"</a>" +
|
||||
"</div>" +
|
||||
"<div class='col-xs-12'>" +
|
||||
"<span>" +
|
||||
rank + " points, posted " +
|
||||
moment(data[i].timePosted).fromNow() +
|
||||
" by <a href='/" + data[i].author.username + "'>@" + data[i].author.username + "</a> " +
|
||||
"</span>" +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"</div>"
|
||||
);
|
||||
$(div).addClass('story-list')
|
||||
$(div).appendTo($('#story-list'));
|
||||
}
|
||||
});
|
40
views/stories/news-nav.jade
Normal file
40
views/stories/news-nav.jade
Normal file
@ -0,0 +1,40 @@
|
||||
.col-xs-12
|
||||
.visible-xs.visible-sm
|
||||
.btn-group.input-group.btn-group-justified(data-toggle='buttons')
|
||||
label.btn.btn-primary.btn-big(class="#{ page === 'hot' ? 'active' : '' }")
|
||||
input#option1(href='/stories/hot', type='radio', name='options')
|
||||
i.ion-ios-pulse-strong
|
||||
label.btn.btn-primary.btn-big(class="#{ page === 'recent' ? 'active' : '' }")
|
||||
input#option2(href='/stories/recent', type='radio', name='options')
|
||||
i.ion-ios-clock
|
||||
label.btn.btn-primary.btn-big(class="#{ page === 'submit' ? 'active' : page === 'storySubmission' ? 'active' : '' }")
|
||||
input#option3(href='/stories/submit', type='radio', name='options')
|
||||
i.ion-plus-circled
|
||||
label.btn.btn-primary.btn-big(class="#{ page === 'search' ? 'active' : '' }")
|
||||
input#option4(href='/stories/search', type='radio', name='options')
|
||||
i.ion-search
|
||||
.visible-md.visible-lg
|
||||
.btn-group.input-group.btn-group-justified(data-toggle='buttons')
|
||||
label.btn.btn-primary.btn-big(class="#{ page === 'hot' ? 'active' : '' }")
|
||||
input#option1(href='/stories/hot', type='radio', name='options')
|
||||
i.ion-ios-pulse-strong  
|
||||
| Hot
|
||||
label.btn.btn-primary.btn-big(class="#{ page === 'recent' ? 'active' : '' }")
|
||||
input#option2(href='/stories/recent', type='radio', name='options')
|
||||
i.ion-ios-clock  
|
||||
| New
|
||||
label.btn.btn-primary.btn-big(class="#{ page === 'submit' ? 'active' : page === 'storySubmission' ? 'active' : '' }")
|
||||
input#option3(href='/stories/submit', type='radio', name='options')
|
||||
i.ion-plus-circled  
|
||||
| Submit
|
||||
label.btn.btn-primary.btn-big(class="#{ page === 'search' ? 'active' : '' }")
|
||||
input#option4(href='/stories/search', type='radio', name='options')
|
||||
i.ion-search  
|
||||
| Search
|
||||
|
||||
script.
|
||||
$('label').on('click', function() {
|
||||
window.location = ($(this).children('input').attr('href'));
|
||||
});
|
||||
|
||||
.spacer
|
47
views/stories/preliminary-submit.jade
Normal file
47
views/stories/preliminary-submit.jade
Normal file
@ -0,0 +1,47 @@
|
||||
.col-xs-12
|
||||
.spacer
|
||||
div(ng-controller="URLSubmitController")
|
||||
|
||||
form.input-group(name='URLSubmit')
|
||||
input#story-url.big-text-field.field-responsive.form-control(placeholder='Paste your link here', name='link', type='url', ng-model='possibleURL' autofocus)
|
||||
span.input-group-btn
|
||||
button#preliminary-story-submit.btn.btn-big.btn-primary.btn-responsive(type='button', ng-disabled='URLSubmit.link.$invalid') Submit
|
||||
div(ng-show="URLSubmit.link.$error.url && !URLsubmit.link.$pristine")
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Please enter a valid URL format (http://www.example.com/).
|
||||
.spacer
|
||||
|
||||
script.
|
||||
$('#story-url').on('keypress', function(e) {
|
||||
if (e.which === 13 || e === 13) {
|
||||
console.log('enter pressed');
|
||||
$('#preliminary-story-submit').click();
|
||||
}
|
||||
});
|
||||
var preliminaryStorySubmit = function preliminaryStorySubmit() {
|
||||
|
||||
var storyURL = $('#story-url').val();
|
||||
console.log(storyURL);
|
||||
$('#preliminary-story-submit').attr('disabled', 'disabled');
|
||||
|
||||
$.post('/stories/preliminary',
|
||||
{
|
||||
data: {
|
||||
url: storyURL
|
||||
}
|
||||
})
|
||||
.fail(function (xhr, textStatus, errorThrown) {
|
||||
$('#preliminary-story-submit').attr('disabled', false);
|
||||
})
|
||||
.done(function (data, textStatus, xhr) {
|
||||
if (data.alreadyPosted) {
|
||||
window.location = '/stories/' + data.storyURL;
|
||||
} else {
|
||||
window.location = '/stories/submit/url=' +
|
||||
encodeURIComponent(data.storyURL) +
|
||||
'&title=' + encodeURIComponent(data.storyTitle);
|
||||
}
|
||||
});
|
||||
}
|
||||
$('#preliminary-story-submit').on('click', preliminaryStorySubmit);
|
74
views/stories/search-stories.jade
Normal file
74
views/stories/search-stories.jade
Normal file
@ -0,0 +1,74 @@
|
||||
.col-xs-12
|
||||
.spacer
|
||||
.input-group
|
||||
input#searchArea.big-text-field.field-responsive.form-control(type='text', placeholder='Search our stories', autofocus)
|
||||
span.input-group-btn
|
||||
button#searchbutton.btn.btn-big.btn-primary.btn-responsive(type='button') Search
|
||||
.spacer
|
||||
|
||||
#story-list
|
||||
ul#stories
|
||||
script.
|
||||
$('#searchArea').keypress(function(event) {
|
||||
if (event.keyCode === 13 || event.which === 13) {
|
||||
executeSearch();
|
||||
$('#searchArea').focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
$('#searchbutton').on('click', function() {
|
||||
executeSearch();
|
||||
});
|
||||
function executeSearch() {
|
||||
$('#stories').empty();
|
||||
console.log('clicked or enter button');
|
||||
var searchTerm = $('#searchArea').val();
|
||||
var getLinkedName = function getLinkedName(name) {
|
||||
return name.toLowerCase().replace(/\s/g, '-');
|
||||
}
|
||||
$.post('/stories/search',
|
||||
{
|
||||
data: {
|
||||
searchValue: searchTerm
|
||||
}
|
||||
})
|
||||
.fail(function (xhr, textStatus, errorThrown) {
|
||||
console.log('failure');
|
||||
})
|
||||
.done(function (data, textStatus, xhr) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var li = document.createElement('li');
|
||||
var linkedName = getLinkedName(data[i].storyLink);
|
||||
var rank = data[i].rank;
|
||||
|
||||
$(li).html(
|
||||
"<div class='row text-left'>" +
|
||||
"<div class='col-xs-3 col-sm-1'>" +
|
||||
"<div class='story-up-votes'>" +
|
||||
"<span>" + rank + "</span>" +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"<div class='col-xs-2 col-sm-1'>" +
|
||||
"<img src='" + data[i].author.picture + "' class='img-responsive'/>" +
|
||||
"</div>" +
|
||||
"<div class='col-xs-7 col-sm-10'>" +
|
||||
"<div class='row'>" +
|
||||
"<div class='col-xs-12'>" +
|
||||
"<a href='/stories/" + linkedName + "'>"
|
||||
+ data[i].storyLink +
|
||||
"</a>" +
|
||||
"</div>" +
|
||||
"<div class='col-xs-12'>" +
|
||||
"<span>Posted " +
|
||||
moment(data[i].timePosted).fromNow() +
|
||||
" by <a href='/" + data[i].author.username + "'>@" + data[i].author.username + "</a> " +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"</li>");
|
||||
$(li).appendTo($('#stories'));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
55
views/stories/show.jade
Normal file
55
views/stories/show.jade
Normal file
@ -0,0 +1,55 @@
|
||||
.spacer
|
||||
script.
|
||||
var challengeName = 'Camper News';
|
||||
var storyId = !{JSON.stringify(id)};
|
||||
var comments = !{JSON.stringify(comments)};
|
||||
var upVotes = !{JSON.stringify(upVotes)};
|
||||
var user = !{JSON.stringify(user)};
|
||||
.spacer
|
||||
|
||||
h3.row.col-xs-12
|
||||
.row.text-left.negative-10
|
||||
.col-xs-3.col-sm-1.text-center
|
||||
.row.negative-5
|
||||
.col-xs-12.big-ion-up-arrow
|
||||
a#upvote
|
||||
i.ion-arrow-up-b
|
||||
h3.story-up-votes
|
||||
span#storyRank= rank
|
||||
.col-xs-2.col-sm-1
|
||||
a(href="/" + author.username)
|
||||
img(src="#{author.picture}", class='img-news')
|
||||
.col-xs-7.col-sm-10
|
||||
|
||||
.col-xs-12.negative-28
|
||||
a(href="#{link}")
|
||||
h3= title
|
||||
h6
|
||||
.col-xs-12.negative-28
|
||||
h4= description
|
||||
.negative-5
|
||||
span Posted #{timeAgo}
|
||||
span  by 
|
||||
a(href="/" + author.username)@#{author.username}
|
||||
| ·
|
||||
a#reply-to-main-post Reply
|
||||
.col-xs-12#reply-area
|
||||
|
||||
|
||||
|
||||
.hidden-element#initial-comment-submit
|
||||
form.form-horizontal.control-label-story-submission
|
||||
.col-xs-12
|
||||
.form-group
|
||||
h3.row
|
||||
textarea#comment-box.form-control(name="comment-box", rows=5)
|
||||
h3.row.text-center
|
||||
.col-xs-6.col-xs-offset-3
|
||||
button.btn.btn-block.btn-primary#comment-button Comment
|
||||
|
||||
script.
|
||||
$('#reply-to-main-post').on('click', function() {
|
||||
$('#initial-comment-submit').removeClass('hidden-element');
|
||||
$(this).unbind('click');
|
||||
});
|
||||
include ./comments
|
29
views/stories/submit-story.jade
Normal file
29
views/stories/submit-story.jade
Normal file
@ -0,0 +1,29 @@
|
||||
.spacer
|
||||
.col-xs-12
|
||||
script.
|
||||
var storyURL = !{JSON.stringify(storyURL)};
|
||||
var storyTitle = !{JSON.stringify(storyTitle)};
|
||||
form.form-horizontal.control-label-story-submission#story-submission-form
|
||||
.col-xs-12
|
||||
.form-group
|
||||
.col-xs-12.col-md-1
|
||||
label.control-label.control-label-story-submission(for='name') Link
|
||||
.col-xs-12.col-md-11
|
||||
input#story-url.form-control(placeholder='Paste your link here', name='Link')
|
||||
.form-group
|
||||
.col-xs-12.col-md-1
|
||||
label.control-label.control-label-story-submission(for='name') Title
|
||||
.col-xs-12.col-md-11
|
||||
input#story-title.form-control(placeholder='Type a headline for your link here', name='Title', autofocus)
|
||||
.form-group
|
||||
.col-xs-12.col-md-1
|
||||
label.control-label.control-label-story-submission(for='name') Description
|
||||
.col-xs-12.col-md-11
|
||||
textarea#description-box.form-control(name="comment-box", rows=5, placeholder="Start off the discussion with a description of your post")
|
||||
|
||||
.spacer
|
||||
.form-group
|
||||
button.btn.btn-big.btn-block.btn-primary#story-submit Submit
|
||||
script.
|
||||
$('#story-url').val(storyURL).attr('disabled', 'disabled');
|
||||
$('#story-title').val(storyTitle);
|
Reference in New Issue
Block a user