Merge branch 'staging' of github.com:FreeCodeCamp/freecodecamp into staging

This commit is contained in:
Quincy Larson
2015-07-31 21:13:46 -07:00
20 changed files with 75 additions and 706 deletions

View File

@@ -1,44 +0,0 @@
{
"name": "bonfire",
"base": "PersistedModel",
"idInjection": true,
"trackChanges": false,
"properties": {
"name": {
"type": "string",
"unique": true
},
"difficulty": {
"type": "string"
},
"description": {
"type": "array"
},
"tests": {
"type": "array"
},
"challengeSeed": {
"type": "array"
},
"MDNlinks": {
"type": "array"
}
},
"validations": [],
"relations": {},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
],
"methods": []
}

View File

@@ -1,68 +0,0 @@
{
"name": "comment",
"base": "PersistedModel",
"idInjection": true,
"trackChanges": false,
"properties": {
"associatedPost": {
"type": "string",
"required": true
},
"originalStoryLink": {
"type": "string",
"default": ""
},
"originalStoryAuthorEmail": {
"type": "string",
"default": ""
},
"body": {
"type": "string",
"default": ""
},
"rank": {
"type": "number",
"default": 0
},
"upvotes": {
"type": "array",
"default": []
},
"author": {
"type": {},
"default": {}
},
"comments": {
"type": "array",
"default": []
},
"commentOn": {
"type": "number",
"defaultFn": "now"
}
},
"validations": [],
"relations": {},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "create"
}
],
"methods": []
}

View File

