mirror of
https://github.com/moparisthebest/kaiwa
synced 2024-11-23 01:32:18 -05:00
Refactor all the things
This commit is contained in:
parent
cae595b1c0
commit
83f68a7a2b
@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
clientapp/libraries
|
||||
clientapp/modules
|
||||
public
|
||||
clientapp/libraries
|
||||
clientapp/templates.js
|
||||
clientapp/.build
|
||||
|
18
.jshintrc
18
.jshintrc
@ -7,17 +7,9 @@
|
||||
"white": true,
|
||||
"undef": true,
|
||||
"browser": true,
|
||||
"es5": true,
|
||||
"predef": [
|
||||
"$",
|
||||
"me",
|
||||
"confirm",
|
||||
"alert",
|
||||
"require",
|
||||
"__dirname",
|
||||
"process",
|
||||
"exports",
|
||||
"Buffer",
|
||||
"module"
|
||||
]
|
||||
"node": true,
|
||||
"trailing": true,
|
||||
"indent": 4,
|
||||
"latedef": true,
|
||||
"newcap": true
|
||||
}
|
||||
|
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
|
||||
module.exports = function (obj, propName) {
|
||||
if (obj[propName] instanceof Function) {
|
@ -1,11 +1,12 @@
|
||||
/* global XMPP */
|
||||
/*global XMPP, me, app, client*/
|
||||
"use strict";
|
||||
|
||||
var crypto = XMPP.crypto;
|
||||
|
||||
var _ = require('underscore');
|
||||
var imageToDataURI = require('image-to-data-uri');
|
||||
var Contact = require('models/contact');
|
||||
var Resource = require('models/resource');
|
||||
var Message = require('models/message');
|
||||
var Contact = require('../models/contact');
|
||||
var Resource = require('../models/resource');
|
||||
var Message = require('../models/message');
|
||||
|
||||
|
||||
function logScroll() {
|
||||
@ -62,8 +63,12 @@ module.exports = function (client, app) {
|
||||
|
||||
client.getRoster(function (err, resp) {
|
||||
resp = resp.toJSON();
|
||||
|
||||
localStorage.rosterVersion = resp.roster.ver;
|
||||
|
||||
_.each(resp.roster.items, function (item) {
|
||||
me.contacts.add(item);
|
||||
console.log(item);
|
||||
me.setContact(item, true);
|
||||
});
|
||||
|
||||
client.updateCaps();
|
||||
@ -75,22 +80,22 @@ module.exports = function (client, app) {
|
||||
});
|
||||
|
||||
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) {
|
||||
var contact = me.getContact(item.jid);
|
||||
|
||||
if (item.subscription === 'remove') {
|
||||
if (contact) {
|
||||
me.contacts.remove(contact);
|
||||
me.removeContact(contact);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (contact) {
|
||||
contact.set(item);
|
||||
} else {
|
||||
me.contacts.add(item);
|
||||
}
|
||||
me.setContact(item, false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -132,16 +137,13 @@ module.exports = function (client, app) {
|
||||
client.on('avatar', function (info) {
|
||||
var contact = me.getContact(info.jid);
|
||||
if (contact) {
|
||||
var id = '';
|
||||
var type = 'image/png';
|
||||
if (info.avatars.length > 0) {
|
||||
client.getAvatar(info.jid, info.avatars[0].id, function (err, resp) {
|
||||
if (err) return;
|
||||
resp = resp.toJSON();
|
||||
var avatar = resp.pubsub.retrieve.item.avatarData;
|
||||
contact.avatar = 'data:' + info.avatars[0].type + ';base64,' + avatar;
|
||||
});
|
||||
} else {
|
||||
contact.useDefaultAvatar();
|
||||
id = info.avatars[0].id;
|
||||
type = info.avatars[0].type || 'image/png';
|
||||
}
|
||||
contact.setAvatar(id, type);
|
||||
}
|
||||
});
|
||||
|
||||
@ -161,8 +163,16 @@ module.exports = function (client, app) {
|
||||
if (contact && !msg.replace) {
|
||||
var message = new Message();
|
||||
message.cid = msg.id;
|
||||
delete msg.id;
|
||||
message.set(msg);
|
||||
|
||||
if (msg.archived) {
|
||||
msg.archived.forEach(function (archived) {
|
||||
if (me.isMe(archived.by)) {
|
||||
message.id = archived.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
contact.messages.add(message);
|
||||
if (!contact.lockedResource) {
|
||||
contact.lockedResource = msg.from;
|
||||
@ -216,16 +226,4 @@ module.exports = function (client, app) {
|
||||
|
||||
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);
|
||||
|
||||
this.send(new Message(data));
|
||||
this.emit('message:sent', message);
|
||||
this.send(message);
|
||||
|
||||
return data.id;
|
||||
};
|
||||
|
||||
Client.prototype.sendPresence = function (data) {
|
||||
@ -431,6 +434,8 @@ Client.prototype.sendPresence = function (data) {
|
||||
data.id = this.nextId();
|
||||
}
|
||||
this.send(new Presence(data));
|
||||
|
||||
return data.id;
|
||||
};
|
||||
|
||||
Client.prototype.sendIq = function (data, cb) {
|
||||
@ -449,6 +454,8 @@ Client.prototype.sendIq = function (data, cb) {
|
||||
});
|
||||
}
|
||||
this.send(new Iq(data));
|
||||
|
||||
return data.id;
|
||||
};
|
||||
|
||||
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) {
|
||||
return stanza.init(this, xml, data);
|
||||
}
|
||||
@ -2904,10 +2883,37 @@ Prefs.prototype = {
|
||||
stanza.extend(Iq, MAMQuery);
|
||||
stanza.extend(Iq, Prefs);
|
||||
stanza.extend(Message, Result);
|
||||
stanza.extend(Message, Archived);
|
||||
stanza.extend(Result, Forwarded);
|
||||
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.Result = Result;
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
// our base collection
|
||||
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 Contact = require('./contact');
|
||||
|
||||
@ -40,13 +44,20 @@ module.exports = BaseCollection.extend({
|
||||
}
|
||||
},
|
||||
initialize: function (model, options) {
|
||||
var self = 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 () {
|
||||
this.sort();
|
||||
},
|
||||
fetchHistory: function (contact) {
|
||||
contact.fetchHistory();
|
||||
}
|
||||
});
|
@ -1,5 +1,9 @@
|
||||
/*global app*/
|
||||
"use strict";
|
||||
|
||||
var StrictModel = require('strictmodel');
|
||||
var Contacts = require('./contacts');
|
||||
var Contact = require('./contact');
|
||||
|
||||
|
||||
module.exports = StrictModel.Model.extend({
|
||||
@ -34,6 +38,21 @@ module.exports = StrictModel.Model.extend({
|
||||
}
|
||||
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) {
|
||||
var hasResource = jid.indexOf('/') > 0;
|
||||
if (hasResource) {
|
@ -1,17 +1,24 @@
|
||||
/*global me*/
|
||||
"use strict";
|
||||
|
||||
var StrictModel = require('strictmodel').Model;
|
||||
|
||||
|
||||
module.exports = StrictModel.extend({
|
||||
init: function (attrs) {
|
||||
initialize: function (attrs) {
|
||||
console.log(attrs);
|
||||
this._created = Date.now();
|
||||
},
|
||||
type: 'message',
|
||||
idDefinition: {
|
||||
type: 'string'
|
||||
},
|
||||
props: {
|
||||
to: ['string', true, ''],
|
||||
from: ['string', true, ''],
|
||||
body: ['string', true, ''],
|
||||
type: ['string', true, 'normal'],
|
||||
acked: ['bool', true, false],
|
||||
acked: ['bool', true, false]
|
||||
},
|
||||
derived: {
|
||||
mine: {
|
||||
@ -38,7 +45,10 @@ module.exports = StrictModel.extend({
|
||||
formattedTime: {
|
||||
deps: ['created'],
|
||||
fn: function () {
|
||||
return this.created.format('{MM}/{dd} {h}:{mm}{t}');
|
||||
if (this.created) {
|
||||
return this.created.format('{MM}/{dd} {h}:{mm}{t}');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -46,7 +56,7 @@ module.exports = StrictModel.extend({
|
||||
_created: 'date',
|
||||
receiptReceived: ['bool', true, false],
|
||||
edited: ['bool', true, false],
|
||||
delay: 'object',
|
||||
delay: 'object'
|
||||
},
|
||||
correct: function (msg) {
|
||||
if (this.from !== msg.from) return;
|
@ -1,3 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var BaseCollection = require('./baseCollection');
|
||||
var Message = require('./message');
|
||||
|
@ -1,8 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
var StrictModel = require('strictmodel').Model;
|
||||
|
||||
|
||||
module.exports = StrictModel.extend({
|
||||
init: function () {},
|
||||
initialize: function () {},
|
||||
type: 'resource',
|
||||
session: {
|
||||
jid: ['string', true],
|
@ -1,3 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var BaseCollection = require('./baseCollection');
|
||||
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'),
|
||||
templates = require('templates');
|
||||
"use strict";
|
||||
|
||||
var BasePage = require('pages/base');
|
||||
var templates = require('templates');
|
||||
|
||||
|
||||
module.exports = BasePage.extend({
|
@ -1,14 +1,9 @@
|
||||
/*global app*/
|
||||
var Backbone = require('backbone'),
|
||||
staticPage = function (url) {
|
||||
return function () {
|
||||
var View = require('pages/wrapper');
|
||||
app.renderPage(new View({
|
||||
url: url
|
||||
}));
|
||||
};
|
||||
};
|
||||
/*global app, me*/
|
||||
"use strict";
|
||||
|
||||
var Backbone = require('backbone');
|
||||
var MainPage = require('./pages/main');
|
||||
var ChatPage = require('./pages/chat');
|
||||
|
||||
|
||||
module.exports = Backbone.Router.extend({
|
||||
@ -18,16 +13,14 @@ module.exports = Backbone.Router.extend({
|
||||
},
|
||||
// ------- ROUTE HANDLERS ---------
|
||||
main: function () {
|
||||
var View = require('pages/main');
|
||||
app.renderPage(new View({
|
||||
app.renderPage(new MainPage({
|
||||
model: me
|
||||
}));
|
||||
},
|
||||
chat: function (jid) {
|
||||
var View = require('pages/chat');
|
||||
var contact = me.contacts.get(jid);
|
||||
if (contact) {
|
||||
app.renderPage(new View({
|
||||
app.renderPage(new ChatPage({
|
||||
model: contact
|
||||
}));
|
||||
} 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
|
||||
nav#contactList
|
||||
header.contactInfo
|
||||
img.avatar(width=30, height=30)
|
||||
h1.name
|
||||
ul#conversation
|
||||
div#chatInput
|
||||
form
|
||||
textarea#chatBuffer(name='chatInput', type='text', placeholder='Send a message...', autocomplete='off')
|
||||
|
@ -1,4 +1,3 @@
|
||||
section.page.main
|
||||
nav#contactList
|
||||
#log
|
||||
h2 Event Log
|
||||
|
@ -1,8 +1,9 @@
|
||||
/*global app, $*/
|
||||
var StrictView = require('strictview');
|
||||
var templates = require('templates');
|
||||
/*global $, app, me*/
|
||||
"use strict";
|
||||
|
||||
var _ = require('underscore');
|
||||
var ContactListItemResource = require('views/contactListItemResource');
|
||||
var StrictView = require('strictview');
|
||||
var templates = require('../templates');
|
||||
|
||||
|
||||
module.exports = StrictView.extend({
|
||||
@ -12,29 +13,25 @@ module.exports = StrictView.extend({
|
||||
subscription: '',
|
||||
chatState: ''
|
||||
},
|
||||
contentBindings: {
|
||||
textBindings: {
|
||||
displayName: '.name',
|
||||
status: '.status'
|
||||
},
|
||||
imageBindings: {
|
||||
avatar: '.avatar img'
|
||||
srcBindings: {
|
||||
avatar: '.avatar'
|
||||
},
|
||||
events: {
|
||||
'click': 'goChat'
|
||||
},
|
||||
initialize: function (opts) {
|
||||
this.containerEl = opts.containerEl;
|
||||
this.render();
|
||||
},
|
||||
render: function () {
|
||||
this.subViewRender({context: {contact: this.model}});
|
||||
//this.collectomatic(this.model.resources, ContactListItemResource, {
|
||||
// containerEl: this.$('.resources')
|
||||
//});
|
||||
this.handleBindings();
|
||||
this.renderAndBind({contact: this.model});
|
||||
return this;
|
||||
},
|
||||
goChat: function () {
|
||||
console.log('event?');
|
||||
app.navigate('chat/' + this.model.jid);
|
||||
}
|
||||
});
|
@ -1,7 +1,9 @@
|
||||
/*global $*/
|
||||
var StrictView = require('strictview');
|
||||
var templates = require('templates');
|
||||
/*global $, app, me*/
|
||||
"use strict";
|
||||
|
||||
var _ = require('underscore');
|
||||
var StrictView = require('strictview');
|
||||
var templates = require('../templates');
|
||||
|
||||
|
||||
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 $*/
|
||||
var StrictView = require('strictview');
|
||||
var templates = require('templates');
|
||||
"use strict";
|
||||
|
||||
var _ = require('underscore');
|
||||
var StrictView = require('strictview');
|
||||
var templates = require('../templates');
|
||||
|
||||
|
||||
module.exports = StrictView.extend({
|
||||
template: templates.includes.message,
|
||||
initialize: function (opts) {
|
||||
this.render();
|
||||
},
|
||||
classBindings: {
|
||||
mine: '.message',
|
||||
receiptReceived: '',
|
||||
@ -13,17 +18,12 @@ module.exports = StrictView.extend({
|
||||
delayed: '',
|
||||
edited: ''
|
||||
},
|
||||
contentBindings: {
|
||||
textBindings: {
|
||||
body: '.body',
|
||||
formattedTime: '.timestamp'
|
||||
},
|
||||
initialize: function (opts) {
|
||||
this.containerEl = opts.containerEl;
|
||||
this.render();
|
||||
},
|
||||
render: function () {
|
||||
this.subViewRender({context: {message: this.model}});
|
||||
this.handleBindings();
|
||||
this.renderAndBind({message: this.model});
|
||||
return this;
|
||||
}
|
||||
});
|
74
package.json
74
package.json
@ -1,48 +1,30 @@
|
||||
{
|
||||
"name": "otalk.im",
|
||||
"description": "OTalk: WebRTC Enabled XMPP Client, in the Browser",
|
||||
"version": "0.0.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:andyet/otalk.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "3.x.x",
|
||||
"connect": "2.7.6",
|
||||
"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",
|
||||
"underscore": "1.4.4",
|
||||
"strictmodel": "0.0.3",
|
||||
"precommit-hook": "0.3.4",
|
||||
"sound-effect-manager": "0.0.5",
|
||||
"andlog": "0.0.3",
|
||||
"strictview": "0.0.2",
|
||||
"system-requirements": "0.0.2",
|
||||
"webrtc.js": "0.0.3",
|
||||
"semi-static": "",
|
||||
"node-uuid": "1.4.0",
|
||||
"cookie-getter": "0.0.2",
|
||||
"wildemitter": "0.0.5",
|
||||
"image-to-data-uri": "",
|
||||
"stanza.io": ""
|
||||
},
|
||||
"clientmodules": [
|
||||
"backbone",
|
||||
"strictmodel",
|
||||
"underscore",
|
||||
"sound-effect-manager",
|
||||
"andlog",
|
||||
"strictview",
|
||||
"cookie-getter",
|
||||
"wildemitter",
|
||||
"image-to-data-uri"
|
||||
],
|
||||
"clientmodulesDir": "clientapp/modules",
|
||||
"scripts": {
|
||||
"postinstall": "node node_modules/clientmodules/install.js"
|
||||
}
|
||||
"name": "otalk.im",
|
||||
"version": "0.0.1",
|
||||
"description": "OTalk: WebRTC Enabled XMPP Client, in the Browser",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:andyet/otalk.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"andlog": "0.0.4",
|
||||
"async": "0.2.9",
|
||||
"backbone": "1.0.0",
|
||||
"express": "3.3.7",
|
||||
"getconfig": "0.0.5",
|
||||
"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",
|
||||
"strictmodel": "0.1.1",
|
||||
"strictview": "1.0.0",
|
||||
"templatizer": "0.1.2",
|
||||
"underscore": "1.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"precommit-hook": "0.3.6"
|
||||
},
|
||||
"main": "server.js"
|
||||
}
|
||||
|
@ -118,14 +118,22 @@ html, body {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.chatView {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#conversation {
|
||||
background: #ecf0f2;
|
||||
box-sizing: border-box;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
padding-bottom: 30px;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
margin-top: 50px;
|
||||
bottom: 50px;
|
||||
}
|
||||
|
||||
#conversation li {
|
||||
@ -187,11 +195,14 @@ html, body {
|
||||
|
||||
.contactInfo {
|
||||
padding: 5px;
|
||||
background-image: #bccad0;
|
||||
background-color: #bccad0;
|
||||
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));
|
||||
min-height: 30px;
|
||||
position: relative;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.contactInfo .avatar {
|
||||
@ -213,3 +224,43 @@ html, body {
|
||||
#me {
|
||||
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 app = express();
|
||||
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 helmet = require('helmet');
|
||||
var Moonboots = require('moonboots');
|
||||
var config = require('getconfig');
|
||||
var semiStatic = require('semi-static');
|
||||
var uuid = require('node-uuid');
|
||||
var templatizer = require('templatizer');
|
||||
|
||||
|
||||
var app = express();
|
||||
app.use(express.compress());
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
app.enable('trust proxy');
|
||||
app.set('view engine', 'jade');
|
||||
app.use(express.bodyParser());
|
||||
app.use(express.cookieParser());
|
||||
app.use(express.session({
|
||||
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'
|
||||
}));
|
||||
if (!config.isDev) {
|
||||
app.use(helmet.xframe());
|
||||
}
|
||||
app.use(helmet.iexss());
|
||||
app.use(helmet.contentTypeOptions());
|
||||
|
||||
|
||||
var clientApp = new Moonboots({
|
||||
fileName: 'stanzaiodemo',
|
||||
dir: __dirname + '/clientapp',
|
||||
main: __dirname + '/clientapp/app.js',
|
||||
developmentMode: config.isDev,
|
||||
libraries: [
|
||||
'jquery.js',
|
||||
'stanza.io.js',
|
||||
'sugar-1.2.1-dates.js',
|
||||
'init.js'
|
||||
__dirname + '/clientapp/libraries/jquery.js',
|
||||
__dirname + '/clientapp/libraries/sugar-1.2.1-dates.js',
|
||||
__dirname + '/clientapp/libraries/IndexedDBShim.min.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
|
||||
app.get('*', clientApp.html());
|
||||
|
||||
server.listen(config.http.port);
|
||||
|
||||
app.listen(config.http.port);
|
||||
console.log('demo.stanza.io running at: ' + config.http.baseUrl);
|
||||
|
Loading…
Reference in New Issue
Block a user