summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/components/boards/body.js2
-rw-r--r--client/components/main/header.jade37
-rw-r--r--client/components/sidebar/sidebar.js2
-rw-r--r--collections/boards.js17
-rw-r--r--collections/users.js5
-rw-r--r--sandstorm-pkgdef.capnp60
-rw-r--r--sandstorm.js101
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