mirror of
https://github.com/vale981/Vulcan
synced 2025-03-06 18:11:40 -05:00
Merge branch 'devel' into i18n
This commit is contained in:
commit
082b704371
45 changed files with 657 additions and 685 deletions
|
@ -156,6 +156,6 @@ underscore@1.0.3
|
|||
url@1.0.4
|
||||
useraccounts:core@1.8.1
|
||||
useraccounts:unstyled@1.8.1
|
||||
utilities:avatar@0.7.11
|
||||
utilities:avatar@0.7.12
|
||||
webapp@1.2.0
|
||||
webapp-hashing@1.0.3
|
||||
|
|
16
History.md
16
History.md
|
@ -1,8 +1,18 @@
|
|||
## v0.21 “SlugScope”
|
||||
|
||||
* Added URL slugs for posts (i.e. `/posts/xyz/my-post-slug`).
|
||||
* i18n files clean-up.
|
||||
* Added post downvote setting.
|
||||
* Refactored notifications code.
|
||||
* Added `kadira-debug` package.
|
||||
* Fixed avatar bug.
|
||||
* Fixed screen refresh bug on post page.
|
||||
|
||||
## v0.20.6 “AutoScope”
|
||||
|
||||
* Add Extra CSS field (thanks @johnthepink!)
|
||||
* Fix security issue with Settings (thanks @jshimko!)
|
||||
* Add automatic template replacement
|
||||
* Added Extra CSS field (thanks @johnthepink!).
|
||||
* Fixed security issue with Settings (thanks @jshimko!).
|
||||
* Added automatic template replacement.
|
||||
|
||||
## v0.20.5 “MinorScope”
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
// ------------------------------------------------------------------------------------------- //
|
||||
// ------------------------------------------ Hooks ------------------------------------------ //
|
||||
// ------------------------------------------------------------------------------------------- //
|
||||
//////////////////////////////////////////////////////
|
||||
// Collection Hooks //
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
Comments.before.insert(function (userId, doc) {
|
||||
// note: only actually sanitizes on the server
|
||||
|
@ -16,10 +15,22 @@ Comments.before.update(function (userId, doc, fieldNames, modifier) {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Disallow $rename
|
||||
*/
|
||||
Comments.before.update(function (userId, doc, fieldNames, modifier) {
|
||||
if (!!modifier.$rename) {
|
||||
throw new Meteor.Error("illegal $rename operator detected!");
|
||||
}
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// Callbacks //
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
function afterCommentOperations (comment) {
|
||||
|
||||
var userId = comment.userId,
|
||||
commentAuthor = Meteor.users.findOne(userId);
|
||||
var userId = comment.userId;
|
||||
|
||||
// increment comment count
|
||||
Meteor.users.update({_id: userId}, {
|
||||
|
@ -33,10 +44,17 @@ function afterCommentOperations (comment) {
|
|||
$addToSet: {commenters: userId}
|
||||
});
|
||||
|
||||
return comment;
|
||||
}
|
||||
Telescope.callbacks.add("commentSubmitAsync", afterCommentOperations);
|
||||
|
||||
function upvoteOwnComment (comment) {
|
||||
|
||||
var commentAuthor = Meteor.users.findOne(comment.userId);
|
||||
|
||||
// upvote comment
|
||||
Telescope.upvoteItem(Comments, comment, commentAuthor);
|
||||
|
||||
return comment;
|
||||
}
|
||||
|
||||
Telescope.callbacks.add("commentSubmitAsync", afterCommentOperations);
|
||||
Telescope.callbacks.add("commentSubmitAsync", upvoteOwnComment);
|
|
@ -29,6 +29,7 @@ AutoForm.hooks({
|
|||
},
|
||||
|
||||
onSuccess: function(formType, comment) {
|
||||
// TODO: find out why comment is undefined here
|
||||
comment = this.currentDoc;
|
||||
Events.track("edit comment", {'commentId': comment._id});
|
||||
Router.go('post_page', {_id: comment.postId});
|
||||
|
|
|
@ -1,26 +1,3 @@
|
|||
//////////////////////////
|
||||
// Notification Helpers //
|
||||
//////////////////////////
|
||||
|
||||
/**
|
||||
* Grab common comment properties (for email notifications, only used on server).
|
||||
* @param {Object} post
|
||||
*/
|
||||
Comments.getProperties = function (comment) {
|
||||
var commentAuthor = Meteor.users.findOne(comment.userId);
|
||||
var post = Posts.findOne(comment.postId);
|
||||
var c = {
|
||||
profileUrl: commentAuthor && commentAuthor.getProfileUrl(true),
|
||||
postUrl: Posts.getPageUrl(post, true),
|
||||
authorName : comment.getAuthorName(true),
|
||||
postTitle: Posts.findOne(comment.postId).title,
|
||||
htmlBody: comment.htmlBody,
|
||||
commentUrl: Comments.getPageUrl(comment, true)
|
||||
};
|
||||
console.log(c)
|
||||
return c;
|
||||
};
|
||||
|
||||
//////////////////
|
||||
// Link Helpers //
|
||||
//////////////////
|
||||
|
|
|
@ -39,10 +39,11 @@ Comments.submit = function (comment) {
|
|||
// --------------------- Server-side Async Callbacks --------------------- //
|
||||
|
||||
// run all post submit server callbacks on comment object successively
|
||||
Telescope.callbacks.runAsync("commentSubmitAsync", comment);
|
||||
// note: query for comment to get fresh document with collection-hooks effects applied
|
||||
Telescope.callbacks.runAsync("commentSubmitAsync", Comments.findOne(comment._id));
|
||||
|
||||
return comment;
|
||||
}
|
||||
};
|
||||
|
||||
Comments.edit = function (commentId, modifier, comment) {
|
||||
|
||||
|
|
|
@ -28,6 +28,10 @@ Template.layout.rendered = function(){
|
|||
link.href = Settings.get('faviconUrl', '/img/favicon.ico');
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
|
||||
// canonical
|
||||
var canonicalLink = document.createElement('link');
|
||||
canonicalLink.rel = 'canonical';
|
||||
document.getElementsByTagName('head')[0].appendChild(canonicalLink);
|
||||
};
|
||||
|
||||
Template.layout.events({
|
||||
|
|
|
@ -7,10 +7,6 @@ AutoForm.addInputType("bootstrap-url", {
|
|||
if (url.substring(0, 7) !== "http://" && url.substring(0, 8) !== "https://") {
|
||||
url = "http://"+url;
|
||||
}
|
||||
// if URL only contains the two "/"" from "http://", then add trailing slash
|
||||
if (url.match(/\//g).length === 2) {
|
||||
url = url + "/";
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
Meteor.startup(function () {
|
||||
|
||||
// New user email
|
||||
|
||||
Router.route('/email/new-user/:id?', {
|
||||
name: 'newUser',
|
||||
where: 'server',
|
||||
action: function() {
|
||||
var html;
|
||||
var user = Meteor.users.findOne(this.params.id);
|
||||
var emailProperties = {
|
||||
profileUrl: Users.getProfileUrl(user),
|
||||
username: Users.getUserName(user)
|
||||
};
|
||||
html = Telescope.email.getTemplate('emailNewUser')(emailProperties);
|
||||
this.response.write(Telescope.email.buildTemplate(html));
|
||||
this.response.end();
|
||||
}
|
||||
});
|
||||
|
||||
// New post email
|
||||
|
||||
Router.route('/email/new-post/:id?', {
|
||||
name: 'newPost',
|
||||
where: 'server',
|
||||
action: function() {
|
||||
var html;
|
||||
var post = Posts.findOne(this.params.id);
|
||||
if (!!post) {
|
||||
html = Telescope.email.getTemplate('emailNewPost')(Posts.getProperties(post));
|
||||
} else {
|
||||
html = "<h3>No post found.</h3>"
|
||||
}
|
||||
this.response.write(Telescope.email.buildTemplate(html));
|
||||
this.response.end();
|
||||
}
|
||||
});
|
||||
|
||||
// Post approved
|
||||
|
||||
Router.route('/email/post-approved/:id?', {
|
||||
name: 'postApproved',
|
||||
where: 'server',
|
||||
action: function() {
|
||||
var html;
|
||||
var post = Posts.findOne(this.params.id);
|
||||
if (!!post) {
|
||||
html = Telescope.email.getTemplate('emailPostApproved')(Posts.getProperties(post));
|
||||
} else {
|
||||
html = "<h3>No post found.</h3>"
|
||||
}
|
||||
this.response.write(Telescope.email.buildTemplate(html));
|
||||
this.response.end();
|
||||
}
|
||||
});
|
||||
|
||||
// New comment email
|
||||
|
||||
Router.route('/email/new-comment/:id?', {
|
||||
name: 'newComment',
|
||||
where: 'server',
|
||||
action: function() {
|
||||
var html;
|
||||
var comment = Comments.findOne(this.params.id);
|
||||
if (!!comment) {
|
||||
html = Telescope.email.getTemplate('emailNewComment')(Comments.getProperties(comment));
|
||||
} else {
|
||||
html = "<h3>No post found.</h3>"
|
||||
}
|
||||
this.response.write(Telescope.email.buildTemplate(html));
|
||||
this.response.end();
|
||||
}
|
||||
});
|
||||
|
||||
// New reply email
|
||||
|
||||
Router.route('/email/new-reply/:id?', {
|
||||
name: 'newReply',
|
||||
where: 'server',
|
||||
action: function() {
|
||||
var html;
|
||||
var comment = Comments.findOne(this.params.id);
|
||||
if (!!comment) {
|
||||
html = Telescope.email.getTemplate('emailNewReply')(Comments.getProperties(comment));
|
||||
} else {
|
||||
html = "<h3>No post found.</h3>"
|
||||
}
|
||||
this.response.write(Telescope.email.buildTemplate(html));
|
||||
this.response.end();
|
||||
}
|
||||
});
|
||||
});
|
|
@ -25,15 +25,7 @@ Package.onUse(function (api) {
|
|||
|
||||
api.addFiles([
|
||||
'lib/server/email.js',
|
||||
'lib/server/routes.js',
|
||||
'lib/server/templates/emailAccountApproved.handlebars',
|
||||
'lib/server/templates/emailInvite.handlebars',
|
||||
'lib/server/templates/emailNewComment.handlebars',
|
||||
'lib/server/templates/emailNewPost.handlebars',
|
||||
'lib/server/templates/emailNewPendingPost.handlebars',
|
||||
'lib/server/templates/emailPostApproved.handlebars',
|
||||
'lib/server/templates/emailNewReply.handlebars',
|
||||
'lib/server/templates/emailNewUser.handlebars',
|
||||
'lib/server/templates/emailTest.handlebars',
|
||||
'lib/server/templates/emailWrapper.handlebars',
|
||||
], ['server']);
|
||||
|
|
|
@ -5,4 +5,4 @@
|
|||
|
||||
Telescope = {};
|
||||
|
||||
Telescope.VERSION = '0.20.6';
|
||||
Telescope.VERSION = '0.21';
|
|
@ -140,9 +140,16 @@ Telescope.utils.getPostCommentUrl = function(postId, commentId) {
|
|||
};
|
||||
|
||||
Telescope.utils.slugify = function (s) {
|
||||
return getSlug(s, {
|
||||
var slug = getSlug(s, {
|
||||
truncate: 60
|
||||
});
|
||||
|
||||
// can't have posts with an "edit" slug
|
||||
if (slug === "edit") {
|
||||
slug = "edit-1";
|
||||
}
|
||||
|
||||
return slug;
|
||||
};
|
||||
|
||||
Telescope.utils.getShortUrl = function(post) {
|
||||
|
|
|
@ -42,7 +42,7 @@ Package.onUse(function (api) {
|
|||
'momentjs:moment@2.10.3',
|
||||
'sacha:spin@0.2.4',
|
||||
'aslagle:reactive-table@0.7.3',
|
||||
'utilities:avatar@0.7.11',
|
||||
'utilities:avatar@0.7.12',
|
||||
'fortawesome:fontawesome@4.3.0',
|
||||
'ccan:cssreset@1.0.0',
|
||||
'djedi:sanitize-html@1.6.1',
|
||||
|
|
103
packages/telescope-notifications/lib/callbacks.js
Normal file
103
packages/telescope-notifications/lib/callbacks.js
Normal file
|
@ -0,0 +1,103 @@
|
|||
// ------------------------------------------------------------------------------------------- //
|
||||
// ----------------------------------------- Posts ------------------------------------------ //
|
||||
// ------------------------------------------------------------------------------------------- //
|
||||
|
||||
// add new post notification callback on post submit
|
||||
function postSubmitNotification (post) {
|
||||
|
||||
var adminIds = _.pluck(Users.find({'isAdmin': true}, {fields: {_id:1}}).fetch(), '_id');
|
||||
var notifiedUserIds = _.pluck(Users.find({'telescope.notifications.posts': true}, {fields: {_id:1}}).fetch(), '_id');
|
||||
var notificationData = {
|
||||
post: _.pick(post, '_id', 'userId', 'title', 'url')
|
||||
};
|
||||
|
||||
// remove post author ID from arrays
|
||||
adminIds = _.without(adminIds, post.userId);
|
||||
notifiedUserIds = _.without(notifiedUserIds, post.userId);
|
||||
|
||||
if (post.status === Posts.config.STATUS_PENDING && !!adminIds.length) {
|
||||
// if post is pending, only notify admins
|
||||
Herald.createNotification(adminIds, {courier: 'newPendingPost', data: notificationData});
|
||||
} else if (!!notifiedUserIds.length) {
|
||||
// if post is approved, notify everybody
|
||||
Herald.createNotification(notifiedUserIds, {courier: 'newPost', data: notificationData});
|
||||
}
|
||||
|
||||
}
|
||||
Telescope.callbacks.add("postSubmitAsync", postSubmitNotification);
|
||||
|
||||
function postApprovedNotification (post) {
|
||||
|
||||
var notificationData = {
|
||||
post: _.pick(post, '_id', 'userId', 'title', 'url')
|
||||
};
|
||||
|
||||
Herald.createNotification(post.userId, {courier: 'postApproved', data: notificationData});
|
||||
}
|
||||
Telescope.callbacks.add("postApprovedAsync", postApprovedNotification);
|
||||
|
||||
// ------------------------------------------------------------------------------------------- //
|
||||
// ---------------------------------------- Comments ----------------------------------------- //
|
||||
// ------------------------------------------------------------------------------------------- //
|
||||
|
||||
// add new comment notification callback on comment submit
|
||||
function commentSubmitNotifications (comment) {
|
||||
|
||||
if(Meteor.isServer && !comment.disableNotifications){
|
||||
|
||||
var post = Posts.findOne(comment.postId),
|
||||
postAuthor = Users.findOne(post.userId),
|
||||
userIdsNotified = [],
|
||||
notificationData = {
|
||||
comment: _.pick(comment, '_id', 'userId', 'author', 'htmlBody'),
|
||||
post: _.pick(post, '_id', 'userId', 'title', 'url')
|
||||
};
|
||||
|
||||
|
||||
// 1. Notify author of post (if they have new comment notifications turned on)
|
||||
// but do not notify author of post if they're the ones posting the comment
|
||||
if (Users.getSetting(postAuthor, "notifications.comments", true) && comment.userId !== postAuthor._id) {
|
||||
Herald.createNotification(post.userId, {courier: 'newComment', data: notificationData});
|
||||
userIdsNotified.push(post.userId);
|
||||
}
|
||||
|
||||
// 2. Notify author of comment being replied to
|
||||
if (!!comment.parentCommentId) {
|
||||
|
||||
var parentComment = Comments.findOne(comment.parentCommentId);
|
||||
|
||||
// do not notify author of parent comment if they're also post author or comment author
|
||||
// (someone could be replying to their own comment)
|
||||
if (parentComment.userId !== post.userId && parentComment.userId !== comment.userId) {
|
||||
|
||||
var parentCommentAuthor = Users.findOne(parentComment.userId);
|
||||
|
||||
// do not notify parent comment author if they have reply notifications turned off
|
||||
if (Users.getSetting(parentCommentAuthor, "notifications.replies", true)) {
|
||||
|
||||
// add parent comment to notification data
|
||||
notificationData.parentComment = _.pick(parentComment, '_id', 'userId', 'author', 'htmlBody');
|
||||
|
||||
Herald.createNotification(parentComment.userId, {courier: 'newReply', data: notificationData});
|
||||
userIdsNotified.push(parentComment.userId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3. Notify users subscribed to the thread
|
||||
// TODO: ideally this would be injected from the telescope-subscribe-to-posts package
|
||||
if (!!post.subscribers) {
|
||||
|
||||
// remove userIds of users that have already been notified
|
||||
// and of comment author (they could be replying in a thread they're subscribed to)
|
||||
var subscriberIdsToNotify = _.difference(post.subscribers, userIdsNotified, [comment.userId]);
|
||||
Herald.createNotification(subscriberIdsToNotify, {courier: 'newCommentSubscribed', data: notificationData});
|
||||
|
||||
userIdsNotified = userIdsNotified.concat(subscriberIdsToNotify);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Telescope.callbacks.add("commentSubmitAsync", commentSubmitNotifications);
|
82
packages/telescope-notifications/lib/custom_fields.js
Normal file
82
packages/telescope-notifications/lib/custom_fields.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
Settings.addField({
|
||||
fieldName: 'emailNotifications',
|
||||
fieldSchema: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: true,
|
||||
autoform: {
|
||||
group: 'notifications',
|
||||
instructions: 'Enable email notifications for new posts and new comments (requires restart).'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// make it possible to disable notifications on a per-comment basis
|
||||
Comments.addField(
|
||||
{
|
||||
fieldName: 'disableNotifications',
|
||||
fieldSchema: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
autoform: {
|
||||
omit: true
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Add notifications options to user profile settings
|
||||
Users.addField([
|
||||
{
|
||||
fieldName: 'telescope.notifications.users',
|
||||
fieldSchema: {
|
||||
label: 'New users',
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: false,
|
||||
editableBy: ['admin'],
|
||||
autoform: {
|
||||
group: 'Email Notifications'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldName: 'telescope.notifications.posts',
|
||||
fieldSchema: {
|
||||
label: 'New posts',
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: false,
|
||||
editableBy: ['admin', 'member'],
|
||||
autoform: {
|
||||
group: 'Email Notifications'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldName: 'telescope.notifications.comments',
|
||||
fieldSchema: {
|
||||
label: 'Comments on my posts',
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: true,
|
||||
editableBy: ['admin', 'member'],
|
||||
autoform: {
|
||||
group: 'Email Notifications'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldName: 'telescope.notifications.replies',
|
||||
fieldSchema: {
|
||||
label: 'Replies to my comments',
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: true,
|
||||
editableBy: ['admin', 'member'],
|
||||
autoform: {
|
||||
group: 'Email Notifications'
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
40
packages/telescope-notifications/lib/helpers.js
Normal file
40
packages/telescope-notifications/lib/helpers.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Use user and post properties to populate post notifications objects.
|
||||
* @param {Object} post
|
||||
*/
|
||||
Posts.getNotificationProperties = function (post) {
|
||||
var postAuthor = Meteor.users.findOne(post.userId);
|
||||
var properties = {
|
||||
postAuthorName : Posts.getAuthorName(post),
|
||||
postTitle : Telescope.utils.cleanUp(post.title),
|
||||
profileUrl: Users.getProfileUrl(postAuthor, true),
|
||||
postUrl: Posts.getPageUrl(post, true),
|
||||
thumbnailUrl: post.thumbnailUrl,
|
||||
linkUrl: !!post.url ? Telescope.utils.getOutgoingUrl(post.url) : Posts.getPageUrl(post, true)
|
||||
};
|
||||
|
||||
if(post.url)
|
||||
properties.url = post.url;
|
||||
|
||||
if(post.htmlBody)
|
||||
properties.htmlBody = post.htmlBody;
|
||||
|
||||
return properties;
|
||||
};
|
||||
|
||||
/**
|
||||
* Use comment, user, and post properties to populate comment notifications objects.
|
||||
* @param {Object} comment
|
||||
*/
|
||||
Comments.getNotificationProperties = function (comment, post) {
|
||||
var commentAuthor = Meteor.users.findOne(comment.userId);
|
||||
var properties = {
|
||||
profileUrl: commentAuthor && commentAuthor.getProfileUrl(true),
|
||||
postUrl: Posts.getPageUrl(post, true),
|
||||
authorName : Comments.getAuthorName(comment),
|
||||
postTitle: post.title,
|
||||
htmlBody: comment.htmlBody,
|
||||
commentUrl: Comments.getPageUrl(comment, true)
|
||||
};
|
||||
return properties;
|
||||
};
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
// send emails every second when in dev environment
|
||||
if (Meteor.absoluteUrl().indexOf('localhost') !== -1)
|
||||
Herald.settings.queueTimer = 1000;
|
||||
|
@ -14,146 +13,3 @@ Meteor.startup(function () {
|
|||
Herald.settings.overrides.email = !Settings.get('emailNotifications', true);
|
||||
|
||||
});
|
||||
|
||||
var commentEmail = function (userToNotify) {
|
||||
var notification = this;
|
||||
// put in setTimeout so it doesn't hold up the rest of the method
|
||||
Meteor.setTimeout(function () {
|
||||
notificationEmail = buildEmailNotification(notification);
|
||||
Telescope.email.send(Users.getEmail(userToNotify), notificationEmail.subject, notificationEmail.html);
|
||||
}, 1);
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------- //
|
||||
// ----------------------------------------- Posts ------------------------------------------ //
|
||||
// ------------------------------------------------------------------------------------------- //
|
||||
|
||||
Herald.addCourier('newPost', {
|
||||
media: {
|
||||
email: {
|
||||
emailRunner: function (user) {
|
||||
var p = Posts.getProperties(this.data);
|
||||
var subject = p.postAuthorName+' has created a new post: '+p.postTitle;
|
||||
var html = Telescope.email.buildTemplate(Telescope.email.getTemplate('emailNewPost')(p));
|
||||
Telescope.email.send(Users.getEmail(user), subject, html);
|
||||
}
|
||||
}
|
||||
}
|
||||
// message: function (user) { return 'email template?' }
|
||||
});
|
||||
|
||||
Herald.addCourier('newPendingPost', {
|
||||
media: {
|
||||
email: {
|
||||
emailRunner: function (user) {
|
||||
var p = Posts.getProperties(this.data);
|
||||
var subject = p.postAuthorName+' has a new post pending approval: '+p.postTitle;
|
||||
var html = Telescope.email.buildTemplate(Telescope.email.getTemplate('emailNewPendingPost')(p));
|
||||
Telescope.email.send(Users.getEmail(user), subject, html);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Herald.addCourier('postApproved', {
|
||||
media: {
|
||||
onsite: {},
|
||||
email: {
|
||||
emailRunner: function (user) {
|
||||
var p = Posts.getProperties(this.data);
|
||||
var subject = 'Your post “'+p.postTitle+'” has been approved';
|
||||
var html = Telescope.email.buildTemplate(Telescope.email.getTemplate('emailPostApproved')(p));
|
||||
Telescope.email.send(Users.getEmail(user), subject, html);
|
||||
}
|
||||
}
|
||||
},
|
||||
message: {
|
||||
default: function () {
|
||||
return Blaze.toHTML(Blaze.With(this, function () {
|
||||
return Template.notification_post_approved;
|
||||
}));
|
||||
}
|
||||
},
|
||||
transform: { // used for on-site notifications
|
||||
postUrl: function () {
|
||||
var p = Posts.getProperties(this.data);
|
||||
return p.postUrl;
|
||||
},
|
||||
postTitle: function () {
|
||||
var p = Posts.getProperties(this.data);
|
||||
return p.postTitle;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------------------------------- //
|
||||
// ---------------------------------------- Comments ----------------------------------------- //
|
||||
// ------------------------------------------------------------------------------------------- //
|
||||
|
||||
// specify how to get properties used in template from comment data, used for on-site notifications
|
||||
var commentCourierTransform = {
|
||||
profileUrl: function () {
|
||||
var user = Meteor.users.findOne(this.data.comment.userId);
|
||||
return user && user.getProfileUrl();
|
||||
},
|
||||
authorName: function () {
|
||||
return Comments.getAuthorName(this.data.comment);
|
||||
},
|
||||
commentUrl: function () {
|
||||
return Posts.getPageUrl({_id: this.data.post._id});
|
||||
},
|
||||
postTitle: function () {
|
||||
return this.data.post.title;
|
||||
}
|
||||
};
|
||||
|
||||
Herald.addCourier('newComment', {
|
||||
media: {
|
||||
onsite: {},
|
||||
email: {
|
||||
emailRunner: commentEmail
|
||||
}
|
||||
},
|
||||
message: {
|
||||
default: function () {
|
||||
return Blaze.toHTML(Blaze.With(this, function () {
|
||||
return Template.notification_new_comment;
|
||||
}));
|
||||
}
|
||||
},
|
||||
transform: commentCourierTransform
|
||||
});
|
||||
|
||||
Herald.addCourier('newReply', {
|
||||
media: {
|
||||
onsite: {},
|
||||
email: {
|
||||
emailRunner: commentEmail
|
||||
}
|
||||
},
|
||||
message: {
|
||||
default: function () {
|
||||
return Blaze.toHTML(Blaze.With(this, function () {
|
||||
return Template.notification_new_reply;
|
||||
}));
|
||||
}
|
||||
},
|
||||
transform: commentCourierTransform
|
||||
});
|
||||
|
||||
Herald.addCourier('newCommentSubscribed', {
|
||||
media: {
|
||||
onsite: {},
|
||||
email: {
|
||||
emailRunner: commentEmail
|
||||
}
|
||||
},
|
||||
message: {
|
||||
default: function () {
|
||||
return Blaze.toHTML(Blaze.With(this, function () {
|
||||
return Template.notification_new_reply;
|
||||
}));
|
||||
}
|
||||
},
|
||||
transform: commentCourierTransform
|
||||
});
|
||||
|
|
|
@ -1,188 +1,97 @@
|
|||
// add new post notification callback on post submit
|
||||
function postSubmitNotification (post) {
|
||||
var notifications = {
|
||||
|
||||
var adminIds = _.pluck(Users.find({'isAdmin': true}, {fields: {_id:1}}).fetch(), '_id');
|
||||
var notifiedUserIds = _.pluck(Users.find({'telescope.notifications.posts': true}, {fields: {_id:1}}).fetch(), '_id');
|
||||
newPost: {
|
||||
properties: function () {
|
||||
return Posts.getNotificationProperties(this.data.post);
|
||||
},
|
||||
subject: function () {
|
||||
return this.postAuthorName+' has created a new post: '+this.postTitle;
|
||||
},
|
||||
emailTemplate: "emailNewPost"
|
||||
},
|
||||
|
||||
// remove post author ID from arrays
|
||||
adminIds = _.without(adminIds, post.userId);
|
||||
notifiedUserIds = _.without(notifiedUserIds, post.userId);
|
||||
newPendingPost: {
|
||||
properties: function () {
|
||||
return Posts.getNotificationProperties(this.data.post);
|
||||
},
|
||||
subject: function () {
|
||||
return this.postAuthorName+' has a new post pending approval: '+this.postTitle;
|
||||
},
|
||||
emailTemplate: "emailNewPendingPost"
|
||||
},
|
||||
|
||||
if (post.status === Posts.config.STATUS_PENDING && !!adminIds.length) {
|
||||
// if post is pending, only notify admins
|
||||
Herald.createNotification(adminIds, {courier: 'newPendingPost', data: post});
|
||||
} else if (!!notifiedUserIds.length) {
|
||||
// if post is approved, notify everybody
|
||||
Herald.createNotification(notifiedUserIds, {courier: 'newPost', data: post});
|
||||
postApproved: {
|
||||
properties: function () {
|
||||
return Posts.getNotificationProperties(this.data.post);
|
||||
},
|
||||
subject: function () {
|
||||
return this.postAuthorName+' has a new post pending approval: '+this.postTitle;
|
||||
},
|
||||
emailTemplate: "emailPostApproved",
|
||||
onsiteTemplate: "notification_post_approved"
|
||||
},
|
||||
|
||||
newComment: {
|
||||
properties: function () {
|
||||
return Comments.getNotificationProperties(this.data.comment, this.data.post);
|
||||
},
|
||||
subject: function () {
|
||||
return this.authorName+' left a new comment on your post "' + this.postTitle + '"';
|
||||
},
|
||||
emailTemplate: "emailNewComment",
|
||||
onsiteTemplate: "notification_new_comment"
|
||||
},
|
||||
|
||||
newReply: {
|
||||
properties: function () {
|
||||
return Comments.getNotificationProperties(this.data.comment, this.data.post);
|
||||
},
|
||||
subject: function () {
|
||||
return this.authorName+' replied to your comment on "'+this.postTitle+'"';
|
||||
},
|
||||
emailTemplate: "emailNewReply",
|
||||
onsiteTemplate: "notification_new_reply"
|
||||
},
|
||||
|
||||
newCommentSubscribed: {
|
||||
properties: function () {
|
||||
return Comments.getNotificationProperties(this.data.comment, this.data.post);
|
||||
},
|
||||
subject: function () {
|
||||
return this.authorName+' left a new comment on "' + this.postTitle + '"';
|
||||
},
|
||||
emailTemplate: "notification_new_comment",
|
||||
onsite: "notification_new_comment"
|
||||
}
|
||||
return post;
|
||||
|
||||
}
|
||||
Telescope.callbacks.add("postSubmitAsync", postSubmitNotification);
|
||||
};
|
||||
|
||||
function postApprovedNotification (post) {
|
||||
Herald.createNotification(post.userId, {courier: 'postApproved', data: post});
|
||||
return post;
|
||||
}
|
||||
Telescope.callbacks.add("postApprovedAsync", postApprovedNotification);
|
||||
// set up couriers
|
||||
_.each(notifications, function (notification, notificationName) {
|
||||
|
||||
// add new comment notification callback on comment submit
|
||||
function addCommentNotification (comment) {
|
||||
|
||||
if(Meteor.isServer && !comment.disableNotifications){
|
||||
|
||||
var post = Posts.findOne(comment.postId),
|
||||
notificationData = {
|
||||
comment: _.pick(comment, '_id', 'userId', 'author', 'body'),
|
||||
post: _.pick(post, '_id', 'userId', 'title', 'url')
|
||||
},
|
||||
postAuthor = Users.findOne(post.userId),
|
||||
userIdsNotified = [];
|
||||
|
||||
// 1. Notify author of post (if they have new comment notifications turned on)
|
||||
// but do not notify author of post if they're the ones posting the comment
|
||||
if (Users.getSetting(postAuthor, "notifications.comments", true) && comment.userId !== postAuthor._id) {
|
||||
Herald.createNotification(post.userId, {courier: 'newComment', data: notificationData});
|
||||
userIdsNotified.push(post.userId);
|
||||
}
|
||||
|
||||
// 2. Notify author of comment being replied to
|
||||
if (!!comment.parentCommentId) {
|
||||
|
||||
var parentComment = Comments.findOne(comment.parentCommentId);
|
||||
|
||||
// do not notify author of parent comment if they're also post author or comment author
|
||||
// (someone could be replying to their own comment)
|
||||
if (parentComment.userId !== post.userId && parentComment.userId !== comment.userId) {
|
||||
|
||||
var parentCommentAuthor = Users.findOne(parentComment.userId);
|
||||
|
||||
// do not notify parent comment author if they have reply notifications turned off
|
||||
if (Users.getSetting(parentCommentAuthor, "notifications.replies", true)) {
|
||||
|
||||
// add parent comment to notification data
|
||||
notificationData.parentComment = _.pick(parentComment, '_id', 'userId', 'author');
|
||||
|
||||
Herald.createNotification(parentComment.userId, {courier: 'newReply', data: notificationData});
|
||||
userIdsNotified.push(parentComment.userId);
|
||||
var courier = {
|
||||
media: {
|
||||
email: {
|
||||
emailRunner: function (user) {
|
||||
var properties = notification.properties.call(this);
|
||||
var subject = notification.subject.call(properties);
|
||||
var html = Telescope.email.buildTemplate(Telescope.email.getTemplate(notification.emailTemplate)(properties));
|
||||
Telescope.email.send(Users.getEmail(user), subject, html);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3. Notify users subscribed to the thread
|
||||
// TODO: ideally this would be injected from the telescope-subscribe-to-posts package
|
||||
if (!!post.subscribers) {
|
||||
|
||||
// remove userIds of users that have already been notified
|
||||
// and of comment author (they could be replying in a thread they're subscribed to)
|
||||
var subscriberIdsToNotify = _.difference(post.subscribers, userIdsNotified, [comment.userId]);
|
||||
Herald.createNotification(subscriberIdsToNotify, {courier: 'newCommentSubscribed', data: notificationData});
|
||||
|
||||
userIdsNotified = userIdsNotified.concat(subscriberIdsToNotify);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return comment;
|
||||
|
||||
}
|
||||
|
||||
Telescope.callbacks.add("commentSubmitAsync", addCommentNotification);
|
||||
|
||||
var emailNotifications = {
|
||||
fieldName: 'emailNotifications',
|
||||
fieldSchema: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: true,
|
||||
autoform: {
|
||||
group: 'notifications',
|
||||
instructions: 'Enable email notifications for new posts and new comments (requires restart).'
|
||||
}
|
||||
}
|
||||
};
|
||||
Settings.addField(emailNotifications);
|
||||
|
||||
// make it possible to disable notifications on a per-comment basis
|
||||
Comments.addField(
|
||||
{
|
||||
fieldName: 'disableNotifications',
|
||||
fieldSchema: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
autoform: {
|
||||
omit: true
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Add notifications options to user profile settings
|
||||
Users.addField([
|
||||
{
|
||||
fieldName: 'telescope.notifications.users',
|
||||
fieldSchema: {
|
||||
label: 'New users',
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: false,
|
||||
editableBy: ['admin'],
|
||||
autoform: {
|
||||
group: 'Email Notifications'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldName: 'telescope.notifications.posts',
|
||||
fieldSchema: {
|
||||
label: 'New posts',
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: false,
|
||||
editableBy: ['admin', 'member'],
|
||||
autoform: {
|
||||
group: 'Email Notifications'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldName: 'telescope.notifications.comments',
|
||||
fieldSchema: {
|
||||
label: 'Comments on my posts',
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: true,
|
||||
editableBy: ['admin', 'member'],
|
||||
autoform: {
|
||||
group: 'Email Notifications'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldName: 'telescope.notifications.replies',
|
||||
fieldSchema: {
|
||||
label: 'Replies to my comments',
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: true,
|
||||
editableBy: ['admin', 'member'],
|
||||
autoform: {
|
||||
group: 'Email Notifications'
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
function setNotificationDefaults (user) {
|
||||
// set notifications default preferences
|
||||
user.telescope.notifications = {
|
||||
users: false,
|
||||
posts: false,
|
||||
comments: true,
|
||||
replies: true
|
||||
};
|
||||
return user;
|
||||
}
|
||||
Telescope.callbacks.add("onCreateUser", setNotificationDefaults);
|
||||
|
||||
if (!!notification.onsiteTemplate) {
|
||||
courier.media.onsite = {};
|
||||
courier.message = function () {
|
||||
var properties = notification.properties.call(this);
|
||||
return Blaze.toHTML(Blaze.With(properties, function () {
|
||||
return Template[notification.onsiteTemplate];
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
Herald.addCourier(notificationName, courier);
|
||||
|
||||
});
|
|
@ -2,53 +2,6 @@ getUnsubscribeLink = function(user){
|
|||
return Telescope.utils.getRouteUrl('unsubscribe', {hash: user.telescope.emailHash});
|
||||
};
|
||||
|
||||
// given a notification, return the correct subject and html to send an email
|
||||
buildEmailNotification = function (notification) {
|
||||
|
||||
var subject,
|
||||
template,
|
||||
post = notification.data.post,
|
||||
comment = notification.data.comment;
|
||||
|
||||
switch(notification.courier){
|
||||
|
||||
case 'newComment':
|
||||
subject = notification.author()+' left a new comment on your post "' + post.title + '"';
|
||||
template = 'emailNewComment';
|
||||
break;
|
||||
|
||||
case 'newReply':
|
||||
subject = notification.author()+' replied to your comment on "'+post.title+'"';
|
||||
template = 'emailNewReply';
|
||||
break;
|
||||
|
||||
case 'newCommentSubscribed':
|
||||
subject = notification.author()+' left a new comment on "' + post.title + '"';
|
||||
template = 'emailNewComment';
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
var emailProperties = _.extend(notification.data, {
|
||||
body: marked(comment.body),
|
||||
profileUrl: Users.getProfileUrl({_id: comment.userId}),
|
||||
postCommentUrl: Telescope.utils.getPostCommentUrl(post._id, comment._id),
|
||||
postLink: Posts.getLink(post)
|
||||
});
|
||||
|
||||
// console.log(emailProperties)
|
||||
|
||||
var notificationHtml = Telescope.email.getTemplate(template)(emailProperties);
|
||||
var html = Telescope.email.buildTemplate(notificationHtml);
|
||||
|
||||
return {
|
||||
subject: subject,
|
||||
html: html
|
||||
};
|
||||
};
|
||||
|
||||
Meteor.methods({
|
||||
unsubscribeUser : function(hash){
|
||||
// TO-DO: currently, if you have somebody's email you can unsubscribe them
|
||||
|
|
|
@ -13,4 +13,95 @@ Meteor.startup(function () {
|
|||
}
|
||||
});
|
||||
|
||||
});
|
||||
// New user email
|
||||
|
||||
Router.route('/email/new-user/:id?', {
|
||||
name: 'newUser',
|
||||
where: 'server',
|
||||
action: function() {
|
||||
var html;
|
||||
var user = Meteor.users.findOne(this.params.id);
|
||||
var emailProperties = {
|
||||
profileUrl: Users.getProfileUrl(user),
|
||||
username: Users.getUserName(user)
|
||||
};
|
||||
html = Telescope.email.getTemplate('emailNewUser')(emailProperties);
|
||||
this.response.write(Telescope.email.buildTemplate(html));
|
||||
this.response.end();
|
||||
}
|
||||
});
|
||||
|
||||
// New post email
|
||||
|
||||
Router.route('/email/new-post/:id?', {
|
||||
name: 'newPost',
|
||||
where: 'server',
|
||||
action: function() {
|
||||
var html;
|
||||
var post = Posts.findOne(this.params.id);
|
||||
if (!!post) {
|
||||
html = Telescope.email.getTemplate('emailNewPost')(Posts.getNotificationProperties(post));
|
||||
} else {
|
||||
html = "<h3>No post found.</h3>"
|
||||
}
|
||||
this.response.write(Telescope.email.buildTemplate(html));
|
||||
this.response.end();
|
||||
}
|
||||
});
|
||||
|
||||
// Post approved
|
||||
|
||||
Router.route('/email/post-approved/:id?', {
|
||||
name: 'postApproved',
|
||||
where: 'server',
|
||||
action: function() {
|
||||
var html;
|
||||
var post = Posts.findOne(this.params.id);
|
||||
if (!!post) {
|
||||
html = Telescope.email.getTemplate('emailPostApproved')(Posts.getNotificationProperties(post));
|
||||
} else {
|
||||
html = "<h3>No post found.</h3>"
|
||||
}
|
||||
this.response.write(Telescope.email.buildTemplate(html));
|
||||
this.response.end();
|
||||
}
|
||||
});
|
||||
|
||||
// New comment email
|
||||
|
||||
Router.route('/email/new-comment/:id?', {
|
||||
name: 'newComment',
|
||||
where: 'server',
|
||||
action: function() {
|
||||
var html;
|
||||
var comment = Comments.findOne(this.params.id);
|
||||
var post = Posts.findOne(comment.postId);
|
||||
if (!!comment) {
|
||||
html = Telescope.email.getTemplate('emailNewComment')(Comments.getNotificationProperties(comment, post));
|
||||
} else {
|
||||
html = "<h3>No post found.</h3>"
|
||||
}
|
||||
this.response.write(Telescope.email.buildTemplate(html));
|
||||
this.response.end();
|
||||
}
|
||||
});
|
||||
|
||||
// New reply email
|
||||
|
||||
Router.route('/email/new-reply/:id?', {
|
||||
name: 'newReply',
|
||||
where: 'server',
|
||||
action: function() {
|
||||
var html;
|
||||
var comment = Comments.findOne(this.params.id);
|
||||
var post = Posts.findOne(comment.postId);
|
||||
if (!!comment) {
|
||||
html = Telescope.email.getTemplate('emailNewReply')(Comments.getNotificationProperties(comment, post));
|
||||
} else {
|
||||
html = "<h3>No post found.</h3>"
|
||||
}
|
||||
this.response.write(Telescope.email.buildTemplate(html));
|
||||
this.response.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,13 +12,15 @@ Package.onUse(function (api) {
|
|||
api.use([
|
||||
'telescope:core@0.20.6',
|
||||
'kestanous:herald@1.3.0',
|
||||
'kestanous:herald-email@0.5.0',
|
||||
'cmather:handlebars-server@0.2.0'
|
||||
'kestanous:herald-email@0.5.0'
|
||||
]);
|
||||
|
||||
api.addFiles([
|
||||
'lib/notifications.js',
|
||||
'lib/herald.js',
|
||||
'lib/helpers.js',
|
||||
'lib/custom_fields.js',
|
||||
'lib/notifications.js',
|
||||
'lib/callbacks.js',
|
||||
'lib/modules.js',
|
||||
'package-tap.i18n'
|
||||
], ['client', 'server']);
|
||||
|
@ -39,7 +41,14 @@ Package.onUse(function (api) {
|
|||
|
||||
api.addFiles([
|
||||
'lib/server/notifications-server.js',
|
||||
'lib/server/routes.js'
|
||||
'lib/server/routes.js',
|
||||
'lib/server/templates/emailAccountApproved.handlebars',
|
||||
'lib/server/templates/emailNewComment.handlebars',
|
||||
'lib/server/templates/emailNewPost.handlebars',
|
||||
'lib/server/templates/emailNewPendingPost.handlebars',
|
||||
'lib/server/templates/emailPostApproved.handlebars',
|
||||
'lib/server/templates/emailNewReply.handlebars',
|
||||
'lib/server/templates/emailNewUser.handlebars'
|
||||
], ['server']);
|
||||
|
||||
api.addFiles([
|
||||
|
|
|
@ -1,12 +1,62 @@
|
|||
|
||||
//////////////////////////////////////////////////////
|
||||
// Collection Hooks //
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generate HTML body from Markdown on post insert
|
||||
*/
|
||||
Posts.before.insert(function (userId, doc) {
|
||||
if(!!doc.body)
|
||||
doc.htmlBody = Telescope.utils.sanitize(marked(doc.body));
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate HTML body from Markdown when post body is updated
|
||||
*/
|
||||
Posts.before.update(function (userId, doc, fieldNames, modifier) {
|
||||
// if body is being modified, update htmlBody too
|
||||
if (Meteor.isServer && modifier.$set && modifier.$set.body) {
|
||||
modifier.$set.htmlBody = Telescope.utils.sanitize(marked(modifier.$set.body));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate slug when post title is updated
|
||||
*/
|
||||
Posts.before.update(function (userId, doc, fieldNames, modifier) {
|
||||
// if title is being modified, update slug too
|
||||
if (Meteor.isServer && modifier.$set && modifier.$set.title) {
|
||||
modifier.$set.slug = Telescope.utils.slugify(modifier.$set.title);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Disallow $rename
|
||||
*/
|
||||
Posts.before.update(function (userId, doc, fieldNames, modifier) {
|
||||
if (!!modifier.$rename) {
|
||||
throw new Meteor.Error("illegal $rename operator detected!");
|
||||
}
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// Callbacks //
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Increment the user's post count and upvote the post
|
||||
*/
|
||||
function afterPostSubmitOperations (post) {
|
||||
var userId = post.userId,
|
||||
postAuthor = Meteor.users.findOne(userId);
|
||||
|
||||
var userId = post.userId;
|
||||
Meteor.users.update({_id: userId}, {$inc: {"telescope.postCount": 1}});
|
||||
Telescope.upvoteItem(Posts, post, postAuthor);
|
||||
return post;
|
||||
}
|
||||
Telescope.callbacks.add("postSubmitAsync", afterPostSubmitOperations);
|
||||
|
||||
function upvoteOwnPost (post) {
|
||||
var postAuthor = Meteor.users.findOne(post.userId);
|
||||
Telescope.upvoteItem(Posts, post, postAuthor);
|
||||
return post;
|
||||
}
|
||||
Telescope.callbacks.add("postSubmitAsync", upvoteOwnPost);
|
||||
|
|
|
@ -28,9 +28,8 @@ AutoForm.hooks({
|
|||
},
|
||||
|
||||
onSuccess: function(formType, post) {
|
||||
post = this.currentDoc;
|
||||
Events.track("edit post", {'postId': post._id});
|
||||
Router.go('post_page', {_id: post._id});
|
||||
Router.go('post_page', post);
|
||||
},
|
||||
|
||||
onError: function(formType, error) {
|
||||
|
|
|
@ -88,8 +88,6 @@ Template.posts_list_controller.helpers({
|
|||
|
||||
// what to do when user clicks "load more"
|
||||
loadMoreHandler: function (instance) {
|
||||
event.preventDefault();
|
||||
|
||||
// increase limit by 5 and update it
|
||||
var limit = instance.postsLimit.get();
|
||||
limit += Settings.get('postsPerPage', 10);
|
||||
|
|
|
@ -1,31 +1,3 @@
|
|||
///////////////////////////
|
||||
// Notifications Helpers //
|
||||
///////////////////////////
|
||||
|
||||
/**
|
||||
* Grab common post properties (for email notifications, only used on server).
|
||||
* @param {Object} post
|
||||
*/
|
||||
Posts.getProperties = function (post) {
|
||||
var postAuthor = Meteor.users.findOne(post.userId);
|
||||
var p = {
|
||||
postAuthorName : post.getAuthorName(),
|
||||
postTitle : Telescope.utils.cleanUp(post.title),
|
||||
profileUrl: Users.getProfileUrl(postAuthor, true),
|
||||
postUrl: post.getPageUrl(true),
|
||||
thumbnailUrl: post.thumbnailUrl,
|
||||
linkUrl: !!post.url ? Telescope.utils.getOutgoingUrl(post.url) : post.getPageUrl(true)
|
||||
};
|
||||
|
||||
if(post.url)
|
||||
p.url = post.url;
|
||||
|
||||
if(post.htmlBody)
|
||||
p.htmlBody = post.htmlBody;
|
||||
|
||||
return p;
|
||||
};
|
||||
|
||||
//////////////////
|
||||
// Link Helpers //
|
||||
//////////////////
|
||||
|
|
|
@ -66,7 +66,8 @@ Posts.submit = function (post) {
|
|||
|
||||
// --------------------- Server-Side Async Callbacks --------------------- //
|
||||
|
||||
Telescope.callbacks.runAsync("postSubmitAsync", post);
|
||||
// note: query for post to get fresh document with collection-hooks effects applied
|
||||
Telescope.callbacks.runAsync("postSubmitAsync", Posts.findOne(post._id));
|
||||
|
||||
return post;
|
||||
};
|
||||
|
|
|
@ -30,7 +30,7 @@ Posts.getSubParams = function (terms) {
|
|||
_.extend(parameters.options, {limit: parseInt(terms.limit)});
|
||||
|
||||
// limit to "maxLimit" posts at most when limit is undefined, equal to 0, or superior to maxLimit
|
||||
if(!parameters.options.limit || parameters.options.limit == 0 || parameters.options.limit > maxLimit) {
|
||||
if(!parameters.options.limit || parameters.options.limit === 0 || parameters.options.limit > maxLimit) {
|
||||
parameters.options.limit = maxLimit;
|
||||
}
|
||||
|
||||
|
|
|
@ -234,35 +234,3 @@ Posts.allow({
|
|||
remove: _.partial(Telescope.allowCheck, Posts)
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// Collection Hooks //
|
||||
// https://atmospherejs.com/matb33/collection-hooks //
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generate HTML body from Markdown on post insert
|
||||
*/
|
||||
Posts.before.insert(function (userId, doc) {
|
||||
if(!!doc.body)
|
||||
doc.htmlBody = Telescope.utils.sanitize(marked(doc.body));
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate HTML body from Markdown when post body is updated
|
||||
*/
|
||||
Posts.before.update(function (userId, doc, fieldNames, modifier) {
|
||||
// if body is being modified, update htmlBody too
|
||||
if (Meteor.isServer && modifier.$set && modifier.$set.body) {
|
||||
modifier.$set.htmlBody = Telescope.utils.sanitize(marked(modifier.$set.body));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate slug when post title is updated
|
||||
*/
|
||||
Posts.before.update(function (userId, doc, fieldNames, modifier) {
|
||||
// if title is being modified, update slug too
|
||||
if (Meteor.isServer && modifier.$set && modifier.$set.title) {
|
||||
modifier.$set.slug = Telescope.utils.slugify(marked(modifier.$set.title));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -115,7 +115,7 @@ Posts.controllers.page = RouteController.extend({
|
|||
|
||||
template: 'post_page',
|
||||
|
||||
waitOn: function() {
|
||||
waitOn: function () {
|
||||
this.postSubscription = coreSubscriptions.subscribe('singlePost', this.params._id);
|
||||
this.postUsersSubscription = coreSubscriptions.subscribe('postUsers', this.params._id);
|
||||
this.commentSubscription = coreSubscriptions.subscribe('commentsList', {view: 'postComments', postId: this.params._id});
|
||||
|
@ -131,11 +131,9 @@ Posts.controllers.page = RouteController.extend({
|
|||
},
|
||||
|
||||
onBeforeAction: function () {
|
||||
if (! this.post()) {
|
||||
if (!this.post()) {
|
||||
if (this.postSubscription.ready()) {
|
||||
this.render('not_found');
|
||||
} else {
|
||||
this.render('loading');
|
||||
}
|
||||
} else {
|
||||
this.next();
|
||||
|
@ -154,8 +152,11 @@ Posts.controllers.page = RouteController.extend({
|
|||
|
||||
onAfterAction: function () {
|
||||
var post = this.post();
|
||||
if (post && post.slug !== this.params.slug) {
|
||||
window.history.replaceState({}, "", post.getPageUrl());
|
||||
if (post) {
|
||||
if (post.slug !== this.params.slug) {
|
||||
window.history.replaceState({}, "", post.getPageUrl());
|
||||
}
|
||||
$('link[rel="canonical"]').attr("href", post.getPageUrl(true));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -202,33 +203,6 @@ Meteor.startup(function () {
|
|||
controller: Posts.controllers.scheduled
|
||||
});
|
||||
|
||||
// Post Page
|
||||
|
||||
// legacy route
|
||||
Router.route('/posts/:_id', {
|
||||
name: 'post_page_id',
|
||||
onBeforeAction: function () {
|
||||
var post = {
|
||||
slug: '_',
|
||||
_id: this.params._id
|
||||
};
|
||||
Router.go("post_page", post);
|
||||
}
|
||||
});
|
||||
|
||||
Router.route('/p/:_id/:slug?', {
|
||||
name: 'post_page',
|
||||
controller: Posts.controllers.page
|
||||
});
|
||||
|
||||
Router.route('/posts/:_id/comment/:commentId', {
|
||||
name: 'post_page_comment',
|
||||
controller: Posts.controllers.page,
|
||||
onAfterAction: function () {
|
||||
// TODO: scroll to comment position
|
||||
}
|
||||
});
|
||||
|
||||
// Post Edit
|
||||
|
||||
Router.route('/posts/:_id/edit', {
|
||||
|
@ -249,6 +223,21 @@ Meteor.startup(function () {
|
|||
fastRender: true
|
||||
});
|
||||
|
||||
// Post Page
|
||||
|
||||
Router.route('/posts/:_id/:slug?', {
|
||||
name: 'post_page',
|
||||
controller: Posts.controllers.page
|
||||
});
|
||||
|
||||
Router.route('/posts/:_id/comment/:commentId', {
|
||||
name: 'post_page_comment',
|
||||
controller: Posts.controllers.page,
|
||||
onAfterAction: function () {
|
||||
// TODO: scroll to comment position
|
||||
}
|
||||
});
|
||||
|
||||
// Post Submit
|
||||
|
||||
Router.route('/submit', {
|
||||
|
|
|
@ -33,6 +33,7 @@ Meteor.startup(function () {
|
|||
importRelease('0.20.4');
|
||||
importRelease('0.20.5');
|
||||
importRelease('0.20.6');
|
||||
importRelease('0.21');
|
||||
|
||||
// if this is before the first run, mark all release notes as read to avoid showing them
|
||||
if (!Events.findOne({name: 'firstRun'})) {
|
||||
|
|
|
@ -54,6 +54,7 @@ Package.onUse(function (api) {
|
|||
api.addFiles('releases/0.20.4.md', 'server', { isAsset: true });
|
||||
api.addFiles('releases/0.20.5.md', 'server', { isAsset: true });
|
||||
api.addFiles('releases/0.20.6.md', 'server', { isAsset: true });
|
||||
api.addFiles('releases/0.21.md', 'server', { isAsset: true });
|
||||
|
||||
// i18n languages (must come last)
|
||||
|
||||
|
|
9
packages/telescope-releases/releases/0.21.md
Normal file
9
packages/telescope-releases/releases/0.21.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
### v0.21 “SlugScope”
|
||||
|
||||
* Added URL slugs for posts (i.e. `/posts/xyz/my-post-slug`).
|
||||
* i18n files clean-up.
|
||||
* Added post downvote setting.
|
||||
* Refactored notifications code.
|
||||
* Added `kadira-debug` package.
|
||||
* Fixed avatar bug.
|
||||
* Fixed screen refresh bug on post page.
|
|
@ -453,10 +453,25 @@ Settings.get = function(setting, defaultValue) {
|
|||
}
|
||||
};
|
||||
|
||||
// use custom template for checkboxes - not working yet
|
||||
// if(Meteor.isClient){
|
||||
// AutoForm.setDefaultTemplateForType('afCheckbox', 'settings');
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Add trailing slash if needed on insert
|
||||
*/
|
||||
Settings.before.insert(function (userId, doc) {
|
||||
if(doc.siteUrl && doc.siteUrl.match(/\//g).length === 2) {
|
||||
doc.siteUrl = doc.siteUrl + "/";
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Add trailing slash if needed on update
|
||||
*/
|
||||
Settings.before.update(function (userId, doc, fieldNames, modifier) {
|
||||
if(modifier.$set && modifier.$set.siteUrl && modifier.$set.siteUrl.match(/\//g).length === 2) {
|
||||
modifier.$set.siteUrl = modifier.$set.siteUrl + "/";
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.startup(function () {
|
||||
Settings.allow({
|
||||
|
|
|
@ -6,7 +6,7 @@ Meteor.startup(function () {
|
|||
});
|
||||
},
|
||||
categoryLink: function(){
|
||||
return getCategoryUrl(this.slug);
|
||||
return Categories.getUrl(this.slug);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 01fbc45251e1e7f9fb9f73beaf131db2bd24a698
|
|
@ -1,3 +1,70 @@
|
|||
//////////////////////////////////////////////////////
|
||||
// Collection Hooks //
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generate HTML body from Markdown on user bio insert
|
||||
*/
|
||||
Users.after.insert(function (userId, user) {
|
||||
|
||||
// run create user async callbacks
|
||||
Telescope.callbacks.runAsync("onCreateUserAsync", user);
|
||||
|
||||
// check if all required fields have been filled in. If so, run profile completion callbacks
|
||||
if (Users.hasCompletedProfile(user)) {
|
||||
Telescope.callbacks.runAsync("profileCompletedAsync", user);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate HTML body from Markdown when user bio is updated
|
||||
*/
|
||||
Users.before.update(function (userId, doc, fieldNames, modifier) {
|
||||
// if bio is being modified, update htmlBio too
|
||||
if (Meteor.isServer && modifier.$set && modifier.$set["telescope.bio"]) {
|
||||
modifier.$set["telescope.htmlBio"] = Telescope.utils.sanitize(marked(modifier.$set["telescope.bio"]));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Disallow $rename
|
||||
*/
|
||||
Users.before.update(function (userId, doc, fieldNames, modifier) {
|
||||
if (!!modifier.$rename) {
|
||||
throw new Meteor.Error("illegal $rename operator detected!");
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* If user.telescope.email has changed, check for existing emails and change user.emails if needed
|
||||
*/
|
||||
if (Meteor.isServer) {
|
||||
Users.before.update(function (userId, doc, fieldNames, modifier) {
|
||||
var user = doc;
|
||||
// if email is being modified, update user.emails too
|
||||
if (Meteor.isServer && modifier.$set && modifier.$set["telescope.email"]) {
|
||||
var newEmail = modifier.$set["telescope.email"];
|
||||
// check for existing emails and throw error if necessary
|
||||
var userWithSameEmail = Users.findByEmail(newEmail);
|
||||
if (userWithSameEmail && userWithSameEmail._id !== doc._id) {
|
||||
throw new Meteor.Error("email_taken2", i18n.t("this_email_is_already_taken") + " (" + newEmail + ")");
|
||||
}
|
||||
|
||||
// if user.emails exists, change it too
|
||||
if (!!user.emails) {
|
||||
user.emails[0].address = newEmail;
|
||||
modifier.$set.emails = user.emails;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// Callbacks //
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Set up user object on creation
|
||||
* @param {Object} user – the user object being iterated on and returned
|
||||
|
|
|
@ -51,9 +51,10 @@ Users.is.invited = function (userOrUserId) {
|
|||
Users.is.invitedById = Users.is.invited;
|
||||
|
||||
Meteor.users.helpers({
|
||||
isAdmin: function () {
|
||||
return Users.is.admin(this);
|
||||
},
|
||||
// conflicts with user.isAdmin property
|
||||
// isAdmin: function () {
|
||||
// return Users.is.admin(this);
|
||||
// },
|
||||
isOwner: function (document) {
|
||||
return Users.is.owner(this, document);
|
||||
},
|
||||
|
|
|
@ -187,7 +187,7 @@ Users.schema = new SimpleSchema({
|
|||
},
|
||||
username: {
|
||||
type: String,
|
||||
regEx: /^[a-z0-9A-Z_]{3,15}$/,
|
||||
// regEx: /^[a-z0-9A-Z_]{3,15}$/,
|
||||
public: true,
|
||||
optional: true
|
||||
},
|
||||
|
@ -249,58 +249,3 @@ Users.allow({
|
|||
remove: _.partial(Telescope.allowCheck, Meteor.users)
|
||||
});
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// Collection Hooks //
|
||||
// https://atmospherejs.com/matb33/collection-hooks //
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generate HTML body from Markdown on user bio insert
|
||||
*/
|
||||
Users.after.insert(function (userId, user) {
|
||||
|
||||
// run create user async callbacks
|
||||
Telescope.callbacks.runAsync("onCreateUserAsync", user);
|
||||
|
||||
// check if all required fields have been filled in. If so, run profile completion callbacks
|
||||
if (Users.hasCompletedProfile(user)) {
|
||||
Telescope.callbacks.runAsync("profileCompletedAsync", user);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate HTML body from Markdown when user bio is updated
|
||||
*/
|
||||
Users.before.update(function (userId, doc, fieldNames, modifier) {
|
||||
// if bio is being modified, update htmlBio too
|
||||
if (Meteor.isServer && modifier.$set && modifier.$set["telescope.bio"]) {
|
||||
modifier.$set["telescope.htmlBio"] = Telescope.utils.sanitize(marked(modifier.$set["telescope.bio"]));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* If user.telescope.email has changed, check for existing emails and change user.emails if needed
|
||||
*/
|
||||
if (Meteor.isServer) {
|
||||
Users.before.update(function (userId, doc, fieldNames, modifier) {
|
||||
var user = doc;
|
||||
// if email is being modified, update user.emails too
|
||||
if (Meteor.isServer && modifier.$set && modifier.$set["telescope.email"]) {
|
||||
var newEmail = modifier.$set["telescope.email"];
|
||||
// check for existing emails and throw error if necessary
|
||||
var userWithSameEmail = Users.findByEmail(newEmail);
|
||||
if (userWithSameEmail && userWithSameEmail._id !== doc._id) {
|
||||
throw new Meteor.Error("email_taken2", i18n.t("this_email_is_already_taken") + " (" + newEmail + ")");
|
||||
}
|
||||
|
||||
// if user.emails exists, change it too
|
||||
if (!!user.emails) {
|
||||
user.emails[0].address = newEmail;
|
||||
modifier.$set.emails = user.emails;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
Loading…
Add table
Reference in a new issue