Compare commits

...

7 Commits
0.2.4 ... 0.2.6

Author SHA1 Message Date
Stefan Zermatten
43c4122fe3 Fixed stack dragging within the same container 2015-05-11 17:10:58 +02:00
Stefan Zermatten
3f4dcc146a fixed charId's being out of date after re-parenting 2015-05-11 17:10:28 +02:00
Stefan Zermatten
e4600decd0 Added more things to except list for need sign in 2015-05-11 16:51:17 +02:00
Stefan Zermatten
f6df716870 Rewrote how item drag and drop works. Need to update charId's to keep up. 2015-05-11 16:51:02 +02:00
Stefan Zermatten
b99da301cd Updated meteor 2015-05-11 12:16:38 +02:00
Stefan Zermatten
0a01885300 Finished implementing useraccounts 2015-05-11 12:15:00 +02:00
Stefan Zermatten
5cb1515235 Began implementing useraccounts and permissions properly 2015-05-08 12:59:38 +02:00
21 changed files with 497 additions and 226 deletions

View File

@@ -20,3 +20,7 @@ mike:mocha
dburles:mongo-collection-instances dburles:mongo-collection-instances
percolate:migrations percolate:migrations
ecwyne:mathjs ecwyne:mathjs
useraccounts:polymer
accounts-google
splendido:accounts-meld
email

View File

@@ -1,9 +1,11 @@
accounts-base@1.2.0 accounts-base@1.2.0
accounts-google@1.0.4
accounts-oauth@1.1.5
accounts-password@1.1.1 accounts-password@1.1.1
accounts-ui@1.1.5 accounts-ui@1.1.5
accounts-ui-unstyled@1.1.7 accounts-ui-unstyled@1.1.7
aldeed:collection2@2.3.3 aldeed:collection2@2.3.3
aldeed:simple-schema@1.3.2 aldeed:simple-schema@1.3.3
amplify@1.0.0 amplify@1.0.0
autoupdate@1.2.1 autoupdate@1.2.1
base64@1.0.3 base64@1.0.3
@@ -24,6 +26,7 @@ ejson@1.0.6
email@1.0.6 email@1.0.6
fastclick@1.0.3 fastclick@1.0.3
geojson-utils@1.0.3 geojson-utils@1.0.3
google@1.1.5
html-tools@1.0.4 html-tools@1.0.4
htmljs@1.0.4 htmljs@1.0.4
http@1.1.0 http@1.1.0
@@ -44,20 +47,22 @@ less@1.0.14
livedata@1.0.13 livedata@1.0.13
localstorage@1.0.3 localstorage@1.0.3
logging@1.0.7 logging@1.0.7
matb33:collection-hooks@0.7.11 matb33:collection-hooks@0.7.13
meteor@1.1.6 meteor@1.1.6
meteor-platform@1.2.2 meteor-platform@1.2.2
mike:mocha@0.5.3 mike:mocha@0.5.4
minifiers@1.1.5 minifiers@1.1.5
minimongo@1.0.8 minimongo@1.0.8
mobile-status-bar@1.0.3 mobile-status-bar@1.0.3
momentjs:moment@2.10.3 momentjs:moment@2.10.3
mongo@1.1.0 mongo@1.1.0
npm-bcrypt@0.7.8_2 npm-bcrypt@0.7.8_2
oauth@1.1.4
oauth2@1.1.3
observe-sequence@1.0.6 observe-sequence@1.0.6
ordered-dict@1.0.3 ordered-dict@1.0.3
package-version-parser@3.0.3 package-version-parser@3.0.3
percolate:migrations@0.7.3 percolate:migrations@0.7.5
practicalmeteor:chai@1.9.2_3 practicalmeteor:chai@1.9.2_3
practicalmeteor:loglevel@1.1.0_3 practicalmeteor:loglevel@1.1.0_3
random@1.0.3 random@1.0.3
@@ -72,16 +77,21 @@ sanjo:meteor-version@1.0.0
service-configuration@1.0.4 service-configuration@1.0.4
session@1.1.0 session@1.1.0
sha@1.0.3 sha@1.0.3
softwarerero:accounts-t9n@1.0.9
spacebars@1.0.6 spacebars@1.0.6
spacebars-compiler@1.0.6 spacebars-compiler@1.0.6
splendido:accounts-emails-field@1.2.0
splendido:accounts-meld@1.3.0
srp@1.0.3 srp@1.0.3
templating@1.1.1 templating@1.1.1
tracker@1.0.7 tracker@1.0.7
ui@1.0.6 ui@1.0.6
underscore@1.0.3 underscore@1.0.3
url@1.0.4 url@1.0.4
useraccounts:core@1.9.1
useraccounts:polymer@1.9.1
velocity:chokidar@0.12.6_1 velocity:chokidar@0.12.6_1
velocity:core@0.6.0 velocity:core@0.6.1
velocity:html-reporter@0.5.3 velocity:html-reporter@0.5.3
velocity:meteor-internals@1.1.0_7 velocity:meteor-internals@1.1.0_7
velocity:shim@0.1.0 velocity:shim@0.1.0

