mirror of
https://github.com/moparisthebest/kaiwa
synced 2024-11-23 09:42:22 -05:00
Refactor all the things
This commit is contained in:
parent
cae595b1c0
commit
83f68a7a2b
@ -1,4 +1,5 @@
|
|||||||
node_modules
|
node_modules
|
||||||
clientapp/libraries
|
|
||||||
clientapp/modules
|
|
||||||
public
|
public
|
||||||
|
clientapp/libraries
|
||||||
|
clientapp/templates.js
|
||||||
|
clientapp/.build
|
||||||
|
18
.jshintrc
18
.jshintrc
@ -7,17 +7,9 @@
|
|||||||
"white": true,
|
"white": true,
|
||||||
"undef": true,
|
"undef": true,
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"es5": true,
|
"node": true,
|
||||||
"predef": [
|
"trailing": true,
|
||||||
"$",
|
"indent": 4,
|
||||||
"me",
|
"latedef": true,
|
||||||
"confirm",
|
"newcap": true
|
||||||
"alert",
|
|
||||||
"require",
|
|
||||||
"__dirname",
|
|
||||||
"process",
|
|
||||||
"exports",
|
|
||||||
"Buffer",
|
|
||||||
"module"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
86
clientapp/app.js
Normal file
86
clientapp/app.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*global $, app, me, client, XMPP*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var _ = require('underscore');
|
||||||
|
var async = require('async');
|
||||||
|
var Backbone = require('backbone');
|
||||||
|
var MeModel = require('./models/me');
|
||||||
|
var MainView = require('./views/main');
|
||||||
|
var Router = require('./router');
|
||||||
|
var Storage = require('./storage');
|
||||||
|
var xmppEventHandlers = require('./helpers/xmppEventHandlers');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
launch: function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
_.extend(this, Backbone.Events);
|
||||||
|
|
||||||
|
var app = window.app = this;
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
async.series([
|
||||||
|
function (cb) {
|
||||||
|
app.storage = new Storage();
|
||||||
|
app.storage.open(cb);
|
||||||
|
},
|
||||||
|
function (cb) {
|
||||||
|
var me = window.me = new MeModel();
|
||||||
|
|
||||||
|
new Router();
|
||||||
|
app.history = Backbone.history;
|
||||||
|
|
||||||
|
if (!localStorage.config) {
|
||||||
|
return app.navigate('signin');
|
||||||
|
}
|
||||||
|
|
||||||
|
app.view = new MainView({
|
||||||
|
model: me,
|
||||||
|
el: document.body
|
||||||
|
});
|
||||||
|
app.view.render();
|
||||||
|
|
||||||
|
var rosterVer = localStorage.rosterVersion;
|
||||||
|
var config = JSON.parse(localStorage.config);
|
||||||
|
|
||||||
|
config.rosterVer = rosterVer;
|
||||||
|
|
||||||
|
var client = window.client = app.client = XMPP.createClient(config);
|
||||||
|
xmppEventHandlers(client, app);
|
||||||
|
client.connect();
|
||||||
|
|
||||||
|
// we have what we need, we can now start our router and show the appropriate page
|
||||||
|
app.history.start({pushState: true, root: '/'});
|
||||||
|
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
whenConnected: function (func) {
|
||||||
|
if (client.sessionStarted) {
|
||||||
|
func();
|
||||||
|
} else {
|
||||||
|
client.once('session:started', func);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
navigate: function (page) {
|
||||||
|
var url = (page.charAt(0) === '/') ? page.slice(1) : page;
|
||||||
|
app.history.navigate(url, true);
|
||||||
|
},
|
||||||
|
renderPage: function (view, animation) {
|
||||||
|
var container = $('#pages');
|
||||||
|
|
||||||
|
if (app.currentPage) {
|
||||||
|
app.currentPage.hide(animation);
|
||||||
|
}
|
||||||
|
// we call render, but if animation is none, we want to tell the view
|
||||||
|
// to start with the active class already before appending to DOM.
|
||||||
|
container.append(view.render(animation === 'none').el);
|
||||||
|
view.show(animation);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports.launch();
|
@ -1,49 +0,0 @@
|
|||||||
/* global app, XMPP */
|
|
||||||
var Backbone = require('backbone');
|
|
||||||
var MeModel = require('models/me');
|
|
||||||
var MainView = require('views/main');
|
|
||||||
var Router = require('router');
|
|
||||||
var xmppEventHandlers = require('helpers/xmppEventHandlers');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
launch: function () {
|
|
||||||
var app = window.app = this;
|
|
||||||
var me = window.me = new MeModel();
|
|
||||||
|
|
||||||
new Router();
|
|
||||||
app.history = Backbone.history;
|
|
||||||
|
|
||||||
if (!localStorage.config) {
|
|
||||||
return app.navigate('signin');
|
|
||||||
}
|
|
||||||
|
|
||||||
app.view = new MainView({
|
|
||||||
el: document.body,
|
|
||||||
model: me
|
|
||||||
}).render();
|
|
||||||
|
|
||||||
var config = JSON.parse(localStorage.config);
|
|
||||||
var client = window.client = app.client = XMPP.createClient(config);
|
|
||||||
xmppEventHandlers(client, app);
|
|
||||||
client.connect();
|
|
||||||
|
|
||||||
// we have what we need, we can now start our router and show the appropriate page
|
|
||||||
app.history.start({pushState: true, root: '/'});
|
|
||||||
},
|
|
||||||
navigate: function (page) {
|
|
||||||
var url = (page.charAt(0) === '/') ? page.slice(1) : page;
|
|
||||||
app.history.navigate(url, true);
|
|
||||||
},
|
|
||||||
renderPage: function (view, animation) {
|
|
||||||
var container = $('#pages');
|
|
||||||
|
|
||||||
if (app.currentPage) {
|
|
||||||
app.currentPage.hide(animation);
|
|
||||||
}
|
|
||||||
// we call render, but if animation is none, we want to tell the view
|
|
||||||
// to start with the active class already before appending to DOM.
|
|
||||||
container.append(view.render(animation === 'none').el);
|
|
||||||
view.show(animation);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,116 +0,0 @@
|
|||||||
/* global XMPP, client */
|
|
||||||
var StrictModel = require('strictmodel').Model;
|
|
||||||
var Resources = require('./resources');
|
|
||||||
var Messages = require('./messages');
|
|
||||||
var Message = require('./message');
|
|
||||||
var crypto = XMPP.crypto;
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = StrictModel.extend({
|
|
||||||
init: function (attrs) {
|
|
||||||
if (attrs.jid) {
|
|
||||||
this.cid = attrs.jid;
|
|
||||||
}
|
|
||||||
if (!attrs.avatar) {
|
|
||||||
this.useDefaultAvatar();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resources.bind('add remove reset change', this.resourceChange, this);
|
|
||||||
},
|
|
||||||
type: 'contact',
|
|
||||||
props: {
|
|
||||||
jid: ['string', true],
|
|
||||||
name: ['string', true, ''],
|
|
||||||
subscription: ['string', true, 'none'],
|
|
||||||
groups: ['array', true, []]
|
|
||||||
},
|
|
||||||
derived: {
|
|
||||||
displayName: {
|
|
||||||
deps: ['name', 'jid'],
|
|
||||||
fn: function () {
|
|
||||||
if (this.name) {
|
|
||||||
return this.name;
|
|
||||||
}
|
|
||||||
return this.jid;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
deps: ['topResourceStatus', 'offlineStatus'],
|
|
||||||
fn: function () {
|
|
||||||
if (this.topResourceStatus) {
|
|
||||||
return this.topResourceStatus;
|
|
||||||
}
|
|
||||||
return this.offlineStatus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
session: {
|
|
||||||
topResourceStatus: ['string', true, ''],
|
|
||||||
offlineStatus: ['string', true, ''],
|
|
||||||
idleSince: 'date',
|
|
||||||
avatar: 'string',
|
|
||||||
show: ['string', true, 'offline'],
|
|
||||||
chatState: ['string', true, 'gone'],
|
|
||||||
lockedResource: 'string'
|
|
||||||
},
|
|
||||||
collections: {
|
|
||||||
resources: Resources,
|
|
||||||
messages: Messages
|
|
||||||
},
|
|
||||||
useDefaultAvatar: function () {
|
|
||||||
this.avatar = 'https://gravatar.com/avatar/' + crypto.createHash('md5').update(this.jid).digest('hex') + '?s=30&d=mm';
|
|
||||||
},
|
|
||||||
resourceChange: function () {
|
|
||||||
// Manually propagate change events for properties that
|
|
||||||
// depend on the resources collection.
|
|
||||||
this.resources.sort();
|
|
||||||
|
|
||||||
var res = this.resources.first();
|
|
||||||
if (res) {
|
|
||||||
this.offlineStatus = '';
|
|
||||||
this.topResourceStatus = res.status;
|
|
||||||
this.show = res.show || 'online';
|
|
||||||
this.lockedResource = undefined;
|
|
||||||
} else {
|
|
||||||
this.topResourceStatus = '';
|
|
||||||
this.show = 'offline';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fetchHistory: function () {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
client.getHistory({
|
|
||||||
with: this.jid,
|
|
||||||
rsm: {
|
|
||||||
count: 20,
|
|
||||||
before: true
|
|
||||||
}
|
|
||||||
}, function (err, res) {
|
|
||||||
if (err) return;
|
|
||||||
|
|
||||||
var results = res.mamQuery.results || [];
|
|
||||||
results.reverse();
|
|
||||||
results.forEach(function (result) {
|
|
||||||
result = result.toJSON();
|
|
||||||
var msg = result.mam.forwarded.message;
|
|
||||||
|
|
||||||
if (!msg.delay) {
|
|
||||||
msg.delay = result.mam.forwarded.delay;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.replace) {
|
|
||||||
var original = self.messages.get(msg.replace);
|
|
||||||
if (original) {
|
|
||||||
return original.correct(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = new Message();
|
|
||||||
message.cid = msg.id;
|
|
||||||
delete msg.id;
|
|
||||||
message.set(msg);
|
|
||||||
self.messages.add(message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,27 +0,0 @@
|
|||||||
/*global app*/
|
|
||||||
var BaseView = require('strictview'),
|
|
||||||
getOrCall = require('helpers/getOrCall');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = BaseView.extend({
|
|
||||||
show: function (animation) {
|
|
||||||
$('body').scrollTop(0);
|
|
||||||
// set the class so it comes into view
|
|
||||||
//this.$el.addClass('active');
|
|
||||||
// store reference to current page
|
|
||||||
app.currentPage = this;
|
|
||||||
// set the document title
|
|
||||||
document.title = getOrCall(this, 'title') + ' • Stanza.io';
|
|
||||||
// trigger an event to the page model in case we want to respond
|
|
||||||
this.trigger('pageloaded');
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
hide: function () {
|
|
||||||
var self = this;
|
|
||||||
// tell the model we're bailing
|
|
||||||
this.trigger('pageunloaded');
|
|
||||||
// unbind all events bound for this view
|
|
||||||
this.remove();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,31 +0,0 @@
|
|||||||
/*global app*/
|
|
||||||
var BasePage = require('pages/base');
|
|
||||||
var templates = require('templates');
|
|
||||||
var ContactListItem = require('views/contactListItem');
|
|
||||||
var ContactListItemResource = require('views/contactListItemResource');
|
|
||||||
var Message = require('views/message');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = BasePage.extend({
|
|
||||||
template: templates.pages.chat,
|
|
||||||
initialize: function (spec) {
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
imageBindings: {
|
|
||||||
avatar: 'header .avatar'
|
|
||||||
},
|
|
||||||
contentBindings: {
|
|
||||||
name: 'header .name'
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
this.basicRender();
|
|
||||||
this.collectomatic(me.contacts, ContactListItem, {
|
|
||||||
containerEl: this.$('#contactList')
|
|
||||||
}, {quick: true});
|
|
||||||
this.collectomatic(this.model.messages, Message, {
|
|
||||||
containerEl: this.$('#conversation')
|
|
||||||
}, {quick: true});
|
|
||||||
this.handleBindings();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,20 +0,0 @@
|
|||||||
/*global app*/
|
|
||||||
var BasePage = require('pages/base');
|
|
||||||
var templates = require('templates');
|
|
||||||
var ContactListItem = require('views/contactListItem');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = BasePage.extend({
|
|
||||||
template: templates.pages.main,
|
|
||||||
initialize: function (spec) {
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
this.basicRender();
|
|
||||||
this.collectomatic(me.contacts, ContactListItem, {
|
|
||||||
containerEl: this.$('#contactList')
|
|
||||||
}, {quick: true});
|
|
||||||
this.handleBindings();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,21 +0,0 @@
|
|||||||
/*global ui, app*/
|
|
||||||
var BasePage = require('pages/base'),
|
|
||||||
templates = require('templates');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = BasePage.extend({
|
|
||||||
template: templates.layout,
|
|
||||||
classBindings: {
|
|
||||||
},
|
|
||||||
contentBindings: {
|
|
||||||
},
|
|
||||||
hrefBindings: {
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
this.$el.html(this.template());
|
|
||||||
this.handleBindings();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,3 +1,5 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
// get a property that's a function or direct property
|
// get a property that's a function or direct property
|
||||||
module.exports = function (obj, propName) {
|
module.exports = function (obj, propName) {
|
||||||
if (obj[propName] instanceof Function) {
|
if (obj[propName] instanceof Function) {
|
@ -1,11 +1,12 @@
|
|||||||
/* global XMPP */
|
/*global XMPP, me, app, client*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
var crypto = XMPP.crypto;
|
var crypto = XMPP.crypto;
|
||||||
|
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
var imageToDataURI = require('image-to-data-uri');
|
var Contact = require('../models/contact');
|
||||||
var Contact = require('models/contact');
|
var Resource = require('../models/resource');
|
||||||
var Resource = require('models/resource');
|
var Message = require('../models/message');
|
||||||
var Message = require('models/message');
|
|
||||||
|
|
||||||
|
|
||||||
function logScroll() {
|
function logScroll() {
|
||||||
@ -62,8 +63,12 @@ module.exports = function (client, app) {
|
|||||||
|
|
||||||
client.getRoster(function (err, resp) {
|
client.getRoster(function (err, resp) {
|
||||||
resp = resp.toJSON();
|
resp = resp.toJSON();
|
||||||
|
|
||||||
|
localStorage.rosterVersion = resp.roster.ver;
|
||||||
|
|
||||||
_.each(resp.roster.items, function (item) {
|
_.each(resp.roster.items, function (item) {
|
||||||
me.contacts.add(item);
|
console.log(item);
|
||||||
|
me.setContact(item, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.updateCaps();
|
client.updateCaps();
|
||||||
@ -75,22 +80,22 @@ module.exports = function (client, app) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
client.on('roster:update', function (iq) {
|
client.on('roster:update', function (iq) {
|
||||||
var items = iq.toJSON().roster.items;
|
iq = iq.toJSON();
|
||||||
|
var items = iq.roster.items;
|
||||||
|
|
||||||
|
localStorage.rosterVersion = iq.roster.ver;
|
||||||
|
|
||||||
_.each(items, function (item) {
|
_.each(items, function (item) {
|
||||||
var contact = me.getContact(item.jid);
|
var contact = me.getContact(item.jid);
|
||||||
|
|
||||||
if (item.subscription === 'remove') {
|
if (item.subscription === 'remove') {
|
||||||
if (contact) {
|
if (contact) {
|
||||||
me.contacts.remove(contact);
|
me.removeContact(contact);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contact) {
|
me.setContact(item, false);
|
||||||
contact.set(item);
|
|
||||||
} else {
|
|
||||||
me.contacts.add(item);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -132,16 +137,13 @@ module.exports = function (client, app) {
|
|||||||
client.on('avatar', function (info) {
|
client.on('avatar', function (info) {
|
||||||
var contact = me.getContact(info.jid);
|
var contact = me.getContact(info.jid);
|
||||||
if (contact) {
|
if (contact) {
|
||||||
|
var id = '';
|
||||||
|
var type = 'image/png';
|
||||||
if (info.avatars.length > 0) {
|
if (info.avatars.length > 0) {
|
||||||
client.getAvatar(info.jid, info.avatars[0].id, function (err, resp) {
|
id = info.avatars[0].id;
|
||||||
if (err) return;
|
type = info.avatars[0].type || 'image/png';
|
||||||
resp = resp.toJSON();
|
|
||||||
var avatar = resp.pubsub.retrieve.item.avatarData;
|
|
||||||
contact.avatar = 'data:' + info.avatars[0].type + ';base64,' + avatar;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
contact.useDefaultAvatar();
|
|
||||||
}
|
}
|
||||||
|
contact.setAvatar(id, type);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -161,8 +163,16 @@ module.exports = function (client, app) {
|
|||||||
if (contact && !msg.replace) {
|
if (contact && !msg.replace) {
|
||||||
var message = new Message();
|
var message = new Message();
|
||||||
message.cid = msg.id;
|
message.cid = msg.id;
|
||||||
delete msg.id;
|
|
||||||
message.set(msg);
|
message.set(msg);
|
||||||
|
|
||||||
|
if (msg.archived) {
|
||||||
|
msg.archived.forEach(function (archived) {
|
||||||
|
if (me.isMe(archived.by)) {
|
||||||
|
message.id = archived.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
contact.messages.add(message);
|
contact.messages.add(message);
|
||||||
if (!contact.lockedResource) {
|
if (!contact.lockedResource) {
|
||||||
contact.lockedResource = msg.from;
|
contact.lockedResource = msg.from;
|
||||||
@ -216,16 +226,4 @@ module.exports = function (client, app) {
|
|||||||
|
|
||||||
client.emit('message', msg);
|
client.emit('message', msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('message:sent', function (msg) {
|
|
||||||
var contact = me.getContact(msg.to);
|
|
||||||
msg = msg.toJSON();
|
|
||||||
if (contact && msg.body) {
|
|
||||||
var message = new Message();
|
|
||||||
message.cid = msg.id;
|
|
||||||
delete msg.id;
|
|
||||||
message.set(msg);
|
|
||||||
contact.messages.add(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
2
clientapp/libraries/IndexedDBShim.min.js
vendored
Normal file
2
clientapp/libraries/IndexedDBShim.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -422,7 +422,10 @@ Client.prototype.sendMessage = function (data) {
|
|||||||
}
|
}
|
||||||
var message = new Message(data);
|
var message = new Message(data);
|
||||||
|
|
||||||
this.send(new Message(data));
|
this.emit('message:sent', message);
|
||||||
|
this.send(message);
|
||||||
|
|
||||||
|
return data.id;
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.sendPresence = function (data) {
|
Client.prototype.sendPresence = function (data) {
|
||||||
@ -431,6 +434,8 @@ Client.prototype.sendPresence = function (data) {
|
|||||||
data.id = this.nextId();
|
data.id = this.nextId();
|
||||||
}
|
}
|
||||||
this.send(new Presence(data));
|
this.send(new Presence(data));
|
||||||
|
|
||||||
|
return data.id;
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.sendIq = function (data, cb) {
|
Client.prototype.sendIq = function (data, cb) {
|
||||||
@ -449,6 +454,8 @@ Client.prototype.sendIq = function (data, cb) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.send(new Iq(data));
|
this.send(new Iq(data));
|
||||||
|
|
||||||
|
return data.id;
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.getRoster = function (cb) {
|
Client.prototype.getRoster = function (cb) {
|
||||||
@ -2811,34 +2818,6 @@ Result.prototype = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function Archived(data, xml) {
|
|
||||||
return stanza.init(this, xml, data);
|
|
||||||
}
|
|
||||||
Archived.prototype = {
|
|
||||||
constructor: {
|
|
||||||
value: Result
|
|
||||||
},
|
|
||||||
NS: 'urn:xmpp:mam:tmp',
|
|
||||||
EL: 'archived',
|
|
||||||
_name: 'archived',
|
|
||||||
_eventname: 'mam:archived',
|
|
||||||
toString: stanza.toString,
|
|
||||||
toJSON: stanza.toJSON,
|
|
||||||
get by() {
|
|
||||||
return stanza.getAttribute(this.xml, 'by');
|
|
||||||
},
|
|
||||||
set by(value) {
|
|
||||||
stanza.setAttribute(this.xml, 'by', value);
|
|
||||||
},
|
|
||||||
get id() {
|
|
||||||
return stanza.getAttribute(this.xml, 'id');
|
|
||||||
},
|
|
||||||
set id(value) {
|
|
||||||
stanza.setAttribute(this.xml, 'id', value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function Prefs(data, xml) {
|
function Prefs(data, xml) {
|
||||||
return stanza.init(this, xml, data);
|
return stanza.init(this, xml, data);
|
||||||
}
|
}
|
||||||
@ -2904,10 +2883,37 @@ Prefs.prototype = {
|
|||||||
stanza.extend(Iq, MAMQuery);
|
stanza.extend(Iq, MAMQuery);
|
||||||
stanza.extend(Iq, Prefs);
|
stanza.extend(Iq, Prefs);
|
||||||
stanza.extend(Message, Result);
|
stanza.extend(Message, Result);
|
||||||
stanza.extend(Message, Archived);
|
|
||||||
stanza.extend(Result, Forwarded);
|
stanza.extend(Result, Forwarded);
|
||||||
stanza.extend(MAMQuery, RSM);
|
stanza.extend(MAMQuery, RSM);
|
||||||
|
|
||||||
|
|
||||||
|
Message.prototype.__defineGetter__('archived', function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var archives = stanza.find(this.xml, 'urn:xmpp:mam:tmp', 'archived');
|
||||||
|
|
||||||
|
var results = [];
|
||||||
|
archives.forEach(function (archive) {
|
||||||
|
results.push({
|
||||||
|
by: stanza.getAttribute(archive, 'by'),
|
||||||
|
id: stanza.getAttribute(archive, 'id')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
});
|
||||||
|
Message.prototype.__defineSetter__('archived', function (value) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
value.forEach(function (val) {
|
||||||
|
var archive = document.createElementNS('urn:xmpp:mam:tmp', 'archived');
|
||||||
|
stanza.setAttribute(archive, 'by', val.by);
|
||||||
|
stanza.setAttribute(archive, 'id', val.id);
|
||||||
|
self.xml.appendChild(archive);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
exports.MAMQuery = MAMQuery;
|
exports.MAMQuery = MAMQuery;
|
||||||
exports.Result = Result;
|
exports.Result = Result;
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
// our base collection
|
// our base collection
|
||||||
var Backbone = require('backbone');
|
var Backbone = require('backbone');
|
||||||
|
|
179
clientapp/models/contact.js
Normal file
179
clientapp/models/contact.js
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
/*global XMPP, app, me, client*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var async = require('async');
|
||||||
|
var StrictModel = require('strictmodel').Model;
|
||||||
|
var imageToDataURI = require('image-to-data-uri');
|
||||||
|
var Resources = require('./resources');
|
||||||
|
var Messages = require('./messages');
|
||||||
|
var Message = require('./message');
|
||||||
|
var crypto = XMPP.crypto;
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = StrictModel.extend({
|
||||||
|
initialize: function (attrs) {
|
||||||
|
if (attrs.jid) {
|
||||||
|
this.cid = attrs.jid;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setAvatar(attrs.avatarID);
|
||||||
|
|
||||||
|
this.resources.bind('add remove reset change', this.resourceChange, this);
|
||||||
|
},
|
||||||
|
type: 'contact',
|
||||||
|
props: {
|
||||||
|
jid: ['string', true],
|
||||||
|
name: ['string', true, ''],
|
||||||
|
subscription: ['string', true, 'none'],
|
||||||
|
groups: ['array', true, []],
|
||||||
|
avatarID: ['string', true, '']
|
||||||
|
},
|
||||||
|
derived: {
|
||||||
|
displayName: {
|
||||||
|
deps: ['name', 'jid'],
|
||||||
|
fn: function () {
|
||||||
|
if (this.name) {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
return this.jid;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
deps: ['topResourceStatus', 'offlineStatus'],
|
||||||
|
fn: function () {
|
||||||
|
if (this.topResourceStatus) {
|
||||||
|
return this.topResourceStatus;
|
||||||
|
}
|
||||||
|
return this.offlineStatus;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lockedJID: {
|
||||||
|
deps: ['jid', 'lockedResource'],
|
||||||
|
fn: function () {
|
||||||
|
if (this.lockedResource) {
|
||||||
|
return this.jid + '/' + this.lockedResource;
|
||||||
|
}
|
||||||
|
return this.jid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
topResourceStatus: ['string', true, ''],
|
||||||
|
offlineStatus: ['string', true, ''],
|
||||||
|
idleSince: 'date',
|
||||||
|
avatar: 'string',
|
||||||
|
show: ['string', true, 'offline'],
|
||||||
|
chatState: ['string', true, 'gone'],
|
||||||
|
lockedResource: 'string',
|
||||||
|
lastSentMessage: 'object'
|
||||||
|
},
|
||||||
|
collections: {
|
||||||
|
resources: Resources,
|
||||||
|
messages: Messages
|
||||||
|
},
|
||||||
|
setAvatar: function (id, type) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
var gID = crypto.createHash('md5').update(this.jid).digest('hex');
|
||||||
|
self.avatar = 'https://gravatar.com/avatar/' + gID + '?s=30&d=mm';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.storage.avatars.get(id, function (err, avatar) {
|
||||||
|
if (err) {
|
||||||
|
if (!type) {
|
||||||
|
// We can't find the ID, and we don't know the type, so fallback.
|
||||||
|
var gID = crypto.createHash('md5').update(self.jid).digest('hex');
|
||||||
|
self.avatar = 'https://gravatar.com/avatar/' + gID + '?s=30&d=mm';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
app.whenConnected(function () {
|
||||||
|
client.getAvatar(self.jid, id, function (err, resp) {
|
||||||
|
if (err) return;
|
||||||
|
resp = resp.toJSON();
|
||||||
|
var avatarData = resp.pubsub.retrieve.item.avatarData;
|
||||||
|
var dataURI = 'data:' + type + ';base64,' + avatarData;
|
||||||
|
app.storage.avatars.add({id: id, uri: dataURI});
|
||||||
|
self.set({
|
||||||
|
avatar: dataURI,
|
||||||
|
avatarID: id
|
||||||
|
});
|
||||||
|
self.save();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.set({
|
||||||
|
avatar: avatar.uri,
|
||||||
|
avatarID: avatar.id
|
||||||
|
});
|
||||||
|
self.save();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resourceChange: function () {
|
||||||
|
// Manually propagate change events for properties that
|
||||||
|
// depend on the resources collection.
|
||||||
|
this.resources.sort();
|
||||||
|
|
||||||
|
var res = this.resources.first();
|
||||||
|
if (res) {
|
||||||
|
this.offlineStatus = '';
|
||||||
|
this.topResourceStatus = res.status;
|
||||||
|
this.show = res.show || 'online';
|
||||||
|
this.lockedResource = undefined;
|
||||||
|
} else {
|
||||||
|
this.topResourceStatus = '';
|
||||||
|
this.show = 'offline';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchHistory: function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
app.whenConnected(function () {
|
||||||
|
client.getHistory({
|
||||||
|
with: self.jid,
|
||||||
|
rsm: {
|
||||||
|
count: 20,
|
||||||
|
before: true
|
||||||
|
}
|
||||||
|
}, function (err, res) {
|
||||||
|
if (err) return;
|
||||||
|
|
||||||
|
var results = res.mamQuery.results || [];
|
||||||
|
results.reverse();
|
||||||
|
results.forEach(function (result) {
|
||||||
|
result = result.toJSON();
|
||||||
|
var msg = result.mam.forwarded.message;
|
||||||
|
|
||||||
|
if (!msg.delay) {
|
||||||
|
msg.delay = result.mam.forwarded.delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.replace) {
|
||||||
|
var original = self.messages.get(msg.replace);
|
||||||
|
if (original) {
|
||||||
|
return original.correct(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = new Message();
|
||||||
|
message.cid = msg.id || result.mam.id;
|
||||||
|
message.set(msg);
|
||||||
|
self.messages.add(message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
save: function () {
|
||||||
|
var data = {
|
||||||
|
jid: this.jid,
|
||||||
|
name: this.name,
|
||||||
|
groups: this.groups,
|
||||||
|
subscription: this.subscription,
|
||||||
|
avatarID: this.avatarID
|
||||||
|
};
|
||||||
|
app.storage.roster.add(data);
|
||||||
|
}
|
||||||
|
});
|
@ -1,3 +1,7 @@
|
|||||||
|
/*global app*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var async = require('async');
|
||||||
var BaseCollection = require('./baseCollection');
|
var BaseCollection = require('./baseCollection');
|
||||||
var Contact = require('./contact');
|
var Contact = require('./contact');
|
||||||
|
|
||||||
@ -40,13 +44,20 @@ module.exports = BaseCollection.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
initialize: function (model, options) {
|
initialize: function (model, options) {
|
||||||
|
var self = this;
|
||||||
this.bind('change', this.orderChange, this);
|
this.bind('change', this.orderChange, this);
|
||||||
this.bind('add', this.fetchHistory, this);
|
|
||||||
|
app.storage.roster.getAll(function (err, contacts) {
|
||||||
|
if (err) return;
|
||||||
|
|
||||||
|
contacts.forEach(function (contact) {
|
||||||
|
contact = new Contact(contact);
|
||||||
|
contact.save();
|
||||||
|
self.add(contact);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
orderChange: function () {
|
orderChange: function () {
|
||||||
this.sort();
|
this.sort();
|
||||||
},
|
|
||||||
fetchHistory: function (contact) {
|
|
||||||
contact.fetchHistory();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
@ -1,5 +1,9 @@
|
|||||||
|
/*global app*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
var StrictModel = require('strictmodel');
|
var StrictModel = require('strictmodel');
|
||||||
var Contacts = require('./contacts');
|
var Contacts = require('./contacts');
|
||||||
|
var Contact = require('./contact');
|
||||||
|
|
||||||
|
|
||||||
module.exports = StrictModel.Model.extend({
|
module.exports = StrictModel.Model.extend({
|
||||||
@ -34,6 +38,21 @@ module.exports = StrictModel.Model.extend({
|
|||||||
}
|
}
|
||||||
return this.contacts.get(jid);
|
return this.contacts.get(jid);
|
||||||
},
|
},
|
||||||
|
setContact: function (data, create) {
|
||||||
|
var contact = this.getContact(data.jid);
|
||||||
|
if (contact) {
|
||||||
|
contact.set(data);
|
||||||
|
contact.save();
|
||||||
|
} else if (create) {
|
||||||
|
contact = new Contact(data);
|
||||||
|
contact.save();
|
||||||
|
this.contacts.add(contact);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeContact: function (jid) {
|
||||||
|
this.contacts.remove(jid);
|
||||||
|
app.storage.roster.remove(jid);
|
||||||
|
},
|
||||||
isMe: function (jid) {
|
isMe: function (jid) {
|
||||||
var hasResource = jid.indexOf('/') > 0;
|
var hasResource = jid.indexOf('/') > 0;
|
||||||
if (hasResource) {
|
if (hasResource) {
|
@ -1,17 +1,24 @@
|
|||||||
|
/*global me*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
var StrictModel = require('strictmodel').Model;
|
var StrictModel = require('strictmodel').Model;
|
||||||
|
|
||||||
|
|
||||||
module.exports = StrictModel.extend({
|
module.exports = StrictModel.extend({
|
||||||
init: function (attrs) {
|
initialize: function (attrs) {
|
||||||
|
console.log(attrs);
|
||||||
this._created = Date.now();
|
this._created = Date.now();
|
||||||
},
|
},
|
||||||
type: 'message',
|
type: 'message',
|
||||||
|
idDefinition: {
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
to: ['string', true, ''],
|
to: ['string', true, ''],
|
||||||
from: ['string', true, ''],
|
from: ['string', true, ''],
|
||||||
body: ['string', true, ''],
|
body: ['string', true, ''],
|
||||||
type: ['string', true, 'normal'],
|
type: ['string', true, 'normal'],
|
||||||
acked: ['bool', true, false],
|
acked: ['bool', true, false]
|
||||||
},
|
},
|
||||||
derived: {
|
derived: {
|
||||||
mine: {
|
mine: {
|
||||||
@ -38,15 +45,18 @@ module.exports = StrictModel.extend({
|
|||||||
formattedTime: {
|
formattedTime: {
|
||||||
deps: ['created'],
|
deps: ['created'],
|
||||||
fn: function () {
|
fn: function () {
|
||||||
|
if (this.created) {
|
||||||
return this.created.format('{MM}/{dd} {h}:{mm}{t}');
|
return this.created.format('{MM}/{dd} {h}:{mm}{t}');
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
_created: 'date',
|
_created: 'date',
|
||||||
receiptReceived: ['bool', true, false],
|
receiptReceived: ['bool', true, false],
|
||||||
edited: ['bool', true, false],
|
edited: ['bool', true, false],
|
||||||
delay: 'object',
|
delay: 'object'
|
||||||
},
|
},
|
||||||
correct: function (msg) {
|
correct: function (msg) {
|
||||||
if (this.from !== msg.from) return;
|
if (this.from !== msg.from) return;
|
@ -1,3 +1,5 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
var BaseCollection = require('./baseCollection');
|
var BaseCollection = require('./baseCollection');
|
||||||
var Message = require('./message');
|
var Message = require('./message');
|
||||||
|
|
@ -1,8 +1,10 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
var StrictModel = require('strictmodel').Model;
|
var StrictModel = require('strictmodel').Model;
|
||||||
|
|
||||||
|
|
||||||
module.exports = StrictModel.extend({
|
module.exports = StrictModel.extend({
|
||||||
init: function () {},
|
initialize: function () {},
|
||||||
type: 'resource',
|
type: 'resource',
|
||||||
session: {
|
session: {
|
||||||
jid: ['string', true],
|
jid: ['string', true],
|
@ -1,3 +1,5 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
var BaseCollection = require('./baseCollection');
|
var BaseCollection = require('./baseCollection');
|
||||||
var Resource = require('./resource');
|
var Resource = require('./resource');
|
||||||
|
|
@ -1,28 +0,0 @@
|
|||||||
// follow @HenrikJoreteg and @andyet if you like this ;)
|
|
||||||
(function (window) {
|
|
||||||
var ls = window.localStorage,
|
|
||||||
out = {},
|
|
||||||
inNode = typeof process !== 'undefined';
|
|
||||||
|
|
||||||
if (inNode) {
|
|
||||||
module.exports = console;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ls && ls.debug && window.console) {
|
|
||||||
out = window.console;
|
|
||||||
} else {
|
|
||||||
var methods = "assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(","),
|
|
||||||
l = methods.length,
|
|
||||||
fn = function () {};
|
|
||||||
|
|
||||||
while (l--) {
|
|
||||||
out[methods[l]] = fn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof exports !== 'undefined') {
|
|
||||||
module.exports = out;
|
|
||||||
} else {
|
|
||||||
window.console = out;
|
|
||||||
}
|
|
||||||
})(this);
|
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +0,0 @@
|
|||||||
// simple commonJS cookie reader, best perf according to http://jsperf.com/cookie-parsing
|
|
||||||
module.exports = function (name) {
|
|
||||||
var cookie = document.cookie,
|
|
||||||
setPos = cookie.indexOf(name + '='),
|
|
||||||
stopPos = cookie.indexOf(';', setPos),
|
|
||||||
res;
|
|
||||||
if (!~setPos) return null;
|
|
||||||
res = decodeURIComponent(cookie.substring(setPos, ~stopPos ? stopPos : undefined).split('=')[1]);
|
|
||||||
return (res.charAt(0) === '{') ? JSON.parse(res) : res;
|
|
||||||
};
|
|
@ -1,229 +0,0 @@
|
|||||||
(function () {
|
|
||||||
// reference to our currently focused element
|
|
||||||
var focusedEl;
|
|
||||||
|
|
||||||
function getFluidGridFunction(selector) {
|
|
||||||
return function (focus) {
|
|
||||||
reOrganize(selector, focus);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function biggestBox(container, aspectRatio) {
|
|
||||||
var aspectRatio = aspectRatio || (3 / 4),
|
|
||||||
height = (container.width * aspectRatio),
|
|
||||||
res = {};
|
|
||||||
|
|
||||||
if (height > container.height) {
|
|
||||||
return {
|
|
||||||
height: container.height,
|
|
||||||
width: container.height / aspectRatio
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
width: container.width,
|
|
||||||
height: container.width * aspectRatio
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function reOrganize(selector, focus) {
|
|
||||||
var floor = Math.floor,
|
|
||||||
elements = $(selector),
|
|
||||||
howMany = elements.length,
|
|
||||||
howManyNonFocused = function () {
|
|
||||||
var hasFocused = !!elements.find('.focused').length;
|
|
||||||
if (hasFocused && howMany > 1) {
|
|
||||||
return howMany - 1;
|
|
||||||
} else if (hasFocused && howMany === 1) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return howMany;
|
|
||||||
}
|
|
||||||
}(),
|
|
||||||
|
|
||||||
totalAvailableWidth = window.innerWidth,
|
|
||||||
totalAvailableHeight = window.innerHeight - 140,
|
|
||||||
|
|
||||||
availableWidth = totalAvailableWidth,
|
|
||||||
availableHeight = totalAvailableHeight,
|
|
||||||
|
|
||||||
container = {
|
|
||||||
width: availableWidth,
|
|
||||||
height: availableHeight
|
|
||||||
},
|
|
||||||
columnPadding = 15,
|
|
||||||
minimumWidth = 290,
|
|
||||||
aspectRatio = 3 / 4,
|
|
||||||
|
|
||||||
numberOfColumns,
|
|
||||||
numberOfRows,
|
|
||||||
|
|
||||||
numberOfPaddingColumns,
|
|
||||||
numberOfPaddingRows,
|
|
||||||
|
|
||||||
itemDimensions,
|
|
||||||
totalWidth,
|
|
||||||
|
|
||||||
videoWidth,
|
|
||||||
leftMargin,
|
|
||||||
|
|
||||||
videoHeight,
|
|
||||||
usedHeight,
|
|
||||||
topMargin,
|
|
||||||
|
|
||||||
// do we have one selected?
|
|
||||||
// this is because having a single
|
|
||||||
// focused element is not treated
|
|
||||||
// differently, but we don't want to
|
|
||||||
// lose that reference.
|
|
||||||
haveFocusedEl;
|
|
||||||
|
|
||||||
|
|
||||||
// if we passed in a string here (could be "none")
|
|
||||||
// then we want to either set or clear our current
|
|
||||||
// focused element.
|
|
||||||
if (focus) focusedEl = $(focus)[0];
|
|
||||||
|
|
||||||
// make sure our cached focused element is still
|
|
||||||
// attached.
|
|
||||||
if (focusedEl && !$(focusedEl).parent().length) focusedEl = undefined;
|
|
||||||
|
|
||||||
// figure out if we should consider us as having any
|
|
||||||
// special focused elements
|
|
||||||
haveFocusedEl = focusedEl && howManyNonFocused > 1;
|
|
||||||
|
|
||||||
elements.height(availableHeight);
|
|
||||||
|
|
||||||
// how we want the to stack at different numbers
|
|
||||||
if (haveFocusedEl) {
|
|
||||||
numberOfColumns = howManyNonFocused - 1;
|
|
||||||
numberOfRows = 1;
|
|
||||||
availableHeight = totalAvailableHeight * .2;
|
|
||||||
} else if (howManyNonFocused === 0) {
|
|
||||||
return;
|
|
||||||
} else if (howManyNonFocused === 1) {
|
|
||||||
numberOfColumns = 1;
|
|
||||||
numberOfRows = 1;
|
|
||||||
} else if (howManyNonFocused === 2) {
|
|
||||||
if (availableWidth > availableHeight) {
|
|
||||||
numberOfColumns = 2;
|
|
||||||
numberOfRows = 1;
|
|
||||||
} else {
|
|
||||||
numberOfColumns = 1;
|
|
||||||
numberOfRows = 2;
|
|
||||||
}
|
|
||||||
} else if (howManyNonFocused === 3) {
|
|
||||||
if (availableWidth > availableHeight) {
|
|
||||||
numberOfColumns = 3;
|
|
||||||
numberOfRows = 1;
|
|
||||||
} else {
|
|
||||||
numberOfColumns = 1;
|
|
||||||
numberOfRows = 3;
|
|
||||||
}
|
|
||||||
} else if (howManyNonFocused === 4) {
|
|
||||||
numberOfColumns = 2;
|
|
||||||
numberOfRows = 2;
|
|
||||||
} else if (howManyNonFocused === 5) {
|
|
||||||
numberOfColumns = 3;
|
|
||||||
numberOfRows = 2;
|
|
||||||
} else if (howManyNonFocused === 6) {
|
|
||||||
if (availableWidth > availableHeight) {
|
|
||||||
numberOfColumns = 3;
|
|
||||||
numberOfRows = 2;
|
|
||||||
} else {
|
|
||||||
numberOfColumns = 2;
|
|
||||||
numberOfRows = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
itemDimensions = biggestBox({
|
|
||||||
width: availableWidth / numberOfColumns,
|
|
||||||
height: availableHeight / numberOfRows
|
|
||||||
});
|
|
||||||
|
|
||||||
numberOfPaddingColumns = numberOfColumns - 1;
|
|
||||||
numberOfPaddingRows = numberOfRows - 1;
|
|
||||||
|
|
||||||
totalWidth = itemDimensions.width * numberOfColumns;
|
|
||||||
|
|
||||||
videoWidth = function () {
|
|
||||||
var totalWidthLessPadding = totalWidth - (columnPadding * numberOfPaddingColumns);
|
|
||||||
return totalWidthLessPadding / numberOfColumns;
|
|
||||||
}();
|
|
||||||
|
|
||||||
leftMargin = (availableWidth - totalWidth) / 2;
|
|
||||||
|
|
||||||
videoHeight = itemDimensions.height - ((numberOfRows > 1) ? (columnPadding / numberOfRows) : 0);
|
|
||||||
usedHeight = (numberOfRows * videoHeight);
|
|
||||||
topMargin = (availableHeight - usedHeight) / 2;
|
|
||||||
|
|
||||||
if (haveFocusedEl) {
|
|
||||||
elements = elements.not('.focused');
|
|
||||||
}
|
|
||||||
|
|
||||||
elements.each(function (index) {
|
|
||||||
var order = index,
|
|
||||||
row = floor(order / numberOfColumns),
|
|
||||||
column = order % numberOfColumns,
|
|
||||||
intensity = 12,
|
|
||||||
rotation = function () {
|
|
||||||
if (numberOfColumns === 3) {
|
|
||||||
if (column === 0) {
|
|
||||||
return 1;
|
|
||||||
} else if (column === 1) {
|
|
||||||
return 0;
|
|
||||||
} else if (column === 2) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
} else if (numberOfColumns === 2) {
|
|
||||||
intensity = 5;
|
|
||||||
return column == 1 ? -1 : 1
|
|
||||||
} else if (numberOfColumns === 1) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}(),
|
|
||||||
transformation = 'rotateY(' + (rotation * intensity) + 'deg)';
|
|
||||||
|
|
||||||
if (rotation === 0) {
|
|
||||||
transformation += ' scale(.98)';
|
|
||||||
}
|
|
||||||
|
|
||||||
var calculatedTop;
|
|
||||||
if (haveFocusedEl) {
|
|
||||||
calculatedTop = (totalAvailableHeight * .8) + topMargin + 'px';
|
|
||||||
} else {
|
|
||||||
calculatedTop = (row * itemDimensions.height) + topMargin + 'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
$(this).css({
|
|
||||||
//transform: transformation,
|
|
||||||
top: calculatedTop,
|
|
||||||
left: (column * itemDimensions.width) + leftMargin + 'px',
|
|
||||||
width: videoWidth + 'px',
|
|
||||||
height: videoHeight + 'px',
|
|
||||||
position: 'absolute'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (haveFocusedEl) {
|
|
||||||
var focusSize = biggestBox({
|
|
||||||
height: (totalAvailableHeight * .8),
|
|
||||||
width: totalAvailableWidth
|
|
||||||
}, focusedEl.videoHeight / focusedEl.videoWidth);
|
|
||||||
|
|
||||||
$(focusedEl).css({
|
|
||||||
top: 0,
|
|
||||||
height: focusSize.height - topMargin,
|
|
||||||
width: focusSize.width,
|
|
||||||
left: (totalAvailableWidth / 2) - (focusSize.width / 2)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof exports !== 'undefined') {
|
|
||||||
module.exports = getFluidGridFunction;
|
|
||||||
} else {
|
|
||||||
window.getFluidGridFunction = getFluidGridFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
@ -1,31 +0,0 @@
|
|||||||
// converts a URL of an image into a dataURI
|
|
||||||
module.exports = function (url, cb) {
|
|
||||||
// Create an empty canvas and image elements
|
|
||||||
var canvas = document.createElement('canvas'),
|
|
||||||
img = document.createElement('img');
|
|
||||||
|
|
||||||
img.onload = function () {
|
|
||||||
var ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
// match size of image
|
|
||||||
canvas.width = img.width;
|
|
||||||
canvas.height = img.height;
|
|
||||||
|
|
||||||
// Copy the image contents to the canvas
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
|
|
||||||
// Get the data-URI formatted image
|
|
||||||
cb(canvas.toDataURL('image/png'));
|
|
||||||
};
|
|
||||||
|
|
||||||
img.ononerror = function () {
|
|
||||||
cb(new Error('FailedToLoadImage'));
|
|
||||||
};
|
|
||||||
|
|
||||||
// canvas is not supported
|
|
||||||
if (!canvas.getContext) {
|
|
||||||
cb(new Error('CanvasIsNotSupported'));
|
|
||||||
} else {
|
|
||||||
img.src = url;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,21 +0,0 @@
|
|||||||
// replaces all whitespace with '-' and removes
|
|
||||||
// all non-url friendly characters
|
|
||||||
(function () {
|
|
||||||
var whitespace = /\s+/g,
|
|
||||||
nonAscii = /[^A-Za-z0-9_ \-]/g;
|
|
||||||
|
|
||||||
function slugger(string, opts) {
|
|
||||||
var maintainCase = opts && opts.maintainCase || false,
|
|
||||||
replacement = opts && opts.replacement || '-',
|
|
||||||
key;
|
|
||||||
if (typeof string !== 'string') return '';
|
|
||||||
if (!maintainCase) string = string.toLowerCase();
|
|
||||||
return string.replace(nonAscii, '').replace(whitespace, replacement);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof module !== 'undefined') {
|
|
||||||
module.exports = slugger;
|
|
||||||
} else {
|
|
||||||
window.slugger = slugger;
|
|
||||||
}
|
|
||||||
})();
|
|
@ -1,140 +0,0 @@
|
|||||||
/*
|
|
||||||
SoundEffectManager
|
|
||||||
|
|
||||||
Loads and plays sound effects useing
|
|
||||||
HTML5 Web Audio API (as only available in webkit, at the moment).
|
|
||||||
|
|
||||||
By @HenrikJoreteg from &yet
|
|
||||||
*/
|
|
||||||
/*global webkitAudioContext define*/
|
|
||||||
(function () {
|
|
||||||
var root = this;
|
|
||||||
|
|
||||||
function SoundEffectManager() {
|
|
||||||
this.support = !!window.webkitAudioContext;
|
|
||||||
if (this.support) {
|
|
||||||
this.context = new webkitAudioContext();
|
|
||||||
}
|
|
||||||
this.sounds = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// async load a file at a given URL, store it as 'name'.
|
|
||||||
SoundEffectManager.prototype.loadFile = function (url, name, delay, cb) {
|
|
||||||
if (this.support) {
|
|
||||||
this._loadWebAudioFile(url, name, delay, cb);
|
|
||||||
} else {
|
|
||||||
this._loadWaveFile(url.replace('.mp3', '.wav'), name, delay, 3, cb);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// async load a file at a given URL, store it as 'name'.
|
|
||||||
SoundEffectManager.prototype._loadWebAudioFile = function (url, name, delay, cb) {
|
|
||||||
if (!this.support) return;
|
|
||||||
var self = this,
|
|
||||||
request = new XMLHttpRequest();
|
|
||||||
|
|
||||||
request.open("GET", url, true);
|
|
||||||
request.responseType = "arraybuffer";
|
|
||||||
request.onload = function () {
|
|
||||||
self.sounds[name] = self.context.createBuffer(request.response, true);
|
|
||||||
cb && cb();
|
|
||||||
};
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
request.send();
|
|
||||||
}, delay || 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
SoundEffectManager.prototype._loadWaveFile = function (url, name, delay, multiplexLimit, cb) {
|
|
||||||
var self = this,
|
|
||||||
limit = multiplexLimit || 3;
|
|
||||||
setTimeout(function () {
|
|
||||||
var a, i = 0;
|
|
||||||
|
|
||||||
self.sounds[name] = [];
|
|
||||||
while (i < limit) {
|
|
||||||
a = new Audio();
|
|
||||||
a.src = url;
|
|
||||||
// for our callback
|
|
||||||
if (i === 0 && cb) {
|
|
||||||
a.addEventListener('canplaythrough', cb, false);
|
|
||||||
}
|
|
||||||
a.load();
|
|
||||||
self.sounds[name][i++] = a;
|
|
||||||
}
|
|
||||||
}, delay || 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
SoundEffectManager.prototype._playWebAudio = function (soundName) {
|
|
||||||
var buffer = this.sounds[soundName],
|
|
||||||
source;
|
|
||||||
|
|
||||||
if (!buffer) return;
|
|
||||||
|
|
||||||
// creates a sound source
|
|
||||||
source = this.context.createBufferSource();
|
|
||||||
// tell the source which sound to play
|
|
||||||
source.buffer = buffer;
|
|
||||||
// connect the source to the context's destination (the speakers)
|
|
||||||
source.connect(this.context.destination);
|
|
||||||
// play it
|
|
||||||
source.noteOn(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
SoundEffectManager.prototype._playWavAudio = function (soundName, loop) {
|
|
||||||
var self = this,
|
|
||||||
audio = this.sounds[soundName],
|
|
||||||
howMany = audio && audio.length || 0,
|
|
||||||
i = 0,
|
|
||||||
currSound;
|
|
||||||
|
|
||||||
if (!audio) return;
|
|
||||||
|
|
||||||
while (i < howMany) {
|
|
||||||
currSound = audio[i++];
|
|
||||||
// this covers case where we loaded an unplayable file type
|
|
||||||
if (currSound.error) return;
|
|
||||||
if (currSound.currentTime === 0 || currSound.currentTime === currSound.duration) {
|
|
||||||
currSound.currentTime = 0;
|
|
||||||
currSound.loop = !!loop;
|
|
||||||
i = howMany;
|
|
||||||
return currSound.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
SoundEffectManager.prototype.play = function (soundName, loop) {
|
|
||||||
if (this.support) {
|
|
||||||
this._playWebAudio(soundName, loop);
|
|
||||||
} else {
|
|
||||||
return this._playWavAudio(soundName, loop);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
SoundEffectManager.prototype.stop = function (soundName) {
|
|
||||||
if (this.support) {
|
|
||||||
// TODO: this
|
|
||||||
} else {
|
|
||||||
var soundArray = this.sounds[soundName],
|
|
||||||
howMany = soundArray && soundArray.length || 0,
|
|
||||||
i = 0,
|
|
||||||
currSound;
|
|
||||||
|
|
||||||
while (i < howMany) {
|
|
||||||
currSound = soundArray[i++];
|
|
||||||
currSound.pause();
|
|
||||||
currSound.currentTime = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// attach to window or export with commonJS
|
|
||||||
if (typeof module !== "undefined") {
|
|
||||||
module.exports = SoundEffectManager;
|
|
||||||
} else if (typeof root.define === "function" && define.amd) {
|
|
||||||
root.define(SoundEffectManager);
|
|
||||||
} else {
|
|
||||||
root.SoundEffectManager = SoundEffectManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
@ -1,565 +0,0 @@
|
|||||||
// (c) 2013 Henrik Joreteg
|
|
||||||
// MIT Licensed
|
|
||||||
// For all details and documentation:
|
|
||||||
// https://github.com/HenrikJoreteg/StrictModel
|
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// Initial setup
|
|
||||||
// -------------
|
|
||||||
|
|
||||||
// Establish the root object, `window` in the browser, or `global` on the server.
|
|
||||||
var root = this;
|
|
||||||
|
|
||||||
// The top-level namespace. All public Backbone classes and modules will
|
|
||||||
// be attached to this. Exported for both CommonJS and the browser.
|
|
||||||
var Strict = typeof exports !== 'undefined' ? exports : root.Strict = {},
|
|
||||||
toString = Object.prototype.toString,
|
|
||||||
slice = Array.prototype.slice;
|
|
||||||
|
|
||||||
// Current version of the library. Keep in sync with `package.json`.
|
|
||||||
Strict.VERSION = '0.0.1';
|
|
||||||
|
|
||||||
// Require Underscore, if we're on the server, and it's not already present.
|
|
||||||
var _ = root._;
|
|
||||||
if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
|
|
||||||
|
|
||||||
// Require Backbone, if we're on the server, and it's not already present.
|
|
||||||
var Backbone = root.Backbone;
|
|
||||||
if (!Backbone && (typeof require !== 'undefined')) Backbone = require('backbone');
|
|
||||||
|
|
||||||
// Backbone Collection compatibility fix:
|
|
||||||
// In backbone, when you add an already instantiated model to a collection
|
|
||||||
// the collection checks to see if what you're adding is already a model
|
|
||||||
// the problem is, it does this witn an instanceof check. We're wanting to
|
|
||||||
// use completely different models so the instanceof will fail even if they
|
|
||||||
// are "real" models. So we work around this by overwriting this method from
|
|
||||||
// backbone 1.0.0. The only difference is it compares against our Strict.Model
|
|
||||||
// instead of backbone's.
|
|
||||||
Backbone.Collection.prototype._prepareModel = function (attrs, options) {
|
|
||||||
if (attrs instanceof Strict.Model) {
|
|
||||||
if (!attrs.collection) attrs.collection = this;
|
|
||||||
return attrs;
|
|
||||||
}
|
|
||||||
options || (options = {});
|
|
||||||
options.collection = this;
|
|
||||||
var model = new this.model(attrs, options);
|
|
||||||
if (!model._validate(attrs, options)) {
|
|
||||||
this.trigger('invalid', this, attrs, options);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helpers
|
|
||||||
// -------
|
|
||||||
|
|
||||||
// Shared empty constructor function to aid in prototype-chain creation.
|
|
||||||
var Constructor = function () {};
|
|
||||||
|
|
||||||
// Helper function to correctly set up the prototype chain, for subclasses.
|
|
||||||
// Similar to `goog.inherits`, but uses a hash of prototype properties and
|
|
||||||
// class properties to be extended.
|
|
||||||
var inherits = function (parent, protoProps, staticProps) {
|
|
||||||
var child;
|
|
||||||
|
|
||||||
// The constructor function for the new subclass is either defined by you
|
|
||||||
// (the "constructor" property in your `extend` definition), or defaulted
|
|
||||||
// by us to simply call the parent's constructor.
|
|
||||||
if (protoProps && protoProps.hasOwnProperty('constructor')) {
|
|
||||||
child = protoProps.constructor;
|
|
||||||
} else {
|
|
||||||
child = function () { return parent.apply(this, arguments); };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inherit class (static) properties from parent.
|
|
||||||
_.extend(child, parent);
|
|
||||||
|
|
||||||
// Set the prototype chain to inherit from `parent`, without calling
|
|
||||||
// `parent`'s constructor function.
|
|
||||||
Constructor.prototype = parent.prototype;
|
|
||||||
child.prototype = new Constructor();
|
|
||||||
|
|
||||||
// Add prototype properties (instance properties) to the subclass,
|
|
||||||
// if supplied.
|
|
||||||
if (protoProps) _.extend(child.prototype, protoProps);
|
|
||||||
|
|
||||||
// Add static properties to the constructor function, if supplied.
|
|
||||||
if (staticProps) _.extend(child, staticProps);
|
|
||||||
|
|
||||||
// Correctly set child's `prototype.constructor`.
|
|
||||||
child.prototype.constructor = child;
|
|
||||||
|
|
||||||
// Set a convenience property in case the parent's prototype is needed later.
|
|
||||||
child.__super__ = parent.prototype;
|
|
||||||
|
|
||||||
return child;
|
|
||||||
};
|
|
||||||
|
|
||||||
var extend = function (protoProps, classProps) {
|
|
||||||
var child = inherits(this, protoProps, classProps);
|
|
||||||
child.extend = this.extend;
|
|
||||||
return child;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mixins
|
|
||||||
// ------
|
|
||||||
|
|
||||||
// Sugar for defining properties a la ES5.
|
|
||||||
var Mixins = Strict.Mixins = {
|
|
||||||
// shortcut for Object.defineProperty
|
|
||||||
define: function (name, def) {
|
|
||||||
Object.defineProperty(this, name, def);
|
|
||||||
},
|
|
||||||
|
|
||||||
defineGetter: function (name, handler) {
|
|
||||||
this.define(name, {
|
|
||||||
get: handler.bind(this)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
defineSetter: function (name, handler) {
|
|
||||||
this.define(name, {
|
|
||||||
set: handler.bind(this)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Strict.Registry
|
|
||||||
// ---------------
|
|
||||||
|
|
||||||
// Internal storage for models, seperate namespace
|
|
||||||
// storage from default to prevent collision of matching
|
|
||||||
// model type+id and namespace name
|
|
||||||
|
|
||||||
var Registry = Strict.Registry = function () {
|
|
||||||
this._cache = {};
|
|
||||||
this._namespaces = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Attach all inheritable methods to the Registry prototype.
|
|
||||||
_.extend(Registry.prototype, {
|
|
||||||
// Get the general or namespaced internal cache
|
|
||||||
_getCache: function (ns) {
|
|
||||||
if (ns) {
|
|
||||||
this._namespaces[ns] || (this._namespaces[ns] = {});
|
|
||||||
return this._namespaces[ns];
|
|
||||||
}
|
|
||||||
return this._cache;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Find the cached model
|
|
||||||
lookup: function (type, id, ns) {
|
|
||||||
var cache = this._getCache(ns);
|
|
||||||
return cache && cache[type + id];
|
|
||||||
},
|
|
||||||
|
|
||||||
// Add a model to the cache if it has not already been set
|
|
||||||
store: function (model) {
|
|
||||||
var cache = this._getCache(model._namespace),
|
|
||||||
key = model.type + model.id;
|
|
||||||
// Prevent overriding a previously stored model
|
|
||||||
cache[key] = cache[key] || model;
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Remove a stored model from the cache, return `true` if removed
|
|
||||||
remove: function (type, id, ns) {
|
|
||||||
var cache = this._getCache(ns);
|
|
||||||
if (this.lookup.apply(this, arguments)) {
|
|
||||||
delete cache[type + id];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Reset internal cache
|
|
||||||
clear: function () {
|
|
||||||
this._cache = {};
|
|
||||||
this._namespaces = {};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create the default Strict.registry.
|
|
||||||
Strict.registry = new Registry();
|
|
||||||
|
|
||||||
// Strict.Model
|
|
||||||
// ------------
|
|
||||||
|
|
||||||
var Model = Strict.Model = function (attrs, options) {
|
|
||||||
attrs = attrs || {};
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
var modelFound,
|
|
||||||
opts = _.defaults(options || {}, {
|
|
||||||
seal: true
|
|
||||||
});
|
|
||||||
|
|
||||||
this._namespace = opts.namespace;
|
|
||||||
this._initted = false;
|
|
||||||
this._deps = {};
|
|
||||||
this._initProperties();
|
|
||||||
this._initCollections();
|
|
||||||
this._cache = {};
|
|
||||||
this._verifyRequired();
|
|
||||||
this.set(attrs, {silent: true});
|
|
||||||
this.init.apply(this, arguments);
|
|
||||||
if (attrs.id) Strict.registry.store(this);
|
|
||||||
this._previous = _.clone(this.attributes); // Should this be set right away?
|
|
||||||
this._initted = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Attach all inheritable methods to the Model prototype.
|
|
||||||
_.extend(Model.prototype, Backbone.Events, Mixins, {
|
|
||||||
idAttribute: 'id',
|
|
||||||
idDefinition: {
|
|
||||||
type: 'number',
|
|
||||||
setOnce: true
|
|
||||||
},
|
|
||||||
|
|
||||||
// stubbed out to be overwritten
|
|
||||||
init: function () {
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Remove model from the registry and unbind events
|
|
||||||
remove: function () {
|
|
||||||
if (this.id) {
|
|
||||||
Strict.registry.remove(this.type, this.id, this._namespace);
|
|
||||||
}
|
|
||||||
this.trigger('remove', this);
|
|
||||||
this.off();
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
set: function (key, value, options) {
|
|
||||||
var self = this,
|
|
||||||
changing = self._changing,
|
|
||||||
opts,
|
|
||||||
changes = [],
|
|
||||||
newType,
|
|
||||||
interpretedType,
|
|
||||||
newVal,
|
|
||||||
def,
|
|
||||||
attr,
|
|
||||||
attrs,
|
|
||||||
val;
|
|
||||||
|
|
||||||
self._changing = true;
|
|
||||||
|
|
||||||
// Handle both `"key", value` and `{key: value}` -style arguments.
|
|
||||||
if (_.isObject(key) || key === null) {
|
|
||||||
attrs = key;
|
|
||||||
options = value;
|
|
||||||
} else {
|
|
||||||
attrs = {};
|
|
||||||
attrs[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
opts = options || {};
|
|
||||||
|
|
||||||
// For each `set` attribute...
|
|
||||||
for (attr in attrs) {
|
|
||||||
val = attrs[attr];
|
|
||||||
newType = typeof val;
|
|
||||||
newVal = val;
|
|
||||||
|
|
||||||
def = this.definition[attr] || {};
|
|
||||||
|
|
||||||
// check type if we have one
|
|
||||||
if (def.type === 'date') {
|
|
||||||
if (!_.isDate(val)) {
|
|
||||||
try {
|
|
||||||
newVal = (new Date(parseInt(val, 10))).valueOf();
|
|
||||||
newType = 'date';
|
|
||||||
} catch (e) {
|
|
||||||
newType = typeof val;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newType = 'date';
|
|
||||||
newVal = val.valueOf();
|
|
||||||
}
|
|
||||||
} else if (def.type === 'array') {
|
|
||||||
newType = _.isArray(val) ? 'array' : typeof val;
|
|
||||||
} else if (def.type === 'object') {
|
|
||||||
// we have to have a way of supporting "missing" objects.
|
|
||||||
// Null is an object, but setting a value to undefined
|
|
||||||
// should work too, IMO. We just override it, in that case.
|
|
||||||
if (typeof val !== 'object' && _.isUndefined(val)) {
|
|
||||||
newVal = null;
|
|
||||||
newType = 'object';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a defined type and the new type doesn't match, throw error.
|
|
||||||
// Unless it's not required and the value is undefined.
|
|
||||||
if (def.type && def.type !== newType && (!def.required && !_.isUndefined(val))) {
|
|
||||||
throw new TypeError('Property \'' + attr + '\' must be of type ' + def.type + '. Tried to set ' + val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if trying to set id after it's already been set
|
|
||||||
// reject that
|
|
||||||
if (def.setOnce && def.value !== undefined && !_.isEqual(def.value, newVal)) {
|
|
||||||
throw new TypeError('Property \'' + key + '\' can only be set once.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// only change if different
|
|
||||||
if (!_.isEqual(def.value, newVal)) {
|
|
||||||
self._previous && (self._previous[attr] = def.value);
|
|
||||||
def.value = newVal;
|
|
||||||
changes.push(attr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_.each(changes, function (key) {
|
|
||||||
if (!opts.silent) {
|
|
||||||
self.trigger('change:' + key, self, self[key]);
|
|
||||||
}
|
|
||||||
// TODO: ensure that all deps are not undefined before triggering a change event
|
|
||||||
(self._deps[key] || []).forEach(function (derTrigger) {
|
|
||||||
// blow away our cache
|
|
||||||
delete self._cache[derTrigger];
|
|
||||||
if (!opts.silent) self.trigger('change:' + derTrigger, self, self.derived[derTrigger]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// fire general change events
|
|
||||||
if (changes.length) {
|
|
||||||
if (!opts.silent) self.trigger('change', self);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
get: function (attr) {
|
|
||||||
return this[attr];
|
|
||||||
},
|
|
||||||
|
|
||||||
// convenience methods for manipulating array properties
|
|
||||||
addListVal: function (prop, value, prepend) {
|
|
||||||
var list = _.clone(this[prop]) || [];
|
|
||||||
if (!_(list).contains(value)) {
|
|
||||||
list[prepend ? 'unshift' : 'push'](value);
|
|
||||||
this[prop] = list;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
previous: function (attr) {
|
|
||||||
return attr ? this._previous[attr] : _.clone(this._previous);
|
|
||||||
},
|
|
||||||
|
|
||||||
removeListVal: function (prop, value) {
|
|
||||||
var list = _.clone(this[prop]) || [];
|
|
||||||
if (_(list).contains(value)) {
|
|
||||||
this[prop] = _(list).without(value);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
hasListVal: function (prop, value) {
|
|
||||||
return _.contains(this[prop] || [], value);
|
|
||||||
},
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
_initCollections: function () {
|
|
||||||
var coll;
|
|
||||||
if (!this.collections) return;
|
|
||||||
for (coll in this.collections) {
|
|
||||||
this[coll] = new this.collections[coll]();
|
|
||||||
this[coll].parent = this;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Check that all required attributes are present
|
|
||||||
// TODO: should this throw an error or return boolean?
|
|
||||||
_verifyRequired: function () {
|
|
||||||
var attrs = this.attributes;
|
|
||||||
for (var def in this.definition) {
|
|
||||||
if (this.definition[def].required && typeof attrs[def] === 'undefined') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
_initProperties: function () {
|
|
||||||
var self = this,
|
|
||||||
definition = this.definition = {},
|
|
||||||
val,
|
|
||||||
prop,
|
|
||||||
item,
|
|
||||||
type,
|
|
||||||
filler;
|
|
||||||
|
|
||||||
this.cid = _.uniqueId('model');
|
|
||||||
|
|
||||||
function addToDef(name, val, isSession) {
|
|
||||||
var def = definition[name] = {};
|
|
||||||
if (_.isString(val)) {
|
|
||||||
// grab our type if all we've got is a string
|
|
||||||
type = self._ensureValidType(val);
|
|
||||||
if (type) def.type = type;
|
|
||||||
} else {
|
|
||||||
type = self._ensureValidType(val[0] || val.type);
|
|
||||||
if (type) def.type = type;
|
|
||||||
if (val[1] || val.required) def.required = true;
|
|
||||||
// set default if defined
|
|
||||||
def.value = !_.isUndefined(val[2]) ? val[2] : val.default;
|
|
||||||
if (isSession) def.session = true;
|
|
||||||
if (val.setOnce) def.setOnce = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop through given properties
|
|
||||||
for (item in this.props) {
|
|
||||||
addToDef(item, this.props[item]);
|
|
||||||
}
|
|
||||||
// loop through session props
|
|
||||||
for (prop in this.session) {
|
|
||||||
addToDef(prop, this.session[prop], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// always add "id" as a definition or make sure it's 'setOnce'
|
|
||||||
if (definition.id) {
|
|
||||||
definition[this.idAttribute].setOnce = true;
|
|
||||||
} else {
|
|
||||||
addToDef(this.idAttribute, this.idDefinition);
|
|
||||||
}
|
|
||||||
|
|
||||||
// register derived properties as part of the definition
|
|
||||||
this._registerDerived();
|
|
||||||
this._createGettersSetters();
|
|
||||||
|
|
||||||
// freeze attributes used to define object
|
|
||||||
if (this.session) Object.freeze(this.session);
|
|
||||||
//if (this.derived) Object.freeze(this.derived);
|
|
||||||
if (this.props) Object.freeze(this.props);
|
|
||||||
},
|
|
||||||
|
|
||||||
// just makes friendlier errors when trying to define a new model
|
|
||||||
// only used when setting up original property definitions
|
|
||||||
_ensureValidType: function (type) {
|
|
||||||
return _.contains(['string', 'number', 'boolean', 'array', 'object', 'date'], type) ? type : undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
_validate: function () {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
_createGettersSetters: function () {
|
|
||||||
var item, def, desc, self = this;
|
|
||||||
|
|
||||||
// create getters/setters based on definitions
|
|
||||||
for (item in this.definition) {
|
|
||||||
def = this.definition[item];
|
|
||||||
desc = {};
|
|
||||||
// create our setter
|
|
||||||
desc.set = function (def, item) {
|
|
||||||
return function (val, options) {
|
|
||||||
self.set(item, val);
|
|
||||||
};
|
|
||||||
}(def, item);
|
|
||||||
// create our getter
|
|
||||||
desc.get = function (def, attributes) {
|
|
||||||
return function (val) {
|
|
||||||
if (typeof def.value !== 'undefined') {
|
|
||||||
if (def.type === 'date') {
|
|
||||||
return new Date(def.value);
|
|
||||||
}
|
|
||||||
return def.value;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}(def);
|
|
||||||
|
|
||||||
// define our property
|
|
||||||
this.define(item, desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.defineGetter('attributes', function () {
|
|
||||||
var res = {};
|
|
||||||
for (var item in this.definition) res[item] = this[item];
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.defineGetter('keys', function () {
|
|
||||||
return Object.keys(this.attributes);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.defineGetter('json', function () {
|
|
||||||
return JSON.stringify(this._getAttributes(false, true));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.defineGetter('derived', function () {
|
|
||||||
var res = {};
|
|
||||||
for (var item in this._derived) res[item] = this._derived[item].fn.apply(this);
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.defineGetter('toTemplate', function () {
|
|
||||||
return _.extend(this._getAttributes(true), this.derived);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_getAttributes: function (includeSession, raw) {
|
|
||||||
var res = {};
|
|
||||||
for (var item in this.definition) {
|
|
||||||
if (!includeSession) {
|
|
||||||
if (!this.definition[item].session) {
|
|
||||||
res[item] = (raw) ? this.definition[item].value : this[item];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res[item] = (raw) ? this.definition[item].value : this[item];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
|
|
||||||
// stores an object of arrays that specifies the derivedProperties
|
|
||||||
// that depend on each attribute
|
|
||||||
_registerDerived: function () {
|
|
||||||
var self = this, depList;
|
|
||||||
if (!this.derived) return;
|
|
||||||
this._derived = this.derived;
|
|
||||||
for (var key in this.derived) {
|
|
||||||
depList = this.derived[key].deps || [];
|
|
||||||
_.each(depList, function (dep) {
|
|
||||||
self._deps[dep] = _(self._deps[dep] || []).union([key]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// defined a top-level getter for derived keys
|
|
||||||
this.define(key, {
|
|
||||||
get: _.bind(function (key) {
|
|
||||||
// is this a derived property we should cache?
|
|
||||||
if (this._derived[key].cache) {
|
|
||||||
// do we have it?
|
|
||||||
if (this._cache.hasOwnProperty(key)) {
|
|
||||||
return this._cache[key];
|
|
||||||
} else {
|
|
||||||
return this._cache[key] = this._derived[key].fn.apply(this);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this._derived[key].fn.apply(this);
|
|
||||||
}
|
|
||||||
}, this, key),
|
|
||||||
set: _.bind(function (key) {
|
|
||||||
var deps = this._derived[key].deps,
|
|
||||||
msg = '"' + key + '" is a derived property, you can\'t set it directly.';
|
|
||||||
if (deps && deps.length) {
|
|
||||||
throw new TypeError(msg + ' It is dependent on "' + deps.join('" and "') + '".');
|
|
||||||
} else {
|
|
||||||
throw new TypeError(msg);
|
|
||||||
}
|
|
||||||
}, this, key)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set up inheritance for the model
|
|
||||||
Strict.Model.extend = extend;
|
|
||||||
|
|
||||||
// Overwrite Backbone.Model so that collections don't need to be modified in Backbone core
|
|
||||||
Backbone.Model = Strict.Model;
|
|
||||||
|
|
||||||
}).call(this);
|
|
@ -1,245 +0,0 @@
|
|||||||
var Backbone = require('backbone'),
|
|
||||||
_ = require('underscore'),
|
|
||||||
templates = require('templates');
|
|
||||||
|
|
||||||
|
|
||||||
// the base view we use to build all our other views
|
|
||||||
module.exports = Backbone.View.extend({
|
|
||||||
// ###handleBindings
|
|
||||||
// This makes it simple to bind model attributes to the view.
|
|
||||||
// To use it, add a `classBindings` and/or a `contentBindings` attribute
|
|
||||||
// to your view and call `this.handleBindings()` at the end of your view's
|
|
||||||
// `render` function. It's also used by `basicRender` which lets you do
|
|
||||||
// a complete attribute-bound views with just this:
|
|
||||||
//
|
|
||||||
// var ProfileView = BaseView.extend({
|
|
||||||
// template: 'profile',
|
|
||||||
// contentBindings: {
|
|
||||||
// 'name': '.name'
|
|
||||||
// },
|
|
||||||
// classBindings: {
|
|
||||||
// 'active': ''
|
|
||||||
// },
|
|
||||||
// render: function () {
|
|
||||||
// this.basicRender();
|
|
||||||
// return this;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
handleBindings: function () {
|
|
||||||
var self = this;
|
|
||||||
if (this.contentBindings) {
|
|
||||||
_.each(this.contentBindings, function (selector, key) {
|
|
||||||
var func = function () {
|
|
||||||
var el = (selector.length > 0) ? self.$(selector) : $(self.el);
|
|
||||||
el.html(self.model[key]);
|
|
||||||
};
|
|
||||||
self.listenTo(self.model, 'change:' + key, func);
|
|
||||||
func();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.imageBindings) {
|
|
||||||
_.each(this.imageBindings, function (selector, key) {
|
|
||||||
var func = function () {
|
|
||||||
var el = (selector.length > 0) ? self.$(selector) : $(self.el);
|
|
||||||
el.attr('src', self.model[key]);
|
|
||||||
};
|
|
||||||
self.listenTo(self.model, 'change:' + key, func);
|
|
||||||
func();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.hrefBindings) {
|
|
||||||
_.each(this.hrefBindings, function (selector, key) {
|
|
||||||
var func = function () {
|
|
||||||
var el = (selector.length > 0) ? self.$(selector) : $(self.el);
|
|
||||||
el.attr('href', self.model[key]);
|
|
||||||
};
|
|
||||||
self.listenTo(self.model, 'change:' + key, func);
|
|
||||||
func();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.classBindings) {
|
|
||||||
_.each(this.classBindings, function (selector, key) {
|
|
||||||
var func = function () {
|
|
||||||
var newValue = self.model[key],
|
|
||||||
prevHash = self.model.previous(),
|
|
||||||
prev = _.isFunction(prevHash) ? prevHash(key) : prevHash[key],
|
|
||||||
el = (selector.length > 0) ? self.$(selector) : $(self.el);
|
|
||||||
if (_.isBoolean(newValue)) {
|
|
||||||
if (newValue) {
|
|
||||||
el.addClass(key);
|
|
||||||
} else {
|
|
||||||
el.removeClass(key);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (prev) el.removeClass(prev);
|
|
||||||
el.addClass(newValue);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.listenTo(self.model, 'change:' + key, func);
|
|
||||||
func();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.inputBindings) {
|
|
||||||
_.each(this.inputBindings, function (selector, key) {
|
|
||||||
var func = function () {
|
|
||||||
var el = (selector.length > 0) ? self.$(selector) : $(self.el);
|
|
||||||
el.val(self.model[key]);
|
|
||||||
};
|
|
||||||
self.listenTo(self.model, 'change:' + key, func);
|
|
||||||
func();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
// ###desist
|
|
||||||
// This is method we used to remove/unbind/destroy the view.
|
|
||||||
// By default we fade it out this seemed like a reasonable default for realtime apps.
|
|
||||||
// So things to just magically disappear and to give some visual indication that
|
|
||||||
// it's going away. You can also pass an options hash `{quick: true}` to remove immediately.
|
|
||||||
desist: function (opts) {
|
|
||||||
opts || (opts = {});
|
|
||||||
_.defaults(opts, {
|
|
||||||
quick: false,
|
|
||||||
animate: true,
|
|
||||||
speed: 300,
|
|
||||||
animationProps: {
|
|
||||||
height: 0,
|
|
||||||
opacity: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
var el = $(this.el),
|
|
||||||
kill = _.bind(this.remove, this);
|
|
||||||
if (this.interval) {
|
|
||||||
clearInterval(this.interval);
|
|
||||||
delete this.interval;
|
|
||||||
}
|
|
||||||
if (opts.quick) {
|
|
||||||
kill();
|
|
||||||
} else if (opts.animate) {
|
|
||||||
el.animate(opts.animationProps, {
|
|
||||||
speed: opts.speed,
|
|
||||||
complete: kill
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setTimeout(kill, opts.speed);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// ###addReferences
|
|
||||||
// This is a shortcut for adding reference to specific elements within your view for
|
|
||||||
// access later. This is avoids excessive DOM queries and gives makes it easier to update
|
|
||||||
// your view if your template changes. You could argue whether this is worth doing or not,
|
|
||||||
// but I like it.
|
|
||||||
// In your `render` method. Use it like so:
|
|
||||||
//
|
|
||||||
// render: function () {
|
|
||||||
// this.basicRender();
|
|
||||||
// this.addReferences({
|
|
||||||
// pages: '#pages',
|
|
||||||
// chat: '#teamChat',
|
|
||||||
// nav: 'nav#views ul',
|
|
||||||
// me: '#me',
|
|
||||||
// cheatSheet: '#cheatSheet',
|
|
||||||
// omniBox: '#awesomeSauce'
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Then later you can access elements by reference like so: `this.$pages`, or `this.$chat`.
|
|
||||||
addReferences: function (hash) {
|
|
||||||
for (var item in hash) {
|
|
||||||
this['$' + item] = $(hash[item], this.el);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// ###basicRender
|
|
||||||
// All the usual stuff when I render a view. It assumes that the view has a `template` property
|
|
||||||
// that is the name of the ICanHaz template. You can also specify the template name by passing
|
|
||||||
// it an options hash like so: `{templateKey: 'profile'}`.
|
|
||||||
basicRender: function (opts) {
|
|
||||||
var newEl;
|
|
||||||
opts || (opts = {});
|
|
||||||
_.defaults(opts, {
|
|
||||||
templateFunc: (typeof this.template === 'string') ? templates[opts.templateKey] : this.template,
|
|
||||||
context: false
|
|
||||||
});
|
|
||||||
newEl = $(opts.templateFunc(opts.contex));
|
|
||||||
$(this.el).replaceWith(newEl);
|
|
||||||
this.setElement(newEl);
|
|
||||||
this.handleBindings();
|
|
||||||
this.delegateEvents();
|
|
||||||
},
|
|
||||||
|
|
||||||
// ###subViewRender
|
|
||||||
// This is handy for views within collections when you use `collectomatic`. Just like `basicRender` it assumes
|
|
||||||
// that the view either has a `template` property or that you pass it an options object with the name of the
|
|
||||||
// `templateKey` name of the ICanHaz template.
|
|
||||||
// Additionally, it handles appending or prepending the view to its parent container.
|
|
||||||
// It takes an options arg where you can optionally specify the `templateKey` and `placement` of the element.
|
|
||||||
// If your collections is stacked newest first, just use `{plaement: 'prepend'}`.
|
|
||||||
subViewRender: function (opts) {
|
|
||||||
opts || (opts = {});
|
|
||||||
_.defaults(opts, {
|
|
||||||
placement: 'append',
|
|
||||||
templateFunc: (typeof this.template === 'string') ? templates[opts.templateKey] : this.template
|
|
||||||
});
|
|
||||||
var data = _.isFunction(this.model.toTemplate) ? this.model.toTemplate() : this.model.toTemplate,
|
|
||||||
newEl = $(opts.templateFunc(opts.context))[0];
|
|
||||||
if (!this.el.parentNode) {
|
|
||||||
$(this.containerEl)[opts.placement](newEl);
|
|
||||||
} else {
|
|
||||||
$(this.el).replaceWith(newEl);
|
|
||||||
}
|
|
||||||
this.setElement(newEl);
|
|
||||||
this.handleBindings();
|
|
||||||
},
|
|
||||||
|
|
||||||
// ### bindomatic
|
|
||||||
// Shortcut for listening and triggering
|
|
||||||
bindomatic: function (object, events, handler, opts) {
|
|
||||||
var bound = _.bind(handler, this);
|
|
||||||
this.listenTo(object, events, bound);
|
|
||||||
if (opts && opts.trigger || opts === true) bound();
|
|
||||||
},
|
|
||||||
|
|
||||||
// ###collectomatic
|
|
||||||
// Shorthand for rendering collections and their invividual views.
|
|
||||||
// Just pass it the collection, and the view to use for the items in the
|
|
||||||
// collection. (anything in the `options` arg just gets passed through to
|
|
||||||
// view. Again, props to @natevw for this.
|
|
||||||
collectomatic: function (collection, ViewClass, options, desistOptions) {
|
|
||||||
var views = {},
|
|
||||||
self = this,
|
|
||||||
refreshResetHandler;
|
|
||||||
function addView(model, collection, opts) {
|
|
||||||
var matches = self.matchesFilters ? self.matchesFilters(model) : true;
|
|
||||||
if (matches) {
|
|
||||||
views[model.cid] = new ViewClass(_({model: model}).extend(options));
|
|
||||||
views[model.cid].parent = self;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.listenTo(collection, 'add', addView);
|
|
||||||
this.listenTo(collection, 'remove', function (model) {
|
|
||||||
if (views[model.cid]) {
|
|
||||||
views[model.cid].desist(desistOptions);
|
|
||||||
delete views[model.cid];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.listenTo(collection, 'move', function () {
|
|
||||||
_(views).each(function (view) {
|
|
||||||
view.desist({quick: true});
|
|
||||||
});
|
|
||||||
views = {};
|
|
||||||
collection.each(addView);
|
|
||||||
});
|
|
||||||
refreshResetHandler = function (opts) {
|
|
||||||
_(views).each(function (view) {
|
|
||||||
view.desist({quick: true});
|
|
||||||
});
|
|
||||||
views = {};
|
|
||||||
collection.each(addView);
|
|
||||||
};
|
|
||||||
this.listenTo(collection, 'refresh reset sort', refreshResetHandler);
|
|
||||||
refreshResetHandler();
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,90 +0,0 @@
|
|||||||
(function () {
|
|
||||||
var root = this, exports = {};
|
|
||||||
|
|
||||||
// The jade runtime:
|
|
||||||
var jade=function(exports){Array.isArray||(Array.isArray=function(arr){return"[object Array]"==Object.prototype.toString.call(arr)}),Object.keys||(Object.keys=function(obj){var arr=[];for(var key in obj)obj.hasOwnProperty(key)&&arr.push(key);return arr}),exports.merge=function merge(a,b){var ac=a["class"],bc=b["class"];if(ac||bc)ac=ac||[],bc=bc||[],Array.isArray(ac)||(ac=[ac]),Array.isArray(bc)||(bc=[bc]),ac=ac.filter(nulls),bc=bc.filter(nulls),a["class"]=ac.concat(bc).join(" ");for(var key in b)key!="class"&&(a[key]=b[key]);return a};function nulls(val){return val!=null}return exports.attrs=function attrs(obj,escaped){var buf=[],terse=obj.terse;delete obj.terse;var keys=Object.keys(obj),len=keys.length;if(len){buf.push("");for(var i=0;i<len;++i){var key=keys[i],val=obj[key];"boolean"==typeof val||null==val?val&&(terse?buf.push(key):buf.push(key+'="'+key+'"')):0==key.indexOf("data")&&"string"!=typeof val?buf.push(key+"='"+JSON.stringify(val)+"'"):"class"==key&&Array.isArray(val)?buf.push(key+'="'+exports.escape(val.join(" "))+'"'):escaped&&escaped[key]?buf.push(key+'="'+exports.escape(val)+'"'):buf.push(key+'="'+val+'"')}}return buf.join(" ")},exports.escape=function escape(html){return String(html).replace(/&(?!(\w+|\#\d+);)/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},exports.rethrow=function rethrow(err,filename,lineno){if(!filename)throw err;var context=3,str=require("fs").readFileSync(filename,"utf8"),lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");throw err.path=filename,err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message,err},exports}({});
|
|
||||||
|
|
||||||
|
|
||||||
// create our folder objects
|
|
||||||
exports.includes = {};
|
|
||||||
exports.pages = {};
|
|
||||||
|
|
||||||
// contactListItem.jade compiled template
|
|
||||||
exports.includes.contactListItem = function anonymous(locals) {
|
|
||||||
var buf = [];
|
|
||||||
with (locals || {}) {
|
|
||||||
buf.push('<li class="contact"><img' + jade.attrs({
|
|
||||||
src: contact.avatar,
|
|
||||||
"class": "avatar"
|
|
||||||
}, {
|
|
||||||
src: true
|
|
||||||
}) + '/><div class="name">' + jade.escape(null == (jade.interp = contact.displayName) ? "" : jade.interp) + '</div><div class="status">' + jade.escape(null == (jade.interp = contact.status) ? "" : jade.interp) + "</div></li>");
|
|
||||||
}
|
|
||||||
return buf.join("");
|
|
||||||
};
|
|
||||||
|
|
||||||
// contactListItemResource.jade compiled template
|
|
||||||
exports.includes.contactListItemResource = function anonymous(locals) {
|
|
||||||
var buf = [];
|
|
||||||
with (locals || {}) {
|
|
||||||
buf.push('<li><p class="jid">' + jade.escape(null == (jade.interp = resource.jid) ? "" : jade.interp) + '</p><p class="status">' + jade.escape(null == (jade.interp = resource.status) ? "" : jade.interp) + "</p></li>");
|
|
||||||
}
|
|
||||||
return buf.join("");
|
|
||||||
};
|
|
||||||
|
|
||||||
// message.jade compiled template
|
|
||||||
exports.includes.message = function anonymous(locals) {
|
|
||||||
var buf = [];
|
|
||||||
with (locals || {}) {
|
|
||||||
buf.push('<li><div class="message"><span class="timestamp">' + jade.escape(null == (jade.interp = message.created) ? "" : jade.interp) + '</span><p class="body">' + jade.escape(null == (jade.interp = message.body) ? "" : jade.interp) + "</p></div></li>");
|
|
||||||
}
|
|
||||||
return buf.join("");
|
|
||||||
};
|
|
||||||
|
|
||||||
// layout.jade compiled template
|
|
||||||
exports.layout = function anonymous(locals) {
|
|
||||||
var buf = [];
|
|
||||||
with (locals || {}) {
|
|
||||||
buf.push('<div class="wrap"><header></header><div id="me"><img class="avatar"/><p class="status"></p></div><section id="pages"></section><footer></footer></div>');
|
|
||||||
}
|
|
||||||
return buf.join("");
|
|
||||||
};
|
|
||||||
|
|
||||||
// chat.jade compiled template
|
|
||||||
exports.pages.chat = function anonymous(locals) {
|
|
||||||
var buf = [];
|
|
||||||
with (locals || {}) {
|
|
||||||
buf.push('<section class="page chat"><nav id="contactList"></nav><header class="contactInfo"><img width="30" height="30" class="avatar"/><h1 class="name"></h1></header><ul id="conversation"></ul></section>');
|
|
||||||
}
|
|
||||||
return buf.join("");
|
|
||||||
};
|
|
||||||
|
|
||||||
// main.jade compiled template
|
|
||||||
exports.pages.main = function anonymous(locals) {
|
|
||||||
var buf = [];
|
|
||||||
with (locals || {}) {
|
|
||||||
buf.push('<section class="page main"><nav id="contactList"></nav><div id="log"><h2>Event Log</h2></div></section>');
|
|
||||||
}
|
|
||||||
return buf.join("");
|
|
||||||
};
|
|
||||||
|
|
||||||
// wrapper.jade compiled template
|
|
||||||
exports.pages.wrapper = function anonymous(locals) {
|
|
||||||
var buf = [];
|
|
||||||
with (locals || {}) {
|
|
||||||
buf.push('<div class="page"></div>');
|
|
||||||
}
|
|
||||||
return buf.join("");
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// attach to window or export with commonJS
|
|
||||||
if (typeof module !== "undefined") {
|
|
||||||
module.exports = exports;
|
|
||||||
} else if (typeof define === "function" && define.amd) {
|
|
||||||
define(exports);
|
|
||||||
} else {
|
|
||||||
root.templatizer = exports;
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
File diff suppressed because it is too large
Load Diff
@ -1,135 +0,0 @@
|
|||||||
/*
|
|
||||||
WildEmitter.js is a slim little event emitter by @henrikjoreteg largely based
|
|
||||||
on @visionmedia's Emitter from UI Kit.
|
|
||||||
|
|
||||||
Why? I wanted it standalone.
|
|
||||||
|
|
||||||
I also wanted support for wildcard emitters like this:
|
|
||||||
|
|
||||||
emitter.on('*', function (eventName, other, event, payloads) {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
emitter.on('somenamespace*', function (eventName, payloads) {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
Please note that callbacks triggered by wildcard registered events also get
|
|
||||||
the event name as the first argument.
|
|
||||||
*/
|
|
||||||
module.exports = WildEmitter;
|
|
||||||
|
|
||||||
function WildEmitter() {
|
|
||||||
this.callbacks = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen on the given `event` with `fn`. Store a group name if present.
|
|
||||||
WildEmitter.prototype.on = function (event, groupName, fn) {
|
|
||||||
var hasGroup = (arguments.length === 3),
|
|
||||||
group = hasGroup ? arguments[1] : undefined,
|
|
||||||
func = hasGroup ? arguments[2] : arguments[1];
|
|
||||||
func._groupName = group;
|
|
||||||
(this.callbacks[event] = this.callbacks[event] || []).push(func);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Adds an `event` listener that will be invoked a single
|
|
||||||
// time then automatically removed.
|
|
||||||
WildEmitter.prototype.once = function (event, groupName, fn) {
|
|
||||||
var self = this,
|
|
||||||
hasGroup = (arguments.length === 3),
|
|
||||||
group = hasGroup ? arguments[1] : undefined,
|
|
||||||
func = hasGroup ? arguments[2] : arguments[1];
|
|
||||||
function on() {
|
|
||||||
self.off(event, on);
|
|
||||||
func.apply(this, arguments);
|
|
||||||
}
|
|
||||||
this.on(event, group, on);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Unbinds an entire group
|
|
||||||
WildEmitter.prototype.releaseGroup = function (groupName) {
|
|
||||||
var item, i, len, handlers;
|
|
||||||
for (item in this.callbacks) {
|
|
||||||
handlers = this.callbacks[item];
|
|
||||||
for (i = 0, len = handlers.length; i < len; i++) {
|
|
||||||
if (handlers[i]._groupName === groupName) {
|
|
||||||
//console.log('removing');
|
|
||||||
// remove it and shorten the array we're looping through
|
|
||||||
handlers.splice(i, 1);
|
|
||||||
i--;
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Remove the given callback for `event` or all
|
|
||||||
// registered callbacks.
|
|
||||||
WildEmitter.prototype.off = function (event, fn) {
|
|
||||||
var callbacks = this.callbacks[event],
|
|
||||||
i;
|
|
||||||
|
|
||||||
if (!callbacks) return this;
|
|
||||||
|
|
||||||
// remove all handlers
|
|
||||||
if (arguments.length === 1) {
|
|
||||||
delete this.callbacks[event];
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove specific handler
|
|
||||||
i = callbacks.indexOf(fn);
|
|
||||||
callbacks.splice(i, 1);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Emit `event` with the given args.
|
|
||||||
// also calls any `*` handlers
|
|
||||||
WildEmitter.prototype.emit = function (event) {
|
|
||||||
var args = [].slice.call(arguments, 1),
|
|
||||||
callbacks = this.callbacks[event],
|
|
||||||
specialCallbacks = this.getWildcardCallbacks(event),
|
|
||||||
i,
|
|
||||||
len,
|
|
||||||
item;
|
|
||||||
|
|
||||||
if (callbacks) {
|
|
||||||
for (i = 0, len = callbacks.length; i < len; ++i) {
|
|
||||||
if (callbacks[i]) {
|
|
||||||
callbacks[i].apply(this, args);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (specialCallbacks) {
|
|
||||||
for (i = 0, len = specialCallbacks.length; i < len; ++i) {
|
|
||||||
if (specialCallbacks[i]) {
|
|
||||||
specialCallbacks[i].apply(this, [event].concat(args));
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper for for finding special wildcard event handlers that match the event
|
|
||||||
WildEmitter.prototype.getWildcardCallbacks = function (eventName) {
|
|
||||||
var item,
|
|
||||||
split,
|
|
||||||
result = [];
|
|
||||||
|
|
||||||
for (item in this.callbacks) {
|
|
||||||
split = item.split('*');
|
|
||||||
if (item === '*' || (split.length === 2 && eventName.slice(0, split[1].length) === split[1])) {
|
|
||||||
result = result.concat(this.callbacks[item]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
50
clientapp/pages/base.js
Normal file
50
clientapp/pages/base.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*global $, app*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var _ = require('underscore');
|
||||||
|
var StrictView = require('strictview');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = StrictView.extend({
|
||||||
|
show: function (animation) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
$('body').scrollTop(0);
|
||||||
|
|
||||||
|
if (this.detached) {
|
||||||
|
this.$('#pages').append(this.el);
|
||||||
|
this.detached = false;
|
||||||
|
} else {
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$el.addClass('active');
|
||||||
|
|
||||||
|
app.currentPage = this;
|
||||||
|
|
||||||
|
document.title = function () {
|
||||||
|
var title = _.result(self, 'title');
|
||||||
|
return title ? title + '- OTalk' : 'OTalk';
|
||||||
|
}();
|
||||||
|
|
||||||
|
this.trigger('pageloaded');
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
hide: function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.$el.removeClass('active');
|
||||||
|
|
||||||
|
this.trigger('pageunloaded');
|
||||||
|
|
||||||
|
if (this.cache) {
|
||||||
|
this.$el.detach();
|
||||||
|
this.detached = true;
|
||||||
|
} else {
|
||||||
|
this.animateRemove();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
93
clientapp/pages/chat.js
Normal file
93
clientapp/pages/chat.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/*global $, app, me, client*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var BasePage = require('./base');
|
||||||
|
var templates = require('../templates');
|
||||||
|
var ContactListItem = require('../views/contactListItem');
|
||||||
|
var ContactListItemResource = require('../views/contactListItemResource');
|
||||||
|
var Message = require('../views/message');
|
||||||
|
var MessageModel = require('../models/message');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = BasePage.extend({
|
||||||
|
template: templates.pages.chat,
|
||||||
|
initialize: function (spec) {
|
||||||
|
this.editMode = false;
|
||||||
|
this.model.fetchHistory();
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
events: {
|
||||||
|
'submit #chatInput': 'killForm',
|
||||||
|
'keydown #chatBuffer': 'handleKeyDown'
|
||||||
|
},
|
||||||
|
srcBindings: {
|
||||||
|
avatar: 'header .avatar'
|
||||||
|
},
|
||||||
|
textBindings: {
|
||||||
|
name: 'header .name'
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
this.renderAndBind();
|
||||||
|
this.$chatBuffer = this.$('#chatBuffer');
|
||||||
|
this.renderCollection(this.model.messages, Message, this.$('#conversation'));
|
||||||
|
this.registerBindings();
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
killForm: function (e) {
|
||||||
|
console.log('kill form', e);
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
handleKeyDown: function (e) {
|
||||||
|
if (e.which === 13 && !e.shiftKey) {
|
||||||
|
this.sendChat();
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.which === 38 && this.$chatBuffer.val() === '' && this.model.lastSentMessage) {
|
||||||
|
this.editMode = true;
|
||||||
|
this.$chatBuffer.addClass('editing');
|
||||||
|
this.$chatBuffer.val(this.model.lastSentMessage.body);
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.which === 40 && this.editMode) {
|
||||||
|
this.editMode = false;
|
||||||
|
this.$chatBuffer.removeClass('editing');
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sendChat: function () {
|
||||||
|
var message;
|
||||||
|
var val = this.$chatBuffer.val();
|
||||||
|
|
||||||
|
if (val) {
|
||||||
|
message = {
|
||||||
|
to: this.model.lockedJID,
|
||||||
|
type: 'chat',
|
||||||
|
body: val,
|
||||||
|
chatState: 'active'
|
||||||
|
};
|
||||||
|
console.log(this.editMode);
|
||||||
|
if (this.editMode) {
|
||||||
|
message.replace = this.model.lastSentMessage.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = client.sendMessage(message);
|
||||||
|
message.id = id;
|
||||||
|
message.from = me.jid;
|
||||||
|
|
||||||
|
console.log(message);
|
||||||
|
console.log(this.editMode);
|
||||||
|
if (this.editMode) {
|
||||||
|
this.model.lastSentMessage.correct(message);
|
||||||
|
} else {
|
||||||
|
var msgModel = new MessageModel(message);
|
||||||
|
this.model.messages.add(msgModel);
|
||||||
|
this.model.lastSentMessage = msgModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.editMode = false;
|
||||||
|
this.$chatBuffer.removeClass('editing');
|
||||||
|
this.$chatBuffer.val('');
|
||||||
|
}
|
||||||
|
});
|
18
clientapp/pages/main.js
Normal file
18
clientapp/pages/main.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/*global app*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var BasePage = require('./base');
|
||||||
|
var templates = require('../templates');
|
||||||
|
var ContactListItem = require('../views/contactListItem');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = BasePage.extend({
|
||||||
|
template: templates.pages.main,
|
||||||
|
initialize: function (spec) {
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
this.renderAndBind();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
@ -1,5 +1,7 @@
|
|||||||
var BasePage = require('pages/base'),
|
"use strict";
|
||||||
templates = require('templates');
|
|
||||||
|
var BasePage = require('pages/base');
|
||||||
|
var templates = require('templates');
|
||||||
|
|
||||||
|
|
||||||
module.exports = BasePage.extend({
|
module.exports = BasePage.extend({
|
@ -1,14 +1,9 @@
|
|||||||
/*global app*/
|
/*global app, me*/
|
||||||
var Backbone = require('backbone'),
|
"use strict";
|
||||||
staticPage = function (url) {
|
|
||||||
return function () {
|
|
||||||
var View = require('pages/wrapper');
|
|
||||||
app.renderPage(new View({
|
|
||||||
url: url
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
var Backbone = require('backbone');
|
||||||
|
var MainPage = require('./pages/main');
|
||||||
|
var ChatPage = require('./pages/chat');
|
||||||
|
|
||||||
|
|
||||||
module.exports = Backbone.Router.extend({
|
module.exports = Backbone.Router.extend({
|
||||||
@ -18,16 +13,14 @@ module.exports = Backbone.Router.extend({
|
|||||||
},
|
},
|
||||||
// ------- ROUTE HANDLERS ---------
|
// ------- ROUTE HANDLERS ---------
|
||||||
main: function () {
|
main: function () {
|
||||||
var View = require('pages/main');
|
app.renderPage(new MainPage({
|
||||||
app.renderPage(new View({
|
|
||||||
model: me
|
model: me
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
chat: function (jid) {
|
chat: function (jid) {
|
||||||
var View = require('pages/chat');
|
|
||||||
var contact = me.contacts.get(jid);
|
var contact = me.contacts.get(jid);
|
||||||
if (contact) {
|
if (contact) {
|
||||||
app.renderPage(new View({
|
app.renderPage(new ChatPage({
|
||||||
model: contact
|
model: contact
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
23
clientapp/storage/archive.js
Normal file
23
clientapp/storage/archive.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function ArchiveStorage(storage) {
|
||||||
|
this.storage = storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArchiveStorage.prototype = {
|
||||||
|
constructor: {
|
||||||
|
value: ArchiveStorage
|
||||||
|
},
|
||||||
|
setup: function (db) {
|
||||||
|
db.createObjectStore('archive', {
|
||||||
|
keyPath: 'id'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
transaction: function (mode) {
|
||||||
|
var trans = this.storage.db.transaction('archive', mode);
|
||||||
|
return trans.objectStore('archive');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = ArchiveStorage;
|
59
clientapp/storage/avatars.js
Normal file
59
clientapp/storage/avatars.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// SCHEMA
|
||||||
|
// id: 'sha1 hash',
|
||||||
|
// dataURI: '...'
|
||||||
|
|
||||||
|
|
||||||
|
function AvatarStorage(storage) {
|
||||||
|
this.storage = storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
AvatarStorage.prototype = {
|
||||||
|
constructor: {
|
||||||
|
value: AvatarStorage
|
||||||
|
},
|
||||||
|
setup: function (db) {
|
||||||
|
db.createObjectStore('avatars', {
|
||||||
|
keyPath: 'id'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
transaction: function (mode) {
|
||||||
|
var trans = this.storage.db.transaction('avatars', mode);
|
||||||
|
return trans.objectStore('avatars');
|
||||||
|
},
|
||||||
|
add: function (avatar, cb) {
|
||||||
|
cb = cb || function () {};
|
||||||
|
var request = this.transaction('readwrite').put(avatar);
|
||||||
|
request.onsuccess = function () {
|
||||||
|
cb(false, avatar);
|
||||||
|
};
|
||||||
|
request.onerror = cb;
|
||||||
|
},
|
||||||
|
get: function (id, cb) {
|
||||||
|
cb = cb || function () {};
|
||||||
|
if (!id) {
|
||||||
|
return cb('not-found');
|
||||||
|
}
|
||||||
|
var request = this.transaction('readonly').get(id);
|
||||||
|
request.onsuccess = function (e) {
|
||||||
|
var res = request.result;
|
||||||
|
if (res === undefined) {
|
||||||
|
return cb('not-found');
|
||||||
|
}
|
||||||
|
cb(false, request.result);
|
||||||
|
};
|
||||||
|
request.onerror = cb;
|
||||||
|
},
|
||||||
|
remove: function (id, cb) {
|
||||||
|
cb = cb || function () {};
|
||||||
|
var request = this.transaction('readwrite')['delete'](id);
|
||||||
|
request.onsuccess = function (e) {
|
||||||
|
cb(false, request.result);
|
||||||
|
};
|
||||||
|
request.onerror = cb;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = AvatarStorage;
|
45
clientapp/storage/disco.js
Normal file
45
clientapp/storage/disco.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function DiscoStorage(storage) {
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscoStorage.prototype = {
|
||||||
|
constructor: {
|
||||||
|
value: DiscoStorage
|
||||||
|
},
|
||||||
|
setup: function (db) {
|
||||||
|
db.createObjectStore('disco', {
|
||||||
|
keyPath: 'ver'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
transaction: function (mode) {
|
||||||
|
var trans = this.storage.db.transaction('disco', mode);
|
||||||
|
return trans.objectStore('disco');
|
||||||
|
},
|
||||||
|
add: function (ver, disco, cb) {
|
||||||
|
cb = cb || function () {};
|
||||||
|
var data = {
|
||||||
|
ver: ver,
|
||||||
|
disco: disco
|
||||||
|
};
|
||||||
|
|
||||||
|
},
|
||||||
|
get: function (ver, cb) {
|
||||||
|
cb = cb || function () {};
|
||||||
|
if (!ver) {
|
||||||
|
return cb('not-found');
|
||||||
|
}
|
||||||
|
var request = this.transaction('readonly').get(ver);
|
||||||
|
request.onsuccess = function (e) {
|
||||||
|
var res = request.result;
|
||||||
|
if (res === undefined) {
|
||||||
|
return cb('not-found');
|
||||||
|
}
|
||||||
|
cb(false, res.disco);
|
||||||
|
};
|
||||||
|
request.onerror = cb;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = DiscoStorage;
|
45
clientapp/storage/index.js
Normal file
45
clientapp/storage/index.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/*global indexedDB*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var AvatarStorage = require('./avatars');
|
||||||
|
var RosterStorage = require('./roster');
|
||||||
|
var DiscoStorage = require('./disco');
|
||||||
|
var ArchiveStorage = require('./archive');
|
||||||
|
|
||||||
|
|
||||||
|
function Storage() {
|
||||||
|
this.db = null;
|
||||||
|
this.init = [];
|
||||||
|
|
||||||
|
this.avatars = new AvatarStorage(this);
|
||||||
|
this.roster = new RosterStorage(this);
|
||||||
|
this.disco = new DiscoStorage(this);
|
||||||
|
this.archive = new ArchiveStorage(this);
|
||||||
|
}
|
||||||
|
Storage.prototype = {
|
||||||
|
constructor: {
|
||||||
|
value: Storage
|
||||||
|
},
|
||||||
|
version: 1,
|
||||||
|
open: function (cb) {
|
||||||
|
cb = cb || function () {};
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var request = indexedDB.open('datastorage', this.version);
|
||||||
|
request.onsuccess = function (e) {
|
||||||
|
self.db = e.target.result;
|
||||||
|
cb(false, self.db);
|
||||||
|
};
|
||||||
|
request.onupgradeneeded = function (e) {
|
||||||
|
var db = e.target.result;
|
||||||
|
self.avatars.setup(db);
|
||||||
|
self.roster.setup(db);
|
||||||
|
self.disco.setup(db);
|
||||||
|
self.archive.setup(db);
|
||||||
|
};
|
||||||
|
request.onerror = cb;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = Storage;
|
78
clientapp/storage/roster.js
Normal file
78
clientapp/storage/roster.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// SCHEMA
|
||||||
|
// jid: string
|
||||||
|
// name: string
|
||||||
|
// subscription: string
|
||||||
|
// groups: array
|
||||||
|
// rosterID: string
|
||||||
|
|
||||||
|
|
||||||
|
function RosterStorage(storage) {
|
||||||
|
this.storage = storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
RosterStorage.prototype = {
|
||||||
|
constructor: {
|
||||||
|
value: RosterStorage
|
||||||
|
},
|
||||||
|
setup: function (db) {
|
||||||
|
db.createObjectStore('roster', {
|
||||||
|
keyPath: 'jid'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
transaction: function (mode) {
|
||||||
|
var trans = this.storage.db.transaction('roster', mode);
|
||||||
|
return trans.objectStore('roster');
|
||||||
|
},
|
||||||
|
add: function (contact, cb) {
|
||||||
|
cb = cb || function () {};
|
||||||
|
var request = this.transaction('readwrite').put(contact);
|
||||||
|
request.onsuccess = function () {
|
||||||
|
cb(false, contact);
|
||||||
|
};
|
||||||
|
request.onerror = cb;
|
||||||
|
},
|
||||||
|
get: function (id, cb) {
|
||||||
|
cb = cb || function () {};
|
||||||
|
if (!id) {
|
||||||
|
return cb('not-found');
|
||||||
|
}
|
||||||
|
var request = this.transaction('readonly').get(id);
|
||||||
|
request.onsuccess = function (e) {
|
||||||
|
var res = request.result;
|
||||||
|
if (res === undefined) {
|
||||||
|
return cb('not-found');
|
||||||
|
}
|
||||||
|
cb(false, request.result);
|
||||||
|
};
|
||||||
|
request.onerror = cb;
|
||||||
|
},
|
||||||
|
getAll: function (cb) {
|
||||||
|
cb = cb || function () {};
|
||||||
|
var results = [];
|
||||||
|
|
||||||
|
var request = this.transaction('readonly').openCursor();
|
||||||
|
request.onsuccess = function (e) {
|
||||||
|
var cursor = e.target.result;
|
||||||
|
if (cursor) {
|
||||||
|
results.push(cursor.value);
|
||||||
|
cursor.continue();
|
||||||
|
} else {
|
||||||
|
cb(false, results);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.onerror = cb;
|
||||||
|
},
|
||||||
|
remove: function (id, cb) {
|
||||||
|
cb = cb || function () {};
|
||||||
|
var request = this.transaction('readwrite')['delete'](id);
|
||||||
|
request.onsuccess = function (e) {
|
||||||
|
cb(false, request.result);
|
||||||
|
};
|
||||||
|
request.onerror = cb;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = RosterStorage;
|
98
clientapp/templates.js
Normal file
98
clientapp/templates.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
(function () {
|
||||||
|
var root = this, exports = {};
|
||||||
|
|
||||||
|
// The jade runtime:
|
||||||
|
var jade = exports.jade=function(exports){Array.isArray||(Array.isArray=function(arr){return"[object Array]"==Object.prototype.toString.call(arr)}),Object.keys||(Object.keys=function(obj){var arr=[];for(var key in obj)obj.hasOwnProperty(key)&&arr.push(key);return arr}),exports.merge=function merge(a,b){var ac=a["class"],bc=b["class"];if(ac||bc)ac=ac||[],bc=bc||[],Array.isArray(ac)||(ac=[ac]),Array.isArray(bc)||(bc=[bc]),ac=ac.filter(nulls),bc=bc.filter(nulls),a["class"]=ac.concat(bc).join(" ");for(var key in b)key!="class"&&(a[key]=b[key]);return a};function nulls(val){return val!=null}return exports.attrs=function attrs(obj,escaped){var buf=[],terse=obj.terse;delete obj.terse;var keys=Object.keys(obj),len=keys.length;if(len){buf.push("");for(var i=0;i<len;++i){var key=keys[i],val=obj[key];"boolean"==typeof val||null==val?val&&(terse?buf.push(key):buf.push(key+'="'+key+'"')):0==key.indexOf("data")&&"string"!=typeof val?buf.push(key+"='"+JSON.stringify(val)+"'"):"class"==key&&Array.isArray(val)?buf.push(key+'="'+exports.escape(val.join(" "))+'"'):escaped&&escaped[key]?buf.push(key+'="'+exports.escape(val)+'"'):buf.push(key+'="'+val+'"')}}return buf.join(" ")},exports.escape=function escape(html){return String(html).replace(/&(?!(\w+|\#\d+);)/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},exports.rethrow=function rethrow(err,filename,lineno){if(!filename)throw err;var context=3,str=require("fs").readFileSync(filename,"utf8"),lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");throw err.path=filename,err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message,err},exports}({});
|
||||||
|
|
||||||
|
// create our folder objects
|
||||||
|
exports.includes = {};
|
||||||
|
exports.pages = {};
|
||||||
|
|
||||||
|
// body.jade compiled template
|
||||||
|
exports.body = function anonymous(locals) {
|
||||||
|
var buf = [];
|
||||||
|
with (locals || {}) {
|
||||||
|
buf.push('<body><div class="wrap"><header id="me"><img class="avatar"/><p class="status"></p></header><nav id="contactList"></nav><section id="pages"></section><footer></footer></div></body>');
|
||||||
|
}
|
||||||
|
return buf.join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
// head.jade compiled template
|
||||||
|
exports.head = function anonymous(locals) {
|
||||||
|
var buf = [];
|
||||||
|
with (locals || {}) {
|
||||||
|
buf.push('<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0"/><meta name="apple-mobile-web-app-capable" content="yes"/>');
|
||||||
|
}
|
||||||
|
return buf.join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
// contactListItem.jade compiled template
|
||||||
|
exports.includes.contactListItem = function anonymous(locals) {
|
||||||
|
var buf = [];
|
||||||
|
with (locals || {}) {
|
||||||
|
buf.push('<li class="contact"><img' + jade.attrs({
|
||||||
|
src: contact.avatar,
|
||||||
|
"class": "avatar"
|
||||||
|
}, {
|
||||||
|
src: true
|
||||||
|
}) + '/><div class="name">' + jade.escape(null == (jade.interp = contact.displayName) ? "" : jade.interp) + '</div><div class="status">' + jade.escape(null == (jade.interp = contact.status) ? "" : jade.interp) + "</div></li>");
|
||||||
|
}
|
||||||
|
return buf.join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
// contactListItemResource.jade compiled template
|
||||||
|
exports.includes.contactListItemResource = function anonymous(locals) {
|
||||||
|
var buf = [];
|
||||||
|
with (locals || {}) {
|
||||||
|
buf.push('<li><p class="jid">' + jade.escape(null == (jade.interp = resource.jid) ? "" : jade.interp) + '</p><p class="status">' + jade.escape(null == (jade.interp = resource.status) ? "" : jade.interp) + "</p></li>");
|
||||||
|
}
|
||||||
|
return buf.join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
// message.jade compiled template
|
||||||
|
exports.includes.message = function anonymous(locals) {
|
||||||
|
var buf = [];
|
||||||
|
with (locals || {}) {
|
||||||
|
buf.push('<li><div class="message"><span class="timestamp">' + jade.escape(null == (jade.interp = message.created) ? "" : jade.interp) + '</span><p class="body">' + jade.escape(null == (jade.interp = message.body) ? "" : jade.interp) + "</p></div></li>");
|
||||||
|
}
|
||||||
|
return buf.join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
// chat.jade compiled template
|
||||||
|
exports.pages.chat = function anonymous(locals) {
|
||||||
|
var buf = [];
|
||||||
|
with (locals || {}) {
|
||||||
|
buf.push('<section class="page chat"><header class="contactInfo"><img width="30" height="30" class="avatar"/><h1 class="name"></h1></header><ul id="conversation"></ul><div id="chatInput"><form><textarea id="chatBuffer" name="chatInput" type="text" placeholder="Send a message..." autocomplete="off"></textarea></form></div></section>');
|
||||||
|
}
|
||||||
|
return buf.join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
// main.jade compiled template
|
||||||
|
exports.pages.main = function anonymous(locals) {
|
||||||
|
var buf = [];
|
||||||
|
with (locals || {}) {
|
||||||
|
buf.push('<section class="page main"><div id="log"><h2>Event Log</h2></div></section>');
|
||||||
|
}
|
||||||
|
return buf.join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
// wrapper.jade compiled template
|
||||||
|
exports.pages.wrapper = function anonymous(locals) {
|
||||||
|
var buf = [];
|
||||||
|
with (locals || {}) {
|
||||||
|
buf.push('<div class="page"></div>');
|
||||||
|
}
|
||||||
|
return buf.join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// attach to window or export with commonJS
|
||||||
|
if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
|
||||||
|
module.exports = exports;
|
||||||
|
} else if (typeof define === "function" && define.amd) {
|
||||||
|
define(exports);
|
||||||
|
} else {
|
||||||
|
root.templatizer = exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
8
clientapp/templates/body.jade
Normal file
8
clientapp/templates/body.jade
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
body
|
||||||
|
.wrap
|
||||||
|
header#me
|
||||||
|
img.avatar
|
||||||
|
p.status
|
||||||
|
nav#contactList
|
||||||
|
section#pages
|
||||||
|
footer
|
2
clientapp/templates/head.jade
Normal file
2
clientapp/templates/head.jade
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
meta(name="viewport", content="width=device-width,initial-scale=1.0,maximum-scale=1.0")
|
||||||
|
meta(name="apple-mobile-web-app-capable", content="yes")
|
@ -1,7 +0,0 @@
|
|||||||
.wrap
|
|
||||||
header
|
|
||||||
#me
|
|
||||||
img.avatar
|
|
||||||
p.status
|
|
||||||
section#pages
|
|
||||||
footer
|
|
@ -1,6 +1,8 @@
|
|||||||
section.page.chat
|
section.page.chat
|
||||||
nav#contactList
|
|
||||||
header.contactInfo
|
header.contactInfo
|
||||||
img.avatar(width=30, height=30)
|
img.avatar(width=30, height=30)
|
||||||
h1.name
|
h1.name
|
||||||
ul#conversation
|
ul#conversation
|
||||||
|
div#chatInput
|
||||||
|
form
|
||||||
|
textarea#chatBuffer(name='chatInput', type='text', placeholder='Send a message...', autocomplete='off')
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
section.page.main
|
section.page.main
|
||||||
nav#contactList
|
|
||||||
#log
|
#log
|
||||||
h2 Event Log
|
h2 Event Log
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
/*global app, $*/
|
/*global $, app, me*/
|
||||||
var StrictView = require('strictview');
|
"use strict";
|
||||||
var templates = require('templates');
|
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
var ContactListItemResource = require('views/contactListItemResource');
|
var StrictView = require('strictview');
|
||||||
|
var templates = require('../templates');
|
||||||
|
|
||||||
|
|
||||||
module.exports = StrictView.extend({
|
module.exports = StrictView.extend({
|
||||||
@ -12,29 +13,25 @@ module.exports = StrictView.extend({
|
|||||||
subscription: '',
|
subscription: '',
|
||||||
chatState: ''
|
chatState: ''
|
||||||
},
|
},
|
||||||
contentBindings: {
|
textBindings: {
|
||||||
displayName: '.name',
|
displayName: '.name',
|
||||||
status: '.status'
|
status: '.status'
|
||||||
},
|
},
|
||||||
imageBindings: {
|
srcBindings: {
|
||||||
avatar: '.avatar img'
|
avatar: '.avatar'
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
'click': 'goChat'
|
'click': 'goChat'
|
||||||
},
|
},
|
||||||
initialize: function (opts) {
|
initialize: function (opts) {
|
||||||
this.containerEl = opts.containerEl;
|
|
||||||
this.render();
|
this.render();
|
||||||
},
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
this.subViewRender({context: {contact: this.model}});
|
this.renderAndBind({contact: this.model});
|
||||||
//this.collectomatic(this.model.resources, ContactListItemResource, {
|
|
||||||
// containerEl: this.$('.resources')
|
|
||||||
//});
|
|
||||||
this.handleBindings();
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
goChat: function () {
|
goChat: function () {
|
||||||
|
console.log('event?');
|
||||||
app.navigate('chat/' + this.model.jid);
|
app.navigate('chat/' + this.model.jid);
|
||||||
}
|
}
|
||||||
});
|
});
|
@ -1,7 +1,9 @@
|
|||||||
/*global $*/
|
/*global $, app, me*/
|
||||||
var StrictView = require('strictview');
|
"use strict";
|
||||||
var templates = require('templates');
|
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
|
var StrictView = require('strictview');
|
||||||
|
var templates = require('../templates');
|
||||||
|
|
||||||
|
|
||||||
module.exports = StrictView.extend({
|
module.exports = StrictView.extend({
|
17
clientapp/views/main.js
Normal file
17
clientapp/views/main.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*global $, app, me*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var StrictView = require('strictview');
|
||||||
|
var templates = require('../templates');
|
||||||
|
var ContactListItem = require('../views/contactListItem');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = StrictView.extend({
|
||||||
|
template: templates.body,
|
||||||
|
render: function () {
|
||||||
|
$('head').append(templates.head());
|
||||||
|
this.renderAndBind();
|
||||||
|
this.renderCollection(me.contacts, ContactListItem, this.$('#contactList'));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
@ -1,11 +1,16 @@
|
|||||||
/*global $*/
|
/*global $*/
|
||||||
var StrictView = require('strictview');
|
"use strict";
|
||||||
var templates = require('templates');
|
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
|
var StrictView = require('strictview');
|
||||||
|
var templates = require('../templates');
|
||||||
|
|
||||||
|
|
||||||
module.exports = StrictView.extend({
|
module.exports = StrictView.extend({
|
||||||
template: templates.includes.message,
|
template: templates.includes.message,
|
||||||
|
initialize: function (opts) {
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
classBindings: {
|
classBindings: {
|
||||||
mine: '.message',
|
mine: '.message',
|
||||||
receiptReceived: '',
|
receiptReceived: '',
|
||||||
@ -13,17 +18,12 @@ module.exports = StrictView.extend({
|
|||||||
delayed: '',
|
delayed: '',
|
||||||
edited: ''
|
edited: ''
|
||||||
},
|
},
|
||||||
contentBindings: {
|
textBindings: {
|
||||||
body: '.body',
|
body: '.body',
|
||||||
formattedTime: '.timestamp'
|
formattedTime: '.timestamp'
|
||||||
},
|
},
|
||||||
initialize: function (opts) {
|
|
||||||
this.containerEl = opts.containerEl;
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
render: function () {
|
render: function () {
|
||||||
this.subViewRender({context: {message: this.model}});
|
this.renderAndBind({message: this.model});
|
||||||
this.handleBindings();
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
});
|
});
|
54
package.json
54
package.json
@ -1,48 +1,30 @@
|
|||||||
{
|
{
|
||||||
"name": "otalk.im",
|
"name": "otalk.im",
|
||||||
"description": "OTalk: WebRTC Enabled XMPP Client, in the Browser",
|
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
|
"description": "OTalk: WebRTC Enabled XMPP Client, in the Browser",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git@github.com:andyet/otalk.git"
|
"url": "git@github.com:andyet/otalk.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "3.x.x",
|
"andlog": "0.0.4",
|
||||||
"connect": "2.7.6",
|
"async": "0.2.9",
|
||||||
"connect-redis": "1.4.5",
|
|
||||||
"getconfig": "0.0.5",
|
|
||||||
"jade": "0.30.0",
|
|
||||||
"moonboots": "0.0.3",
|
|
||||||
"clientmodules": "0.0.5",
|
|
||||||
"backbone": "1.0.0",
|
"backbone": "1.0.0",
|
||||||
"underscore": "1.4.4",
|
"express": "3.3.7",
|
||||||
"strictmodel": "0.0.3",
|
"getconfig": "0.0.5",
|
||||||
"precommit-hook": "0.3.4",
|
"jade": "0.35.0",
|
||||||
|
"moonboots": "0.4.1",
|
||||||
|
"helmet": "0.1.0",
|
||||||
|
"node-uuid": "1.4.1",
|
||||||
|
"semi-static": "0.0.4",
|
||||||
"sound-effect-manager": "0.0.5",
|
"sound-effect-manager": "0.0.5",
|
||||||
"andlog": "0.0.3",
|
"strictmodel": "0.1.1",
|
||||||
"strictview": "0.0.2",
|
"strictview": "1.0.0",
|
||||||
"system-requirements": "0.0.2",
|
"templatizer": "0.1.2",
|
||||||
"webrtc.js": "0.0.3",
|
"underscore": "1.5.1"
|
||||||
"semi-static": "",
|
|
||||||
"node-uuid": "1.4.0",
|
|
||||||
"cookie-getter": "0.0.2",
|
|
||||||
"wildemitter": "0.0.5",
|
|
||||||
"image-to-data-uri": "",
|
|
||||||
"stanza.io": ""
|
|
||||||
},
|
},
|
||||||
"clientmodules": [
|
"devDependencies": {
|
||||||
"backbone",
|
"precommit-hook": "0.3.6"
|
||||||
"strictmodel",
|
},
|
||||||
"underscore",
|
"main": "server.js"
|
||||||
"sound-effect-manager",
|
|
||||||
"andlog",
|
|
||||||
"strictview",
|
|
||||||
"cookie-getter",
|
|
||||||
"wildemitter",
|
|
||||||
"image-to-data-uri"
|
|
||||||
],
|
|
||||||
"clientmodulesDir": "clientapp/modules",
|
|
||||||
"scripts": {
|
|
||||||
"postinstall": "node node_modules/clientmodules/install.js"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -118,14 +118,22 @@ html, body {
|
|||||||
color: #777;
|
color: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chatView {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
#conversation {
|
#conversation {
|
||||||
background: #ecf0f2;
|
background: #ecf0f2;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding-bottom: 30px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 50px;
|
||||||
|
bottom: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#conversation li {
|
#conversation li {
|
||||||
@ -187,11 +195,14 @@ html, body {
|
|||||||
|
|
||||||
.contactInfo {
|
.contactInfo {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
background-image: #bccad0;
|
background-color: #bccad0;
|
||||||
background-image: -moz-linear-gradient(top, #bccad0, #95a4a9);
|
background-image: -moz-linear-gradient(top, #bccad0, #95a4a9);
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #bccad0), color-stop(1, #95a4a9));
|
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #bccad0), color-stop(1, #95a4a9));
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
position: relative;
|
position: fixed;
|
||||||
|
top: 0px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contactInfo .avatar {
|
.contactInfo .avatar {
|
||||||
@ -213,3 +224,43 @@ html, body {
|
|||||||
#me {
|
#me {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#chatInput {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 176px;
|
||||||
|
right: 0px;
|
||||||
|
z-index: 100;
|
||||||
|
background: #ecf0f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chatInput form {
|
||||||
|
background-color: #e1e4e6;
|
||||||
|
background-image: -moz-linear-gradient(top, #e1e4e6, #cacdce);
|
||||||
|
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #e1e4e6), color-stop(1, #cacdce));
|
||||||
|
padding: 10px;
|
||||||
|
z-index: 1000;
|
||||||
|
border-top: 1px solid #fff;
|
||||||
|
border-left: 1px solid #ddd;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chatBuffer {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
resize: none;
|
||||||
|
outline: none;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-top-color: #bbb;
|
||||||
|
border-radius: 5px;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: 'lucida grande';
|
||||||
|
}
|
||||||
|
|
||||||
|
#chatBuffer.editing {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
69
server.js
69
server.js
@ -1,57 +1,46 @@
|
|||||||
/*global console*/
|
|
||||||
var fs = require('fs');
|
|
||||||
var privateKey = fs.readFileSync('fakekeys/privatekey.pem').toString();
|
|
||||||
var certificate = fs.readFileSync('fakekeys/certificate.pem').toString();
|
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var app = express();
|
var helmet = require('helmet');
|
||||||
var server = require('https').createServer({key: privateKey, cert: certificate}, app);
|
|
||||||
var connect = require('connect');
|
|
||||||
var RedisStore = require('connect-redis')(connect);
|
|
||||||
var https = require('https');
|
|
||||||
var Moonboots = require('moonboots');
|
var Moonboots = require('moonboots');
|
||||||
var config = require('getconfig');
|
var config = require('getconfig');
|
||||||
var semiStatic = require('semi-static');
|
var templatizer = require('templatizer');
|
||||||
var uuid = require('node-uuid');
|
|
||||||
|
|
||||||
|
|
||||||
|
var app = express();
|
||||||
|
app.use(express.compress());
|
||||||
app.use(express.static(__dirname + '/public'));
|
app.use(express.static(__dirname + '/public'));
|
||||||
app.enable('trust proxy');
|
if (!config.isDev) {
|
||||||
app.set('view engine', 'jade');
|
app.use(helmet.xframe());
|
||||||
app.use(express.bodyParser());
|
}
|
||||||
app.use(express.cookieParser());
|
app.use(helmet.iexss());
|
||||||
app.use(express.session({
|
app.use(helmet.contentTypeOptions());
|
||||||
proxy: true,
|
|
||||||
secret: config.session.secret,
|
|
||||||
store: new RedisStore({
|
|
||||||
host: config.session.host,
|
|
||||||
port: config.session.port,
|
|
||||||
db: config.session.db
|
|
||||||
}),
|
|
||||||
cookie: {
|
|
||||||
maxAge: 1000 * 60 * 60 * 24 * 90, // 90 days
|
|
||||||
secure: config.session.secure
|
|
||||||
},
|
|
||||||
key: 'o.im'
|
|
||||||
}));
|
|
||||||
|
|
||||||
var clientApp = new Moonboots({
|
var clientApp = new Moonboots({
|
||||||
fileName: 'stanzaiodemo',
|
main: __dirname + '/clientapp/app.js',
|
||||||
dir: __dirname + '/clientapp',
|
|
||||||
developmentMode: config.isDev,
|
developmentMode: config.isDev,
|
||||||
libraries: [
|
libraries: [
|
||||||
'jquery.js',
|
__dirname + '/clientapp/libraries/jquery.js',
|
||||||
'stanza.io.js',
|
__dirname + '/clientapp/libraries/sugar-1.2.1-dates.js',
|
||||||
'sugar-1.2.1-dates.js',
|
__dirname + '/clientapp/libraries/IndexedDBShim.min.js',
|
||||||
'init.js'
|
__dirname + '/clientapp/libraries/stanza.io.js'
|
||||||
],
|
],
|
||||||
server: app
|
stylesheets: [
|
||||||
|
__dirname + '/public/style.css'
|
||||||
|
],
|
||||||
|
browserify: {
|
||||||
|
debug: false
|
||||||
|
},
|
||||||
|
server: app,
|
||||||
|
beforeBuild: function () {
|
||||||
|
var clientFolder = __dirname + '/clientapp';
|
||||||
|
templatizer(clientFolder + '/templates', clientFolder + '/templates.js');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('test', function (req, res) {
|
|
||||||
res.send('<html></html>');
|
|
||||||
});
|
|
||||||
// serves app on every other url
|
// serves app on every other url
|
||||||
app.get('*', clientApp.html());
|
app.get('*', clientApp.html());
|
||||||
|
|
||||||
server.listen(config.http.port);
|
|
||||||
|
app.listen(config.http.port);
|
||||||
console.log('demo.stanza.io running at: ' + config.http.baseUrl);
|
console.log('demo.stanza.io running at: ' + config.http.baseUrl);
|
||||||
|
Loading…
Reference in New Issue
Block a user