@@ -44,10 +44,6 @@
"author": { "author": {
"type": {} "type": {}
}, },
"comments": {
"type": "array",
"default": []
},
"image": { "image": {
"type": "string", "type": "string",
"default": "" "default": ""

View File

@@ -126,7 +126,7 @@ gulp.task('serve', function(cb) {
script: paths.server, script: paths.server,
ext: '.js .json', ext: '.js .json',
ignore: paths.serverIgnore, ignore: paths.serverIgnore,
exec: './node_modules/.bin/babel-node', exec: path.join(__dirname, 'node_modules/.bin/babel-node'),
env: { env: {
'NODE_ENV': 'development', 'NODE_ENV': 'development',
'DEBUG': process.env.DEBUG || 'freecc:*' 'DEBUG': process.env.DEBUG || 'freecc:*'

View File

@@ -277,34 +277,32 @@ $(document).ready(function() {
$('#long-instructions').hide(); $('#long-instructions').hide();
}); });
var upvoteHandler = function () { function upvoteHandler(e) {
var id = storyId; e.preventDefault();
$('#upvote').unbind('click'); var upvoteBtn = this;
var id = upvoteBtn.id;
var upVotes = $(upvoteBtn).data().upVotes;
var username = typeof username !== 'undefined' ? username : '';
var alreadyUpvoted = false; var alreadyUpvoted = false;
for (var i = 0; i < upVotes.length; i++) { for (var i = 0; i < upVotes.length; i++) {
if (upVotes[i].upVotedBy === B3BA669EC5C1DD70FB478221E067A7E1B686929C569F5E73561B69C8F42129B) { if (upVotes[i].upVotedBy === username) {
alreadyUpvoted = true; alreadyUpvoted = true;
break; break;
} }
} }
if (!alreadyUpvoted) { if (!alreadyUpvoted) {
$.post('/stories/upvote', $.post('/stories/upvote', { id: id })
{ .fail(function(xhr, textStatus, errorThrown) {
data: { $(upvoteBtn).bind('click', upvoteHandler);
id: id
}
}) })
.fail(function (xhr, textStatus, errorThrown) { .done(function(data, textStatus, xhr) {
$('#upvote').bind('click', upvoteHandler); $(upvoteBtn).text('Upvoted!').addClass('disabled');
})
.done(function (data, textStatus, xhr) {
$('#upvote').text('Upvoted!').addClass('disabled');
$('#storyRank').text(data.rank + " points"); $('#storyRank').text(data.rank + " points");
}); });
} }
}; };
$('#upvote').on('click', upvoteHandler); $('#story-list').on('click', 'button.btn-upvote', upvoteHandler);
var storySubmitButtonHandler = function storySubmitButtonHandler() { var storySubmitButtonHandler = function storySubmitButtonHandler() {
@@ -322,7 +320,6 @@ $(document).ready(function() {
description: description, description: description,
storyMetaDescription: storyMetaDescription, storyMetaDescription: storyMetaDescription,
rank: 1, rank: 1,
comments: [],
image: storyImage image: storyImage
} }
}) })
@@ -336,29 +333,6 @@ $(document).ready(function() {
$('#story-submit').on('click', storySubmitButtonHandler); $('#story-submit').on('click', storySubmitButtonHandler);
var commentSubmitButtonHandler = function commentSubmitButtonHandler() {
$('#comment-button').unbind('click');
var data = $('#comment-box').val();
$('#comment-button').attr('disabled', 'disabled');
$.post('/stories/comment/',
{
data: {
associatedPost: storyId,
originalStoryLink: originalStoryLink,
originalStoryAuthorEmail: originalStoryAuthorEmail,
body: data
}
})
.fail(function (xhr, textStatus, errorThrown) {
$('#comment-button').attr('disabled', false);
$('#comment-button').bind('click', commentSubmitButtonHandler);
})
.done(function (data, textStatus, xhr) {
window.location.reload();
});
};
//fakeiphone positioning hotfix //fakeiphone positioning hotfix
if($('.iphone-position').html() !==undefined || $('.iphone').html() !== undefined){ if($('.iphone-position').html() !==undefined || $('.iphone').html() !== undefined){
var startIphonePosition = parseInt($('.iphone-position').css('top').replace('px', '')); var startIphonePosition = parseInt($('.iphone-position').css('top').replace('px', ''));
@@ -405,9 +379,7 @@ $(document).ready(function() {
lockTop(initOff); lockTop(initOff);
}); });
} }
} }
$('#comment-button').on('click', commentSubmitButtonHandler);
}); });
var profileValidation = angular.module('profileValidation', var profileValidation = angular.module('profileValidation',

View File

@@ -448,7 +448,7 @@
"Let's now go create a nested array called <code>myArray</code>" "Let's now go create a nested array called <code>myArray</code>"
], ],
"tests":[ "tests":[
"assert((function(){if(typeof(myArray) !== 'undefined' && typeof(myArray) === 'object' && typeof(myArray[0]) !== 'undefined' && typeof(myArray) === 'object'){return(true);}else{return(false);}})(), 'myArray should contain at least one array');" "assert((function(){if(typeof(myArray) !== 'undefined' && typeof(myArray) === 'object' && typeof(myArray[0]) !== 'undefined' && typeof(myArray[0]) === 'object' && editor.getValue().match(/[[]]/g).length >= 1){return(true);}else{return(false);}})(), 'myArray should contain at least one array');"
], ],
"challengeSeed":[ "challengeSeed":[
"var myArray = [];", "var myArray = [];",
@@ -548,7 +548,7 @@
"//console.log(removed); //Gives 3", "//console.log(removed); //Gives 3",
"", "",
"var myArray = ['John', 23, ['cat', 2]];", "var myArray = ['John', 23, ['cat', 2]];",
"var removed = _;//This should be ['cat', 2] and myArray should now be ['John', 23]", "var removed = myArray;//This should be ['cat', 2] and myArray should now be ['John', 23]",
"", "",
"", "",
"(function(y, z){return('myArray = ' + JSON.stringify(y) + ' & removed = ' + JSON.stringify(z));})(myArray, removed);" "(function(y, z){return('myArray = ' + JSON.stringify(y) + ' & removed = ' + JSON.stringify(z));})(myArray, removed);"
@@ -566,7 +566,7 @@
"Let's take the code we had last time and <code> push </code> this value to the end: <code> ['dog', 3] </code>" "Let's take the code we had last time and <code> push </code> this value to the end: <code> ['dog', 3] </code>"
], ],
"tests": [ "tests": [
"assert((function(d){if(d[2] != undefined && d[0] == 'John' && d[1] == 23 && d[2][0] == 'dog' && d[2][1] == 3){return(true);}else{return(false);}})(myArray), 'myArray should only have the first two values left([\"John\", 23, [\"dog\", 3]])');" "assert((function(d){if(d[2] != undefined && d[0] == 'John' && d[1] == 23 && d[2][0] == 'dog' && d[2][1] == 3 && d[2].length == 2){return(true);}else{return(false);}})(myArray), 'myArray should only have the first two values left([\"John\", 23, [\"dog\", 3]])');"
], ],
"challengeSeed": [ "challengeSeed": [
"var myArray = ['John', 23, ['cat', 2]];", "var myArray = ['John', 23, ['cat', 2]];",
@@ -595,7 +595,7 @@
], ],
"challengeSeed": [ "challengeSeed": [
"var myArray = ['John', 23, ['dog', 3]];", "var myArray = ['John', 23, ['dog', 3]];",
"var removed = _;//This should be ['John'] and myArray should now be ['John', 23]", "var removed = myArray;//This should be ['John'] and myArray should now be ['John', 23]",
"", "",
"", "",
"(function(y, z){return('myArray = ' + JSON.stringify(y) + ' & removed = ' + JSON.stringify(z));})(myArray, removed);" "(function(y, z){return('myArray = ' + JSON.stringify(y) + ' & removed = ' + JSON.stringify(z));})(myArray, removed);"
@@ -644,7 +644,7 @@
"Let's try creating and calling a function now called <code>myFunction</code>" "Let's try creating and calling a function now called <code>myFunction</code>"
], ],
"tests":[ "tests":[
"assert((function(){if(typeof(f) !== 'undefined' && typeof(f) === 'number' && f === 9){return(true);}else{return(false);}})(), 'Your function should return the value of a + b');" "assert((function(){if(typeof(f) !== 'undefined' && typeof(f) === 'number' && f === a + b && editor.getValue().match(RegExp('return\\\\(a\\\\+b\\\\)', 'g')).length >= 1){return(true);}else{return(false);}})(), 'Your function should return the value of a + b');"
], ],
"challengeSeed":[ "challengeSeed":[
"var a = 4;", "var a = 4;",
@@ -726,7 +726,7 @@
"" ""
], ],
"tests":[ "tests":[
"assert(myDog.bark != undefined, 'The property tails should have been deleted');", "assert(myDog.bark != undefined, 'You should have added the property bark to myDog');",
"assert(myDog.tails == undefined, 'The property tails should have been deleted');" "assert(myDog.tails == undefined, 'The property tails should have been deleted');"
], ],
"challengeSeed":[ "challengeSeed":[
@@ -740,9 +740,9 @@
"//Re-create myDog", "//Re-create myDog",
"", "",
"var myDog = {", "var myDog = {",
" \"name\": _,", " \"name\": 'Camper',",
" \"legs\": _,", " \"legs\": 4,",
" \"tails\": _,", " \"tails\": 1,",
" \"friends\": []", " \"friends\": []",
"};", "};",
"", "",
@@ -1021,7 +1021,7 @@
], ],
"tests":[ "tests":[
"assert(test === 2, 'Your RegEx should have found two numbers in the testString');", "assert(test === 2, 'Your RegEx should have found two numbers in the testString');",
"assert(editor.getValue().match(/\\/\\\\d\\+\\//gi), 'You should be using the following expression /\\d+/gi to find the numbers in the testString');" "assert(editorValue.match(/\\/\\\\d\\+\\//gi), 'You should be using the following expression /\\d+/gi to find the numbers in the testString');"
], ],
"challengeSeed":[ "challengeSeed":[
"var test = (function(){", "var test = (function(){",
@@ -1052,7 +1052,7 @@
], ],
"tests":[ "tests":[
"assert(test === 7, 'Your RegEx should have found seven spaces in the testString');", "assert(test === 7, 'Your RegEx should have found seven spaces in the testString');",
"assert(editor.getValue().match(/\\/\\\\s\\+\\//gi), 'You should be using the following expression /\\s+/gi to find the spaces in the testString');" "assert(editorValue.match(/\\/\\\\s\\+\\//gi), 'You should be using the following expression /\\s+/gi to find the spaces in the testString');"
], ],
"challengeSeed":[ "challengeSeed":[
"var test = (function(){", "var test = (function(){",
@@ -1083,14 +1083,14 @@
"<code> Math.floor(Math.random() * (5 - 1 + 1)) + 1; </code>" "<code> Math.floor(Math.random() * (5 - 1 + 1)) + 1; </code>"
], ],
"tests":[ "tests":[
"assert(typeof(runSlots($(''))[0]) == 'number', 'SlotOne should be a random number');", "assert(typeof(runSlots($('.slot'))[0]) == 'number', 'SlotOne should be a random number');",
"assert(typeof(runSlots($(''))[1]) == 'number', 'SlotTwo should be a random number');", "assert(typeof(runSlots($('.slot'))[1]) == 'number', 'SlotTwo should be a random number');",
"assert(typeof(runSlots($(''))[2]) == 'number', 'SlotThree should be a random number');", "assert(typeof(runSlots($('.slot'))[2]) == 'number', 'SlotThree should be a random number');",
"assert(editor.getValue().match(/Math.floor\\(Math.random\\(\\) \\* \\(5 \\- 1 \\+ 1\\)\\) \\+ 1/g).length === 3);" "assert(editorValue.match(/Math.floor\\(Math.random\\(\\) \\* \\(5 \\- 1 \\+ 1\\)\\) \\+ 1/g).length === 3);"
], ],
"challengeSeed":[ "challengeSeed":[
"fccss", "fccss",
" function runSlots(slots){", " function runSlots(){",
" var slotOne;", " var slotOne;",
" var slotTwo;", " var slotTwo;",
" var slotThree;", " var slotThree;",
@@ -1111,7 +1111,7 @@
"", "",
" $(document).ready(function(){", " $(document).ready(function(){",
" $('.go').click(function(){", " $('.go').click(function(){",
" runSlots(slots);", " runSlots();",
" });", " });",
" });", " });",
"fcces", "fcces",

View File

@@ -254,9 +254,8 @@
"Click \"News\" in the upper right hand corner.", "Click \"News\" in the upper right hand corner.",
"You'll see a variety of links that have been submitted. Click on the \"Discuss\" button under one of them.", "You'll see a variety of links that have been submitted. Click on the \"Discuss\" button under one of them.",
"You can upvote links. This will push the link up the rankings of hot links.", "You can upvote links. This will push the link up the rankings of hot links.",
"You can also comment on a link. If someone responds to your comment, you'll get an email notification so you can come back and respond to them.", "You can also submit links.",
"You can also submit links. You can modify the link's headline and also leave an initial comment about the link.", "You can view the portfolio pages of any camper who has posted links on Camper News. Just click on their photo.",
"You can view the portfolio pages of any camper who has posted links or comments on Camper News. Just click on their photo.",
"When you submit a link, you'll get a point. You will also get a point each time someone upvotes your link.", "When you submit a link, you'll get a point. You will also get a point each time someone upvotes your link.",
"Now that you've learned how to use Camper News, let's move on to your next challenge." "Now that you've learned how to use Camper News, let's move on to your next challenge."
], ],