View File

@@ -19,6 +19,156 @@ Schemas.Item = new SimpleSchema({
Items.attachSchema(Schemas.Item); Items.attachSchema(Schemas.Item);
var checkMovePermission = function(itemId, parent) {
var item = Items.findOne(itemId);
if (!item)
throw new Meteor.Error("No such item",
"An item could not be found to move");
//handle permissions
var permission = Meteor.call("canWriteCharacter", item.charId);
if (!permission){
throw new Meteor.Error("Access denied",
"Not permitted to move items from this character");
}
if (parent.collection === "Characters"){
permission = Meteor.call("canWriteCharacter", parent.id);
if (!permission){
throw new Meteor.Error("Access denied",
"Not permitted to move items to this character");
}
} else {
var parentCollectionObject = global[parent.collection];
var parentObject = null;
if (parentCollectionObject)
parentObject = parentCollectionObject.findOne(
parent.id, {fields: {_id: 1, charId: 1}}
);
if (!parentObject) throw new Meteor.Error(
"Invalid parent",
"The destination parent " + parent.id +
" does not exist in the collection " + parent.collection
);
if (parentObject.charId){
permission = Meteor.call("canWriteCharacter", parentObject.charId);
if (!permission){
throw new Meteor.Error("Access denied",
"Not permitted to move items to this character");
}
}
}
};
var moveItem = function(itemId, enable, parentCollection, parentId) {
var item = Items.findOne(itemId);
if (!item) return;
parentCollection = parentCollection || item.parent.collection;
parentId = parentId || item.parent.id;
if (Meteor.isServer) {
checkMovePermission(itemId, {collection: parentCollection, id: parentId});
}
//update the item provided the update will actually change something
if (
item.parent.collection !== parentCollection ||
item.parent.id !== parentId ||
item.enabled !== enable
){
Items.update(
itemId,
{$set: {
"parent.collection": parentCollection,
"parent.id": parentId,
enabled: enable,
}}
);
}
};
Meteor.methods({
moveItemToParent: function(itemId, parent) {
check(itemId, String);
check(parent, {collection: String, id: String});
moveItem(itemId, false, parent.collection, parent.id);
},
moveItemToCharacter: function(itemId, charId) {
check(itemId, String);
check(charId, String);
moveItem(itemId, false, "Characters", charId);
},
moveItemToContainer: function(itemId, containerId) {
check(itemId, String);
check(containerId, String);
moveItem(itemId, false, "Containers", containerId);
},
equipItem: function(itemId, charId){
check(itemId, String);
check(charId, String);
moveItem(itemId, true, "Characters", charId);
},
unequipItem: function(itemId, charId){
check(itemId, String);
check(charId, String);
moveItem(itemId, false, "Characters", charId);
},
splitItemToParent: function(itemId, moveQuantity, parent){
check(itemId, String);
check(moveQuantity, Number);
check(parent, {id: String, collection: String});
//get the item
var item = Items.findOne(itemId);
if (!item) return;
//don't bother moving nothing
if (moveQuantity <= 0 || item.quantity <= 0){
return;
}
//ensure we are only moving up to the current stack size
if (item.quantity < moveQuantity){
moveQuantity = this.quantity;
}
if (Meteor.isServer) {
checkMovePermission(itemId, parent);
}
//create a new item stack
var newStack = _.omit(EJSON.clone(item), "_id");
newStack.parent = parent;
newStack.quantity = moveQuantity;
//find out if we have an exact replica in the destination
var query = _.omit(newStack, ["parent", "quantity"]);
query["parent.collection"] = newStack.parent.collection;
query["parent.id"] = newStack.parent.id;
query._id = {$ne: itemId}; //make sure we don't join it to itself
var existingStack = Items.findOne(query);
if (existingStack){
//increase the existing stack's size
Items.update(
existingStack._id,
{$inc: {quantity: moveQuantity}}
);
} else {
//insert the new stack
Items.insert(newStack, function(err, id){
if (err) throw err;
//copy the children also
Meteor.call("cloneChildren", item._id, {collection: "Items", id: id});
});
}
//reduce the old stack's size
var oldQuantity = item.quantity - moveQuantity;
if (oldQuantity === 0){
Items.remove(itemId);
} else {
Items.update(itemId, {$set: {quantity: oldQuantity}});
}
},
});
Items.helpers({ Items.helpers({
totalValue: function(){ totalValue: function(){
return this.value * this.quantity; return this.value * this.quantity;
@@ -33,103 +183,6 @@ Items.helpers({
return this.name; return this.name;
} }
}, },
equip: function(characterId){
var charId = characterId || this.charId;
if (!charId || !Characters.findOne(charId)) throw "Invalid character";
if (this.parent.collection === "Characters" &&
this.parent.id === charId &&
this.enabled) {
return;
}
Items.update(
this._id,
{$set: {
"parent.collection": "Characters",
"parent.id": charId,
enabled: true,
}}
);
},
unequip: function(){
if (!this.enabled) return;
Items.update(this._id, {$set: {enabled: false}});
},
moveToContainer: function(containerId){
if (!containerId || !Containers.findOne(containerId)){
throw "Invalid container";
}
if (this.parent.collection === "Containers" &&
this.parent.id === containerId &&
!this.enabled) return;
Items.update(
this._id,
{$set: {
"parent.collection": "Containers",
"parent.id": containerId,
enabled: false,
}}
);
},
moveToCharacter: function(characterId){
if (!characterId || !Characters.findOne(characterId)) {
throw "Invalid character";
}
if (this.parent.collection === "Characters" &&
this.parent.id === characterId &&
!this.enabled) return;
Items.update(
this._id,
{$set: {
"parent.collection": "Characters",
"parent.id": characterId,
charId: characterId,
enabled: false,
}}
);
},
splitToParent: function(parent, moveQuantity){
check(parent, {id: String, collection: String});
check(moveQuantity, Number);
var parentCollection = Meteor.isClient ?
window[parent.collection] : global[parent.collection];
if (!parent.id || !parentCollection.findOne(parent.id)){
throw "Invalid parent";
}
var oldStack = this;
//we can only move as much as we have, leaving 0 behind at worst
if (oldStack.quantity < moveQuantity) moveQuantity = oldStack.quantity;
var oldQuantity = oldStack.quantity - moveQuantity;
var newStack = _.pick(oldStack, Schemas.Item.objectKeys());
newStack.parent = parent;
newStack.quantity = moveQuantity;
var existingStack = Items.findOne(_.omit(newStack, "quantity"));
var updateStackSize = function(){
if (oldQuantity > 0){
Items.update(oldStack._id, {$set: {quantity: oldQuantity}});
} else {
Items.remove(oldStack._id);
}
};
if (existingStack){
Items.update(
existingStack._id,
{$inc: {quantity: moveQuantity}},
{},
function(){
updateStackSize();
}
);
} else {
Items.insert(newStack, function(err, id){
if (err) throw err;
updateStackSize();
//copy the children also
Meteor.call("cloneChildren", oldStack._id, {collection: "Items", id: id});
});
}
},
}); });
Items.before.update(function(userId, doc, fieldNames, modifier, options){ Items.before.update(function(userId, doc, fieldNames, modifier, options){

View File

@@ -1,44 +1,23 @@
Schema = {};
Schema.User = new SimpleSchema({
username: {
type: String,
regEx: /^[a-z0-9A-Z_]{3,15}$/,
optional: true,
},
emails: {
type: [Object],
// this must be optional if you also use other login services like facebook,
// but if you use only accounts-password, then it can be required
optional: true,
},
"emails.$.address": {
type: String,
regEx: SimpleSchema.RegEx.Email,
},
"emails.$.verified": {
type: Boolean
},
createdAt: {
type: Date
},
services: {
type: Object,
optional: true,
blackbox: true,
},
roles: {
type: [String],
optional: true,
},
});
Meteor.users.attachSchema(Schema.User);
Meteor.users.allow({ Meteor.users.allow({
update: function(userId, doc, fields, modifier) { update: function(userId, doc, fields, modifier) {
return userId === doc._id && if (
fields.length === 1 && doc._id === userId &&
fields[0] === "username"; _.contains(fields, "username") &&
_.contains(fields, "profile") &&
fields.length === 2 &&
_.keys(modifier).length === 1 &&
modifier.$set &&
modifier.$set["profile.username"] &&
modifier.$set.username &&
_.keys(modifier.$set).length === 2
){
var expectedUsername = modifier.$set["profile.username"];
expectedUsername = expectedUsername.toLowerCase().replace(/\s+/gm, "");
if (modifier.$set.username !== expectedUsername){
return false;
}
var foundUser = Meteor.call("getUserId", expectedUsername);
return !foundUser || foundUser === userId;
}
} }
}); });

View File

@@ -3,6 +3,23 @@ Router.configure({
layoutTemplate: "layout", layoutTemplate: "layout",
}); });
Router.plugin("ensureSignedIn", {
except: [
"home",
"atSignIn",
"atSignUp",
"atForgotPassword",
"atResetPwd",
"atEnrollAccount",
"atVerifyEmail",
"atresendVerificationEmail",
"loginButtons",
"notFound",
]
});
Router.plugin("dataNotFound", {notFoundTemplate: "notFound"});
Router.map(function() { Router.map(function() {
this.route("/", { this.route("/", {
name: "home", name: "home",
@@ -56,4 +73,8 @@ Router.map(function() {
document.title = appName + " Account"; document.title = appName + " Account";
}, },
}); });
this.route("/loginButtons", {
name: "loginButtons",
})
}); });

View File

@@ -174,71 +174,93 @@ Template.inventoryItem.helpers({
Template.layout.events({ Template.layout.events({
"dragstart .inventoryItem": function(event, instance){ "dragstart .inventoryItem": function(event, instance){
event.originalEvent.dataTransfer.setData("dicecloud-id/items", this._id);
Session.set("inventory.dragItemId", this._id); Session.set("inventory.dragItemId", this._id);
Session.set("inventory.dragItemOriginalContainer", this.container); },
Session.set("inventory.dragItemOriginalCharacter", this.charId); "dragover .itemContainer, dragenter .itemContainer":
function(event, instance){
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
event.preventDefault();
}
},
"dragover .equipmentContainer, dragenter .equipmentContainer":
function(event, instance){
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
event.preventDefault();
}
},
"dragover .carriedContainer, dragenter .carriedContainer":
function(event, instance){
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
event.preventDefault();
}
},
"dragover .characterRepresentative, dragenter .characterRepresentative":
function(event, instance){
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
event.preventDefault();
}
}, },
"dragend .inventoryItem": function(event, instance){ "dragend .inventoryItem": function(event, instance){
resetInvetorySession(); //this is a valid drop zone Session.set("inventory.dragItemId", null);
}, },
"dragover .itemContainer": function(event, instance){ "drop .itemContainer": function(event, instance){
event.preventDefault(); var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
},
"dragover .equipmentContainer": function(event, instance){
event.preventDefault();
},
"dragover .carriedContainer": function(event, instance){
event.preventDefault();
},
"drop .itemContainer": function(event, instacne){
var item = Items.findOne(Session.get("inventory.dragItemId"));
if (event.ctrlKey){ if (event.ctrlKey){
//split the stack to the container //split the stack to the container
GlobalUI.showDialog({ GlobalUI.showDialog({
template: "splitStackDialog", template: "splitStackDialog",
data: { data: {
id: item._id, id: itemId,
parentCollection: "Containers", parentCollection: "Containers",
parentId: this._id, parentId: this._id,
}, },
}); });
} else { } else {
//move item to the container //move item to the container
item.moveToContainer(this._id); Meteor.call("moveItemToContainer", itemId, this._id);
} }
resetInvetorySession(); Session.set("inventory.dragItemId", null);
}, },
"drop .equipmentContainer": function(event, instance){ "drop .equipmentContainer": function(event, instance){
var charId = Session.get("inventory.dragItemOriginalCharacter"); var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
var item = Items.findOne(Session.get("inventory.dragItemId")); Meteor.call("equipItem", itemId, this._id);
item.equip(charId); Session.set("inventory.dragItemId", null);
resetInvetorySession();
}, },
"drop .carriedContainer": function(event, instance){ "drop .carriedContainer": function(event, instance){
var charId = Session.get("inventory.dragItemOriginalCharacter"); var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
var item = Items.findOne(Session.get("inventory.dragItemId"));
if (event.ctrlKey){ if (event.ctrlKey){
//split the stack to the container //split the stack to the container
GlobalUI.showDialog({ GlobalUI.showDialog({
template: "splitStackDialog", template: "splitStackDialog",
data: { data: {
id: item._id, id: itemId,
parentCollection: "Characters", parentCollection: "Characters",
parentId: this._id, parentId: this._id,
}, },
}); });
} else { } else {
//move item to the character //move item to the character
item.moveToCharacter(this._id); Meteor.call("moveItemToCharacter", itemId, this._id);
} }
resetInvetorySession(); Session.set("inventory.dragItemId", null);
},
"drop .characterRepresentative": function(event, instance) {
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
if (event.ctrlKey){
//split the stack to the container
GlobalUI.showDialog({
template: "splitStackDialog",
data: {
id: itemId,
parentCollection: "Characters",
parentId: this._id,
},
});
} else {
//move item to the character
Meteor.call("moveItemToCharacter", itemId, this._id);
}
Session.set("inventory.dragItemId", null);
}, },
}); });
var resetInvetorySession = function(){
_.defer(function(){
Session.set("inventory.dragItemId", null);
Session.set("inventory.dragItemOriginalContainer", null);
Session.set("inventory.dragItemOriginalCharacter", null);
});
};

View File

@@ -81,13 +81,10 @@ Template.itemEdit.events({
}, },
"change #equippedInput": function(event){ "change #equippedInput": function(event){
var equipped = Template.instance().find("#equippedInput").checked; var equipped = Template.instance().find("#equippedInput").checked;
var item = Items.findOne(this._id); if (equipped){
if (item){ Meteor.call("equipItem", this._id, this.charId);
if (equipped){ } else {
item.equip(); Meteor.call("unequipItem", this._id, this.charId);
} else {
item.unequip();
}
} }
}, },
"change #attunementCheckbox": function(event){ "change #attunementCheckbox": function(event){
@@ -107,7 +104,6 @@ Template.containerDropdown.events({
var detail = event.originalEvent.detail; var detail = event.originalEvent.detail;
if (!detail.isSelected) return; if (!detail.isSelected) return;
var containerId = detail.item.getAttribute("name"); var containerId = detail.item.getAttribute("name");
var item = Items.findOne(Template.currentData()._id); Meteor.call("moveItemToContainer", Template.currentData()._id, containerId);
item.moveToContainer(containerId);
} }
}); });

