Add hooks so packages can modify profiles

Add the following hooks:
 - ``addToUserSchema``: fields to add to the (currently unused) user
   Schema
 - ``postAuthor``: templates to use when rendering the post author in the
   byline
 - ``userProfileDisplay``: additional templates to add to the user
   profile display.
 - ``userProfileEdit``: additional templates to add to the user profile
   editing form.
 - ``userProfileFinishSignup``: additional templates to show in the view
   for completing user signup (adding email, username, etc).
 - ``userEditRenderedCallbacks``: Callbacks executed on "rendered" for
   user_edit view.
 - ``userEditClientCallbacks``: Callbacks used to further process user
   properties before saving changes in user_edit view.
 - ``userProfileCompleteChecks``: Functions called to determine whether
   a user profile is "complete" (e.g. has email, username, and whatever
   else).

These hooks facilitate package authors changing which profile fields are
displayed, which profile fields are required, and how to display
usernames next to posts.
This commit is contained in:
Charlie DeTar 2014-10-06 17:11:43 -06:00
parent 77d21ea139
commit 83bb43ab46
13 changed files with 112 additions and 11 deletions

View file

@ -35,6 +35,12 @@ postHeading = _.sortBy(postHeading, 'order');
postMeta = _.sortBy(postMeta, 'order');
postAuthor = _.sortBy(postAuthor, 'order');
userProfileDisplay = _.sortBy(userProfileDisplay, 'order');
userProfileEdit = _.sortBy(userProfileEdit, 'order');
userProfileFinishSignup = _.sortBy(userProfileFinishSignup, 'order');
Meteor.startup(function () {
$('#rss-link').attr('title', i18n.t('New Posts'));
});

View file