View File

@@ -18,7 +18,7 @@
"<span class='text-info'>User Story:</span> As a user, I can click on a post to be taken to the story's original URL.", "<span class='text-info'>User Story:</span> As a user, I can click on a post to be taken to the story's original URL.",
"<span class='text-info'>User Story:</span> As a user, I can click a link to go directly to the post's discussion page.", "<span class='text-info'>User Story:</span> As a user, I can click a link to go directly to the post's discussion page.",
"<span class='text-info'>Bonus User Story:</span> As a user, I can see how many upvotes each story has.", "<span class='text-info'>Bonus User Story:</span> As a user, I can see how many upvotes each story has.",
"<span class='text-info'>Hint:</span> Here's the Camper News Hot Stories API endpoint: <code>http://www.freecodecamp.com/stories/hotStories</code>.", "<span class='text-info'>Hint:</span> Here's the Camper News Hot Stories API endpoint: <code>http://www.freecodecamp.com/news/hot</code>.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck.", "Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.", "When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>" "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"

View File

@@ -1,5 +1,4 @@
var Rx = require('rx'), var Rx = require('rx'),
nodemailer = require('nodemailer'),
assign = require('object.assign'), assign = require('object.assign'),
sanitizeHtml = require('sanitize-html'), sanitizeHtml = require('sanitize-html'),
moment = require('moment'), moment = require('moment'),
@@ -19,23 +18,6 @@ var unDasherize = utils.unDasherize;
var dasherize = utils.dasherize; var dasherize = utils.dasherize;
var getURLTitle = utils.getURLTitle; var getURLTitle = utils.getURLTitle;
var transporter = nodemailer.createTransport({
service: 'Mandrill',
auth: {
user: secrets.mandrill.user,
pass: secrets.mandrill.password
}
});
function sendMailWhillyNilly(mailOptions) {
transporter.sendMail(mailOptions, function(err) {
if (err) {
console.log('err sending mail whilly nilly', err);
console.log('logging err but not carring');
}
});
}
function hotRank(timeValue, rank) { function hotRank(timeValue, rank) {
/* /*
* Hotness ranking algorithm: http://amix.dk/blog/post/19588 * Hotness ranking algorithm: http://amix.dk/blog/post/19588
@@ -69,7 +51,6 @@ module.exports = function(app) {
var router = app.loopback.Router(); var router = app.loopback.Router();
var User = app.models.User; var User = app.models.User;
var findUserById = observeMethod(User, 'findById'); var findUserById = observeMethod(User, 'findById');
var findOneUser = observeMethod(User, 'findOne');
var Story = app.models.Story; var Story = app.models.Story;
var findStory = observeMethod(Story, 'find'); var findStory = observeMethod(Story, 'find');
@@ -77,14 +58,8 @@ module.exports = function(app) {
var findStoryById = observeMethod(Story, 'findById'); var findStoryById = observeMethod(Story, 'findById');
var countStories = observeMethod(Story, 'count'); var countStories = observeMethod(Story, 'count');
var Comment = app.models.Comment; router.get('/news/hot', hotJSON);
var findCommentById = observeMethod(Comment, 'findById');
router.get('/stories/hotStories', hotJSON); router.get('/stories/hotStories', hotJSON);
router.get('/stories/comments/:id', comments);
router.post('/stories/comment/', commentSubmit);
router.post('/stories/comment/:id/comment', commentOnCommentSubmit);
router.put('/stories/comment/:id/edit', commentEdit);
router.get('/stories/submit', submitNew); router.get('/stories/submit', submitNew);
router.get('/stories/submit/new-story', preSubmit); router.get('/stories/submit/new-story', preSubmit);
router.post('/stories/preliminary', newStory); router.post('/stories/preliminary', newStory);
@@ -194,7 +169,6 @@ module.exports = function(app) {
description: story.description, description: story.description,
rank: story.upVotes.length, rank: story.upVotes.length,
upVotes: story.upVotes, upVotes: story.upVotes,
comments: story.comments,
id: story.id, id: story.id,
timeAgo: moment(story.timePosted).fromNow(), timeAgo: moment(story.timePosted).fromNow(),
image: story.image, image: story.image,
@@ -224,7 +198,6 @@ module.exports = function(app) {
rank: 1, rank: 1,
upVotes: 1, upVotes: 1,
author: 1, author: 1,
comments: 1,
image: 1, image: 1,
storyLink: 1, storyLink: 1,
metaDescription: 1, metaDescription: 1,
@@ -250,7 +223,7 @@ module.exports = function(app) {
} }
function upvote(req, res, next) { function upvote(req, res, next) {
var id = req.body.data.id; const { id } = req.body;
var story$ = findStoryById(id).shareReplay(); var story$ = findStoryById(id).shareReplay();
story$.flatMap(function(story) { story$.flatMap(function(story) {
@@ -284,16 +257,6 @@ module.exports = function(app) {
); );
} }
function comments(req, res, next) {
var id = req.params.id;
findCommentById(id).subscribe(
function(comment) {
res.send(comment);
},
next
);
}
function newStory(req, res, next) { function newStory(req, res, next) {
if (!req.user) { if (!req.user) {
return next(new Error('Must be logged in')); return next(new Error('Must be logged in'));
@@ -407,7 +370,6 @@ module.exports = function(app) {
username: req.user.username, username: req.user.username,
email: req.user.email email: req.user.email
}, },
comments: [],
image: data.image, image: data.image,
storyLink: storyLink, storyLink: storyLink,
metaDescription: data.storyMetaDescription, metaDescription: data.storyMetaDescription,
@@ -428,174 +390,4 @@ module.exports = function(app) {
next next
); );
} }
function commentSubmit(req, res, next) {
var data = req.body.data;
if (!req.user) {
return next(new Error('Not authorized'));
}
var sanitizedBody = cleanData(data.body);
if (data.body !== sanitizedBody) {
req.flash('errors', {
msg: 'HTML is not allowed'
});
return res.send(true);
}
var comment = new Comment({
associatedPost: data.associatedPost,
originalStoryLink: data.originalStoryLink,
originalStoryAuthorEmail: data.originalStoryAuthorEmail,
body: sanitizedBody,
rank: 0,
upvotes: 0,
author: {
picture: req.user.picture,
userId: req.user.id,
username: req.user.username,
email: req.user.email
},
comments: [],
topLevel: true,
commentOn: Date.now()
});
commentSave(comment, findStoryById).subscribe(
function() {},
next,
function() {
res.send(true);
}
);
}
function commentOnCommentSubmit(req, res, next) {
var data = req.body.data;
if (!req.user) {
return next(new Error('Not authorized'));
}
var sanitizedBody = cleanData(data.body);
if (data.body !== sanitizedBody) {
req.flash('errors', {
msg: 'HTML is not allowed'
});
return res.send(true);
}
var comment = new Comment({
associatedPost: data.associatedPost,
body: sanitizedBody,
rank: 0,
upvotes: 0,
originalStoryLink: data.originalStoryLink,
originalStoryAuthorEmail: data.originalStoryAuthorEmail,
author: {
picture: req.user.picture,
userId: req.user.id,
username: req.user.username,
email: req.user.email
},
comments: [],
topLevel: false,
commentOn: Date.now()
});
commentSave(comment, findCommentById).subscribe(
function() {},
next,
function() {
res.send(true);
}
);
}
function commentEdit(req, res, next) {
findCommentById(req.params.id)
.doOnNext(function(comment) {
if (!req.user && comment.author.userId !== req.user.id) {
throw new Error('Not authorized');
}
})
.flatMap(function(comment) {
var sanitizedBody = cleanData(req.body.body);
if (req.body.body !== sanitizedBody) {
req.flash('errors', {
msg: 'HTML is not allowed'
});
}
comment.body = sanitizedBody;
comment.commentOn = Date.now();
return saveInstance(comment);
})
.subscribe(
function() {
res.send(true);
},
next
);
}
function commentSave(comment, findContextById) {
return saveInstance(comment)
.flatMap(function(comment) {
// Based on the context retrieve the parent
// object of the comment (Story/Comment)
return findContextById(comment.associatedPost);
})
.flatMap(function(associatedContext) {
if (associatedContext) {
associatedContext.comments.push(comment.id);
}
// NOTE(berks): saveInstance is safe
// it will automatically call onNext with null and onCompleted if
// argument is falsey or has no method save
return saveInstance(associatedContext);
})
.flatMap(function(associatedContext) {
// Find the author of the parent object
// if no username
var username = associatedContext && associatedContext.author ?
associatedContext.author.username :
null;
var query = { where: { username: username } };
return findOneUser(query);
})
// if no user is found we don't want to hit the doOnNext
// filter here will call onCompleted without running through the following
// steps
.filter(function(user) {
return !!user;
})
// if this is called user is guarenteed to exits
// this is a side effect, hence we use do/tap observable methods
.doOnNext(function(user) {
// If the emails of both authors differ,
// only then proceed with email notification
if (
comment.author &&
comment.author.email &&
user.email &&
(comment.author.email !== user.email)
) {
sendMailWhillyNilly({
to: user.email,
from: 'Team@freecodecamp.com',
subject: comment.author.username +
' replied to your post on Camper News',
text: [
'Just a quick heads-up: ',
comment.author.username,
' replied to you on Camper News.',
'You can keep this conversation going.',
'Just head back to the discussion here: ',
'http://freecodecamp.com/stories/',
comment.originalStoryLink,
'- the Free Code Camp Volunteer Team'
].join('\n')
});
}
});
}
}; };

View File

@@ -12,7 +12,6 @@ module.exports = function(app) {
var router = app.loopback.Router(); var router = app.loopback.Router();
var User = app.models.User; var User = app.models.User;
var Story = app.models.Story; var Story = app.models.Story;
var Comment = app.models.Comment;
router.get('/login', function(req, res) { router.get('/login', function(req, res) {
res.redirect(301, '/signin'); res.redirect(301, '/signin');
@@ -623,55 +622,23 @@ module.exports = function(app) {
} }
function updateUserStoryPictures(userId, picture, username, cb) { function updateUserStoryPictures(userId, picture, username, cb) {
var counter = 0,
foundStories,
foundComments;
Story.find({ 'author.userId': userId }, function(err, stories) { Story.find({ 'author.userId': userId }, function(err, stories) {
if (err) { if (err) { return cb(err); }
return cb(err);
}
foundStories = stories;
counter++;
saveStoriesAndComments();
});
Comment.find({ 'author.userId': userId }, function(err, comments) { const tasks = [];
if (err) { stories.forEach(function(story) {
return cb(err);
}
foundComments = comments;
counter++;
saveStoriesAndComments();
});
function saveStoriesAndComments() {
if (counter !== 2) {
return;
}
var tasks = [];
R.forEach(function(comment) {
comment.author.picture = picture;
comment.author.username = username;
tasks.push(function(cb) {
comment.save(cb);
});
}, foundComments);
R.forEach(function(story) {
story.author.picture = picture; story.author.picture = picture;
story.author.username = username; story.author.username = username;
tasks.push(function(cb) { tasks.push(function(cb) {
story.save(cb); story.save(cb);
}); });
}, foundStories); });
async.parallel(tasks, function(err) { async.parallel(tasks, function(err) {
if (err) { if (err) {
return cb(err); return cb(err);
} }
cb(); cb();
}); });
} });
} }
}; };

View File

@@ -31,18 +31,10 @@
"dataSource": "mail", "dataSource": "mail",
"public": false "public": false
}, },
"bonfire": {
"dataSource": "db",
"public": true
},
"challenge": { "challenge": {
"dataSource": "db", "dataSource": "db",
"public": true "public": true
}, },
"comment": {
"dataSource": "db",
"public": true
},
"fieldGuide": { "fieldGuide": {
"dataSource": "db", "dataSource": "db",
"public": true "public": true

View File

@@ -208,7 +208,7 @@ app.use(function(req, res, next) {
var path = req.path.split('/')[1]; var path = req.path.split('/')[1];
if (/auth|login|logout|signin|signup|fonts|favicon/i.test(path)) { if (/auth|login|logout|signin|signup|fonts|favicon/i.test(path)) {
return next(); return next();
} else if (/\/stories\/comments\/\w+/i.test(req.path)) { } else if (/\/stories\/\w+/i.test(req.path)) {
return next(); return next();
} }
req.session.returnTo = req.path; req.session.returnTo = req.path;

View File

@@ -1,164 +0,0 @@
.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;
R.forEach(function displayComments(comment) {
$.ajax({
type: 'GET',
url: '/stories/comments/' + comment,
error: function (xhr, textStatus, errorThrown) {
commentDetails = {
error: true,
body: 'There seems to be a problem fetching this comment.'
}
},
success: function (data, textStatus, xhr) {
commentDetails = data;
var div = document.createElement('div');
var editButton = "";
// todo
if (commentDetails.author.username === DF105CFA89562196E702912B3818C6A5B46E80D262442FDF29976621E5AF0D23) {
if ((Date.now() - commentDetails.commentOn) < 600000){
editButton = "<a class='btn btn-no-shadow btn-primary btn-xs btn-primary-ghost edit-btn' id='" + commentDetails.id + "'>Edit</a> · ";
}
}
$(div)
.html(
'<div class="media 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">' +
"<a class='btn btn-no-shadow btn-primary btn-xs btn-primary-ghost' id='" + commentDetails.id + "'>Reply</a> · " +
editButton +
"commented " + moment(commentDetails.commentOn).fromNow() + " by " +
"<a href='/" + commentDetails.author.username + "'>@" + commentDetails.author.username + "</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() {
var editOrComment = 'comment';
if ($(this).hasClass("edit-btn")){
editOrComment = 'edit';
}
if (!isLoggedIn) {
window.location.href = '/signin';
return;
}
$(this).unbind('click');
$('.comment-to-comment-formgroup').empty();
$('#initial-comment-submit').addClass('hidden-element');
var div = document.createElement('div');
var commentId = $(this).attr('id');
$(div).html(
"<div class='formgroup comment-to-comment-formgroup control-label-story-submission'>" +
'<div class="col-xs-12">' +
"<div class='input-group'>" +
"<input class='form-control big-text-field field-responsive' id='comment-to-comment-textinput' type='text' maxlength='140' autofocus />" +
"<span class='input-group-btn'>" +
"<button id='submit-comment-to-" + editOrComment + "' class='btn btn-big btn-primary btn-responsive'>Send</button>" +
"</span>" +
"</div>" +
"</div>" +
"<div id='textarea-comment-feedback'></div>" +
"</div>"
)
.addClass('row')
.appendTo($(this).closest('.media-body-wrapper'));
var text_max = 140;
$('#textarea-comment-feedback').html(text_max + ' characters remaining');
$('#comment-to-comment-textinput').keyup(function (e) {
if (e.which === 13 || e === 13) {
$('#submit-comment-to-comment').click();
$('#submit-comment-to-edit').click();
}
var text_length = $('#comment-to-comment-textinput').val().length;
var text_remaining = text_max - text_length;
$('#textarea-comment-feedback').html(text_remaining + ' characters remaining');
});
var submitCommentToCommentHandler = function submitCommentToCommentHandler() {
$('#submit-comment-to-comment').unbind('click');
$.post('/stories/comment/' + commentId + '/comment',
{
data: {
associatedPost: commentId,
originalStoryLink: originalStoryLink,
originalStoryAuthorEmail: originalStoryAuthorEmail,
body: $('#comment-to-comment-textinput').val(),
}
})
.fail(function (xhr, textStatus, errorThrown) {
$('#submit-comment-to-comment').bind('click', submitCommentToCommentHandler);
})
.done(function (data, textStatus, xhr) {
window.location.reload();
});
};
// todo
var submitCommentForEditToCommentHandler = function submitCommentForEditToCommentHandler() {
$('#submit-comment-to-edit').unbind('click');
$.ajax({
type: "PUT",
url: '/stories/comment/' + commentId + '/edit',
data: {
associatedPost: commentId,
originalStoryLink: originalStoryLink,
body: $('#comment-to-comment-textinput').val()
},
dataType: "json",
success: function (msg) {
window.location.reload();
},
error: function (err){
$('#submit-comment-to-edit').bind('click', submitCommentForEditToCommentHandler);
}
});
};
$('#submit-comment-to-edit').on('click', submitCommentForEditToCommentHandler)
$('#submit-comment-to-comment').on('click', submitCommentToCommentHandler);
});
}
}
})
}, comments);
}
sentinel += comments.length;
renderComments(comments, $('#comment-list'), 0);

View File

@@ -9,7 +9,7 @@
return name.trim().toLowerCase().replace(/\s/g, '-'); return name.trim().toLowerCase().replace(/\s/g, '-');
} }
$.ajax({ $.ajax({
url: '/stories/hotStories', url: '/news/hot',
type: 'GET' type: 'GET'
}) })
.success( .success(
@@ -18,6 +18,12 @@
var div = document.createElement('div'); var div = document.createElement('div');
var linkedName = getLinkedName(data[i].storyLink); var linkedName = getLinkedName(data[i].storyLink);
var rank = data[i].rank; var rank = data[i].rank;
var alreadyUpvoted = false;
if (typeof username !== 'undefined') {
alreadyUpvoted = data[i].upVotes.some(function(vote) {
return vote.upVotedByUsername === username
});
}
$(div) $(div)
.html( .html(
@@ -38,7 +44,11 @@
" by <a href='/" + data[i].author.username + "'>@" + data[i].author.username + "</a> " + " by <a href='/" + data[i].author.username + "'>@" + data[i].author.username + "</a> " +
"</div>" + "</div>" +
"<div class='col-xs-12'>" + "<div class='col-xs-12'>" +
"<br><a class='btn btn-no-shadow btn-primary btn-block btn-primary-ghost' href='/stories/" + linkedName + "'>discuss</a>" + "<br>" +
(typeof username !== 'undefined' ?
"<button id='" + data[i].id + "' class='btn btn-no-shadow btn-primary btn-block btn-primary-ghost btn-upvote'>upvote</button>" :
"<a href='/signup' class='btn btn-no-shadow btn-primary btn-block btn-primary-ghost btn-upvote'>upvote</a>") +
"<a class='btn btn-no-shadow btn-primary btn-block btn-primary-ghost' href='/news/" + linkedName + "'>more info...</a>" +
"</div>" + "</div>" +
"</div>" + "</div>" +
"<div class='hidden-xs row media-stories'>" + "<div class='hidden-xs row media-stories'>" +
@@ -56,7 +66,10 @@
"</a>" + "</a>" +
"</div>" + "</div>" +
"<div class='story-byline col-xs-12 wrappable'>" + "<div class='story-byline col-xs-12 wrappable'>" +
"<a class='btn btn-no-shadow btn-primary btn-xs btn-primary-ghost' href='/stories/" + linkedName + "'>discuss</a> · " + (typeof username !== 'undefined' ?
"<button id='" + data[i].id + "' class='btn btn-no-shadow btn-primary btn-xs btn-primary-ghost btn-upvote'>upvote</button>" :
"<a href='/signin' class='btn btn-no-shadow btn-primary btn-xs btn-primary-ghost'>upvote</a>") +
" · <a class='btn btn-no-shadow btn-primary btn-xs btn-primary-ghost' href='/news/" + linkedName + "'>more info...</a> · " +
rank + (rank > 1 ? " points" : " point") + " · posted " + rank + (rank > 1 ? " points" : " point") + " · posted " +
moment(data[i].timePosted).fromNow() + moment(data[i].timePosted).fromNow() +
" by <a href='/" + data[i].author.username + "'>@" + data[i].author.username + "</a> " + " by <a href='/" + data[i].author.username + "'>@" + data[i].author.username + "</a> " +
@@ -68,5 +81,13 @@
); );
$(div).addClass('story-list news-box') $(div).addClass('story-list news-box')
$(div).appendTo($('#story-list')); $(div).appendTo($('#story-list'));
$(div).find('.btn-upvote').each(function(idx, btn) {
var $btn = $(btn);
if (alreadyUpvoted) {
$btn.addClass('disabled');
$btn.text('upvoted!');
}
$btn.data('upVotes', data[i].upVotes);
});
} }
}); });

View File

@@ -3,8 +3,8 @@ block content
if (user) if (user)
script. script.
var isLoggedIn = true; var isLoggedIn = true;
var B3BA669EC5C1DD70FB478221E067A7E1B686929C569F5E73561B69C8F42129B = !{JSON.stringify(user.id)}; var userId = !{JSON.stringify(user.id)};
var DF105CFA89562196E702912B3818C6A5B46E80D262442FDF29976621E5AF0D23 = !{JSON.stringify(user.username)}; var username = !{JSON.stringify(user.username)};
else else
script. script.
var isLoggedIn = false; var isLoggedIn = false;

View File

@@ -1,55 +0,0 @@
.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'>" +
"<h2 class='col-xs-1 col-sm-1 positive-5'>" +
(i + 1) +
"</h2>" +
"<div class='col-xs-2 col-sm-1'>" +
"<a href='/" + data[i].author.username + "'>" +
"<img src='" + data[i].author.picture + "' class='img-news'/>" +
"</a>" +
"</div>" +
"<div class='col-xs-9 col-sm-10'>" +
"<div class='row'>" +
"<div class='story-headline col-xs-12'>" +
"<a href='" + data[i].link + "' target='_blank'>" +
data[i].headline +
"</a>" +
"</div>" +
"<div class='col-xs-12'>" +
"<span>" +
"<a class='btn btn-no-shadow btn-primary btn-xs btn-primary-ghost' href='/stories/" + linkedName + "'>discuss</a> · " +
rank + (rank > 1 ? " points" : " point") + " · posted " +
moment(data[i].timePosted).fromNow() +
" by <a href='/" + data[i].author.username + "'>@" + data[i].author.username + "</a> " +"</a> " +
"</span>" +
"</div>" +
"</div>" +
"</div>" +
"</div>"
);
$(div).addClass('story-list')
$(div).appendTo($('#story-list'));
}
});

View File

@@ -84,7 +84,7 @@ script.
"</div>" + "</div>" +
"<div class='col-xs-12'>" + "<div class='col-xs-12'>" +
"<br>" + "<br>" +
"<a class='btn btn-no-shadow btn-primary btn-block btn-primary-ghost' href='/news/" + linkedName + "'>discuss</a>" + "<a class='btn btn-no-shadow btn-primary btn-block btn-primary-ghost' href='/news/" + linkedName + "'>more info...</a>" +
"</div>" + "</div>" +
"</div>" + "</div>" +
"</div>" + "</div>" +
@@ -103,7 +103,7 @@ script.
"</a>" + "</a>" +
"</div>" + "</div>" +
"<div class='story-byline col-xs-12 wrappable'>" + "<div class='story-byline col-xs-12 wrappable'>" +
"<a class='btn btn-no-shadow btn-primary btn-xs btn-primary-ghost' href='/news/" + linkedName + "'>discuss</a> · " + "<a class='btn btn-no-shadow btn-primary btn-xs btn-primary-ghost' href='/news/" + linkedName + "'>more info.more info...</a> · " +
rank + (rank > 1 ? " points" : " point") + " · posted " + rank + (rank > 1 ? " points" : " point") + " · posted " +
moment(data[i].timePosted).fromNow() + moment(data[i].timePosted).fromNow() +
" by <a href='/" + data[i].author.username + "'>@" + data[i].author.username + " by <a href='/" + data[i].author.username + "'>@" + data[i].author.username +

View File

@@ -2,7 +2,6 @@ script.
var storyId = !{JSON.stringify(id)}; var storyId = !{JSON.stringify(id)};
var originalStoryLink = !{JSON.stringify(originalStoryLink)}; var originalStoryLink = !{JSON.stringify(originalStoryLink)};
var originalStoryAuthorEmail = !{JSON.stringify(originalStoryAuthorEmail)}; var originalStoryAuthorEmail = !{JSON.stringify(originalStoryAuthorEmail)};
var comments = !{JSON.stringify(comments)};
var upVotes = !{JSON.stringify(upVotes)}; var upVotes = !{JSON.stringify(upVotes)};
var image = !{JSON.stringify(image)}; var image = !{JSON.stringify(image)};
var hasUserVoted = !{JSON.stringify(hasUserVoted)}; var hasUserVoted = !{JSON.stringify(hasUserVoted)};
@@ -26,52 +25,19 @@ h3.row
.col-xs-12 .col-xs-12
h4= description h4= description
.negative-5 .negative-5
if !user if !hasUserVoted
a#upvote.btn.signup-btn.btn-xs(href='/signin') Sign in to reply or upvote a#upvote.btn.btn-no-shadow.btn-primary.btn-xs.btn-primary-ghost Upvote
| &thinsp;·&thinsp; | &thinsp;·&thinsp;
else else
a#reply-to-main-post.btn.btn-no-shadow.btn-primary.btn-xs.btn-primary-ghost Reply a#upvote.btn.disabled.btn-no-shadow.btn-primary.btn-xs.btn-primary-ghost Upvoted!
| &thinsp;·&thinsp;&thinsp; | &thinsp;·&thinsp;
if !hasUserVoted
a#upvote.btn.btn-no-shadow.btn-primary.btn-xs.btn-primary-ghost Upvote
| &thinsp;·&thinsp;
else
a#upvote.btn.disabled.btn-no-shadow.btn-primary.btn-xs.btn-primary-ghost Upvoted!
| &thinsp;·&thinsp;
span#storyRank= rank + (rank > 1 ? " points" : " point") span#storyRank= rank + (rank > 1 ? " points" : " point")
| &thinsp;·&thinsp; | &thinsp;·&thinsp;
span Posted #{timeAgo} span Posted #{timeAgo}
span &thinsp;by&thinsp; span &thinsp;by&thinsp;
a(href="/" + author.username) @#{author.username} a(href="/" + author.username) @#{author.username}
if (user !== null)
.col-xs-12#reply-area
.hidden-element#initial-comment-submit
form.form-horizontal.control-label-story-submission
.col-xs-12
.input-group
input#comment-box.big-text-field.field-responsive.form-control(type='text', placeholder='Enter your reply', autofocus)
span.input-group-btn
button#comment-button.btn.btn-big.btn-primary.btn-responsive(type='button') Send
span.spacer.pull-left#textarea_feedback
script. script.
if (image) { if (image) {
$('#image-display').removeClass('hidden-element') $('#image-display').removeClass('hidden-element')
} }
$('#reply-to-main-post').on('click', function() {
$('#initial-comment-submit').removeClass('hidden-element');
$(this).unbind('click');
$('.comment-to-comment-formgroup').empty();
});
var text_max = 140;
$('#textarea_feedback').html(text_max + ' characters remaining');
$('#comment-box').keyup(function (e) {
if (e.which === 13 || e === 13) {
$('#comment-button').click();
}
var text_length = $('#comment-box').val().length;
var text_remaining = text_max - text_length;
$('#textarea_feedback').html(text_remaining + ' characters remaining');
});
include comments

View File

@@ -22,11 +22,6 @@
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
| A headline is required to submit. | A headline is required to submit.
.form-group
.col-xs-12.col-md-1
label.control-label.control-label-story-submission(for='name') Comment
.col-xs-12.col-md-11
input#description-box.form-control(name="comment-box", placeholder="Kick off the discussion with an optional comment about this link" maxlength='140')
.form-group .form-group
.col-xs-12.col-md-offset-1 .col-xs-12.col-md-offset-1
span.pull-left#textarea_feedback span.pull-left#textarea_feedback