View File

@@ -7,13 +7,12 @@ Template.splitStackDialog.helpers({
Template.splitStackDialog.events({ Template.splitStackDialog.events({
"tap #moveButton": function(event, instance){ "tap #moveButton": function(event, instance){
var item = Items.findOne(this.id); Meteor.call(
if (item){ "splitItemToParent",
item.splitToParent( this.id,
{collection: this.parentCollection , id: this.parentId}, +instance.find("#quantityInput").value,
+instance.find("#quantityInput").value {collection: this.parentCollection , id: this.parentId}
); );
}
}, },
"tap #oneButton":function(event, instance){ "tap #oneButton":function(event, instance){
instance.find("#quantityInput").value = 1; instance.find("#quantityInput").value = 1;

View File

@@ -3,7 +3,7 @@
{{#if characters.count}} {{#if characters.count}}
<div> <div>
{{#each characters}} {{#each characters}}
<div class="singleLineItem">{{name}}</div> <div class="singleLineItem characterRepresentative">{{name}}</div>
{{/each}} {{/each}}
</div> </div>
{{/if}} {{/if}}

View File

@@ -1,13 +1,13 @@
<template name="layout"> <template name="layout">
<core-drawer-panel> <core-drawer-panel>
<core-header-panel drawer navigation flex mode="seamed" class="white"> <core-header-panel drawer navigation flex mode="seamed" class="white">
<div id="accountSummary"> <div id="accountSummary">
{{> loginButtons}}
{{#if currentUser}} {{#if currentUser}}
<div id="profileLink" style="text-decoration: underline; cursor: pointer;"> <div id="profileLink" style="text-decoration: underline; cursor: pointer;">
My account {{profileLink}}
</div> </div>
{{else}}
<a href="/sign-in" style="color: white;">Sign in</a>
{{/if}} {{/if}}
</div> </div>
<div id="navPanel"> <div id="navPanel">

View File

@@ -9,7 +9,11 @@ Template.layout.destroyed = function() {
Template.layout.helpers({ Template.layout.helpers({
notSelected: function(){ notSelected: function(){
return Session.get("global.ui.detailShow") ? "not-selected" : null; return Session.get("global.ui.detailShow") ? "not-selected" : null;
} },
profileLink: function() {
var user = Meteor.user();
return user.profile && user.profile.username || user.username || "My Account";
},
}); });
Template.layout.events({ Template.layout.events({

View File

@@ -0,0 +1,11 @@
<template name="notFound">
<div layout vertical center center-justified fit>
<h2>The data for the page you requested could not be found.</h2>
{{#if currentUser}}
<h2>It might not exist, or you might not have permission to view it.</h2>
{{else}}
<h2>Perhaps you need to sign in first:</h2>
{{atForm}}
{{/if}}
</div>
</template>

View File

@@ -3,11 +3,7 @@
<core-toolbar class="blue-grey white-text"> <core-toolbar class="blue-grey white-text">
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button> <core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
<div id="username" class="clickable" flex> <div id="username" class="clickable" flex>
{{#if username}} {{profileName}}
{{username}}
{{else}}
Tap to set username
{{/if}}
</div> </div>
</core-toolbar> </core-toolbar>
<div id="userProfile" class="padded"> <div id="userProfile" class="padded">
@@ -20,5 +16,7 @@
{{/each}} {{/each}}
</div> </div>
</div> </div>
{{> atForm}}
{{> atNavButton }}
{{/with}} {{/with}}
</template> </template>

View File

@@ -1,3 +1,12 @@
Template.profile.helpers({
profileName: function() {
var user = Meteor.user();
return user.profile && user.profile.username ||
user.username ||
"Tap to set username";
}
});
Template.profile.events({ Template.profile.events({
"tap #username": function(){ "tap #username": function(){
if (this._id === Meteor.userId()){ if (this._id === Meteor.userId()){

View File

@@ -1,9 +1,8 @@
<template name="usernameDialog"> <template name="usernameDialog">
{{#with currentUser}} <div>
<div> <paper-input id="usernameInput" label="Username" value={{profileName}}></paper-input>
<paper-input id="usernameInput" label="Username" value={{username}}></paper-input> </div>
</div> <div style="color: red;" class="vertMargin">{{errorMessage}}</div>
{{/with}}
<paper-button id="cancelButton" affirmative> Cancel </paper-button> <paper-button id="cancelButton" affirmative> Cancel </paper-button>
<paper-button id="changeButton" affirmative> Change Username </paper-button> <paper-button id="changeButton" disabled={{invalid}} affirmative> Change Username </paper-button>
</template> </template>

View File

@@ -1,8 +1,49 @@
var getUsername = function() {
var user = Meteor.user();
return user.profile && user.profile.username || user.username;
};
Template.usernameDialog.onCreated(function() {
this.errorMessage = new ReactiveVar("");
this.username = new ReactiveVar(getUsername());
});
Template.usernameDialog.helpers({
profileName: function() {
return getUsername();
},
invalid: function() {
return !!Template.instance().errorMessage.get();
},
errorMessage: function() {
return Template.instance().errorMessage.get();
},
});
Template.usernameDialog.events({ Template.usernameDialog.events({
"change #usernameInput, input #usernameInput": function(event, instance) {
var username = instance.find("#usernameInput").value;
username = username.trim().toLowerCase().replace(/\s+/gm, "");
if (username.length < 3){
instance.errorMessage.set("Username too short");
} else {
instance.errorMessage.set("Validating...");
Meteor.call("getUserId", username, function(err, userId){
if (userId && userId !== Meteor.userId())
instance.errorMessage.set("This username is taken");
else
instance.errorMessage.set("");
});
}
},
"tap #changeButton": function(event, instance){ "tap #changeButton": function(event, instance){
var username = instance.find("#usernameInput").value;
username = username.trim().replace(/\s+/gm, " ");
var profileName = username;
username = username.toLowerCase().replace(/\s+/gm, "");
Meteor.users.update( Meteor.users.update(
Meteor.userId(), Meteor.userId(),
{$set: {username: instance.find("#usernameInput").value}} {$set: {username: username, "profile.username": profileName}}
); );
} },
}); });

View File

@@ -0,0 +1,13 @@
<template name="titledAtForm">
<core-toolbar class="blue-grey white-text">
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
<div flex>
</div>
</core-toolbar>
<div class="scroll-y padded" fit layout vertical center center-justified>
<paper-shadow class="white" style="max-width: 400px;">
{{> atForm}}
</paper-shadow>
</div>
</template>

View File

@@ -1,3 +1,14 @@
Meteor.methods({
canWriteCharacter: function(charId) {
var userId = this.userId;
var char = Characters.findOne(
charId,
{fields: {owner: 1, writers: 1}}
);
return (userId && char.owner === userId || _.contains(char.writers, userId));
},
});
CHARACTER_SUBSCHEMA_ALLOW = { CHARACTER_SUBSCHEMA_ALLOW = {
// the user must be logged in, and the user must be a writer of the character // the user must be logged in, and the user must be a writer of the character
insert: function(userId, doc) { insert: function(userId, doc) {

View File

@@ -0,0 +1,71 @@
AccountsTemplates.configure({
//behaviour
confirmPassword: true,
enablePasswordChange: true,
enforceEmailVerification: true,
overrideLoginErrors: false,
sendVerificationEmail: true,
lowercaseUsername: true,
//appearance
continuousValidation: true,
negativeValidation: true,
negativeFeedback: true,
showValidating: true,
showAddRemoveServices: true,
showForgotPasswordLink: true,
showResendVerificationEmailLink: true,
});
AccountsTemplates.configureRoute("changePwd", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("enrollAccount", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("forgotPwd", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("resetPwd", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("signIn", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("signUp", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("verifyEmail", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("resendVerificationEmail", {
template: "titledAtForm",
});
if (Meteor.isServer){
Meteor.methods({
"userExists": function(username){
return !!Meteor.users.findOne({username: username});
},
});
}
AccountsTemplates.addField({
_id: "username",
type: "text",
required: true,
func: function(value){
if (Meteor.isClient) {
var self = this;
Meteor.call("userExists", value, function(err, userExists){
if (!userExists)
self.setSuccess();
else
self.setError("This username is taken");
self.setValidating(false);
});
return;
}
// Server
return Meteor.call("userExists", value);
},
});

View File

@@ -42,6 +42,12 @@ var inheritParentProperties = function(doc, collection){
"Document's parent does not exist" "Document's parent does not exist"
); );
var handMeDowns = _.pick(parent, collection.inheritedKeys); var handMeDowns = _.pick(parent, collection.inheritedKeys);
if (
_.contains(collection.inheritedKeys, "charId") &&
doc.parent.collection === "Characters"
){
handMeDowns.charId = doc.parent.id;
}
if (_.isEmpty(handMeDowns)) return; if (_.isEmpty(handMeDowns)) return;
collection.update(doc._id, {$set: handMeDowns}); collection.update(doc._id, {$set: handMeDowns});
}; };
@@ -76,14 +82,13 @@ makeChild = function(collection, inheritedKeys){
} }
}); });
if (Meteor.isClient) { collection.after.update(function(userId, doc, fieldNames, modifier, options) {
collection.after.update(function(userId, doc, fieldNames, modifier, options) { if (modifier && modifier.$set && modifier.$set["parent.id"]){
if (modifier && modifier.$set && modifier.$set.parent){ //when we change parents, inherit its properties
//when we change parents, inherit its properties console.log("re-inheriting")
inheritParentProperties(doc, collection); inheritParentProperties(doc, collection);
} }
}); });
}
collection.softRemoveNode = collection.softRemoveNode || function(id){ collection.softRemoveNode = collection.softRemoveNode || function(id){
collection.softRemove(id); collection.softRemove(id);
@@ -102,14 +107,12 @@ makeParent = function(collection, donatedKeys){
donatedKeys = joinWithDefaultKeys(donatedKeys); donatedKeys = joinWithDefaultKeys(donatedKeys);
var collectionName = collection._collection.name; var collectionName = collection._collection.name;
//after changing, push the changes to all children //after changing, push the changes to all children
if (Meteor.isClient) { collection.after.update(function(userId, doc, fieldNames, modifier, options) {
collection.after.update(function(userId, doc, fieldNames, modifier, options) { modifier = limitModifierToKeys(modifier, donatedKeys);
modifier = limitModifierToKeys(modifier, donatedKeys); doc = _.pick(doc, ["_id", "charId"]);
doc = _.pick(doc, ["_id", "charId"]); if (!modifier) return;
if (!modifier) return; Meteor.call("updateChildren", doc, modifier, true);
Meteor.call("updateChildren", doc, modifier, true); });
});
}
collection.softRemoveNode = function(id){ collection.softRemoveNode = function(id){
Meteor.call("softRemoveNode", collectionName, id); Meteor.call("softRemoveNode", collectionName, id);
}; };

View File

@@ -0,0 +1,27 @@
AccountsMeld.configure({
meldDBCallback: function(sourceUserId, destinationUserId){
// Here you can modify every collection you need for the document referencing
// to sourceUserId to be modified in order to point to destinationUserId
Characters.update(
{owner: sourceUserId},
{$set: {owner: destinationUserId}},
{multi: true}
);
Characters.update(
{writers: sourceUserId},
{
$pull: {writers: sourceUserId},
$addToSet: {writers: destinationUserId},
},
{multi: true}
);
Characters.update(
{readers: sourceUserId},
{
$pull: {readers: sourceUserId},
$addToSet: {readers: destinationUserId},
},
{multi: true}
);
},
});