@ -1,8 +1,16 @@
<template name="postInfo">
<div class="post-meta-item">
<span class="points">{{baseScore}} <span class="unit">{{pointsUnitDisplayText}} </span></span>by <a class="post-author" href="{{profileUrl}}">{{authorName}}</a> <span class="post-time">{{ago}}</span>
<span class="points">{{baseScore}} <span class="unit">{{pointsUnitDisplayText}}</span></span>
{{#each postAuthor}}
{{> UI.dynamic template=getTemplate data=..}}
{{/each}}
<span class="post-time">{{ago}}</span>
{{#if can_edit}}
| <a href="/posts/{{_id}}/edit" class="edit-link goto-edit">Edit</a>
{{/if}}
</div>
</template>
<template name="postAuthorName">
by <a class="post-author" href="{{profileUrl}}">{{authorName}}</a>
</template>

View file

@ -5,18 +5,27 @@ Template[getTemplate('postInfo')].helpers({
can_edit: function(){
return canEdit(Meteor.user(), this);
},
authorName: function(){
return getAuthorName(this);
},
ago: function(){
// if post is approved show submission time, else show creation time.
time = this.status == STATUS_APPROVED ? this.postedAt : this.createdAt;
return moment(time).fromNow();
},
postAuthor: function() {
return postAuthor
},
getTemplate: function() {
return getTemplate(this.template);
}
});
Template[getTemplate('postAuthorName')].helpers({
authorName: function(){
return getAuthorName(this);
},
profileUrl: function(){
// note: we don't want the post to be re-rendered every time user properties change
var user = Meteor.users.findOne(this.userId, {reactive: false});
if(user)
return getProfileUrl(user);
}
});
})

View file

@ -55,6 +55,9 @@
<input name="site" type="text" value="{{profile.site}}" />
</div>
</div>
{{#each userProfileEdit}}
{{> UI.dynamic template=getTemplate data=..}}
{{/each}}
{{#if hasPassword}}
<h3>{{i18n "Change Password?"}}</h3>
<div class="control-group">

View file

@ -31,9 +31,22 @@ Template[getTemplate('user_edit')].helpers({
},
hasPassword: function () {
return hasPassword(Meteor.user());
},
getTemplate: function() {
return getTemplate(this.template);
},
userProfileEdit: function() {
return userProfileEdit;
}
});
Template[getTemplate('user_edit')].rendered = function() {
var instance = this;
userEditRenderedCallbacks.forEach(function(callback) {
callback(instance);
});
};
Template[getTemplate('user_edit')].events({
'submit #account-form': function(e){
e.preventDefault();
@ -89,4 +102,4 @@ Template[getTemplate('user_edit')].events({
}
});
});

View file

@ -16,10 +16,13 @@
<input name="email" type="text" value="{{profile.email}}" />
</div>
</div>
{{#each userProfileFinishSignup}}
{{> UI.dynamic template=getTemplate data=..}}
{{/each}}
<div class="form-actions">
<input type="submit" class="button" value="{{i18n "Submit"}}" />
</div>
</form>
{{/with}}
</div>
</template>
</template>

View file

@ -4,6 +4,12 @@ Template[getTemplate('user_email')].helpers({
},
username: function () {
return getUserName(Meteor.user());
},
getTemplate: function() {
return getTemplate(this.template);
},
userProfileFinishSignup: function() {
return userProfileFinishSignup;
}
});

View file

@ -41,6 +41,9 @@
<td><a href="{{profile.site}}">{{profile.site}}</a></td>
</tr>
{{/if}}
{{#each userProfileDisplay}}
{{> UI.dynamic template=getTemplate data=..}}
{{/each}}
</table>
{{#if canEditProfile}}
<a class="button inline" href="/users/{{slug}}/edit">{{i18n "Edit profile"}}</a>

View file

@ -73,6 +73,12 @@ Template[getTemplate('user_profile')].helpers({
},
hasMoreComments: function () {
return Comments.find({userId: this._id}).count() > Session.get('commentsShown');
},
getTemplate: function() {
return getTemplate(this.template);
},
userProfileDisplay: function() {
return userProfileDisplay;
}
});

View file

@ -1,6 +1,5 @@
var Schema = {};
Schema.User = new SimpleSchema({
var userSchemaObj = {
_id: {
type: String,
optional: true
@ -42,7 +41,14 @@ Schema.User = new SimpleSchema({
optional: true,
blackbox: true
}
};
// add any extra properties to postSchemaObject (provided by packages for example)
_.each(addToUserSchema, function(item){
userSchemaObj[item.propertyName] = item.propertySchema;
});
Schema.User = new SimpleSchema(userSchemaObj);
// Meteor.users.attachSchema(Schema.User);

View file

@ -93,7 +93,12 @@ getCurrentUserEmail = function(){
return Meteor.user() ? getEmail(Meteor.user()) : '';
};
userProfileComplete = function(user) {
return !!getEmail(user);
for (var i = 0; i < userProfileCompleteChecks.length; i++) {
if (!userProfileCompleteChecks[i](user)) {
return false;
}
}
return true;
};
findLast = function(user, collection){

View file

@ -4,6 +4,7 @@
addToPostSchema = [];
addToCommentsSchema = [];
addToSettingsSchema = [];
addToUserSchema = [];
// ------------------------------------- Navigation -------------------------------- //
@ -146,6 +147,19 @@ postMeta = [
order: 5
}
]
postAuthor = [
{
template: 'postAuthorName',
order: 1
}
]
// Additional form fields for display and editing of user profiles.
userProfileDisplay = [];
userProfileEdit = [];
userProfileFinishSignup = [];
// ------------------------------ Callbacks ------------------------------ //
postSubmitRenderedCallbacks = [];
@ -168,6 +182,16 @@ commentEditClientCallbacks = [];
commentEditMethodCallbacks = []; // not used yet
commentAfterEditMethodCallbacks = []; // not used yet
userEditRenderedCallbacks = [];
userEditClientCallbacks = [];
userProfileCompleteChecks = [
function(user) {
return !!getEmail(user) && !!user.username;
}
];
// ------------------------------ Dynamic Templates ------------------------------ //

View file

@ -14,6 +14,7 @@ Package.onUse(function (api) {
'addToPostSchema',
'addToCommentsSchema',
'addToSettingsSchema',
'addToUserSchema',
'preloadSubscriptions',
'primaryNav',
'secondaryNav',
@ -23,7 +24,11 @@ Package.onUse(function (api) {
'postModules',
'postHeading',
'postMeta',
'postAuthor',
'modulePositions',
'userProfileDisplay',
'userProfileEdit',
'userProfileFinishSignup',
'postSubmitRenderedCallbacks',
'postSubmitClientCallbacks',
@ -44,10 +49,14 @@ Package.onUse(function (api) {
'commentEditClientCallbacks',
'commentEditMethodCallbacks',
'commentAfterEditMethodCallbacks',
'userEditRenderedCallbacks',
'userEditClientCallbacks',
'userProfileCompleteChecks',
'getTemplate',
'templates',
'themeSettings'
]);
});
});