stormfront-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +31 -0
  6. data/Rakefile +2 -0
  7. data/lib/stormfront/rails/version.rb +5 -0
  8. data/lib/stormfront/rails.rb +7 -0
  9. data/stormfront-rails.gemspec +22 -0
  10. data/vendor/assets/javascripts/stormfront/Alert.js +17 -0
  11. data/vendor/assets/javascripts/stormfront/Container.js +31 -0
  12. data/vendor/assets/javascripts/stormfront/Entity.js +53 -0
  13. data/vendor/assets/javascripts/stormfront/Errors.js +25 -0
  14. data/vendor/assets/javascripts/stormfront/KeyboardEvents.js +52 -0
  15. data/vendor/assets/javascripts/stormfront/Layout.js +60 -0
  16. data/vendor/assets/javascripts/stormfront/List.js +123 -0
  17. data/vendor/assets/javascripts/stormfront/MouseEvents.js +146 -0
  18. data/vendor/assets/javascripts/stormfront/Namespaces.js +5 -0
  19. data/vendor/assets/javascripts/stormfront/Overlay.js +178 -0
  20. data/vendor/assets/javascripts/stormfront/Reducer.js +9 -0
  21. data/vendor/assets/javascripts/stormfront/Request.js +39 -0
  22. data/vendor/assets/javascripts/stormfront/View.js +55 -0
  23. data/vendor/assets/javascripts/stormfront/ViewBase.js +57 -0
  24. data/vendor/assets/javascripts/stormfront/mixin/Dispatch.js +19 -0
  25. data/vendor/assets/javascripts/stormfront/mixin/Other.js +25 -0
  26. data/vendor/assets/javascripts/stormfront/support/Chaperone.js +69 -0
  27. data/vendor/assets/javascripts/stormfront/support/Template.js +75 -0
  28. data/vendor/assets/javascripts/stormfront/types/Arguments.js +27 -0
  29. data/vendor/assets/javascripts/stormfront/types/Class.js +25 -0
  30. data/vendor/assets/javascripts/stormfront/types/Hash.js +23 -0
  31. data/vendor/assets/javascripts/stormfront/types/Number.js +15 -0
  32. data/vendor/assets/javascripts/stormfront/types/Response.js +31 -0
  33. data/vendor/assets/javascripts/stormfront/types/String.js +44 -0
  34. data/vendor/assets/javascripts/stormfront/types/Time.js +29 -0
  35. data/vendor/assets/javascripts/stormfront.js +33 -0
  36. metadata +148 -0
