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": {
"type": {}
},
"comments": {
"type": "array",
"default": []
},
"image": {
"type": "string",
"default": ""

View File

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

View File

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

View File

@@ -448,7 +448,7 @@
"Let's now go create a nested array called <code>myArray</code>"
],
"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":[
"var myArray = [];",
@@ -548,7 +548,7 @@
"//console.log(removed); //Gives 3",
"",
"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);"
@@ -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>"
],
"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": [
"var myArray = ['John', 23, ['cat', 2]];",
@@ -595,7 +595,7 @@
],
"challengeSeed": [
"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);"
@@ -644,7 +644,7 @@
"Let's try creating and calling a function now called <code>myFunction</code>"
],
"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":[
"var a = 4;",
@@ -726,7 +726,7 @@
""
],
"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');"
],
"challengeSeed":[
@@ -740,9 +740,9 @@
"//Re-create myDog",
"",
"var myDog = {",
" \"name\": _,",
" \"legs\": _,",
" \"tails\": _,",
" \"name\": 'Camper',",
" \"legs\": 4,",
" \"tails\": 1,",
" \"friends\": []",
"};",
"",
@@ -1021,7 +1021,7 @@
],
"tests":[
"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":[
"var test = (function(){",
@@ -1052,7 +1052,7 @@
],
"tests":[
"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":[
"var test = (function(){",
@@ -1083,14 +1083,14 @@
"<code> Math.floor(Math.random() * (5 - 1 + 1)) + 1; </code>"
],
"tests":[
"assert(typeof(runSlots($(''))[0]) == 'number', 'SlotOne should be a random number');",
"assert(typeof(runSlots($(''))[1]) == 'number', 'SlotTwo should be a random number');",
"assert(typeof(runSlots($(''))[2]) == 'number', 'SlotThree should be a random number');",
"assert(editor.getValue().match(/Math.floor\\(Math.random\\(\\) \\* \\(5 \\- 1 \\+ 1\\)\\) \\+ 1/g).length === 3);"
"assert(typeof(runSlots($('.slot'))[0]) == 'number', 'SlotOne should be a random number');",
"assert(typeof(runSlots($('.slot'))[1]) == 'number', 'SlotTwo should be a random number');",
"assert(typeof(runSlots($('.slot'))[2]) == 'number', 'SlotThree should be a random number');",
"assert(editorValue.match(/Math.floor\\(Math.random\\(\\) \\* \\(5 \\- 1 \\+ 1\\)\\) \\+ 1/g).length === 3);"
],
"challengeSeed":[
"fccss",
" function runSlots(slots){",
" function runSlots(){",
" var slotOne;",
" var slotTwo;",
" var slotThree;",
@@ -1111,7 +1111,7 @@
"",
" $(document).ready(function(){",
" $('.go').click(function(){",
" runSlots(slots);",
" runSlots();",
" });",
" });",
"fcces",

View File

@@ -254,9 +254,8 @@
"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 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 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 or comments on Camper News. Just click on their photo.",
"You can also submit links.",
"You can view the portfolio pages of any camper who has posted links 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.",
"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 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'>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.",
"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>"

View File

@@ -1,5 +1,4 @@
var Rx = require('rx'),
nodemailer = require('nodemailer'),
assign = require('object.assign'),
sanitizeHtml = require('sanitize-html'),
moment = require('moment'),
@@ -19,23 +18,6 @@ var unDasherize = utils.unDasherize;
var dasherize = utils.dasherize;
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) {
/*
* Hotness ranking algorithm: http://amix.dk/blog/post/19588
@@ -69,7 +51,6 @@ module.exports = function(app) {
var router = app.loopback.Router();
var User = app.models.User;
var findUserById = observeMethod(User, 'findById');
var findOneUser = observeMethod(User, 'findOne');
var Story = app.models.Story;
var findStory = observeMethod(Story, 'find');
@@ -77,14 +58,8 @@ module.exports = function(app) {
var findStoryById = observeMethod(Story, 'findById');
var countStories = observeMethod(Story, 'count');
var Comment = app.models.Comment;
var findCommentById = observeMethod(Comment, 'findById');
router.get('/news/hot', 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/new-story', preSubmit);
router.post('/stories/preliminary', newStory);
@@ -194,7 +169,6 @@ module.exports = function(app) {
description: story.description,
rank: story.upVotes.length,
upVotes: story.upVotes,
comments: story.comments,
id: story.id,
timeAgo: moment(story.timePosted).fromNow(),
image: story.image,
@@ -224,7 +198,6 @@ module.exports = function(app) {
rank: 1,
upVotes: 1,
author: 1,
comments: 1,
image: 1,
storyLink: 1,
metaDescription: 1,
@@ -250,7 +223,7 @@ module.exports = function(app) {
}
function upvote(req, res, next) {
var id = req.body.data.id;
const { id } = req.body;
var story$ = findStoryById(id).shareReplay();
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) {
if (!req.user) {
return next(new Error('Must be logged in'));
@@ -407,7 +370,6 @@ module.exports = function(app) {
username: req.user.username,
email: req.user.email
},
comments: [],
image: data.image,
storyLink: storyLink,
metaDescription: data.storyMetaDescription,
@@ -428,174 +390,4 @@ module.exports = function(app) {
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 User = app.models.User;
var Story = app.models.Story;
var Comment = app.models.Comment;
router.get('/login', function(req, res) {
res.redirect(301, '/signin');
@@ -623,55 +622,23 @@ module.exports = function(app) {
}
function updateUserStoryPictures(userId, picture, username, cb) {
var counter = 0,
foundStories,
foundComments;
Story.find({ 'author.userId': userId }, function(err, stories) {
if (err) {
return cb(err);
}
foundStories = stories;
counter++;
saveStoriesAndComments();
});
if (err) { return cb(err); }
Comment.find({ 'author.userId': userId }, function(err, comments) {
if (err) {
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) {
const tasks = [];
stories.forEach(function(story) {
story.author.picture = picture;
story.author.username = username;
tasks.push(function(cb) {
story.save(cb);
});
}, foundStories);
});
async.parallel(tasks, function(err) {
if (err) {
return cb(err);
}
cb();
});
}
});
}
};

View File

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

View File

@@ -208,7 +208,7 @@ app.use(function(req, res, next) {
var path = req.path.split('/')[1];
if (/auth|login|logout|signin|signup|fonts|favicon/i.test(path)) {
return next();
} else if (/\/stories\/comments\/\w+/i.test(req.path)) {
} else if (/\/stories\/\w+/i.test(req.path)) {
return next();
}
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, '-');
}
$.ajax({
url: '/stories/hotStories',
url: '/news/hot',
type: 'GET'
})
.success(
@@ -18,6 +18,12 @@
var div = document.createElement('div');
var linkedName = getLinkedName(data[i].storyLink);
var rank = data[i].rank;
var alreadyUpvoted = false;
if (typeof username !== 'undefined') {
alreadyUpvoted = data[i].upVotes.some(function(vote) {
return vote.upVotedByUsername === username
});
}
$(div)
.html(
@@ -38,7 +44,11 @@
" by <a href='/" + data[i].author.username + "'>@" + data[i].author.username + "</a> " +
"</div>" +
"<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 class='hidden-xs row media-stories'>" +
@@ -56,7 +66,10 @@
"</a>" +
"</div>" +
"<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 " +
moment(data[i].timePosted).fromNow() +
" by <a href='/" + data[i].author.username + "'>@" + data[i].author.username + "</a> " +
@@ -68,5 +81,13 @@
);
$(div).addClass('story-list news-box')
$(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)
script.
var isLoggedIn = true;
var B3BA669EC5C1DD70FB478221E067A7E1B686929C569F5E73561B69C8F42129B = !{JSON.stringify(user.id)};
var DF105CFA89562196E702912B3818C6A5B46E80D262442FDF29976621E5AF0D23 = !{JSON.stringify(user.username)};
var userId = !{JSON.stringify(user.id)};
var username = !{JSON.stringify(user.username)};
else
script.
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 class='col-xs-12'>" +
"<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>" +
@@ -103,7 +103,7 @@ script.
"</a>" +
"</div>" +
"<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 " +
moment(data[i].timePosted).fromNow() +
" by <a href='/" + data[i].author.username + "'>@" + data[i].author.username +

View File

@@ -2,7 +2,6 @@ script.
var storyId = !{JSON.stringify(id)};
var originalStoryLink = !{JSON.stringify(originalStoryLink)};
var originalStoryAuthorEmail = !{JSON.stringify(originalStoryAuthorEmail)};
var comments = !{JSON.stringify(comments)};
var upVotes = !{JSON.stringify(upVotes)};
var image = !{JSON.stringify(image)};
var hasUserVoted = !{JSON.stringify(hasUserVoted)};
@@ -26,12 +25,6 @@ h3.row
.col-xs-12
h4= description
.negative-5
if !user
a#upvote.btn.signup-btn.btn-xs(href='/signin') Sign in to reply or upvote
| &thinsp;·&thinsp;
else
a#reply-to-main-post.btn.btn-no-shadow.btn-primary.btn-xs.btn-primary-ghost Reply
| &thinsp;·&thinsp;&thinsp;
if !hasUserVoted
a#upvote.btn.btn-no-shadow.btn-primary.btn-xs.btn-primary-ghost Upvote
| &thinsp;·&thinsp;
@@ -44,34 +37,7 @@ h3.row
span &thinsp;by&thinsp;
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.
if (image) {
$('#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')
span.ion-close-circled
| 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
.col-xs-12.col-md-offset-1
span.pull-left#textarea_feedback