diff options
-rw-r--r-- | client/components/boards/body.js | 2 | ||||
-rw-r--r-- | client/components/main/header.jade | 37 | ||||
-rw-r--r-- | client/components/sidebar/sidebar.js | 2 | ||||
-rw-r--r-- | collections/boards.js | 17 | ||||
-rw-r--r-- | collections/users.js | 5 | ||||
-rw-r--r-- | sandstorm-pkgdef.capnp | 60 | ||||
-rw-r--r-- | sandstorm.js | 101 |
7 files changed, 142 insertions, 82 deletions
diff --git a/client/components/boards/body.js b/client/components/boards/body.js index 612097e4..cf32f764 100644 --- a/client/components/boards/body.js +++ b/client/components/boards/body.js @@ -70,7 +70,7 @@ BlazeComponent.extendComponent({ } }; - if (! Meteor.user().isBoardMember()) + if (! Meteor.userId() || ! Meteor.user().isBoardMember()) return; self.$(lists).sortable({ diff --git a/client/components/main/header.jade b/client/components/main/header.jade index 588c9b6e..510ef484 100644 --- a/client/components/main/header.jade +++ b/client/components/main/header.jade @@ -5,26 +5,27 @@ template(name="header") list all starred boards with a link to go there. This is inspired by the Reddit "subreddit" bar. The first link goes to the boards page. - if currentUser - #header-quick-access - ul - li - +linkTo(route="Boards") - span.fa.fa-home - | All boards - each currentUser.starredBoards - li.separator - - li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}") - +linkTo(route="Board" data=this) - = title - else - li.current Star a board to add a shortcut in this bar. + unless isSandstorm + if currentUser + #header-quick-access + ul + li + +linkTo(route="Boards") + span.fa.fa-home + | All boards + each currentUser.starredBoards + li.separator - + li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}") + +linkTo(route="Board" data=this) + = title + else + li.current Star a board to add a shortcut in this bar. - li - a.js-create-board - i.fa.fa-plus(title="Create a new board") + li + a.js-create-board + i.fa.fa-plus(title="Create a new board") - +headerUserBar + +headerUserBar //- The main bar is a colorful bar that provide all the meta-data for the diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index 764f16eb..6e45b5cf 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -48,7 +48,7 @@ BlazeComponent.extendComponent({ onRendered: function() { var self = this; - if (! Meteor.user().isBoardMember()) + if (! Meteor.userId() || ! Meteor.user().isBoardMember()) return; $(document).on('mouseover', function() { diff --git a/collections/boards.js b/collections/boards.js index e406b10c..2d5c6099 100644 --- a/collections/boards.js +++ b/collections/boards.js @@ -139,7 +139,7 @@ Boards.before.insert(function(userId, doc) { // In some cases (Chinese and Japanese for instance) the `getSlug` function // return an empty string. This is causes bugs in our application so we set // a default slug in this case. - doc.slug = getSlug(doc.title) || 'board'; + doc.slug = doc.slug || getSlug(doc.title) || 'board'; doc.createdAt = new Date(); doc.archived = false; doc.members = [{ @@ -153,22 +153,13 @@ Boards.before.insert(function(userId, doc) { // Handle labels var colors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues; var defaultLabelsColors = _.clone(colors).splice(0, 6); - doc.labels = []; - _.each(defaultLabelsColors, function(val) { - doc.labels.push({ + doc.labels = _.map(defaultLabelsColors, function(val) { + return { _id: Random.id(6), name: '', color: val - }); - }); - - // We randomly chose one of the default background colors for the board - if (Meteor.isClient) { - doc.background = { - type: 'color', - color: Random.choice(Boards.simpleSchema()._schema.color.allowedValues) }; - } + }); }); Boards.before.update(function(userId, doc, fieldNames, modifier) { diff --git a/collections/users.js b/collections/users.js index 1dcccf12..54c0a298 100644 --- a/collections/users.js +++ b/collections/users.js @@ -33,13 +33,10 @@ Users.helpers({ }); Users.before.insert(function(userId, doc) { - doc.profile = {}; + doc.profile = doc.profile || {}; // connect profile.status default doc.profile.status = 'offline'; - - // slugify to username - //doc.username = getSlug(doc.profile.name, ''); }); if (Meteor.isServer) { diff --git a/sandstorm-pkgdef.capnp b/sandstorm-pkgdef.capnp index 51bede4c..54215a06 100644 --- a/sandstorm-pkgdef.capnp +++ b/sandstorm-pkgdef.capnp @@ -1,3 +1,5 @@ +# Use use the meteor-spk tool to generate a sandstorm package (spk) from this +# meteor application source code. https://github.com/sandstorm-io/meteor-spk @0xa5275bd3ad124e12; using Spk = import "/sandstorm/package.capnp"; @@ -10,18 +12,32 @@ const pkgdef :Spk.PackageDefinition = ( # "pkgdef" constant. id = "m86q05rdvj14yvn78ghaxynqz7u2svw6rnttptxx49g1785cdv1h", - # Your app ID is actually its public key. The private key was placed in your + # The app ID is actually its public key. The private key was placed in your # keyring. All updates must be signed with the same key. manifest = ( - # This manifest is included in your app package to tell Sandstorm - # about your app. + # This manifest is included in our app package to tell Sandstorm about our + # app. - appVersion = 1, # Increment this for every release. + appTitle = (defaultText = "LibreBoard"), + # The name of the app as it is displayed to the user. + + appVersion = 2, + # Increment this for every release. + + appMarketingVersion = (defaultText = "0.9.0"), + # Human-readable presentation of the app version. + + minUpgradableAppVersion = 0, + # The minimum version of the app which can be safely replaced by this app + # package without data loss. This might be non-zero if the app's data store + # format changed drastically in the past and the app is no longer able to + # read the old format. actions = [ # Define your "new document" handlers here. - ( title = (defaultText = "New board"), + ( + title = (defaultText = "New board"), command = .myCommand # The command to run when starting for the first time. (".myCommand" is # just a constant defined at the bottom of the file.) @@ -43,11 +59,43 @@ const pkgdef :Spk.PackageDefinition = ( ] ), - alwaysInclude = [ "." ] + alwaysInclude = [ "." ], # This says that we always want to include all files from the source map. (An # alternative is to automatically detect dependencies by watching what the app # opens while running in dev mode. To see what that looks like, run `spk init` # without the -A option.) + + bridgeConfig = ( + viewInfo = ( + permissions = [( + name = "participate", + title = (defaultText = "participate"), + description = (defaultText = "allows participating in the board") + ), ( + name = "configure", + title = (defaultText = "configure"), + description = (defaultText = "allows configuring the board") + )], + + roles = [( + title = (defaultText = "observer"), + permissions = [false, false], + verbPhrase = (defaultText = "can read") + ), ( + title = (defaultText = "member"), + permissions = [true, false], + verbPhrase = (defaultText = "can edit"), + default = true + # ), ( + # title = (defaultText = "administrator"), + # permissions = [true, true], + # verbPhrase = (defaultText = "can configure") + # + # XXX Administrators configuration options aren’t implemented yet, so this + # role is currently useless. + )] + ) + ) ); const myCommand :Spk.Manifest.Command = ( diff --git a/sandstorm.js b/sandstorm.js index 5319ee60..5bfe06be 100644 --- a/sandstorm.js +++ b/sandstorm.js @@ -8,71 +8,94 @@ var isSandstorm = Meteor.settings && Meteor.settings.public && // redirect the user to this particular board. var sandstormBoard = { _id: 'sandstorm', - slug: 'board', // XXX Should be shared with the grain instance name. title: 'LibreBoard', - permission: 'public', - background: { - type: 'color', - color: '#16A085' - }, + slug: 'libreboard', - // XXX Not certain this is a bug, but we except these fields to get inserted - // by the `Lists.before.insert` collection-hook. Since this hook is not called - // in this case, we have to duplicate the logic and set them here. - archived: false, - createdAt: new Date() + // Board access security is handled by sandstorm, so in our point of view we + // can alway assume that the board is public (unauthorized users won’t be able + // to access it anyway). + permission: 'public' +}; + +// The list of permissions a user have is provided by sandstorm accounts +// package. +var userHasPermission = function(user, permission) { + var userPermissions = user.services.sandstorm.permissions; + return userPermissions.indexOf(permission) > -1; }; -// On the first launch of the instance a user is automatically created thanks to -// the `accounts-sandstorm` package. After its creation we insert the unique -// board document. Note that when the `Users.after.insert` hook is called, the -// user is inserted into the database but not connected. So despite the -// appearances `userId` is null in this block. -// -// If the hard-coded board already exists and we are inserting a new user, we -// assume that the owner of the board want to share write privileges with the -// new user. -// XXX Improve that when the Sandstorm sharing model (“Powerbox”) arrives. if (isSandstorm && Meteor.isServer) { + // Redirect the user to the hard-coded board. On the first launch the user + // will be redirected to the board before its creation. But that’s not a + // problem thanks to the reactive board publication. We used to do this + // redirection on the client side but that was sometime visible on loading, + // and the home page was accessible by pressing the back button of the + // browser, a server-side redirection solves both of these issues. + // + // XXX Maybe sandstorm manifest could provide some kind of "home url"? + Router.route('/', function() { + var base = this.request.headers['x-sandstorm-base-path']; + // XXX If this routing scheme changes, this will break. We should generation + // the location url using the router, but at the time of writting, the + // router is only accessible on the client. + var path = '/boards/' + sandstormBoard._id + '/' + sandstormBoard.slug; + + this.response.writeHead(301, { + Location: base + path + }); + this.response.end(); + }, { where: 'server' }); + + // On the first launch of the instance a user is automatically created thanks + // to the `accounts-sandstorm` package. After its creation we insert the + // unique board document. Note that when the `Users.after.insert` hook is + // called, the user is inserted into the database but not connected. So + // despite the appearances `userId` is null in this block. Users.after.insert(function(userId, doc) { if (! Boards.findOne(sandstormBoard._id)) { - Boards.insert(_.extend(sandstormBoard, { userId: doc._id })); + Boards.insert(sandstormBoard, {validate: false}); Boards.update(sandstormBoard._id, { $set: { - 'members.0.userId': doc._id + // The first member (the grain creator) has all rights + 'members.0': { + userId: doc._id, + isActive: true, + isAdmin: true + } } }); - Activities.update({ - activityTypeId: sandstormBoard._id - }, { - $set: { - userId: doc._id - } + Activities.update( + { activityTypeId: sandstormBoard._id }, { + $set: { userId: doc._id } }); - } else { + } + + // If the hard-coded board already exists and we are inserting a new user, + // we need to update our user collection. + else if (userHasPermission(doc, 'participate')) { Boards.update({ _id: sandstormBoard._id, permission: 'public' }, { $push: { - members: doc._id + members: { + userId: doc._id, + isActive: true, + isAdmin: userHasPermission(doc, 'configure') + } } }); } + + // The sandstom user package put the username in `profile.name`. We need to + // move this field value to follow our schema. + Users.update(doc._id, { $rename: { 'profile.name': 'username' }}); }); } -// On the client, redirect the user to the hard-coded board. On the first launch -// the user will be redirected to the board before its creation. But that’s not -// a problem thanks to the reactive board publication. if (isSandstorm && Meteor.isClient) { - Router.go('Board', { - boardId: sandstormBoard._id, - slug: getSlug(sandstormBoard.title) - }); - // XXX Hack. `Meteor.absoluteUrl` doesn't work in Sandstorm, since every // session has a different URL whereas Meteor computes absoluteUrl based on // the ROOT_URL environment variable. So we overwrite this function on a |