@@ -0,0 +1,178 @@
1
+ Stormfront.Overlay = Stormfront.Class.extend({
2
+ initialize: function(options){
3
+ var selector = options.selector;
4
+
5
+ if (!selector)
6
+ this.overlay = new Stormfront.FullScreenOverlay({model: options});
7
+ else if (options.saveRequest)
8
+ this.overlay = new Stormfront.InlineOverlay();
9
+ else
10
+ this.overlay = new Stormfront.AnonymousOverlay();
11
+
12
+ selector = selector ? selector : document.body;
13
+ this.location = selector.size ? selector : $(selector);
14
+ this.render();
15
+ },
16
+ render: function(){
17
+ this.location.append(this.overlay.render().$el);
18
+ this.overlay.transition('attached');
19
+ return this;
20
+ },
21
+ close: function(){
22
+ this.overlay.close();
23
+ this.location = null;
24
+ },
25
+ error: function(){
26
+ this.overlay.error.apply(this.overlay, arguments);
27
+ }
28
+ });
29
+
30
+ Stormfront.OverlayBase = Stormfront.View.extend({
31
+ className: 'overlay',
32
+ activated: false,
33
+ PAUSE_DURATION: 0,
34
+ MINIMUM_DURATION: 0,
35
+ base_css: {
36
+ overlay_background: {
37
+ top: '0px', left: '0px',
38
+ width: '100%', height: '100%'
39
+ },
40
+ overlay_panel: {
41
+ position: 'absolute',
42
+ top: '50%', left: '50%'
43
+ }
44
+ },
45
+ base: {
46
+ rendering: function(){
47
+ function applyCss(value, key){
48
+ var selector = '.' + key;
49
+ this.$(selector).css(value);
50
+ }
51
+ this.startTime = stormfront.time().now();
52
+ _.each(this.base_css, applyCss, this);
53
+ _.each(this.css, applyCss, this);
54
+ this.$el.css({opacity: '0', 'pointer-events': 'none'});
55
+ },
56
+ attached: function(){
57
+ var self = this;
58
+ function activate(){
59
+ self.$el.css({opacity: '1', 'pointer-events': 'auto'});
60
+ self.activated = true;
61
+ }
62
+ function setDimensions(panel){
63
+ var content = self.$('.content');
64
+ var position = {
65
+ 'margin-left': - content.outerWidth() / 2,
66
+ 'margin-top': - content.outerHeight() / 2
67
+ };
68
+ panel.css(position);
69
+ }
70
+ setDimensions(this.$('.overlay_panel'));
71
+ if (this.PAUSE_DURATION)
72
+ setTimeout(activate, this.PAUSE_DURATION);
73
+ else
74
+ activate();
75
+ },
76
+ closing: function(){
77
+ var self = this;
78
+ function remove(){
79
+ self.remove();
80
+ }
81
+ if (!this.activated)
82
+ this.remove();
83
+
84
+ var currentDuration = stormfront.time().now() - this.startTime;
85
+ var remainingDuration = this.MINIMUM_DURATION - currentDuration;
86
+ if (remainingDuration > 0)
87
+ setTimeout(remove, remainingDuration);
88
+ else
89
+ this.remove();
90
+ }
91
+ },
92
+ initialize: function(){
93
+ this.addSubscriber(this.base);
94
+ Stormfront.View.prototype.initialize.apply(this, arguments);
95
+ },
96
+ error: function(){
97
+ this.remove();
98
+ },
99
+ close: function(){
100
+ this.transition.apply(this, stormfront.arguments(arguments).prepend('closing').get());
101
+ }
102
+ });
103
+
104
+ Stormfront.FullScreenOverlay = Stormfront.OverlayBase.extend({
105
+ MINIMUM_DURATION: 900,
106
+ css: {
107
+ overlay_background: {
108
+ position: 'fixed',
109
+ 'z-index': '100000'
110
+ },
111
+ overlay_panel: {
112
+ 'z-index': '100001'
113
+ }
114
+ },
115
+ when: {
116
+ rendering: function(){
117
+ this.$el.addClass('blocking');
118
+ }
119
+ }
120
+ });
121
+
122
+ Stormfront.AnonymousOverlay = Stormfront.OverlayBase.extend({
123
+ PAUSE_DURATION: 200,
124
+ MINIMUM_DURATION: 450,
125
+ css: {
126
+ overlay_background: {
127
+ position: 'absolute',
128
+ 'z-index': 'auto'
129
+ },
130
+ overlay_panel: {
131
+ 'z-index': 'auto'
132
+ }
133
+ },
134
+ when: {
135
+ rendering: function(){
136
+ this.$el.addClass('anonymous');
137
+ }
138
+ }
139
+ });
140
+
141
+ Stormfront.InlineOverlay = Stormfront.OverlayBase.extend({
142
+ className: 'input_overlay',
143
+ MINIMUM_DURATION: 1500,
144
+ MAXIMUM_WIDTH: 1200,
145
+ base_css: {
146
+ input: {
147
+ position: 'absolute',
148
+ top: '2px',
149
+ right: '10px',
150
+ width:'16px',
151
+ 'z-index':'10000'
152
+ }
153
+ },
154
+ when: {
155
+ rendering: function () {
156
+ this.$el.find('.error').hide();
157
+ this.$el.find('.confirm').hide();
158
+ },
159
+ closing: function(){
160
+ this.$el.find('.spinner').hide();
161
+ this.$el.find('.confirm').show();
162
+ }
163
+ },
164
+ error: function (xhr) {
165
+ //TODO: Elevate this code up a level to the Request
166
+ try {
167
+ var response = JSON.parse(xhr.responseText);
168
+ var error = response.error || response.message;
169
+ this.$el.find('.spinner').hide();
170
+ this.$el.find('.error').show().attr('title', error);
171
+ xhr.handled = true;
172
+ }
173
+ catch (e) {
174
+ console.warn('Could not parse error', arguments);
175
+ this.remove();
176
+ }
177
+ }
178
+ });
@@ -0,0 +1,9 @@
1
+ Stormfront.Reducer = Stormfront.Class.extend({
2
+ type: Stormfront.Errors.NOT_IMPLEMENTED,
3
+ initialize: function(){
4
+ _.extend(this, Stormfront.Mixin.Dispatch);
5
+ },
6
+ execute: function(model, event){
7
+ throw Stormfront.Errors.UseCases.NOT_IMPLEMENTED;
8
+ }
9
+ });
@@ -0,0 +1,39 @@
1
+ Stormfront.Request = function(model, request, options){
2
+ var startTime = stormfront.time.now();
3
+ var overlay = new Stormfront.Overlay(options);
4
+ function tryRequest(){
5
+ return shouldWait() ? delayRequest() : sendRequest();
6
+ }
7
+ function shouldWait(){
8
+ //TODO: This is a dependency upon the Indigo behavior
9
+ return options.wait && $.xhrPool.length > 0;
10
+ }
11
+ function delayRequest(){
12
+ if (stormfront.time.now() - startTime < 5000)
13
+ setTimeout(tryRequest, 1000);
14
+ else
15
+ overlay.error('Request timed out. Please try again soon.');
16
+ return null;
17
+ }
18
+ function sendRequest(){
19
+ function onComplete(){
20
+ removeListeners();
21
+ overlay.close();
22
+ }
23
+ function onError(model, xhr){
24
+ removeListeners();
25
+ overlay.error(xhr);
26
+ }
27
+ function removeListeners(){
28
+ model.off('error', onError);
29
+ model.off('sync', onComplete);
30
+ model.off('invalid', onComplete);
31
+ }
32
+ model.once('invalid', onError);
33
+ model.once('error', onError);
34
+ model.once('sync', onComplete);
35
+
36
+ return request.call(model, options.args, options.options);
37
+ }
38
+ return tryRequest();
39
+ };
@@ -0,0 +1,55 @@
1
+ Stormfront.View = Stormfront.ViewBase.extend({
2
+ initialize: function(options){
3
+ Stormfront.ViewBase.prototype.initialize.call(this, options);
4
+ this.when = this.when || { };
5
+ this.addPublisher(this);
6
+ this.addSubscriber(this.when);
7
+ this.transition.apply(this, stormfront.arguments(arguments).prepend('initializing').get());
8
+ },
9
+ render: function(){
10
+ Stormfront.ViewBase.prototype.render.call(this);
11
+ this.transition.apply(this, stormfront.arguments(arguments).prepend('rendering').get());
12
+ return this;
13
+ },
14
+ addPublisher: function(publisher, identity){
15
+ function notify(event){
16
+ var args = stormfront.arguments(arguments).skip(1).get();
17
+ function executeHandler(subscriber){
18
+ var handler = stormfront.hash(subscriber).findNestedValue(event.split(':'));
19
+ if (_.isFunction(handler))
20
+ handler.apply(this, args);
21
+ }
22
+ _.each(this._subscribers, executeHandler, this);
23
+ }
24
+ function prepend(identity){
25
+ return function(event){
26
+ var newEvent = identity + ':' + event;
27
+ var args = stormfront.arguments(arguments).skip(1).prepend(newEvent).get();
28
+ notify.apply(this, args);
29
+ }
30
+ }
31
+ if (publisher === this)
32
+ this.listenTo(publisher, 'transition', notify);
33
+ else if (this.model && publisher === this.model)
34
+ this.listenTo(publisher, 'all', prepend('model'));
35
+ else if (this.collection && publisher === this.collection)
36
+ this.listenTo(publisher, 'all', prepend('collection'));
37
+ else if (publisher.className)
38
+ this.listenTo(publisher, 'transition', prepend(publisher.getIdentity()));
39
+ else
40
+ this.listenTo(publisher, 'all', prepend(identity));
41
+ },
42
+ addSubscriber: function(whenBlock){
43
+ this._subscribers = this._subscribers || [];
44
+ if (whenBlock)
45
+ this._subscribers.push(whenBlock);
46
+ },
47
+ transition: function(){
48
+ var args = stormfront.arguments(arguments).prepend('transition').get();
49
+ this.trigger.apply(this, args);
50
+ },
51
+ close: function(){
52
+ this.transition.apply(this, stormfront.arguments(arguments).prepend('closing').get());
53
+ Stormfront.ViewBase.prototype.close.call(this);
54
+ }
55
+ });
@@ -0,0 +1,57 @@
1
+ Stormfront.ViewBase = Backbone.View.extend({
2
+ initialize: function(){
3
+ this.errors = [];
4
+ },
5
+ render: function(){
6
+ try {
7
+ this.$el.html(new Stormfront.Template(this).render());
8
+ }
9
+ catch(e) {
10
+ this.handleException(e);
11
+ }
12
+ return this;
13
+ },
14
+ getViewModel: function(){
15
+ return this.model ? this.model.toJSON ? this.model.toJSON() : this.model : {};
16
+ },
17
+ handleException: function(exception){
18
+ if (exception.innerException)
19
+ console.warn(exception.message, exception.innerException);
20
+ else
21
+ console.warn(exception.message);
22
+
23
+ this.handleError(exception.message);
24
+ },
25
+ handleError: function(message){
26
+ if (_.contains(this.errors, message))
27
+ return;
28
+ this.notifyUser(message);
29
+ },
30
+ notifyUser: function(message){
31
+ this.alert && this.alert.close();
32
+
33
+ this.errors.push(message);
34
+ this.alert = new Stormfront.Alert({
35
+ model: {
36
+ summary: Stormfront.Errors.FEEDBACK,
37
+ details: this.errors.join(', ')
38
+ }
39
+ });
40
+
41
+ this.listenTo(this.alert, 'closing', this.removeNotification);
42
+ this.$el.append(this.alert.render().$el);
43
+ },
44
+ removeNotification: function(){
45
+ this.errors = [];
46
+ this.stopListening(this.alert);
47
+ },
48
+ getIdentity: function(){
49
+ return stormfront.string(this.className).firstWord();
50
+ },
51
+ toString: function(){
52
+ return this.getIdentity();
53
+ },
54
+ close: function(){
55
+ this.remove();
56
+ }
57
+ });
@@ -0,0 +1,19 @@
1
+ Stormfront.Mixin.Dispatch = {
2
+ hasEvents: function(){
3
+ return this.trigger;
4
+ },
5
+ addEvents: function(){
6
+ $.extend(this, Backbone.Events);
7
+ },
8
+ streamlineDispatch: function(event){
9
+ this.dispatch = function(){
10
+ this.trigger('dispatch', event)
11
+ };
12
+ this.dispatch(event);
13
+ },
14
+ dispatch: function(event){
15
+ if (!this.hasEvents())
16
+ this.addEvents();
17
+ this.streamlineDispatch(event);
18
+ }
19
+ };
@@ -0,0 +1,25 @@
1
+ Stormfront.Mixin.Other = {
2
+ unauthorized: function(entity){
3
+ function propagateUnauthorized(source, response, options){
4
+ if (response.status === 403)
5
+ this.trigger('unauthorized', source, response, options);
6
+ }
7
+ entity.on('error', propagateUnauthorized);
8
+ },
9
+ invalid: function(entity){
10
+ function propagateInvalid(source, response, options){
11
+ if (response.status === 400)
12
+ this.trigger('invalid', source, response, options);
13
+ }
14
+ entity.on('error', propagateInvalid);
15
+ },
16
+ empty: function(collection){
17
+ function determineEmpty(collection, response, options){
18
+ if (collection.isEmpty())
19
+ this.trigger('empty', collection, response, options);
20
+ else
21
+ this.trigger('populated', collection, response, options);
22
+ }
23
+ collection.on('sync', determineEmpty);
24
+ }
25
+ };
@@ -0,0 +1,69 @@
1
+ Stormfront.Chaperone = Stormfront.Class.extend({
2
+ initialize: function(){
3
+ this.children = {};
4
+ _.extend(this, Backbone.Events);
5
+ this.throttle('create', 'created', Stormfront.Errors.INITIALIZING);
6
+ this.throttle('render', 'rendered', Stormfront.Errors.RENDERING);
7
+ this.throttle('attach', 'attached', Stormfront.Errors.ATTACHING);
8
+ this.wrap('add', 'added', Stormfront.Errors.ADDING);
9
+ this.wrap('remove', 'removed', Stormfront.Errors.CLOSING);
10
+ },
11
+
12
+ create: function(childClass, options, context){
13
+ return new window[childClass](options.call(context));
14
+ },
15
+ find: function(identity){
16
+ return this.children[identity];
17
+ },
18
+ add: function(child){
19
+ var identity = child.getIdentity();
20
+ this.remove(this.find(identity));
21
+ this.children[identity] = child;
22
+ return child;
23
+ },
24
+ remove: function(child){
25
+ if (child)
26
+ this.children = _.omit(this.children, child.getIdentity());
27
+ return child;
28
+ },
29
+ render: function(child){
30
+ return child.render();
31
+ },
32
+ attach: function(child, location, method){
33
+ if (location.size() === 0)
34
+ throw Stormfront.Errors.NO_LOCATION(child);
35
+ else if (location.size() > 1)
36
+ throw Stormfront.Errors.MULTIPLE_LOCATIONS(child);
37
+
38
+ location[method](child.$el);
39
+ return child;
40
+ },
41
+
42
+ wrap: function(field, event, error){
43
+ var implementation = this[field];
44
+ this[field] = function(){
45
+ try {
46
+ var result = implementation.apply(this, arguments);
47
+ result && this.trigger(event, result);
48
+ } catch (e) {
49
+ this.trigger('error', error(arguments[0].toString()), e);
50
+ }
51
+ }
52
+ },
53
+ throttle: function(field, event, error){
54
+ this.wrap(field, event, error);
55
+ var implementation = this[field];
56
+ this[field] = function(){
57
+ var self = this;
58
+ var capturedArguments = arguments;
59
+ function func(){
60
+ implementation.apply(self, capturedArguments);
61
+ }
62
+ setTimeout(func, 5);
63
+ }
64
+ },
65
+
66
+ close: function(){
67
+ _.each(this.children, this.remove, this);
68
+ }
69
+ });
@@ -0,0 +1,75 @@
1
+ Stormfront.Template = Stormfront.Class.extend({
2
+ initialize: function(view){
3
+ this.view = view;
4
+ },
5
+ render: function(){
6
+ function renderTemplate(view){
7
+ function getTemplate(view){
8
+ var selector = view.getIdentity();
9
+ return $('#' + selector + '_template');
10
+ }
11
+ function compileTemplate(selection){
12
+ return Handlebars.compile(selection.html());
13
+ }
14
+ function expandTemplate(template){
15
+ return template(view.getViewModel());
16
+ }
17
+ return expandTemplate(compileTemplate(getTemplate(view)))
18
+ }
19
+ function findError(view){
20
+ function getTemplate(view){
21
+ if (!view.className)
22
+ throw Stormfront.Errors.NO_SELECTOR();
23
+ var selector = view.getIdentity();
24
+ return $('#' + selector + '_template');
25
+ }
26
+ function compileTemplate(template){
27
+ if (template.size() === 1)
28
+ return template;
29
+ if (template.size() === 0)
30
+ throw Stormfront.Errors.NO_TEMPLATE(view.getIdentity());
31
+
32
+ return Handlebars.compile(template.html());
33
+ }
34
+ function expandTemplate(template, vm){
35
+ try {
36
+ return template(vm);
37
+ }
38
+ catch(e) {
39
+ throw Stormfront.Errors.INCOMPATIBLE_TEMPLATE(view.getIdentity());
40
+ }
41
+ }
42
+ function getViewModel(view){
43
+ try {
44
+ return view.getViewModel();
45
+ }
46
+ catch (e){
47
+ throw Stormfront.Errors.VIEW_MODEL(view.getIdentity());
48
+ }
49
+ }
50
+ try {
51
+ return expandTemplate(compileTemplate(getTemplate(view)), getViewModel(view));
52
+ }
53
+ catch(e) {
54
+ return e;
55
+ }
56
+ }
57
+ try {
58
+ return renderTemplate(this.view);
59
+ }
60
+ catch(e) {
61
+ throw new TemplateError(findError(this.view), e.message);
62
+ }
63
+ }
64
+ });
65
+
66
+ function TemplateError(message, innerException) {
67
+ var error = Error.call(this, message);
68
+ this.name = 'TemplateError';
69
+ this.message = error.message;
70
+ this.stack = error.stack;
71
+ this.innerException = innerException;
72
+ }
73
+
74
+ TemplateError.prototype = Object.create(Error.prototype);
75
+ TemplateError.prototype.constructor = TemplateError;
@@ -0,0 +1,27 @@
1
+ stormfront.arguments = function(args){
2
+ return new Stormfront.Arguments(args);
3
+ };
4
+
5
+ Stormfront.Arguments = Stormfront.Class.extend({
6
+ initialize: function(array){
7
+ if (array)
8
+ this.array = [].slice.call(array);
9
+ else
10
+ this.array = [];
11
+ },
12
+ skip: function(start) {
13
+ this.array = this.array.slice(start);
14
+ return this;
15
+ },
16
+ prepend: function(item){
17
+ this.array.unshift(item);
18
+ return this;
19
+ },
20
+ at: function(index, value){
21
+ this.array[index] = value;
22
+ return this;
23
+ },
24
+ get: function(){
25
+ return this.array;
26
+ }
27
+ });
@@ -0,0 +1,25 @@
1
+ Stormfront.Class = function(options) {
2
+ if (this.initialize)
3
+ this.initialize.apply(this, arguments);
4
+ };
5
+
6
+ Stormfront.Class.extend = function(protoProps, staticProps) {
7
+ var parent = this;
8
+ var child;
9
+
10
+ if (protoProps && _.has(protoProps, 'constructor'))
11
+ child = protoProps.constructor;
12
+ else
13
+ child = function(){ return parent.apply(this, arguments); };
14
+
15
+ _.extend(child, parent, staticProps);
16
+
17
+ var Surrogate = function(){ this.constructor = child; };
18
+ Surrogate.prototype = parent.prototype;
19
+ child.prototype = new Surrogate;
20
+
21
+ if (protoProps) _.extend(child.prototype, protoProps);
22
+ child.__super__ = parent.prototype;
23
+
24
+ return child;
25
+ };
@@ -0,0 +1,23 @@
1
+ stormfront.hash = function(hash){
2
+ return new Stormfront.Hash(hash);
3
+ };
4
+
5
+ Stormfront.Hash = Stormfront.Class.extend({
6
+ initialize: function(hash){
7
+ this.hash = hash;
8
+ },
9
+ findNestedValue: function(keys){
10
+ function retrieve(keys, context){
11
+ if (!context)
12
+ return null;
13
+ if (keys.length === 0)
14
+ return context;
15
+ var current = keys.shift();
16
+ return retrieve(keys, context[current]);
17
+ }
18
+ return retrieve(keys, this.hash);
19
+ },
20
+ subtract: function(values){
21
+ return _.omit(this.hash, _.keys(values));
22
+ }
23
+ });
@@ -0,0 +1,15 @@
1
+ stormfront.number = function(number){
2
+ return new Stormfront.Number(number);
3
+ };
4
+
5
+ Stormfront.Number = Stormfront.Class.extend({
6
+ initialize: function(number){
7
+ this.number = number;
8
+ },
9
+ isWithin: function(range){
10
+ return range[0] < this.number && range[1] > this.number;
11
+ },
12
+ compare: function(value){
13
+ return this.number < value ? -1 : this.number > value ? 1 : 0;
14
+ }
15
+ });
@@ -0,0 +1,31 @@
1
+ Stormfront.Response = Stormfront.Class.extend({
2
+ initialize: function(xhr){
3
+ this.xhr = xhr;
4
+ },
5
+ isActionable: function(){
6
+ return !(this.hasBeenHandled() || this.isUserUnauthorized() || this.hasBeenAborted());
7
+ },
8
+ hasBeenHandled: function(){
9
+ return this.xhr.handled === true;
10
+ },
11
+ markAsHandled: function(){
12
+ this.xhr.handled = true;
13
+ },
14
+ isUserUnauthorized: function(){
15
+ return this.xhr.status === 401;
16
+ },
17
+ hasBeenAborted: function(){
18
+ //TODO: consider xhr.status === 'abort'?
19
+ return this.xhr.readyState != 4;
20
+ },
21
+ getError: function(){
22
+ try {
23
+ var message = JSON.parse(this.xhr.responseText);
24
+ return message.error || message.message;
25
+ }
26
+ catch (e){
27
+ console.warn('Unhandled error', this.xhr, e);
28
+ return Stormfront.Errors.FEEDBACK;
29
+ }
30
+ }
31
+ });