mirror of
https://github.com/moparisthebest/kaiwa
synced 2024-11-22 17:22:22 -05:00
Add avatar changer.
This commit is contained in:
parent
6be75b7f6c
commit
f790aaebc2
@ -186,7 +186,14 @@ module.exports = function (client, app) {
|
|||||||
|
|
||||||
client.on('avatar', function (info) {
|
client.on('avatar', function (info) {
|
||||||
var contact = me.getContact(info.jid);
|
var contact = me.getContact(info.jid);
|
||||||
if (contact) {
|
if (!contact) {
|
||||||
|
if (me.isMe(info.jid)) {
|
||||||
|
contact = me;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var id = '';
|
var id = '';
|
||||||
var type = 'image/png';
|
var type = 'image/png';
|
||||||
if (info.avatars.length > 0) {
|
if (info.avatars.length > 0) {
|
||||||
@ -194,7 +201,6 @@ module.exports = function (client, app) {
|
|||||||
type = info.avatars[0].type || 'image/png';
|
type = info.avatars[0].type || 'image/png';
|
||||||
}
|
}
|
||||||
contact.setAvatar(id, type);
|
contact.setAvatar(id, type);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('chatState', function (info) {
|
client.on('chatState', function (info) {
|
||||||
|
114
clientapp/libraries/resampler.js
Normal file
114
clientapp/libraries/resampler.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
var Resample = (function (canvas) {
|
||||||
|
|
||||||
|
// (C) WebReflection Mit Style License
|
||||||
|
|
||||||
|
// Resample function, accepts an image
|
||||||
|
// as url, base64 string, or Image/HTMLImgElement
|
||||||
|
// optional width or height, and a callback
|
||||||
|
// to invoke on operation complete
|
||||||
|
function Resample(img, width, height, onresample) {
|
||||||
|
var
|
||||||
|
// check the image type
|
||||||
|
load = typeof img == "string",
|
||||||
|
// Image pointer
|
||||||
|
i = load || img
|
||||||
|
;
|
||||||
|
// if string, a new Image is needed
|
||||||
|
if (load) {
|
||||||
|
i = new Image;
|
||||||
|
// with propers callbacks
|
||||||
|
i.onload = onload;
|
||||||
|
i.onerror = onerror;
|
||||||
|
}
|
||||||
|
// easy/cheap way to store info
|
||||||
|
i._onresample = onresample;
|
||||||
|
i._width = width;
|
||||||
|
i._height = height;
|
||||||
|
// if string, we trust the onload event
|
||||||
|
// otherwise we call onload directly
|
||||||
|
// with the image as callback context
|
||||||
|
load ? (i.src = img) : onload.call(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
// just in case something goes wrong
|
||||||
|
function onerror() {
|
||||||
|
throw ("not found: " + this.src);
|
||||||
|
}
|
||||||
|
|
||||||
|
// called when the Image is ready
|
||||||
|
function onload() {
|
||||||
|
var
|
||||||
|
// minifier friendly
|
||||||
|
img = this,
|
||||||
|
// the desired width, if any
|
||||||
|
width = img._width,
|
||||||
|
// the desired height, if any
|
||||||
|
height = img._height,
|
||||||
|
// the callback
|
||||||
|
onresample = img._onresample
|
||||||
|
;
|
||||||
|
// if width and height are both specified
|
||||||
|
// the resample uses these pixels
|
||||||
|
// if width is specified but not the height
|
||||||
|
// the resample respects proportions
|
||||||
|
// accordingly with orginal size
|
||||||
|
// same is if there is a height, but no width
|
||||||
|
width == null && (width = round(img.width * height / img.height));
|
||||||
|
height == null && (height = round(img.height * width / img.width));
|
||||||
|
// remove (hopefully) stored info
|
||||||
|
delete img._onresample;
|
||||||
|
delete img._width;
|
||||||
|
delete img._height;
|
||||||
|
// when we reassign a canvas size
|
||||||
|
// this clears automatically
|
||||||
|
// the size should be exactly the same
|
||||||
|
// of the final image
|
||||||
|
// so that toDataURL ctx method
|
||||||
|
// will return the whole canvas as png
|
||||||
|
// without empty spaces or lines
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
// drawImage has different overloads
|
||||||
|
// in this case we need the following one ...
|
||||||
|
context.drawImage(
|
||||||
|
// original image
|
||||||
|
img,
|
||||||
|
// starting x point
|
||||||
|
0,
|
||||||
|
// starting y point
|
||||||
|
0,
|
||||||
|
// image width
|
||||||
|
img.width,
|
||||||
|
// image height
|
||||||
|
img.height,
|
||||||
|
// destination x point
|
||||||
|
0,
|
||||||
|
// destination y point
|
||||||
|
0,
|
||||||
|
// destination width
|
||||||
|
width,
|
||||||
|
// destination height
|
||||||
|
height
|
||||||
|
);
|
||||||
|
// retrieve the canvas content as
|
||||||
|
// base4 encoded PNG image
|
||||||
|
// and pass the result to the callback
|
||||||
|
onresample(canvas.toDataURL("image/png"));
|
||||||
|
}
|
||||||
|
|
||||||
|
var
|
||||||
|
// point one, use every time ...
|
||||||
|
context = canvas.getContext("2d"),
|
||||||
|
// local scope shortcut
|
||||||
|
round = Math.round
|
||||||
|
;
|
||||||
|
|
||||||
|
return Resample;
|
||||||
|
|
||||||
|
}(
|
||||||
|
// lucky us we don't even need to append
|
||||||
|
// and render anything on the screen
|
||||||
|
// let's keep this DOM node in RAM
|
||||||
|
// for all resizes we want
|
||||||
|
this.document.createElement("canvas"))
|
||||||
|
);
|
@ -708,14 +708,14 @@ module.exports = function (client) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
client.publishAvatar = function (id, data, cb) {
|
client.publishAvatar = function (id, data, cb) {
|
||||||
client.publish(null, 'urn:xmpp:avatar:data', {
|
client.publish('', 'urn:xmpp:avatar:data', {
|
||||||
id: id,
|
id: id,
|
||||||
avatarData: data
|
avatarData: data
|
||||||
}, cb);
|
}, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
client.useAvatars = function (info, cb) {
|
client.useAvatars = function (info, cb) {
|
||||||
client.publish(null, 'urn:xmpp:avatar:metadata', {
|
client.publish('', 'urn:xmpp:avatar:metadata', {
|
||||||
id: 'current',
|
id: 'current',
|
||||||
avatars: info
|
avatars: info
|
||||||
}, cb);
|
}, cb);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/*global app, client*/
|
/*global app, client, XMPP*/
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var HumanModel = require('human-model');
|
var HumanModel = require('human-model');
|
||||||
@ -20,6 +20,7 @@ module.exports = HumanModel.define({
|
|||||||
jid: ['object', true],
|
jid: ['object', true],
|
||||||
status: ['string', true, ''],
|
status: ['string', true, ''],
|
||||||
avatar: ['string', true, ''],
|
avatar: ['string', true, ''],
|
||||||
|
avatarID: ['string', true, ''],
|
||||||
connected: ['bool', true, false],
|
connected: ['bool', true, false],
|
||||||
shouldAskForAlertsPermission: ['bool', true, false],
|
shouldAskForAlertsPermission: ['bool', true, false],
|
||||||
hasFocus: ['bool', true, false],
|
hasFocus: ['bool', true, false],
|
||||||
@ -41,6 +42,44 @@ module.exports = HumanModel.define({
|
|||||||
}
|
}
|
||||||
this._activeContact = jid;
|
this._activeContact = jid;
|
||||||
},
|
},
|
||||||
|
setAvatar: function (id, type) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
var gID = XMPP.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 = XMPP.crypto.createHash('md5').update(self.jid.bare).digest('hex');
|
||||||
|
self.avatar = 'https://gravatar.com/avatar/' + gID + '?s=30&d=mm';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
app.whenConnected(function () {
|
||||||
|
client.getAvatar(self.jid.bare, 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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.set({
|
||||||
|
avatar: avatar.uri,
|
||||||
|
avatarID: avatar.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
getContact: function (jid, alt) {
|
getContact: function (jid, alt) {
|
||||||
if (typeof jid === 'string') jid = new client.JID(jid);
|
if (typeof jid === 'string') jid = new client.JID(jid);
|
||||||
if (typeof alt === 'string') alt = new client.JID(alt);
|
if (typeof alt === 'string') alt = new client.JID(alt);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/*global app, me*/
|
/*global app, me, XMPP, client, Resample*/
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var BasePage = require('./base');
|
var BasePage = require('./base');
|
||||||
@ -8,10 +8,15 @@ var templates = require('../templates');
|
|||||||
module.exports = BasePage.extend({
|
module.exports = BasePage.extend({
|
||||||
template: templates.pages.main,
|
template: templates.pages.main,
|
||||||
classBindings: {
|
classBindings: {
|
||||||
'shouldAskForAlertsPermission': '.enableAlerts'
|
shouldAskForAlertsPermission: '.enableAlerts'
|
||||||
|
},
|
||||||
|
srcBindings: {
|
||||||
|
avatar: '#avatarChanger img'
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
'click .enableAlerts': 'enableAlerts'
|
'click .enableAlerts': 'enableAlerts',
|
||||||
|
'dragover': 'handleAvatarChangeDragOver',
|
||||||
|
'drop': 'handleAvatarChange'
|
||||||
},
|
},
|
||||||
initialize: function (spec) {
|
initialize: function (spec) {
|
||||||
me.shouldAskForAlertsPermission = app.notifier.shouldAskPermission();
|
me.shouldAskForAlertsPermission = app.notifier.shouldAskPermission();
|
||||||
@ -27,5 +32,36 @@ module.exports = BasePage.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
handleAvatarChangeDragOver: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
handleAvatarChange: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var file = e.dataTransfer.files[0];
|
||||||
|
if (file.type.match('image.*')) {
|
||||||
|
console.log('Got an image file!', file.type);
|
||||||
|
var fileTracker = new FileReader();
|
||||||
|
fileTracker.onload = function () {
|
||||||
|
var resampler = new Resample(this.result, 80, 80, function (data) {
|
||||||
|
var b64Data = data.split(',')[1];
|
||||||
|
var id = XMPP.crypto.createHash('sha1').update(atob(b64Data)).digest('hex');
|
||||||
|
console.log(id);
|
||||||
|
app.storage.avatars.add({id: id, uri: data});
|
||||||
|
client.publishAvatar(id, b64Data, function (err, res) {
|
||||||
|
if (err) return;
|
||||||
|
client.useAvatars([{
|
||||||
|
id: id,
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
type: 'image/png',
|
||||||
|
bytes: b64Data.length
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
fileTracker.readAsDataURL(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -73,6 +73,15 @@ exports.includes.mucListItem = function anonymous(locals) {
|
|||||||
return buf.join("");
|
return buf.join("");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// mucMessage.jade compiled template
|
||||||
|
exports.includes.mucMessage = function anonymous(locals) {
|
||||||
|
var buf = [];
|
||||||
|
with (locals || {}) {
|
||||||
|
buf.push('<li><div class="message"><span class="sender">' + jade.escape(null == (jade.interp = message.nick) ? "" : jade.interp) + '</span><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("");
|
||||||
|
};
|
||||||
|
|
||||||
// growlMessage.jade compiled template
|
// growlMessage.jade compiled template
|
||||||
exports.misc.growlMessage = function anonymous(locals) {
|
exports.misc.growlMessage = function anonymous(locals) {
|
||||||
var buf = [];
|
var buf = [];
|
||||||
@ -122,7 +131,7 @@ exports.pages.groupchat = function anonymous(locals) {
|
|||||||
exports.pages.main = function anonymous(locals) {
|
exports.pages.main = function anonymous(locals) {
|
||||||
var buf = [];
|
var buf = [];
|
||||||
with (locals || {}) {
|
with (locals || {}) {
|
||||||
buf.push('<section class="page main"><button class="enableAlerts">Enable alerts</button><h1>This space intentionally blank</h1></section>');
|
buf.push('<section class="page main"><button class="enableAlerts">Enable alerts</button><div id="avatarChanger"><h1>Change Avatar</h1><div class="uploadRegion"><p>Drag and drop a new avatar here</p><img width="40" height="40"/><form><input id="uploader" type="file"/></form></div></div><h1>This space intentionally blank</h1></section>');
|
||||||
}
|
}
|
||||||
return buf.join("");
|
return buf.join("");
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
section.page.main
|
section.page.main
|
||||||
button.enableAlerts Enable alerts
|
button.enableAlerts Enable alerts
|
||||||
|
|
||||||
|
div#avatarChanger
|
||||||
|
h1 Change Avatar
|
||||||
|
div.uploadRegion
|
||||||
|
p Drag and drop a new avatar here
|
||||||
|
img(width="40", height="40")
|
||||||
|
form
|
||||||
|
input#uploader(type="file")
|
||||||
|
|
||||||
h1 This space intentionally blank
|
h1 This space intentionally blank
|
||||||
|
9
public/css/app/settings.css
Normal file
9
public/css/app/settings.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.uploadRegion {
|
||||||
|
-moz-border-radius: 10px;
|
||||||
|
-webkit-border-radius: 10px;
|
||||||
|
-khtml-border-radius: 10px;
|
||||||
|
-o-border-radius: 10px;
|
||||||
|
-border-radius: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px dashed #777;
|
||||||
|
}
|
12
public/css/app/settings.styl
Normal file
12
public/css/app/settings.styl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
@import '../_variables'
|
||||||
|
@import '../_mixins'
|
||||||
|
|
||||||
|
.uploadRegion
|
||||||
|
font-size: 12px
|
||||||
|
roundall: 10px
|
||||||
|
border: 1px dashed #777
|
||||||
|
margin: 10px
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
input
|
||||||
|
width: 100%
|
@ -245,6 +245,67 @@ td {
|
|||||||
.clearfix {
|
.clearfix {
|
||||||
zoom: 1;
|
zoom: 1;
|
||||||
}
|
}
|
||||||
|
body {
|
||||||
|
background: #ecf0f2;
|
||||||
|
color: #222;
|
||||||
|
font-family: 'Lucdia Grande', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 500;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
body #pages {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
left: 156px;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body #menu {
|
||||||
|
width: 155px;
|
||||||
|
}
|
||||||
|
#connectionOverlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
z-index: 1000;
|
||||||
|
-webkit-transition: all 0.25s linear 0;
|
||||||
|
-o-transition: all 0.25s linear 0;
|
||||||
|
transition: all 0.25s linear 0;
|
||||||
|
-moz-transition: all 0.25s linear 0;
|
||||||
|
}
|
||||||
|
#connectionOverlay.connected {
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
#connectionOverlay.connected #connectionStatus {
|
||||||
|
top: -51px;
|
||||||
|
}
|
||||||
|
#connectionStatus {
|
||||||
|
height: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
top: 0px;
|
||||||
|
position: fixed;
|
||||||
|
background-color: #333;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 9999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#connectionStatus span.message {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0px 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
#connectionStatus button {
|
||||||
|
padding: 5px 8px;
|
||||||
|
position: relative;
|
||||||
|
top: -3px;
|
||||||
|
}
|
||||||
#menu {
|
#menu {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
@ -627,66 +688,20 @@ td {
|
|||||||
.chatBox textarea.editing {
|
.chatBox textarea.editing {
|
||||||
background-color: #ff0;
|
background-color: #ff0;
|
||||||
}
|
}
|
||||||
#connectionOverlay {
|
.uploadRegion {
|
||||||
position: fixed;
|
font-size: 12px;
|
||||||
top: 0px;
|
-moz-border-radius: 10px;
|
||||||
left: 0px;
|
-webkit-border-radius: 10px;
|
||||||
width: 100%;
|
-khtml-border-radius: 10px;
|
||||||
height: 100%;
|
-o-border-radius: 10px;
|
||||||
background: rgba(0,0,0,0.5);
|
-border-radius: 10px;
|
||||||
z-index: 1000;
|
border-radius: 10px;
|
||||||
-webkit-transition: all 0.25s linear 0;
|
border: 1px dashed #777;
|
||||||
-o-transition: all 0.25s linear 0;
|
margin: 10px;
|
||||||
transition: all 0.25s linear 0;
|
|
||||||
-moz-transition: all 0.25s linear 0;
|
|
||||||
}
|
|
||||||
#connectionOverlay.connected {
|
|
||||||
height: 0px;
|
|
||||||
}
|
|
||||||
#connectionOverlay.connected #connectionStatus {
|
|
||||||
top: -51px;
|
|
||||||
}
|
|
||||||
#connectionStatus {
|
|
||||||
height: 50px;
|
|
||||||
line-height: 50px;
|
|
||||||
top: 0px;
|
|
||||||
position: fixed;
|
|
||||||
background-color: #333;
|
|
||||||
border-bottom: 1px solid #000;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 9999;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
#connectionStatus span.message {
|
.uploadRegion input {
|
||||||
display: inline-block;
|
width: 100%;
|
||||||
padding: 0px 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
#connectionStatus button {
|
|
||||||
padding: 5px 8px;
|
|
||||||
position: relative;
|
|
||||||
top: -3px;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
background: #ecf0f2;
|
|
||||||
color: #222;
|
|
||||||
font-family: 'Lucdia Grande', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 500;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
}
|
|
||||||
body #pages {
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
right: 0px;
|
|
||||||
left: 156px;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
body #menu {
|
|
||||||
width: 155px;
|
|
||||||
}
|
}
|
||||||
.aux header {
|
.aux header {
|
||||||
margin-top: 10%;
|
margin-top: 10%;
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
@import '_reset'
|
@import '_reset'
|
||||||
@import '_variables'
|
@import '_variables'
|
||||||
@import '_mixins'
|
@import '_mixins'
|
||||||
|
@import 'app/layout'
|
||||||
|
@import 'app/connectionStatus'
|
||||||
@import 'app/roster'
|
@import 'app/roster'
|
||||||
@import 'app/chat'
|
@import 'app/chat'
|
||||||
@import 'app/connectionStatus'
|
@import 'app/settings'
|
||||||
@import 'app/layout'
|
|
||||||
@import 'app/aux'
|
@import 'app/aux'
|
||||||
|
@ -24,6 +24,7 @@ var clientApp = new Moonboots({
|
|||||||
libraries: [
|
libraries: [
|
||||||
__dirname + '/clientapp/libraries/zepto.js',
|
__dirname + '/clientapp/libraries/zepto.js',
|
||||||
__dirname + '/clientapp/libraries/ui.js',
|
__dirname + '/clientapp/libraries/ui.js',
|
||||||
|
__dirname + '/clientapp/libraries/resampler.js',
|
||||||
__dirname + '/clientapp/libraries/IndexedDBShim.min.js',
|
__dirname + '/clientapp/libraries/IndexedDBShim.min.js',
|
||||||
__dirname + '/clientapp/libraries/stanza.io.js'
|
__dirname + '/clientapp/libraries/stanza.io.js'
|
||||||
],
|
],
|
||||||
|
Loading…
Reference in New Issue
Block a user